Skip to content
Snippets Groups Projects
Commit 2750e2f8 authored by Benjamin Hättasch's avatar Benjamin Hättasch Committed by Nadja Geisler
Browse files

Scheduling: Persistent plan changes

Introduce writable API for events (protected)
Implement change callback function in javascript
Use full version of jquery in admin backend (to make ajax functionality available)
parent 749a984d
No related branches found
No related tags found
No related merge requests found
......@@ -15,9 +15,10 @@ api_router.register('akslot', views.AKSlotViewSet, basename='AKSlot')
extra_paths = []
if apps.is_installed("AKScheduling"):
from AKScheduling.api import ResourcesViewSet, RoomAvailabilitiesView, EventsView
from AKScheduling.api import ResourcesViewSet, RoomAvailabilitiesView, EventsView, EventsViewSet
api_router.register('scheduling-resources', ResourcesViewSet, basename='scheduling-resources')
api_router.register('scheduling-event', EventsViewSet, basename='scheduling-event')
extra_paths = [
path('api/scheduling-events/', EventsView.as_view(), name='scheduling-events'),
......
from rest_framework import viewsets, permissions, mixins, serializers
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.models import Room
from AKModel.availability.models import Availability
from AKModel.models import Room, AKSlot
from AKModel.views import EventSlugMixin
......@@ -23,3 +30,83 @@ class ResourcesViewSet(EventSlugMixin, mixins.RetrieveModelMixin, mixins.ListMod
def get_queryset(self):
return Room.objects.filter(event=self.event)
class EventsView(LoginRequiredMixin, EventSlugMixin, ListView):
model = AKSlot
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(
[{
"slotID": slot.pk,
"title": slot.ak.short_name,
"description": slot.ak.name,
"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,
# TODO Mark conflicts here?
"borderColor": slot.ak.category.color,
"constraint": 'roomAvailable',
'url': str(reverse('submit:ak_detail', kwargs={"event_slug": self.event.slug, "pk": slot.ak.pk})),
} for slot in context["object_list"]],
safe=False,
**response_kwargs
)
class RoomAvailabilitiesView(LoginRequiredMixin, EventSlugMixin, ListView):
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"),
"backgroundColor": "#28B62C",
"display": 'background',
"groupId": 'roomAvailable',
} for a in context["availabilities"]],
safe=False,
**response_kwargs
)
class EventSerializer(serializers.ModelSerializer):
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):
start = timezone.make_aware(timezone.make_naive(validated_data.get('start', instance.start)), instance.event.timezone)
end = timezone.make_aware(timezone.make_naive(validated_data.get('end', instance.end)), instance.event.timezone)
instance.start = start
instance.room = get_object_or_404(Room, pk=validated_data.get('room')["pk"])
diff = end - start
instance.duration = round(diff.days * 24 + (diff.seconds / 3600), 2)
instance.save()
return instance
class EventsViewSet(EventSlugMixin, viewsets.ModelViewSet):
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)
......@@ -21,6 +21,45 @@
<script>
document.addEventListener('DOMContentLoaded', function () {
// CSRF Protection/Authentication
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
// Place slots by dropping placeholders on calendar
var containerEl = document.getElementById('unscheduled-slots');
new FullCalendar.Draggable(containerEl, {
itemSelector: '.unscheduled-slot',
});
// Calendar
var planEl = document.getElementById('planCalendar');
var plan = new FullCalendar.Calendar(planEl, {
......@@ -67,6 +106,7 @@
slotDuration: '00:30',
}
},
// Show full AK title as tooltip for each AK (needs to be removed and newly placed when AK is moved)
eventDidMount : function(info) {
if(info.event.extendedProps.description !== undefined) {
$(info.el).tooltip({title: info.event.extendedProps.description, trigger: 'hover'});
......@@ -76,6 +116,8 @@
$(info.el).tooltip('dispose');
},
// React to event changes (moving or change of duration)
eventChange: updateEvent,
editable: true,
allDaySlot: false,
nowIndicator: true,
......@@ -94,6 +136,24 @@
plan.setOption('contentHeight', $(window).height() - $('#header').height() * 11);
plan.render();
function updateEvent(changeInfo) {
room = changeInfo.event.getResources()[0];
$.ajax({
url: '{% url "model:scheduling-event-list" event_slug=event.slug %}' + changeInfo.event.extendedProps.slotID + "/",
type: 'PUT',
data: {
start: plan.formatIso(changeInfo.event.start),
end: plan.formatIso(changeInfo.event.end),
roomId: room.id,
},
success: function(response) {},
error: function(response) {
changeInfo.revert();
alert("ERROR. Did not update "+changeInfo.event.title)
}
});
}
});
</script>
{% endblock extrahead %}
......
......@@ -5,7 +5,7 @@
{% block extrahead %}
{% bootstrap_css %}
{% bootstrap_javascript jquery='slim' %}
{% bootstrap_javascript jquery='full' %}
{% fontawesome_5_static %}
<style>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment