From 1133ded42c0dde439b616179ea3e972cb461987b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Mon, 26 Sep 2022 22:10:16 +0200
Subject: [PATCH 1/7] Improve slot handling for wishes

This implements #144
Don't show slots, slot creation button, and availabilities for wishes on detail page
Don't create slots for wishes automatically (and don't ask for durations)
Automatically create one slot when wish changes to AK
Automatically remove all unscheduled slots when AK changes to wish
---
 AKSubmission/forms.py                         |   2 +-
 .../templates/AKSubmission/ak_detail.html     | 149 +++++++++---------
 AKSubmission/views.py                         |  22 ++-
 3 files changed, 94 insertions(+), 79 deletions(-)

diff --git a/AKSubmission/forms.py b/AKSubmission/forms.py
index e24e0a41..55959be4 100644
--- a/AKSubmission/forms.py
+++ b/AKSubmission/forms.py
@@ -151,7 +151,7 @@ class AKEditForm(AKForm):
         self.fields["tags_raw"].initial = "; ".join(str(tag) for tag in self.instance.tags.all())
 
 
-class AKWishForm(AKSubmissionForm):
+class AKWishForm(AKForm):
     class Meta(AKForm.Meta):
         exclude = ['owners', 'link', 'protocol_link']
 
diff --git a/AKSubmission/templates/AKSubmission/ak_detail.html b/AKSubmission/templates/AKSubmission/ak_detail.html
index 69324b63..e5052faf 100644
--- a/AKSubmission/templates/AKSubmission/ak_detail.html
+++ b/AKSubmission/templates/AKSubmission/ak_detail.html
@@ -235,95 +235,96 @@
 
     <p style="margin-top: 30px;margin-bottom: 30px;">{{ ak.description|linebreaks }}</p>
 
-
-    <table class="table">
-        <thead>
-        <tr>
-            {% if not ak.event.plan_hidden or user.is_staff %}
-                <th>{% trans "When?" %}</th>
-            {% endif %}
-            <th>{% trans "Duration" %}</th>
-            {% if not ak.event.plan_hidden or user.is_staff %}
-                <th>{% trans "Room" %}</th>
-            {% endif %}
-            <th></th>
-        </tr>
-        </thead>
-        <tbody>
-        {% for slot in ak.akslot_set.all %}
+    {% if not ak.wish %}
+        <table class="table">
+            <thead>
             <tr>
                 {% if not ak.event.plan_hidden or user.is_staff %}
-                    <td>{{ slot.time_simplified }}</td>
+                    <th>{% trans "When?" %}</th>
                 {% endif %}
-                <td>{{ slot.duration_simplified }}</td>
+                <th>{% trans "Duration" %}</th>
                 {% if not ak.event.plan_hidden or user.is_staff %}
-                    <td>
-                        {% if slot.room %}
-                            {% if "AKPlan"|check_app_installed %}
-                                <a href="{% url 'plan:plan_room' event_slug=ak.event.slug pk=slot.room.pk %}">{{ slot.room }}</a>
+                    <th>{% trans "Room" %}</th>
+                {% endif %}
+                <th></th>
+            </tr>
+            </thead>
+            <tbody>
+            {% for slot in ak.akslot_set.all %}
+                <tr>
+                    {% if not ak.event.plan_hidden or user.is_staff %}
+                        <td>{{ slot.time_simplified }}</td>
+                    {% endif %}
+                    <td>{{ slot.duration_simplified }}</td>
+                    {% if not ak.event.plan_hidden or user.is_staff %}
+                        <td>
+                            {% if slot.room %}
+                                {% if "AKPlan"|check_app_installed %}
+                                    <a href="{% url 'plan:plan_room' event_slug=ak.event.slug pk=slot.room.pk %}">{{ slot.room }}</a>
+                                {% else %}
+                                    {{ slot.room }}
+                                {% endif %}
                             {% else %}
-                                {{ slot.room }}
+                                -
                             {% endif %}
+                        </td>
+                    {% endif %}
+                    <td>
+                        {% if not slot.start %}
+                            <a href="{% url 'submit:akslot_edit' event_slug=ak.event.slug pk=slot.pk %}"
+                               data-toggle="tooltip" title="{% trans 'Edit' %}"
+                               class="btn btn-success">{% fa5_icon 'pencil-alt' 'fas' %}</a>
+                            <a href="{% url 'submit:akslot_delete' event_slug=ak.event.slug pk=slot.pk %}"
+                               data-toggle="tooltip" title="{% trans 'Delete' %}"
+                               class="btn btn-danger">{% fa5_icon 'times' 'fas' %}</a>
                         {% else %}
-                            -
+                            {% if "AKOnline"|check_app_installed and slot.room and slot.room.virtualroom and slot.room.virtualroom.url != '' %}
+                                <a class="btn btn-success" href="{{ slot.room.virtualroom.url }}">
+                                    {% fa5_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
+                                </a>
+                            {% endif %}
                         {% endif %}
-                    </td>
-                {% endif %}
-                <td>
-                    {% if not slot.start %}
-                        <a href="{% url 'submit:akslot_edit' event_slug=ak.event.slug pk=slot.pk %}"
-                           data-toggle="tooltip" title="{% trans 'Edit' %}"
-                           class="btn btn-success">{% fa5_icon 'pencil-alt' 'fas' %}</a>
-                        <a href="{% url 'submit:akslot_delete' event_slug=ak.event.slug pk=slot.pk %}"
-                           data-toggle="tooltip" title="{% trans 'Delete' %}"
-                           class="btn btn-danger">{% fa5_icon 'times' 'fas' %}</a>
-                    {% else %}
-                        {% if "AKOnline"|check_app_installed and slot.room and slot.room.virtualroom and slot.room.virtualroom.url != '' %}
-                            <a class="btn btn-success" href="{{ slot.room.virtualroom.url }}">
-                                {% fa5_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
-                            </a>
+                        {% if user.is_staff %}
+                            <a href="{% url 'admin:AKModel_akslot_change' slot.pk %}"
+                               data-toggle="tooltip" title="{% trans 'Schedule' %}"
+                               class="btn btn-outline-success">{% fa5_icon 'stream' 'fas' %}</a>
                         {% endif %}
-                    {% endif %}
-                    {% if user.is_staff %}
-                        <a href="{% url 'admin:AKModel_akslot_change' slot.pk %}"
-                           data-toggle="tooltip" title="{% trans 'Schedule' %}"
-                           class="btn btn-outline-success">{% fa5_icon 'stream' 'fas' %}</a>
-                    {% endif %}
-                </td>
-            </tr>
-        {% endfor %}
-        </tbody>
-    </table>
+                    </td>
+                </tr>
+            {% endfor %}
+            </tbody>
+        </table>
 
-    {% if ak.event.active %}
-        <div class="">
-            <a href="{% url 'submit:akslot_add' event_slug=ak.event.slug pk=ak.pk %}"
-               class="btn btn-success">{% fa5_icon 'plus' 'fas' %} {% trans "Add another slot" %}</a>
-        </div>
-    {% endif %}
+        {% if ak.event.active %}
+            <div class="">
+                <a href="{% url 'submit:akslot_add' event_slug=ak.event.slug pk=ak.pk %}"
+                   class="btn btn-success">{% fa5_icon 'plus' 'fas' %} {% trans "Add another slot" %}</a>
+            </div>
+        {% endif %}
 
 
-    {% if 'AKPlan'|check_app_installed %}
-        <div id='akSlotCalendar' style="margin-top: 50px;margin-bottom: 50px;"></div>
-    {% endif %}
+        {% if 'AKPlan'|check_app_installed %}
+            <div id='akSlotCalendar' style="margin-top: 50px;margin-bottom: 50px;"></div>
+        {% endif %}
 
 
-    <h4 style="margin-top: 30px;">{% trans "Possible Times" %}</h4>
-    <table class="table">
-        <thead>
-        <tr>
-            <th>{% trans "Start" %}</th>
-            <th>{% trans "End" %}</th>
-        </tr>
-        </thead>
-        <tbody>
-        {% for a in availabilities %}
+        <h4 style="margin-top: 30px;">{% trans "Possible Times" %}</h4>
+        <table class="table">
+            <thead>
             <tr>
-                <td>{{ a.start | timezone:event.timezone | date:"l H:i"  }}</td>
-                <td>{{ a.end | timezone:event.timezone | date:"l H:i" }}</td>
+                <th>{% trans "Start" %}</th>
+                <th>{% trans "End" %}</th>
             </tr>
-        {% endfor %}
-        </tbody>
-    </table>
+            </thead>
+            <tbody>
+            {% for a in availabilities %}
+                <tr>
+                    <td>{{ a.start | timezone:event.timezone | date:"l H:i"  }}</td>
+                    <td>{{ a.end | timezone:event.timezone | date:"l H:i" }}</td>
+                </tr>
+            {% endfor %}
+            </tbody>
+        </table>
+    {% endif %}
 
 {% endblock %}
diff --git a/AKSubmission/views.py b/AKSubmission/views.py
index 28e6b6d1..15d0811d 100644
--- a/AKSubmission/views.py
+++ b/AKSubmission/views.py
@@ -223,10 +223,11 @@ class AKAndAKWishSubmissionView(EventSlugMixin, EventInactiveRedirectMixin, Crea
             tag, was_created = AKTag.objects.get_or_create(name=tag_name)
             self.object.tags.add(tag)
 
-        # Generate slot(s)
-        for duration in form.cleaned_data["durations"]:
-            new_slot = AKSlot(ak=self.object, duration=duration, event=self.object.event)
-            new_slot.save()
+        # Generate slot(s) (but not for wishes)
+        if "durations" in form.cleaned_data:
+            for duration in form.cleaned_data["durations"]:
+                new_slot = AKSlot(ak=self.object, duration=duration, event=self.object.event)
+                new_slot.save()
 
         return super_form_valid
 
@@ -269,6 +270,8 @@ class AKEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView):
             return redirect(reverse_lazy('submit:submission_overview',
                                          kwargs={'event_slug': form.cleaned_data["event"].slug}))
 
+        previous_owner_count = self.object.owners.count()
+
         super_form_valid = super().form_valid(form)
 
         # Detach existing tags
@@ -279,6 +282,17 @@ class AKEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView):
             tag, was_created = AKTag.objects.get_or_create(name=tag_name)
             self.object.tags.add(tag)
 
+        # Did this AK change from wish to AK or vice versa?
+        new_owner_count = self.object.owners.count()
+        # Now AK:
+        if previous_owner_count == 0 and new_owner_count > 0 and self.object.akslot_set.count() == 0:
+            # Create one slot with default length
+            AKSlot.objects.create(ak=self.object, duration=self.object.event.default_slot, event=self.object.event)
+        # Now wish:
+        elif previous_owner_count > 0 and new_owner_count == 0:
+            # Delete all unscheduled slots
+            self.object.akslot_set.filter(start__isnull=True).delete()
+
         return super_form_valid
 
 
-- 
GitLab


From 2f9789a89c619d8a820344a247e33a973cbfaefd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Tue, 27 Sep 2022 13:24:08 +0200
Subject: [PATCH 2/7] Introduce generic admin action view with form & adapt
 orga message deletion

Introduce a new generic view that can be used for all admin actions that require a preview/confirmation or a form to specify parameters
Change AK Orga Message deletion view to use this generic view instead of a manually adapted delete view
---
 AKModel/forms.py                              |  4 +++
 .../admin/AKModel/action_intermediate.html    | 30 ++++++++++++++++
 .../admin/AKModel/message_delete.html         | 17 ++-------
 AKModel/templates/admin/AKModel/status.html   |  2 +-
 AKModel/urls.py                               |  2 +-
 AKModel/views.py                              | 36 +++++++++++++++----
 6 files changed, 67 insertions(+), 24 deletions(-)
 create mode 100644 AKModel/templates/admin/AKModel/action_intermediate.html

diff --git a/AKModel/forms.py b/AKModel/forms.py
index 43dcfa96..1f5c07ee 100644
--- a/AKModel/forms.py
+++ b/AKModel/forms.py
@@ -68,3 +68,7 @@ class NewEventWizardActivateForm(forms.ModelForm):
     class Meta:
         fields = ["active"]
         model = Event
+
+
+class AdminIntermediateForm(forms.Form):
+    pass
diff --git a/AKModel/templates/admin/AKModel/action_intermediate.html b/AKModel/templates/admin/AKModel/action_intermediate.html
new file mode 100644
index 00000000..0c211ecb
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/action_intermediate.html
@@ -0,0 +1,30 @@
+{% extends "admin/base_site.html" %}
+{% load tags_AKModel %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome_5 %}
+
+
+{% block title %}{{event}}: {{ title }}{% endblock %}
+
+{% block content %}
+    {% block action_preview %}
+        <p>
+            {{ preview|linebreaksbr }}
+        </p>
+    {% endblock %}
+
+    <form method="post">{% csrf_token %}
+        {% bootstrap_form form %}
+
+        <div class="float-right">
+            <button type="submit" class="save btn btn-success" value="Submit">
+                {% fa5_icon "check" 'fas' %} {% trans "Confirm" %}
+            </button>
+        </div>
+        <a href="javascript:history.back()" class="btn btn-info">
+            {% fa5_icon "times" 'fas' %} {% trans "Cancel" %}
+        </a>
+    </form>
+{% endblock %}
diff --git a/AKModel/templates/admin/AKModel/message_delete.html b/AKModel/templates/admin/AKModel/message_delete.html
index 03b5899b..1bdbf0a5 100644
--- a/AKModel/templates/admin/AKModel/message_delete.html
+++ b/AKModel/templates/admin/AKModel/message_delete.html
@@ -1,24 +1,11 @@
-{% extends "admin/base_site.html" %}
+{% extends "admin/AKModel/action_intermediate.html" %}
 {% load tags_AKModel %}
 
 {% load i18n %}
 {% load fontawesome_5 %}
 
-{% block title %}{{event}}: {% trans "Delete Orga-Messages" %}{% endblock %}
-
-{% block content %}
-    <h2>{% trans "Delete AK Orga Messages" %}</h2>
+{% block action_preview %}
     <p>{% blocktrans with message_count=ak_messages.count %}Are you sure you want to delete all orga messages for {{ event }}? This will permanently delete {{ message_count }} message(s):{% endblocktrans %}</p>
 
     {% include "admin/AKModel/render_ak_messages.html" %}
-
-    <form method="post">{% csrf_token %}
-        <button type="submit" class="save btn btn-danger float-right" value="Confirm">
-            {% fa5_icon "check" 'fas' %} {% trans "Delete" %}
-        </button>
-
-        <a href="{% url 'admin:event_status' slug=event.slug %}" class="btn btn-info">
-            {% fa5_icon "times" 'fas' %} {% trans "Cancel" %}
-        </a>
-    </form>
 {% endblock %}
diff --git a/AKModel/templates/admin/AKModel/status.html b/AKModel/templates/admin/AKModel/status.html
index f64a5a0c..362c7240 100644
--- a/AKModel/templates/admin/AKModel/status.html
+++ b/AKModel/templates/admin/AKModel/status.html
@@ -115,7 +115,7 @@
             <div class="col-md-4">
                 <h3 class="block-header">{% trans "Messages" %}</h3>
                 {% include "admin/AKModel/render_ak_messages.html" %}
-                <a class="btn btn-danger" href="{% url 'admin:ak_delete_orga_messages' slug=event.slug %}">{% trans "Delete all messages" %}</a>
+                <a class="btn btn-danger" href="{% url 'admin:ak_delete_orga_messages' event_slug=event.slug %}">{% trans "Delete all messages" %}</a>
             </div>
         </div>
     {% endtimezone %}
diff --git a/AKModel/urls.py b/AKModel/urls.py
index 2071acc9..ca9cfe67 100644
--- a/AKModel/urls.py
+++ b/AKModel/urls.py
@@ -79,7 +79,7 @@ def get_admin_urls_event(admin_site):
              name="ak_csv_export"),
         path('<slug:slug>/ak-wiki-export/', admin_site.admin_view(AKWikiExportView.as_view()),
              name="ak_wiki_export"),
