Skip to content

Commit

Permalink
Add experimental.noacquisition (#126)
Browse files Browse the repository at this point in the history
* move patches in separate file and patch absolutize_path method

* enable experimental.noacquisition

* enable experimental.noacquisition

* isort

* add tests

* disable test for plone < 6

* isort

* upgrade minor

* ci

---------

Co-authored-by: Mauro Amico <[email protected]>
  • Loading branch information
cekk and mamico authored Feb 6, 2025
1 parent 77bee35 commit a516dea
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 84 deletions.
12 changes: 7 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ jobs:
strategy:
max-parallel: 4
matrix:
python: ["3.8", "3.9", "3.10", "3.11"]
plone: ["52", "60"]
# python: ["3.8", "3.9", "3.10", "3.11"]
# plone: ["52", "60"]
python: ["3.9", "3.10", "3.11"]
plone: ["60"]
tz: ["UTC", "Europe/Rome"]
exclude:
- python: "3.8"
Expand All @@ -24,14 +26,14 @@ jobs:
- python: "3.11"
plone: "52"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Cache eggs
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: eggs
key: ${{ runner.OS }}-build-python${{ matrix.python }}-${{ matrix.plone }}
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Install dependencies
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/zpretty.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.8]
python-version: [3.11]

steps:
# git checkout
- uses: actions/checkout@v2
- uses: actions/checkout@v4

# python setup
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

# python cache
- uses: actions/cache@v1
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
Expand Down
6 changes: 5 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
Changelog
=========

5.6.4 (unreleased)
5.7.0 (unreleased)
------------------

- Add experimental.noacquisition as dependency.
[cekk]
- Patch absolutize_path method to disable acquisition when checking aliases.
[cekk]
- Update it translations
[lucabel]

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

