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

Adjust filters for long term events #3410

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
9 changes: 6 additions & 3 deletions integreat_cms/cms/constants/recurrence.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@

#: Events that are recurring, i.e. take place more than once
RECURRING: Final = 1
#: Events that are not recurring, i.e. take place only once
NOT_RECURRING: Final = 2
#: Events that are not recurring and only take place on one day
SINGLE_DAY: Final = 2
#: Events that are not recurring but take place over a long period of time
LONG_TERM: Final = 3

#: Choices to use these constants in a database field
CHOICES: Final[list[tuple[int, Promise]]] = [
(RECURRING, _("Recurring events")),
(NOT_RECURRING, _("Non-recurring events")),
(SINGLE_DAY, _("Single day events")),
(LONG_TERM, _("Long term events")),
]
145 changes: 101 additions & 44 deletions integreat_cms/cms/forms/events/event_filter_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import TYPE_CHECKING

from django import forms
from django.db.models import F, Q

if TYPE_CHECKING:
from ...models import Region
Expand Down Expand Up @@ -82,35 +83,24 @@ class EventFilterForm(CustomFilterForm):

query = forms.CharField(required=False)

def apply(
self,
events: EventQuerySet,
region: Region,
language_slug: str,
) -> tuple[EventQuerySet, None, None]:
def filter_events_by_time_range(self, events: EventQuerySet) -> EventQuerySet:
"""
Filter the events according to the given filter data
Filter events by time range

:param events: The list of events
:param region: The current region
:param language_slug: The slug of the current language
:return: The filtered list of events, the poi used for filtering, and the search query
:param events: the unfiltered events
:param events: the filtered events
"""
if not self.is_enabled:
events = events.filter_upcoming()
return events, None, None
return events

# Filter events by time range
cleaned_time_range = self.cleaned_data["events_time_range"]
if not cleaned_time_range or set(cleaned_time_range) == set(
events_time_range.ALL_EVENTS,
):
# Either post & upcoming or no checkboxes are checked => skip filtering
# We do this to check if either all options or none were selected from this group
pass
elif events_time_range.CUSTOM in cleaned_time_range:
# Filter events for their start and end
tzinfo = zoneinfo.ZoneInfo(region.timezone)
return events

if events_time_range.CUSTOM in cleaned_time_range:
tzinfo = zoneinfo.ZoneInfo(self.region.timezone)
if date_from := self.cleaned_data["date_from"]:
from_local = datetime.combine(date_from, time.min, tzinfo=tzinfo)
else:
Expand All @@ -122,53 +112,83 @@ def apply(
)
events = events.filter_upcoming(from_local).filter(end__lte=to_local)
elif events_time_range.UPCOMING in cleaned_time_range:
# Only upcoming events
events = events.filter_upcoming()
elif events_time_range.PAST in cleaned_time_range:
# Only past events
events = events.filter_completed()
# Filter events for their location
if poi := region.pois.filter(id=self.cleaned_data["poi_id"]).first():
return events

def filter_events_by_location(self, events: EventQuerySet) -> EventQuerySet:
"""
Filter events by location

:param events: the unfiltered events
:param events: the filtered events
"""
if poi := self.region.pois.filter(id=self.cleaned_data["poi_id"]).first():
events = events.filter(location=poi)
# Filter events for their all-day property
return events

def filter_events_by_all_day(self, events: EventQuerySet) -> EventQuerySet:
"""
Filter events by all-day property

:param events: the unfiltered events
:param events: the filtered events
"""
if (
len(self.cleaned_data["all_day"]) == len(all_day.CHOICES)
or not self.cleaned_data["all_day"]
):
# Either all or no checkboxes are checked => skip filtering
pass
elif all_day.ALL_DAY in self.cleaned_data["all_day"]:
# Filter for all-day events
return events
if all_day.ALL_DAY in self.cleaned_data["all_day"]:
events = events.filter(
start__time=time.min,
end__time=time.max.replace(second=0, microsecond=0),
)
elif all_day.NOT_ALL_DAY in self.cleaned_data["all_day"]:
# Exclude all-day events
events = events.exclude(
start__time=time.min,
end__time=time.max.replace(second=0, microsecond=0),
)
# Filter events for recurrence
return events

def filter_events_by_recurrence(self, events: EventQuerySet) -> EventQuerySet:
"""
Filter events by recurrence

:param events: the unfiltered events
:param events: the filtered events
"""
if (
len(self.cleaned_data["recurring"]) == len(recurrence.CHOICES)
or not self.cleaned_data["recurring"]
):
# Either all or no checkboxes are checked => skip filtering
pass
elif recurrence.RECURRING in self.cleaned_data["recurring"]:
# Only recurring events
events = events.filter(recurrence_rule__isnull=False)
elif recurrence.NOT_RECURRING in self.cleaned_data["recurring"]:
# Only non-recurring events
events = events.filter(recurrence_rule__isnull=True)
return events

query = Q()

if recurrence.RECURRING in self.cleaned_data["recurring"]:
query |= Q(recurrence_rule__isnull=False)
if recurrence.SINGLE_DAY in self.cleaned_data["recurring"]:
query |= Q(start__date=F("end__date"), recurrence_rule__isnull=True)
if recurrence.LONG_TERM in self.cleaned_data["recurring"]:
query |= Q(~Q(start__date=F("end__date")), recurrence_rule__isnull=True)

return events.filter(query)

