From 20e28d03e0f8bc378e4e985a6a0422e9a01688e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?= <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de> Date: Sun, 9 May 2021 20:27:33 +0200 Subject: [PATCH] Introduce basic constraint violation admin view showing current violations (using async reloading) --- AKModel/templates/admin/AKModel/status.html | 4 + .../locale/de_DE/LC_MESSAGES/django.po | 73 ++++++++-- .../AKScheduling/constraint_violations.html | 137 ++++++++++++++++++ AKScheduling/urls.py | 5 +- AKScheduling/views.py | 15 +- 5 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 AKScheduling/templates/admin/AKScheduling/constraint_violations.html diff --git a/AKModel/templates/admin/AKModel/status.html b/AKModel/templates/admin/AKModel/status.html index ee84a94a..db22d935 100644 --- a/AKModel/templates/admin/AKModel/status.html +++ b/AKModel/templates/admin/AKModel/status.html @@ -74,6 +74,10 @@ <a class="btn btn-success" href="{% url 'admin:schedule' event_slug=event.slug %}">{% trans "Scheduling" %}</a> + {% if "AKScheduling | is_installed" %} + <a class="btn btn-success" + href="{% url 'admin:constraint-violations' slug=event.slug %}">{% trans "Constraint Violations" %} <span class="badge badge-secondary">{{ event.constraintviolation_set.count }}</span></a> + {% endif %} <a class="btn btn-success" href="{% url 'admin:tracks_manage' event_slug=event.slug %}">{% trans "Manage ak tracks" %}</a> <a class="btn btn-success" diff --git a/AKScheduling/locale/de_DE/LC_MESSAGES/django.po b/AKScheduling/locale/de_DE/LC_MESSAGES/django.po index f25c002d..5ed003e8 100644 --- a/AKScheduling/locale/de_DE/LC_MESSAGES/django.po +++ b/AKScheduling/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-04-29 22:48+0000\n" +"POT-Creation-Date: 2021-05-09 18:23+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -17,49 +17,92 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:11 #: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:11 #: AKScheduling/templates/admin/AKScheduling/scheduling.html:10 msgid "Scheduling for" msgstr "Scheduling für" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:126 +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:74 +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:128 +msgid "No violations" +msgstr "Keine Verletzungen" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:81 +msgid "Cannot load current violations from server" +msgstr "Kann die aktuellen Verletzungen nicht vom Server laden" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:106 +msgid "Violation(s)" +msgstr "Verletzung(en)" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:109 +msgid "Auto reload?" +msgstr "Automatisch neu laden?" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:113 +msgid "Reload now" +msgstr "Jetzt neu laden" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:118 +msgid "Violation" +msgstr "Verletzung" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:119 +msgid "Problem" +msgstr "Problem" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:120 +msgid "Details" +msgstr "Details" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:121 +msgid "Since" +msgstr "Seit" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:134 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:243 +#: AKScheduling/templates/admin/AKScheduling/scheduling.html:197 +#: AKScheduling/templates/admin/AKScheduling/unscheduled.html:34 +msgid "Event Status" +msgstr "Event-Status" + +#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:136 +msgid "Scheduling" +msgstr "Scheduling" + +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:129 msgid "Name of new ak track" msgstr "Name des neuen AK-Tracks" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:142 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:145 msgid "Could not create ak track" msgstr "Konnte neuen AK-Track nicht anlegen" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:168 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:171 msgid "Could not update ak track name" msgstr "Konnte Namen des AK-Tracks nicht ändern" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:174 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:177 msgid "Do you really want to delete this ak track?" msgstr "Soll dieser AK-Track wirklich gelöscht werden?" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:188 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:191 msgid "Could not delete ak track" msgstr "AK-Track konnte nicht gelöscht werden" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:200 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:203 msgid "Manage AK Tracks" msgstr "AK-Tracks verwalten" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:201 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:204 msgid "Add ak track" msgstr "AK-Track hinzufügen" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:206 +#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:209 msgid "AKs without track" msgstr "AKs ohne Track" -#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:240 -#: AKScheduling/templates/admin/AKScheduling/scheduling.html:197 -#: AKScheduling/templates/admin/AKScheduling/unscheduled.html:34 -msgid "Event Status" -msgstr "Event-Status" - #: AKScheduling/templates/admin/AKScheduling/scheduling.html:87 msgid "Day (Horizontal)" msgstr "Tag (horizontal)" diff --git a/AKScheduling/templates/admin/AKScheduling/constraint_violations.html b/AKScheduling/templates/admin/AKScheduling/constraint_violations.html new file mode 100644 index 00000000..8645c192 --- /dev/null +++ b/AKScheduling/templates/admin/AKScheduling/constraint_violations.html @@ -0,0 +1,137 @@ +{% extends "admin/base_site.html" %} +{% load tags_AKModel %} + +{% load i18n %} +{% load l10n %} +{% load tz %} +{% load static %} +{% load tags_AKPlan %} +{% load fontawesome_5 %} + +{% block title %}{% trans "Scheduling for" %} {{event}}{% endblock %} + +{% block extrahead %} + {{ block.super }} + + <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); + } + } + }); + + // (Re-)Load constraint violations using AJAX and visualize using violation count badge and violation table + function reload() { + $.ajax({ + url: "{% url "model:scheduling-constraint-violations-list" event_slug=event.slug %}", + type: 'GET', + success: function (response) { + console.log(response); + + let table_html = ''; + + if(response.length > 0) { + // Update violation count badge + $('#violationCountBadge').html(response.length).removeClass('badge-success').addClass('badge-warning'); + + // Update violations table + for(let i=0;i<response.length;i++) { + table_html += "<tr><td>" + response[i].level_display + "</td><td>" + response[i].type_display + "</td><td>" + response[i].details + "</td><td>" + response[i].timestamp_display + "</td><td></td></tr>"; + } + } + else { + // Update violation count badge + $('#violationCountBadge').html(0).removeClass('badge-warning').addClass('badge-success'); + + // Update violations table + table_html ='<tr class="text-muted"><td colspan="5" class="text-center">{% trans "No violations" %}</td></tr>' + } + + // Show violation list (potentially empty) in violations table + $('#violationsTableBody').html(table_html); + }, + error: function (response) { + alert("{% trans 'Cannot load current violations from server' %}"); + } + }); + } + reload(); + + // Bind reload button + $('#btnReloadNow').click(reload); + + // Toggle automatic reloading (every 30 s) based on checkbox + let autoReloadInterval = undefined; + $('#cbxAutoReload').change(function () { + if(this.checked) { + autoReloadInterval = setInterval(reload, 30*1000); + } + else { + if(autoReloadInterval !== undefined) + clearInterval(autoReloadInterval); + } + }); + }); + </script> +{% endblock extrahead %} + +{% block content %} + <h4 class="mt-4 mb-4"><span id="violationCountBadge" class="badge badge-success">0</span> {% trans "Violation(s)" %}</h4> + + <input type="checkbox" id="cbxAutoReload"> + <label for="cbxAutoReload">{% trans "Auto reload?" %}</label> + + <br> + + <a href="#" id="btnReloadNow" class="btn btn-info">{% fa5_icon "sync-alt" "fas" %} {% trans "Reload now" %}</a> + + <table class="table table-striped mt-4 mb-4"> + <thead> + <tr> + <th>{% trans "Violation" %}</th> + <th>{% trans "Problem" %}</th> + <th>{% trans "Details" %}</th> + <th>{% trans "Since" %}</th> + <th></th> + </tr> + </thead> + <tbody id="violationsTableBody"> + <tr class="text-muted"> + <td colspan="5" class="text-center"> + {% trans "No violations" %} + </td> + </tr> + </tbody> + </table> + + <a href="{% url 'admin:event_status' event.slug %}">{% trans "Event Status" %}</a> + · + <a href="{% url 'admin:schedule' event.slug %}">{% trans "Scheduling" %}</a> +{% endblock %} diff --git a/AKScheduling/urls.py b/AKScheduling/urls.py index 171f3a1a..93f49095 100644 --- a/AKScheduling/urls.py +++ b/AKScheduling/urls.py @@ -1,6 +1,7 @@ from django.urls import path -from AKScheduling.views import SchedulingAdminView, UnscheduledSlotsAdminView, TrackAdminView +from AKScheduling.views import SchedulingAdminView, UnscheduledSlotsAdminView, TrackAdminView, \ + ConstraintViolationsAdminView def get_admin_urls_scheduling(admin_site): @@ -9,6 +10,8 @@ def get_admin_urls_scheduling(admin_site): name="schedule"), path('<slug:event_slug>/unscheduled/', admin_site.admin_view(UnscheduledSlotsAdminView.as_view()), name="slots_unscheduled"), + path('<slug:slug>/constraint-violations/', admin_site.admin_view(ConstraintViolationsAdminView.as_view()), + name="constraint-violations"), path('<slug:event_slug>/tracks/', admin_site.admin_view(TrackAdminView.as_view()), name="tracks_manage"), ] diff --git a/AKScheduling/views.py b/AKScheduling/views.py index 73719bd8..9caadf5a 100644 --- a/AKScheduling/views.py +++ b/AKScheduling/views.py @@ -1,7 +1,7 @@ -from django.views.generic import ListView +from django.views.generic import ListView, DetailView from django.utils.translation import gettext_lazy as _ -from AKModel.models import AKSlot, AKTrack +from AKModel.models import AKSlot, AKTrack, Event from AKModel.views import AdminViewMixin, FilterByEventSlugMixin @@ -47,3 +47,14 @@ class TrackAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): context = super().get_context_data(object_list=object_list, **kwargs) context["aks_without_track"] = self.event.ak_set.filter(track=None) return context + + +class ConstraintViolationsAdminView(AdminViewMixin, DetailView): + template_name = "admin/AKScheduling/constraint_violations.html" + model = Event + context_object_name = "event" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["title"] = f"{_('Constraint violations for')} {context['event']}" + return context -- GitLab