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

Rewrite MetaFilter macro #1368

Open
wants to merge 2 commits into
base: staging
Choose a base branch
from
Open
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
77 changes: 43 additions & 34 deletions inyoka/wiki/macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
import itertools
import operator

from django.db.models import Q
from django.conf import settings
from django.utils.translation import gettext as _

from functools import reduce

from inyoka.markup import macros, nodes
from inyoka.markup.parsertools import MultiMap, flatten_iterator
from inyoka.markup.templates import expand_page_template
Expand Down Expand Up @@ -216,8 +219,16 @@ def build_node(self, context, format):


class FilterByMetaData(macros.Macro):
"""
Filter pages by their metadata
"""Filter pages by their metadata.

The metadata given can be combined with keywords to customize
the filtering. The ``NOT`` keyword negates the filter. The ``EXACT``
keyword uses direct matching and does not accept any other value
for the metadata key. Examples:

X-Link: mardi; tag: gras
X-Link: mardi, gras
tag: EXACT mardi; X-Link: NOT gras
"""

names = ('FilterByMetaData', 'MetaFilter')
Expand All @@ -233,42 +244,40 @@ def __init__(self, filters):
def build_node(self, context, format):
mapping = []
for part in self.filters:
# TODO: Can we do something else instead of skipping?
if ':' not in part:
continue
key = part.split(':')[0].strip()
values = [x.strip() for x in part.split(':')[1].split(',')]
mapping.extend([(key, x) for x in values])
if part.count(":") != 1:
return nodes.error_box(_('No result'),
_('Invalid filter syntax. Query: %(query)s') % {
'query': '; '.join(self.filters)})
key, values = part.split(":")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to the old variant, this will most likely raise an exception, if multiple : are in part.

Just a inspiration for a test case.

(Ideally, we can be sure that only exact one : is in part. If not, display a nodes.error_box that the query is invalid. As a side-effect, also the above TODO should be addressed.)

values = values.split(",")
mapping.extend([(key.strip(), x.strip()) for x in values])
mapping = MultiMap(mapping)

pages = set()

pages_query = Q()
exclude_query = Q()
for key in list(mapping.keys()):
values = list(flatten_iterator(mapping[key]))
includes = [x for x in values if not x.startswith('NOT ')]
kwargs = {'key': key, 'value__in': includes}
q = MetaData.objects.select_related('page').filter(**kwargs)
res = {
x.page
for x in q
if not is_privileged_wiki_page(x.page.name)
}
pages = pages.union(res)

# filter the pages with `AND`
res = set()
for key in list(mapping.keys()):
for page in pages:
e = [x[4:] for x in mapping[key] if x.startswith('NOT ')]
i = [x for x in mapping[key] if not x.startswith('NOT ')]
exclude = False
for val in set(page.metadata[key]):
if val in e:
exclude = True
if not exclude and set(page.metadata[key]) == set(i):
res.add(page)

names = [p.name for p in res]
includes = {x for x in values if not x.startswith(('NOT ', 'EXACT',))}
exclude_values = set()
kwargs = {'key': key}
if values[0].startswith("EXACT"):
exact_value = values[0].removeprefix("EXACT ")
kwargs['value__exact'] = exact_value
elif values[0].startswith("NOT"):
exclude_values.add(values[0].removeprefix("NOT "))
else:
kwargs['value__in'] = includes
pages_query |= Q(**kwargs)
exclude_query |= Q(**{"key": key, "value__in": exclude_values})

exclude_privileged_query = Q()
for prefix in settings.WIKI_PRIVILEGED_PAGES:
exclude_privileged_query |= Q(**{"page__name__startswith": prefix})

pages = MetaData.objects.select_related('page').filter(pages_query).exclude(
exclude_query).exclude(exclude_privileged_query)

names = {p.page.name for p in pages}
names = sorted(names, key=lambda s: s.lower())

if not names:
Expand Down
Loading