-        path('<slug:slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()),
+        path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()),
              name="ak_delete_orga_messages"),
         path('<slug:event_slug>/ak-slide-export/', export_slides, name="ak_slide_export"),
 
diff --git a/AKModel/views.py b/AKModel/views.py
index 2729bdb6..08f054f1 100644
--- a/AKModel/views.py
+++ b/AKModel/views.py
@@ -1,3 +1,4 @@
+from abc import ABC, abstractmethod
 from itertools import zip_longest
 
 from django.contrib import admin, messages
@@ -11,7 +12,7 @@ from django_tex.shortcuts import render_to_pdf
 from rest_framework import viewsets, permissions, mixins
 
 from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \
-    NewEventWizardImportForm, NewEventWizardActivateForm
+    NewEventWizardImportForm, NewEventWizardActivateForm, AdminIntermediateForm
 from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement
 from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \
     AKOwnerSerializer
@@ -194,22 +195,43 @@ class AKWikiExportView(AdminViewMixin, DetailView):
         return context
 
 
-class AKMessageDeleteView(AdminViewMixin, DeleteView):
-    model = Event
+class IntermediateAdminView(AdminViewMixin, FormView, ABC):
+    template_name = "admin/AKModel/action_intermediate.html"
+    form_class = AdminIntermediateForm
+
+    @abstractmethod
+    def get_preview(self):
+        pass
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["title"] = self.title
+        context["preview"] = self.get_preview()
+        return context
+
+
+class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView):
     template_name = "admin/AKModel/message_delete.html"
+    title = _("Delete AK Orga Messages")
 
     def get_orga_messages_for_event(self, event):
         return AKOrgaMessage.objects.filter(ak__event=event)
 
+    def get_preview(self):
+        return None
+
+    def get_success_url(self):
+        return reverse_lazy('admin:event_status', kwargs={'slug': self.event.slug})
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        context["ak_messages"] = self.get_orga_messages_for_event(self.get_object())
+        context["ak_messages"] = self.get_orga_messages_for_event(self.event)
         return context
 
-    def post(self, request, *args, **kwargs):
-        self.get_orga_messages_for_event(self.get_object()).delete()
+    def form_valid(self, form):
+        self.get_orga_messages_for_event(self.event).delete()
         messages.add_message(self.request, messages.SUCCESS, _("AK Orga Messages successfully deleted"))
-        return HttpResponseRedirect(reverse_lazy('admin:event_status', kwargs={'slug': self.get_object().slug}))
+        return super().form_valid(form)
 
 
 class WizardViewMixin:
-- 
GitLab


From 563b72347a1cad3f748a0bade199830706fcb4b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Tue, 27 Sep 2022 14:18:54 +0200
Subject: [PATCH 3/7] Introduce admin action/view to delete unscheduled slots
 of wishes

This implements first part of #145
---
 AKModel/locale/de_DE/LC_MESSAGES/django.po    | 413 +++++++++---------
 AKModel/models.py                             |  11 +
 .../locale/de_DE/LC_MESSAGES/django.po        |  33 +-
 .../admin/AKScheduling/special_attention.html |   1 +
 AKScheduling/urls.py                          |   4 +-
 AKScheduling/views.py                         |  22 +-
 6 files changed, 270 insertions(+), 214 deletions(-)

diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po
index 6e3bc74d..3aefe1d4 100644
--- a/AKModel/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKModel/locale/de_DE/LC_MESSAGES/django.po
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-08-17 22:41+0200\n"
+"POT-Creation-Date: 2022-09-27 14:14+0200\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"
@@ -65,17 +65,17 @@ msgstr "Die eingegebene Verfügbarkeit enthält ein ungültiges Datum."
 msgid "Please fill in your availabilities!"
 msgstr "Bitte Verfügbarkeiten eintragen!"
 
-#: .\AKModel\availability\models.py:38 .\AKModel\models.py:53
-#: .\AKModel\models.py:117 .\AKModel\models.py:172 .\AKModel\models.py:191
-#: .\AKModel\models.py:223 .\AKModel\models.py:277 .\AKModel\models.py:343
-#: .\AKModel\models.py:376 .\AKModel\models.py:447 .\AKModel\models.py:488
+#: .\AKModel\availability\models.py:38 .\AKModel\models.py:54
+#: .\AKModel\models.py:120 .\AKModel\models.py:175 .\AKModel\models.py:194
+#: .\AKModel\models.py:226 .\AKModel\models.py:280 .\AKModel\models.py:354
+#: .\AKModel\models.py:387 .\AKModel\models.py:458 .\AKModel\models.py:499
 msgid "Event"
 msgstr "Event"
 
-#: .\AKModel\availability\models.py:39 .\AKModel\models.py:118
-#: .\AKModel\models.py:173 .\AKModel\models.py:192 .\AKModel\models.py:224
-#: .\AKModel\models.py:278 .\AKModel\models.py:344 .\AKModel\models.py:377
-#: .\AKModel\models.py:448 .\AKModel\models.py:489
+#: .\AKModel\availability\models.py:39 .\AKModel\models.py:121
+#: .\AKModel\models.py:176 .\AKModel\models.py:195 .\AKModel\models.py:227
+#: .\AKModel\models.py:281 .\AKModel\models.py:355 .\AKModel\models.py:388
+#: .\AKModel\models.py:459 .\AKModel\models.py:500
 msgid "Associated event"
 msgstr "Zugehöriges Event"
 
@@ -87,8 +87,8 @@ msgstr "Person"
 msgid "Person whose availability this is"
 msgstr "Person deren Verfügbarkeit hier abgebildet wird"
 
-#: .\AKModel\availability\models.py:56 .\AKModel\models.py:347
-#: .\AKModel\models.py:366 .\AKModel\models.py:497
+#: .\AKModel\availability\models.py:56 .\AKModel\models.py:358
+#: .\AKModel\models.py:377 .\AKModel\models.py:508
 msgid "Room"
 msgstr "Raum"
 
@@ -96,8 +96,8 @@ msgstr "Raum"
 msgid "Room whose availability this is"
 msgstr "Raum dessen Verfügbarkeit hier abgebildet wird"
 
-#: .\AKModel\availability\models.py:65 .\AKModel\models.py:283
-#: .\AKModel\models.py:365 .\AKModel\models.py:442
+#: .\AKModel\availability\models.py:65 .\AKModel\models.py:286
+#: .\AKModel\models.py:376 .\AKModel\models.py:453
 msgid "AK"
 msgstr "AK"
 
@@ -105,8 +105,8 @@ msgstr "AK"
 msgid "AK whose availability this is"
 msgstr "Verfügbarkeiten"
 
-#: .\AKModel\availability\models.py:74 .\AKModel\models.py:176
-#: .\AKModel\models.py:503
+#: .\AKModel\availability\models.py:74 .\AKModel\models.py:179
+#: .\AKModel\models.py:514
 msgid "AK Category"
 msgstr "AK-Kategorie"
 
@@ -135,131 +135,131 @@ msgstr "AK-Kategorien kopieren"
 msgid "Copy ak requirements"
 msgstr "AK-Anforderungen kopieren"
 
-#: .\AKModel\models.py:17 .\AKModel\models.py:164 .\AKModel\models.py:188
-#: .\AKModel\models.py:207 .\AKModel\models.py:221 .\AKModel\models.py:239
-#: .\AKModel\models.py:335
+#: .\AKModel\models.py:18 .\AKModel\models.py:167 .\AKModel\models.py:191
+#: .\AKModel\models.py:210 .\AKModel\models.py:224 .\AKModel\models.py:242
+#: .\AKModel\models.py:346
 msgid "Name"
 msgstr "Name"
 
-#: .\AKModel\models.py:18
+#: .\AKModel\models.py:19
 msgid "Name or iteration of the event"
 msgstr "Name oder Iteration des Events"
 
-#: .\AKModel\models.py:19
+#: .\AKModel\models.py:20
 msgid "Short Form"
 msgstr "Kurzer Name"
 
-#: .\AKModel\models.py:20
+#: .\AKModel\models.py:21
 msgid "Short name of letters/numbers/dots/dashes/underscores used in URLs."
 msgstr ""
 "Kurzname bestehend aus Buchstaben, Nummern, Punkten und Unterstrichen zur "
 "Nutzung in URLs"
 
-#: .\AKModel\models.py:22
+#: .\AKModel\models.py:23
 msgid "Place"
 msgstr "Ort"
 
-#: .\AKModel\models.py:23
+#: .\AKModel\models.py:24
 msgid "City etc. the event takes place in"
 msgstr "Stadt o.ä. in der das Event stattfindet"
 
-#: .\AKModel\models.py:25
+#: .\AKModel\models.py:26
 msgid "Time Zone"
 msgstr "Zeitzone"
 
-#: .\AKModel\models.py:25
+#: .\AKModel\models.py:26
 msgid "Time Zone where this event takes place in"
 msgstr "Zeitzone in der das Event stattfindet"
 
-#: .\AKModel\models.py:26 .\AKModel\views.py:220
+#: .\AKModel\models.py:27 .\AKModel\views.py:242
 msgid "Start"
 msgstr "Start"
 
-#: .\AKModel\models.py:26
+#: .\AKModel\models.py:27
 msgid "Time the event begins"
 msgstr "Zeit zu der das Event beginnt"
 
-#: .\AKModel\models.py:27
+#: .\AKModel\models.py:28
 msgid "End"
 msgstr "Ende"
 
-#: .\AKModel\models.py:27
+#: .\AKModel\models.py:28
 msgid "Time the event ends"
 msgstr "Zeit zu der das Event endet"
 
-#: .\AKModel\models.py:28
+#: .\AKModel\models.py:29
 msgid "Resolution Deadline"
 msgstr "Resolutionsdeadline"
 
-#: .\AKModel\models.py:29
+#: .\AKModel\models.py:30
 msgid "When should AKs with intention to submit a resolution be done?"
 msgstr "Wann sollen AKs mit Resolutionsabsicht stattgefunden haben?"
 
-#: .\AKModel\models.py:31
+#: .\AKModel\models.py:32
 msgid "Interest Window Start"
 msgstr "Beginn Interessensbekundung"
 
-#: .\AKModel\models.py:32
+#: .\AKModel\models.py:33
 msgid "Opening time for expression of interest."
 msgstr "Öffnungszeitpunkt für die Angabe von Interesse an AKs."
 
-#: .\AKModel\models.py:33
+#: .\AKModel\models.py:34
 msgid "Interest Window End"
 msgstr "Ende Interessensbekundung"
 
-#: .\AKModel\models.py:34
+#: .\AKModel\models.py:35
 msgid "Closing time for expression of interest."
 msgstr "Öffnungszeitpunkt für die Angabe von Interesse an AKs."
 
-#: .\AKModel\models.py:36
+#: .\AKModel\models.py:37
 msgid "Public event"
 msgstr "Öffentliches Event"
 
-#: .\AKModel\models.py:37
+#: .\AKModel\models.py:38
 msgid "Show this event on overview page."
 msgstr "Zeige dieses Event auf der Ãœbersichtseite an"
 
-#: .\AKModel\models.py:39
+#: .\AKModel\models.py:40
 msgid "Active State"
 msgstr "Aktiver Status"
 
-#: .\AKModel\models.py:39
+#: .\AKModel\models.py:40
 msgid "Marks currently active events"
 msgstr "Markiert aktuell aktive Events"
 
-#: .\AKModel\models.py:40
+#: .\AKModel\models.py:41
 msgid "Plan Hidden"
 msgstr "Plan verborgen"
 
-#: .\AKModel\models.py:40
+#: .\AKModel\models.py:41
 msgid "Hides plan for non-staff users"
 msgstr "Verbirgt den Plan für Nutzer*innen ohne erweiterte Rechte"
 
-#: .\AKModel\models.py:43
+#: .\AKModel\models.py:44
 msgid "Base URL"
 msgstr "URL-Prefix"
 
-#: .\AKModel\models.py:43
+#: .\AKModel\models.py:44
 msgid "Prefix for wiki link construction"
 msgstr "Prefix für die automatische Generierung von Wiki-Links"
 
-#: .\AKModel\models.py:44
+#: .\AKModel\models.py:45
 msgid "Wiki Export Template Name"
 msgstr "Wiki-Export Templatename"
 
-#: .\AKModel\models.py:45
+#: .\AKModel\models.py:46
 msgid "Default Slot Length"
 msgstr "Standardslotlänge"
 
-#: .\AKModel\models.py:46
+#: .\AKModel\models.py:47
 msgid "Default length in hours that is assumed for AKs in this event."
 msgstr "Standardlänge von Slots (in Stunden) für dieses Event"
 
-#: .\AKModel\models.py:48
+#: .\AKModel\models.py:49
 msgid "Contact email address"
 msgstr "E-Mail Kontaktadresse"
 
-#: .\AKModel\models.py:50
+#: .\AKModel\models.py:51
 msgid ""
 "An email address that is displayed on every page and can be used for all "
 "kinds of questions"
@@ -267,75 +267,75 @@ msgstr ""
 "Eine Mailadresse die auf jeder Seite angezeigt wird und für alle Arten von "
 "Fragen genutzt werden kann"
 
-#: .\AKModel\models.py:54
+#: .\AKModel\models.py:55
 msgid "Events"
 msgstr "Events"
 
-#: .\AKModel\models.py:112
+#: .\AKModel\models.py:115
 msgid "Nickname"
 msgstr "Spitzname"
 
-#: .\AKModel\models.py:112
+#: .\AKModel\models.py:115
 msgid "Name to identify an AK owner by"
 msgstr "Name, durch den eine AK-Leitung identifiziert wird"
 
-#: .\AKModel\models.py:113
+#: .\AKModel\models.py:116
 msgid "Slug"
 msgstr "Slug"
 
-#: .\AKModel\models.py:113
+#: .\AKModel\models.py:116
 msgid "Slug for URL generation"
 msgstr "Slug für URL-Generierung"
 
-#: .\AKModel\models.py:114
+#: .\AKModel\models.py:117
 msgid "Institution"
 msgstr "Instutution"
 
-#: .\AKModel\models.py:114
+#: .\AKModel\models.py:117
 msgid "Uni etc."
 msgstr "Universität o.ä."
 
-#: .\AKModel\models.py:115 .\AKModel\models.py:248
+#: .\AKModel\models.py:118 .\AKModel\models.py:251
 msgid "Web Link"
 msgstr "Internet Link"
 
-#: .\AKModel\models.py:115
+#: .\AKModel\models.py:118
 msgid "Link to Homepage"
 msgstr "Link zu Homepage oder Webseite"
 
-#: .\AKModel\models.py:121 .\AKModel\models.py:496
+#: .\AKModel\models.py:124 .\AKModel\models.py:507
 msgid "AK Owner"
 msgstr "AK-Leitung"
 
-#: .\AKModel\models.py:122
+#: .\AKModel\models.py:125
 msgid "AK Owners"
 msgstr "AK-Leitungen"
 
-#: .\AKModel\models.py:164
+#: .\AKModel\models.py:167
 msgid "Name of the AK Category"
 msgstr "Name der AK-Kategorie"
 
-#: .\AKModel\models.py:165 .\AKModel\models.py:189
+#: .\AKModel\models.py:168 .\AKModel\models.py:192
 msgid "Color"
 msgstr "Farbe"
 
-#: .\AKModel\models.py:165 .\AKModel\models.py:189
+#: .\AKModel\models.py:168 .\AKModel\models.py:192
 msgid "Color for displaying"
 msgstr "Farbe für die Anzeige"
 
-#: .\AKModel\models.py:166 .\AKModel\models.py:242
+#: .\AKModel\models.py:169 .\AKModel\models.py:245
 msgid "Description"
 msgstr "Beschreibung"
 
