Skip to content

Commit

Permalink
Merge pull request #110 from plone/thet-excludes
Browse files Browse the repository at this point in the history
Add negative query support: string.isNot and selection.none
  • Loading branch information
jensens authored May 24, 2022
2 parents 08376be + 6b2b8e5 commit a1e7091
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 7 deletions.
3 changes: 3 additions & 0 deletions news/110.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add negation-query operators string.isNot and selection.none.
New ``plone.app.querystring.operation.string.isNot`` and ``plone.app.querystring.operation.selection.none`` including upgrade steps.
[thet]
1 change: 1 addition & 0 deletions plone/app/querystring/hiddenprofiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ def getNonInstallableProfiles(self):
'plone.app.querystring:upgrade_to_9',
'plone.app.querystring:upgrade_to_10',
'plone.app.querystring:upgrade_to_11',
'plone.app.querystring:upgrade_to_14',
]
18 changes: 14 additions & 4 deletions plone/app/querystring/indexmodifiers/query_index_modifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,19 @@ class Subject(object):
"""

def __call__(self, value):
query = value['query']
if not six.PY2:
return ('Subject', value)

# Get the query operator
op = None
if 'query' in value:
op = 'query'
elif 'not' in value:
op = 'not'

query = value[op]
# query can be a unicode string or a list of unicode strings.
if six.PY2 and isinstance(query, six.text_type):
if isinstance(query, six.text_type):
query = query.encode("utf-8")
elif isinstance(query, list):
# We do not want to change the collections' own query string,
Expand All @@ -34,13 +44,13 @@ def __call__(self, value):
# unicode strings
i = 0
for item in copy_of_query:
if six.PY2 and isinstance(item, six.text_type):
if isinstance(item, six.text_type):
copy_of_query[i] = item.encode("utf-8")
i += 1
query = copy_of_query
else:
pass
value['query'] = query
value[op] = query
return ('Subject', value)


Expand Down
8 changes: 8 additions & 0 deletions plone/app/querystring/profiles.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,12 @@
provides="Products.GenericSetup.interfaces.EXTENSION"
/>

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

</configure>
2 changes: 1 addition & 1 deletion 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"?>
<metadata>
<version>13</version>
<version>14</version>
<dependencies>
<dependency>profile-plone.app.registry:default</dependency>
</dependencies>
Expand Down
22 changes: 22 additions & 0 deletions plone/app/querystring/profiles/default/registry.xml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@
<value key="widget">StringWidget</value>
</records>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.string.isNot">
<value key="title" i18n:translate="">Is not</value>
<value key="description" i18n:translate="">Tip: you can use * to autocomplete.</value>
<value key="operation">plone.app.querystring.queryparser._excludes</value>
<value key="widget">StringWidget</value>
</records>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.selection.is">
<value key="title" i18n:translate="">Is</value>
Expand Down Expand Up @@ -234,6 +242,14 @@
<value key="widget">MultipleSelectionWidget</value>
</records>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.selection.none">
<value key="title" i18n:translate="">Matches none of</value>
<value key="description" i18n:translate="">Tip: you can use * to autocomplete.</value>
<value key="operation">plone.app.querystring.queryparser._excludes</value>
<value key="widget">MultipleSelectionWidget</value>
</records>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.selection.all">
<value key="title" i18n:translate="">Matches all of</value>
Expand Down Expand Up @@ -265,6 +281,7 @@
<value key="sortable">True</value>
<value key="operations">
<element>plone.app.querystring.operation.string.is</element>
<element>plone.app.querystring.operation.string.isNot</element>
</value>
<value key="group" i18n:translate="">Metadata</value>
</records>
Expand Down Expand Up @@ -299,6 +316,7 @@
<value key="operations">
<element>plone.app.querystring.operation.string.currentUser</element>
<element>plone.app.querystring.operation.selection.any</element>
<element>plone.app.querystring.operation.selection.none</element>
</value>
<value key="vocabulary">plone.app.vocabularies.Users</value>
<value key="group" i18n:translate="">Metadata</value>
Expand Down Expand Up @@ -471,6 +489,7 @@
<value key="sortable">False</value>
<value key="operations">
<element>plone.app.querystring.operation.selection.any</element>
<element>plone.app.querystring.operation.selection.none</element>
</value>
<value key="vocabulary">plone.app.vocabularies.ReallyUserFriendlyTypes</value>
<value key="group" i18n:translate="">Metadata</value>
Expand All @@ -497,6 +516,7 @@
<value key="sortable">True</value>
<value key="operations">
<element>plone.app.querystring.operation.selection.any</element>
<element>plone.app.querystring.operation.selection.none</element>
</value>
<value key="vocabulary">plone.app.vocabularies.WorkflowStates</value>
<value key="group" i18n:translate="">Metadata</value>
Expand All @@ -523,6 +543,7 @@
<value key="operations">
<element>plone.app.querystring.operation.string.contains</element>
<element>plone.app.querystring.operation.string.is</element>
<element>plone.app.querystring.operation.string.isNot</element>
</value>
<value key="group" i18n:translate="">Text</value>
</records>
Expand Down Expand Up @@ -557,6 +578,7 @@
<value key="operations">
<element>plone.app.querystring.operation.selection.any</element>
<element>plone.app.querystring.operation.selection.all</element>
<element>plone.app.querystring.operation.selection.none</element>
</value>
<value key="vocabulary">plone.app.vocabularies.Keywords</value>
<value key="group" i18n:translate="">Text</value>
Expand Down
72 changes: 72 additions & 0 deletions plone/app/querystring/profiles/upgrades/to_14/registry.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<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.isNot">
<value key="title" i18n:translate="">Is not</value>
<value key="description" i18n:translate="">Tip: you can use * to autocomplete.</value>
<value key="operation">plone.app.querystring.queryparser._excludes</value>
<value key="widget">StringWidget</value>
</records>

<records interface="plone.app.querystring.interfaces.IQueryOperation"
prefix="plone.app.querystring.operation.selection.none">
<value key="title" i18n:translate="">Matches none of</value>
<value key="description" i18n:translate="">Tip: you can use * to autocomplete.</value>
<value key="operation">plone.app.querystring.queryparser._excludes</value>
<value key="widget">MultipleSelectionWidget</value>
</records>

<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.getId"
purge="False">
<value key="operations" purge="False">
<element>plone.app.querystring.operation.string.isNot</element>
</value>
</records>

<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.sortable_title"
purge="False">
<value key="operations" purge="False">
<element>plone.app.querystring.operation.string.isNot</element>
</value>
</records>


<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.Creator"
purge="False">
<value key="operations" purge="False">
<element>plone.app.querystring.operation.selection.none</element>
</value>
</records>


<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.portal_type"
purge="False">
<value key="operations" purge="False">
<element>plone.app.querystring.operation.selection.none</element>
</value>
</records>


<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.review_state"
purge="False">
<value key="operations" purge="False">
<element>plone.app.querystring.operation.selection.none</element>
</value>
</records>


<records interface="plone.app.querystring.interfaces.IQueryField"
prefix="plone.app.querystring.field.Subject"
purge="False">
<value key="operations" purge="False">
<element>plone.app.querystring.operation.selection.none</element>
</value>
</records>

</registry>
4 changes: 4 additions & 0 deletions plone/app/querystring/queryparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ def _contains(context, row):
return _equal(context, row)


def _excludes(context, row):
return {row.index: {'not': row.values}}


def _equal(context, row):
return {row.index: {'query': row.values, }}

Expand Down
13 changes: 12 additions & 1 deletion plone/app/querystring/tests/testIndexmodifiers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from DateTime import DateTime
from datetime import datetime
from DateTime import DateTime
from plone.app.querystring.indexmodifiers import query_index_modifiers

import unittest
Expand All @@ -13,6 +13,16 @@ def test_subject_encoded(self):
query_index_modifiers.Subject()({'query': u'foobar'}),
('Subject', {'query': u'foobar'}))

def test_subject_encoded__list(self):
self.assertEqual(
query_index_modifiers.Subject()({'query': [u'foobar']}),
('Subject', {'query': [u'foobar']}))

def test_subject_encoded__list_not(self):
self.assertEqual(
query_index_modifiers.Subject()({'not': [u'foobar']}),
('Subject', {'not': ['foobar']}))

def test_date_modifier(self):
modifier = query_index_modifiers.start()
self.assertTrue(
Expand All @@ -32,6 +42,7 @@ def test_date_modifier_list(self):
def test_date_modifier_list_DateTime(self):
"""Test a case with largerThanRelativeDate operatiors, where
plone.app.querystring.querybuilder parses a querystring like this one:
>>> query
[{
u'i': u'end',
Expand Down
61 changes: 61 additions & 0 deletions plone/app/querystring/tests/testQueryBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,35 @@ def testMakeQuery(self):
results[0].getURL(),
'http://nohost/plone/collectionstestpage')

def testQueryStringIs(self):
query = [{
'i': 'sortable_title',
'o': 'plone.app.querystring.operation.string.is',
'v': 'collectionstestpage',
}]

# Test normal, without custom_query.
results = self.querybuilder._makequery(query=query)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].Title(), 'Collectionstestpage')

