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
  • feature-type-filters
  • komasolver
  • main
  • renovate/django-5.x
  • renovate/django_csp-4.x
  • renovate/jsonschema-4.x
  • renovate/uwsgi-2.x
7 results

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
  • 520-akowner
  • 520-fix-event-wizard-datepicker
  • 520-fix-scheduling
  • 520-improve-scheduling
  • 520-improve-scheduling-2
  • 520-improve-submission
  • 520-improve-trackmanager
  • 520-improve-wall
  • 520-message-resolved
  • 520-status
  • 520-upgrades
  • add_express_interest_to_ak_overview
  • admin-production-color
  • bugfixes
  • csp
  • featire-ical-export
  • feature-ak-requirement-lists
  • feature-akslide-export-better-filename
  • feature-akslides
  • feature-better-admin
  • feature-better-cv-list
  • feature-colors
  • feature-constraint-checking
  • feature-constraint-checking-wip
  • feature-dashboard-history-button
  • feature-event-status
  • feature-event-wizard
  • feature-export-flag
  • feature-improve-admin
  • feature-improve-filters
  • feature-improved-user-creation-workflow
  • feature-interest-view
  • feature-mails
  • feature-modular-status
  • feature-plan-autoreload
  • feature-present-default
  • feature-register-link
  • feature-remaining-constraint-validation
  • feature-room-import
  • feature-scheduler-improve
  • feature-scheduling-2.0
  • feature-special-attention
  • feature-time-input
  • feature-tracker
  • feature-wiki-wishes
  • feature-wish-slots
  • feature-wizard-buttons
  • features-availabilities
  • fix-ak-times-above-folg
  • fix-api
  • fix-constraint-violation-string
  • fix-cv-checking
  • fix-default-slot-length
  • fix-default-slot-localization
  • fix-doc-minor
  • fix-duration-display
  • fix-event-tz-pytz-update
  • fix-history-interest
  • fix-interest-view
  • fix-js
  • fix-pipeline
  • fix-plan-timezone-now
  • fix-room-add
  • fix-scheduling-drag
  • fix-slot-defaultlength
  • fix-timezone
  • fix-translation-scheduling
  • fix-virtual-room-admin
  • fix-wizard-csp
  • font-locally
  • improve-admin
  • improve-online
  • improve-slides
  • improve-submission-coupling
  • interest_restriction
  • main
  • master
  • meta-debug-toolbar
  • meta-export
  • meta-makemessages
  • meta-performance
  • meta-tests
  • meta-tests-gitlab-test
  • meta-upgrades
  • mollux-master-patch-02906
  • port-availabilites-fullcalendar
  • qs
  • remove-tags
  • renovate/configure
  • renovate/django-4.x
  • renovate/django-5.x
  • renovate/django-bootstrap-datepicker-plus-5.x
  • renovate/django-bootstrap5-23.x
  • renovate/django-bootstrap5-24.x
  • renovate/django-compressor-4.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-registration-redux-2.x
  • renovate/django-simple-history-3.x
  • renovate/django-split-settings-1.x
  • renovate/django-timezone-field-5.x