setup(
name="redturtle.volto",
version="5.6.4.dev0",
version="5.7.0.dev0",
description="Helper package to setup a RedTurtle's Plone site ready to work with Volto.",
long_description=long_description,
# Get more from https://pypi.org/classifiers/
Expand Down Expand Up @@ -63,6 +63,7 @@
"Products.PortalTransforms>=3.2.0",
"collective.volto.sitesettings",
"z3c.jbot",
"experimental.noacquisition",
],
extras_require={
"advancedquery": [
Expand Down
73 changes: 1 addition & 72 deletions src/redturtle/volto/__init__.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,6 @@
# -*- coding: utf-8 -*-
"""Init and utils."""
from plone.app.content.browser.vocabulary import PERMISSIONS
from plone.folder.nogopip import GopipIndex
from Products.ZCatalog.Catalog import Catalog
from redturtle.volto.catalogplan import Catalog_sorted_search_indexes
from redturtle.volto import patches # noqa
from zope.i18nmessageid import MessageFactory
from ZTUtils.Lazy import LazyCat
from ZTUtils.Lazy import LazyMap

import logging


logger = logging.getLogger(__name__)


_ = MessageFactory("redturtle.volto")

PERMISSIONS["plone.app.vocabularies.Keywords"] = "View"

# CATALOG PATCHES

logger.info(
"install monkey patch for Products.ZCatalog.Catalog.Catalog._sorted_search_indexes #### WORK IN PROGRESS ####"
)
Catalog._orig_sorted_search_indexes = Catalog._sorted_search_indexes
Catalog._sorted_search_indexes = Catalog_sorted_search_indexes

MAX_SORTABLE = 5000


def Catalog_sortResults(
self,
rs,
sort_index,
reverse=False,
limit=None,
merge=True,
actual_result_count=None,
b_start=0,
b_size=None,
):
if MAX_SORTABLE > 0:
if actual_result_count is None:
actual_result_count = len(rs)
if actual_result_count >= MAX_SORTABLE and isinstance(sort_index, GopipIndex):
logger.warning(
"too many results %s disable GopipIndex sorting", actual_result_count
)
switched_reverse = bool(
b_size and b_start and b_start > actual_result_count / 2
)
if hasattr(rs, "keys"):
sequence, slen = self._limit_sequence(
rs.keys(), actual_result_count, b_start, b_size, switched_reverse
)
return LazyMap(
self.__getitem__,
sequence,
len(sequence),
actual_result_count=actual_result_count,
)
else:
logger.error(
"too many results %s disable GopipIndex sorting results %s has no key",
actual_result_count,
type(rs),
)
return LazyCat([], 0, actual_result_count)
return self._orig_sortResults(
rs, sort_index, reverse, limit, merge, actual_result_count, b_start, b_size
)


logger.info("install monkey patch for Products.ZCatalog.Catalog.Catalog.sortResults")
Catalog._orig_sortResults = Catalog.sortResults
Catalog.sortResults = Catalog_sortResults
169 changes: 169 additions & 0 deletions src/redturtle/volto/patches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# These are patches not managed by collective.monkeypatcher
from experimental.noacquisition import config
from plone.app.content.browser.vocabulary import PERMISSIONS
from plone.app.redirector.interfaces import IRedirectionStorage
from plone.folder.nogopip import GopipIndex
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.controlpanel.browser import redirects
from Products.ZCatalog.Catalog import Catalog
from redturtle.volto.catalogplan import Catalog_sorted_search_indexes
from urllib.parse import urlparse
from zope.component import getUtility
from zope.component.hooks import getSite
from zope.i18nmessageid import MessageFactory
from ZTUtils.Lazy import LazyCat
from ZTUtils.Lazy import LazyMap

import logging


logger = logging.getLogger(__name__)

_ = MessageFactory("plone")


def absolutize_path_patched(path, is_source=True):
"""Create path including the path of the portal root.
The path must be absolute, so starting with a slash.
Or it can be a full url.
If is_source is true, this is an alternative url
that will point to a target (unknown here).
If is_source is true, path is the path of a target.
An object must exist at this path, unless it is a full url.
Return a 2-tuple: (absolute redirection path,
an error message if something goes wrong and otherwise '').
"""

portal = getSite()
err = None
is_external_url = False
if not path:
if is_source:
err = _("You have to enter an alternative url.")
else:
err = _("You have to enter a target.")
elif not path.startswith("/"):
if is_source:
err = _("Alternative url path must start with a slash.")
else:
# For targets, we accept external urls.
# Do basic check.
parsed = urlparse(path)
if parsed.scheme in ("https", "http") and parsed.netloc:
is_external_url = True
else:
err = _("Target path must start with a slash.")
elif "@@" in path:
if is_source:
err = _("Alternative url path must not be a view.")
else:
err = _("Target path must not be a view.")
else:
context_path = "/".join(portal.getPhysicalPath())
path = f"{context_path}{path}"
if not err and not is_external_url:
catalog = getToolByName(portal, "portal_catalog")
if is_source:
# Check whether already exists in storage
storage = getUtility(IRedirectionStorage)
if storage.get(path):
err = _("The provided alternative url already exists!")
else:
# Check whether obj exists at source path.
# A redirect would be useless then.

# THIS IS THE PATCH
# unrestrictedTraverse returns the object with acquisition, so if
# you have a content with path /Plone/aaa and try to call unrestrictedTraverse
# with /Plone/aaa/aaa/aaa/aaa it will always return /Plone/aaa object
# and this is not correct because we could want to create an alias for /Plone/aaa
# that is /Plone/aaa/aaa
# In Plone7 this will not be a problem anymore because of this:
# https://github.com/plone/Products.CMFPlone/issues/3871
item = portal.unrestrictedTraverse(path, None)
# if item is not None: this is the original check
if item is not None and "/".join(item.getPhysicalPath()) == path:
# if paths are different, it's an acquisition false positive,
# so go on and let create the alias
err = _("Cannot use a working path as alternative url.")
# END OF PATCH
else:
# Check whether obj exists at target path
result = catalog.searchResults(path={"query": path})
if len(result) == 0:
err = _("The provided target object does not exist.")

return path, err


MAX_SORTABLE = 5000


def Catalog_sortResults(
self,
rs,
sort_index,
reverse=False,
limit=None,
merge=True,
actual_result_count=None,
b_start=0,
b_size=None,
):
if MAX_SORTABLE > 0:
if actual_result_count is None:
actual_result_count = len(rs)
if actual_result_count >= MAX_SORTABLE and isinstance(sort_index, GopipIndex):
logger.warning(
"too many results %s disable GopipIndex sorting", actual_result_count
)
switched_reverse = bool(
b_size and b_start and b_start > actual_result_count / 2
)
if hasattr(rs, "keys"):
sequence, slen = self._limit_sequence(
rs.keys(), actual_result_count, b_start, b_size, switched_reverse
)
return LazyMap(
self.__getitem__,
sequence,
len(sequence),
actual_result_count=actual_result_count,
)
else:
logger.error(
"too many results %s disable GopipIndex sorting results %s has no key",
actual_result_count,
type(rs),
)
return LazyCat([], 0, actual_result_count)
return self._orig_sortResults(
rs, sort_index, reverse, limit, merge, actual_result_count, b_start, b_size
)


# apply patches
logger.info(
"install monkey patch for Products.ZCatalog.Catalog.Catalog._sorted_search_indexes #### WORK IN PROGRESS ####"
)
Catalog._orig_sorted_search_indexes = Catalog._sorted_search_indexes
Catalog._sorted_search_indexes = Catalog_sorted_search_indexes

logger.info("install monkey patch for Products.ZCatalog.Catalog.Catalog.sortResults")
Catalog._orig_sortResults = Catalog.sortResults
Catalog.sortResults = Catalog_sortResults

logger.info("install monkey patch for plone.app.content.browser.vocabulary.PERMISSIONS")
PERMISSIONS["plone.app.vocabularies.Keywords"] = "View"

logger.info(
"install monkey patch for from Products.CMFPlone.controlpanel.browser.redirects.absolutize_path"
)
redirects.absolutize_path = absolutize_path_patched

logger.info("enable experimental.noacquisition")
config.DRYRUN = False
5 changes: 4 additions & 1 deletion src/redturtle/volto/restapi/services/search/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
from plone.restapi.search.handler import SearchHandler as OriginalHandler
from plone.restapi.search.utils import unflatten_dotted_dict
from plone.restapi.services import Service
from redturtle.volto import logger
from redturtle.volto.config import MAX_LIMIT
from redturtle.volto.interfaces import IRedTurtleVoltoSettings
from zope.component import getMultiAdapter

import logging


logger = logging.getLogger(__name__)

# search for 'ranking' in 'SearchableText' and rank very high
# when the term is in 'Subject' and high when it is in 'Title'.
Expand Down
3 changes: 3 additions & 0 deletions src/redturtle/volto/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import collective.volto.gdprcookie
import collective.volto.sitesettings
import experimental.noacquisition
import kitconcept.seo
import plone.app.caching
import plone.restapi
Expand All @@ -31,6 +32,7 @@ def setUpZope(self, app, configurationContext):
self.loadZCML(package=plone.volto)
self.loadZCML(package=plone.app.caching)
self.loadZCML(package=kitconcept.seo)
self.loadZCML(package=experimental.noacquisition)

def setUpPloneSite(self, portal):
applyProfile(portal, "plone.app.caching:default")
Expand Down Expand Up @@ -75,6 +77,7 @@ def setUpZope(self, app, configurationContext):
self.loadZCML(package=redturtle.volto)
self.loadZCML(package=plone.app.caching)
self.loadZCML(package=kitconcept.seo)
self.loadZCML(package=experimental.noacquisition)

def setUpPloneSite(self, portal):
applyProfile(portal, "plone.app.caching:default")
Expand Down
Loading

0 comments on commit a516dea

Please sign in to comment.