diff --git a/AKModel/admin.py b/AKModel/admin.py index e9601f2496e96b4ced00a3ac3432cd004667b13d..0e7cef569f152c31b71d0f9232e8b1af25fc828c 100644 --- a/AKModel/admin.py +++ b/AKModel/admin.py @@ -217,11 +217,13 @@ class AKSlotAdmin(admin.ModelAdmin): urls = super().get_urls() custom_urls = [] if apps.is_installed("AKScheduling"): - from AKScheduling.views import UnscheduledSlotsAdminView + from AKScheduling.views import SchedulingAdminView, UnscheduledSlotsAdminView custom_urls.extend([ + path('<slug:event_slug>/schedule/', self.admin_site.admin_view(SchedulingAdminView.as_view()), + name="schedule"), path('<slug:event_slug>/unscheduled/', self.admin_site.admin_view(UnscheduledSlotsAdminView.as_view()), - name="slots_unscheduled") + name="slots_unscheduled"), ]) return custom_urls + urls diff --git a/AKScheduling/templates/admin/AKScheduling/scheduling.html b/AKScheduling/templates/admin/AKScheduling/scheduling.html new file mode 100644 index 0000000000000000000000000000000000000000..8c4faf98704e929c0526b49012fa35931115f03e --- /dev/null +++ b/AKScheduling/templates/admin/AKScheduling/scheduling.html @@ -0,0 +1,139 @@ +{% extends "admin_base.html" %} +{% load tags_AKModel %} + +{% load i18n %} +{% load tz %} +{% load static %} +{% load tags_AKPlan %} + +{% block title %}{% trans "Scheduling for" %} {{event}}{% endblock %} + +{% block extrahead %} + {{ block.super }} + {% get_current_language as LANGUAGE_CODE %} + + <script src='{% static 'AKPlan/vendor/fullcalendar-scheduler/main.js' %}'></script> + <link href='{% static 'AKPlan/vendor/fullcalendar-scheduler/main.css' %}' rel='stylesheet'/> + + {% with 'AKPlan/vendor/fullcalendar-scheduler/locales/'|add:LANGUAGE_CODE|add:'.js' as locale_file %} + <script src="{% static locale_file %}"></script> + {% endwith %} + + <script> + document.addEventListener('DOMContentLoaded', function () { + var planEl = document.getElementById('planCalendar'); + + var plan = new FullCalendar.Calendar(planEl, { + timeZone: '{{ event.timezone }}', + headerToolbar : { + left: 'today prev,next', + center: 'title', + right: 'resourceTimelineDayVert,resourceTimelineDayHoriz,resourceTimelineEventVert,resourceTimelineEventHoriz' + }, + //aspectRatio: 2, + themeSystem: 'bootstrap', + // Adapt to user selected locale + locale: '{{ LANGUAGE_CODE }}', + initialView: 'resourceTimelineEventVert', + views: { + resourceTimelineDayHoriz: { + type: 'resourceTimelineDay', + buttonText: '{% trans "Day (Horizontal)" %}', + slotDuration: '00:15', + scrollTime: '08:00', + }, + resourceTimelineDayVert: { + type: 'resourceTimeGridDay', + buttonText: '{% trans "Day (Vertical)" %}', + slotDuration: '00:30', + scrollTime: '08:00', + }, + resourceTimelineEventHoriz: { + type: 'resourceTimeline', + visibleRange: { + start: '{{ event.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', + end: '{{ event.end | timezone:event.timezone | date:"Y-m-d H:i:s"}}', + }, + buttonText: '{% trans "Event (Horizontal)" %}', + slotDuration: '00:15', + }, + resourceTimelineEventVert: { + type: 'resourceTimeGrid', + visibleRange: { + start: '{{ event.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', + end: '{{ event.end | timezone:event.timezone | date:"Y-m-d H:i:s"}}', + }, + buttonText: '{% trans "Event (Vertical)" %}', + slotDuration: '00:30', + } + }, + eventDidMount : function(info) { + if(info.event.extendedProps.description !== undefined) { + $(info.el).tooltip({title: info.event.extendedProps.description, trigger: 'hover'}); + } + }, + eventWillUnmount : function(info) { + $(info.el).tooltip('dispose'); + }, + + editable: true, + allDaySlot: false, + nowIndicator: true, + eventTextColor: '#fff', + eventColor: '#127ba3', + datesAboveResources: true, + resourceAreaHeaderContent: '{% trans "Room" %}', + resources: [ + {% for room in rooms %} + { + 'id': '{{ room.title }}', + 'title': '{{ room.title }}', + }, + {% endfor %} + ], + events: [ + {% for slot in akslots %} + {% if slot.start %} + { + 'title': '{{ slot.ak.short_name }}', + 'description': '{{ slot.ak.name }}', + 'start': '{{ slot.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', + 'end': '{{ slot.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}', + 'resourceId': '{{ slot.room.title }}', + 'backgroundColor': '{{ slot|highlight_change_colors }}', + 'borderColor': '{{ slot.ak.category.color }}', + 'url': '{% url 'submit:ak_detail' event_slug=event.slug pk=slot.ak.pk %}', + constraint: 'roomAvailable', + }, + {% endif %} + {% endfor %} + {% for a in availabilities %} + {% if a.room != None %} + { + title: '', + start: '{{ a.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', + end: '{{ a.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}', + resourceId: '{{ a.room.title }}', + backgroundColor: '#28B62C', + display: 'background', + groupId: 'roomAvailable', + }, + {% endif %} + {% endfor %} + ], + schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source', + dayMinWidth: 100, + }); + + plan.setOption('contentHeight', $(window).height() - $('#header').height() * 11); + plan.render(); + }); + </script> +{% endblock extrahead %} + +{% block content %} + + <div id="planCalendar" style="margin-bottom: 50px;"></div> + + <a href="{% url 'admin:event_status' event.slug %}">{% trans "Event Status" %}</a> +{% endblock %} diff --git a/AKScheduling/views.py b/AKScheduling/views.py index 88523e54dad18c2083450b71c868f8417c92b6e1..fd30864c1782d4f2f45fb315fcd404a384f54adb 100644 --- a/AKScheduling/views.py +++ b/AKScheduling/views.py @@ -1,7 +1,7 @@ -from django.urls import reverse_lazy from django.views.generic import ListView from django.utils.translation import gettext_lazy as _ +from AKModel.availability.models import Availability from AKModel.models import AKSlot from AKModel.views import AdminViewMixin, FilterByEventSlugMixin @@ -18,3 +18,23 @@ class UnscheduledSlotsAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView def get_queryset(self): return super().get_queryset().filter(start=None) + + +class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): + template_name = "admin/AKScheduling/scheduling.html" + model = AKSlot + context_object_name = "akslots" + + def get_context_data(self, *, object_list=None, **kwargs): + context = super().get_context_data(object_list=object_list, **kwargs) + context["title"] = f"{_('Scheduling')} for {context['event']}" + + context["event"] = self.event + context["start"] = self.event.start + context["end"] = self.event.end + + context["rooms"] = self.event.room_set.all() + + context["availabilities"] = Availability.objects.filter(event=self.event, room__isnull=False) + + return context