From c58d12245eb01eda3e95557b252ffdb2462fec30 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Fri, 16 Oct 2026 11:05:23 +0200 Subject: [PATCH] UI - Fixing notification settings still trying to access main settings form that caused a crash on save (#4561) --- .../blueprint/settings/__init__.py | 10 +-- changedetectionio/forms.py | 61 +++++++++--------- .../tests/test_settings_notifications_page.py | 62 +++++++++++++++++-- .../translations/cs/LC_MESSAGES/messages.po | 16 ++--- .../translations/de/LC_MESSAGES/messages.po | 16 ++--- .../en_GB/LC_MESSAGES/messages.po | 12 ++-- .../en_US/LC_MESSAGES/messages.po | 12 ++-- .../translations/es/LC_MESSAGES/messages.po | 16 ++--- .../translations/fr/LC_MESSAGES/messages.po | 16 ++--- .../translations/id/LC_MESSAGES/messages.po | 16 ++--- .../translations/it/LC_MESSAGES/messages.po | 16 ++--- .../translations/ja/LC_MESSAGES/messages.po | 16 ++--- .../translations/ko/LC_MESSAGES/messages.po | 16 ++--- changedetectionio/translations/messages.pot | 14 ++--- .../translations/pl/LC_MESSAGES/messages.po | 16 ++--- .../pt_BR/LC_MESSAGES/messages.po | 16 ++--- .../translations/ru/LC_MESSAGES/messages.po | 16 ++--- .../translations/tr/LC_MESSAGES/messages.po | 16 ++--- .../translations/uk/LC_MESSAGES/messages.po | 16 ++--- .../translations/zh/LC_MESSAGES/messages.po | 16 ++--- .../zh_Hant_TW/LC_MESSAGES/messages.po | 16 ++--- changedetectionio/widgets/ternary_boolean.py | 2 +- changedetectionio/widgets/test_custom_text.py | 4 +- 23 files changed, 231 insertions(+), 186 deletions(-) diff --git a/changedetectionio/blueprint/settings/__init__.py b/changedetectionio/blueprint/settings/__init__.py index f6b062187..bff387d5a 100644 --- a/changedetectionio/blueprint/settings/__init__.py +++ b/changedetectionio/blueprint/settings/__init__.py @@ -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) diff --git a/changedetectionio/forms.py b/changedetectionio/forms.py index 69f64a817..6a50c0c7f 100644 --- a/changedetectionio/forms.py +++ b/changedetectionio/forms.py @@ -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) diff --git a/changedetectionio/tests/test_settings_notifications_page.py b/changedetectionio/tests/test_settings_notifications_page.py index bccbbf320..1ad8dd8d1 100644 --- a/changedetectionio/tests/test_settings_notifications_page.py +++ b/changedetectionio/tests/test_settings_notifications_page.py @@ -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 diff --git a/changedetectionio/translations/cs/LC_MESSAGES/messages.po b/changedetectionio/translations/cs/LC_MESSAGES/messages.po index 9037a423b..25cdf6d04 100644 --- a/changedetectionio/translations/cs/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/cs/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/de/LC_MESSAGES/messages.po b/changedetectionio/translations/de/LC_MESSAGES/messages.po index 8f647ab3b..65f3360a5 100644 --- a/changedetectionio/translations/de/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/de/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po b/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po index 6275276db..e2dff179d 100644 --- a/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/en_GB/LC_MESSAGES/messages.po @@ -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 diff --git a/changedetectionio/translations/en_US/LC_MESSAGES/messages.po b/changedetectionio/translations/en_US/LC_MESSAGES/messages.po index f9f6e2943..a933f8f3a 100644 --- a/changedetectionio/translations/en_US/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/en_US/LC_MESSAGES/messages.po @@ -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 diff --git a/changedetectionio/translations/es/LC_MESSAGES/messages.po b/changedetectionio/translations/es/LC_MESSAGES/messages.po index a2552fe6a..f5a6f220f 100644 --- a/changedetectionio/translations/es/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/es/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/fr/LC_MESSAGES/messages.po b/changedetectionio/translations/fr/LC_MESSAGES/messages.po index 236fe30e2..fb802115b 100644 --- a/changedetectionio/translations/fr/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/fr/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/id/LC_MESSAGES/messages.po b/changedetectionio/translations/id/LC_MESSAGES/messages.po index 109ec846e..db0b5673a 100644 --- a/changedetectionio/translations/id/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/id/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/it/LC_MESSAGES/messages.po b/changedetectionio/translations/it/LC_MESSAGES/messages.po index 01eb69176..ceffcea1f 100644 --- a/changedetectionio/translations/it/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/it/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/ja/LC_MESSAGES/messages.po b/changedetectionio/translations/ja/LC_MESSAGES/messages.po index 2b6914457..11037ff34 100644 --- a/changedetectionio/translations/ja/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/ja/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/ko/LC_MESSAGES/messages.po b/changedetectionio/translations/ko/LC_MESSAGES/messages.po index 8244be46d..41b846e79 100644 --- a/changedetectionio/translations/ko/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/ko/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/messages.pot b/changedetectionio/translations/messages.pot index 6c17904f8..443f5ff5c 100644 --- a/changedetectionio/translations/messages.pot +++ b/changedetectionio/translations/messages.pot @@ -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 \n" "Language-Team: LANGUAGE \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 diff --git a/changedetectionio/translations/pl/LC_MESSAGES/messages.po b/changedetectionio/translations/pl/LC_MESSAGES/messages.po index 30a06e8dd..0c02a513d 100644 --- a/changedetectionio/translations/pl/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/pl/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po b/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po index eacb016c5..b0a83f4a4 100644 --- a/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/pt_BR/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/ru/LC_MESSAGES/messages.po b/changedetectionio/translations/ru/LC_MESSAGES/messages.po index e907d6851..1cfd28dfb 100644 --- a/changedetectionio/translations/ru/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/ru/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/tr/LC_MESSAGES/messages.po b/changedetectionio/translations/tr/LC_MESSAGES/messages.po index c07b36b10..8c1034462 100644 --- a/changedetectionio/translations/tr/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/tr/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/uk/LC_MESSAGES/messages.po b/changedetectionio/translations/uk/LC_MESSAGES/messages.po index a326ec5fd..75014e5f5 100644 --- a/changedetectionio/translations/uk/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/uk/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/zh/LC_MESSAGES/messages.po b/changedetectionio/translations/zh/LC_MESSAGES/messages.po index 13ac2ca36..a29d71d40 100644 --- a/changedetectionio/translations/zh/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/zh/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po b/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po index ad5f38bd7..72752d566 100644 --- a/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po +++ b/changedetectionio/translations/zh_Hant_TW/LC_MESSAGES/messages.po @@ -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?" diff --git a/changedetectionio/widgets/ternary_boolean.py b/changedetectionio/widgets/ternary_boolean.py index 54bb49696..cbe32bd4c 100644 --- a/changedetectionio/widgets/ternary_boolean.py +++ b/changedetectionio/widgets/ternary_boolean.py @@ -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() diff --git a/changedetectionio/widgets/test_custom_text.py b/changedetectionio/widgets/test_custom_text.py index 5dad06ae8..887173502 100644 --- a/changedetectionio/widgets/test_custom_text.py +++ b/changedetectionio/widgets/test_custom_text.py @@ -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 ---")