100 results
Show changes
"""AKPlanning URL Configuration
"""
AKPlanning URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/
......@@ -13,13 +14,20 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
import debug_toolbar
from django.apps import apps
from django.contrib import admin
from django.urls import path, include
from django.urls import path, include, re_path
urlpatterns = [
path('admin/', admin.site.urls),
path('i18n/', include('django.conf.urls.i18n'))
re_path(r'^docs/', include('docs.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('accounts/', include('registration.backends.simple.urls')),
path('', include('AKModel.urls', namespace='model')),
path('i18n/', include('django.conf.urls.i18n')),
path('__debug__/', include(debug_toolbar.urls)),
]
# Load URLs dynamically (only if components are active)
......
# Register your models here.
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils import timezone
from django.views.generic import ListView
from rest_framework import viewsets, mixins, serializers, permissions
from AKModel.availability.models import Availability
from AKModel.models import Room, AKSlot, ConstraintViolation, DefaultSlot
from AKModel.metaviews.admin import EventSlugMixin
class ResourceSerializer(serializers.ModelSerializer):
"""
REST Framework Serializer for Rooms to produce format required for fullcalendar resources
"""
class Meta:
model = Room
fields = ['id', 'title']
title = serializers.SerializerMethodField('transform_title')
@staticmethod
def transform_title(obj):
"""
Adapt title, add capacity information if room has a restriction (capacity is not -1)
"""
if obj.capacity > 0:
return f"{obj.title} [{obj.capacity}]"
return obj.title
class ResourcesViewSet(EventSlugMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
"""
API View: Rooms (resources to schedule for in fullcalendar)
Read-only, adaption to fullcalendar format through :class:`ResourceSerializer`
"""
permission_classes = (permissions.DjangoModelPermissions,)
serializer_class = ResourceSerializer
def get_queryset(self):
return Room.objects.filter(event=self.event).order_by('location', 'name')
class EventsView(LoginRequiredMixin, EventSlugMixin, ListView):
"""
API View: Slots (events to schedule in fullcalendar)
Read-only, JSON formatted response is created manually since it requires a bunch of "custom" fields that have
different names compared to the normal model or are not present at all and need to be computed to create the
required format for fullcalendar.
"""
model = AKSlot
def get_queryset(self):
return super().get_queryset().select_related('ak').filter(
event=self.event, room__isnull=False, start__isnull=False
)
def render_to_response(self, context, **response_kwargs):
return JsonResponse(
[{
"slotID": slot.pk,
"title": f'{slot.ak.short_name}:\n{slot.ak.owners_list}',
"description": slot.ak.details,
"resourceId": slot.room.id,
"start": timezone.localtime(slot.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"end": timezone.localtime(slot.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"backgroundColor": slot.ak.category.color,
"borderColor":
"#2c3e50" if slot.fixed
else '#e74c3c' if slot.constraintviolation_set.count() > 0
else slot.ak.category.color,
"constraint": 'roomAvailable',
"editable": not slot.fixed,
'url': str(reverse('admin:AKModel_akslot_change', args=[slot.pk])),
} for slot in context["object_list"]],
safe=False,
**response_kwargs
)
class RoomAvailabilitiesView(LoginRequiredMixin, EventSlugMixin, ListView):
"""
API view: Availabilities of rooms
Read-only, JSON formatted response is created manually since it requires a bunch of "custom" fields that have
different names compared to the normal model or are not present at all and need to be computed to create the
required format for fullcalendar.
"""
model = Availability
context_object_name = "availabilities"
def get_queryset(self):
return super().get_queryset().filter(event=self.event, room__isnull=False)
def render_to_response(self, context, **response_kwargs):
return JsonResponse(
[{
"title": "",
"resourceId": a.room.id,
"start": timezone.localtime(a.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"end": timezone.localtime(a.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"display": 'background',
"groupId": 'roomAvailable',
} for a in context["availabilities"]],
safe=False,
**response_kwargs
)
class DefaultSlotsView(LoginRequiredMixin, EventSlugMixin, ListView):
"""
API view: default slots
Read-only, JSON formatted response is created manually since it requires a bunch of "custom" fields that have
different names compared to the normal model or are not present at all and need to be computed to create the
required format for fullcalendar.
"""
model = DefaultSlot
context_object_name = "default_slots"
def get_queryset(self):
return super().get_queryset().filter(event=self.event)
def render_to_response(self, context, **response_kwargs):
all_room_ids = [r.pk for r in self.event.room_set.all()]
return JsonResponse(
[{
"title": "",
"resourceIds": all_room_ids,
"start": timezone.localtime(a.start, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"end": timezone.localtime(a.end, self.event.timezone).strftime("%Y-%m-%d %H:%M:%S"),
"display": 'background',
"groupId": 'defaultSlot',
"backgroundColor": '#69b6d4'
} for a in context["default_slots"]],
safe=False,
**response_kwargs
)
class EventSerializer(serializers.ModelSerializer):
"""
REST framework serializer to adapt between AKSlot model and the event format of fullcalendar
"""
class Meta:
model = AKSlot
fields = ['id', 'start', 'end', 'roomId']
start = serializers.DateTimeField()
end = serializers.DateTimeField()
roomId = serializers.IntegerField(source='room.pk')
def update(self, instance, validated_data):
# Ignore timezone of input (treat it as timezone-less) and set the event timezone
# By working like this, the client does not need to know about timezones, since every timestamp it deals with
# has the timezone offsets already applied
start = timezone.make_aware(timezone.make_naive(validated_data.get('start')), instance.event.timezone)
end = timezone.make_aware(timezone.make_naive(validated_data.get('end')), instance.event.timezone)
instance.start = start
# Also, adapt from start & end format of fullcalendar to our start & duration model
diff = end - start
instance.duration = round(diff.days * 24 + (diff.seconds / 3600), 2)
# Updated room if needed (pk changed -- otherwise, no need for an additional database lookup)
new_room_id = validated_data.get('room')["pk"]
if instance.room is None or instance.room.pk != new_room_id:
instance.room = get_object_or_404(Room, pk=new_room_id)
instance.save()
return instance
class EventsViewSet(EventSlugMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
"""
API view: Update scheduling of a slot (event in fullcalendar format)
Write-only (will however reply with written values to PUT request)
"""
permission_classes = (permissions.DjangoModelPermissions,)
serializer_class = EventSerializer
def get_object(self):
return get_object_or_404(AKSlot, pk=self.kwargs["pk"])
def get_queryset(self):
return AKSlot.objects.filter(event=self.event)
class ConstraintViolationSerializer(serializers.ModelSerializer):
"""
REST Framework Serializer for constraint violations
"""
class Meta:
model = ConstraintViolation
fields = ['pk', 'type_display', 'aks', 'ak_slots', 'ak_owner', 'room', 'requirement', 'category', 'comment',
'timestamp_display', 'manually_resolved', 'level_display', 'details', 'edit_url']
class ConstraintViolationsViewSet(EventSlugMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
"""
API View: Constraint Violations of an event
Read-only, fields and model selected in :class:`ConstraintViolationSerializer`
"""
permission_classes = (permissions.DjangoModelPermissions,)
serializer_class = ConstraintViolationSerializer
def get_queryset(self):
# Optimize query to reduce database load
return (ConstraintViolation.objects.select_related('event', 'room')
.prefetch_related('aks', 'ak_slots', 'ak_owner', 'requirement', 'category')
.filter(event=self.event).order_by('manually_resolved', '-type', '-timestamp'))
......@@ -2,4 +2,7 @@ from django.apps import AppConfig
class AkschedulingConfig(AppConfig):
"""
App configuration (default, only specifies name of the app)
"""
name = 'AKScheduling'
from django import forms
from django.utils.translation import gettext_lazy as _
from AKModel.models import AK
class AKInterestForm(forms.ModelForm):
"""
Form for quickly changing the interest count and notes of an AK
"""
required_css_class = 'required'
class Meta:
model = AK
fields = ['interest',
'notes',
]
class AKAddSlotForm(forms.Form):
"""
Form to create a new slot for an existing AK directly from scheduling view
"""
start = forms.CharField(label=_("Start"), disabled=True)
end = forms.CharField(label=_("End"), disabled=True)
duration = forms.CharField(label=_("Duration"), disabled=True)
room = forms.IntegerField(label=_("Room"), disabled=True, widget=forms.HiddenInput())
room_name = forms.CharField(label=_("Room"), disabled=True)
def __init__(self, event):
super().__init__()
self.fields['ak'] = forms.ModelChoiceField(event.ak_set.all(), label=_("AK"))
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-22 19:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: AKScheduling/forms.py:24
msgid "Start"
msgstr "Start"
#: AKScheduling/forms.py:25
msgid "End"
msgstr "Ende"
#: AKScheduling/forms.py:26
msgid "Duration"
msgstr "Dauer"
#: AKScheduling/forms.py:27
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:171
msgid "Room"
msgstr "Raum"
#: AKScheduling/forms.py:31
msgid "AK"
msgstr "AK"
#: AKScheduling/models.py:92
#, python-format
msgid ""
"Not enough space for AK interest (Interest: %(interest)d, Capacity: "
"%(capacity)d)"
msgstr ""
"Nicht genug Platz für AK-Interesse (Interesse: %(interest)d, Kapazität: "
"%(capacity)d)"
#: AKScheduling/models.py:106
#, python-format
msgid ""
"Space is too close to AK interest (Interest: %(interest)d, Capacity: "
"%(capacity)d)"
msgstr ""
"Verfügbarer Platz zu dicht an Interesse (Interesse: %(interest)d, Kapazität: "
"%(capacity)d)"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:11
msgid "Constraint Violations for"
msgstr ""
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:44
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:105
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:240
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:375
msgid "No violations"
msgstr "Keine Verletzungen"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:82
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:346
msgid "Violation(s)"
msgstr "Verletzung(en)"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:85
msgid "Auto reload?"
msgstr "Automatisch neu laden?"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:89
msgid "Reload now"
msgstr "Jetzt neu laden"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:95
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:228
msgid "Violation"
msgstr "Verletzung"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:96
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:369
msgid "Problem"
msgstr "Problem"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:97
msgid "Details"
msgstr "Details"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:98
msgid "Since"
msgstr "Seit"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:111
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:256
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:332
#: AKScheduling/templates/admin/AKScheduling/special_attention.html:48
#: AKScheduling/templates/admin/AKScheduling/unscheduled.html:34
msgid "Event Status"
msgstr "Event-Status"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:113
#: AKScheduling/views.py:48
msgid "Scheduling"
msgstr "Scheduling"
#: AKScheduling/templates/admin/AKScheduling/interest.html:32
msgid "Submit"
msgstr "Abschicken"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:11
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:21
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:329
msgid "Scheduling for"
msgstr "Scheduling für"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:135
msgid "Name of new ak track"
msgstr "Name des neuen AK-Tracks"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:151
msgid "Could not create ak track"
msgstr "Konnte neuen AK-Track nicht anlegen"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:177
msgid "Could not update ak track name"
msgstr "Konnte Namen des AK-Tracks nicht ändern"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:183
msgid "Do you really want to delete this ak track?"
msgstr "Soll dieser AK-Track wirklich gelöscht werden?"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:197
msgid "Could not delete ak track"
msgstr "AK-Track konnte nicht gelöscht werden"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:216
msgid "Manage AK Tracks"
msgstr "AK-Tracks verwalten"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:217
msgid "Add ak track"
msgstr "AK-Track hinzufügen"
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:222
msgid "AKs without track"
msgstr "AKs ohne Track"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:106
msgid "Day (Horizontal)"
msgstr "Tag (horizontal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:113
msgid "Day (Vertical)"
msgstr "Tag (vertikal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:124
msgid "Event (Horizontal)"
msgstr "Event (horizontal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:133
msgid "Event (Vertical)"
msgstr "Event (vertikal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:271
msgid "Please choose AK"
msgstr "Bitte AK auswählen"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:291
msgid "Could not create slot"
msgstr "Konnte Slot nicht anlegen"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:307
msgid "Add slot"
msgstr "Slot hinzufügen"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:315
msgid "Add"
msgstr "Hinzufügen"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:316
msgid "Cancel"
msgstr "Abbrechen"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:343
msgid "Unscheduled"
msgstr "Nicht gescheduled"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:368
msgid "Level"
msgstr "Level"
#: AKScheduling/templates/admin/AKScheduling/special_attention.html:14
msgid "AKs with public notes"
msgstr "AKs mit öffentlichen Kommentaren"
#: AKScheduling/templates/admin/AKScheduling/special_attention.html:21
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:38
msgid "Delete slots for wishes"
msgstr ""
#: AKScheduling/templates/admin/AKScheduling/special_attention.html:40
msgid "AKs without slots"
msgstr "AKs ohne Slots"
#: AKScheduling/templates/admin/AKScheduling/status/cvs.html:6
msgid ""
"\n"
" <h3>Constraint Violation</h3>\n"
" "
msgid_plural ""
"\n"
" <h3>Constraint Violations</h3>\n"
" "
msgstr[0] ""
"\n"
" <h3>Constraintverletzung</h3>\n"
" "
msgstr[1] ""
"\n"
" <h3>Constraintverletzungen</h3>\n"
" "
#: AKScheduling/templates/admin/AKScheduling/unscheduled.html:7
#: AKScheduling/views.py:25
msgid "Unscheduled AK Slots"
msgstr "Noch nicht geschedulte AK-Slots"
#: AKScheduling/templates/admin/AKScheduling/unscheduled.html:11
msgid "Count"
msgstr "Anzahl"
#: AKScheduling/views.py:89
msgid "Constraint violations for"
msgstr "Constraintverletzungen für"
#: AKScheduling/views.py:104
msgid "AKs requiring special attention for"
msgstr "AKs die besondere Aufmerksamkeit erfordern für"
#: AKScheduling/views.py:150
msgid "Interest updated"
msgstr "Interesse aktualisiert"
#: AKScheduling/views.py:157
msgid "Enter interest"
msgstr "Interesse eingeben"
#: AKScheduling/views.py:201
msgid "Wishes"
msgstr "Wünsche"
#: AKScheduling/views.py:219
msgid "Cleanup: Delete unscheduled slots for wishes"
msgstr "Aufräumen: Noch nicht geplante Slots für Wünsche löschen"
#: AKScheduling/views.py:226
#, 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:233
msgid "Unscheduled slots for wishes successfully deleted"
msgstr "Noch nicht geplante Slots für Wünsche erfolgreich gelöscht"
#: AKScheduling/views.py:247
msgid "Create default availabilities for AKs"
msgstr "Standardverfügbarkeiten für AKs anlegen"
#: AKScheduling/views.py:254
#, 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:274
#, 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:279
#, python-brace-format
msgid "Created default availabilities for {count} AKs"
msgstr "Standardverfügbarkeiten für {count} AKs angelegt"
#: AKScheduling/views.py:290
msgid "Constraint Violations"
msgstr "Constraintverletzungen"
#~ msgid "Bitte AK auswählen"
#~ msgstr "Please sel"
#~ msgid "Cannot load current violations from server"
#~ msgstr "Kann die aktuellen Verletzungen nicht vom Server laden"
# Create your models here.
# This file mainly contains signal receivers, which follow a very strong interface, having e.g., a sender attribute
# that is hardly used by us. Nevertheless, to follow the django receiver coding style and since changes might
# cause issues when loading fixtures or model dumps, it is not wise to replace that attribute with "_".
# Therefore, the check that finds unused arguments is disabled for this whole file:
# pylint: disable=unused-argument
from django.db.models.signals import post_save, m2m_changed, pre_delete
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from AKModel.availability.models import Availability
from AKModel.models import AK, AKSlot, Room, Event, ConstraintViolation
def update_constraint_violations(new_violations, existing_violations_to_check):
"""
Update existing constraint violations (subset for which new violations were computed) based on these new violations.
This will add all new violations without a match, preserve the matching ones
and delete the obsolete ones (those without a match from the newly calculated violations).
:param new_violations: list of new (not yet saved) violations that exist after the last change
:type new_violations: list[ConstraintViolation]
:param existing_violations_to_check: list of related violations currently in the db
:type existing_violations_to_check: list[ConstraintViolation]
"""
for new_violation in new_violations:
found_match = False
for existing_violation in existing_violations_to_check:
if existing_violation.matches(new_violation):
# Remove from existing violations set since it should stay in db
existing_violations_to_check.remove(existing_violation)
found_match = True
break
# Only save new violation if no match was found
if not found_match:
new_violation.save()
# Cleanup obsolete violations (ones without matches computed under current conditions)
for outdated_violation in existing_violations_to_check:
outdated_violation.delete()
def update_cv_reso_deadline_for_slot(slot):
"""
Update constraint violation AK_AFTER_RESODEADLINE for given slot
:param slot: slot to check/update
:type slot: AKSlot
"""
event = slot.event
# Update only if reso_deadline exists
# if event was changed and reso_deadline is removed, CVs will be deleted by event changed handler
# Update only has to be done for already scheduled slots with reso intention
if slot.ak.reso and slot.event.reso_deadline and slot.start:
violation_type = ConstraintViolation.ViolationType.AK_AFTER_RESODEADLINE
new_violations = []
# Violation?
if slot.end > event.reso_deadline:
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
)
c.aks_tmp.add(slot.ak)
c.ak_slots_tmp.add(slot)
new_violations.append(c)
update_constraint_violations(new_violations, list(slot.constraintviolation_set.filter(type=violation_type)))
def check_capacity_for_slot(slot: AKSlot):
"""
Check whether this slot violates the capacity requirement
:param slot: slot to check
:type slot: AKSlot
:return: Violation (if any) or None
:rtype: ConstraintViolation or None
"""
# If slot is scheduled in a room and interest was specified
if slot.room and slot.room.capacity >= 0 and slot.ak.interest >= 0:
# Create a violation if interest exceeds room capacity
if slot.room.capacity < slot.ak.interest:
c = ConstraintViolation(
type=ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=slot.event,
room=slot.room,
comment=_("Not enough space for AK interest (Interest: %(interest)d, Capacity: %(capacity)d)")
% {'interest': slot.ak.interest, 'capacity': slot.room.capacity},
)
c.ak_slots_tmp.add(slot)
c.aks_tmp.add(slot.ak)
return c
# Create a warning if interest is close to room capacity
if slot.room.capacity < slot.ak.interest + 5 or slot.room.capacity < slot.ak.interest * 1.25:
c = ConstraintViolation(
type=ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED,
level=ConstraintViolation.ViolationLevel.WARNING,
event=slot.event,
room=slot.room,
comment=_("Space is too close to AK interest (Interest: %(interest)d, Capacity: %(capacity)d)")
% {'interest': slot.ak.interest, 'capacity': slot.room.capacity}
)
c.ak_slots_tmp.add(slot)
c.aks_tmp.add(slot.ak)
return c
return None
@receiver(post_save, sender=AK)
def ak_changed_handler(sender, instance: AK, **kwargs):
"""
Signal receiver: Check for violations after AK changed
Changes might affect: Reso intention, Category, Interest
"""
# TODO Reso intention changes
# Check room capacities
violation_type = ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED
new_violations = []
for slot in instance.akslot_set.all():
cv = check_capacity_for_slot(slot)
if cv is not None:
new_violations.append(cv)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(m2m_changed, sender=AK.owners.through)
def ak_owners_changed_handler(sender, instance: AK, action: str, **kwargs):
"""
Signal receiver: Owners of AK changed
"""
# Only signal after change (post_add, post_delete, post_clear) are relevant
if not action.startswith("post"):
return
event = instance.event
# Owner(s) changed: Might affect multiple AKs by the same owner(s) at the same time
violation_type = ConstraintViolation.ViolationType.OWNER_TWO_SLOTS
new_violations = []
slots_of_this_ak: [AKSlot] = instance.akslot_set.filter(start__isnull=False)
# For all owners (after recent change)...
for owner in instance.owners.all():
# ...find other slots that might be overlapping...
for ak in owner.ak_set.all():
# ...find overlapping slots...
if ak != instance:
for slot in slots_of_this_ak:
for other_slot in ak.akslot_set.filter(start__isnull=False):
if slot.overlaps(other_slot):
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
ak_owner=owner
)
c.aks_tmp.add(instance)
c.aks_tmp.add(other_slot.ak)
c.ak_slots_tmp.add(slot)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(m2m_changed, sender=AK.conflicts.through)
def ak_conflicts_changed_handler(sender, instance: AK, action: str, **kwargs):
"""
Signal receiver: Conflicts of AK changed
"""
# Only signal after change (post_add, post_delete, post_clear) are relevant
if not action.startswith("post"):
return
event = instance.event
# Conflict(s) changed: Might affect multiple AKs that are conflicts of each other
violation_type = ConstraintViolation.ViolationType.AK_CONFLICT_COLLISION
new_violations = []
slots_of_this_ak: [AKSlot] = instance.akslot_set.filter(start__isnull=False)
conflicts_of_this_ak: [AK] = instance.conflicts.all()
# Loop over all existing conflicts
for ak in conflicts_of_this_ak:
if ak != instance:
for other_slot in ak.akslot_set.filter(start__isnull=False):
for slot in slots_of_this_ak:
# ...find overlapping slots...
if slot.overlaps(other_slot):
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
)
c.aks_tmp.add(instance)
c.ak_slots_tmp.add(slot)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(m2m_changed, sender=AK.prerequisites.through)
def ak_prerequisites_changed_handler(sender, instance: AK, action: str, **kwargs):
"""
Signal receiver: Prerequisites of AK changed
"""
# Only signal after change (post_add, post_delete, post_clear) are relevant
if not action.startswith("post"):
return
event = instance.event
# Prerequisite(s) changed: Might affect multiple AKs that should have a certain order
violation_type = ConstraintViolation.ViolationType.AK_BEFORE_PREREQUISITE
new_violations = []
slots_of_this_ak: [AKSlot] = instance.akslot_set.filter(start__isnull=False)
prerequisites_of_this_ak: [AK] = instance.prerequisites.all()
# Loop over all prerequisites
for ak in prerequisites_of_this_ak:
if ak != instance:
for other_slot in ak.akslot_set.filter(start__isnull=False):
for slot in slots_of_this_ak:
# ...find overlapping slots...
if other_slot.end > slot.start:
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
)
c.aks_tmp.add(instance)
c.ak_slots_tmp.add(slot)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(m2m_changed, sender=AK.requirements.through)
def ak_requirements_changed_handler(sender, instance: AK, action: str, **kwargs):
"""
Signal receiver: Requirements of AK changed
"""
# Only signal after change (post_add, post_delete, post_clear) are relevant
if not action.startswith("post"):
return
event = instance.event
# Requirement(s) changed: Might affect slots and rooms
violation_type = ConstraintViolation.ViolationType.REQUIRE_NOT_GIVEN
new_violations = []
slots_of_this_ak: [AKSlot] = instance.akslot_set.filter(start__isnull=False)
# For all requirements (after recent change)...
for slot in slots_of_this_ak:
room = slot.room
if room is None:
continue
room_requirements = room.properties.all()
for requirement in instance.requirements.all():
if not requirement in room_requirements:
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
requirement=requirement,
room=room,
)
c.aks_tmp.add(instance)
c.ak_slots_tmp.add(slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(post_save, sender=AKSlot)
def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
"""
Signal receiver: AKSlot changed
Changes might affect: Duplicate parallel, Two in room, Resodeadline
"""
# TODO Consider rewriting this very long and complex method to resolve several (style) issues:
# pylint: disable=too-many-nested-blocks,too-many-locals,too-many-branches,too-many-statements
event = instance.event
# == Check for two parallel slots by one of the owners ==
violation_type = ConstraintViolation.ViolationType.OWNER_TWO_SLOTS
new_violations = []
if instance.start:
# For all owners (after recent change)...
for owner in instance.ak.owners.all():
# ...find other slots that might be overlapping...
for ak in owner.ak_set.all():
# ...find overlapping slots...
if ak != instance.ak:
for other_slot in ak.akslot_set.filter(start__isnull=False):
if instance.overlaps(other_slot):
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
ak_owner=owner
)
c.aks_tmp.add(instance.ak)
c.aks_tmp.add(other_slot.ak)
c.ak_slots_tmp.add(instance)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
# == Check for two aks in the same room at the same time ==
violation_type = ConstraintViolation.ViolationType.ROOM_TWO_SLOTS
new_violations = []
# For all slots in this room...
if instance.room and instance.start:
for other_slot in instance.room.akslot_set.filter(start__isnull=False):
if other_slot != instance:
# ... find overlapping slots...
if instance.overlaps(other_slot):
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.WARNING,
event=event,
room=instance.room
)
c.aks_tmp.add(instance.ak)
c.aks_tmp.add(other_slot.ak)
c.ak_slots_tmp.add(instance)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the slot that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
# == Check for reso ak after reso deadline ==
update_cv_reso_deadline_for_slot(instance)
# == Check for two slots of the same AK at the same time (warning) ==
violation_type = ConstraintViolation.ViolationType.AK_SLOT_COLLISION
new_violations = []
if instance.start:
# For all other slots of this ak...
for other_slot in instance.ak.akslot_set.filter(start__isnull=False):
if other_slot != instance:
# ... find overlapping slots...
if instance.overlaps(other_slot):
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.WARNING,
event=event,
)
c.aks_tmp.add(instance.ak)
c.ak_slots_tmp.add(instance)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the slot that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
update_constraint_violations(new_violations, existing_violations_to_check)
# == Check for slot outside availability ==
# An AK's availability changed: Might affect AK slots scheduled outside the permitted time
violation_type = ConstraintViolation.ViolationType.SLOT_OUTSIDE_AVAIL
new_violations = []
if instance.start:
availabilities_of_this_ak: [Availability] = instance.ak.availabilities.all()
covered = False
for availability in availabilities_of_this_ak:
covered = availability.start <= instance.start and availability.end >= instance.end
if covered:
break
if not covered:
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event
)
c.aks_tmp.add(instance.ak)
c.ak_slots_tmp.add(instance)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
# == Check for requirement not fulfilled by room ==
# Room(s) changed: Might affect slots and rooms
violation_type = ConstraintViolation.ViolationType.REQUIRE_NOT_GIVEN
new_violations = []
if instance.room:
room_requirements = instance.room.properties.all()
for requirement in instance.ak.requirements.all():
if requirement not in room_requirements:
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
requirement=requirement,
room=instance.room,
)
c.aks_tmp.add(instance.ak)
c.ak_slots_tmp.add(instance)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
# == check for simultaneous slots of conflicting AKs ==
violation_type = ConstraintViolation.ViolationType.AK_CONFLICT_COLLISION
new_violations = []
if instance.start:
conflicts_of_this_ak: [AK] = instance.ak.conflicts.all()
for ak in conflicts_of_this_ak:
if ak != instance.ak:
for other_slot in ak.akslot_set.filter(start__isnull=False):
# ...find overlapping slots...
if instance.overlaps(other_slot):
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
)
c.aks_tmp.add(instance.ak)
c.ak_slots_tmp.add(instance)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.ak.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
# == check for missing prerequisites ==
violation_type = ConstraintViolation.ViolationType.AK_BEFORE_PREREQUISITE
new_violations = []
if instance.start:
prerequisites_of_this_ak: [AK] = instance.ak.prerequisites.all()
for ak in prerequisites_of_this_ak:
if ak != instance.ak:
for other_slot in ak.akslot_set.filter(start__isnull=False):
# ...find slots in the wrong order...
if other_slot.end > instance.start:
# ...and create a temporary violation if necessary...
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event,
)
c.aks_tmp.add(instance.ak)
c.ak_slots_tmp.add(instance)
c.ak_slots_tmp.add(other_slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.ak.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
# == Check for room capacity ==
cv = check_capacity_for_slot(instance)
new_violations = [cv] if cv is not None else []
# Compare to/update list of existing violations of this type for this slot
existing_violations_to_check = list(
instance.constraintviolation_set.filter(type=ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED)
)
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(pre_delete, sender=AKSlot)
def akslot_deleted_handler(sender, instance: AKSlot, **kwargs):
"""
Signal receiver: AKSlot deleted
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")
for cv in instance.constraintviolation_set.all():
# Make sure not delete CVs that e.g., show three parallel slots in a single room
if cv.ak_slots.count() <= 2:
cv.delete()
@receiver(post_save, sender=Room)
def room_changed_handler(sender, instance: Room, **kwargs):
"""
Signal receiver: Room changed
Changes might affect: Room size
"""
# Check room capacities
violation_type = ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED
new_violations = []
for slot in instance.akslot_set.all():
cv = check_capacity_for_slot(slot)
if cv is not None:
new_violations.append(cv)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(m2m_changed, sender=Room.properties.through)
def room_requirements_changed_handler(sender, instance: Room, action: str, **kwargs):
"""
Signal Receiver: Requirements of room changed
"""
# Only signal after change (post_add, post_delete, post_clear) are relevant
if not action.startswith("post"):
return
# event = instance.event
# TODO React to changes
@receiver(post_save, sender=Availability)
def availability_changed_handler(sender, instance: Availability, **kwargs):
"""
Signal receiver: Availalability changed
Changes might affect: category availability, AK availability, Room availability
"""
event = instance.event
# An AK's availability changed: Might affect AK slots scheduled outside the permitted time
if instance.ak:
violation_type = ConstraintViolation.ViolationType.SLOT_OUTSIDE_AVAIL
new_violations = []
availabilities_of_this_ak: [Availability] = instance.ak.availabilities.all()
slots_of_this_ak: [AKSlot] = instance.ak.akslot_set.filter(start__isnull=False)
for slot in slots_of_this_ak:
covered = False
for availability in availabilities_of_this_ak:
covered = availability.start <= slot.start and availability.end >= slot.end
if covered:
break
if not covered:
c = ConstraintViolation(
type=violation_type,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=event
)
c.aks_tmp.add(instance.ak)
c.ak_slots_tmp.add(slot)
new_violations.append(c)
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
existing_violations_to_check = list(instance.ak.constraintviolation_set.filter(type=violation_type))
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(post_save, sender=Event)
def event_changed_handler(sender, instance: Event, **kwargs):
"""
Signal receiver: Event changed
Changes might affect: Reso deadline
"""
# Check for reso ak after reso deadline (which might have changed)
if instance.reso_deadline:
for slot in instance.akslot_set.filter(start__isnull=False, ak__reso=True):
update_cv_reso_deadline_for_slot(slot)
else:
# No reso deadline, delete all violations
violation_type = ConstraintViolation.ViolationType.AK_AFTER_RESODEADLINE
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
update_constraint_violations([], existing_violations_to_check)
function loadCVs(url, callback_success, callback_error) {
$.ajax({
url: url,
type: 'GET',
success: callback_success,
error: callback_error
});
}
const default_cv_callback_error = function(response) {
alert("{% trans 'Cannot load current violations from server' %}");
}
.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
opacity: 0.8;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
}
.gu-hide {
display: none !important;
}
.gu-unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
.gu-transit {
opacity: 0.2;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
filter: alpha(opacity=20);
}