diff --git a/dnscms/dnscms/wagtail_hooks.py b/dnscms/dnscms/wagtail_hooks.py index 9226ad8..d48957e 100644 --- a/dnscms/dnscms/wagtail_hooks.py +++ b/dnscms/dnscms/wagtail_hooks.py @@ -6,7 +6,6 @@ from wagtail import hooks from wagtail.admin.menu import MenuItem from associations.models import AssociationIndex -from events.models import EventIndex from news.models import NewsIndex @@ -15,15 +14,6 @@ def enable_additional_rich_text_features(features): features.default_features.extend(["h5", "h6", "blockquote"]) -@hooks.register("register_admin_menu_item") -def register_events_menu_item(): - page = EventIndex.objects.first() - events_url = "#" - if page: - events_url = reverse("wagtailadmin_explore", args=(quote(page.pk),)) - return MenuItem("Arrangementer", events_url, icon_name="date", order=1) - - @hooks.register("register_admin_menu_item") def register_associations_menu_item(): page = AssociationIndex.objects.first() @@ -34,7 +24,7 @@ def register_associations_menu_item(): @hooks.register("register_admin_menu_item") -def register_associations_menu_item(): +def register_news_menu_item(): page = NewsIndex.objects.first() news_url = "#" if page: diff --git a/dnscms/events/admin.py b/dnscms/events/admin.py new file mode 100644 index 0000000..cfb2fea --- /dev/null +++ b/dnscms/events/admin.py @@ -0,0 +1,86 @@ +from urllib.parse import urlencode + +from django.urls import reverse +from django.utils import timezone +from wagtail.admin.ui.tables import Column, DateColumn +from wagtail.admin.ui.tables.pages import PageStatusColumn, PageTitleColumn +from wagtail.admin.views.pages.choose_parent import ChooseParentView +from wagtail.admin.views.pages.listing import IndexView +from wagtail.admin.viewsets.pages import PageListingViewSet + +from events.models import EventPage + + +class EventDateColumn(Column): + def get_value(self, instance): + occurrences = list(instance.occurrences.order_by("start")) + if not occurrences: + return "—" + if len(occurrences) == 1: + local = timezone.localtime(occurrences[0].start) + return local.strftime("%Y-%m-%d kl %H:%M") + return f"{len(occurrences)} forekomster" + + +class OrganizersColumn(Column): + def get_value(self, instance): + names = list(instance.organizers.values_list("name", flat=True)) + if not names: + return "—" + if len(names) == 1: + return names[0] + return f"{names[0]} (+{len(names) - 1})" + + +class EventPageIndexView(IndexView): + def annotate_queryset(self, pages): + pages = super().annotate_queryset(pages) + return pages.prefetch_related( + "occurrences", + "organizer_links__organizer", + ) + + +class EventChooseParentView(ChooseParentView): + """Redirect newly-created EventPages back to the events listing.""" + + def _with_next(self, response): + if response.status_code != 302: + return response + url = response["Location"] + sep = "&" if "?" in url else "?" + response["Location"] = f"{url}{sep}{urlencode({'next': reverse('events:index')})}" + return response + + def get(self, request, *args, **kwargs): + return self._with_next(super().get(request, *args, **kwargs)) + + def form_valid(self, form): + return self._with_next(super().form_valid(form)) + + +class EventPageListingViewSet(PageListingViewSet): + model = EventPage + index_view_class = EventPageIndexView + choose_parent_view_class = EventChooseParentView + icon = "date" + menu_label = "Arrangementer" + menu_order = 1 + add_to_admin_menu = True + ordering = "-latest_revision_created_at" + + columns = [ + PageTitleColumn("title", label="Tittel", sort_key="title", classname="title"), + EventDateColumn("event_date", label="Dato", width="13%"), + OrganizersColumn("organizers", label="Arrangører", width="12%"), + DateColumn( + "latest_revision_created_at", + label="Oppdatert", + sort_key="latest_revision_created_at", + width="10%", + ), + PageStatusColumn("status", label="Status", sort_key="live", width="10%"), + ] + + +event_page_listing_viewset = EventPageListingViewSet("events") diff --git a/dnscms/events/wagtail_hooks.py b/dnscms/events/wagtail_hooks.py index 8ec0175..1f60608 100644 --- a/dnscms/events/wagtail_hooks.py +++ b/dnscms/events/wagtail_hooks.py @@ -1,8 +1,14 @@ from wagtail import hooks +from .admin import event_page_listing_viewset from .views import event_organizer_chooser_viewset @hooks.register("register_admin_viewset") def register_viewset(): return event_organizer_chooser_viewset + + +@hooks.register("register_admin_viewset") +def register_event_page_listing_viewset(): + return event_page_listing_viewset diff --git a/dnscms/tests/test_events.py b/dnscms/tests/test_events.py index a415d75..f406b44 100644 --- a/dnscms/tests/test_events.py +++ b/dnscms/tests/test_events.py @@ -4,6 +4,7 @@ import pytest from django.core.exceptions import ValidationError from django.utils import timezone +from events.admin import EventDateColumn, OrganizersColumn from events.models import ( EventCategory, EventOccurrence, @@ -212,6 +213,62 @@ def test_graphql_event_index_future_events_ordered_by_next_occurrence(event_inde assert titles.index("Sooner gig") < titles.index("Later gig") +def test_event_date_column_no_occurrences(event_index): + event = EventPageFactory(parent=event_index) + column = EventDateColumn("event_date") + + assert column.get_value(event) == "—" + + +def test_event_date_column_single_occurrence(event_index): + event = EventPageFactory(parent=event_index) + start = timezone.make_aware(datetime(2025, 7, 22, 19, 30)) + EventOccurrence.objects.create(event=event, start=start, venue_custom="X") + column = EventDateColumn("event_date") + + assert column.get_value(event) == "2025-07-22 kl 19:30" + + +def test_event_date_column_multiple_occurrences_shows_count(event_index): + event = EventPageFactory(parent=event_index) + now = timezone.now() + EventOccurrence.objects.create(event=event, start=now, venue_custom="X") + EventOccurrence.objects.create(event=event, start=now + timedelta(days=1), venue_custom="X") + EventOccurrence.objects.create(event=event, start=now + timedelta(days=2), venue_custom="X") + column = EventDateColumn("event_date") + + assert column.get_value(event) == "3 forekomster" + + +def test_organizers_column_no_organizers(event_index): + event = EventPageFactory(parent=event_index) + column = OrganizersColumn("organizers") + + assert column.get_value(event) == "—" + + +def test_organizers_column_single_organizer_shows_name(event_index): + org = EventOrganizer.objects.create(name="Forening A", slug="forening-a") + event = EventPageFactory(parent=event_index) + EventOrganizerLink.objects.create(event=event, organizer=org) + column = OrganizersColumn("organizers") + + assert column.get_value(event) == "Forening A" + + +def test_organizers_column_multiple_organizers_truncates_with_count(event_index): + org_a = EventOrganizer.objects.create(name="Forening A", slug="forening-a") + org_b = EventOrganizer.objects.create(name="Forening B", slug="forening-b") + org_c = EventOrganizer.objects.create(name="Forening C", slug="forening-c") + event = EventPageFactory(parent=event_index) + EventOrganizerLink.objects.create(event=event, organizer=org_a, sort_order=0) + EventOrganizerLink.objects.create(event=event, organizer=org_b, sort_order=1) + EventOrganizerLink.objects.create(event=event, organizer=org_c, sort_order=2) + column = OrganizersColumn("organizers") + + assert column.get_value(event) == "Forening A (+2)" + + @pytest.fixture def comprehensive_event(event_index, venue, association_index): """A fully-populated paid EventPage exercising every field exposed via GraphQL."""