import shlex from collections.abc import Iterator from django.contrib.postgres.search import SearchQuery from .models.assemblies import Assembly from .models.conference import Conference, ConferenceTrack from .models.events import Event from .models.rooms import Room from .models.tags import ConferenceTag from .models.users import PlatformUser def search( user: PlatformUser, conference: Conference, search_term: str, max_per_category: int = 10 ) -> Iterator[ConferenceTag | ConferenceTrack | Assembly | Event | Room]: """ Search assemblies, events, pages and tags for the search_term(s). Matches on the name are ranked higher than those only in the description. """ search_q = SearchQuery(search_term, search_type='websearch') try: terms = shlex.split(search_term) except ValueError: return include_terms = [term for term in terms if not term.startswith('-')] exclude_terms = [term for term in terms if term.startswith('-')] def apply_terms_name(qs): for term in include_terms: qs = qs.filter(name__icontains=term) for term in exclude_terms: qs = qs.exclude(name__icontains=term) return qs def apply_terms_description(qs): for term in include_terms: qs = qs.filter(description__icontains=term) for term in exclude_terms: qs = qs.exclude(description__icontains=term) return qs # matching tags are the best tags = ConferenceTag.objects.filter(conference=conference, is_public=True) if len(include_terms) > 0: for term in include_terms: tags = tags.filter(slug__icontains=term) if len(exclude_terms) > 0: for term in exclude_terms: tags = tags.exclude(slug__icontains=term) yield from tags[:max_per_category] # matching tracks are good, too tracks = conference.tracks.filter(is_public=True) for term in include_terms: tracks = tracks.filter(name__icontains=term) for term in exclude_terms: tracks = tracks.exclude(name__icontains=term) yield from tracks[:max_per_category] # search static pages pages = conference.pages.filter(public_revision__gte=0) yield from pages.filter(search_vector=search_q) # tracking which assemblies we've already seen so we don't return them twice assemblies_seen = [] events_seen = [] rooms_seen = [] # name match on Assembly assemblies = apply_terms_name(Assembly.objects.conference_accessible(conference=conference))[:max_per_category] for v in assemblies: assemblies_seen.append(v.pk) yield v # name match on Event events = apply_terms_name(Event.objects.conference_accessible(conference=conference))[:max_per_category] for e in events: events_seen.append(e.pk) yield e # name match on Room rooms = apply_terms_name(Room.objects.conference_accessible(conference=conference))[:max_per_category] for r in rooms: rooms_seen.append(r.pk) yield r # description match on Assembly if len(assemblies) < max_per_category: description_matches = Assembly.objects.conference_accessible(conference=conference).exclude(pk__in=assemblies_seen) yield from apply_terms_description(description_matches)[: max_per_category - len(assemblies)] # description match on Event if len(events) < max_per_category: description_matches = Event.objects.conference_accessible(conference=conference).exclude(pk__in=events_seen) yield from apply_terms_description(description_matches)[: max_per_category - len(events)] # description match on Room if len(rooms) < max_per_category: description_matches = Room.objects.conference_accessible(conference=conference).exclude(pk__in=rooms_seen) yield from apply_terms_description(description_matches)[: max_per_category - len(rooms)]