mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
fix(epg): improve parameter descriptions and enhance title whole word matching tests. Fix bug with postgres whole words (they use \y not \b for regex)
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
dbbc2f5735
commit
c486f641a8
2 changed files with 26 additions and 12 deletions
|
|
@ -183,15 +183,15 @@ class ProgramViewSet(viewsets.ModelViewSet):
|
|||
OpenApiTypes.STR,
|
||||
description='Title search query. Supports AND/OR operators (case-insensitive), quoted phrases, and parentheses. Double-quote a phrase to match it literally: `"Law and Order"`. Unquoted space-separated terms are matched as a phrase; use AND/OR to combine separate terms.',
|
||||
),
|
||||
OpenApiParameter('title_regex', OpenApiTypes.BOOL, description='Enable regex matching for title (case-insensitive). Example: `^The` matches titles starting with "The".'),
|
||||
OpenApiParameter('title_whole_words', OpenApiTypes.BOOL, description='Match whole words only in title. Prevents "NEW" from matching "News".'),
|
||||
OpenApiParameter('title_regex', OpenApiTypes.BOOL, description='Enable regex matching for title (case-insensitive, default: false). e.g. `^The` matches titles starting with "The".'),
|
||||
OpenApiParameter('title_whole_words', OpenApiTypes.BOOL, description='Match whole words only in title (default: false). e.g. `new` matches "Newcastle" normally but not with whole words enabled.'),
|
||||
OpenApiParameter(
|
||||
'description',
|
||||
OpenApiTypes.STR,
|
||||
description='Description search query. Same syntax and features as title search.'
|
||||
),
|
||||
OpenApiParameter('description_regex', OpenApiTypes.BOOL, description='Enable regex matching for description (case-insensitive).'),
|
||||
OpenApiParameter('description_whole_words', OpenApiTypes.BOOL, description='Match whole words only in description.'),
|
||||
OpenApiParameter('description_regex', OpenApiTypes.BOOL, description='Enable regex matching for description (case-insensitive, default: false).'),
|
||||
OpenApiParameter('description_whole_words', OpenApiTypes.BOOL, description='Match whole words only in description (default: false). Same behaviour as title_whole_words.'),
|
||||
OpenApiParameter('start_after', OpenApiTypes.DATETIME, description='Filter programs starting at or after this time. ISO 8601 format, e.g. `2026-02-14T18:00:00Z`.'),
|
||||
OpenApiParameter('start_before', OpenApiTypes.DATETIME, description='Filter programs starting at or before this time. ISO 8601 format.'),
|
||||
OpenApiParameter('end_after', OpenApiTypes.DATETIME, description='Filter programs ending at or after this time. ISO 8601 format.'),
|
||||
|
|
@ -780,8 +780,14 @@ def _build_q_object(field_name, term, use_regex=False, whole_words=False):
|
|||
# Use Django's __iregex (case-insensitive regex)
|
||||
return Q(**{f'{field_name}__iregex': term})
|
||||
elif whole_words:
|
||||
# Use word boundary regex (case-insensitive)
|
||||
pattern = r'\b' + re.escape(term) + r'\b'
|
||||
# Word boundary syntax differs by database engine:
|
||||
# PostgreSQL uses \y (or \m/\M); Python re (SQLite) uses \b.
|
||||
from django.db import connection
|
||||
if connection.vendor == 'postgresql':
|
||||
boundary = r'\y'
|
||||
else:
|
||||
boundary = r'\b'
|
||||
pattern = boundary + re.escape(term) + boundary
|
||||
return Q(**{f'{field_name}__iregex': pattern})
|
||||
else:
|
||||
# Standard case-insensitive contains
|
||||
|
|
|
|||
|
|
@ -191,14 +191,22 @@ class ProgramSearchAPIViewTests(TestCase):
|
|||
self.assertEqual(data["results"], [])
|
||||
|
||||
def test_title_whole_word_matching(self):
|
||||
"""title_whole_words=true does not match partial words."""
|
||||
# 'new' as substring matches both 'Newcastle vs Villa' and 'BBC News at Ten'
|
||||
"""title_whole_words=true matches complete words but not partial words."""
|
||||
# 'new' as substring matches 'Newcastle vs Villa' and 'BBC News at Ten'
|
||||
partial = self.client.get(SEARCH_URL, {"title": "new"}).json()
|
||||
whole = self.client.get(SEARCH_URL, {"title": "new", "title_whole_words": "true"}).json()
|
||||
# icontains matches 'new' inside 'Newcastle' and 'News'
|
||||
# Whole-word \bnew\b matches neither 'Newcastle' nor 'News' (partial matches)
|
||||
whole_no_match = self.client.get(SEARCH_URL, {"title": "new", "title_whole_words": "true"}).json()
|
||||
self.assertEqual(partial["count"], 2)
|
||||
# Whole-word \bnew\b matches neither 'Newcastle' nor 'News'
|
||||
self.assertEqual(whole["count"], 0)
|
||||
self.assertEqual(whole_no_match["count"], 0)
|
||||
|
||||
# 'football' is a complete word in 'Premier League Football' — must still match
|
||||
whole_match = self.client.get(SEARCH_URL, {"title": "football", "title_whole_words": "true"}).json()
|
||||
self.assertEqual(whole_match["count"], 1)
|
||||
self.assertEqual(whole_match["results"][0]["title"], "Premier League Football")
|
||||
|
||||
# 'league' is also a complete word — and 'Premier AND league' with whole_words works
|
||||
both_words = self.client.get(SEARCH_URL, {"title": "Premier AND league", "title_whole_words": "true"}).json()
|
||||
self.assertEqual(both_words["count"], 1)
|
||||
|
||||
def test_title_regex(self):
|
||||
"""title_regex=true applies the query as a regex pattern."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue