Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • konstantin/akplanning
  • matedealer/akplanning
  • kif/akplanning
  • mirco/akplanning
  • lordofthevoid/akplanning
  • voidptr/akplanning
  • xayomer/akplanning-fork
  • mollux/akplanning
  • neumantm/akplanning
  • mmarx/akplanning
  • nerf/akplanning
  • felix_bonn/akplanning
  • sebastian.uschmann/akplanning
13 results
Select Git revision
  • komasolver
  • main
  • renovate/django-5.x
  • renovate/django-debug-toolbar-5.x
  • renovate/django_csp-4.x
  • renovate/djangorestframework-3.x
  • renovate/sphinxcontrib-apidoc-0.x
  • renovate/tzdata-2025.x
  • renovate/uwsgi-2.x
9 results
Show changes
Commits on Source (7)
Showing
with 535 additions and 340 deletions
...@@ -68,3 +68,7 @@ class NewEventWizardActivateForm(forms.ModelForm): ...@@ -68,3 +68,7 @@ class NewEventWizardActivateForm(forms.ModelForm):
class Meta: class Meta:
fields = ["active"] fields = ["active"]
model = Event model = Event
class AdminIntermediateForm(forms.Form):
pass
...@@ -2,6 +2,7 @@ import itertools ...@@ -2,6 +2,7 @@ import itertools
from datetime import timedelta from datetime import timedelta
from django.db import models from django.db import models
from django.db.models import Count
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.datetime_safe import datetime from django.utils.datetime_safe import datetime
...@@ -105,6 +106,12 @@ class Event(models.Model): ...@@ -105,6 +106,12 @@ class Event(models.Model):
categories_with_aks.append((category, ak_list)) categories_with_aks.append((category, ak_list))
return categories_with_aks 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)
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): class AKOwner(models.Model):
""" An AKOwner describes the person organizing/holding an AK. """ An AKOwner describes the person organizing/holding an AK.
...@@ -328,6 +335,14 @@ class AK(models.Model): ...@@ -328,6 +335,14 @@ class AK(models.Model):
def availabilities(self): def availabilities(self):
return "Availability".objects.filter(ak=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): class Room(models.Model):
""" A room describes where an AK can be held. """ A room describes where an AK can be held.
......
{% 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 %}
{% extends "admin/base_site.html" %} {% extends "admin/AKModel/action_intermediate.html" %}
{% load tags_AKModel %} {% load tags_AKModel %}
{% load i18n %} {% load i18n %}
{% load fontawesome_5 %} {% load fontawesome_5 %}
{% block title %}{{event}}: {% trans "Delete Orga-Messages" %}{% endblock %} {% block action_preview %}
{% block content %}
<h2>{% trans "Delete AK Orga Messages" %}</h2>
<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> <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" %} {% 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 %} {% endblock %}
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
<div class="col-md-4"> <div class="col-md-4">
<h3 class="block-header">{% trans "Messages" %}</h3> <h3 class="block-header">{% trans "Messages" %}</h3>
{% include "admin/AKModel/render_ak_messages.html" %} {% 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>
</div> </div>
{% endtimezone %} {% endtimezone %}
......
...@@ -79,7 +79,7 @@ def get_admin_urls_event(admin_site): ...@@ -79,7 +79,7 @@ def get_admin_urls_event(admin_site):
name="ak_csv_export"), name="ak_csv_export"),
path('<slug:slug>/ak-wiki-export/', admin_site.admin_view(AKWikiExportView.as_view()), path('<slug:slug>/ak-wiki-export/', admin_site.admin_view(AKWikiExportView.as_view()),
name="ak_wiki_export"), 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"), name="ak_delete_orga_messages"),
path('<slug:event_slug>/ak-slide-export/', export_slides, name="ak_slide_export"), path('<slug:event_slug>/ak-slide-export/', export_slides, name="ak_slide_export"),
......
from abc import ABC, abstractmethod
from itertools import zip_longest from itertools import zip_longest
from django.contrib import admin, messages from django.contrib import admin, messages
...@@ -11,7 +12,7 @@ from django_tex.shortcuts import render_to_pdf ...@@ -11,7 +12,7 @@ from django_tex.shortcuts import render_to_pdf
from rest_framework import viewsets, permissions, mixins from rest_framework import viewsets, permissions, mixins
from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \ 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.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement
from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \ from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \
AKOwnerSerializer AKOwnerSerializer
...@@ -194,22 +195,43 @@ class AKWikiExportView(AdminViewMixin, DetailView): ...@@ -194,22 +195,43 @@ class AKWikiExportView(AdminViewMixin, DetailView):
return context return context
class AKMessageDeleteView(AdminViewMixin, DeleteView): class IntermediateAdminView(AdminViewMixin, FormView, ABC):
model = Event 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" template_name = "admin/AKModel/message_delete.html"
title = _("Delete AK Orga Messages")
def get_orga_messages_for_event(self, event): def get_orga_messages_for_event(self, event):
return AKOrgaMessage.objects.filter(ak__event=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): def get_context_data(self, **kwargs):
context = super().get_context_data(**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 return context
def post(self, request, *args, **kwargs): def form_valid(self, form):
self.get_orga_messages_for_event(self.get_object()).delete() self.get_orga_messages_for_event(self.event).delete()
messages.add_message(self.request, messages.SUCCESS, _("AK Orga Messages successfully deleted")) 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: class WizardViewMixin:
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-17 22:41+0200\n" "POT-Creation-Date: 2022-09-27 17:59+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -79,7 +79,7 @@ msgstr "Seit" ...@@ -79,7 +79,7 @@ msgstr "Seit"
#: .\AKScheduling\templates\admin\AKScheduling\constraint_violations.html:139 #: .\AKScheduling\templates\admin\AKScheduling\constraint_violations.html:139
#: .\AKScheduling\templates\admin\AKScheduling\manage_tracks.html:243 #: .\AKScheduling\templates\admin\AKScheduling\manage_tracks.html:243
#: .\AKScheduling\templates\admin\AKScheduling\scheduling.html:208 #: .\AKScheduling\templates\admin\AKScheduling\scheduling.html:208
#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:43 #: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:48
#: .\AKScheduling\templates\admin\AKScheduling\unscheduled.html:34 #: .\AKScheduling\templates\admin\AKScheduling\unscheduled.html:34
msgid "Event Status" msgid "Event Status"
msgstr "Event-Status" msgstr "Event-Status"
...@@ -158,10 +158,18 @@ msgid "AKs without availabilities" ...@@ -158,10 +158,18 @@ msgid "AKs without availabilities"
msgstr "AKs ohne Verfügbarkeiten" msgstr "AKs ohne Verfügbarkeiten"
#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:28 #: .\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" msgid "AK wishes with slots"
msgstr "AK-Wünsche mit Slots" msgstr "AK-Wünsche mit Slots"
#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:35 #: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:38
msgid "Delete slots for wishes"
msgstr ""
#: .\AKScheduling\templates\admin\AKScheduling\special_attention.html:40
msgid "AKs without slots" msgid "AKs without slots"
msgstr "AKs ohne Slots" msgstr "AKs ohne Slots"
...@@ -173,10 +181,57 @@ msgstr "Noch nicht geschedulte AK-Slots" ...@@ -173,10 +181,57 @@ msgstr "Noch nicht geschedulte AK-Slots"
msgid "Count" msgid "Count"
msgstr "Anzahl" msgstr "Anzahl"
#: .\AKScheduling\views.py:103 #: .\AKScheduling\views.py:109
msgid "Interest updated" msgid "Interest updated"
msgstr "Interesse aktualisiert" msgstr "Interesse aktualisiert"
#: .\AKScheduling\views.py:141 #: .\AKScheduling\views.py:147
msgid "Wishes" msgid "Wishes"
msgstr "Wünsche" msgstr "Wünsche"
#: .\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: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"
"\n"
" {slots}"
#: .\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"
...@@ -157,7 +157,7 @@ def ak_owners_changed_handler(sender, instance: AK, action: str, **kwargs): ...@@ -157,7 +157,7 @@ def ak_owners_changed_handler(sender, instance: AK, action: str, **kwargs):
c.ak_slots_tmp.add(other_slot) c.ak_slots_tmp.add(other_slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -203,7 +203,7 @@ def ak_conflicts_changed_handler(sender, instance: AK, action: str, **kwargs): ...@@ -203,7 +203,7 @@ def ak_conflicts_changed_handler(sender, instance: AK, action: str, **kwargs):
c.ak_slots_tmp.add(other_slot) c.ak_slots_tmp.add(other_slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -249,7 +249,7 @@ def ak_prerequisites_changed_handler(sender, instance: AK, action: str, **kwargs ...@@ -249,7 +249,7 @@ def ak_prerequisites_changed_handler(sender, instance: AK, action: str, **kwargs
c.ak_slots_tmp.add(other_slot) c.ak_slots_tmp.add(other_slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -298,7 +298,7 @@ def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs) ...@@ -298,7 +298,7 @@ def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs)
c.ak_slots_tmp.add(slot) c.ak_slots_tmp.add(slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -310,7 +310,7 @@ def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs) ...@@ -310,7 +310,7 @@ def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs)
@receiver(post_save, sender=AKSlot) @receiver(post_save, sender=AKSlot)
def akslot_changed_handler(sender, instance: AKSlot, **kwargs): def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
# Changes might affect: Duplicate parallel, Two in room, Resodeadline # Changes might affect: Duplicate parallel, Two in room, Resodeadline
print(f"{sender} changed") # print(f"{sender} changed")
event = instance.event event = instance.event
# == Check for two parallel slots by one of the owners == # == Check for two parallel slots by one of the owners ==
...@@ -341,7 +341,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs): ...@@ -341,7 +341,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
c.ak_slots_tmp.add(other_slot) c.ak_slots_tmp.add(other_slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -373,7 +373,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs): ...@@ -373,7 +373,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
c.ak_slots_tmp.add(other_slot) c.ak_slots_tmp.add(other_slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the slot that was recently changed (important!) # belonging to the slot that was recently changed (important!)
...@@ -437,7 +437,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs): ...@@ -437,7 +437,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
c.ak_slots_tmp.add(instance) c.ak_slots_tmp.add(instance)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -470,7 +470,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs): ...@@ -470,7 +470,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
c.ak_slots_tmp.add(instance) c.ak_slots_tmp.add(instance)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -502,7 +502,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs): ...@@ -502,7 +502,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
c.ak_slots_tmp.add(other_slot) c.ak_slots_tmp.add(other_slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -534,7 +534,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs): ...@@ -534,7 +534,7 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
c.ak_slots_tmp.add(other_slot) c.ak_slots_tmp.add(other_slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
...@@ -556,7 +556,7 @@ def akslot_deleted_handler(sender, instance: AKSlot, **kwargs): ...@@ -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 # 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 # 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 # 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(): for cv in instance.constraintviolation_set.all():
# Make sure not delete CVs that e.g., show three parallel slots in a single room # 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 ...@@ -599,7 +599,7 @@ def room_requirements_changed_handler(sender, instance: Room, action: str, **kwa
@receiver(post_save, sender=Availability) @receiver(post_save, sender=Availability)
def availability_changed_handler(sender, instance: Availability, **kwargs): def availability_changed_handler(sender, instance: Availability, **kwargs):
# Changes might affect: category availability, AK availability, Room availability # Changes might affect: category availability, AK availability, Room availability
print(f"{instance} changed") # print(f"{instance} changed")
event = instance.event event = instance.event
...@@ -627,7 +627,7 @@ def availability_changed_handler(sender, instance: Availability, **kwargs): ...@@ -627,7 +627,7 @@ def availability_changed_handler(sender, instance: Availability, **kwargs):
c.ak_slots_tmp.add(slot) c.ak_slots_tmp.add(slot)
new_violations.append(c) 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 # ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!) # belonging to the AK that was recently changed (important!)
......
...@@ -22,21 +22,26 @@ ...@@ -22,21 +22,26 @@
{% for ak in aks_without_availabilities %} {% for ak in aks_without_availabilities %}
<a href="{% url "submit:ak_edit" event_slug=event.slug pk=ak.pk %}">{{ ak }}</a><br> <a href="{% url "submit:ak_edit" event_slug=event.slug pk=ak.pk %}">{{ ak }}</a><br>
{% empty %} {% empty %}
- -<br>
{% endfor %} {% 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> <h4 class="mt-4 mb-4">{% trans "AK wishes with slots" %}</h4>
{% for ak in ak_wishes_with_slots %} {% 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 %} {% empty %}
- -<br>
{% endfor %} {% endfor %}
<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> <h4 class="mt-4 mb-4">{% trans "AKs without slots" %}</h4>
{% for ak in aks_without_slots %} {% for ak in aks_without_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><br>
{% empty %} {% empty %}
- -<br>
{% endfor %} {% endfor %}
<div class="mt-5"> <div class="mt-5">
......
from django.urls import path from django.urls import path
from AKScheduling.views import SchedulingAdminView, UnscheduledSlotsAdminView, TrackAdminView, \ from AKScheduling.views import SchedulingAdminView, UnscheduledSlotsAdminView, TrackAdminView, \
ConstraintViolationsAdminView, SpecialAttentionAKsAdminView, InterestEnteringAdminView ConstraintViolationsAdminView, SpecialAttentionAKsAdminView, InterestEnteringAdminView, WishSlotCleanupView, \
AvailabilityAutocreateView
def get_admin_urls_scheduling(admin_site): def get_admin_urls_scheduling(admin_site):
...@@ -14,6 +15,10 @@ def get_admin_urls_scheduling(admin_site): ...@@ -14,6 +15,10 @@ def get_admin_urls_scheduling(admin_site):
name="constraint-violations"), name="constraint-violations"),
path('<slug:slug>/special-attention/', admin_site.admin_view(SpecialAttentionAKsAdminView.as_view()), path('<slug:slug>/special-attention/', admin_site.admin_view(SpecialAttentionAKsAdminView.as_view()),
name="special-attention"), 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()), path('<slug:event_slug>/tracks/', admin_site.admin_view(TrackAdminView.as_view()),
name="tracks_manage"), name="tracks_manage"),
path('<slug:event_slug>/enter-interest/<int:pk>', admin_site.admin_view(InterestEnteringAdminView.as_view()), path('<slug:event_slug>/enter-interest/<int:pk>', admin_site.admin_view(InterestEnteringAdminView.as_view()),
......
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin 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.utils.translation import gettext_lazy as _
from django.views.generic import ListView, DetailView, UpdateView from django.views.generic import ListView, DetailView, UpdateView
from AKModel.models import AKSlot, AKTrack, Event, AK, AKCategory 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 from AKScheduling.forms import AKInterestForm
...@@ -71,7 +74,7 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView): ...@@ -71,7 +74,7 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["title"] = f"{_('AKs requiring special attention for')} {context['event']}" 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 = [] aks_with_comment = []
ak_wishes_with_slots = [] ak_wishes_with_slots = []
aks_without_availabilities = [] aks_without_availabilities = []
...@@ -81,13 +84,13 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView): ...@@ -81,13 +84,13 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
if ak.notes != "": if ak.notes != "":
aks_with_comment.append(ak) aks_with_comment.append(ak)
if ak.wish: if ak.owners__count == 0:
if ak.akslot_set.count() > 0: if ak.akslot__count > 0:
ak_wishes_with_slots.append(ak) ak_wishes_with_slots.append(ak)
else: else:
if ak.akslot_set.count() == 0: if ak.akslot__count == 0:
aks_without_slots.append(ak) aks_without_slots.append(ak)
if ak.availabilities.count() == 0: if ak.availabilities__count == 0:
aks_without_availabilities.append(ak) aks_without_availabilities.append(ak)
context["aks_with_comment"] = aks_with_comment context["aks_with_comment"] = aks_with_comment
...@@ -146,3 +149,58 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi ...@@ -146,3 +149,58 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
context["categories_with_aks"] = categories_with_aks context["categories_with_aks"] = categories_with_aks
return context 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)
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)
...@@ -151,7 +151,7 @@ class AKEditForm(AKForm): ...@@ -151,7 +151,7 @@ class AKEditForm(AKForm):
self.fields["tags_raw"].initial = "; ".join(str(tag) for tag in self.instance.tags.all()) 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): class Meta(AKForm.Meta):
exclude = ['owners', 'link', 'protocol_link'] exclude = ['owners', 'link', 'protocol_link']
......
...@@ -235,95 +235,96 @@ ...@@ -235,95 +235,96 @@
<p style="margin-top: 30px;margin-bottom: 30px;">{{ ak.description|linebreaks }}</p> <p style="margin-top: 30px;margin-bottom: 30px;">{{ ak.description|linebreaks }}</p>
{% if not ak.wish %}
<table class="table"> <table class="table">
<thead> <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 %}
<tr> <tr>
{% if not ak.event.plan_hidden or user.is_staff %} {% if not ak.event.plan_hidden or user.is_staff %}
<td>{{ slot.time_simplified }}</td> <th>{% trans "When?" %}</th>
{% endif %} {% endif %}
<td>{{ slot.duration_simplified }}</td> <th>{% trans "Duration" %}</th>
{% if not ak.event.plan_hidden or user.is_staff %} {% if not ak.event.plan_hidden or user.is_staff %}
<td> <th>{% trans "Room" %}</th>
{% if slot.room %} {% endif %}
{% if "AKPlan"|check_app_installed %} <th></th>
<a href="{% url 'plan:plan_room' event_slug=ak.event.slug pk=slot.room.pk %}">{{ slot.room }}</a> </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 %} {% else %}
{{ slot.room }} -
{% 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 %} {% 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 %} {% endif %}
</td> {% if user.is_staff %}
{% endif %} <a href="{% url 'admin:AKModel_akslot_change' slot.pk %}"
<td> data-toggle="tooltip" title="{% trans 'Schedule' %}"
{% if not slot.start %} class="btn btn-outline-success">{% fa5_icon 'stream' 'fas' %}</a>
<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 %}
{% endif %} </td>
{% if user.is_staff %} </tr>
<a href="{% url 'admin:AKModel_akslot_change' slot.pk %}" {% endfor %}
data-toggle="tooltip" title="{% trans 'Schedule' %}" </tbody>
class="btn btn-outline-success">{% fa5_icon 'stream' 'fas' %}</a> </table>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if ak.event.active %} {% if ak.event.active %}
<div class=""> <div class="">
<a href="{% url 'submit:akslot_add' event_slug=ak.event.slug pk=ak.pk %}" <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> class="btn btn-success">{% fa5_icon 'plus' 'fas' %} {% trans "Add another slot" %}</a>
</div> </div>
{% endif %} {% endif %}
{% if 'AKPlan'|check_app_installed %} {% if 'AKPlan'|check_app_installed %}
<div id='akSlotCalendar' style="margin-top: 50px;margin-bottom: 50px;"></div> <div id='akSlotCalendar' style="margin-top: 50px;margin-bottom: 50px;"></div>
{% endif %} {% endif %}
<h4 style="margin-top: 30px;">{% trans "Possible Times" %}</h4> <h4 style="margin-top: 30px;">{% trans "Possible Times" %}</h4>
<table class="table"> <table class="table">
<thead> <thead>
<tr>
<th>{% trans "Start" %}</th>
<th>{% trans "End" %}</th>
</tr>
</thead>
<tbody>
{% for a in availabilities %}
<tr> <tr>
<td>{{ a.start | timezone:event.timezone | date:"l H:i" }}</td> <th>{% trans "Start" %}</th>
<td>{{ a.end | timezone:event.timezone | date:"l H:i" }}</td> <th>{% trans "End" %}</th>
</tr> </tr>
{% endfor %} </thead>
</tbody> <tbody>
</table> {% 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 %} {% endblock %}
...@@ -223,10 +223,11 @@ class AKAndAKWishSubmissionView(EventSlugMixin, EventInactiveRedirectMixin, Crea ...@@ -223,10 +223,11 @@ class AKAndAKWishSubmissionView(EventSlugMixin, EventInactiveRedirectMixin, Crea
tag, was_created = AKTag.objects.get_or_create(name=tag_name) tag, was_created = AKTag.objects.get_or_create(name=tag_name)
self.object.tags.add(tag) self.object.tags.add(tag)
# Generate slot(s) # Generate slot(s) (but not for wishes)
for duration in form.cleaned_data["durations"]: if "durations" in form.cleaned_data:
new_slot = AKSlot(ak=self.object, duration=duration, event=self.object.event) for duration in form.cleaned_data["durations"]:
new_slot.save() new_slot = AKSlot(ak=self.object, duration=duration, event=self.object.event)
new_slot.save()
return super_form_valid return super_form_valid
...@@ -269,6 +270,8 @@ class AKEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView): ...@@ -269,6 +270,8 @@ class AKEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView):
return redirect(reverse_lazy('submit:submission_overview', return redirect(reverse_lazy('submit:submission_overview',
kwargs={'event_slug': form.cleaned_data["event"].slug})) kwargs={'event_slug': form.cleaned_data["event"].slug}))
previous_owner_count = self.object.owners.count()
super_form_valid = super().form_valid(form) super_form_valid = super().form_valid(form)
# Detach existing tags # Detach existing tags
...@@ -279,6 +282,17 @@ class AKEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView): ...@@ -279,6 +282,17 @@ class AKEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView):
tag, was_created = AKTag.objects.get_or_create(name=tag_name) tag, was_created = AKTag.objects.get_or_create(name=tag_name)
self.object.tags.add(tag) 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 return super_form_valid
......