From ccd83721896a094e102ec69d78396a5464486508 Mon Sep 17 00:00:00 2001 From: Cameron Lamb Date: Wed, 9 Oct 2024 13:34:46 +0100 Subject: [PATCH] INTR-318 Event listing page updates and filtering (#753) --- poetry.lock | 12 +-- pyproject.toml | 1 + src/core/templates/dwds_content.html | 6 +- src/events/filters.py | 40 ++++++++++ src/events/models.py | 76 ++++++++++++++++++- src/events/templates/events/events_home.html | 33 ++++---- .../includes/event_listing_section.html | 15 ++++ .../services/uk_staff_locations.py | 6 ++ 8 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/events/filters.py create mode 100644 src/events/templates/events/includes/event_listing_section.html diff --git a/poetry.lock b/poetry.lock index 8a8603503..28b732d56 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1113,17 +1113,17 @@ django-crispy-forms = ">=1.9,<2.0" [[package]] name = "django-filter" -version = "23.3" +version = "24.3" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "django-filter-23.3.tar.gz", hash = "sha256:015fe155582e1805b40629344e4a6cf3cc40450827d294d040b4b8c1749a9fa6"}, - {file = "django_filter-23.3-py3-none-any.whl", hash = "sha256:65bc5d1d8f4fff3aaf74cb5da537b6620e9214fb4b3180f6c560776b1b6dccd0"}, + {file = "django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64"}, + {file = "django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3"}, ] [package.dependencies] -Django = ">=3.2" +Django = ">=4.2" [[package]] name = "django-hawk" @@ -4181,4 +4181,4 @@ test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-it [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "9b5de3a8a72d6a1df547f8ece75786ce3d55b499a2633249089263cad96cc0dc" +content-hash = "28a728f4f4c1c7e5b4e3e2ab10d634ab8ba7bd9a08a8884f9f22bf4e0b3772b4" diff --git a/pyproject.toml b/pyproject.toml index 9b30d861c..afccb6a3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,7 @@ opentelemetry-instrumentation-wsgi = "^0.43b0" opentelemetry-propagator-aws-xray = "^1.0.1" opentelemetry-sdk-extension-aws = "^2.0.1" wagtail-orderable = "^1.2.0" +django-filter = "^24.3" django-csp = "^3.8" [tool.poetry.group.dev.dependencies] diff --git a/src/core/templates/dwds_content.html b/src/core/templates/dwds_content.html index 078ec2e6a..f18483b14 100644 --- a/src/core/templates/dwds_content.html +++ b/src/core/templates/dwds_content.html @@ -15,7 +15,11 @@ {% block pre_content %} {% endblock pre_content %} {% block page_title %} -

{{ page.title }}

+ {% if page_title %} +

{{ page_title }}

+ {% else %} +

{{ page.title }}

+ {% endif %} {% if page.description %}

{{ page.description }}

{% endif %} {% endblock page_title %} {% block bookmark %} diff --git a/src/events/filters.py b/src/events/filters.py new file mode 100644 index 000000000..50cccccf5 --- /dev/null +++ b/src/events/filters.py @@ -0,0 +1,40 @@ +import django_filters +from django import forms + +from events import types +from peoplefinder.services.uk_staff_locations import UkStaffLocationService + + +EVENT_TYPE_ALL_VALUE = "" +EVENT_TYPE_CHOICES = [ + (EVENT_TYPE_ALL_VALUE, "All types"), + (types.EventType.IN_PERSON, "In person"), + (types.EventType.ONLINE, "Online"), +] + + +class EventsFilters(django_filters.FilterSet): + event_type = django_filters.ChoiceFilter( + method="event_format_filter", + choices=EVENT_TYPE_CHOICES, + widget=forms.widgets.RadioSelect, + empty_label=None, + ) + + location = django_filters.ChoiceFilter( + field_name="location__city", + choices=[ + (c, c) for c in UkStaffLocationService().get_uk_staff_location_cities() + ], + lookup_expr="exact", + widget=forms.widgets.Select, + ) + + def event_format_filter(self, queryset, name, value): + if value == EVENT_TYPE_ALL_VALUE: + return queryset + + event_types = [types.EventType.HYBRID] + event_types.append(types.EventType(value)) + + return queryset.filter(event_type__in=event_types) diff --git a/src/events/models.py b/src/events/models.py index 6c3880321..02db049ff 100644 --- a/src/events/models.py +++ b/src/events/models.py @@ -1,8 +1,13 @@ -from datetime import timedelta +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta from django.db import models +from django.http import Http404 +from django.shortcuts import redirect +from django.template.response import TemplateResponse from django.utils import timezone from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel +from wagtail.contrib.routable_page.models import RoutablePageMixin, route from content.models import BasePage, ContentPage from core.models import fields @@ -10,7 +15,7 @@ from events.utils import get_event_datetime_display_string -class EventsHome(BasePage): +class EventsHome(RoutablePageMixin, BasePage): template = "events/events_home.html" show_in_menus = True is_creatable = False @@ -19,11 +24,74 @@ class EventsHome(BasePage): def get_template(self, request, *args, **kwargs): return self.template + @route(r"^(?P\d{4})/$", name="month_year") + def year_events(self, request, year): + return redirect( + self.full_url + self.reverse_subpage("month_events", args=(year, 1)) + ) + + @route(r"^(?P\d{4})/(?P\d{1,2})/$", name="month_events") + def month_events(self, request, year, month): + year = int(year) + month = int(month) + + if month < 1 or month > 12: + raise Http404 + + filter_date = datetime(int(year), int(month), 1) + + return TemplateResponse( + request, + self.get_template(request), + self.get_context(request, filter_date=filter_date), + ) + def get_context(self, request, *args, **kwargs): + from events.filters import EventsFilters + context = super().get_context(request, *args, **kwargs) - events = EventPage.objects.live().public() - context["events"] = events + filter_date = kwargs.get("filter_date", timezone.now()).date() + + month_start = filter_date.replace(day=1) + + previous_month = month_start - relativedelta(months=1) + next_month = month_start + relativedelta(months=1) + + month_end = month_start.replace(month=next_month.month) + + events = ( + EventPage.objects.live() + .public() + .filter( + event_date__gte=month_start, + event_date__lt=month_end, + ) + .order_by("event_date") + ) + + current_month_start = timezone.now().date().replace(day=1) + + page_title_prefix = "What's on in" + if filter_date < current_month_start: + page_title_prefix = "What happened in" + + # Filtering events + events_filters = EventsFilters(request.GET, queryset=events) + events = events_filters.qs + + context.update( + events_filters=events_filters, + page_title=f"{page_title_prefix} {month_start.strftime('%B %Y')}", + upcoming_events=events.filter( + event_date__gte=timezone.now().date(), + ), + past_events=events.filter( + event_date__lt=timezone.now().date(), + ), + next_month=next_month, + previous_month=previous_month, + ) return context diff --git a/src/events/templates/events/events_home.html b/src/events/templates/events/events_home.html index 707ed860d..ccee79228 100644 --- a/src/events/templates/events/events_home.html +++ b/src/events/templates/events/events_home.html @@ -1,6 +1,5 @@ {% extends "dwds_content.html" %} -{% load wagtailcore_tags %} -{% load wagtailimages_tags %} +{% load wagtailroutablepage_tags %} {% block main_content_classes %} content_page @@ -10,18 +9,24 @@ {% endblock bookmark %} {% block primary_content %} -
    - {% for event in events %} -
  • -

    - {{ event.title }} -

    -

    {{ event.event_start|date:"d M Y" }}

    - {% image event.preview_image width-540 %} -

    {{ event.description }}

    -
  • - {% endfor %} -
+ Previous month + Next month + +
+ {{ events_filters.form.as_p }} + +
+ + {% if not upcoming_events and not past_events %}

There are no events to display.

{% endif %} + + {% if upcoming_events %} +

Upcoming events

+ {% include "events/includes/event_listing_section.html" with events=upcoming_events %} + {% endif %} + {% if past_events %} +

Past events

+ {% include "events/includes/event_listing_section.html" with events=past_events %} + {% endif %} {% endblock primary_content %} {% block secondary_content %} diff --git a/src/events/templates/events/includes/event_listing_section.html b/src/events/templates/events/includes/event_listing_section.html new file mode 100644 index 000000000..6a459d7a7 --- /dev/null +++ b/src/events/templates/events/includes/event_listing_section.html @@ -0,0 +1,15 @@ +{% load wagtailcore_tags %} +{% load wagtailimages_tags %} + +
    + {% for event in events %} +
  • +

    + {{ event.title }} +

    +

    {{ event.event_date }}

    + {% image event.preview_image width-540 %} +

    {{ event.description }}

    +
  • + {% endfor %} +
diff --git a/src/peoplefinder/services/uk_staff_locations.py b/src/peoplefinder/services/uk_staff_locations.py index 290258f98..73818b31d 100644 --- a/src/peoplefinder/services/uk_staff_locations.py +++ b/src/peoplefinder/services/uk_staff_locations.py @@ -73,3 +73,9 @@ def ingest_uk_staff_locations(self) -> IngestOutput: "updated": updated, "deleted": deleted, } + + def get_uk_staff_location_cities(self) -> list[str]: + return [ + c["city"] + for c in UkStaffLocation.objects.values("city").distinct().order_by("city") + ]