diff --git a/AKModel/admin.py b/AKModel/admin.py index ae8da12f3a60f47c64cb21b6611ead2b7b099059..4675d84262aa5c38ca1eda84a2ecc2b5c1a6a54b 100644 --- a/AKModel/admin.py +++ b/AKModel/admin.py @@ -3,8 +3,9 @@ from django.apps import apps from django.contrib import admin from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter from django.db.models import Count, F +from django.http import HttpResponseRedirect from django.shortcuts import render, redirect -from django.urls import reverse_lazy +from django.urls import reverse_lazy, path from django.utils import timezone from django.utils.html import format_html from django.utils.safestring import mark_safe @@ -17,6 +18,7 @@ from AKModel.availability.models import Availability from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \ ConstraintViolation from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event +from AKModel.views import CVMarkResolvedView, CVSetLevelViolationView, CVSetLevelWarningView class EventRelatedFieldListFilter(RelatedFieldListFilter): @@ -317,3 +319,28 @@ class ConstraintViolationAdmin(admin.ModelAdmin): list_filter = ['event'] readonly_fields = ['timestamp'] form = ConstraintViolationAdminForm + actions = ['mark_resolved', 'set_violation', 'set_warning'] + + def get_urls(self): + urls = [ + path('mark-resolved/', CVMarkResolvedView.as_view(), name="cv-mark-resolved"), + path('set-violation/', CVSetLevelViolationView.as_view(), name="cv-set-violation"), + path('set-warning/', CVSetLevelWarningView.as_view(), name="cv-set-warning"), + ] + urls.extend(super().get_urls()) + return urls + + def mark_resolved(self, request, queryset): + selected = queryset.values_list('pk', flat=True) + return HttpResponseRedirect(f"{reverse_lazy('admin:cv-mark-resolved')}?pks={','.join(str(pk) for pk in selected)}") + mark_resolved.short_description = _("Mark Constraint Violations as manually resolved") + + def set_violation(self, request, queryset): + selected = queryset.values_list('pk', flat=True) + return HttpResponseRedirect(f"{reverse_lazy('admin:cv-set-violation')}?pks={','.join(str(pk) for pk in selected)}") + set_violation.short_description = _('Set to Constraint Violations to level "violation"') + + def set_warning(self, request, queryset): + selected = queryset.values_list('pk', flat=True) + return HttpResponseRedirect(f"{reverse_lazy('admin:cv-set-warning')}?pks={','.join(str(pk) for pk in selected)}") + set_warning.short_description = _('Set Constraint Violations to level "warning"') diff --git a/AKModel/forms.py b/AKModel/forms.py index 28e5c678be075e8ee71831e961de17472d95c8bd..d72c973786776736cf176db9c5b3ee916e842584 100644 --- a/AKModel/forms.py +++ b/AKModel/forms.py @@ -74,6 +74,10 @@ class AdminIntermediateForm(forms.Form): pass +class AdminIntermediateActionForm(AdminIntermediateForm): + pks = forms.CharField(widget=forms.HiddenInput) + + class SlideExportForm(AdminIntermediateForm): num_next = forms.IntegerField( min_value=0, diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po index b9eb5368f3ff32979b37eba5ae3e365962ee0194..25980a446d3fd10bc881331c0e2544c04d33780a 100644 --- a/AKModel/locale/de_DE/LC_MESSAGES/django.po +++ b/AKModel/locale/de_DE/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-27 23:49+0200\n" +"POT-Creation-Date: 2022-09-28 01:20+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" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: .\AKModel\admin.py:56 .\AKModel\admin.py:58 +#: .\AKModel\admin.py:58 .\AKModel\admin.py:60 #: .\AKModel\templates\admin\AKModel\event_wizard\activate.html:32 #: .\AKModel\templates\admin\AKModel\event_wizard\created_prepare_import.html:48 #: .\AKModel\templates\admin\AKModel\event_wizard\finish.html:21 @@ -21,26 +21,38 @@ msgstr "" msgid "Status" msgstr "Status" -#: .\AKModel\admin.py:132 +#: .\AKModel\admin.py:134 msgid "Wish" msgstr "AK-Wunsch" -#: .\AKModel\admin.py:138 +#: .\AKModel\admin.py:140 msgid "Is wish" msgstr "Ist ein Wunsch" -#: .\AKModel\admin.py:139 +#: .\AKModel\admin.py:141 msgid "Is not a wish" msgstr "Ist kein Wunsch" -#: .\AKModel\admin.py:185 +#: .\AKModel\admin.py:187 msgid "Export to wiki syntax" msgstr "In Wiki-Syntax exportieren" -#: .\AKModel\admin.py:279 +#: .\AKModel\admin.py:281 msgid "AK Details" msgstr "AK-Details" +#: .\AKModel\admin.py:336 .\AKModel\views.py:412 +msgid "Mark Constraint Violations as manually resolved" +msgstr "Markiere Constraintverletzungen manuell als behoben" + +#: .\AKModel\admin.py:341 +msgid "Set to Constraint Violations to level \"violation\"" +msgstr "Constraintverletzungen auf Level \"Violation\" setzen" + +#: .\AKModel\admin.py:346 .\AKModel\views.py:432 +msgid "Set Constraint Violations to level \"warning\"" +msgstr "Constraintverletzungen auf Level \"Warning\" setzen" + #: .\AKModel\availability\forms.py:21 .\AKModel\availability\models.py:248 msgid "Availability" msgstr "Verfügbarkeit" @@ -135,35 +147,35 @@ msgstr "AK-Kategorien kopieren" msgid "Copy ak requirements" msgstr "AK-Anforderungen kopieren" -#: .\AKModel\forms.py:82 +#: .\AKModel\forms.py:86 msgid "# next AKs" msgstr "# nächste AKs" -#: .\AKModel\forms.py:83 +#: .\AKModel\forms.py:87 msgid "How many next AKs should be shown on a slide?" msgstr "Wie viele nächste AKs sollen auf einer Folie angezeigt werden?" -#: .\AKModel\forms.py:86 +#: .\AKModel\forms.py:90 msgid "Presentation only?" msgstr "Nur Vorstellung?" -#: .\AKModel\forms.py:88 .\AKModel\forms.py:95 +#: .\AKModel\forms.py:92 .\AKModel\forms.py:99 msgid "Yes" msgstr "Ja" -#: .\AKModel\forms.py:88 .\AKModel\forms.py:95 +#: .\AKModel\forms.py:92 .\AKModel\forms.py:99 msgid "No" msgstr "Nein" -#: .\AKModel\forms.py:90 +#: .\AKModel\forms.py:94 msgid "Restrict AKs to those that asked for chance to be presented?" msgstr "AKs auf solche, die um eine Vorstellung gebeten haben, einschränken?" -#: .\AKModel\forms.py:93 +#: .\AKModel\forms.py:97 msgid "Space for notes in wishes?" msgstr "Platz für Notizen bei den Wünschen?" -#: .\AKModel\forms.py:97 +#: .\AKModel\forms.py:101 msgid "" "Create symbols indicating space to note down owners and timeslots for " "wishes, e.g., to be filled out on a touch screen while presenting?" @@ -208,7 +220,7 @@ msgstr "Zeitzone" msgid "Time Zone where this event takes place in" msgstr "Zeitzone in der das Event stattfindet" -#: .\AKModel\models.py:27 .\AKModel\views.py:239 +#: .\AKModel\models.py:27 .\AKModel\views.py:241 msgid "Start" msgstr "Start" @@ -557,7 +569,7 @@ msgstr "Anzahl Personen, die online Interesse bekundet haben" #: .\AKModel\models.py:291 .\AKModel\models.py:498 #: .\AKModel\templates\admin\AKModel\status.html:49 -#: .\AKModel\templates\admin\AKModel\status.html:56 .\AKModel\views.py:357 +#: .\AKModel\templates\admin\AKModel\status.html:56 .\AKModel\views.py:359 msgid "AKs" msgstr "AKs" @@ -842,7 +854,7 @@ msgid "Successfully imported.<br><br>Do you want to activate your event now?" msgstr "Erfolgreich importiert.<br><br>Soll das Event jetzt aktiviert werden?" #: .\AKModel\templates\admin\AKModel\event_wizard\activate.html:27 -#: .\AKModel\views.py:244 +#: .\AKModel\views.py:246 msgid "Finish" msgstr "Abschluss" @@ -968,7 +980,7 @@ msgstr "AKs als CSV exportieren" msgid "Export AKs for Wiki" msgstr "AKs im Wiki-Format exportieren" -#: .\AKModel\templates\admin\AKModel\status.html:92 .\AKModel\views.py:327 +#: .\AKModel\templates\admin\AKModel\status.html:92 .\AKModel\views.py:329 msgid "Export AK Slides" msgstr "AK-Folien exportieren" @@ -1021,82 +1033,114 @@ msgstr "Login" msgid "Register" msgstr "Registrieren" -#: .\AKModel\views.py:145 +#: .\AKModel\views.py:147 msgid "Event Status" msgstr "Eventstatus" -#: .\AKModel\views.py:158 +#: .\AKModel\views.py:160 msgid "Requirements for Event" msgstr "Anforderungen für das Event" -#: .\AKModel\views.py:172 +#: .\AKModel\views.py:174 msgid "AK CSV Export" msgstr "AK-CSV-Export" -#: .\AKModel\views.py:186 +#: .\AKModel\views.py:188 msgid "AK Wiki Export" msgstr "AK-Wiki-Export" -#: .\AKModel\views.py:194 .\AKModel\views.py:343 +#: .\AKModel\views.py:196 .\AKModel\views.py:345 msgid "Wishes" msgstr "Wünsche" -#: .\AKModel\views.py:215 +#: .\AKModel\views.py:217 msgid "Delete AK Orga Messages" msgstr "AK-Organachrichten löschen" -#: .\AKModel\views.py:230 +#: .\AKModel\views.py:232 msgid "AK Orga Messages successfully deleted" msgstr "AK-Organachrichten erfolgreich gelöscht" -#: .\AKModel\views.py:240 +#: .\AKModel\views.py:242 msgid "Settings" msgstr "Einstellungen" -#: .\AKModel\views.py:241 +#: .\AKModel\views.py:243 msgid "Event created, Prepare Import" msgstr "Event angelegt, Import vorbereiten" -#: .\AKModel\views.py:242 +#: .\AKModel\views.py:244 msgid "Import categories & requirements" msgstr "Kategorien & Anforderungen kopieren" -#: .\AKModel\views.py:243 +#: .\AKModel\views.py:245 #, fuzzy #| msgid "Active State" msgid "Activate?" msgstr "Aktivieren?" -#: .\AKModel\views.py:302 +#: .\AKModel\views.py:304 #, python-format msgid "Copied '%(obj)s'" msgstr "'%(obj)s' kopiert" -#: .\AKModel\views.py:305 +#: .\AKModel\views.py:307 #, python-format msgid "Could not copy '%(obj)s' (%(error)s)" msgstr "'%(obj)s' konnte nicht kopiert werden (%(error)s)" -#: .\AKModel\views.py:338 +#: .\AKModel\views.py:340 msgid "Symbols" msgstr "Symbole" -#: .\AKModel\views.py:339 +#: .\AKModel\views.py:341 msgid "Who?" msgstr "Wer?" -#: .\AKModel\views.py:340 +#: .\AKModel\views.py:342 msgid "Duration(s)" msgstr "Dauer(n)" -#: .\AKModel\views.py:341 +#: .\AKModel\views.py:343 msgid "Reso intention?" msgstr "Resolutionsabsicht?" -#: .\AKModel\views.py:342 +#: .\AKModel\views.py:344 msgid "Category (for Wishes)" msgstr "Kategorie (für Wünsche)" +#: .\AKModel\views.py:414 +msgid "The following Constraint Violations will be marked as manually resolved" +msgstr "" +"Die folgenden Constraintverletzungen werden als manuell behoben markiert" + +#: .\AKModel\views.py:415 +msgid "Constraint Violations marked as resolved" +msgstr "Constraintverletzungen als behoben markiert" + +#: .\AKModel\views.py:422 +msgid "Set Constraint Violations to level \"violation\"" +msgstr "Constraintverletzungen auf Level \"violation\" setzen" + +#: .\AKModel\views.py:424 +msgid "The following Constraint Violations will be set to level 'violation'" +msgstr "" +"Die folgenden Constraintverletzungen werden auf das Level \"Violation\" " +"gesetzt" + +#: .\AKModel\views.py:425 +msgid "Constraint Violations set to level 'violation'" +msgstr "Constraintverletzungen auf Level \"Violation\" gesetzt" + +#: .\AKModel\views.py:434 +msgid "The following Constraint Violations will be set to level 'warning'" +msgstr "" +"Die folgenden Constraintverletzungen werden auf das Level \"Warning\" gesetzt" + +#: .\AKModel\views.py:435 +msgid "Constraint Violations set to level 'warning'" +msgstr "Constraintverletzungen auf Level \"Warning\" gesetzt" + #, fuzzy #~| msgid "Export AK Slides" #~ msgid "Export Slides" diff --git a/AKModel/views.py b/AKModel/views.py index 530a26c5f9d0a9ab9ba6245a135e6fc995c0e74a..b6311f3fe84afeed90aef0dd6ec4124aadca85ec 100644 --- a/AKModel/views.py +++ b/AKModel/views.py @@ -1,10 +1,11 @@ import os import tempfile +from abc import ABC, abstractmethod from itertools import zip_longest from django.contrib import admin, messages from django.shortcuts import get_object_or_404, redirect -from django.urls import reverse_lazy +from django.urls import reverse_lazy, reverse from django.utils.translation import gettext_lazy as _ from django.views.generic import TemplateView, DetailView, ListView, DeleteView, CreateView, FormView, UpdateView from django_tex.core import render_template_with_context, run_tex_in_directory @@ -12,8 +13,10 @@ from django_tex.response import PDFResponse from rest_framework import viewsets, permissions, mixins from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \ - NewEventWizardImportForm, NewEventWizardActivateForm, AdminIntermediateForm, SlideExportForm -from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement + NewEventWizardImportForm, NewEventWizardActivateForm, AdminIntermediateForm, SlideExportForm, \ + AdminIntermediateActionForm +from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement, \ + ConstraintViolation from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \ AKOwnerSerializer @@ -369,3 +372,67 @@ class ExportSlidesView(EventSlugMixin, IntermediateAdminView): pdf = run_tex_in_directory(source, tempdir, template_name=self.template_name) return PDFResponse(pdf, filename='slides.pdf') + + +class IntermediateAdminActionView(IntermediateAdminView, ABC): + form_class = AdminIntermediateActionForm + + def get_queryset(self, pks=None): + if pks is None: + pks = self.request.GET['pks'] + return self.model.objects.filter(pk__in=pks.split(",")) + + def get_initial(self): + initial = super().get_initial() + initial['pks'] = self.request.GET['pks'] + return initial + + def get_preview(self): + entities = self.get_queryset() + joined_entities = '\n'.join(str(e) for e in entities) + return f"{self.confirmation_message}:\n\n {joined_entities}" + + def get_success_url(self): + return reverse(f"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_changelist") + + @abstractmethod + def perform_action(self, entity): + pass + + def form_valid(self, form): + entities = self.get_queryset(pks=form.cleaned_data['pks']) + for entity in entities: + self.perform_action(entity) + entity.save() + messages.add_message(self.request, messages.SUCCESS, self.success_message) + return super().form_valid(form) + + +class CVMarkResolvedView(IntermediateAdminActionView): + title = _('Mark Constraint Violations as manually resolved') + model = ConstraintViolation + confirmation_message = _("The following Constraint Violations will be marked as manually resolved") + success_message = _("Constraint Violations marked as resolved") + + def perform_action(self, entity): + entity.manually_resolved = True + + +class CVSetLevelViolationView(IntermediateAdminActionView): + title = _('Set Constraint Violations to level "violation"') + model = ConstraintViolation + confirmation_message = _("The following Constraint Violations will be set to level 'violation'") + success_message = _("Constraint Violations set to level 'violation'") + + def perform_action(self, entity): + entity.level = ConstraintViolation.ViolationLevel.VIOLATION + + +class CVSetLevelWarningView(IntermediateAdminActionView): + title = _('Set Constraint Violations to level "warning"') + model = ConstraintViolation + confirmation_message = _("The following Constraint Violations will be set to level 'warning'") + success_message = _("Constraint Violations set to level 'warning'") + + def perform_action(self, entity): + entity.level = ConstraintViolation.ViolationLevel.WARNING