-#: .\AKModel\models.py:167
+#: .\AKModel\models.py:170
 msgid "Short description of this AK Category"
 msgstr "Beschreibung der AK-Kategorie"
 
-#: .\AKModel\models.py:168
+#: .\AKModel\models.py:171
 msgid "Present by default"
 msgstr "Defaultmäßig präsentieren"
 
-#: .\AKModel\models.py:170
+#: .\AKModel\models.py:173
 msgid ""
 "Present AKs of this category by default if AK owner did not specify whether "
 "this AK should be presented?"
@@ -343,152 +343,152 @@ msgstr ""
 "AKs dieser Kategorie standardmäßig vorstellen, wenn die Leitungen das für "
 "ihren AK nicht explizit spezifiziert haben?"
 
-#: .\AKModel\models.py:177
+#: .\AKModel\models.py:180
 msgid "AK Categories"
 msgstr "AK-Kategorien"
 
-#: .\AKModel\models.py:188
+#: .\AKModel\models.py:191
 msgid "Name of the AK Track"
 msgstr "Name des AK-Tracks"
 
-#: .\AKModel\models.py:195
+#: .\AKModel\models.py:198
 msgid "AK Track"
 msgstr "AK-Track"
 
-#: .\AKModel\models.py:196
+#: .\AKModel\models.py:199
 msgid "AK Tracks"
 msgstr "AK-Tracks"
 
-#: .\AKModel\models.py:207
+#: .\AKModel\models.py:210
 msgid "Name of the AK Tag"
 msgstr "Name das AK-Tags"
 
-#: .\AKModel\models.py:210
+#: .\AKModel\models.py:213
 msgid "AK Tag"
 msgstr "AK-Tag"
 
-#: .\AKModel\models.py:211
+#: .\AKModel\models.py:214
 msgid "AK Tags"
 msgstr "AK-Tags"
 
-#: .\AKModel\models.py:221
+#: .\AKModel\models.py:224
 msgid "Name of the Requirement"
 msgstr "Name der Anforderung"
 
-#: .\AKModel\models.py:227 .\AKModel\models.py:500
+#: .\AKModel\models.py:230 .\AKModel\models.py:511
 msgid "AK Requirement"
 msgstr "AK-Anforderung"
 
-#: .\AKModel\models.py:228
+#: .\AKModel\models.py:231
 msgid "AK Requirements"
 msgstr "AK-Anforderungen"
 
-#: .\AKModel\models.py:239
+#: .\AKModel\models.py:242
 msgid "Name of the AK"
 msgstr "Name des AKs"
 
-#: .\AKModel\models.py:240
+#: .\AKModel\models.py:243
 msgid "Short Name"
 msgstr "Kurzer Name"
 
-#: .\AKModel\models.py:241
+#: .\AKModel\models.py:244
 msgid "Name displayed in the schedule"
 msgstr "Name zur Anzeige im AK-Plan"
 
-#: .\AKModel\models.py:242
+#: .\AKModel\models.py:245
 msgid "Description of the AK"
 msgstr "Beschreibung des AKs"
 
-#: .\AKModel\models.py:244
+#: .\AKModel\models.py:247
 msgid "Owners"
 msgstr "Leitungen"
 
-#: .\AKModel\models.py:245
+#: .\AKModel\models.py:248
 msgid "Those organizing the AK"
 msgstr "Menschen, die den AK organisieren und halten"
 
-#: .\AKModel\models.py:248
+#: .\AKModel\models.py:251
 msgid "Link to wiki page"
 msgstr "Link zur Wiki Seite"
 
-#: .\AKModel\models.py:249
+#: .\AKModel\models.py:252
 msgid "Protocol Link"
 msgstr "Protokolllink"
 
-#: .\AKModel\models.py:249
+#: .\AKModel\models.py:252
 msgid "Link to protocol"
 msgstr "Link zum Protokoll"
 
-#: .\AKModel\models.py:251
+#: .\AKModel\models.py:254
 msgid "Category"
 msgstr "Kategorie"
 
-#: .\AKModel\models.py:252
+#: .\AKModel\models.py:255
 msgid "Category of the AK"
 msgstr "Kategorie des AKs"
 
-#: .\AKModel\models.py:253
+#: .\AKModel\models.py:256
 msgid "Tags"
 msgstr "Tags"
 
-#: .\AKModel\models.py:253
+#: .\AKModel\models.py:256
 msgid "Tags provided by owners"
 msgstr "Tags, die durch die AK-Leitung vergeben wurden"
 
-#: .\AKModel\models.py:254
+#: .\AKModel\models.py:257
 msgid "Track"
 msgstr "Track"
 
-#: .\AKModel\models.py:255
+#: .\AKModel\models.py:258
 msgid "Track the AK belongs to"
 msgstr "Track zu dem der AK gehört"
 
-#: .\AKModel\models.py:257
+#: .\AKModel\models.py:260
 msgid "Resolution Intention"
 msgstr "Resolutionsabsicht"
 
-#: .\AKModel\models.py:258
+#: .\AKModel\models.py:261
 msgid "Intends to submit a resolution"
 msgstr "Beabsichtigt eine Resolution einzureichen"
 
-#: .\AKModel\models.py:259
+#: .\AKModel\models.py:262
 msgid "Present this AK"
 msgstr "AK präsentieren"
 
-#: .\AKModel\models.py:260
+#: .\AKModel\models.py:263
 msgid "Present results of this AK"
 msgstr "Die Ergebnisse dieses AKs vorstellen"
 
-#: .\AKModel\models.py:262 .\AKModel\templates\admin\AKModel\status.html:97
+#: .\AKModel\models.py:265 .\AKModel\templates\admin\AKModel\status.html:97
 msgid "Requirements"
 msgstr "Anforderungen"
 
-#: .\AKModel\models.py:263
+#: .\AKModel\models.py:266
 msgid "AK's Requirements"
 msgstr "Anforderungen des AKs"
 
-#: .\AKModel\models.py:265
+#: .\AKModel\models.py:268
 msgid "Conflicting AKs"
 msgstr "AK-Konflikte"
 
-#: .\AKModel\models.py:266
+#: .\AKModel\models.py:269
 msgid "AKs that conflict and thus must not take place at the same time"
 msgstr ""
 "AKs, die Konflikte haben und deshalb nicht gleichzeitig stattfinden dürfen"
 
-#: .\AKModel\models.py:267
+#: .\AKModel\models.py:270
 msgid "Prerequisite AKs"
 msgstr "Vorausgesetzte AKs"
 
-#: .\AKModel\models.py:268
+#: .\AKModel\models.py:271
 msgid "AKs that should precede this AK in the schedule"
 msgstr "AKs die im AK-Plan vor diesem AK stattfinden müssen"
 
-#: .\AKModel\models.py:270
+#: .\AKModel\models.py:273
 msgid "Organizational Notes"
 msgstr "Notizen zur Organisation"
 
-#: .\AKModel\models.py:271
+#: .\AKModel\models.py:274
 #, fuzzy
 #| msgid ""
 #| "Notes to organizers. These are public. For private notes, please send an "
@@ -502,258 +502,258 @@ msgstr ""
 "Anmerkungen bitte den Button für Direktnachrichten verwenden (nach dem "
 "Anlegen/Bearbeiten)."
 
-#: .\AKModel\models.py:273
+#: .\AKModel\models.py:276
 msgid "Interest"
 msgstr "Interesse"
 
-#: .\AKModel\models.py:273
+#: .\AKModel\models.py:276
 msgid "Expected number of people"
 msgstr "Erwartete Personenzahl"
 
-#: .\AKModel\models.py:274
+#: .\AKModel\models.py:277
 msgid "Interest Counter"
 msgstr "Interessenszähler"
 
-#: .\AKModel\models.py:275
+#: .\AKModel\models.py:278
 msgid "People who have indicated interest online"
 msgstr "Anzahl Personen, die online Interesse bekundet haben"
 
-#: .\AKModel\models.py:284 .\AKModel\models.py:491
+#: .\AKModel\models.py:287 .\AKModel\models.py:502
 #: .\AKModel\templates\admin\AKModel\status.html:49
-#: .\AKModel\templates\admin\AKModel\status.html:56 .\AKModel\views.py:337
+#: .\AKModel\templates\admin\AKModel\status.html:56 .\AKModel\views.py:359
 msgid "AKs"
 msgstr "AKs"
 
-#: .\AKModel\models.py:335
+#: .\AKModel\models.py:346
 msgid "Name or number of the room"
 msgstr "Name oder Nummer des Raums"
 
-#: .\AKModel\models.py:336
+#: .\AKModel\models.py:347
 msgid "Location"
 msgstr "Ort"
 
-#: .\AKModel\models.py:337
+#: .\AKModel\models.py:348
 msgid "Name or number of the location"
 msgstr "Name oder Nummer des Ortes"
 
-#: .\AKModel\models.py:338
+#: .\AKModel\models.py:349
 msgid "Capacity"
 msgstr "Kapazität"
 
-#: .\AKModel\models.py:339
+#: .\AKModel\models.py:350
 msgid "Maximum number of people (-1 for unlimited)."
 msgstr "Maximale Personenzahl (-1 wenn unbeschränkt)."
 
-#: .\AKModel\models.py:340
+#: .\AKModel\models.py:351
 msgid "Properties"
 msgstr "Eigenschaften"
 
-#: .\AKModel\models.py:341
+#: .\AKModel\models.py:352
 msgid "AK requirements fulfilled by the room"
 msgstr "AK-Anforderungen, die dieser Raum erfüllt"
 
-#: .\AKModel\models.py:348 .\AKModel\templates\admin\AKModel\status.html:33
+#: .\AKModel\models.py:359 .\AKModel\templates\admin\AKModel\status.html:33
 msgid "Rooms"
 msgstr "Räume"
 
-#: .\AKModel\models.py:365
+#: .\AKModel\models.py:376
 msgid "AK being mapped"
 msgstr "AK, der zugeordnet wird"
 
-#: .\AKModel\models.py:367
+#: .\AKModel\models.py:378
 msgid "Room the AK will take place in"
 msgstr "Raum in dem der AK stattfindet"
 
-#: .\AKModel\models.py:368
+#: .\AKModel\models.py:379
 msgid "Slot Begin"
 msgstr "Beginn des Slots"
 
-#: .\AKModel\models.py:368
+#: .\AKModel\models.py:379
 msgid "Time and date the slot begins"
 msgstr "Zeit und Datum zu der der AK beginnt"
 
-#: .\AKModel\models.py:370
+#: .\AKModel\models.py:381
 msgid "Duration"
 msgstr "Dauer"
 
