diff --git a/pages/api/sequences.py b/pages/api/sequences.py index 168ea77..2db6ef8 100644 --- a/pages/api/sequences.py +++ b/pages/api/sequences.py @@ -24,12 +24,16 @@ import logging from django.db import IntegrityError +from django.shortcuts import get_object_or_404 from rest_framework import (response as api_response, viewsets, status) from rest_framework.decorators import action +from rest_framework.views import APIView -from ..models import (Sequence, EnumeratedElements) -from ..serializers import (SequenceSerializer, EnumeratedElementSerializer) +from ..models import (Sequence, EnumeratedElements, + SequenceProgress, EnumeratedProgress, LiveEvent) +from ..serializers import (SequenceSerializer, + EnumeratedElementSerializer, AttendanceInputSerializer) LOGGER = logging.getLogger(__name__) @@ -275,8 +279,62 @@ def remove_element(self, request, slug=None, element_rank=None): try: element = EnumeratedElements.objects.get(sequence=sequence, rank=element_rank) element.delete() - return api_response.Response({'detail': 'element removed'}, status=status.HTTP_200_OK) + return api_response.Response( + {'detail': 'element removed'}, status=status.HTTP_200_OK) except EnumeratedElements.DoesNotExist: return api_response.Response({'detail': 'element not found in sequence'}, status=status.HTTP_404_NOT_FOUND) return api_response.Response({'detail': 'Invalid rank'}, status=status.HTTP_400_BAD_REQUEST) + + +class LiveEventAttendanceAPIView(APIView): + ''' + Allows marking a user's attendance to a Live Event. + + The user's EnumeratedProgress viewing duration for the event is updated to + meet the minimum viewing duration requirement of the associated EnumeratedElement. + + **Tags**: attendance, live events + ''' + def post(self, request, *args, **kwargs): + """ + - **Mark a User's attendance at a Live Event** + + .. code-block:: http + + POST /api/sequences/{sequence}/{rank}/{username}/mark-attendance HTTP/1.1 + + Responds + + .. code-block:: json + + { + "detail": "Attendance marked successfully" + } + + """ + + input_serializer = AttendanceInputSerializer(data=self.kwargs) + input_serializer.is_valid(raise_exception=True) + + sequence = input_serializer.validated_data['sequence'] + user = input_serializer.validated_data['username'] + rank = input_serializer.validated_data['rank'] + + sequence_progress, _ = SequenceProgress.objects.get_or_create( + user=user, sequence=sequence) + enumerated_progress, _ = EnumeratedProgress.objects.get_or_create( + progress=sequence_progress, rank=rank) + enumerated_element = get_object_or_404( + EnumeratedElements, rank=rank, sequence=sequence) + page_element = enumerated_element.page_element + live_event = LiveEvent.objects.filter(element=page_element).first() + + # We use if live_event to confirm the existence of the LiveEvent object + if live_event and enumerated_progress.viewing_duration <= enumerated_element.min_viewing_duration: + enumerated_progress.viewing_duration = enumerated_element.min_viewing_duration + enumerated_progress.save() + return api_response.Response( + {'detail': 'Attendance marked successfully'}, status=status.HTTP_200_OK) + return api_response.Response( + {'detail': 'Attendance not marked'}, status=status.HTTP_400_BAD_REQUEST) diff --git a/pages/models.py b/pages/models.py index f150367..f93efe4 100644 --- a/pages/models.py +++ b/pages/models.py @@ -462,7 +462,7 @@ class SequenceProgress(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) extra = get_extra_field_class()(null=True, blank=True, help_text=_("Extra meta data (can be stringify JSON)")) - completion_date = models.DateTimeField( + completion_date = models.DateTimeField(blank=True, null=True, help_text=_("Time when the user completed the Sequence")) def __str__(self): diff --git a/pages/serializers.py b/pages/serializers.py index a4abb8b..293fb80 100644 --- a/pages/serializers.py +++ b/pages/serializers.py @@ -29,6 +29,7 @@ from rest_framework import serializers from django.contrib.auth import get_user_model from django.db import transaction +from django.shortcuts import get_object_or_404 from . import settings @@ -368,3 +369,18 @@ class EnumeratedProgressPingSerializer(serializers.ModelSerializer): class Meta: model = EnumeratedProgress fields = ('created_at', 'viewing_duration', 'last_ping_time') + +class AttendanceInputSerializer(serializers.Serializer): + """ + Serializer to validate input to mark users' attendance + to a LiveEvent(LiveEventAttendanceAPIView) + """ + sequence = serializers.SlugField() + username = serializers.CharField() + rank = serializers.IntegerField() + + def validate_sequence(self, value): + return get_object_or_404(Sequence, slug=value) + + def validate_username(self, value): + return get_object_or_404(get_user_model(), username=value) diff --git a/pages/urls/api/progress.py b/pages/urls/api/progress.py index 988ed10..c7feb15 100644 --- a/pages/urls/api/progress.py +++ b/pages/urls/api/progress.py @@ -27,7 +27,7 @@ """ from ...api.progress import EnumeratedProgressAPIView -from django.urls import path +from ...compat import path urlpatterns = [ path('', diff --git a/pages/urls/api/sequences.py b/pages/urls/api/sequences.py index 247ede9..86f0b45 100644 --- a/pages/urls/api/sequences.py +++ b/pages/urls/api/sequences.py @@ -26,12 +26,17 @@ API URLs for sequence objects """ -from ...api.sequences import (SequenceAPIView) +from ...api.sequences import (SequenceAPIView, LiveEventAttendanceAPIView) from rest_framework.routers import DefaultRouter +from ...compat import path + router = DefaultRouter(trailing_slash=False) router.register(r'sequences', SequenceAPIView, basename='api_sequences') urlpatterns = [ + path('sequences////mark-attendance', + LiveEventAttendanceAPIView.as_view(), + name='api_mark_attendance') ] + router.urls