def testQueryStringIsNot(self):
query = [{
'i': 'portal_type',
'o': 'plone.app.querystring.operation.selection.none',
'v': 'Plone Site',
}, {
'i': 'sortable_title',
'o': 'plone.app.querystring.operation.string.isNot',
'v': 'collectionstestpage',
}]

# Test normal, without custom_query.
results = self.querybuilder._makequery(query=query)
print([it.Title() for it in results])
self.assertEqual(len(results), 1)
self.assertEqual(results[0].Title(), 'Test Folder')

def testMakeQueryWithSubject(self):
self.testpage.setSubject(['Lorem'])
self.testpage.reindexObject()
Expand All @@ -83,6 +112,22 @@ def testMakeQueryWithSubject(self):
results[0].getURL(),
'http://nohost/plone/collectionstestpage')

def testMakeQueryWithSubjectNot(self):
self.folder.setSubject(['Ipsum'])
self.folder.reindexObject()
self.testpage.setSubject(['Lorem'])
self.testpage.reindexObject()
query = [{
'i': 'Subject',
'o': 'plone.app.querystring.operation.selection.none',
'v': 'Lorem',
}]
results = self.querybuilder._makequery(query=query)
self.assertEqual(len(results), 1)
self.assertEqual(
results[0].getURL(),
'http://nohost/plone/testfolder')

