From 0daab7cdf869b39aecc8f46dcb682013724da24b Mon Sep 17 00:00:00 2001 From: David Glick Date: Thu, 17 Oct 2024 09:32:22 -0700 Subject: [PATCH] Backport filter improvements for URL Management control panel (#4032) * Merge pull request #4009 from Faakhir30/add_date_filters Added start and end filters in redirectionSet. * Allow substring matches when filtering aliases in the URL Management control panel --------- Co-authored-by: Faakhir Zahid <110815427+Faakhir30@users.noreply.github.com> --- .../controlpanel/browser/redirects.py | 52 +++++++++++++++---- .../test_controlpanel_browser_redirection.py | 38 +++++++++++--- news/4009.feature | 1 + news/4031.feature | 1 + 4 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 news/4009.feature create mode 100644 news/4031.feature diff --git a/Products/CMFPlone/controlpanel/browser/redirects.py b/Products/CMFPlone/controlpanel/browser/redirects.py index 46b128ca75..25e271028e 100644 --- a/Products/CMFPlone/controlpanel/browser/redirects.py +++ b/Products/CMFPlone/controlpanel/browser/redirects.py @@ -1,4 +1,5 @@ from csv import writer +import warnings from DateTime import DateTime from DateTime.interfaces import DateTimeError from io import StringIO @@ -163,7 +164,11 @@ def view_url(self): class RedirectionSet: - def __init__(self, query="", created="", manual=""): + # Marker so that plone.restapi can detect if this is + # a version that supports the start and end parameters + supports_date_range_filtering = True + + def __init__(self, query="", created="", manual="", start="", end=""): self.storage = getUtility(IRedirectionStorage) portal = getSite() @@ -171,7 +176,7 @@ def __init__(self, query="", created="", manual=""): self.portal_path_len = len(self.portal_path) # noinspection PyProtectedMember - if query: + if query and query.startswith("/"): # with query path /Plone/news: # min_k is /Plone/news and # max_k is /Plone/newt @@ -179,6 +184,8 @@ def __init__(self, query="", created="", manual=""): min_k = "{:s}/{:s}".format(self.portal_path, query.strip("/")) max_k = min_k[:-1] + chr(ord(min_k[-1]) + 1) self.data = self.storage._paths.keys(min=min_k, max=max_k, excludemax=True) + elif query: + self.data = [path for path in self.storage._paths.keys() if query in path] else: self.data = self.storage._paths.keys() if manual: @@ -190,20 +197,35 @@ def __init__(self, query="", created="", manual=""): else: manual = "" if created: + end = created + warnings.warn( + "The 'created' parameter is deprecated. Use 'end' parameter instead.", + DeprecationWarning, + ) + if start: + try: + start = DateTime(start) + except DateTimeError: + logger.warning("Failed to parse as DateTime: %s", start) + start = "" + if end: try: - created = DateTime(created) + end = DateTime(end) except DateTimeError: - logger.warning("Failed to parse as DateTime: %s", created) - created = "" - if created or manual != "": + logger.warning("Failed to parse as DateTime: %s", end) + end = "" + if start or end or manual != "": chosen = [] for redirect in self.data: info = self.storage.get_full(redirect) if manual != "": if info[2] != manual: continue - if created and info[1]: - if info[1] >= created: + if start and info[1]: + if info[1] < start: + continue + if end and info[1]: + if info[1] >= end: continue chosen.append(redirect) self.data = chosen @@ -256,6 +278,8 @@ def redirects(self): RedirectionSet( query=self.request.form.get("q", ""), created=self.request.form.get("datetime", ""), + start=self.request.form.get("start", ""), + end=self.request.form.get("end", ""), manual=self.request.form.get("manual", ""), ), int(self.request.form.get("b_size", "15")), @@ -280,9 +304,17 @@ def __call__(self): else: query = self.request.form.get("q", "") created = self.request.form.get("datetime", "") + start = self.request.form.get("start", "") + end = self.request.form.get("end", "") manual = self.request.form.get("manual", "") - if created or manual or (query and query != "/"): - rset = RedirectionSet(query=query, created=created, manual=manual) + if created or start or end or manual or (query and query != "/"): + rset = RedirectionSet( + query=query, + created=created, + manual=manual, + start=start, + end=end, + ) redirects = list(rset.data) else: redirects = [] diff --git a/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_redirection.py b/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_redirection.py index 3a219f5e57..2ba3fdde99 100644 --- a/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_redirection.py +++ b/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_redirection.py @@ -241,6 +241,9 @@ def test_redirection_controlpanel_filtering(self): # this should return one and not two (we need excludemax=True) redirects = RedirectionSet(query="/foo1/777") self.assertEqual(len(redirects), 1) + # query without an initial slash matches any substring + redirects = RedirectionSet(query="999") + self.assertEqual(len(redirects), 2) request = self.layer["request"].clone() request.form["q"] = "/foo" @@ -336,7 +339,7 @@ def test_redirection_controlpanel_filter_date(self): redirects = RedirectionSet() self.assertEqual(len(redirects), 400) - # created can be anything that can be parsed by DateTime. + # created can be anything that can be parsed by DateTime. (deprecated) # Otherwise it is ignored. self.assertEqual(len(RedirectionSet(created="2019-01-01")), 400) self.assertEqual(len(RedirectionSet(created="1999-01-01")), 0) @@ -346,38 +349,57 @@ def test_redirection_controlpanel_filter_date(self): self.assertEqual(len(RedirectionSet(created="2001-02-01 00:00:00")), 31) self.assertEqual(len(RedirectionSet(created="2001-02-01 00:00:01")), 32) self.assertEqual(len(RedirectionSet(created="badvalue")), 400) + # start is inclusive and can be anything that can be parsed by DateTime. + # Otherwise it is ignored. + self.assertEqual(len(RedirectionSet(start="2019-01-01")), 0) + self.assertEqual(len(RedirectionSet(start="2001-01-02")), 399) + self.assertEqual(len(RedirectionSet(start="2001-02-01 00:00:00")), 369) + self.assertEqual(len(RedirectionSet(start="2001-02-01 00:00:01")), 368) + self.assertEqual(len(RedirectionSet(start="badvalue")), 400) + + # End is exclisive and can be anything that can be parsed by DateTime. + # Otherwise it is ignored. + self.assertEqual(len(RedirectionSet(end="1999-01-01")), 0) + self.assertEqual(len(RedirectionSet(end="2000-01-01")), 0) + self.assertEqual(len(RedirectionSet(end="2001-02-01")), 31) + self.assertEqual(len(RedirectionSet(end="2001-02-01 00:00:00")), 31) + self.assertEqual(len(RedirectionSet(end="2001-02-01 00:00:01")), 32) + self.assertEqual(len(RedirectionSet(end="badvalue")), 400) + + self.assertEqual(len(RedirectionSet(start="2001-01-01", end="2001-01-01")), 0) + self.assertEqual(len(RedirectionSet(start="2001-01-01", end="2001-01-02")), 1) # DateTime('2002-01-01') results in a timezone GMT+0 - self.assertEqual(len(RedirectionSet(created="2002-01-01")), 365) + self.assertEqual(len(RedirectionSet(end="2002-01-01")), 365) # DateTime('2002/01/01') results in a timezone GMT+1 for me, # or a different zone depending on where in the world you are. # So we need to be lenient in the tests. - self.assertGreaterEqual(len(RedirectionSet(created="2002/01/01")), 364) - self.assertLessEqual(len(RedirectionSet(created="2002/01/01")), 366) + self.assertGreaterEqual(len(RedirectionSet(end="2002/01/01")), 364) + self.assertLessEqual(len(RedirectionSet(end="2002/01/01")), 366) request = self.layer["request"].clone() - request.form["datetime"] = "" + request.form["start"] = "" view = getMultiAdapter( (self.layer["portal"], request), name="redirection-controlpanel" ) self.assertEqual(view.redirects().numpages, math.ceil(400 / 15.0)) request = self.layer["request"].clone() - request.form["datetime"] = "2001-01-27" + request.form["end"] = "2001-01-27" view = getMultiAdapter( (self.layer["portal"], request), name="redirection-controlpanel" ) self.assertEqual(view.redirects().numpages, math.ceil(27 / 15.0)) request = self.layer["request"].clone() - request.form["datetime"] = "2002-01-01" + request.form["end"] = "2002-01-01" view = getMultiAdapter( (self.layer["portal"], request), name="redirection-controlpanel" ) self.assertEqual(view.redirects().numpages, math.ceil(365 / 15.0)) request = self.layer["request"].clone() - request.form["datetime"] = "2019-01-01" + request.form["end"] = "2019-01-01" view = getMultiAdapter( (self.layer["portal"], request), name="redirection-controlpanel" ) diff --git a/news/4009.feature b/news/4009.feature new file mode 100644 index 0000000000..1a58fdb77f --- /dev/null +++ b/news/4009.feature @@ -0,0 +1 @@ +Redirection control panel: Added support for start and end filters. @Faakhir30 diff --git a/news/4031.feature b/news/4031.feature new file mode 100644 index 0000000000..767054e50c --- /dev/null +++ b/news/4031.feature @@ -0,0 +1 @@ +URL Management control panel: Find substring matches when querying aliases. @davisagli