From 4d723631ca3492c6f64d9f889895d6787d21388a Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Fri, 30 Jan 2026 11:32:26 +0100 Subject: [PATCH 1/3] Make AdvancedQuery ranking rules configurable. --- CHANGES.rst | 3 +- src/redturtle/volto/configure.zcml | 8 +- src/redturtle/volto/interfaces.py | 22 ++++++ .../locales/it/LC_MESSAGES/redturtle.volto.po | 76 ++++++++++++++++--- .../volto/locales/redturtle.volto.pot | 74 ++++++++++++++++-- .../volto/profiles/default/metadata.xml | 2 +- .../default/registry/controlpanel.xml | 14 +++- .../volto/profiles/to_4700/registry.xml | 12 +++ .../volto/restapi/services/search/get.py | 76 +++++++++++++------ src/redturtle/volto/setuphandlers.py | 6 +- .../volto/tests/test_advancedsearch.py | 39 ++++++++++ src/redturtle/volto/upgrades.py | 9 +++ src/redturtle/volto/upgrades.zcml | 8 ++ 13 files changed, 303 insertions(+), 46 deletions(-) create mode 100644 src/redturtle/volto/profiles/to_4700/registry.xml diff --git a/CHANGES.rst b/CHANGES.rst index 85f4547b..1fa4f677 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,8 @@ Changelog - Fix bug search event restapi (#148) [mamico] - +- Make AdvancedQuery ranking rules configurable. + [cekk] 5.9.3 (2025-11-24) ------------------ diff --git a/src/redturtle/volto/configure.zcml b/src/redturtle/volto/configure.zcml index 00c6d078..167a029f 100644 --- a/src/redturtle/volto/configure.zcml +++ b/src/redturtle/volto/configure.zcml @@ -44,7 +44,13 @@ provides="Products.GenericSetup.interfaces.EXTENSION" directory="profiles/to_4307" /> - + \n" "Language-Team: LANGUAGE \n" @@ -14,13 +14,25 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: DOMAIN\n" +#: redturtle/volto/patches.py:62 +msgid "Alternative url path must not be a view." +msgstr "" + +#: redturtle/volto/patches.py:51 +msgid "Alternative url path must start with a slash." +msgstr "" + +#: redturtle/volto/patches.py:92 +msgid "Cannot use a working path as alternative url." +msgstr "" + #: redturtle/volto/configure.zcml:29 msgid "Installs the redturtle.volto add-on." -msgstr "" +msgstr "Installa il componente aggiuntivo redturtle.volto." #: redturtle/volto/profiles/default/registry/criteria.xml msgid "Metadata" -msgstr "" +msgstr "Metadati" #: redturtle/volto/browser/controlpanel.py:11 #: redturtle/volto/profiles/default/controlpanel.xml @@ -29,11 +41,19 @@ msgstr "RedTurtle Volto" #: redturtle/volto/configure.zcml:29 msgid "RedTurtle: Volto" -msgstr "" +msgstr "RedTurtle: Volto" + +#: redturtle/volto/configure.zcml:46 +msgid "RedTurtle: Volto (to 4307)" +msgstr "RedTurtle: Volto (to 4307)" + +#: redturtle/volto/configure.zcml:53 +msgid "RedTurtle: Volto (to 4700)" +msgstr "RedTurtle: Volto (to 4700)" #: redturtle/volto/configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" -msgstr "" +msgstr "RedTurtle: Volto (disinstalla)" #: redturtle/volto/profiles/default/registry/criteria.xml msgid "Select False to show only elements not excluded from navigation." @@ -43,27 +63,65 @@ msgstr "Seleziona False per mostrare solo gli elementi non omessi dalla navigazi msgid "Show elements excluded from navigation" msgstr "Elementi omessi dalla navigazione" +#: redturtle/volto/patches.py:64 +msgid "Target path must not be a view." +msgstr "" + +#: redturtle/volto/patches.py:59 +msgid "Target path must start with a slash." +msgstr "" + +#: redturtle/volto/patches.py:74 +msgid "The provided alternative url already exists!" +msgstr "" + +#: redturtle/volto/patches.py:98 +msgid "The provided target object does not exist." +msgstr "" + #: redturtle/volto/configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" +#: redturtle/volto/browser/configure.zcml:82 +msgid "View Document" +msgstr "" + +#: redturtle/volto/patches.py:48 +msgid "You have to enter a target." +msgstr "" + +#: redturtle/volto/patches.py:46 +msgid "You have to enter an alternative url." +msgstr "" + +#. Default: "List of AdvancedQuery ranking rules. Use '__TERM__' for current search term." +#: redturtle/volto/interfaces.py:48 +msgid "advanced_query_ranking_rules_help" +msgstr "Una lista di regole di ranking per AdvancedQuery. Usa '__TERM__' per il termine di ricerca corrente." + +#. Default: "AdvancedQuery Ranking rules" +#: redturtle/volto/interfaces.py:45 +msgid "advanced_query_ranking_rules_label" +msgstr "Regole di ranking per AdvancedQuery" + #. Default: "If enabled, users can't create contents with ids that are already used as aliases." -#: redturtle/volto/interfaces.py:36 +#: redturtle/volto/interfaces.py:61 msgid "check_aliases_in_namechooser_help" msgstr "Se attivato, alla creazione o rinomina di un contenuto, verrĂ  eseguito anche un controllo su eventuali alias presenti (quelli visibili in Gestione URL), ed eventualmente viene impedita la creazione con quell'id." #. Default: "Disallow ids used in aliases" -#: redturtle/volto/interfaces.py:32 +#: redturtle/volto/interfaces.py:57 msgid "check_aliases_in_namechooser_label" msgstr "Controllo degli id anche sugli alias" #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: redturtle/volto/interfaces.py:23 +#: redturtle/volto/interfaces.py:37 msgid "enable_advanced_query_ranking_help" msgstr "Se abilitato, verrĂ  utilizzato un ranking custom per la ricerca testuale." #. Default: "Enable AdvancedQuery ranking" -#: redturtle/volto/interfaces.py:19 +#: redturtle/volto/interfaces.py:33 msgid "enable_advanced_query_ranking_label" msgstr "Abilita ranking custom con AdvancedQuery" diff --git a/src/redturtle/volto/locales/redturtle.volto.pot b/src/redturtle/volto/locales/redturtle.volto.pot index a33cfec1..10c8b737 100644 --- a/src/redturtle/volto/locales/redturtle.volto.pot +++ b/src/redturtle/volto/locales/redturtle.volto.pot @@ -1,10 +1,10 @@ -#--- PLEASE EDIT THE LINES BELOW CORRECTLY --- -#SOME DESCRIPTIVE TITLE. -#FIRST AUTHOR , YEAR. +# --- PLEASE EDIT THE LINES BELOW CORRECTLY --- +# SOME DESCRIPTIVE TITLE. +# FIRST AUTHOR , YEAR. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-03-28 12:42+0000\n" +"POT-Creation-Date: 2026-01-30 10:25+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,18 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: redturtle.volto\n" +#: redturtle/volto/patches.py:62 +msgid "Alternative url path must not be a view." +msgstr "" + +#: redturtle/volto/patches.py:51 +msgid "Alternative url path must start with a slash." +msgstr "" + +#: redturtle/volto/patches.py:92 +msgid "Cannot use a working path as alternative url." +msgstr "" + #: redturtle/volto/configure.zcml:29 msgid "Installs the redturtle.volto add-on." msgstr "" @@ -34,6 +46,14 @@ msgstr "" msgid "RedTurtle: Volto" msgstr "" +#: redturtle/volto/configure.zcml:46 +msgid "RedTurtle: Volto (to 4307)" +msgstr "" + +#: redturtle/volto/configure.zcml:53 +msgid "RedTurtle: Volto (to 4700)" +msgstr "" + #: redturtle/volto/configure.zcml:38 msgid "RedTurtle: Volto (uninstall)" msgstr "" @@ -46,27 +66,65 @@ msgstr "" msgid "Show elements excluded from navigation" msgstr "" +#: redturtle/volto/patches.py:64 +msgid "Target path must not be a view." +msgstr "" + +#: redturtle/volto/patches.py:59 +msgid "Target path must start with a slash." +msgstr "" + +#: redturtle/volto/patches.py:74 +msgid "The provided alternative url already exists!" +msgstr "" + +#: redturtle/volto/patches.py:98 +msgid "The provided target object does not exist." +msgstr "" + #: redturtle/volto/configure.zcml:38 msgid "Uninstalls the redturtle.volto add-on." msgstr "" +#: redturtle/volto/browser/configure.zcml:82 +msgid "View Document" +msgstr "" + +#: redturtle/volto/patches.py:48 +msgid "You have to enter a target." +msgstr "" + +#: redturtle/volto/patches.py:46 +msgid "You have to enter an alternative url." +msgstr "" + +#. Default: "List of AdvancedQuery ranking rules. Use '__TERM__' for current search term." +#: redturtle/volto/interfaces.py:48 +msgid "advanced_query_ranking_rules_help" +msgstr "" + +#. Default: "AdvancedQuery Ranking rules" +#: redturtle/volto/interfaces.py:45 +msgid "advanced_query_ranking_rules_label" +msgstr "" + #. Default: "If enabled, users can't create contents with ids that are already used as aliases." -#: redturtle/volto/interfaces.py:36 +#: redturtle/volto/interfaces.py:61 msgid "check_aliases_in_namechooser_help" msgstr "" #. Default: "Disallow ids used in aliases" -#: redturtle/volto/interfaces.py:32 +#: redturtle/volto/interfaces.py:57 msgid "check_aliases_in_namechooser_label" msgstr "" #. Default: "If enabled, a custom ranking for SearchableText searches will be used." -#: redturtle/volto/interfaces.py:23 +#: redturtle/volto/interfaces.py:37 msgid "enable_advanced_query_ranking_help" msgstr "" #. Default: "Enable AdvancedQuery ranking" -#: redturtle/volto/interfaces.py:19 +#: redturtle/volto/interfaces.py:33 msgid "enable_advanced_query_ranking_label" msgstr "" diff --git a/src/redturtle/volto/profiles/default/metadata.xml b/src/redturtle/volto/profiles/default/metadata.xml index 4babeb28..a93da81c 100644 --- a/src/redturtle/volto/profiles/default/metadata.xml +++ b/src/redturtle/volto/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 4600 + 4700 profile-plone.volto:default profile-plone.app.caching:with-caching-proxy diff --git a/src/redturtle/volto/profiles/default/registry/controlpanel.xml b/src/redturtle/volto/profiles/default/registry/controlpanel.xml index d6d72582..5855a7e1 100644 --- a/src/redturtle/volto/profiles/default/registry/controlpanel.xml +++ b/src/redturtle/volto/profiles/default/registry/controlpanel.xml @@ -1,4 +1,12 @@ - - - + + + + [ + {"index": "Subject", "value": "__TERM__", "weight": 16}, + {"index": "Title", "value": "__TERM__", "weight": 8}, + {"index": "Description", "value": "__TERM__", "weight": 6}, + {"index": "portal_type", "value": "UnitaOrganizzativa", "weight": 20} + ] + + diff --git a/src/redturtle/volto/profiles/to_4700/registry.xml b/src/redturtle/volto/profiles/to_4700/registry.xml new file mode 100644 index 00000000..bf7f82ad --- /dev/null +++ b/src/redturtle/volto/profiles/to_4700/registry.xml @@ -0,0 +1,12 @@ + + + + [ + {"index": "Subject", "value": "__TERM__", "weight": 16}, + {"index": "Title", "value": "__TERM__", "weight": 8}, + {"index": "Description", "value": "__TERM__", "weight": 6}, + {"index": "portal_type", "value": "UnitaOrganizzativa", "weight": 20} + ] + + + diff --git a/src/redturtle/volto/restapi/services/search/get.py b/src/redturtle/volto/restapi/services/search/get.py index e4d44258..8015778f 100644 --- a/src/redturtle/volto/restapi/services/search/get.py +++ b/src/redturtle/volto/restapi/services/search/get.py @@ -8,6 +8,7 @@ from redturtle.volto.interfaces import IRedTurtleVoltoSettings from zope.component import getMultiAdapter +import json import logging @@ -74,28 +75,7 @@ def search(self, query=None): query = unflatten_dotted_dict(query) return super(SearchHandler, self).search(query) - # TODO: mettere i parametri di ranking in registry - # XXX: il default sul subject ha senso ? (probabilmente no), rivedere eventualmente anche i test - term = query.get("SearchableText") - - sort_on = query.get("sort_on", "") - if sort_on: - sort_order = query.get("sort_order", "") - if not sort_order: - if sort_on in ["Date", "effective"]: - sort_order = "desc" - else: - sort_order = "asc" - if sort_order == "reverse": - sort_order = "desc" - rs = (query["sort_on"], sort_order) - else: - # use custom ranking - rs = RankByQueries_Sum( - (Eq("Subject", term), 16), - (Eq("Title", term), 8), - (Eq("Description", term), 6), - ) + rs = self.get_ranking_scheme(query) lazy_resultset = self.catalog.evalAdvancedQuery( # Eq("SearchableText", term), (rs,), **query And(*queries), @@ -111,6 +91,58 @@ def search(self, query=None): return results return super().search(query) + def get_ranking_scheme(self, query): + """ + Read from registry the ranking rules and build the RankByQueries_Sum + accordingly. + """ + term = query.get("SearchableText") + + sort_on = query.get("sort_on", "") + if sort_on: + sort_order = query.get("sort_order", "") + if not sort_order: + if sort_on in ["Date", "effective"]: + sort_order = "desc" + else: + sort_order = "asc" + if sort_order == "reverse": + sort_order = "desc" + return (query["sort_on"], sort_order) + + ranking_json = api.portal.get_registry_record( + "advanced_query_ranking_rules", + interface=IRedTurtleVoltoSettings, + default="[]", + ) + + try: + ranking_rules = json.loads(ranking_json) + except (ValueError, TypeError): + logger.error("Invalid JSON in ranking_rules_json configuration") + ranking_rules = [] + + # Costruiamo dinamicamente la lista per RankByQueries_Sum + rank_args = [] + + for rule in ranking_rules: + index = rule.get("index") + raw_value = rule.get("value") + weight = rule.get("weight", 0) + + if not index or not raw_value: + continue + + if raw_value == "__TERM__": + if not term: + continue + final_value = term + else: + final_value = raw_value + + rank_args.append((Eq(index, final_value), weight)) + return RankByQueries_Sum(*rank_args) + def get_advanced_search_query(self, query): if "use_site_search_settings" in query: del query["use_site_search_settings"] diff --git a/src/redturtle/volto/setuphandlers.py b/src/redturtle/volto/setuphandlers.py index 95ea06dc..99b6a82d 100644 --- a/src/redturtle/volto/setuphandlers.py +++ b/src/redturtle/volto/setuphandlers.py @@ -13,7 +13,11 @@ class HiddenProfiles(object): def getNonInstallableProfiles(self): """Hide uninstall profile from site-creation and quickinstaller.""" - return ["redturtle.volto:uninstall"] + return [ + "redturtle.volto:uninstall", + "redturtle.volto:profile_to_4307", + "redturtle.volto:profile_to_4700", + ] # DEPRECATED diff --git a/src/redturtle/volto/tests/test_advancedsearch.py b/src/redturtle/volto/tests/test_advancedsearch.py index 5b0268f3..240f6fb0 100644 --- a/src/redturtle/volto/tests/test_advancedsearch.py +++ b/src/redturtle/volto/tests/test_advancedsearch.py @@ -10,6 +10,7 @@ from redturtle.volto.testing import REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING from transaction import commit +import json import unittest @@ -222,6 +223,44 @@ def test_search_use_sort_on_if_in_query_and_ignore_custom_order( ["d1", "f1", "e1"], [item["@id"].split("/")[-1] for item in result["items"]] ) + def test_search_with_custom_rank(self): + query = {"SearchableText": "foo"} + + # this is a query with default ranking values + result = self.api_session.get("/@search", params=query).json() + self.assertEqual(result["items_total"], 3) + self.assertEqual( + ["d1", "f1", "e1"], [item["@id"].split("/")[-1] for item in result["items"]] + ) + + # now we change ranking rules to have different order: add Event with + # weight 20 (higher than others) + api.portal.set_registry_record( + "advanced_query_ranking_rules", + json.dumps( + [ + {"index": "Subject", "value": "__TERM__", "weight": 16}, + {"index": "Title", "value": "__TERM__", "weight": 8}, + {"index": "Description", "value": "__TERM__", "weight": 6}, + { + "index": "portal_type", + "value": "Event", + "weight": 20, + }, + ] + ), + interface=IRedTurtleVoltoSettings, + ) + + commit() + + # re-execute query + result = self.api_session.get("/@search", params=query).json() + self.assertEqual(result["items_total"], 3) + self.assertEqual( + ["e1", "d1", "f1"], [item["@id"].split("/")[-1] for item in result["items"]] + ) + class AdvancedSearchWithFlagTest(BaseTest): def test_by_default_flag_is_disabled(self): diff --git a/src/redturtle/volto/upgrades.py b/src/redturtle/volto/upgrades.py index d466f259..c2848d1c 100644 --- a/src/redturtle/volto/upgrades.py +++ b/src/redturtle/volto/upgrades.py @@ -635,3 +635,12 @@ def to_4600(context): portal_types = api.portal.get_tool(name="portal_types") portal_types["Plone Site"].default_view = "homepage_view" portal_types["Plone Site"].immediate_view = "homepage_view" + + +def to_4700(context): + logger.info("Make AdvancedQuery ranking rules configurable") + context.runImportStepFromProfile( + "profile-redturtle.volto:profile_to_4700", + "plone.app.registry", + run_dependencies=False, + ) diff --git a/src/redturtle/volto/upgrades.zcml b/src/redturtle/volto/upgrades.zcml index f5bc6972..2d5095c7 100644 --- a/src/redturtle/volto/upgrades.zcml +++ b/src/redturtle/volto/upgrades.zcml @@ -280,4 +280,12 @@ destination="4600" handler=".upgrades.to_4600" /> + From 16ea95574255961bf7ea6cac55d6d4af6db56161 Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Fri, 30 Jan 2026 11:34:37 +0100 Subject: [PATCH 2/3] fix ci --- .github/workflows/black.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 9d8d08f4..a57a7f8a 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -24,7 +24,8 @@ jobs: ${{ runner.os }}-pip- - name: install black - run: pip install black + # temporary fix: https://community.plone.org/t/clash-between-isort-and-black-after-pre-commit-autoupdate/22787 + run: pip install black==25.12.0 - name: run black run: black src/ --check --diff From 02fde38c83aaa68715ef0ef1630a94bd18ac5f82 Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Fri, 13 Feb 2026 10:32:54 +0100 Subject: [PATCH 3/3] Update src/redturtle/volto/restapi/services/search/get.py Co-authored-by: Mauro Amico --- .../volto/restapi/services/search/get.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/redturtle/volto/restapi/services/search/get.py b/src/redturtle/volto/restapi/services/search/get.py index 8015778f..e6f7f320 100644 --- a/src/redturtle/volto/restapi/services/search/get.py +++ b/src/redturtle/volto/restapi/services/search/get.py @@ -110,16 +110,15 @@ def get_ranking_scheme(self, query): sort_order = "desc" return (query["sort_on"], sort_order) - ranking_json = api.portal.get_registry_record( - "advanced_query_ranking_rules", - interface=IRedTurtleVoltoSettings, - default="[]", - ) - try: + ranking_json = api.portal.get_registry_record( + "advanced_query_ranking_rules", + interface=IRedTurtleVoltoSettings, + default="[]", + ) ranking_rules = json.loads(ranking_json) - except (ValueError, TypeError): - logger.error("Invalid JSON in ranking_rules_json configuration") + except (ValueError, TypeError, KeyError): + logger.error("Invalid or missing JSON in ranking_rules_json configuration") ranking_rules = [] # Costruiamo dinamicamente la lista per RankByQueries_Sum