-#: .\AKModel\models.py:371
+#: .\AKModel\models.py:382
 msgid "Length in hours"
 msgstr "Länge in Stunden"
 
-#: .\AKModel\models.py:373
+#: .\AKModel\models.py:384
 msgid "Scheduling fixed"
 msgstr "Planung fix"
 
-#: .\AKModel\models.py:374
+#: .\AKModel\models.py:385
 msgid "Length and time of this AK should not be changed"
 msgstr "Dauer und Zeit dieses AKs sollten nicht verändert werden"
 
-#: .\AKModel\models.py:379
+#: .\AKModel\models.py:390
 msgid "Last update"
 msgstr "Letzte Aktualisierung"
 
-#: .\AKModel\models.py:382
+#: .\AKModel\models.py:393
 msgid "AK Slot"
 msgstr "AK-Slot"
 
-#: .\AKModel\models.py:383 .\AKModel\models.py:493
+#: .\AKModel\models.py:394 .\AKModel\models.py:504
 msgid "AK Slots"
 msgstr "AK-Slot"
 
-#: .\AKModel\models.py:405 .\AKModel\models.py:414
+#: .\AKModel\models.py:416 .\AKModel\models.py:425
 msgid "Not scheduled yet"
 msgstr "Noch nicht geplant"
 
-#: .\AKModel\models.py:443
+#: .\AKModel\models.py:454
 msgid "AK this message belongs to"
 msgstr "AK zu dem die Nachricht gehört"
 
-#: .\AKModel\models.py:444
+#: .\AKModel\models.py:455
 msgid "Message text"
 msgstr "Nachrichtentext"
 
-#: .\AKModel\models.py:445
+#: .\AKModel\models.py:456
 msgid "Message to the organizers. This is not publicly visible."
 msgstr ""
 "Nachricht an die Organisator*innen. Diese ist nicht öffentlich sichtbar."
 
-#: .\AKModel\models.py:451
+#: .\AKModel\models.py:462
 msgid "AK Orga Message"
 msgstr "AK-Organachricht"
 
-#: .\AKModel\models.py:452
+#: .\AKModel\models.py:463
 msgid "AK Orga Messages"
 msgstr "AK-Organachrichten"
 
-#: .\AKModel\models.py:461
+#: .\AKModel\models.py:472
 msgid "Constraint Violation"
 msgstr "Constraintverletzung"
 
-#: .\AKModel\models.py:462 .\AKModel\templates\admin\AKModel\status.html:79
+#: .\AKModel\models.py:473 .\AKModel\templates\admin\AKModel\status.html:79
 msgid "Constraint Violations"
 msgstr "Constraintverletzungen"
 
-#: .\AKModel\models.py:466
+#: .\AKModel\models.py:477
 msgid "Owner has two parallel slots"
 msgstr "Leitung hat zwei Slots parallel"
 
-#: .\AKModel\models.py:467
+#: .\AKModel\models.py:478
 msgid "AK Slot was scheduled outside the AK's availabilities"
 msgstr "AK Slot wurde außerhalb der Verfügbarkeit des AKs platziert"
 
-#: .\AKModel\models.py:468
+#: .\AKModel\models.py:479
 msgid "Room has two AK slots scheduled at the same time"
 msgstr "Raum hat zwei AK Slots gleichzeitig"
 
-#: .\AKModel\models.py:469
+#: .\AKModel\models.py:480
 msgid "Room does not satisfy the requirement of the scheduled AK"
 msgstr "Room erfüllt die Anforderungen des platzierten AKs nicht"
 
-#: .\AKModel\models.py:470
+#: .\AKModel\models.py:481
 msgid "AK Slot is scheduled at the same time as an AK listed as a conflict"
 msgstr ""
 "AK Slot wurde wurde zur gleichen Zeit wie ein Konflikt des AKs platziert"
 
-#: .\AKModel\models.py:471
+#: .\AKModel\models.py:482
 msgid "AK Slot is scheduled before an AK listed as a prerequisite"
 msgstr "AK Slot wurde vor einem als Voraussetzung gelisteten AK platziert"
 
-#: .\AKModel\models.py:473
+#: .\AKModel\models.py:484
 msgid ""
 "AK Slot for AK with intention to submit a resolution is scheduled after "
 "resolution deadline"
 msgstr ""
 "AK Slot eines AKs mit Resoabsicht wurde nach der Resodeadline platziert"
 
-#: .\AKModel\models.py:474
+#: .\AKModel\models.py:485
 msgid "AK Slot in a category is outside that categories availabilities"
 msgstr "AK Slot wurde außerhalb der Verfügbarkeiten seiner Kategorie"
 
-#: .\AKModel\models.py:475
+#: .\AKModel\models.py:486
 msgid "Two AK Slots for the same AK scheduled at the same time"
 msgstr "Zwei AK Slots eines AKs wurden zur selben Zeit platziert"
 
-#: .\AKModel\models.py:476
+#: .\AKModel\models.py:487
 msgid "Room does not have enough space for interest in scheduled AK Slot"
 msgstr "Room hat nicht genug Platz für das Interesse am geplanten AK-Slot"
 
-#: .\AKModel\models.py:477
+#: .\AKModel\models.py:488
 msgid "AK Slot is scheduled outside the event's availabilities"
 msgstr "AK Slot wurde außerhalb der Verfügbarkeit des Events platziert"
 
-#: .\AKModel\models.py:480
+#: .\AKModel\models.py:491
 msgid "Warning"
 msgstr "Warnung"
 
-#: .\AKModel\models.py:481
+#: .\AKModel\models.py:492
 msgid "Violation"
 msgstr "Verletzung"
 
-#: .\AKModel\models.py:483
+#: .\AKModel\models.py:494
 msgid "Type"
 msgstr "Art"
 
-#: .\AKModel\models.py:484
+#: .\AKModel\models.py:495
 msgid "Type of violation, i.e. what kind of constraint was violated"
 msgstr "Art der Verletzung, gibt an welche Art Constraint verletzt wurde"
 
-#: .\AKModel\models.py:485
+#: .\AKModel\models.py:496
 msgid "Level"
 msgstr "Level"
 
-#: .\AKModel\models.py:486
+#: .\AKModel\models.py:497
 msgid "Severity level of the violation"
 msgstr "Schweregrad der Verletzung"
 
-#: .\AKModel\models.py:492
+#: .\AKModel\models.py:503
 msgid "AK(s) belonging to this constraint"
 msgstr "AK(s), die zu diesem Constraint gehören"
 
-#: .\AKModel\models.py:494
+#: .\AKModel\models.py:505
 msgid "AK Slot(s) belonging to this constraint"
 msgstr "AK Slot(s), die zu diesem Constraint gehören"
 
-#: .\AKModel\models.py:496
+#: .\AKModel\models.py:507
 msgid "AK Owner belonging to this constraint"
 msgstr "AK Leitung(en), die zu diesem Constraint gehören"
 
-#: .\AKModel\models.py:498
+#: .\AKModel\models.py:509
 msgid "Room belonging to this constraint"
 msgstr "Raum, der zu diesem Constraint gehört"
 
-#: .\AKModel\models.py:501
+#: .\AKModel\models.py:512
 msgid "AK Requirement belonging to this constraint"
 msgstr "AK Anforderung, die zu diesem Constraint gehört"
 
-#: .\AKModel\models.py:503
+#: .\AKModel\models.py:514
 msgid "AK Category belonging to this constraint"
 msgstr "AK Kategorie, di zu diesem Constraint gehört"
 
-#: .\AKModel\models.py:505
+#: .\AKModel\models.py:516
 msgid "Comment"
 msgstr "Kommentar"
 
-#: .\AKModel\models.py:505
+#: .\AKModel\models.py:516
 msgid "Comment or further details for this violation"
 msgstr "Kommentar oder weitere Details zu dieser Vereletzung"
 
-#: .\AKModel\models.py:508
+#: .\AKModel\models.py:519
 msgid "Timestamp"
 msgstr "Timestamp"
 
-#: .\AKModel\models.py:508
+#: .\AKModel\models.py:519
 msgid "Time of creation"
 msgstr "Zeitpunkt der ERstellung"
 
-#: .\AKModel\models.py:509
+#: .\AKModel\models.py:520
 msgid "Manually Resolved"
 msgstr "Manuell behoben"
 
-#: .\AKModel\models.py:510
+#: .\AKModel\models.py:521
 msgid "Mark this violation manually as resolved"
 msgstr "Markiere diese Verletzung manuell als behoben"
 
-#: .\AKModel\models.py:537
+#: .\AKModel\models.py:548
 #: .\AKModel\templates\admin\AKModel\requirements_overview.html:27
 msgid "Details"
 msgstr "Details"
@@ -779,6 +779,17 @@ msgstr ""
 msgid "Logout"
 msgstr "Ausloggen"
 
+#: .\AKModel\templates\admin\AKModel\action_intermediate.html:23
+msgid "Confirm"
+msgstr "Bestätigen"
+
+#: .\AKModel\templates\admin\AKModel\action_intermediate.html:27
+#: .\AKModel\templates\admin\AKModel\event_wizard\import.html:24
+#: .\AKModel\templates\admin\AKModel\event_wizard\settings.html:29
+#: .\AKModel\templates\admin\AKModel\event_wizard\start.html:23
+msgid "Cancel"
+msgstr "Abbrechen"
+
 #: .\AKModel\templates\admin\AKModel\event_wizard\activate.html:9
 #: .\AKModel\templates\admin\AKModel\event_wizard\created_prepare_import.html:9
 #: .\AKModel\templates\admin\AKModel\event_wizard\finish.html:9
@@ -794,7 +805,7 @@ msgid "Successfully imported.<br><br>Do you want to activate your event now?"
 msgstr "Erfolgreich importiert.<br><br>Soll das Event jetzt aktiviert werden?"
 
 #: .\AKModel\templates\admin\AKModel\event_wizard\activate.html:27
-#: .\AKModel\views.py:225
+#: .\AKModel\views.py:247
 msgid "Finish"
 msgstr "Abschluss"
 
@@ -821,13 +832,6 @@ msgstr "Fortfahren"
 msgid "Congratulations. Everything is set up!"
 msgstr "Herzlichen Glückwunsch. Alles ist eingerichtet!"
 
-#: .\AKModel\templates\admin\AKModel\event_wizard\import.html:24
-#: .\AKModel\templates\admin\AKModel\event_wizard\settings.html:29
-#: .\AKModel\templates\admin\AKModel\event_wizard\start.html:23
-#: .\AKModel\templates\admin\AKModel\message_delete.html:21
-msgid "Cancel"
-msgstr "Abbrechen"
-
 #: .\AKModel\templates\admin\AKModel\event_wizard\settings.html:26
 msgid "Back"
 msgstr "Zurück"
