Skip to content
Snippets Groups Projects
api.py 8.47 KiB
Newer Older
  • Learn to ignore specific revisions
  • from django.contrib.auth.mixins import LoginRequiredMixin
    from django.http import JsonResponse
    from django.shortcuts import get_object_or_404
    from django.urls import reverse
    from django.utils import timezone
    from django.views.generic import ListView
    from rest_framework import viewsets, mixins, serializers, permissions
    
    from AKModel.availability.models import Availability
    
    from AKModel.models import Room, AKSlot, ConstraintViolation, DefaultSlot
    
    from AKModel.metaviews.admin import EventSlugMixin
    
    
    
    class ResourceSerializer(serializers.ModelSerializer):
    
        """
        REST Framework Serializer for Rooms to produce format required for fullcalendar resources
        """
    
        class Meta:
            model = Room
            fields = ['id', 'title']
    
        title = serializers.SerializerMethodField('transform_title')
    
    
        @staticmethod
        def transform_title(obj):
            """
            Adapt title, add capacity information if room has a restriction (capacity is not -1)
            """
    
            if obj.capacity > 0:
                return f"{obj.title} [{obj.capacity}]"
            return obj.title
    
    
    
    class ResourcesViewSet(EventSlugMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
        """
        API View: Rooms (resources to schedule for in fullcalendar)
    
        Read-only, adaption to fullcalendar format through :class:`ResourceSerializer`
        """
        permission_classes = (permissions.DjangoModelPermissions,)
    
        serializer_class = ResourceSerializer
    
        def get_queryset(self):
    
            return Room.objects.filter(event=self.event).order_by('location', 'name')
    
    
    
    class EventsView(LoginRequiredMixin, EventSlugMixin, ListView):
    
        """
        API View: Slots (events to schedule in fullcalendar)
    
        Read-only, JSON formatted response is created manually since it requires a bunch of "custom" fields that have
        different names compared to the normal model or are not present at all and need to be computed to create the
        required format for fullcalendar.
        """
    
        model = AKSlot
    
        def get_queryset(self):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return super().get_queryset().select_related('ak').filter(event=self.event, room__isnull=False)
    
    
        def render_to_response(self, context, **response_kwargs):
            return JsonResponse(
                [{
                    "slotID": slot.pk,
    
                    "title": f'{slot.ak.short_name}:\n{slot.ak.owners_list}',
    
                    "description": slot.ak.details,
    
                    "resourceId": slot.room.id,
                    "start": timezone.localtime(slot.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
                    "end": timezone.localtime(slot.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
                    "backgroundColor": slot.ak.category.color,
    
                    "borderColor":
                        "#2c3e50" if slot.fixed
                        else '#e74c3c' if slot.constraintviolation_set.count() > 0
                        else slot.ak.category.color,
    
                    "constraint": 'roomAvailable',
    
                    "editable": not slot.fixed,
                    'url': str(reverse('admin:AKModel_akslot_change', args=[slot.pk])),
    
                } for slot in context["object_list"]],
                safe=False,
                **response_kwargs
            )
    
    
    class RoomAvailabilitiesView(LoginRequiredMixin, EventSlugMixin, ListView):
    
        """
        API view: Availabilities of rooms
    
        Read-only, JSON formatted response is created manually since it requires a bunch of "custom" fields that have
        different names compared to the normal model or are not present at all and need to be computed to create the
        required format for fullcalendar.
        """
    
        model = Availability
        context_object_name = "availabilities"
    
        def get_queryset(self):
            return super().get_queryset().filter(event=self.event, room__isnull=False)
    
        def render_to_response(self, context, **response_kwargs):
            return JsonResponse(
                [{
                    "title": "",
                    "resourceId": a.room.id,
                    "start": timezone.localtime(a.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
                    "end": timezone.localtime(a.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
                    "display": 'background',
                    "groupId": 'roomAvailable',
                } for a in context["availabilities"]],
                safe=False,
                **response_kwargs
            )
    
    
    
    class DefaultSlotsView(LoginRequiredMixin, EventSlugMixin, ListView):
    
        """
        API view: default slots
    
        Read-only, JSON formatted response is created manually since it requires a bunch of "custom" fields that have
        different names compared to the normal model or are not present at all and need to be computed to create the
        required format for fullcalendar.
        """
    
        model = DefaultSlot
        context_object_name = "default_slots"
    
        def get_queryset(self):
            return super().get_queryset().filter(event=self.event)
    
        def render_to_response(self, context, **response_kwargs):
            all_room_ids = [r.pk for r in self.event.room_set.all()]
            return JsonResponse(
                [{
                    "title": "",
                    "resourceIds": all_room_ids,
                    "start": timezone.localtime(a.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
                    "end": timezone.localtime(a.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
                    "display": 'background',
                    "groupId": 'defaultSlot',
                    "backgroundColor": '#69b6d4'
                } for a in context["default_slots"]],
                safe=False,
                **response_kwargs
            )
    
    
    
    class EventSerializer(serializers.ModelSerializer):
    
        """
        REST framework serializer to adapt between AKSlot model and the event format of fullcalendar
        """
    
        class Meta:
            model = AKSlot
            fields = ['id', 'start', 'end', 'roomId']
    
        start = serializers.DateTimeField()
        end = serializers.DateTimeField()
        roomId = serializers.IntegerField(source='room.pk')
    
        def update(self, instance, validated_data):
    
            # Ignore timezone of input (treat it as timezone-less) and set the event timezone
            # By working like this, the client does not need to know about timezones, since every timestamp it deals with
            # has the timezone offsets already applied
    
            start = timezone.make_aware(timezone.make_naive(validated_data.get('start')), instance.event.timezone)
            end = timezone.make_aware(timezone.make_naive(validated_data.get('end')), instance.event.timezone)
    
            instance.start = start
    
            # Also, adapt from start & end format of fullcalendar to our start & duration model
    
            diff = end - start
            instance.duration = round(diff.days * 24 + (diff.seconds / 3600), 2)
    
    
            # Updated room if needed (pk changed -- otherwise, no need for an additional database lookup)
            new_room_id = validated_data.get('room')["pk"]
            if instance.room.pk != new_room_id:
                instance.room = get_object_or_404(Room, pk=new_room_id)
    
    
            instance.save()
            return instance
    
    
    
    class EventsViewSet(EventSlugMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
        """
        API view: Update scheduling of a slot (event in fullcalendar format)
    
        Write-only (will however reply with written values to PUT request)
        """
    
        permission_classes = (permissions.DjangoModelPermissions,)
        serializer_class = EventSerializer
    
        def get_object(self):
            return get_object_or_404(AKSlot, pk=self.kwargs["pk"])
    
        def get_queryset(self):
            return AKSlot.objects.filter(event=self.event)
    
    
    
    class ConstraintViolationSerializer(serializers.ModelSerializer):
    
        """
        REST Framework Serializer for constraint violations
        """
    
            fields = ['pk', 'type_display', 'aks', 'ak_slots', 'ak_owner', 'room', 'requirement', 'category', 'comment',
                      'timestamp_display', 'manually_resolved', 'level_display', 'details', 'edit_url']
    
    
    class ConstraintViolationsViewSet(EventSlugMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
        """
        API View: Constraint Violations of an event
    
        Read-only, fields and model selected in :class:`ConstraintViolationSerializer`
        """
    
        permission_classes = (permissions.DjangoModelPermissions,)
        serializer_class = ConstraintViolationSerializer
    
        def get_queryset(self):
    
            # Optimize query to reduce database load
            return (ConstraintViolation.objects.select_related('event', 'room')
                    .prefetch_related('aks', 'ak_slots', 'ak_owner', 'requirement', 'category')
                    .filter(event=self.event).order_by('manually_resolved', '-type', '-timestamp'))