From 6dc8446d050616a272cca46694aa3b3f38201f6b Mon Sep 17 00:00:00 2001 From: Jordan Singarajah Date: Tue, 7 Jul 2026 04:14:16 +0000 Subject: [PATCH 1/2] Add events app: models, serializers, viewsets, and added region field to EventInstance model --- server/api/events/__init__.py | 0 server/api/events/admin.py | 5 ++ server/api/events/apps.py | 6 ++ server/api/events/migrations/0001_initial.py | 60 +++++++++++++++++++ .../migrations/0002_eventinstance_region.py | 18 ++++++ .../0003_alter_event_cover_image.py | 18 ++++++ server/api/events/migrations/__init__.py | 0 server/api/events/models.py | 39 ++++++++++++ server/api/events/serializers.py | 39 ++++++++++++ server/api/events/tests.py | 3 + server/api/events/urls.py | 8 +++ server/api/events/views.py | 30 ++++++++++ server/api/settings.py | 1 + server/api/urls.py | 1 + 14 files changed, 228 insertions(+) create mode 100644 server/api/events/__init__.py create mode 100644 server/api/events/admin.py create mode 100644 server/api/events/apps.py create mode 100644 server/api/events/migrations/0001_initial.py create mode 100644 server/api/events/migrations/0002_eventinstance_region.py create mode 100644 server/api/events/migrations/0003_alter_event_cover_image.py create mode 100644 server/api/events/migrations/__init__.py create mode 100644 server/api/events/models.py create mode 100644 server/api/events/serializers.py create mode 100644 server/api/events/tests.py create mode 100644 server/api/events/urls.py create mode 100644 server/api/events/views.py diff --git a/server/api/events/__init__.py b/server/api/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/api/events/admin.py b/server/api/events/admin.py new file mode 100644 index 0000000..c60e7af --- /dev/null +++ b/server/api/events/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from .models import Event, EventInstance + +admin.site.register(Event) +admin.site.register(EventInstance) \ No newline at end of file diff --git a/server/api/events/apps.py b/server/api/events/apps.py new file mode 100644 index 0000000..99c73f0 --- /dev/null +++ b/server/api/events/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EventsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "api.events" diff --git a/server/api/events/migrations/0001_initial.py b/server/api/events/migrations/0001_initial.py new file mode 100644 index 0000000..5d1247c --- /dev/null +++ b/server/api/events/migrations/0001_initial.py @@ -0,0 +1,60 @@ +# Generated by Django 5.1.14 on 2026-07-04 03:50 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Event", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=255)), + ("slug", models.SlugField(max_length=255, unique=True)), + ("description", models.TextField(blank=True)), + ("cover_image", models.URLField(blank=True)), + ], + ), + migrations.CreateModel( + name="EventInstance", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(blank=True, max_length=255)), + ("starts_at", models.DateTimeField()), + ("ends_at", models.DateTimeField()), + ("location", models.CharField(max_length=255)), + ("ticket_url", models.URLField(blank=True)), + ( + "event", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="instances", + to="events.event", + ), + ), + ], + ), + ] diff --git a/server/api/events/migrations/0002_eventinstance_region.py b/server/api/events/migrations/0002_eventinstance_region.py new file mode 100644 index 0000000..bf641dc --- /dev/null +++ b/server/api/events/migrations/0002_eventinstance_region.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.14 on 2026-07-04 06:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="eventinstance", + name="region", + field=models.CharField(default="Perth", max_length=255), + ), + ] diff --git a/server/api/events/migrations/0003_alter_event_cover_image.py b/server/api/events/migrations/0003_alter_event_cover_image.py new file mode 100644 index 0000000..b544c04 --- /dev/null +++ b/server/api/events/migrations/0003_alter_event_cover_image.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.14 on 2026-07-07 04:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0002_eventinstance_region"), + ] + + operations = [ + migrations.AlterField( + model_name="event", + name="cover_image", + field=models.CharField(blank=True), + ), + ] diff --git a/server/api/events/migrations/__init__.py b/server/api/events/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/api/events/models.py b/server/api/events/models.py new file mode 100644 index 0000000..ddb3bdb --- /dev/null +++ b/server/api/events/models.py @@ -0,0 +1,39 @@ +from django.db import models +from django.core.exceptions import ValidationError +import uuid + + +class Event(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, unique=True) + description = models.TextField(blank=True) + cover_image = models.CharField(blank=True) + + def __str__(self): + return self.name + + +class EventInstance(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + event = models.ForeignKey( + Event, + on_delete=models.CASCADE, + related_name="instances", + ) + name = models.CharField(max_length=255, blank=True) + starts_at = models.DateTimeField() + ends_at = models.DateTimeField() + location = models.CharField(max_length=255) + # region tag for location filtering + region = models.CharField(max_length=255, blank=False, default="Perth") + ticket_url = models.URLField(blank=True) + + def __str__(self): + return f"{self.event.name} — {self.starts_at:%Y-%m-%d}" + + def clean(self): + if self.ends_at and self.starts_at and self.ends_at <= self.starts_at: + raise ValidationError( + {"ends_at": "End time must be after start time."} + ) diff --git a/server/api/events/serializers.py b/server/api/events/serializers.py new file mode 100644 index 0000000..437206f --- /dev/null +++ b/server/api/events/serializers.py @@ -0,0 +1,39 @@ +from rest_framework import serializers +from .models import Event, EventInstance + + +class EventInstanceSerializer(serializers.ModelSerializer): + class Meta: + model = EventInstance + fields = [ + "id", + "event", + "name", + "starts_at", + "ends_at", + "location", + "region", + "ticket_url" + ] + read_only_fields = ["id"] + + def validate(self, data): + + if data['starts_at'] >= data['ends_at']: + raise serializers.ValidationError("Finish time must occur after start time") + return data + + +class EventSerializer(serializers.ModelSerializer): + instances = EventInstanceSerializer(many=True, read_only=True) + + class Meta: + model = Event + fields = [ + "id", + "name", + "slug", + "description", + "cover_image", + "instances", + ] diff --git a/server/api/events/tests.py b/server/api/events/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/api/events/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/api/events/urls.py b/server/api/events/urls.py new file mode 100644 index 0000000..15f7b95 --- /dev/null +++ b/server/api/events/urls.py @@ -0,0 +1,8 @@ +from rest_framework.routers import DefaultRouter +from .views import EventViewSet, EventInstanceViewSet + +router = DefaultRouter() +router.register("event-instances", EventInstanceViewSet, basename="event-instance") +router.register("", EventViewSet, basename="event") + +urlpatterns = router.urls diff --git a/server/api/events/views.py b/server/api/events/views.py new file mode 100644 index 0000000..1e64c0f --- /dev/null +++ b/server/api/events/views.py @@ -0,0 +1,30 @@ +from rest_framework import viewsets + + +from .models import Event, EventInstance +from .serializers import EventSerializer, EventInstanceSerializer + + +class EventViewSet(viewsets.ModelViewSet): + queryset = Event.objects.all() + serializer_class = EventSerializer + lookup_field = "slug" + + +class EventInstanceViewSet(viewsets.ModelViewSet): + queryset = EventInstance.objects.all() + serializer_class = EventInstanceSerializer + + def get_queryset(self): + queryset = EventInstance.objects.all() + + region = self.request.query_params.get("region") + date = self.request.query_params.get("date") + + if region: + queryset = queryset.filter(region=region) + + if date: + queryset = queryset.filter(starts_at__date=date) + + return queryset diff --git a/server/api/settings.py b/server/api/settings.py index 0bf673e..ba6f331 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -52,6 +52,7 @@ "rest_framework", "corsheaders", "api.healthcheck", + "api.events" ] MIDDLEWARE = [ diff --git a/server/api/urls.py b/server/api/urls.py index 1347bb7..144bea8 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -21,4 +21,5 @@ urlpatterns = [ path("admin/", admin.site.urls), path("api/healthcheck/", include(("api.healthcheck.urls"))), + path("api/events/", include("api.events.urls")), ] From 02b1c844c5d6c6e3e2e4f28f52c97d91136fe431 Mon Sep 17 00:00:00 2001 From: Jordan Singarajah Date: Sat, 11 Jul 2026 04:01:50 +0000 Subject: [PATCH 2/2] Fixed flake8 linting errors --- server/api/events/admin.py | 2 +- server/api/events/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api/events/admin.py b/server/api/events/admin.py index c60e7af..424b9ff 100644 --- a/server/api/events/admin.py +++ b/server/api/events/admin.py @@ -2,4 +2,4 @@ from .models import Event, EventInstance admin.site.register(Event) -admin.site.register(EventInstance) \ No newline at end of file +admin.site.register(EventInstance) diff --git a/server/api/events/tests.py b/server/api/events/tests.py index 7ce503c..c1b49c8 100644 --- a/server/api/events/tests.py +++ b/server/api/events/tests.py @@ -1,3 +1,3 @@ -from django.test import TestCase + # Create your tests here.