@@ -844,15 +848,7 @@ msgstr ""
 msgid "Step"
 msgstr "Schritt"
 
-#: .\AKModel\templates\admin\AKModel\message_delete.html:7
-msgid "Delete Orga-Messages"
-msgstr "Organachrichten löschen"
-
-#: .\AKModel\templates\admin\AKModel\message_delete.html:10
-msgid "Delete AK Orga Messages"
-msgstr "AK-Organachrichten löschen"
-
-#: .\AKModel\templates\admin\AKModel\message_delete.html:11
+#: .\AKModel\templates\admin\AKModel\message_delete.html:8
 #, python-format
 msgid ""
 "Are you sure you want to delete all orga messages for %(event)s? This will "
@@ -861,10 +857,6 @@ msgstr ""
 "Sollen wirklich alle Organachrichten für %(event)s gelöscht werden? Dadurch "
 "werden %(message_count)s Nachricht(en) dauerhaft gelöscht:"
 
-#: .\AKModel\templates\admin\AKModel\message_delete.html:17
-msgid "Delete"
-msgstr "Löschen"
-
 #: .\AKModel\templates\admin\AKModel\requirements_overview.html:12
 msgid "Requirements Overview"
 msgstr "Ãœbersicht Anforderungen"
@@ -996,86 +988,93 @@ msgstr "Login"
 msgid "Register"
 msgstr "Registrieren"
 
-#: .\AKModel\views.py:143
+#: .\AKModel\views.py:144
 msgid "Event Status"
 msgstr "Eventstatus"
 
-#: .\AKModel\views.py:156
+#: .\AKModel\views.py:157
 msgid "Requirements for Event"
 msgstr "Anforderungen für das Event"
 
-#: .\AKModel\views.py:170
+#: .\AKModel\views.py:171
 msgid "AK CSV Export"
 msgstr "AK-CSV-Export"
 
-#: .\AKModel\views.py:184
+#: .\AKModel\views.py:185
 msgid "AK Wiki Export"
 msgstr "AK-Wiki-Export"
 
-#: .\AKModel\views.py:192 .\AKModel\views.py:323
+#: .\AKModel\views.py:193 .\AKModel\views.py:345
 msgid "Wishes"
 msgstr "Wünsche"
 
-#: .\AKModel\views.py:211
+#: .\AKModel\views.py:215
+msgid "Delete AK Orga Messages"
+msgstr "AK-Organachrichten löschen"
+
+#: .\AKModel\views.py:233
 msgid "AK Orga Messages successfully deleted"
 msgstr "AK-Organachrichten erfolgreich gelöscht"
 
-#: .\AKModel\views.py:221
+#: .\AKModel\views.py:243
 msgid "Settings"
 msgstr "Einstellungen"
 
-#: .\AKModel\views.py:222
+#: .\AKModel\views.py:244
 msgid "Event created, Prepare Import"
 msgstr "Event angelegt, Import vorbereiten"
 
-#: .\AKModel\views.py:223
+#: .\AKModel\views.py:245
 msgid "Import categories & requirements"
 msgstr "Kategorien & Anforderungen kopieren"
 
-#: .\AKModel\views.py:224
+#: .\AKModel\views.py:246
 #, fuzzy
 #| msgid "Active State"
 msgid "Activate?"
 msgstr "Aktivieren?"
 
-#: .\AKModel\views.py:283
+#: .\AKModel\views.py:305
 #, python-format
 msgid "Copied '%(obj)s'"
 msgstr "'%(obj)s' kopiert"
 
-#: .\AKModel\views.py:286
+#: .\AKModel\views.py:308
 #, python-format
 msgid "Could not copy '%(obj)s' (%(error)s)"
 msgstr "'%(obj)s' konnte nicht kopiert werden (%(error)s)"
 
-#: .\AKModel\views.py:318
+#: .\AKModel\views.py:340
 msgid "Symbols"
 msgstr "Symbole"
 
-#: .\AKModel\views.py:319
+#: .\AKModel\views.py:341
 msgid "Who?"
 msgstr "Wer?"
 
-#: .\AKModel\views.py:320
+#: .\AKModel\views.py:342
 msgid "Duration(s)"
 msgstr "Dauer(n)"
 
-#: .\AKModel\views.py:321
+#: .\AKModel\views.py:343
 msgid "Reso intention?"
 msgstr "Resolutionsabsicht?"
 
-#: .\AKModel\views.py:322
+#: .\AKModel\views.py:344
 msgid "Category (for Wishes)"
 msgstr "Kategorie (für Wünsche)"
 
+#~ msgid "Delete Orga-Messages"
+#~ msgstr "Organachrichten löschen"
+
+#~ msgid "Delete"
+#~ msgstr "Löschen"
+
 #~ msgid "AK Slot is scheduled in a room with less space than interest"
 #~ msgstr ""
 #~ "AK Slot wurde in einem Raum mit weniger Plätzen als am AK Interessierten "
 #~ "platziert"
 
-#~ msgid "Confirm"
-#~ msgstr "Bestätigen"
-
 #~ msgid "messages will be permanently deleted:"
 #~ msgstr "Nachrichten werden dauerhaft gelöscht:"
 
diff --git a/AKModel/models.py b/AKModel/models.py
index 7f82cbd6..e8fcf8aa 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -2,6 +2,7 @@ import itertools
 from datetime import timedelta
 
 from django.db import models
+from django.db.models import Count
 from django.urls import reverse_lazy
 from django.utils import timezone
 from django.utils.datetime_safe import datetime
@@ -105,6 +106,8 @@ class Event(models.Model):
                 categories_with_aks.append((category, ak_list))
             return categories_with_aks
 
+    def get_unscheduled_wish_slots(self):
+        return self.akslot_set.filter(start__isnull=True).annotate(Count('ak__owners')).filter(ak__owners__count=0)
 
 class AKOwner(models.Model):
     """ An AKOwner describes the person organizing/holding an AK.
@@ -328,6 +331,14 @@ class AK(models.Model):
     def availabilities(self):
         return "Availability".objects.filter(ak=self)
 
+    @property
+    def availabilities_total_duration(self):
+        from AKModel.availability.models import Availability
+        for a in Availability.objects.filter(ak=self):
+            print(a)
+            print(a.end)
+            #        [a.end - a.start for a in ]
+        return 0
 
 class Room(models.Model):
     """ A room describes where an AK can be held.
diff --git a/AKScheduling/locale/de_DE/LC_MESSAGES/django.po b/AKScheduling/locale/de_DE/LC_MESSAGES/django.po
index 5729c218..3bf6a9e5 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: 2022-08-17 22:41+0200\n"
+"POT-Creation-Date: 2022-09-27 14:14+0200\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"
@@ -79,7 +79,7 @@ msgstr "Seit"
 #: .\AKScheduling\templates\admin\AKScheduling\constraint_violations.html:139
 #: .\AKScheduling\templates\admin\AKScheduling\manage_tracks.html:243
 #: .\AKScheduling\templates\admin\AKScheduling\scheduling.html:208
-#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:43
+#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:44
 #: .\AKScheduling\templates\admin\AKScheduling\unscheduled.html:34
 msgid "Event Status"
 msgstr "Event-Status"
@@ -161,7 +161,11 @@ msgstr "AKs ohne Verfügbarkeiten"
 msgid "AK wishes with slots"
 msgstr "AK-Wünsche mit Slots"
 
-#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:35
+#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:34
+msgid "Delete slots for wishes"
+msgstr ""
+
+#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:36
 msgid "AKs without slots"
 msgstr "AKs ohne Slots"
 
@@ -173,10 +177,29 @@ msgstr "Noch nicht geschedulte AK-Slots"
 msgid "Count"
 msgstr "Anzahl"
 
-#: .\AKScheduling\views.py:103
+#: .\AKScheduling\views.py:108
 msgid "Interest updated"
 msgstr "Interesse aktualisiert"
 
-#: .\AKScheduling\views.py:141
+#: .\AKScheduling\views.py:146
 msgid "Wishes"
 msgstr "Wünsche"
+
+#: .\AKScheduling\views.py:154
+msgid "Cleanup: Delete unscheduled slots for wishes"
+msgstr "Aufräumen: Noch nicht geplante Slots für Wünsche löschen"
+
+#: .\AKScheduling\views.py:160
+#, python-brace-format
+msgid ""
+"The following {count} unscheduled slots of wishes will be deleted:\n"
+"\n"
+" {slots}"
+msgstr ""
+"Die folgenden {count} noch nicht geplanten Slots von Wünschen werden gelöscht:\n"
+"\n"
+" {slots}"
+
+#: .\AKScheduling\views.py:167
+msgid "Unscheduled slots for wishes successfully deleted"
+msgstr "Noch nicht geplante Slots für Wünsche erfolgreich gelöscht"
diff --git a/AKScheduling/templates/admin/AKScheduling/special_attention.html b/AKScheduling/templates/admin/AKScheduling/special_attention.html
index 8977c199..a3c48d19 100644
--- a/AKScheduling/templates/admin/AKScheduling/special_attention.html
+++ b/AKScheduling/templates/admin/AKScheduling/special_attention.html
@@ -31,6 +31,7 @@
     {% empty %}
         -
     {% endfor %}
+    <a class="btn btn-warning" href="{% url "admin:cleanup-wish-slots" event_slug=event.slug %}">{% trans "Delete slots for wishes" %}</a>
 
     <h4 class="mt-4 mb-4">{% trans "AKs without slots" %}</h4>
     {% for ak in aks_without_slots %}
diff --git a/AKScheduling/urls.py b/AKScheduling/urls.py
index e0fc27f5..40f293a1 100644
--- a/AKScheduling/urls.py
+++ b/AKScheduling/urls.py
@@ -1,7 +1,7 @@
 from django.urls import path
 
 from AKScheduling.views import SchedulingAdminView, UnscheduledSlotsAdminView, TrackAdminView, \
-    ConstraintViolationsAdminView, SpecialAttentionAKsAdminView, InterestEnteringAdminView
+    ConstraintViolationsAdminView, SpecialAttentionAKsAdminView, InterestEnteringAdminView, WishSlotCleanupView
 
 
 def get_admin_urls_scheduling(admin_site):
@@ -14,6 +14,8 @@ def get_admin_urls_scheduling(admin_site):
              name="constraint-violations"),
         path('<slug:slug>/special-attention/', admin_site.admin_view(SpecialAttentionAKsAdminView.as_view()),
              name="special-attention"),
