Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions events/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ class EventViewSet(viewsets.ModelViewSet):

def get_queryset(self):
"""
Optimized queryset with:
- select_related for organizer (avoids N+1 on organizer access)
- Exists annotation for is_registered (avoids N+1 per-event query)
Return the Event queryset for this view, optimized for organizer access and optionally annotated with the requesting user's registration status.

The queryset is ordered by `start_time` descending and includes related organizer information for efficient access. If the request user is authenticated, each Event will be annotated with `is_registered_annotation` (a boolean indicating whether that user has a Registration for the event).

Returns:
QuerySet: Event queryset ordered by newest `start_time` first, with related organizer selected and `is_registered_annotation` added for authenticated users.
"""
queryset = Event.objects.select_related("organizer").order_by("-start_time")

Expand All @@ -67,6 +70,12 @@ def get_queryset(self):
return queryset

def perform_create(self, serializer):
"""
Save a new Event instance and assign the current user as its organizer.

Parameters:
serializer: Serializer instance with validated event data; `save` is called with `organizer` set to the requesting user.
"""
serializer.save(organizer=self.request.user)

@action(
Expand Down Expand Up @@ -152,7 +161,12 @@ def unregister(self, request, pk=None):
detail=True, methods=["get"], permission_classes=[permissions.IsAuthenticated]
)
def registrations(self, request, pk=None):
"""Get all registrations for an event (organizer only)."""
"""
Retrieve registrations for a specific event; access is limited to the event's organizer.

Returns:
registrations (list): Serialized registration objects for the event, ordered by most recent.
"""

event = self.get_object()

Expand All @@ -178,9 +192,18 @@ class RegistrationViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [permissions.IsAuthenticated]

def get_queryset(self):
"""Optimized queryset with select_related for event and participant."""
"""
Return the queryset of Registration objects belonging to the requesting user.

The queryset is ordered by `registered_at` descending and includes the related
`event` and `participant` via `select_related` for query efficiency.

Returns:
QuerySet[Registration]: Registrations for the authenticated request user,
ordered newest first, with `event` and `participant` selected.
"""
return (
Registration.objects.filter(participant=self.request.user)
.select_related("event", "participant")
.order_by("-registered_at")
)
)
13 changes: 12 additions & 1 deletion events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class Meta:
]

def save(self, *args, **kwargs):
"""
Ensure the model has a unique slug and save it to the database.

If the instance's slug is empty, generate one from the title (fall back to a UUID hex). Attempt to save up to five times; on an IntegrityError caused by the slug, append a short UUID suffix and retry. Re-raise the error if it is not slug-related or if all attempts fail.
"""
if not self.slug:
base_slug = slugify(self.title) or uuid4().hex
self.slug = base_slug
Expand Down Expand Up @@ -90,6 +95,12 @@ class Meta:
]

def __str__(self):
"""
Human-readable representation of the registration combining the participant and event title.

Returns:
str: A string in the format "<participant> - <event title>".
"""
return f"{self.participant} - {self.event.title}"


Expand All @@ -103,4 +114,4 @@ class Webhook(models.Model):
is_active = models.BooleanField(default=True)

def __str__(self):
return f"Webhook for {self.event.title} ({self.url})"
return f"Webhook for {self.event.title} ({self.url})"
12 changes: 7 additions & 5 deletions events/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ class Meta:

def get_is_registered(self, obj):
"""
Check if the current user is registered for this event.

Optimized to use the pre-annotated value from the queryset when available,
falling back to a database query for detail views or when annotation is missing.
Determine whether the current request user is registered for the given event.

This prefers a pre-annotated `is_registered_annotation` on the event object when present (to avoid extra queries in list views); otherwise it checks the database for a registration for the authenticated request user.

Returns:
`true` if the current request user is registered for the event, `false` otherwise.
"""
# Use annotated value if available (from list views with Exists() annotation)
# This avoids N+1 queries when listing multiple events
Expand Down Expand Up @@ -89,4 +91,4 @@ class Meta:
"registered_at",
"answers",
]
read_only_fields = ["participant", "status", "registered_at", "event"]
read_only_fields = ["participant", "status", "registered_at", "event"]
26 changes: 25 additions & 1 deletion events/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ class EventListView(ListView):
paginate_by = 6

def get_queryset(self):
"""
Return the Event queryset filtered by optional `q` (title or description) and `location` GET parameters, with `organizer` and `organizer.profile` preloaded.

Filters:
- `q`: case-insensitive substring match against `title` or `description`.
- `location`: case-insensitive substring match against `location`.

Returns:
QuerySet: Event objects matching the provided filters with `organizer` and `organizer.profile` selected for eager loading.
"""
queryset = (
super().get_queryset().select_related("organizer", "organizer__profile")
)
Expand Down Expand Up @@ -128,13 +138,27 @@ class UserEventListView(LoginRequiredMixin, ListView):
context_object_name = "hosted_events"

def get_queryset(self):
"""
Retrieve the current user's events with organizer and organizer profile eager-loaded, ordered by start_time.

Returns:
QuerySet[Event]: Events organized by the request's user with `organizer` and `organizer__profile` selected and ordered by `start_time`.
"""
return (
Event.objects.filter(organizer=self.request.user)
.select_related("organizer", "organizer__profile")
.order_by("start_time")
)

def get_context_data(self, **kwargs):
"""
Add the current user's registrations to the template context under the key "attended_registrations".

The value is a queryset of Registration objects for the requesting user, eager-loading related event and organizer (including organizer profile) and ordered by the related event's start_time.

Returns:
context (dict): Template context including "attended_registrations".
"""
context = super().get_context_data(**kwargs)
context["attended_registrations"] = (
Registration.objects.filter(participant=self.request.user)
Expand Down Expand Up @@ -467,4 +491,4 @@ def post(self, request, pk):

def test_func(self):
webhook = get_object_or_404(Webhook, pk=self.kwargs["pk"])
return self.request.user == webhook.event.organizer
return self.request.user == webhook.event.organizer