Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monkey patch search handler #102

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Changelog
5.4.9 (unreleased)
------------------

- Nothing changed yet.
- Customize SearchHandler functionlaity via monkeypatches instead of service override.
[folix-01]


5.4.8 (2024-03-19)
Expand Down
2 changes: 1 addition & 1 deletion src/redturtle/volto/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
<include package=".browser" />
<include package=".restapi" />
<include package=".types" />
<include package=".monkeypatch" />

<include file="indexers.zcml" />
<include file="monkey.zcml" />
<include file="permissions.zcml" />
<include file="upgrades.zcml" />

Expand Down
Original file line number Diff line number Diff line change
@@ -1,67 +1,87 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:monkey="http://namespaces.plone.org/monkey"
i18n_domain="collective.monkeypatcher"
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="redturtle.volto"
>

<include package="collective.monkeypatcher" />

<monkey:patch
original="occurrences"
replacement=".monkey.occurrences"
replacement=".occurrences"
class="plone.app.event.recurrence.RecurrenceSupport"
description="This fix the problem with Events recurrences"
/>

<monkey:patch
original="_recurrence_upcoming_event"
replacement=".monkey._recurrence_upcoming_event"
replacement="._recurrence_upcoming_event"
class="plone.app.event.dx.behaviors.EventAccessor"
description="This fix the problem with Events recurrences"
/>

<monkey:patch
original="_verifyObjectPaste"
replacement=".monkey._verifyObjectPaste"
replacement="._verifyObjectPaste"
class="plone.dexterity.content.PasteBehaviourMixin"
description="Patch for disallow paste object also for locally filters"
preserveOriginal="True"
/>

<monkey:patch
original="__call__"
replacement=".monkey.plone_volto_deserializer_call"
replacement=".plone_volto_deserializer_call"
class="plone.volto.transforms.NestedResolveUIDDeserializerBase"
description="Patch for disallow plone.volto deserialization"
/>
<monkey:patch
original="__call__"
replacement=".monkey.plone_volto_serializer_call"
replacement=".plone_volto_serializer_call"
class="plone.volto.transforms.NestedResolveUIDSerializerBase"
description="Patch for disallow plone.volto serialization"
/>

<monkey:patch
original="getPotentialMembers"
replacement=".monkey.getPotentialMembers"
replacement=".getPotentialMembers"
class="Products.CMFPlone.controlpanel.browser.usergroups_groupmembership.GroupMembershipControlPanel"
description="Patch for disallow search all users in group"
preserveOriginal="True"
/>

<monkey:patch
original="__call__"
replacement=".monkey.plone_restapi_pam_translations_get"
replacement=".plone_restapi_pam_translations_get"
class="plone.restapi.services.multilingual.pam.Translations"
description="Fix long request in case pam is not installed"
preserveOriginal="True"
/>
<monkey:patch
original="search_for_similar"
replacement=".monkey.search_for_similar"
replacement=".search_for_similar"
class="plone.app.redirector.browser.FourOhFourView"
description="Cancel the seach_for_similiar call when usin volto frontend"
preserveOriginal="True"
/>

<monkey:patch
original="search"
replacement=".ploneRestapiSearchHandler.search"
class="plone.restapi.search.handler.SearchHandler"
docstringWarning="Monkeypatched search method of the plone.restapi.search.handler.SearchHandler"
description="Custom search"
preserveOriginal="true"
/>

<monkey:patch
original="_parse_query"
replacement=".ploneRestapiSearchHandler._parse_query"
class="plone.restapi.search.handler.SearchHandler"
docstringWarning="Monkeypatched _parse_quesry method of the plone.restapi.search.handler.SearchHandler"
description="Custom _parse_query"
preserveOriginal="true"
/>


</configure>
152 changes: 152 additions & 0 deletions src/redturtle/volto/monkeypatch/ploneRestapiSearchHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
from plone import api
from plone.restapi.interfaces import ISerializeToJson
from plone.restapi.search.utils import unflatten_dotted_dict
from redturtle.volto import logger
from redturtle.volto.config import MAX_LIMIT
from redturtle.volto.interfaces import IRedTurtleVoltoSettings
from zope.component import getMultiAdapter


# search for 'ranking' in 'SearchableText' and rank very high
# when the term is in 'Subject' and high when it is in 'Title'.
# print the id and the normalized rank
try:
from Products.AdvancedQuery import And
from Products.AdvancedQuery import Eq
from Products.AdvancedQuery import In
from Products.AdvancedQuery import Or
from Products.AdvancedQuery import RankByQueries_Sum