+        path('<slug:event_slug>/cleanup-wish-slots/', admin_site.admin_view(WishSlotCleanupView.as_view()),
+             name="cleanup-wish-slots"),
         path('<slug:event_slug>/tracks/', admin_site.admin_view(TrackAdminView.as_view()),
              name="tracks_manage"),
         path('<slug:event_slug>/enter-interest/<int:pk>', admin_site.admin_view(InterestEnteringAdminView.as_view()),
diff --git a/AKScheduling/views.py b/AKScheduling/views.py
index 47781cf0..1332060a 100644
--- a/AKScheduling/views.py
+++ b/AKScheduling/views.py
@@ -1,9 +1,11 @@
+from django.contrib import messages
 from django.contrib.messages.views import SuccessMessageMixin
+from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import ListView, DetailView, UpdateView
 
 from AKModel.models import AKSlot, AKTrack, Event, AK, AKCategory
-from AKModel.views import AdminViewMixin, FilterByEventSlugMixin, EventSlugMixin
+from AKModel.views import AdminViewMixin, FilterByEventSlugMixin, EventSlugMixin, IntermediateAdminView
 from AKScheduling.forms import AKInterestForm
 
 
@@ -153,3 +155,21 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
         context["categories_with_aks"] = categories_with_aks
 
         return context
+
+
+class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView):
+    title = _('Cleanup: Delete unscheduled slots for wishes')
+    def get_success_url(self):
+        return reverse_lazy('admin:special-attention', kwargs={'slug': self.event.slug})
+
+    def get_preview(self):
+        slots = self.event.get_unscheduled_wish_slots()
+        return _("The following {count} unscheduled slots of wishes will be deleted:\n\n {slots}").format(
+            count=len(slots),
+            slots=", ".join(str(s.ak) for s in slots)
+        )
+
+    def form_valid(self, form):
+        self.event.get_unscheduled_wish_slots().delete()
+        messages.add_message(self.request, messages.SUCCESS, _("Unscheduled slots for wishes successfully deleted"))
+        return super().form_valid(form)
-- 
GitLab


From 8abe5f1f211793713af92178f0347232d81765af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Tue, 27 Sep 2022 14:37:52 +0200
Subject: [PATCH 4/7] Speedup special attention ak admin view

Use annotations to query counts of owners, slots and availabilities directly in main query
---
 AKScheduling/views.py | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/AKScheduling/views.py b/AKScheduling/views.py
index 1332060a..381c0a1e 100644
--- a/AKScheduling/views.py
+++ b/AKScheduling/views.py
@@ -1,5 +1,6 @@
 from django.contrib import messages
 from django.contrib.messages.views import SuccessMessageMixin
+from django.db.models import Count
 from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import ListView, DetailView, UpdateView
@@ -73,7 +74,7 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
         context = super().get_context_data(**kwargs)
         context["title"] = f"{_('AKs requiring special attention for')} {context['event']}"
 
-        aks = AK.objects.filter(event=context["event"])
+        aks = AK.objects.filter(event=context["event"]).annotate(Count('owners', distinct=True)).annotate(Count('akslot', distinct=True)).annotate(Count('availabilities', distinct=True))
         aks_with_comment = []
         ak_wishes_with_slots = []
         aks_without_availabilities = []
@@ -83,13 +84,13 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
             if ak.notes != "":
                 aks_with_comment.append(ak)
 
-            if ak.wish:
-                if ak.akslot_set.count() > 0:
+            if ak.owners__count == 0:
+                if ak.akslot__count > 0:
                     ak_wishes_with_slots.append(ak)
             else:
-                if ak.akslot_set.count() == 0:
+                if ak.akslot__count == 0:
                     aks_without_slots.append(ak)
-                if ak.availabilities.count() == 0:
+                if ak.availabilities__count == 0:
                     aks_without_availabilities.append(ak)
 
         context["aks_with_comment"] = aks_with_comment
-- 
GitLab


From ea1401beaafbcaa507bdf1f28d701fec4de80582 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Tue, 27 Sep 2022 17:26:34 +0200
Subject: [PATCH 5/7] Improve page for special attention requiring AKs

Show count of slots for wishes and link directly to a filtered list
---
 .../templates/admin/AKScheduling/special_attention.html      | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/AKScheduling/templates/admin/AKScheduling/special_attention.html b/AKScheduling/templates/admin/AKScheduling/special_attention.html
index a3c48d19..68cbb11e 100644
--- a/AKScheduling/templates/admin/AKScheduling/special_attention.html
+++ b/AKScheduling/templates/admin/AKScheduling/special_attention.html
@@ -27,11 +27,12 @@
 
     <h4 class="mt-4 mb-4">{% trans "AK wishes with slots" %}</h4>
     {% for ak in ak_wishes_with_slots %}
-        <a href="{% url "submit:ak_detail" event_slug=event.slug pk=ak.pk %}">{{ ak }}</a><br>
+        <a href="{% url "submit:ak_detail" event_slug=event.slug pk=ak.pk %}">{{ ak }}</a> <a href="{% url "admin:AKModel_akslot_changelist" %}?ak={{ ak.pk }}">({{ ak.akslot__count }})</a><br>
     {% empty %}
         -
     {% endfor %}
-    <a class="btn btn-warning" href="{% url "admin:cleanup-wish-slots" event_slug=event.slug %}">{% trans "Delete slots for wishes" %}</a>
+
+    <a class="btn btn-warning mt-2" href="{% url "admin:cleanup-wish-slots" event_slug=event.slug %}">{% trans "Delete slots for wishes" %}</a>
 
     <h4 class="mt-4 mb-4">{% trans "AKs without slots" %}</h4>
     {% for ak in aks_without_slots %}
-- 
GitLab


From e340dfe1a63651e37a16eff52dfd43a42a7b686e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Tue, 27 Sep 2022 17:55:56 +0200
Subject: [PATCH 6/7] Enhance robustness by deactivating print statements in cv
 signal receivers

---
 AKScheduling/models.py | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/AKScheduling/models.py b/AKScheduling/models.py
index cabf78e1..6664a8ae 100644
--- a/AKScheduling/models.py
+++ b/AKScheduling/models.py
@@ -157,7 +157,7 @@ def ak_owners_changed_handler(sender, instance: AK, action: str, **kwargs):
                             c.ak_slots_tmp.add(other_slot)
                             new_violations.append(c)
 
-        print(f"{owner} has the following conflicts: {new_violations}")
+        #print(f"{owner} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -203,7 +203,7 @@ def ak_conflicts_changed_handler(sender, instance: AK, action: str, **kwargs):
                         c.ak_slots_tmp.add(other_slot)
                         new_violations.append(c)
 
-        print(f"{instance} has the following conflicts: {new_violations}")
+        # print(f"{instance} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -249,7 +249,7 @@ def ak_prerequisites_changed_handler(sender, instance: AK, action: str, **kwargs
                         c.ak_slots_tmp.add(other_slot)
                         new_violations.append(c)
 
-        print(f"{instance} has the following conflicts: {new_violations}")
+        # print(f"{instance} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -298,7 +298,7 @@ def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs)
                 c.ak_slots_tmp.add(slot)
                 new_violations.append(c)
 
-    print(f"{instance} has the following conflicts: {new_violations}")
+    # print(f"{instance} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -310,7 +310,7 @@ def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs)
 @receiver(post_save, sender=AKSlot)
 def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
     # Changes might affect: Duplicate parallel, Two in room, Resodeadline
-    print(f"{sender} changed")
+    # print(f"{sender} changed")
     event = instance.event
 
     # == Check for two parallel slots by one of the owners ==
@@ -341,7 +341,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
                             c.ak_slots_tmp.add(other_slot)
                             new_violations.append(c)
 
-            print(f"{owner} has the following conflicts: {new_violations}")
+            # print(f"{owner} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -373,7 +373,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
                     c.ak_slots_tmp.add(other_slot)
                     new_violations.append(c)
 
-        print(f"Multiple slots in room {instance.room}: {new_violations}")
+        # print(f"Multiple slots in room {instance.room}: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the slot that was recently changed (important!)
@@ -437,7 +437,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
             c.ak_slots_tmp.add(instance)
             new_violations.append(c)
 
-    print(f"{instance.ak} has the following slots outside availabilities: {new_violations}")
+    # print(f"{instance.ak} has the following slots outside availabilities: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -470,7 +470,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
                 c.ak_slots_tmp.add(instance)
                 new_violations.append(c)
 
-    print(f"{instance} has the following conflicts: {new_violations}")
+    # print(f"{instance} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -502,7 +502,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
                         c.ak_slots_tmp.add(other_slot)
                         new_violations.append(c)
 
-            print(f"{instance} has the following conflicts: {new_violations}")
+            # print(f"{instance} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -534,7 +534,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
                         c.ak_slots_tmp.add(other_slot)
                         new_violations.append(c)
 
-            print(f"{instance} has the following conflicts: {new_violations}")
+            # print(f"{instance} has the following conflicts: {new_violations}")
 
     # ... and compare to/update list of existing violations of this type
     # belonging to the AK that was recently changed (important!)
@@ -556,7 +556,7 @@ def akslot_deleted_handler(sender, instance: AKSlot, **kwargs):
     # Manually clean up or remove constraint violations that belong to this slot since there is no cascade deletion
     # for many2many relationships. Explicitly listening for AK deletion signals is not necessary since they will
     # transitively trigger this signal and we always set both AK and AKSlot references in a constraint violation
-    print(f"{instance} deleted")
+    # print(f"{instance} deleted")
 
     for cv in instance.constraintviolation_set.all():
         # Make sure not delete CVs that e.g., show three parallel slots in a single room
