Skip to content
Snippets Groups Projects
Commit 7536ac88 authored by Benjamin Hättasch's avatar Benjamin Hättasch
Browse files

Implement admin actions for constraint violations

Add three admin actions to mark CVs as resolved, or set the level to 'warning' or 'violation' -- each including a preview/confirmation step
Introduce a generic view inheriting from the generic admin intermediate view to handle confirmations for admin actions (performing on multiple, freely selected items)
This implements #154
parent d4f23176
No related branches found
No related tags found
No related merge requests found
......@@ -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"')
......@@ -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,
......
......@@ -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"
......
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment