WesternFriend/WF-website

View on GitHub
events/models.py

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
from django.db import models
from django.db.models import Q
from django.http import Http404, HttpRequest
from django.utils import timezone
from modelcluster.fields import ParentalKey  # type: ignore
from timezone_field import TimeZoneField  # type: ignore
from wagtail.admin.panels import FieldPanel, InlinePanel, PageChooserPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Orderable, Page
from wagtail.search import index

from common.models import DrupalFields
from core.constants import COMMON_STREAMFIELD_BLOCKS
from pagination.helpers import get_paginated_items


class EventSponsor(Orderable):
    event = ParentalKey(
        "events.Event",
        on_delete=models.CASCADE,
        related_name="sponsors",
    )
    sponsor = models.ForeignKey(
        "wagtailcore.Page",
        on_delete=models.CASCADE,
        related_name="events_sponsored",
    )

    panels = [
        PageChooserPanel(
            "sponsor",
            ["contact.Person", "contact.Meeting", "contact.Organization"],
        ),
    ]


class Event(DrupalFields, Page):  # type: ignore
    class EventCategoryChoices(models.TextChoices):
        WESTERN = ("western", "Western")
        OTHER = ("other", "Other")

    teaser = models.TextField(max_length=100, null=True, blank=True)
    body = StreamField(
        COMMON_STREAMFIELD_BLOCKS,
        null=True,
        blank=True,
        use_json_field=True,
    )

    start_date = models.DateTimeField()
    end_date = models.DateTimeField(blank=True, null=True)
    timezone = TimeZoneField(
        default="US/Pacific",
        choices_display="WITH_GMT_OFFSET",
    )

    website = models.URLField(blank=True, null=True, max_length=300)
    is_featured = models.BooleanField(
        default=False,
        help_text="Whether this event should be featured on the home page.",
    )
    category = models.CharField(
        max_length=255,
        choices=EventCategoryChoices.choices,
        default=EventCategoryChoices.WESTERN,
    )
    drupal_node_id = models.IntegerField(null=True, blank=True)

    content_panels = Page.content_panels + [
        FieldPanel("is_featured"),
        FieldPanel("category"),
        InlinePanel(
            "sponsors",
            heading="Sponsors",
            label="Sponsor",
        ),
        FieldPanel("teaser"),
        FieldPanel("body"),
        FieldPanel("start_date"),
        FieldPanel("end_date"),
        FieldPanel("timezone"),
        FieldPanel("website"),
    ]

    context_object_name = "event"

    search_template = "search/event.html"

    search_fields = Page.search_fields + [
        index.SearchField(
            "body",
        ),
        index.RelatedFields(
            "sponsors",
            [
                index.RelatedFields(
                    "sponsor",
                    [
                        index.SearchField("title"),
                    ],
                ),
            ],
        ),
    ]

    parent_page_types = ["events.EventsIndexPage"]
    subpage_types: list[str] = []

    class Meta:
        db_table = "events"
        ordering = ["start_date"]
        indexes = [
            # default ordering is by start date
            models.Index(fields=["start_date"]),
            # Some queries also check the end date
            models.Index(fields=["end_date"]),
        ]


class EventsIndexPage(Page):
    intro = RichTextField(blank=True)

    content_panels = Page.content_panels + [FieldPanel("intro")]

    parent_page_types = ["home.HomePage"]
    subpage_types: list[str] = ["events.Event"]

    max_count = 1

    def get_context(
        self,
        request: HttpRequest,
        *args: tuple,
        **kwargs: dict,
    ) -> dict:
        context = super().get_context(request)
        request_category = request.GET.get("category", None)

        if request_category:
            # Note: using the querystring parameter directly in the filter object
            # seems safe since Django querysets are protected from SQL injection
            # https://docs.djangoproject.com/en/4.1/topics/security/#sql-injection-protection
            # Adding this note to reappraise the security of this code if needed.

            # ensure the category is valid
            filter_category = request_category.lower()
            if filter_category not in Event.EventCategoryChoices.values:
                raise Http404
        else:
            # Default to Western events
            filter_category = Event.EventCategoryChoices.WESTERN
        now = timezone.now()

        # Upcoming events are events that have not yet ended
        # or have not yet started
        upcoming_events = (
            Event.objects.live()
            .filter(
                Q(start_date__gte=now) | Q(end_date__gte=now),
                category=filter_category,
            )
            .order_by("start_date")
        )

        page_number = request.GET.get("page", "1")
        items_per_page = 10

        context["events"] = get_paginated_items(
            items=upcoming_events,
            items_per_page=items_per_page,
            page_number=page_number,
        )

        context["event_category_title"] = filter_category.capitalize()

        return context