Skip to content
Open
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
25 changes: 25 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ It includes a registry reader which reads operators, values and criteria
from the Plone registry.


Filtering by the current item
-----------------------------

The ``plone.app.querystring.operation.string.currentUID`` operation
("Current item") compares an index with the UID of the item where the query
runs, for example the page that holds a listing block. It needs no value from
the editor.

No field uses this operation by default. It is meant for indexes that store
the UIDs of related items: a collection or listing placed on an item can then
show every item that points to it, such as "posts by this author". An add-on
enables it on the field for its own index in its ``registry.xml``::

<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.authors">
<value key="title">Authors</value>
<value key="enabled">True</value>
<value key="sortable">False</value>
<value key="operations">
<element>plone.app.querystring.operation.string.currentUID</element>
</value>
<value key="group">Metadata</value>
</records>


Compatibility with Plone versions
---------------------------------

Expand Down
1 change: 1 addition & 0 deletions news/207.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow sorting query results by location (``path``), which orders items by their full physical path. @ericof
1 change: 1 addition & 0 deletions news/208.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the ``plone.app.querystring.operation.string.currentUID`` ("Current item") operation, which filters an index by the UID of the item where the query runs. Add-ons can enable it on fields for their own relation indexes. @ericof
8 changes: 8 additions & 0 deletions src/plone/app/querystring/profiles.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,12 @@
directory="profiles/upgrades/to_15"
/>

<genericsetup:registerProfile
name="upgrade_to_16"
title="Querystring Upgrade profile to v16"
description=""
provides="Products.GenericSetup.interfaces.EXTENSION"
directory="profiles/upgrades/to_16"
/>

</configure>
2 changes: 1 addition & 1 deletion src/plone/app/querystring/profiles/default/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<metadata>
<version>15</version>
<version>16</version>
<dependencies>
<dependency>profile-plone.app.registry:default</dependency>
</dependencies>
Expand Down
15 changes: 14 additions & 1 deletion src/plone/app/querystring/profiles/default/registry.xml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,19 @@
<value key="widget" />
</records>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.string.currentUID"
>
<value key="title"
i18n:translate=""
>Current item</value>
<value key="description"
i18n:translate=""
>The item where the querystring results are displayed</value>
<value key="operation">plone.app.querystring.queryparser._currentUID</value>
<value key="widget" />
</records>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.string.showInactive"
>
Expand Down Expand Up @@ -411,7 +424,7 @@
i18n:translate=""
>The location of an item </value>
<value key="enabled">True</value>
<value key="sortable">False</value>
<value key="sortable">True</value>
<value key="operations">
<element>plone.app.querystring.operation.string.absolutePath</element>
<element>plone.app.querystring.operation.string.path</element>
Expand Down
25 changes: 25 additions & 0 deletions src/plone/app/querystring/profiles/upgrades/to_16/registry.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<registry xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="plone"
>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.string.currentUID"
>
<value key="title"
i18n:translate=""
>Current item</value>
<value key="description"
i18n:translate=""
>The item where the querystring results are displayed</value>
<value key="operation">plone.app.querystring.queryparser._currentUID</value>
<value key="widget" />
</records>

<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.path"
>
<value key="sortable">True</value>
</records>

</registry>
8 changes: 8 additions & 0 deletions src/plone/app/querystring/queryparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,14 @@ def _currentUser(context, row):
return {row.index: {"query": user.getId()}}


def _currentUID(context, row):
"""Current item UID lookup"""
uid = IUUID(context, None)
if not uid:
return {}
return {row.index: {"query": uid}}