def testMakeQueryWithMultipleSubject(self):
self.testpage.setSubject(['Lorem'])
self.testpage.reindexObject()
Expand All @@ -97,6 +142,22 @@ def testMakeQueryWithMultipleSubject(self):
results[0].getURL(),
'http://nohost/plone/collectionstestpage')

def testMakeQueryWithMultipleSubjectNot(self):
self.folder.setSubject(['Ipsum'])
self.folder.reindexObject()
self.testpage.setSubject(['Lorem'])
self.testpage.reindexObject()
query = [{
'i': 'Subject',
'o': 'plone.app.querystring.operation.selection.none',
'v': ['Lorem', 'Dolor'],
}]
results = self.querybuilder._makequery(query=query)
self.assertEqual(len(results), 1)
self.assertEqual(
results[0].getURL(),
'http://nohost/plone/testfolder')

def testMakeQueryWithSubjectWithSpecialCharacters(self):
self.testpage.setSubject(['Äüö'])
self.testpage.reindexObject()
Expand Down
5 changes: 4 additions & 1 deletion plone/app/querystring/tests/testRegistryIntegration.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ def test_getId(self):
self.assertEqual(registry[prefix + ".title"], "Short name (id)")

operations = registry[prefix + ".operations"]
self.assertEqual(len(operations), 1)
self.assertEqual(len(operations), 2)

equal = 'plone.app.querystring.operation.string.is'
self.assertTrue(equal in operations)

exclude = 'plone.app.querystring.operation.string.isNot'
self.assertTrue(exclude in operations)

self.assertEqual(registry[prefix + ".description"],
"The short name of an item (used in the url)")
self.assertEqual(registry[prefix + ".enabled"], True)
Expand Down
10 changes: 10 additions & 0 deletions plone/app/querystring/upgrades.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,14 @@
/>
</genericsetup:upgradeSteps>

<genericsetup:upgradeSteps
source="13"
destination="14"
profile="plone.app.querystring:default">
<genericsetup:upgradeDepends
title="Add new 'string.isNot' and 'selection.none' query operators."
import_profile="plone.app.querystring:upgrade_to_14"
/>
</genericsetup:upgradeSteps>

</configure>

0 comments on commit a1e7091

Please sign in to comment.