Skip to content

Commit 087a114

Browse files
committed
add smarter searching; fix bug to allow 0.0 for free events
1 parent 0f44570 commit 087a114

File tree

3 files changed

+91
-17
lines changed

3 files changed

+91
-17
lines changed

backend/apps/clubs/views.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,37 @@ def get_clubs(request):
2828

2929
# Apply search filter
3030
if search_term:
31-
queryset = queryset.filter(club_name__icontains=search_term)
31+
import re
32+
from django.db.models import Q
33+
34+
# 1. Standard search
35+
search_query = Q(club_name__icontains=search_term)
36+
37+
# 2. Normalized search using translate
38+
normalized_term = re.sub(r'[^a-z0-9]', '', search_term.lower())
39+
40+
if normalized_term and normalized_term != search_term.lower():
41+
# Common accented chars
42+
accents_from = "àáâãäåèéêëìíîïòóôõöùúûüýÿñç"
43+
accents_to = "aaaaaaeeeeiiiiooooouuuuyync"
44+
45+
# Special chars to remove
46+
special_from = "-_.,!?:;()[]{}|/\\ "
47+
48+
map_from = accents_from + special_from
49+
map_to = accents_to
50+
51+
translate_sql = f"TRANSLATE(LOWER(club_name), '{map_from}', '{map_to}') LIKE %s"
52+
like_param = f"%{normalized_term}%"
53+
54+
search_query |= Q(
55+
id__in=queryset.extra(
56+
where=[translate_sql],
57+
params=[like_param]
58+
).values_list('id', flat=True)
59+
)
60+
61+
queryset = queryset.filter(search_query)
3262

3363
# Apply category filter
3464
if category_filter and category_filter.lower() != "all":

backend/apps/events/views.py

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,27 @@ def get_events(request):
101101

102102
if search_term:
103103
import re
104+
from django.db.models import Func, Value, F
104105

105106
# Parse semicolon-separated filters (for OR query)
106107
search_terms = [
107108
term.strip() for term in search_term.split(";") if term.strip()
108109
]
109110

110-
# Build OR query: match any of the search terms in any field
111+
# Define character mapping for translate
112+
# Common accented chars
113+
accents_from = "àáâãäåèéêëìíîïòóôõöùúûüýÿñç"
114+
accents_to = "aaaaaaeeeeiiiiooooouuuuyync"
115+
116+
# Special chars to remove
117+
special_from = "-_.,!?:;()[]{}|/\\ "
118+
map_from = accents_from + special_from
119+
map_to = accents_to
120+
121+
# Build OR query
111122
or_queries = Q()
112123
for term in search_terms:
113-
# Search with original term (exact/partial matches)
124+
# 1. Standard search (exact/partial matches)
114125
term_query = (
115126
Q(title__icontains=term)
116127
| Q(location__icontains=term)
@@ -125,20 +136,39 @@ def get_events(request):
125136
| Q(fb_handle__icontains=term)
126137
)
127138

128-
# Search with normalized term
139+
# 2. Normalized search using translate
129140
normalized_term = re.sub(r'[^a-z0-9]', '', term.lower())
130141
if normalized_term and normalized_term != term.lower():
131-
term_query |= (
132-
Q(title__icontains=normalized_term)
133-
| Q(location__icontains=normalized_term)
134-
| Q(description__icontains=normalized_term)
135-
| Q(food__icontains=normalized_term)
136-
| Q(club_type__icontains=normalized_term)
137-
| Q(ig_handle__icontains=normalized_term)
138-
| Q(discord_handle__icontains=normalized_term)
139-
| Q(x_handle__icontains=normalized_term)
140-
| Q(tiktok_handle__icontains=normalized_term)
141-
| Q(fb_handle__icontains=normalized_term)
142+
# Func for translate:
143+
# translate(lower(field), map_from, map_to)
144+
class Translate(Func):
145+
function = 'TRANSLATE'
146+
template = "%(function)s(LOWER(%(expressions)s), '%(from)s', '%(to)s')"
147+
148+
# Use .extra() to filter on normalized fields (Django doesn't support function calls in filter keys)
149+
translate_sql = f"TRANSLATE(LOWER({{}}), '{map_from}', '{map_to}') LIKE %s"
150+
like_param = f"%{normalized_term}%"
151+
fields = [
152+
"title", "location", "description", "food",
153+
"club_type", "ig_handle", "discord_handle",
154+
"x_handle", "tiktok_handle", "fb_handle"
155+
]
156+
where_clauses = []
157+
params = []
158+
for field in fields:
159+
# Handle COALESCE for nullable fields
160+
col_ref = field
161+
if field not in ["title"]: # title is nullable but usually present, others might be null
162+
col_ref = f"COALESCE({field}, '')"
163+
where_clauses.append(translate_sql.format(col_ref))
164+
params.append(like_param)
165+
166+
full_where = " OR ".join(where_clauses)
167+
term_query |= Q(
168+
id__in=filtered_queryset.extra(
169+
where=[full_where],
170+
params=params
171+
).values_list('id', flat=True)
142172
)
143173

144174
or_queries |= term_query

backend/utils/scraping_utils.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def insert_event_to_db(event_data, ig_handle, source_url, club_type=None):
132132
"club_type": club_type[:50] if club_type else None,
133133
"location": location,
134134
"food": food[:255] if food else None,
135-
"price": price or None,
135+
"price": price,
136136
"registration": registration,
137137
"description": description or None,
138138
"reactions": {},
@@ -238,8 +238,22 @@ def _check_same_club_update(self, ig_handle, title, log_prefix):
238238
if not ig_handle:
239239
return None
240240

241-
same_club_events = Events.objects.filter(ig_handle=ig_handle)
241+
same_club_events = Events.objects.filter(ig_handle=ig_handle).prefetch_related("event_dates")
242+
now = timezone.now()
243+
242244
for existing_event in same_club_events:
245+
# Check if event is in the past
246+
dates = list(existing_event.event_dates.all())
247+
if not dates:
248+
continue
249+
250+
# Get the latest end time (or start time)
251+
latest_end = max((d.dtend_utc or d.dtstart_utc) for d in dates)
252+
253+
# If the event has already passed, treat as new event
254+
if latest_end < now:
255+
continue
256+
243257
c_title = getattr(existing_event, "title", "") or ""
244258

245259
title_sim = max(

0 commit comments

Comments
 (0)