HAS_ADVANCEDQUERY = True
except ImportError:
HAS_ADVANCEDQUERY = False

# custom search handler


def get_indexes_mapping():
indexes = api.portal.get_tool("portal_catalog").getIndexObjects()
res = {}
for index in indexes:
index_type = index.meta_type
res[index.getId()] = index_type
return res


def is_advanced_query(query):
if not query:
return False
if query.get("sort_on", None):
return False
if query.get("SimpleQuery", None):
return False
custom_ranking_enabled = api.portal.get_registry_record(
"enable_advanced_query_ranking", interface=IRedTurtleVoltoSettings
)
if HAS_ADVANCEDQUERY and "SearchableText" in query and custom_ranking_enabled:
return True
return False


# XXX: sarebbe meglio una monkeypatch a catalog.searchResults ? eviterebbe di
# riportare tutto il codice di search qui
def search(self, query=None):
query = self.request.form.copy()
query = unflatten_dotted_dict(query)
if is_advanced_query(query):
if "fullobjects" in query:
fullobjects = True
del query["fullobjects"]
else:
fullobjects = False

if "use_site_search_settings" in query:
del query["use_site_search_settings"]
query = self.filter_query(query)

self._constrain_query_by_path(query)
query = self._parse_query(query)
queries = []
term = query.get("SearchableText")
indexes_mapping = get_indexes_mapping()

for key, value in query.items():
index_type = indexes_mapping.get(key, None)
if index_type == "ZCTextIndex":
# SearchableText, Title, Description
queries.append(Eq(key, value))
elif index_type in ["KeywordIndex", "FieldIndex"]:
if isinstance(value, str):
value = [value]
queries.append(In(key, value))
elif index_type == "ExtendedPathIndex":
if isinstance(value["query"], list):
queries.append(Or(*[Eq(key, p) for p in value["query"]]))
else: # list/tuple ?
queries.append(Eq(key, value["query"]))
elif key in ("b_start", "b_size"):
continue
elif index_type is None:
# skip, non-existent index
continue
else:
logger.warning(
f"Unsupported query parameter: {key} {index_type} {value}. Fall back to the standard query."
)
query = self.request.form.copy()
query = unflatten_dotted_dict(query)
return self._old_search(query)

# term = query.pop("SearchableText")
# TODO: mettere i parametri di ranking in registry
# XXX: il default sul subject ha senso ? (probabilmente no), rivedere eventualmente anche i test
rs = RankByQueries_Sum(
(Eq("Subject", term), 16),
(Eq("Title", term), 8),
(Eq("Description", term), 6),
)

lazy_resultset = self.catalog.evalAdvancedQuery(
# Eq("SearchableText", term), (rs,), **query
And(*queries),
(rs,),
)
# DEBUG: TODO: potrebbe essere utile mettere i ranking nella risposta ?
# norm = 1 + rs.getQueryValueSum()
# print([(r.getId, (1 + r.data_record_score_[0]) / norm) for r in lazy_resultset])
results = getMultiAdapter((lazy_resultset, self.request), ISerializeToJson)(
fullobjects=fullobjects
)

return results

return self._old_search(query)


def _parse_query(self, query):
"""
set a max limit for anonymous calls
"""
query = self._old__parse_query(query)
if api.user.is_anonymous():
for idx in ["sort_limit", "b_size"]:
if idx not in query:
continue
value = query.get(idx, MAX_LIMIT)
if value <= 0:
logger.warning(
'[wrong query] {} is wrong: "{}". Set to default ({}).'.format(
idx, query, MAX_LIMIT
)
)
query[idx] = MAX_LIMIT

if value > MAX_LIMIT:
logger.warning(
'[wrong query] {} is too high: "{}". Set to default ({}).'.format(
idx, query, MAX_LIMIT
)
)
query[idx] = MAX_LIMIT
return query
2 changes: 0 additions & 2 deletions src/redturtle/volto/restapi/services/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

<include package=".navigation" />
<include package=".querystringsearch" />
<include package=".search" />
<include package=".site_search" />
<include package=".sitemap" />
<include package=".search" />

<adapter
factory=".controlpanel.RedTurtleVoltoSettings"
Expand Down
Empty file.
21 changes: 0 additions & 21 deletions src/redturtle/volto/restapi/services/search/configure.zcml

This file was deleted.

Loading
Loading