Coverage for website/events/api/v2/views.py: 61.24%
154 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from django.db.models import Prefetch, Q
2from django.utils import timezone
4from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
5from rest_framework import filters as framework_filters
6from rest_framework import status
7from rest_framework.exceptions import PermissionDenied, ValidationError
8from rest_framework.generics import (
9 DestroyAPIView,
10 ListAPIView,
11 RetrieveAPIView,
12 get_object_or_404,
13)
14from rest_framework.response import Response
15from rest_framework.views import APIView
17from events import services
18from events.api.v2 import filters
19from events.api.v2.serializers.event import EventListSerializer, EventSerializer
20from events.api.v2.serializers.event_registration import EventRegistrationSerializer
21from events.api.v2.serializers.external_event import ExternalEventSerializer
22from events.exceptions import RegistrationError
23from events.models import Event, EventRegistration, ExternalEvent
24from events.services import is_user_registered
25from members.models import Membership
26from thaliawebsite.api.v2.permissions import IsAuthenticatedOrTokenHasScopeForMethod
27from thaliawebsite.api.v2.serializers import EmptySerializer
28from utils.media.services import fetch_thumbnails
31class EventListView(ListAPIView):
32 """Returns an overview of all upcoming events."""
34 serializer_class = EventListSerializer
35 filter_backends = (
36 framework_filters.OrderingFilter,
37 framework_filters.SearchFilter,
38 filters.EventDateFilter,
39 filters.CategoryFilter,
40 filters.OrganiserFilter,
41 )
42 ordering_fields = ("start", "end")
43 search_fields = ("title",)
44 permission_classes = [IsAuthenticatedOrTokenHasScope]
45 required_scopes = ["events:read"]
47 def get_queryset(self):
48 events = (
49 Event.objects.filter(published=True)
50 .select_related("food_event")
51 .select_properties("participant_count")
52 .prefetch_related(
53 "registrationinformationfield_set",
54 "documents",
55 "organisers",
56 "organisers__board",
57 "organisers__committee",
58 "organisers__society",
59 "organisers__contact_mailinglist",
60 )
61 )
62 if self.request.member: 62 ↛ 72line 62 didn't jump to line 72 because the condition on line 62 was always true
63 events = events.prefetch_related(
64 Prefetch(
65 "eventregistration_set",
66 to_attr="member_registration",
67 queryset=EventRegistration.objects.filter(
68 member=self.request.member
69 ).select_properties("queue_position"),
70 )
71 )
72 return events
75class EventDetailView(RetrieveAPIView):
76 """Returns details of an event."""
78 serializer_class = EventSerializer
79 permission_classes = [IsAuthenticatedOrTokenHasScope]
80 required_scopes = ["events:read"]
82 def get_queryset(self):
83 events = Event.objects.filter(published=True)
84 if self.request.member: 84 ↛ 94line 84 didn't jump to line 94 because the condition on line 84 was always true
85 events = events.prefetch_related(
86 Prefetch(
87 "eventregistration_set",
88 to_attr="member_registration",
89 queryset=EventRegistration.objects.filter(
90 member=self.request.member
91 ).select_properties("queue_position"),
92 )
93 )
94 return events
97class EventRegistrationsView(ListAPIView):
98 """Returns a list of registrations."""
100 serializer_class = EventRegistrationSerializer
101 permission_classes = [IsAuthenticatedOrTokenHasScopeForMethod]
102 required_scopes_per_method = {
103 "GET": ["events:read"],
104 "POST": ["events:register"],
105 "DELETE": ["events:register"],
106 }
107 filter_backends = (framework_filters.OrderingFilter,)
108 ordering_fields = (
109 "date",
110 "member",
111 )
113 def __init__(self):
114 super().__init__()
115 self.event = None
117 def get_serializer_class(self):
118 if self.request.method.lower() == "post":
119 return EmptySerializer
120 return super().get_serializer_class()
122 def get_queryset(self):
123 if self.event:
124 today = timezone.now().date()
125 return (
126 EventRegistration.objects.filter(event=self.event, date_cancelled=None)
127 .select_related("member__profile")
128 .prefetch_related(
129 Prefetch(
130 "member__membership_set",
131 queryset=Membership.objects.filter(
132 Q(until__isnull=True) | Q(until__gt=today), since__lte=today
133 ).order_by("-since")[:1],
134 to_attr="_current_membership",
135 ),
136 )[: self.event.max_participants]
137 )
138 return EventRegistration.objects.none()
140 def get_serializer(self, *args, **kwargs):
141 if len(args) > 0:
142 registrations = args[0]
143 fetch_thumbnails(
144 [r.member.profile.photo for r in registrations if r.member]
145 )
146 return super().get_serializer(*args, **kwargs)
148 def initial(self, request, *args, **kwargs):
149 """Run anything that needs to occur prior to calling the method handler."""
150 self.format_kwarg = self.get_format_suffix(**kwargs)
152 # Perform content negotiation and store the accepted info on the request
153 neg = self.perform_content_negotiation(request)
154 request.accepted_renderer, request.accepted_media_type = neg
156 # Determine the API version, if versioning is in use.
157 version, scheme = self.determine_version(request, *args, **kwargs)
158 request.version, request.versioning_scheme = version, scheme
160 # Ensure that the incoming request is permitted
161 self.perform_authentication(request)
163 self.event = get_object_or_404(Event, pk=self.kwargs.get("pk"), published=True)
165 self.check_permissions(request)
166 self.check_throttles(request)
168 def post(self, request, *args, **kwargs):
169 try:
170 registration = services.create_registration(request.member, self.event)
171 serializer = EventRegistrationSerializer(
172 instance=registration, context=self.get_serializer_context()
173 )
174 return Response(serializer.data, status=status.HTTP_201_CREATED)
175 except RegistrationError as e:
176 raise PermissionDenied(detail=e) from e
179class EventRegistrationDetailView(RetrieveAPIView, DestroyAPIView):
180 """Returns details of an event registration."""
182 serializer_class = EventRegistrationSerializer
183 queryset = EventRegistration.objects.all()
184 permission_classes = [IsAuthenticatedOrTokenHasScopeForMethod]
185 required_scopes_per_method = {
186 "GET": ["events:read"],
187 "DELETE": ["events:register"],
188 }
190 def get_queryset(self):
191 return (
192 super()
193 .get_queryset()
194 .filter(
195 event=self.kwargs["event_id"],
196 event__published=True,
197 date_cancelled=None,
198 )
199 )
201 def get_serializer(self, *args, **kwargs):
202 if (
203 len(args) > 0
204 and isinstance(args[0], EventRegistration)
205 and args[0].member == self.request.member
206 ):
207 kwargs.update(
208 fields=(
209 "pk",
210 "member",
211 "name",
212 "present",
213 "queue_position",
214 "date",
215 "payment",
216 )
217 )
218 return super().get_serializer(*args, **kwargs)
220 def delete(self, request, *args, **kwargs):
221 if self.get_object().member != request.member:
222 raise PermissionDenied()
224 try:
225 services.cancel_registration(request.member, self.get_object().event)
226 return Response(status=status.HTTP_204_NO_CONTENT)
227 except RegistrationError as e:
228 raise PermissionDenied(detail=e) from e
231class EventRegistrationFieldsView(APIView):
232 """Returns details of an event registration."""
234 permission_classes = [IsAuthenticatedOrTokenHasScopeForMethod]
235 required_scopes_per_method = {
236 "GET": ["events:read"],
237 "PUT": ["events:register"],
238 "PATCH": ["events:register"],
239 }
241 def get_object(self):
242 return get_object_or_404(
243 EventRegistration,
244 event=self.kwargs["event_id"],
245 event__published=True,
246 pk=self.kwargs["registration_id"],
247 member=self.request.member,
248 )
250 def get(self, request, *args, **kwargs):
251 return Response(
252 data=services.registration_fields(request, registration=self.get_object()),
253 status=status.HTTP_200_OK,
254 )
256 def put(self, request, *args, **kwargs):
257 original = services.registration_fields(request, registration=self.get_object())
258 required_keys = set(original.keys()) - set(request.data.keys())
259 if len(required_keys) > 0:
260 raise ValidationError(
261 f"Missing keys '{', '.join(required_keys)}' in request"
262 )
264 try:
265 services.update_registration(
266 registration=self.get_object(), field_values=request.data.items()
267 )
269 return Response(
270 data=services.registration_fields(
271 request, registration=self.get_object()
272 ),
273 status=status.HTTP_200_OK,
274 )
275 except RegistrationError as e:
276 raise ValidationError(e) from e
278 def patch(self, request, *args, **kwargs):
279 try:
280 services.update_registration(
281 registration=self.get_object(), field_values=request.data.items()
282 )
284 return Response(
285 data=services.registration_fields(
286 request, registration=self.get_object()
287 ),
288 status=status.HTTP_200_OK,
289 )
290 except RegistrationError as e:
291 raise ValidationError(e) from e
294class ExternalEventListView(ListAPIView):
295 """Returns an overview of all partner events."""
297 serializer_class = ExternalEventSerializer
298 queryset = ExternalEvent.objects.filter(published=True)
299 filter_backends = (
300 framework_filters.OrderingFilter,
301 framework_filters.SearchFilter,
302 filters.EventDateFilter,
303 )
304 ordering_fields = ("start", "end", "title")
305 search_fields = ("title",)
306 permission_classes = [IsAuthenticatedOrTokenHasScope]
307 required_scopes = ["events:read"]
310class ExternalEventDetailView(RetrieveAPIView):
311 """Returns a single partner event."""
313 serializer_class = ExternalEventSerializer
314 queryset = ExternalEvent.objects.filter(published=True)
315 permission_classes = [IsAuthenticatedOrTokenHasScope]
316 required_scopes = ["events:read"]
319class MarkPresentAPIView(APIView):
320 """A view that allows uses to mark their presence at an event using a secret token."""
322 permission_classes = [IsAuthenticatedOrTokenHasScope]
323 required_scopes = ["events:register"]
325 def patch(self, request, *args, **kwargs):
326 """Mark a user as present.
328 Checks if the url is correct, the event has not ended yet, and the user is registered.
329 """
330 event = get_object_or_404(Event, pk=kwargs["pk"])
331 if kwargs["token"] != event.mark_present_url_token:
332 raise PermissionDenied(detail="Invalid url.")
334 if not request.member or not is_user_registered(request.member, event):
335 raise PermissionDenied(detail="You are not registered for this event.")
337 registration = event.registrations.get(
338 member=request.member, date_cancelled=None
339 )
341 if registration.present:
342 return Response(
343 data={"detail": "You were already marked as present."},
344 status=status.HTTP_200_OK,
345 )
346 if event.end < timezone.now():
347 raise PermissionDenied(
348 detail="This event has already ended.",
349 )
351 registration.present = True
352 registration.save()
353 return Response(
354 data={"detail": "You have been marked as present."},
355 status=status.HTTP_200_OK,
356 )