mirror of
https://github.com/searxng/searxng.git
synced 2026-06-01 07:27:16 +02:00
[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>
This commit is contained in:
committed by
Brock Vojkovic
parent
e1d25c5078
commit
4ac822fd7f
@@ -5,7 +5,6 @@ import type { KeyBindingLayout } from "./main/keyboard.ts";
|
|||||||
// synced with searx/webapp.py get_client_settings
|
// synced with searx/webapp.py get_client_settings
|
||||||
type Settings = {
|
type Settings = {
|
||||||
plugins?: string[];
|
plugins?: string[];
|
||||||
advanced_search?: boolean;
|
|
||||||
autocomplete?: string;
|
autocomplete?: string;
|
||||||
autocomplete_min?: number;
|
autocomplete_min?: number;
|
||||||
doi_resolver?: string;
|
doi_resolver?: string;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Settings
|
|||||||
settings_search
|
settings_search
|
||||||
settings_server
|
settings_server
|
||||||
settings_ui
|
settings_ui
|
||||||
|
settings_preferences
|
||||||
settings_redis
|
settings_redis
|
||||||
settings_valkey
|
settings_valkey
|
||||||
settings_outgoing
|
settings_outgoing
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.. _settings preferences:
|
||||||
|
|
||||||
|
================
|
||||||
|
``preferences:``
|
||||||
|
================
|
||||||
|
|
||||||
|
.. autoclass:: searx._settings.SettingsPref
|
||||||
|
:members:
|
||||||
+7
-6
@@ -10,6 +10,7 @@ from os.path import dirname, abspath
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import msgspec
|
import msgspec
|
||||||
|
from ._settings import SettingsPref
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s'
|
LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s'
|
||||||
@@ -44,15 +45,15 @@ def init_settings():
|
|||||||
cfg = cfg or {}
|
cfg = cfg or {}
|
||||||
apply_schema(cfg, SCHEMA, [])
|
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.clear()
|
||||||
settings.update(cfg)
|
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")
|
sxng_debug = get_setting("general.debug")
|
||||||
if sxng_debug:
|
if sxng_debug:
|
||||||
_logging_config_debug()
|
_logging_config_debug()
|
||||||
|
|||||||
@@ -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."""
|
||||||
+52
-63
@@ -17,13 +17,14 @@ import babel.core
|
|||||||
|
|
||||||
import searx.plugins
|
import searx.plugins
|
||||||
|
|
||||||
from searx import settings, autocomplete, favicons
|
from searx import get_setting, settings, autocomplete, favicons
|
||||||
from searx.enginelib import Engine
|
from searx.enginelib import Engine
|
||||||
from searx.engines import DEFAULT_CATEGORY
|
from searx.engines import DEFAULT_CATEGORY
|
||||||
from searx.extended_types import SXNG_Request
|
from searx.extended_types import SXNG_Request
|
||||||
from searx.locales import LOCALE_NAMES
|
from searx.locales import LOCALE_NAMES
|
||||||
from searx.webutils import VALID_LANGUAGE_CODE
|
from searx.webutils import VALID_LANGUAGE_CODE
|
||||||
|
|
||||||
|
from ._settings import SettingsPref
|
||||||
|
|
||||||
COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years
|
COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years
|
||||||
DOI_RESOLVERS = list(settings['doi_resolvers'])
|
DOI_RESOLVERS = list(settings['doi_resolvers'])
|
||||||
@@ -386,6 +387,7 @@ class ClientPref:
|
|||||||
return cls(locale=locale)
|
return cls(locale=locale)
|
||||||
|
|
||||||
|
|
||||||
|
@t.final
|
||||||
class Preferences:
|
class Preferences:
|
||||||
"""Validates and saves preferences to cookies"""
|
"""Validates and saves preferences to cookies"""
|
||||||
|
|
||||||
@@ -400,95 +402,91 @@ class Preferences:
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
self.cfg: SettingsPref = get_setting("preferences")
|
||||||
|
|
||||||
self.key_value_settings: dict[str, Setting] = {
|
self.key_value_settings: dict[str, Setting] = {
|
||||||
# fmt: off
|
|
||||||
'categories': MultipleChoiceSetting(
|
'categories': MultipleChoiceSetting(
|
||||||
['general'],
|
["general"],
|
||||||
locked=is_locked('categories'),
|
locked="categories" in self.cfg.lock,
|
||||||
choices=categories + ['none']
|
choices=categories + ["none"],
|
||||||
),
|
),
|
||||||
'language': SearchLanguageSetting(
|
'language': SearchLanguageSetting(
|
||||||
settings['search']['default_lang'],
|
get_setting("search.default_lang"),
|
||||||
locked=is_locked('language'),
|
locked="language" in self.cfg.lock,
|
||||||
choices=settings['search']['languages'] + ['']
|
choices=get_setting("search.languages") + [""],
|
||||||
),
|
),
|
||||||
'locale': EnumStringSetting(
|
'locale': EnumStringSetting(
|
||||||
settings['ui']['default_locale'],
|
get_setting("ui.default_locale"),
|
||||||
locked=is_locked('locale'),
|
locked="locale" in self.cfg.lock,
|
||||||
choices=list(LOCALE_NAMES.keys()) + ['']
|
choices=list(LOCALE_NAMES.keys()) + [""],
|
||||||
),
|
),
|
||||||
'autocomplete': EnumStringSetting(
|
'autocomplete': EnumStringSetting(
|
||||||
settings['search']['autocomplete'],
|
get_setting("search.autocomplete"),
|
||||||
locked=is_locked('autocomplete'),
|
locked="autocomplete" in self.cfg.lock,
|
||||||
choices=list(autocomplete.backends.keys()) + ['']
|
choices=list(autocomplete.backends.keys()) + [""],
|
||||||
),
|
),
|
||||||
'favicon_resolver': EnumStringSetting(
|
'favicon_resolver': EnumStringSetting(
|
||||||
settings['search']['favicon_resolver'],
|
get_setting("search.favicon_resolver"),
|
||||||
locked=is_locked('favicon_resolver'),
|
locked="favicon_resolver" in self.cfg.lock,
|
||||||
choices=list(favicons.proxy.CFG.resolver_map.keys()) + ['']
|
choices=list(favicons.proxy.CFG.resolver_map.keys()) + [''],
|
||||||
),
|
),
|
||||||
'image_proxy': BooleanSetting(
|
'image_proxy': BooleanSetting(
|
||||||
settings['server']['image_proxy'],
|
get_setting("server.image_proxy"),
|
||||||
locked=is_locked('image_proxy')
|
locked="image_proxy" in self.cfg.lock,
|
||||||
),
|
),
|
||||||
'method': EnumStringSetting(
|
'method': EnumStringSetting(
|
||||||
settings['server']['method'],
|
get_setting("server.method"),
|
||||||
locked=is_locked('method'),
|
locked="method" in self.cfg.lock,
|
||||||
choices=('GET', 'POST')
|
choices=("GET", "POST"),
|
||||||
),
|
),
|
||||||
'safesearch': MapSetting(
|
'safesearch': MapSetting(
|
||||||
settings['search']['safe_search'],
|
get_setting("search.safe_search"),
|
||||||
locked=is_locked('safesearch'),
|
locked="safesearch" in self.cfg.lock,
|
||||||
map={
|
map={
|
||||||
'0': 0,
|
"0": 0,
|
||||||
'1': 1,
|
"1": 1,
|
||||||
'2': 2
|
"2": 2,
|
||||||
}
|
},
|
||||||
),
|
),
|
||||||
'theme': EnumStringSetting(
|
'theme': EnumStringSetting(
|
||||||
settings['ui']['default_theme'],
|
get_setting("ui.default_theme"),
|
||||||
locked=is_locked('theme'),
|
locked="theme" in self.cfg.lock,
|
||||||
choices=themes
|
choices=themes,
|
||||||
),
|
),
|
||||||
'results_on_new_tab': BooleanSetting(
|
'results_on_new_tab': BooleanSetting(
|
||||||
settings['ui']['results_on_new_tab'],
|
get_setting("ui.results_on_new_tab"),
|
||||||
locked=is_locked('results_on_new_tab')
|
locked="results_on_new_tab" in self.cfg.lock,
|
||||||
),
|
),
|
||||||
'doi_resolver': MultipleChoiceSetting(
|
'doi_resolver': MultipleChoiceSetting(
|
||||||
[settings['default_doi_resolver'], ],
|
[get_setting("default_doi_resolver")],
|
||||||
locked=is_locked('doi_resolver'),
|
locked="doi_resolver" in self.cfg.lock,
|
||||||
choices=DOI_RESOLVERS
|
choices=DOI_RESOLVERS,
|
||||||
),
|
),
|
||||||
'simple_style': EnumStringSetting(
|
'simple_style': EnumStringSetting(
|
||||||
settings['ui']['theme_args']['simple_style'],
|
get_setting("ui.theme_args.simple_style"),
|
||||||
locked=is_locked('simple_style'),
|
locked="simple_style" in self.cfg.lock,
|
||||||
choices=['', 'auto', 'light', 'dark', 'black']
|
choices=["", "auto", "light", "dark", "black"],
|
||||||
),
|
),
|
||||||
'center_alignment': BooleanSetting(
|
'center_alignment': BooleanSetting(
|
||||||
settings['ui']['center_alignment'],
|
get_setting("ui.center_alignment"),
|
||||||
locked=is_locked('center_alignment')
|
locked="center_alignment" in self.cfg.lock,
|
||||||
),
|
|
||||||
'advanced_search': BooleanSetting(
|
|
||||||
settings['ui']['advanced_search'],
|
|
||||||
locked=is_locked('advanced_search')
|
|
||||||
),
|
),
|
||||||
'query_in_title': BooleanSetting(
|
'query_in_title': BooleanSetting(
|
||||||
settings['ui']['query_in_title'],
|
get_setting("ui.query_in_title"),
|
||||||
locked=is_locked('query_in_title')
|
locked="query_in_title" in self.cfg.lock,
|
||||||
),
|
),
|
||||||
'search_on_category_select': BooleanSetting(
|
'search_on_category_select': BooleanSetting(
|
||||||
settings['ui']['search_on_category_select'],
|
get_setting("ui.search_on_category_select"),
|
||||||
locked=is_locked('search_on_category_select')
|
locked="search_on_category_select" in self.cfg.lock,
|
||||||
),
|
),
|
||||||
'hotkeys': EnumStringSetting(
|
'hotkeys': EnumStringSetting(
|
||||||
settings['ui']['hotkeys'],
|
get_setting("ui.hotkeys"),
|
||||||
choices=['default', 'vim']
|
choices=["default", "vim"],
|
||||||
),
|
),
|
||||||
'url_formatting': EnumStringSetting(
|
'url_formatting': EnumStringSetting(
|
||||||
settings['ui']['url_formatting'],
|
get_setting("ui.url_formatting"),
|
||||||
choices=['pretty', 'full', 'host']
|
choices=["pretty", "full", "host"],
|
||||||
),
|
),
|
||||||
# fmt: on
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.engines = EnginesSetting('engines', engines=engines.values())
|
self.engines = EnginesSetting('engines', engines=engines.values())
|
||||||
@@ -597,12 +595,3 @@ class Preferences:
|
|||||||
break
|
break
|
||||||
|
|
||||||
return valid
|
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']
|
|
||||||
|
|||||||
+4
-5
@@ -154,14 +154,13 @@ ui:
|
|||||||
# URL formatting: pretty, full or host
|
# URL formatting: pretty, full or host
|
||||||
url_formatting: pretty
|
url_formatting: pretty
|
||||||
|
|
||||||
# Lock arbitrary settings on the preferences page.
|
preferences:
|
||||||
#
|
# Lock arbitrary settings on the preferences page.
|
||||||
# preferences:
|
lock: []
|
||||||
# lock:
|
|
||||||
# - categories
|
# - categories
|
||||||
# - language
|
# - language
|
||||||
# - autocomplete
|
# - autocomplete
|
||||||
# - favicon
|
# - favicon_resolver
|
||||||
# - safesearch
|
# - safesearch
|
||||||
# - method
|
# - method
|
||||||
# - doi_resolver
|
# - doi_resolver
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import msgspec
|
|||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from .brand import SettingsBrand
|
from .brand import SettingsBrand
|
||||||
from .sxng_locales import sxng_locales
|
from .sxng_locales import sxng_locales
|
||||||
|
from ._settings import SettingsPref
|
||||||
|
|
||||||
searx_dir = abspath(dirname(__file__))
|
searx_dir = abspath(dirname(__file__))
|
||||||
|
|
||||||
@@ -236,16 +237,13 @@ SCHEMA: dict[str, t.Any] = {
|
|||||||
},
|
},
|
||||||
'center_alignment': SettingsValue(bool, False),
|
'center_alignment': SettingsValue(bool, False),
|
||||||
'results_on_new_tab': SettingsValue(bool, False),
|
'results_on_new_tab': SettingsValue(bool, False),
|
||||||
'advanced_search': SettingsValue(bool, False),
|
|
||||||
'query_in_title': SettingsValue(bool, False),
|
'query_in_title': SettingsValue(bool, False),
|
||||||
'cache_url': SettingsValue(str, 'https://web.archive.org/web/'),
|
'cache_url': SettingsValue(str, 'https://web.archive.org/web/'),
|
||||||
'search_on_category_select': SettingsValue(bool, True),
|
'search_on_category_select': SettingsValue(bool, True),
|
||||||
'hotkeys': SettingsValue(('default', 'vim'), 'default'),
|
'hotkeys': SettingsValue(('default', 'vim'), 'default'),
|
||||||
'url_formatting': SettingsValue(('pretty', 'full', 'host'), 'pretty'),
|
'url_formatting': SettingsValue(('pretty', 'full', 'host'), 'pretty'),
|
||||||
},
|
},
|
||||||
'preferences': {
|
"preferences": SettingsPref,
|
||||||
'lock': SettingsValue(list, []),
|
|
||||||
},
|
|
||||||
'outgoing': {
|
'outgoing': {
|
||||||
'useragent_suffix': SettingsValue(str, ''),
|
'useragent_suffix': SettingsValue(str, ''),
|
||||||
'request_timeout': SettingsValue(numbers.Real, 3.0),
|
'request_timeout': SettingsValue(numbers.Real, 3.0),
|
||||||
|
|||||||
@@ -180,7 +180,7 @@
|
|||||||
{%- if 'autocomplete' not in locked_preferences -%}
|
{%- if 'autocomplete' not in locked_preferences -%}
|
||||||
{%- include 'simple/preferences/autocomplete.html' -%}
|
{%- include 'simple/preferences/autocomplete.html' -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- if 'favicon' not in locked_preferences -%}
|
{%- if 'favicon_resolver' not in locked_preferences -%}
|
||||||
{%- include 'simple/preferences/favicon.html' -%}
|
{%- include 'simple/preferences/favicon.html' -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{% if 'safesearch' not in locked_preferences %}
|
{% if 'safesearch' not in locked_preferences %}
|
||||||
|
|||||||
+6
-6
@@ -8,7 +8,7 @@ from searx.webutils import VALID_LANGUAGE_CODE
|
|||||||
from searx.query import RawTextQuery
|
from searx.query import RawTextQuery
|
||||||
from searx.engines import categories, engines
|
from searx.engines import categories, engines
|
||||||
from searx.search.models import SearchQuery, EngineRef
|
from searx.search.models import SearchQuery, EngineRef
|
||||||
from searx.preferences import Preferences, is_locked
|
from searx.preferences import Preferences
|
||||||
|
|
||||||
|
|
||||||
# remove duplicate queries.
|
# 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:
|
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')
|
return preferences.get_value('language')
|
||||||
# get language
|
# get language
|
||||||
# set specific language if set on request, query or preferences
|
# 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:
|
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')
|
return preferences.get_value('safesearch')
|
||||||
|
|
||||||
if 'safesearch' in form:
|
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]:
|
def get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]:
|
||||||
selected_categories = []
|
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():
|
for name, value in form.items():
|
||||||
parse_category_form(selected_categories, name, value)
|
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
|
# set categories/engines
|
||||||
explicit_engine_list = False
|
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
|
# parse the form only if the categories are not locked
|
||||||
for pd_name, pd in form.items(): # pylint: disable=invalid-name
|
for pd_name, pd in form.items(): # pylint: disable=invalid-name
|
||||||
if pd_name == 'engines':
|
if pd_name == 'engines':
|
||||||
@@ -266,7 +266,7 @@ def get_search_query_from_webapp(
|
|||||||
if query_lang == 'auto':
|
if query_lang == 'auto':
|
||||||
query_lang = preferences.client.locale_tag or 'all'
|
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,
|
# if engines are calculated from query,
|
||||||
# set categories by using that information
|
# set categories by using that information
|
||||||
query_engineref_list = raw_text_query.enginerefs
|
query_engineref_list = raw_text_query.enginerefs
|
||||||
|
|||||||
+1
-2
@@ -377,7 +377,6 @@ def get_client_settings():
|
|||||||
'theme_static_path': custom_url_for('static', filename='themes/simple'),
|
'theme_static_path': custom_url_for('static', filename='themes/simple'),
|
||||||
'results_on_new_tab': req_pref.get_value('results_on_new_tab'),
|
'results_on_new_tab': req_pref.get_value('results_on_new_tab'),
|
||||||
'favicon_resolver': req_pref.get_value('favicon_resolver'),
|
'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'),
|
'query_in_title': req_pref.get_value('query_in_title'),
|
||||||
'safesearch': req_pref.get_value('safesearch'),
|
'safesearch': req_pref.get_value('safesearch'),
|
||||||
'theme': req_pref.get_value('theme'),
|
'theme': req_pref.get_value('theme'),
|
||||||
@@ -977,7 +976,7 @@ def preferences():
|
|||||||
current_doi_resolver = get_doi_resolver(),
|
current_doi_resolver = get_doi_resolver(),
|
||||||
allowed_plugins = allowed_plugins,
|
allowed_plugins = allowed_plugins,
|
||||||
preferences_url_params = sxng_request.preferences.get_as_url_params(),
|
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", {}),
|
doi_resolvers = get_setting("doi_resolvers", {}),
|
||||||
# fmt: on
|
# fmt: on
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user