summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Heiser <markus.heiser@darmarit.de>2026-05-30 14:43:50 +0200
committerBrock Vojkovic <brock@vojk.au>2026-05-30 22:43:50 +0800
commit4ac822fd7fbb5bd35b3e0d8695e4942fbc4d202a (patch)
tree43b6d3b44b51813c817cafc4d5647e89b3d33f9b
parente1d25c5078dfc5c8b3ba90e818d25eff1b0135ce (diff)
[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 <markus.heiser@darmarit.de>
-rw-r--r--client/simple/src/js/toolkit.ts1
-rw-r--r--docs/admin/settings/index.rst1
-rw-r--r--docs/admin/settings/settings_preferences.rst8
-rw-r--r--searx/__init__.py13
-rw-r--r--searx/_settings.py42
-rw-r--r--searx/preferences.py115
-rw-r--r--searx/settings.yml9
-rw-r--r--searx/settings_defaults.py6
-rw-r--r--searx/templates/simple/preferences.html2
-rw-r--r--searx/webadapter.py12
-rwxr-xr-xsearx/webapp.py3
11 files changed, 124 insertions, 88 deletions
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 <searx.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
)