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] 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