mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-10-19 02:10:48 +00:00
UI - Fixing notification settings still trying to access main settings form that caused a crash on save (#4561)
This commit is contained in:
1 parent
c09bb66e33
commit
c58d12245e
23 files changed
+231
-186
No files matched your search
@@ -66,9 +66,6 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
extra_notification_tokens=datastore.get_unique_notification_tokens_available()
|
||||
)
|
||||
|
||||
# Remove the last option 'System default'
|
||||
form.application.form.notification_format.choices.pop()
|
||||
|
||||
if datastore.proxy_list is None:
|
||||
# @todo - Couldn't get setattr() etc dynamic addition working, so remove it instead
|
||||
del form.requests.form.proxy
|
||||
@@ -95,10 +92,9 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
||||
if 'password' in app_update and not app_update['password']:
|
||||
del (app_update['password'])
|
||||
|
||||
# Notification fields live on the dedicated /settings/notifications page now.
|
||||
# The application sub-form still defines them (inherited from commonSettingsForm),
|
||||
# so an unrelated save here would clobber the stored values with empty WTForms
|
||||
# defaults. Drop them from the merge to leave the notifications config alone.
|
||||
# Notification fields live on the dedicated /settings/notifications page and are
|
||||
# no longer part of this form (#4554). Defensive - never let a save here clobber
|
||||
# the stored notification config should one get re-added to the application sub-form.
|
||||
for nf in ('notification_urls', 'notification_title', 'notification_body',
|
||||
'notification_format', 'base_url'):
|
||||
app_update.pop(nf, None)
|
||||
|
||||
+29
-32
@@ -51,6 +51,7 @@ from changedetectionio.browser_steps.browser_steps import browser_step_ui_config
|
||||
from changedetectionio import html_tools, content_fetchers
|
||||
|
||||
from changedetectionio.notification import (
|
||||
USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH,
|
||||
valid_notification_formats,
|
||||
)
|
||||
|
||||
@@ -860,21 +861,37 @@ class quickWatchForm(Form):
|
||||
edit_and_watch_submit_button = SubmitField(_l('Edit > Watch'), render_kw={"class": "pure-button pure-button-primary"})
|
||||
|
||||
|
||||
# Apprise notification fields, common to a single watch and the global
|
||||
# /settings/notifications page (NOT the main /settings page, which no longer
|
||||
# renders them - a hidden field there can fail validation with no visible error, see #4554)
|
||||
class commonNotificationSettingsForm(Form):
|
||||
def __init__(self, formdata=None, obj=None, prefix="", data=None, meta=None, **kwargs):
|
||||
super().__init__(formdata, obj, prefix, data, meta, **kwargs)
|
||||
extra = kwargs.get('extra_notification_tokens', {})
|
||||
self.notification_body.extra_notification_tokens = extra
|
||||
self.notification_title.extra_notification_tokens = extra
|
||||
self.notification_urls.extra_notification_tokens = extra
|
||||
|
||||
notification_urls = StringListField(_l('Notification URL List'),
|
||||
validators=[validators.Optional(), ValidateAppRiseServers(), ValidateJinja2Template()])
|
||||
notification_title = StringField(_l('Notification Title'),
|
||||
default='ChangeDetection.io Notification - {{ watch_url }}',
|
||||
validators=[validators.Optional(), ValidateJinja2Template()])
|
||||
notification_body = TextAreaField(_l('Notification Body'),
|
||||
default='{{ watch_url }} had a change.',
|
||||
validators=[validators.Optional(), ValidateJinja2Template()])
|
||||
notification_format = SelectField(_l('Notification format'),
|
||||
choices=list(valid_notification_formats.items()))
|
||||
|
||||
|
||||
# Common to a single watch and the global settings
|
||||
class commonSettingsForm(Form):
|
||||
from . import processors
|
||||
|
||||
def __init__(self, formdata=None, obj=None, prefix="", data=None, meta=None, **kwargs):
|
||||
super().__init__(formdata, obj, prefix, data, meta, **kwargs)
|
||||
self.notification_body.extra_notification_tokens = kwargs.get('extra_notification_tokens', {})
|
||||
self.notification_title.extra_notification_tokens = kwargs.get('extra_notification_tokens', {})
|
||||
self.notification_urls.extra_notification_tokens = kwargs.get('extra_notification_tokens', {})
|
||||
|
||||
fetch_backend = RadioField(_l('Fetch Method'), choices=content_fetchers.available_fetchers(), validators=[ValidateContentFetcherIsReady()])
|
||||
notification_body = TextAreaField(_l('Notification Body'), default='{{ watch_url }} had a change.', validators=[validators.Optional(), ValidateJinja2Template()])
|
||||
notification_format = SelectField(_l('Notification format'), choices=list(valid_notification_formats.items()))
|
||||
notification_title = StringField(_l('Notification Title'), default='ChangeDetection.io Notification - {{ watch_url }}', validators=[validators.Optional(), ValidateJinja2Template()])
|
||||
notification_urls = StringListField(_l('Notification URL List'), validators=[validators.Optional(), ValidateAppRiseServers(), ValidateJinja2Template()])
|
||||
processor = RadioField( label=_l("Processor - What do you want to achieve?"), choices=lambda: processors.available_processors(), default=processors.get_default_processor)
|
||||
scheduler_timezone_default = StringField(_l("Default timezone for watch check scheduler"), render_kw={"list": "timezones"}, validators=[validateTimeZoneName()])
|
||||
webdriver_delay = IntegerField(_l('Wait seconds before extracting text'), validators=[validators.Optional(), validators.NumberRange(min=1, message=_l("Should contain one or more seconds"))])
|
||||
@@ -900,24 +917,11 @@ class commonSettingsForm(Form):
|
||||
#
|
||||
# Named for the backend (apprise) on purpose: future backends (simple_email,
|
||||
# webhook, etc.) will land as their own forms next to this one.
|
||||
class globalSettingsAppriseNotificationForm(Form):
|
||||
def __init__(self, formdata=None, obj=None, prefix="", data=None, meta=None, **kwargs):
|
||||
super().__init__(formdata, obj, prefix, data, meta, **kwargs)
|
||||
extra = kwargs.get('extra_notification_tokens', {})
|
||||
self.notification_body.extra_notification_tokens = extra
|
||||
self.notification_title.extra_notification_tokens = extra
|
||||
self.notification_urls.extra_notification_tokens = extra
|
||||
|
||||
notification_urls = StringListField(_l('Notification URL List'),
|
||||
validators=[validators.Optional(), ValidateAppRiseServers(), ValidateJinja2Template()])
|
||||
notification_title = StringField(_l('Notification Title'),
|
||||
default='ChangeDetection.io Notification - {{ watch_url }}',
|
||||
validators=[validators.Optional(), ValidateJinja2Template()])
|
||||
notification_body = TextAreaField(_l('Notification Body'),
|
||||
default='{{ watch_url }} had a change.',
|
||||
validators=[validators.Optional(), ValidateJinja2Template()])
|
||||
class globalSettingsAppriseNotificationForm(commonNotificationSettingsForm):
|
||||
# 'System default' only makes sense per-watch (it means "use this global value")
|
||||
notification_format = SelectField(_l('Notification format'),
|
||||
choices=list(valid_notification_formats.items()))
|
||||
choices=[(k, v) for k, v in valid_notification_formats.items()
|
||||
if k != USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH])
|
||||
base_url = StringField(_l('Notification base URL override'),
|
||||
validators=[validators.Optional()],
|
||||
render_kw={"placeholder": os.getenv('BASE_URL', _l('Not set'))})
|
||||
@@ -941,7 +945,7 @@ class SingleBrowserStep(Form):
|
||||
# remove_button = SubmitField(_l('-'), render_kw={"type": "button", "class": "pure-button pure-button-primary", 'title': 'Remove'})
|
||||
# add_button = SubmitField(_l('+'), render_kw={"type": "button", "class": "pure-button pure-button-primary", 'title': 'Add new step after'})
|
||||
|
||||
class processor_text_json_diff_form(commonSettingsForm):
|
||||
class processor_text_json_diff_form(commonSettingsForm, commonNotificationSettingsForm):
|
||||
|
||||
url = StringField(_l('Web Page URL'), validators=[validateURL()])
|
||||
link_to_open = StringField(_l('Open Link Override'), validators=[validators.Optional(), validateURL()], default='')
|
||||
@@ -1192,10 +1196,6 @@ class globalSettingsApplicationUIForm(Form):
|
||||
class globalSettingsApplicationForm(commonSettingsForm):
|
||||
|
||||
api_access_token_enabled = BooleanField(_l('API access token security check enabled'), default=True, validators=[validators.Optional()])
|
||||
base_url = StringField(_l('Notification base URL override'),
|
||||
validators=[validators.Optional()],
|
||||
render_kw={"placeholder": os.getenv('BASE_URL', _l('Not set'))}
|
||||
)
|
||||
empty_pages_are_a_change = BooleanField(_l('Treat empty pages as a change?'), default=False)
|
||||
fetch_backend = RadioField(_l('Fetch Method'), default="html_requests", choices=content_fetchers.available_fetchers(), validators=[ValidateContentFetcherIsReady()])
|
||||
global_ignore_text = StringListField(_l('Ignore Text'), [ValidateListRegex()])
|
||||
@@ -1417,9 +1417,6 @@ class globalSettingsForm(Form):
|
||||
# datastore.data['settings']['requests']..
|
||||
def __init__(self, formdata=None, obj=None, prefix="", data=None, meta=None, **kwargs):
|
||||
super().__init__(formdata, obj, prefix, data, meta, **kwargs)
|
||||
self.application.notification_body.extra_notification_tokens = kwargs.get('extra_notification_tokens', {})
|
||||
self.application.notification_title.extra_notification_tokens = kwargs.get('extra_notification_tokens', {})
|
||||
self.application.notification_urls.extra_notification_tokens = kwargs.get('extra_notification_tokens', {})
|
||||
|
||||
requests = FormField(globalSettingsRequestForm)
|
||||
application = FormField(globalSettingsApplicationForm)
|
||||
|
||||
@@ -118,10 +118,8 @@ def test_main_settings_save_does_not_clobber_notification_config(
|
||||
"""
|
||||
Regression — the main /settings page and the new /settings/notifications page
|
||||
write to the SAME dict (datastore.data['settings']['application']). The main
|
||||
page's application sub-form still inherits the notification_* fields from
|
||||
commonSettingsForm, so a naive `app.update(form.data['application'])` here
|
||||
would wipe notification_urls / title / body / format / base_url with empty
|
||||
WTForms defaults whenever the user saves the main settings page.
|
||||
page must never write notification_urls / title / body / format / base_url,
|
||||
otherwise saving it would wipe them with empty WTForms defaults.
|
||||
|
||||
This test seeds a real notification config, then POSTs the main settings
|
||||
page WITHOUT touching the notification fields, and asserts every notification
|
||||
@@ -139,7 +137,6 @@ def test_main_settings_save_does_not_clobber_notification_config(
|
||||
'application-fetch_backend': 'html_requests',
|
||||
'application-rss_diff_length': '5',
|
||||
'application-filter_failure_notification_threshold_attempts': '0',
|
||||
'application-notification_format': 'html', # required choice, no Optional()
|
||||
'requests-time_between_check-days': '0',
|
||||
'requests-time_between_check-hours': '0',
|
||||
'requests-time_between_check-minutes': '5',
|
||||
@@ -225,3 +222,58 @@ def test_notifications_page_save_does_not_clobber_other_settings(
|
||||
# And the notification field we DID submit was actually written.
|
||||
assert ds.data['settings']['application'].get('notification_urls') == \
|
||||
['json://example.invalid/only-this']
|
||||
|
||||
|
||||
def test_main_settings_save_with_global_system_default_format(
|
||||
client, live_server, measure_memory_usage, datastore_path):
|
||||
"""
|
||||
Regression for #4554 - a global notification_format of 'System default' made
|
||||
every save of the main /settings page fail with "An error occurred" and no
|
||||
visible field error, because the main form still carried a hidden
|
||||
notification_format SelectField whose choices excluded that value.
|
||||
"""
|
||||
from changedetectionio.model import USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH
|
||||
|
||||
ds = client.application.config.get('DATASTORE')
|
||||
ds.data['settings']['application']['notification_format'] = USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH
|
||||
|
||||
res = client.post(
|
||||
url_for('settings.settings_page'),
|
||||
data={
|
||||
'application-pager_size': '50',
|
||||
'application-fetch_backend': 'html_requests',
|
||||
'application-rss_diff_length': '5',
|
||||
'application-filter_failure_notification_threshold_attempts': '0',
|
||||
'requests-time_between_check-days': '0',
|
||||
'requests-time_between_check-hours': '0',
|
||||
'requests-time_between_check-minutes': '5',
|
||||
'requests-time_between_check-seconds': '0',
|
||||
'requests-time_between_check-weeks': '0',
|
||||
'requests-jitter_seconds': '0',
|
||||
'requests-workers': '10',
|
||||
'requests-timeout': '60',
|
||||
},
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert res.status_code == 200
|
||||
assert b'Settings updated.' in res.data
|
||||
assert b'An error occurred' not in res.data
|
||||
|
||||
|
||||
def test_notifications_page_rejects_system_default_format(
|
||||
client, live_server, measure_memory_usage, datastore_path):
|
||||
"""'System default' is a per-watch choice meaning "use the global value",
|
||||
it must not be offered or accepted as the global value itself (#4554)."""
|
||||
from changedetectionio.model import USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH
|
||||
|
||||
res = client.get(url_for('settings.notifications.apprise'))
|
||||
assert f'value="{USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH}"'.encode() not in res.data
|
||||
|
||||
res = client.post(
|
||||
url_for('settings.notifications.apprise'),
|
||||
data={'notification_format': USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH},
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert b'Settings updated.' not in res.data
|
||||
ds = client.application.config.get('DATASTORE')
|
||||
assert ds.data['settings']['application'].get('notification_format') != USE_SYSTEM_DEFAULT_NOTIFICATION_FORMAT_FOR_WATCH
|
||||
@@ -3229,8 +3229,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Upravit > Monitorovat"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Nastavte metodu načítání"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Seznam URL oznámení"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Název oznámení"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3241,12 +3245,8 @@ msgid "Notification format"
|
||||
msgstr "Formát oznámení"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Název oznámení"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Seznam URL oznámení"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Nastavte metodu načítání"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3278,8 +3278,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Fetch Methode"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Liste der Benachrichtigungs-URLs"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Benachrichtigungstitel"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3290,12 +3294,8 @@ msgid "Notification format"
|
||||
msgstr "Benachrichtigungsformat"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Benachrichtigungstitel"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Liste der Benachrichtigungs-URLs"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Fetch Methode"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3221,7 +3221,11 @@ msgid "Edit > Watch"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgid "Notification URL List"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3233,11 +3237,7 @@ msgid "Notification format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgid "Fetch Method"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
|
||||
@@ -3221,7 +3221,11 @@ msgid "Edit > Watch"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgid "Notification URL List"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3233,11 +3237,7 @@ msgid "Notification format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgid "Fetch Method"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
|
||||
@@ -3304,8 +3304,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Editar > Ver"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Método de recuperación"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista de URL de notificación"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Título de la notificación"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3316,12 +3320,8 @@ msgid "Notification format"
|
||||
msgstr "Formato de notificación"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Título de la notificación"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista de URL de notificación"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Método de recuperación"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3234,8 +3234,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Modifier > Surveiller"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Définir la méthode de récupération"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Liste d'URL de notification"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Titre de notification"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3246,12 +3250,8 @@ msgid "Notification format"
|
||||
msgstr "Format de notification"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Titre de notification"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Liste d'URL de notification"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Définir la méthode de récupération"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3334,8 +3334,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Edit > Monitor"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Metode Pengambilan"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Daftar URL Notifikasi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Judul Notifikasi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3346,12 +3350,8 @@ msgid "Notification format"
|
||||
msgstr "Format notifikasi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Judul Notifikasi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Daftar URL Notifikasi"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Metode Pengambilan"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3223,8 +3223,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Modifica > Monitora"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Metodo di recupero"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista URL notifiche"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Titolo notifica"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3235,12 +3239,8 @@ msgid "Notification format"
|
||||
msgstr "Formato notifica"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Titolo notifica"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista URL notifiche"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Metodo di recupero"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3240,8 +3240,12 @@ msgid "Edit > Watch"
|
||||
msgstr "編集 > ウォッチ"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "取得方式"
|
||||
msgid "Notification URL List"
|
||||
msgstr "通知URLリスト"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "通知タイトル"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3252,12 +3256,8 @@ msgid "Notification format"
|
||||
msgstr "通知フォーマット"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "通知タイトル"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "通知URLリスト"
|
||||
msgid "Fetch Method"
|
||||
msgstr "取得方式"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3231,8 +3231,12 @@ msgid "Edit > Watch"
|
||||
msgstr "편집 > 모니터링"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "가져오기 방식"
|
||||
msgid "Notification URL List"
|
||||
msgstr "알림 URL 목록"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "알림 제목"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3243,12 +3247,8 @@ msgid "Notification format"
|
||||
msgstr "알림 형식"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "알림 제목"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "알림 URL 목록"
|
||||
msgid "Fetch Method"
|
||||
msgstr "가져오기 방식"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: changedetection.io 0.60.9\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-10-13 11:16+0200\n"
|
||||
"POT-Creation-Date: 2026-10-16 10:41+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -3220,7 +3220,11 @@ msgid "Edit > Watch"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgid "Notification URL List"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
@@ -3232,11 +3236,7 @@ msgid "Notification format"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgid "Fetch Method"
|
||||
msgstr ""
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
|
||||
@@ -3380,8 +3380,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Edycja > Obserwuj"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Metoda pobierania"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista adresów URL powiadomień"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Tytuł powiadomienia"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3392,12 +3396,8 @@ msgid "Notification format"
|
||||
msgstr "Format powiadomienia"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Tytuł powiadomienia"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista adresów URL powiadomień"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Metoda pobierania"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3271,8 +3271,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Editar > Monitoramento"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Método de Busca"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista de URLs de Notificação"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Título da Notificação"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3283,12 +3287,8 @@ msgid "Notification format"
|
||||
msgstr "Formato da notificação"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Título da Notificação"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Lista de URLs de Notificação"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Método de Busca"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3339,8 +3339,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Редактировать > Смотреть"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Метод выборки"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Список URL-адресов уведомлений"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Название уведомления"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3351,12 +3355,8 @@ msgid "Notification format"
|
||||
msgstr "Формат уведомления"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Название уведомления"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Список URL-адресов уведомлений"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Метод выборки"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3274,8 +3274,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Düzenle > İzleyici"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Getirme Yöntemi"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Bildirim URL Listesi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Bildirim Başlığı"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3286,12 +3290,8 @@ msgid "Notification format"
|
||||
msgstr "Bildirim formatı"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Bildirim Başlığı"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Bildirim URL Listesi"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Getirme Yöntemi"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3253,8 +3253,12 @@ msgid "Edit > Watch"
|
||||
msgstr "Редагувати > Завдання"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "Метод завантаження"
|
||||
msgid "Notification URL List"
|
||||
msgstr "Список URL сповіщень"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Заголовок сповіщення"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3265,12 +3269,8 @@ msgid "Notification format"
|
||||
msgstr "Формат сповіщення"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "Заголовок сповіщення"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "Список URL сповіщень"
|
||||
msgid "Fetch Method"
|
||||
msgstr "Метод завантаження"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3227,8 +3227,12 @@ msgid "Edit > Watch"
|
||||
msgstr "编辑 > 监控项"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "抓取方式"
|
||||
msgid "Notification URL List"
|
||||
msgstr "通知 URL 列表"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "通知标题"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3239,12 +3243,8 @@ msgid "Notification format"
|
||||
msgstr "通知格式"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "通知标题"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "通知 URL 列表"
|
||||
msgid "Fetch Method"
|
||||
msgstr "抓取方式"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -3232,8 +3232,12 @@ msgid "Edit > Watch"
|
||||
msgstr "編輯 > 監測任務"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Fetch Method"
|
||||
msgstr "抓取方式"
|
||||
msgid "Notification URL List"
|
||||
msgstr "通知 URL 列表"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "通知標題"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Body"
|
||||
@@ -3244,12 +3248,8 @@ msgid "Notification format"
|
||||
msgstr "通知格式"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification Title"
|
||||
msgstr "通知標題"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Notification URL List"
|
||||
msgstr "通知 URL 列表"
|
||||
msgid "Fetch Method"
|
||||
msgstr "抓取方式"
|
||||
|
||||
#: changedetectionio/forms.py
|
||||
msgid "Processor - What do you want to achieve?"
|
||||
|
||||
@@ -59,7 +59,7 @@ class TernaryNoneBooleanField(Field):
|
||||
Custom text can be provided for each option:
|
||||
- yes_text: Text for True option (default: "Yes")
|
||||
- no_text: Text for False option (default: "No")
|
||||
- none_text: Text for None option (default: "Default")
|
||||
- none_text: Text for None option (default: "Main settings")
|
||||
"""
|
||||
widget = TernaryNoneBooleanWidget()
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ def test_custom_text():
|
||||
default_html = default_field.widget(default_field)
|
||||
print(f"Contains 'Yes': {'Yes' in default_html}")
|
||||
print(f"Contains 'No': {'No' in default_html}")
|
||||
print(f"Contains 'Default': {'Default' in default_html}")
|
||||
assert 'Yes' in default_html and 'No' in default_html and 'Default' in default_html
|
||||
print(f"Contains 'Main settings': {'Main settings' in default_html}")
|
||||
assert 'Yes' in default_html and 'No' in default_html and 'Main settings' in default_html
|
||||
|
||||
# Test custom text field
|
||||
print("\n--- Custom Text Field with Emojis ---")
|
||||
|
||||
Reference in new issue
Block a user