From 4ac822fd7fbb5bd35b3e0d8695e4942fbc4d202a Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Sat, 30 May 2026 14:43:50 +0200 Subject: [PATCH] [mod] typification of the preference settings no functional change / except the missing online doc which is now available:: $ make docs.live $ xdg-open "http://127.0.0.1:8000/admin/settings/settings_preferences.html" Signed-off-by: Markus Heiser --- client/simple/src/js/toolkit.ts | 1 - docs/admin/settings/index.rst | 1 + docs/admin/settings/settings_preferences.rst | 8 ++ searx/__init__.py | 13 ++- searx/_settings.py | 42 +++++++ searx/preferences.py | 115 +++++++++---------- searx/settings.yml | 9 +- searx/settings_defaults.py | 6 +- searx/templates/simple/preferences.html | 2 +- searx/webadapter.py | 12 +- searx/webapp.py | 3 +- 11 files changed, 124 insertions(+), 88 deletions(-) create mode 100644 docs/admin/settings/settings_preferences.rst create mode 100644 searx/_settings.py diff --git a/client/simple/src/js/toolkit.ts b/client/simple/src/js/toolkit.ts index c007e3251..6157f31ad 100644 --- a/client/simple/src/js/toolkit.ts +++ b/client/simple/src/js/toolkit.ts @@ -5,7 +5,6 @@ import type { KeyBindingLayout } from "./main/keyboard.ts"; // synced with searx/webapp.py get_client_settings type Settings = { plugins?: string[]; - advanced_search?: boolean; autocomplete?: string; autocomplete_min?: number; doi_resolver?: string; diff --git a/docs/admin/settings/index.rst b/docs/admin/settings/index.rst index b9d0408ea..c7812c9f6 100644 --- a/docs/admin/settings/index.rst +++ b/docs/admin/settings/index.rst @@ -19,6 +19,7 @@ Settings settings_search settings_server settings_ui + settings_preferences settings_redis settings_valkey settings_outgoing diff --git a/docs/admin/settings/settings_preferences.rst b/docs/admin/settings/settings_preferences.rst new file mode 100644 index 000000000..f98b16003 --- /dev/null +++ b/docs/admin/settings/settings_preferences.rst @@ -0,0 +1,8 @@ +.. _settings preferences: + +================ +``preferences:`` +================ + +.. autoclass:: searx._settings.SettingsPref + :members: diff --git a/searx/__init__.py b/searx/__init__.py index 74c578c1d..900019d68 100644 --- a/searx/__init__.py +++ b/searx/__init__.py @@ -10,6 +10,7 @@ from os.path import dirname, abspath import logging import msgspec +from ._settings import SettingsPref # Debug LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s' @@ -44,15 +45,15 @@ def init_settings(): cfg = cfg or {} apply_schema(cfg, SCHEMA, []) - if cfg['server']['public_instance']: - cfg['server']['image_proxy'] = True - lock = cfg.setdefault('preferences', {}).setdefault('lock', []) - if 'image_proxy' not in lock: - lock.append('image_proxy') - settings.clear() settings.update(cfg) + if get_setting("server.public_instance"): + # enable image proxy for public instances #6125 + settings["server"]["image_proxy"] = True + pref: SettingsPref = get_setting("preferences") + pref.lock.add("image_proxy") + sxng_debug = get_setting("general.debug") if sxng_debug: _logging_config_debug() diff --git a/searx/_settings.py b/searx/_settings.py new file mode 100644 index 000000000..0db62474c --- /dev/null +++ b/searx/_settings.py @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Implementation of the :py:obj:`preference ` settings.""" +# pylint: disable = too-few-public-methods + +import typing as t +import msgspec + + +class SettingsPref(msgspec.Struct, kw_only=True, forbid_unknown_fields=True): + """Options for configuring the preferences + + .. code:: yaml + + preferences: + lock: + - favicon_resolver + - image_proxy + - method + # ... + + """ + + lock: set[ + t.Literal[ + "categories", + "language", + "locale", + "autocomplete", + "favicon_resolver", + "image_proxy", + "method", + "safesearch", + "theme", + "results_on_new_tab", + "doi_resolver", + "simple_style", + "center_alignment", + "query_in_title", + "search_on_category_select", + ] + ] = set() + """Lock arbitrary settings on the preferences page.""" diff --git a/searx/preferences.py b/searx/preferences.py index 1bfa9d82b..edc80f8bd 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -17,13 +17,14 @@ import babel.core import searx.plugins -from searx import settings, autocomplete, favicons +from searx import get_setting, settings, autocomplete, favicons from searx.enginelib import Engine from searx.engines import DEFAULT_CATEGORY from searx.extended_types import SXNG_Request from searx.locales import LOCALE_NAMES from searx.webutils import VALID_LANGUAGE_CODE +from ._settings import SettingsPref COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years DOI_RESOLVERS = list(settings['doi_resolvers']) @@ -386,6 +387,7 @@ class ClientPref: return cls(locale=locale) +@t.final class Preferences: """Validates and saves preferences to cookies""" @@ -400,95 +402,91 @@ class Preferences: super().__init__() + self.cfg: SettingsPref = get_setting("preferences") + self.key_value_settings: dict[str, Setting] = { - # fmt: off 'categories': MultipleChoiceSetting( - ['general'], - locked=is_locked('categories'), - choices=categories + ['none'] + ["general"], + locked="categories" in self.cfg.lock, + choices=categories + ["none"], ), 'language': SearchLanguageSetting( - settings['search']['default_lang'], - locked=is_locked('language'), - choices=settings['search']['languages'] + [''] + get_setting("search.default_lang"), + locked="language" in self.cfg.lock, + choices=get_setting("search.languages") + [""], ), 'locale': EnumStringSetting( - settings['ui']['default_locale'], - locked=is_locked('locale'), - choices=list(LOCALE_NAMES.keys()) + [''] + get_setting("ui.default_locale"), + locked="locale" in self.cfg.lock, + choices=list(LOCALE_NAMES.keys()) + [""], ), 'autocomplete': EnumStringSetting( - settings['search']['autocomplete'], - locked=is_locked('autocomplete'), - choices=list(autocomplete.backends.keys()) + [''] + get_setting("search.autocomplete"), + locked="autocomplete" in self.cfg.lock, + choices=list(autocomplete.backends.keys()) + [""], ), 'favicon_resolver': EnumStringSetting( - settings['search']['favicon_resolver'], - locked=is_locked('favicon_resolver'), - choices=list(favicons.proxy.CFG.resolver_map.keys()) + [''] + get_setting("search.favicon_resolver"), + locked="favicon_resolver" in self.cfg.lock, + choices=list(favicons.proxy.CFG.resolver_map.keys()) + [''], ), 'image_proxy': BooleanSetting( - settings['server']['image_proxy'], - locked=is_locked('image_proxy') + get_setting("server.image_proxy"), + locked="image_proxy" in self.cfg.lock, ), 'method': EnumStringSetting( - settings['server']['method'], - locked=is_locked('method'), - choices=('GET', 'POST') + get_setting("server.method"), + locked="method" in self.cfg.lock, + choices=("GET", "POST"), ), 'safesearch': MapSetting( - settings['search']['safe_search'], - locked=is_locked('safesearch'), + get_setting("search.safe_search"), + locked="safesearch" in self.cfg.lock, map={ - '0': 0, - '1': 1, - '2': 2 - } + "0": 0, + "1": 1, + "2": 2, + }, ), 'theme': EnumStringSetting( - settings['ui']['default_theme'], - locked=is_locked('theme'), - choices=themes + get_setting("ui.default_theme"), + locked="theme" in self.cfg.lock, + choices=themes, ), 'results_on_new_tab': BooleanSetting( - settings['ui']['results_on_new_tab'], - locked=is_locked('results_on_new_tab') + get_setting("ui.results_on_new_tab"), + locked="results_on_new_tab" in self.cfg.lock, ), 'doi_resolver': MultipleChoiceSetting( - [settings['default_doi_resolver'], ], - locked=is_locked('doi_resolver'), - choices=DOI_RESOLVERS + [get_setting("default_doi_resolver")], + locked="doi_resolver" in self.cfg.lock, + choices=DOI_RESOLVERS, ), 'simple_style': EnumStringSetting( - settings['ui']['theme_args']['simple_style'], - locked=is_locked('simple_style'), - choices=['', 'auto', 'light', 'dark', 'black'] + get_setting("ui.theme_args.simple_style"), + locked="simple_style" in self.cfg.lock, + choices=["", "auto", "light", "dark", "black"], ), 'center_alignment': BooleanSetting( - settings['ui']['center_alignment'], - locked=is_locked('center_alignment') - ), - 'advanced_search': BooleanSetting( - settings['ui']['advanced_search'], - locked=is_locked('advanced_search') + get_setting("ui.center_alignment"), + locked="center_alignment" in self.cfg.lock, ), 'query_in_title': BooleanSetting( - settings['ui']['query_in_title'], - locked=is_locked('query_in_title') + get_setting("ui.query_in_title"), + locked="query_in_title" in self.cfg.lock, ), 'search_on_category_select': BooleanSetting( - settings['ui']['search_on_category_select'], - locked=is_locked('search_on_category_select') + get_setting("ui.search_on_category_select"), + locked="search_on_category_select" in self.cfg.lock, ), 'hotkeys': EnumStringSetting( - settings['ui']['hotkeys'], - choices=['default', 'vim'] + get_setting("ui.hotkeys"), + choices=["default", "vim"], ), 'url_formatting': EnumStringSetting( - settings['ui']['url_formatting'], - choices=['pretty', 'full', 'host'] + get_setting("ui.url_formatting"), + choices=["pretty", "full", "host"], ), - # fmt: on } self.engines = EnginesSetting('engines', engines=engines.values()) @@ -597,12 +595,3 @@ class Preferences: break return valid - - -def is_locked(setting_name: str): - """Checks if a given setting name is locked by settings.yml""" - if 'preferences' not in settings: - return False - if 'lock' not in settings['preferences']: - return False - return setting_name in settings['preferences']['lock'] diff --git a/searx/settings.yml b/searx/settings.yml index 7a98b0594..4b544dcfe 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -154,14 +154,13 @@ ui: # URL formatting: pretty, full or host url_formatting: pretty -# Lock arbitrary settings on the preferences page. -# -# preferences: -# lock: +preferences: + # Lock arbitrary settings on the preferences page. + lock: [] # - categories # - language # - autocomplete -# - favicon +# - favicon_resolver # - safesearch # - method # - doi_resolver diff --git a/searx/settings_defaults.py b/searx/settings_defaults.py index 2b82dce14..40c68a029 100644 --- a/searx/settings_defaults.py +++ b/searx/settings_defaults.py @@ -15,6 +15,7 @@ import msgspec from typing_extensions import override from .brand import SettingsBrand from .sxng_locales import sxng_locales +from ._settings import SettingsPref searx_dir = abspath(dirname(__file__)) @@ -236,16 +237,13 @@ SCHEMA: dict[str, t.Any] = { }, 'center_alignment': SettingsValue(bool, False), 'results_on_new_tab': SettingsValue(bool, False), - 'advanced_search': SettingsValue(bool, False), 'query_in_title': SettingsValue(bool, False), 'cache_url': SettingsValue(str, 'https://web.archive.org/web/'), 'search_on_category_select': SettingsValue(bool, True), 'hotkeys': SettingsValue(('default', 'vim'), 'default'), 'url_formatting': SettingsValue(('pretty', 'full', 'host'), 'pretty'), }, - 'preferences': { - 'lock': SettingsValue(list, []), - }, + "preferences": SettingsPref, 'outgoing': { 'useragent_suffix': SettingsValue(str, ''), 'request_timeout': SettingsValue(numbers.Real, 3.0), diff --git a/searx/templates/simple/preferences.html b/searx/templates/simple/preferences.html index a496bd3ae..ed5a6504a 100644 --- a/searx/templates/simple/preferences.html +++ b/searx/templates/simple/preferences.html @@ -180,7 +180,7 @@ {%- if 'autocomplete' not in locked_preferences -%} {%- include 'simple/preferences/autocomplete.html' -%} {%- endif -%} - {%- if 'favicon' not in locked_preferences -%} + {%- if 'favicon_resolver' not in locked_preferences -%} {%- include 'simple/preferences/favicon.html' -%} {%- endif -%} {% if 'safesearch' not in locked_preferences %} diff --git a/searx/webadapter.py b/searx/webadapter.py index f1924dd4c..e2e0f4521 100644 --- a/searx/webadapter.py +++ b/searx/webadapter.py @@ -8,7 +8,7 @@ from searx.webutils import VALID_LANGUAGE_CODE from searx.query import RawTextQuery from searx.engines import categories, engines from searx.search.models import SearchQuery, EngineRef -from searx.preferences import Preferences, is_locked +from searx.preferences import Preferences # remove duplicate queries. @@ -53,7 +53,7 @@ def parse_pageno(form: Dict[str, str]) -> int: def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: RawTextQuery) -> str: - if is_locked('language'): + if "language" in preferences.cfg.lock: return preferences.get_value('language') # get language # set specific language if set on request, query or preferences @@ -73,7 +73,7 @@ def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: R def parse_safesearch(preferences: Preferences, form: Dict[str, str]) -> int: - if is_locked('safesearch'): + if "safesearch" in preferences.cfg.lock: return preferences.get_value('safesearch') if 'safesearch' in form: @@ -135,7 +135,7 @@ def parse_category_form(query_categories: List[str], name: str, value: str) -> N def get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]: selected_categories = [] - if not is_locked('categories') and form is not None: + if not "categories" in preferences.cfg.lock and form is not None: for name, value in form.items(): parse_category_form(selected_categories, name, value) @@ -175,7 +175,7 @@ def parse_generic(preferences: Preferences, form: Dict[str, str], disabled_engin # set categories/engines explicit_engine_list = False - if not is_locked('categories'): + if not "categories" in preferences.cfg.lock: # parse the form only if the categories are not locked for pd_name, pd in form.items(): # pylint: disable=invalid-name if pd_name == 'engines': @@ -266,7 +266,7 @@ def get_search_query_from_webapp( if query_lang == 'auto': query_lang = preferences.client.locale_tag or 'all' - if not is_locked('categories') and raw_text_query.specific: + if not "categories" in preferences.cfg.lock and raw_text_query.specific: # if engines are calculated from query, # set categories by using that information query_engineref_list = raw_text_query.enginerefs diff --git a/searx/webapp.py b/searx/webapp.py index 8626a72f3..d83ab149a 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -377,7 +377,6 @@ def get_client_settings(): 'theme_static_path': custom_url_for('static', filename='themes/simple'), 'results_on_new_tab': req_pref.get_value('results_on_new_tab'), 'favicon_resolver': req_pref.get_value('favicon_resolver'), - 'advanced_search': req_pref.get_value('advanced_search'), 'query_in_title': req_pref.get_value('query_in_title'), 'safesearch': req_pref.get_value('safesearch'), 'theme': req_pref.get_value('theme'), @@ -977,7 +976,7 @@ def preferences(): current_doi_resolver = get_doi_resolver(), allowed_plugins = allowed_plugins, preferences_url_params = sxng_request.preferences.get_as_url_params(), - locked_preferences = get_setting("preferences.lock", []), + locked_preferences = get_setting("preferences").lock, doi_resolvers = get_setting("doi_resolvers", {}), # fmt: on )