def filter_events_by_imported_event(self, events: EventQuerySet) -> EventQuerySet:
"""
Filter events by imported event

:param events: the unfiltered events
:param events: the filtered events
"""
if (
len(self.cleaned_data["imported_event"]) == len(calendar_filters.CHOICES)
or not self.cleaned_data["imported_event"]
):
pass
elif (
return events
if (
calendar_filters.EVENT_NOT_FROM_EXTERNAL_CALENDAR
in self.cleaned_data["imported_event"]
):
Expand All @@ -178,12 +198,49 @@ def apply(
in self.cleaned_data["imported_event"]
):
events = events.filter(external_calendar__isnull=False)
return events

def search_events(self, events: EventQuerySet) -> EventQuerySet:
"""
Search events for given query

# Filter events by the search query
:param events: The unsearched events
:return events: The searched events
"""
if query := self.cleaned_data["query"]:
event_ids = EventTranslation.search(region, language_slug, query).values(
event_ids = EventTranslation.search(
self.region, self.language_slug, query
).values(
"event__pk",
)
events = events.filter(pk__in=event_ids)
return events

def apply(
self,
events: EventQuerySet,
region: Region,
language_slug: str,
) -> tuple[EventQuerySet, None, None]:
"""
Filter the events according to the given filter data

:param events: The list of events
:param region: The current region
:param language_slug: The slug of the current language
:return: The filtered list of events, the poi used for filtering, and the search query
"""
self.region = region
self.language_slug = language_slug

if not self.is_enabled:
events = events.filter_upcoming()

events = self.filter_events_by_time_range(events)
events = self.filter_events_by_location(events)
events = self.filter_events_by_all_day(events)
events = self.filter_events_by_recurrence(events)
events = self.filter_events_by_imported_event(events)
events = self.search_events(events)

return events, poi, query
return events, None, None
26 changes: 15 additions & 11 deletions integreat_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -1673,8 +1673,12 @@ msgid "Recurring events"
msgstr "Sich wiederholende Veranstaltungen"

#: cms/constants/recurrence.py
msgid "Non-recurring events"
msgstr "Einmalig stattfindende Veranstaltungen"
msgid "Single day events"
msgstr "Einmalige Veranstaltungen"

#: cms/constants/recurrence.py
msgid "Long term events"
msgstr "Fortlaufende Veranstaltungen"

#: cms/constants/region_status.py
#: cms/templates/languages/region_list_section/region_list.html
Expand Down Expand Up @@ -11396,6 +11400,9 @@ msgstr ""
"Diese Seite konnte nicht importiert werden, da sie zu einer anderen Region "
"gehört ({})."

#~ msgid "Non-recurring events"
#~ msgstr "Einmalig stattfindende Veranstaltungen"

#~ msgid "Administrative division"
#~ msgstr "Verwaltungseinheit"

Expand Down Expand Up @@ -11574,15 +11581,15 @@ msgstr ""
#~ "Übersetzungen. Bitte haben Sie noch ein wenig Geduld."

#~ msgid ""
#~ "Your pages currently have <b>"
#~ "%(number_of_missing_or_outdated_translations)s pages </b>that have an "
#~ "Your pages currently have "
#~ "<b>%(number_of_missing_or_outdated_translations)s pages </b>that have an "
#~ "outdated or no translation. In order for the users to benefit from your "
#~ "content you should translate them or have them translated."
#~ msgstr ""
#~ "Ihre Inhalten haben aktuell <b>"
#~ "%(number_of_missing_or_outdated_translations)s Seiten </b> mit fehlender "
#~ "oder veralteter Übersetzung. Damit die Nutzer:innen von Ihren Inhalten "
#~ "profitieren können, sollten Sie diese übersetzen (lassen)."
#~ "Ihre Inhalten haben aktuell "
#~ "<b>%(number_of_missing_or_outdated_translations)s Seiten </b> mit "
#~ "fehlender oder veralteter Übersetzung. Damit die Nutzer:innen von Ihren "
#~ "Inhalten profitieren können, sollten Sie diese übersetzen (lassen)."

#~ msgid "View location"
#~ msgstr "Ort ansehen"
Expand Down Expand Up @@ -12692,9 +12699,6 @@ msgstr ""
#~ msgid "All language tree nodes"
#~ msgstr "Alle Sprach-Knoten"

#~ msgid "Filter events"
#~ msgstr "Veranstaltungen filtern"

#~ msgid "Use of Heading 1 style not allowed."
#~ msgstr "Bitte benutzen Sie keine Überschrift 1 in diesem Feld."

Expand Down
2 changes: 2 additions & 0 deletions integreat_cms/release_notes/current/unreleased/3379.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
en: Adjust event filters for long-term events
de: Passe die Veranstaltungsfilter für fortlaufende Ereignisse an
47 changes: 47 additions & 0 deletions tests/cms/views/events/test_event_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pytest
from django.conf import settings
from django.test.client import Client
from django.urls import reverse

from tests.conftest import (
ANONYMOUS,
)

data = [
("Wöchentlich wiederholende Veranstaltung", "?recurring=1"),
("Einmalige Veranstaltung", "?recurring=2"),
("Dauerveranstaltung", "?recurring=3"),
]


@pytest.mark.django_db
def test_filtering_by_single_selected_recurrence_is_successful(
load_test_data: None,
login_role_user: tuple[Client, str],
) -> None:
client, role = login_role_user

get_event_list = reverse(
"events",
kwargs={"region_slug": "augsburg"},
)

for item in data:
title, query = item
url = get_event_list + query
response = client.get(url)

if role == ANONYMOUS:
assert response.status_code == 302
assert (
response.headers.get("location")
== f"{settings.LOGIN_URL}?next={get_event_list}"
)

else:
assert response.status_code == 302
redirect_url = response.headers.get("location")
response = client.get(redirect_url)
print(response)
assert response.status_code == 200
assert title in response.content.decode("utf-8")
Loading