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
[docs]
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')
[docs]
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')
[docs]
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):
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
)
[docs]
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
)
[docs]
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
)
[docs]
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 is None or instance.room.pk != new_room_id:
instance.room = get_object_or_404(Room, pk=new_room_id)
instance.save()
return instance
[docs]
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)
[docs]
class ConstraintViolationSerializer(serializers.ModelSerializer):
"""
REST Framework Serializer for constraint violations
"""
class Meta:
model = ConstraintViolation
fields = ['pk', 'type_display', 'aks', 'ak_slots', 'ak_owner', 'room', 'requirement', 'category', 'comment',
'timestamp_display', 'manually_resolved', 'level_display', 'details', 'edit_url']
[docs]
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'))