diff --git a/AKModel/admin.py b/AKModel/admin.py index 39fc678db42611d3b2192a006179a46524805f6a..61a590abcd0b1a09f5c5cbdec196aec59090320c 100644 --- a/AKModel/admin.py +++ b/AKModel/admin.py @@ -4,8 +4,9 @@ from django.contrib import admin, messages from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter, action from django.db.models import Count, F from django.db.models.functions import Now +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 @@ -18,6 +19,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): @@ -329,3 +331,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 8554efd9b65c5fc03dbf74960db7eaac5ac3505a..3aa9486813bc9fb91ee21860fc8614bb0ff96cfa 100644 --- a/AKModel/forms.py +++ b/AKModel/forms.py @@ -75,6 +75,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/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