def _showInactive(context, row):
"""Current user roles lookup in order to determine whether user should
be allowed to view inactive content
Expand Down
74 changes: 74 additions & 0 deletions src/plone/app/querystring/tests/testQueryBuilder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from plone.app.querystring.testing import PLONEAPPQUERYSTRING_INTEGRATION_TESTING
from plone.app.querystring.testing import (
TEST_PROFILE_PLONEAPPQUERYSTRING_INTEGRATION_TESTING,
)
Expand Down Expand Up @@ -403,3 +404,76 @@ def setUp(self):

def testGettingJSONConfiguration(self):
self.folder.restrictedTraverse("@@querybuilderjsonconfig")()


class TestQuerybuilderDefaultProfile(unittest.TestCase):
layer = PLONEAPPQUERYSTRING_INTEGRATION_TESTING

def setUp(self):
self.portal = self.layer["portal"]
self.request = TestRequest()
self.portal.invokeFactory("Folder", "listing", title="Listing")
listing = self.portal.listing
listing.invokeFactory("Folder", "section-b", title="Section B")
listing["section-b"].invokeFactory("Document", "page", title="Page")
listing.invokeFactory("Document", "about", title="About")
listing.invokeFactory("Folder", "section-a", title="Section A")
self.listing = listing

def querybuilder(self, context):
return getMultiAdapter((context, self.request), name="querybuilderresults")

def test_sort_on_path(self):
query = [
{
"i": "path",
"o": "plone.app.querystring.operation.string.absolutePath",
"v": "/listing",
}
]
results = self.querybuilder(self.portal)(
query=query, sort_on="path", brains=True
)
self.assertEqual(
[brain.getPath() for brain in results],
[
"/plone/listing",
"/plone/listing/about",
"/plone/listing/section-a",
"/plone/listing/section-b",
"/plone/listing/section-b/page",
],
)

def test_sort_on_path_reverse(self):
query = [
{
"i": "path",
"o": "plone.app.querystring.operation.string.absolutePath",
"v": "/listing/section-b",
}
]
results = self.querybuilder(self.portal)(
query=query, sort_on="path", sort_order="reverse", brains=True
)
self.assertEqual(
[brain.getPath() for brain in results],
["/plone/listing/section-b/page", "/plone/listing/section-b"],
)

def test_current_uid(self):
page = self.listing["section-b"]["page"]
query = [
{
"i": "UID",
"o": "plone.app.querystring.operation.string.currentUID",
}
]
results = self.querybuilder(page)(query=query, brains=True)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].UID, page.UID())

# The same query run on another context finds that context instead
results = self.querybuilder(self.listing)(query=query, brains=True)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].UID, self.listing.UID())
22 changes: 22 additions & 0 deletions src/plone/app/querystring/tests/testQueryParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from plone.registry import Record
from plone.registry import Registry
from plone.registry.interfaces import IRegistry
from plone.uuid.interfaces import ATTRIBUTE_NAME
from plone.uuid.interfaces import IAttributeUUID
from Products.CMFCore.interfaces import ICatalogTool
from Products.CMFCore.interfaces import IMembershipTool
from Products.CMFCore.interfaces import IURLTool
Expand Down Expand Up @@ -104,6 +106,13 @@ class MockNavRoot(MockObject):
pass


@implementer(IAttributeUUID)
class MockUUIDObject(MockObject):
def __init__(self, uid, path):
super().__init__(uid, path)
setattr(self, ATTRIBUTE_NAME, uid)


class MockUser:
def __init__(self, userid=None, roles=None):
self.userid = userid or "Anonymous User"
Expand Down Expand Up @@ -492,6 +501,19 @@ def test__currentUser(self):
expected = {"Creator": {"query": "admin"}}
self.assertEqual(parsed, expected)

def test__currentUID(self):
context = MockUUIDObject(uid="00000000000000001", path="/%s/foo" % MOCK_SITE_ID)
data = Row(index="related_to", operator="_currentUID", values=None)
parsed = queryparser._currentUID(context, data)
expected = {"related_to": {"query": "00000000000000001"}}
self.assertEqual(parsed, expected)

def test__currentUID_without_uid(self):
context = MockObject(uid=None, path="/%s/foo" % MOCK_SITE_ID)
data = Row(index="related_to", operator="_currentUID", values=None)
parsed = queryparser._currentUID(context, data)
self.assertEqual(parsed, {})

def test__showInactive(self):
# Anonymous user
u = MockUser()
Expand Down
49 changes: 49 additions & 0 deletions src/plone/app/querystring/tests/testRegistryIntegration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from plone.app.querystring.testing import PLONEAPPQUERYSTRING_INTEGRATION_TESTING
from plone.app.testing import applyProfile

import unittest

Expand Down Expand Up @@ -36,6 +37,19 @@ def test_date_lessthan(self):
"plone.app.querystring.queryparser._dateLessThan",
)

def test_current_uid(self):
registry = self.portal.portal_registry
prefix = "plone.app.querystring.operation.string.currentUID"

self.assertTrue(prefix + ".title" in registry)

self.assertEqual(registry[prefix + ".title"], "Current item")
self.assertEqual(
registry[prefix + ".operation"],
"plone.app.querystring.queryparser._currentUID",
)
self.assertIsNone(registry[prefix + ".widget"])


class TestFieldDefinitions(unittest.TestCase):
layer = PLONEAPPQUERYSTRING_INTEGRATION_TESTING
Expand Down Expand Up @@ -80,3 +94,38 @@ def test_getobjpositioninparent_largerthan(self):
# check if operation is used for getObjPositionInParent
operations = registry.get(key)
self.assertTrue(operation in operations)

def test_path_sortable(self):
registry = self.portal.portal_registry
self.assertEqual(registry["plone.app.querystring.field.path.sortable"], True)


class TestUpgradeTo16(unittest.TestCase):
layer = PLONEAPPQUERYSTRING_INTEGRATION_TESTING

def setUp(self):
self.portal = self.layer["portal"]
registry = self.portal.portal_registry
# Bring the registry back to its state before version 16
prefix = "plone.app.querystring.operation.string.currentUID."
for key in [k for k in registry.records.keys() if k.startswith(prefix)]:
del registry.records[key]
registry["plone.app.querystring.field.path.sortable"] = False

def test_upgrade(self):
registry = self.portal.portal_registry
prefix = "plone.app.querystring.operation.string.currentUID"
self.assertFalse(prefix + ".title" in registry)

applyProfile(self.portal, "plone.app.querystring:upgrade_to_16")

self.assertEqual(registry[prefix + ".title"], "Current item")
self.assertEqual(
registry[prefix + ".operation"],
"plone.app.querystring.queryparser._currentUID",
)
field = "plone.app.querystring.field.path"
self.assertEqual(registry[field + ".sortable"], True)
# The other values of the path field are kept
self.assertEqual(registry[field + ".title"], "Location")
self.assertEqual(len(registry[field + ".operations"]), 3)
11 changes: 11 additions & 0 deletions src/plone/app/querystring/upgrades.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,15 @@
/>
</genericsetup:upgradeSteps>

<genericsetup:upgradeSteps
profile="plone.app.querystring:default"
source="15"
destination="16"
>
<genericsetup:upgradeDepends
title="Add 'currentUID' operation and make 'path' sortable."
import_profile="plone.app.querystring:upgrade_to_16"
/>
</genericsetup:upgradeSteps>

</configure>
Loading