@@ -599,7 +599,7 @@ def room_requirements_changed_handler(sender, instance: Room, action: str, **kwa
 @receiver(post_save, sender=Availability)
 def availability_changed_handler(sender, instance: Availability, **kwargs):
     # Changes might affect: category availability, AK availability, Room availability
-    print(f"{instance} changed")
+    # print(f"{instance} changed")
 
     event = instance.event
 
@@ -627,7 +627,7 @@ def availability_changed_handler(sender, instance: Availability, **kwargs):
                 c.ak_slots_tmp.add(slot)
                 new_violations.append(c)
 
-        print(f"{instance.ak} has the following slots outside availabilities: {new_violations}")
+        # print(f"{instance.ak} has the following slots outside availabilities: {new_violations}")
 
         # ... and compare to/update list of existing violations of this type
         # belonging to the AK that was recently changed (important!)
-- 
GitLab


From 5fd2fc96090c8aad8a02cc4548259c2c5882c724 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Tue, 27 Sep 2022 18:10:03 +0200
Subject: [PATCH 7/7] Create admin action to create default availabilities

Introduce admin action
Add function to find AKs without availabilities to event model
Link new action on view of AKs requiring special attention
Minor design improvements
This implements the final part of #145
---
 AKModel/models.py                             | 12 ++---
 .../locale/de_DE/LC_MESSAGES/django.po        | 52 +++++++++++++++----
 .../admin/AKScheduling/special_attention.html |  9 ++--
 AKScheduling/urls.py                          |  5 +-
 AKScheduling/views.py                         | 37 +++++++++++++
 5 files changed, 93 insertions(+), 22 deletions(-)

diff --git a/AKModel/models.py b/AKModel/models.py
index e8fcf8aa..ef86329e 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -109,6 +109,10 @@ class Event(models.Model):
     def get_unscheduled_wish_slots(self):
         return self.akslot_set.filter(start__isnull=True).annotate(Count('ak__owners')).filter(ak__owners__count=0)
 
+    def get_aks_without_availabilities(self):
+        return self.ak_set.annotate(Count('availabilities', distinct=True)).annotate(Count('owners', distinct=True)).filter(availabilities__count=0, owners__count__gt=0)
+
+
 class AKOwner(models.Model):
     """ An AKOwner describes the person organizing/holding an AK.
     """
@@ -331,14 +335,6 @@ class AK(models.Model):
     def availabilities(self):
         return "Availability".objects.filter(ak=self)
 
-    @property
-    def availabilities_total_duration(self):
-        from AKModel.availability.models import Availability
-        for a in Availability.objects.filter(ak=self):
-            print(a)
-            print(a.end)
-            #        [a.end - a.start for a in ]
-        return 0
 
 class Room(models.Model):
     """ A room describes where an AK can be held.
diff --git a/AKScheduling/locale/de_DE/LC_MESSAGES/django.po b/AKScheduling/locale/de_DE/LC_MESSAGES/django.po
index 3bf6a9e5..ceb03d7f 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: 2022-09-27 14:14+0200\n"
+"POT-Creation-Date: 2022-09-27 17:59+0200\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"
@@ -79,7 +79,7 @@ msgstr "Seit"
 #: .\AKScheduling\templates\admin\AKScheduling\constraint_violations.html:139
 #: .\AKScheduling\templates\admin\AKScheduling\manage_tracks.html:243
 #: .\AKScheduling\templates\admin\AKScheduling\scheduling.html:208
-#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:44
+#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:48
 #: .\AKScheduling\templates\admin\AKScheduling\unscheduled.html:34
 msgid "Event Status"
 msgstr "Event-Status"
@@ -158,14 +158,18 @@ msgid "AKs without availabilities"
 msgstr "AKs ohne Verfügbarkeiten"
 
 #: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:28
+msgid "Create default availabilities"
+msgstr "Standardverfügbarkeiten anlegen"
+
+#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:31
 msgid "AK wishes with slots"
 msgstr "AK-Wünsche mit Slots"
 
-#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:34
+#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:38
 msgid "Delete slots for wishes"
 msgstr ""
 
-#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:36
+#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:40
 msgid "AKs without slots"
 msgstr "AKs ohne Slots"
 
@@ -177,29 +181,57 @@ msgstr "Noch nicht geschedulte AK-Slots"
 msgid "Count"
 msgstr "Anzahl"
 
-#: .\AKScheduling\views.py:108
+#: .\AKScheduling\views.py:109
 msgid "Interest updated"
 msgstr "Interesse aktualisiert"
 
-#: .\AKScheduling\views.py:146
+#: .\AKScheduling\views.py:147
 msgid "Wishes"
 msgstr "Wünsche"
 
-#: .\AKScheduling\views.py:154
+#: .\AKScheduling\views.py:155
 msgid "Cleanup: Delete unscheduled slots for wishes"
 msgstr "Aufräumen: Noch nicht geplante Slots für Wünsche löschen"
 
-#: .\AKScheduling\views.py:160
+#: .\AKScheduling\views.py:162
 #, python-brace-format
 msgid ""
 "The following {count} unscheduled slots of wishes will be deleted:\n"
 "\n"
 " {slots}"
 msgstr ""
-"Die folgenden {count} noch nicht geplanten Slots von Wünschen werden gelöscht:\n"
+"Die folgenden {count} noch nicht geplanten Slots von Wünschen werden "
+"gelöscht:\n"
 "\n"
 " {slots}"
 
-#: .\AKScheduling\views.py:167
+#: .\AKScheduling\views.py:169
 msgid "Unscheduled slots for wishes successfully deleted"
 msgstr "Noch nicht geplante Slots für Wünsche erfolgreich gelöscht"
+
+#: .\AKScheduling\views.py:174
+msgid "Create default availabilities for AKs"
+msgstr "Standardverfügbarkeiten für AKs anlegen"
+
+#: .\AKScheduling\views.py:181
+#, python-brace-format
+msgid ""
+"The following {count} AKs don't have any availability information. Create "
+"default availability for them:\n"
+"\n"
+" {aks}"
+msgstr ""
+"Die folgenden {count} AKs haben keine Verfügbarkeitsinformationen. "
+"Standardverfügbarkeiten für sie anlegen:\n"
+"\n"
+" {aks}"
+
+#: .\AKScheduling\views.py:199
+#, python-brace-format
+msgid "Could not create default availabilities for AK: {ak}"
+msgstr "Konnte keine Verfügbarkeit anlegen für AK: {ak}"
+
+#: .\AKScheduling\views.py:204
+#, python-brace-format
+msgid "Created default availabilities for {count} AKs"
+msgstr "Standardverfügbarkeiten für {count} AKs angelegt"
diff --git a/AKScheduling/templates/admin/AKScheduling/special_attention.html b/AKScheduling/templates/admin/AKScheduling/special_attention.html
index 68cbb11e..44d2ced1 100644
--- a/AKScheduling/templates/admin/AKScheduling/special_attention.html
+++ b/AKScheduling/templates/admin/AKScheduling/special_attention.html
@@ -22,14 +22,17 @@
     {% for ak in aks_without_availabilities %}
         <a href="{% url "submit:ak_edit" event_slug=event.slug pk=ak.pk %}">{{ ak }}</a><br>
     {% empty %}
-        -
+        -<br>
     {% endfor %}
 
+    <a class="btn btn-warning mt-2" href="{% url "admin:autocreate-availabilities" event_slug=event.slug %}">{% trans "Create default availabilities" %}</a>
+
+
     <h4 class="mt-4 mb-4">{% trans "AK wishes with slots" %}</h4>
     {% for ak in ak_wishes_with_slots %}
         <a href="{% url "submit:ak_detail" event_slug=event.slug pk=ak.pk %}">{{ ak }}</a> <a href="{% url "admin:AKModel_akslot_changelist" %}?ak={{ ak.pk }}">({{ ak.akslot__count }})</a><br>
     {% empty %}
-        -
+        -<br>
     {% endfor %}
 
     <a class="btn btn-warning mt-2" href="{% url "admin:cleanup-wish-slots" event_slug=event.slug %}">{% trans "Delete slots for wishes" %}</a>
@@ -38,7 +41,7 @@
     {% for ak in aks_without_slots %}
         <a href="{% url "submit:ak_detail" event_slug=event.slug pk=ak.pk %}">{{ ak }}</a><br>
     {% empty %}
-        -
+        -<br>
     {% endfor %}
 
     <div class="mt-5">
diff --git a/AKScheduling/urls.py b/AKScheduling/urls.py
index 40f293a1..1db4c182 100644
--- a/AKScheduling/urls.py
+++ b/AKScheduling/urls.py
@@ -1,7 +1,8 @@
 from django.urls import path
 
 from AKScheduling.views import SchedulingAdminView, UnscheduledSlotsAdminView, TrackAdminView, \
-    ConstraintViolationsAdminView, SpecialAttentionAKsAdminView, InterestEnteringAdminView, WishSlotCleanupView
+    ConstraintViolationsAdminView, SpecialAttentionAKsAdminView, InterestEnteringAdminView, WishSlotCleanupView, \
+    AvailabilityAutocreateView
 
 
 def get_admin_urls_scheduling(admin_site):
@@ -16,6 +17,8 @@ def get_admin_urls_scheduling(admin_site):
              name="special-attention"),
         path('<slug:event_slug>/cleanup-wish-slots/', admin_site.admin_view(WishSlotCleanupView.as_view()),
              name="cleanup-wish-slots"),
+        path('<slug:event_slug>/autocreate-availabilities/', admin_site.admin_view(AvailabilityAutocreateView.as_view()),
+             name="autocreate-availabilities"),
         path('<slug:event_slug>/tracks/', admin_site.admin_view(TrackAdminView.as_view()),
              name="tracks_manage"),
         path('<slug:event_slug>/enter-interest/<int:pk>', admin_site.admin_view(InterestEnteringAdminView.as_view()),
diff --git a/AKScheduling/views.py b/AKScheduling/views.py
index 381c0a1e..3b40d07c 100644
--- a/AKScheduling/views.py
+++ b/AKScheduling/views.py
@@ -160,6 +160,7 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
 
 class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView):
     title = _('Cleanup: Delete unscheduled slots for wishes')
+
     def get_success_url(self):
         return reverse_lazy('admin:special-attention', kwargs={'slug': self.event.slug})
 
@@ -174,3 +175,39 @@ class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView):
         self.event.get_unscheduled_wish_slots().delete()
         messages.add_message(self.request, messages.SUCCESS, _("Unscheduled slots for wishes successfully deleted"))
         return super().form_valid(form)
+
+
+class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView):
+    title = _('Create default availabilities for AKs')
+
+    def get_success_url(self):
+        return reverse_lazy('admin:special-attention', kwargs={'slug': self.event.slug})
+
+    def get_preview(self):
+        aks = self.event.get_aks_without_availabilities()
+        return _("The following {count} AKs don't have any availability information. "
+                 "Create default availability for them:\n\n {aks}").format(
+            count=len(aks),
+            aks=", ".join(str(ak) for ak in aks)
+        )
+
+    def form_valid(self, form):
+        from AKModel.availability.models import Availability
+
+        success_count = 0
+        for ak in self.event.get_aks_without_availabilities():
+            try:
+                availability = Availability.with_event_length(event=self.event, ak=ak)
+                availability.save()
+                success_count += 1
+            except:
+                messages.add_message(
+                    self.request, messages.WARNING,
+                    _("Could not create default availabilities for AK: {ak}").format(ak=ak)
+                )
+
+        messages.add_message(
+            self.request, messages.SUCCESS,
+            _("Created default availabilities for {count} AKs").format(count=success_count)
+        )
+        return super().form_valid(form)
-- 
GitLab