Skip to content
Snippets Groups Projects
admin.py 22.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • from django import forms
    
    from django.apps import apps
    
    from django.contrib import admin, messages
    
    from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter, action, display
    
    from django.db.models import Count, F
    
    from django.http import HttpResponseRedirect
    
    from django.shortcuts import render, redirect
    
    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
    
    from django.utils.translation import gettext_lazy as _
    
    Nadja Geisler's avatar
    Nadja Geisler committed
    from simple_history.admin import SimpleHistoryAdmin
    
    from AKModel.availability.models import Availability
    
    from AKModel.forms import RoomFormWithAvailabilities
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        ConstraintViolation, DefaultSlot, AKType
    
    from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event
    
    from AKModel.views.ak import AKResetInterestView, AKResetInterestCounterView
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    from AKModel.views.manage import CVMarkResolvedView, CVSetLevelViolationView, CVSetLevelWarningView
    
    class EventRelatedFieldListFilter(RelatedFieldListFilter):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Reusable filter to restrict the possible choices of a field to those belonging to a certain event
        as specified in the event__id__exact GET parameter.
        The choices are only restricted if this parameter is present, otherwise all choices are used/returned
        """
    
        def field_choices(self, field, request, model_admin):
            ordering = self.field_admin_ordering(field, request, model_admin)
            limit_choices = {}
            if "event__id__exact" in request.GET:
                limit_choices['event__id__exact'] = request.GET["event__id__exact"]
            return field.get_choices(include_blank=False, limit_choices_to=limit_choices, ordering=ordering)
    
    
    
    @admin.register(Event)
    class EventAdmin(admin.ModelAdmin):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Admin interface for Event
    
        This allows to edit most fields of an event, some can only be changed by admin actions, since they have side effects
    
        This admin interface registers additional views as defined in urls.py, the wizard, and the full scheduling
        functionality if the AKScheduling app is active.
    
        The interface overrides the built-in creation interface for a new event and replaces it with the event creation
        wizard.
        """
    
        model = Event
    
        list_display = ['name', 'status_url', 'place', 'start', 'end', 'active', 'plan_hidden']
    
        list_filter = ['active']
        list_editable = ['active']
    
        ordering = ['-start']
    
        readonly_fields = ['status_url', 'plan_hidden', 'plan_published_at', 'toggle_plan_visibility']
    
        actions = ['publish', 'unpublish']
    
        def add_view(self, request, form_url='', extra_context=None):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            # Override
            # Always use wizard to create new events (the built-in form wouldn't work anyway since the timezone cannot
    
            # be specified before starting to fill the form)
    
            return redirect("admin:new_event_wizard_start")
    
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Get all event-related URLs
            This will be both the built-in URLs and additional views providing additional functionality
            :return: list of all relevant URLs
            :rtype: List[path]
            """
            # Load wizard URLs and the additional URLs defined in urls.py
            # (first, to have the highest priority when overriding views)
    
            urls = get_admin_urls_event_wizard(self.admin_site)
            urls.extend(get_admin_urls_event(self.admin_site))
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    
            # Make scheduling admin views available if app is active
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                from AKScheduling.urls import get_admin_urls_scheduling  # pylint: disable=import-outside-toplevel
    
                urls.extend(get_admin_urls_scheduling(self.admin_site))
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    
            # Make sure built-in URLs are available as well
    
            urls.extend(super().get_urls())
            return urls
    
        @display(description=_("Status"))
    
        def status_url(self, obj):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Define a read-only field to go to the status page of the event
    
            :param obj: the event to link
            :return: status page link (HTML)
            :rtype: str
            """
    
            return format_html("<a href='{url}'>{text}</a>",
    
                               url=reverse_lazy('admin:event_status', kwargs={'event_slug': obj.slug}), text=_("Status"))
    
        @display(description=_("Toggle plan visibility"))
        def toggle_plan_visibility(self, obj):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Define a read-only field to toggle the visibility of the plan of this event
            This will choose from two different link targets/views depending on the current visibility status
    
            :param obj: event to change the visibility of the plan for
            :return: toggling link (HTML)
            :rtype: str
            """
    
            if obj.plan_hidden:
                url = f"{reverse_lazy('admin:plan-publish')}?pks={obj.pk}"
                text = _('Publish plan')
            else:
                url = f"{reverse_lazy('admin:plan-unpublish')}?pks={obj.pk}"
                text = _('Unpublish plan')
            return format_html("<a href='{url}'>{text}</a>", url=url, text=text)
    
    
        def get_form(self, request, obj=None, change=False, **kwargs):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            # Override (update) form rendering to make sure the timezone of the event is used
    
            return super().get_form(request, obj, change, **kwargs)
    
        @action(description=_('Publish plan'))
        def publish(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Admin action to publish the plan
            """
    
            selected = queryset.values_list('pk', flat=True)
            return HttpResponseRedirect(f"{reverse_lazy('admin:plan-publish')}?pks={','.join(str(pk) for pk in selected)}")
    
    
        @action(description=_('Unpublish plan'))
        def unpublish(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Admin action to hide the plan
            """
    
            selected = queryset.values_list('pk', flat=True)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return HttpResponseRedirect(
                f"{reverse_lazy('admin:plan-unpublish')}?pks={','.join(str(pk) for pk in selected)}")
    
    
    class PrepopulateWithNextActiveEventMixin:
        """
        Mixin for automated pre-population of the event field
        """
        # pylint: disable=too-few-public-methods
    
        def formfield_for_foreignkey(self, db_field, request, **kwargs):
            """
            Override field generation for foreign key fields to introduce special handling for event fields:
            Pre-populate the event field with the next active event (since that is the most likeliest event to be worked
            on in the admin interface) to make creation of new owners easier
            """
            if db_field.name == 'event':
                kwargs['initial'] = Event.get_next_active()
            return super().formfield_for_foreignkey(db_field, request, **kwargs)
    
    @admin.register(AKOwner)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class AKOwnerAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
        """
        Admin interface for AKOwner
        """
    
        model = AKOwner
    
        list_display = ['name', 'institution', 'event', 'aks_url']
    
        list_editable = []
        ordering = ['name']
    
        readonly_fields = ['aks_url']
    
        @display(description=_("AKs"))
        def aks_url(self, obj):
            """
            Define a read-only field to go to the list of all AKs by this user
    
            :param obj: user
            :return: AK list page link (HTML)
            :rtype: str
            """
            return format_html("<a href='{url}'>{text}</a>",
                               url=reverse_lazy('admin:aks_by_owner', kwargs={'event_slug': obj.event.slug, 'pk': obj.pk}),
                               text=obj.ak_set.count())
    
    @admin.register(AKCategory)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class AKCategoryAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
        """
        Admin interface for AKCategory
        """
    
        model = AKCategory
        list_display = ['name', 'color', 'event']
        list_filter = ['event']
        list_editable = ['color']
        ordering = ['name']
    
    
    @admin.register(AKTrack)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class AKTrackAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
        """
        Admin interface for AKTrack
        """
    
        model = AKTrack
        list_display = ['name', 'color', 'event']
        list_filter = ['event']
        list_editable = ['color']
        ordering = ['name']
    
    
    @admin.register(AKRequirement)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class AKRequirementAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
        """
        Admin interface for AKRequirements
        """
    
        model = AKRequirement
        list_display = ['name', 'event']
        list_filter = ['event']
        list_editable = []
        ordering = ['name']
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    @admin.register(AKType)
    class AKTypeAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
        """
        Admin interface for AKRequirements
        """
        model = AKType
        list_display = ['name', 'event']
        list_filter = ['event']
        list_editable = []
        ordering = ['name']
    
    
    
    class WishFilter(SimpleListFilter):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Re-usable filter for wishes
        """
    
        title = _("Wish")  # a label for our filter
        parameter_name = 'wishes'  # you can put anything here
    
        def lookups(self, request, model_admin):
            # This is where you create filter options; we have two:
            return [
                ('WISH', _("Is wish")),
                ('NO_WISH', _("Is not a wish")),
            ]
    
        def queryset(self, request, queryset):
            annotated_queryset = queryset.annotate(owner_count=Count(F('owners')))
            if self.value() == 'NO_WISH':
                return annotated_queryset.filter(owner_count__gt=0)
            if self.value() == 'WISH':
                return annotated_queryset.filter(owner_count=0)
            return queryset
    
    
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Modified admin form for AKs, to be used in :class:`AKAdmin`
        """
    
        class Meta:
            widgets = {
                'requirements': forms.CheckboxSelectMultiple,
            }
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # Filter possible values for foreign keys & m2m when event is specified
            if hasattr(self.instance, "event") and self.instance.event is not None:
                self.fields["category"].queryset = AKCategory.objects.filter(event=self.instance.event)
                self.fields["track"].queryset = AKTrack.objects.filter(event=self.instance.event)
                self.fields["owners"].queryset = AKOwner.objects.filter(event=self.instance.event)
                self.fields["requirements"].queryset = AKRequirement.objects.filter(event=self.instance.event)
                self.fields["conflicts"].queryset = AK.objects.filter(event=self.instance.event)
                self.fields["prerequisites"].queryset = AK.objects.filter(event=self.instance.event)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                self.fields["types"].queryset = AKType.objects.filter(event=self.instance.event)
    
    @admin.register(AK)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class AKAdmin(PrepopulateWithNextActiveEventMixin, SimpleHistoryAdmin):
        """
        Admin interface for AKs
    
        Uses a modified form (see :class:`AKAdminForm`)
        """
    
    Nadja Geisler's avatar
    Nadja Geisler committed
        model = AK
    
        list_display = ['name', 'short_name', 'category', 'track', 'is_wish', 'interest', 'interest_counter', 'event']
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        list_filter = ['event',
                       WishFilter,
                       ('category', EventRelatedFieldListFilter),
                       ('requirements', EventRelatedFieldListFilter)
                       ]
    
        list_editable = ['short_name', 'track', 'interest_counter']
    
        actions = ['wiki_export', 'reset_interest', 'reset_interest_counter']
    
    Nadja Geisler's avatar
    Nadja Geisler committed
    
    
        @display(boolean=True)
    
    Nadja Geisler's avatar
    Nadja Geisler committed
        def is_wish(self, obj):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Property: Is this AK a wish?
            """
    
    Nadja Geisler's avatar
    Nadja Geisler committed
            return obj.wish
    
    
        @action(description=_("Export to wiki syntax"))
    
        def wiki_export(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Action: Export to wiki syntax
            This will use the wiki export view (therefore, all AKs have to have the same event to correclty handle the
            categories and to prevent accidentially merging AKs from different events in the wiki)
            but restrict the AKs to the ones explicitly selected here.
            """
    
            # Only export when all AKs belong to the same event
            if queryset.values("event").distinct().count() == 1:
                event = queryset.first().event
                pks = set(ak.pk for ak in queryset.all())
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                categories_with_aks = event.get_categories_with_aks(wishes_seperately=False,
                                                                    filter_func=lambda ak: ak.pk in pks,
    
                                                                    hide_empty_categories=True)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                return render(request, 'admin/AKModel/wiki_export.html',
                              context={"categories_with_aks": categories_with_aks})
    
            self.message_user(request, _("Cannot export AKs from more than one event at the same time."), messages.ERROR)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return redirect('admin:AKModel_ak_changelist')
    
        def get_urls(self):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Add additional URLs/views
            Currently used to reset the interest field and interest counter field
            """
    
            urls = [
                path('reset-interest/', AKResetInterestView.as_view(), name="ak-reset-interest"),
                path('reset-interest-counter/', AKResetInterestCounterView.as_view(), name="ak-reset-interest-counter"),
            ]
            urls.extend(super().get_urls())
            return urls
    
    
        @action(description=_("Reset interest in AKs"))
    
        def reset_interest(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Action: Reset interest field for the given AKs
            Will use a typical admin confirmation view flow
            """
    
            selected = queryset.values_list('pk', flat=True)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return HttpResponseRedirect(
                f"{reverse_lazy('admin:ak-reset-interest')}?pks={','.join(str(pk) for pk in selected)}")
    
        @action(description=_("Reset AKs' interest counters"))
    
        def reset_interest_counter(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Action: Reset interest counter field for the given AKs
            Will use a typical admin confirmation view flow
            """
    
            selected = queryset.values_list('pk', flat=True)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return HttpResponseRedirect(
                f"{reverse_lazy('admin:ak-reset-interest-counter')}?pks={','.join(str(pk) for pk in selected)}")
    
    Nadja Geisler's avatar
    Nadja Geisler committed
    
    
    @admin.register(Room)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class RoomAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
        """
        Admin interface for Rooms
        """
    
        model = Room
    
        list_display = ['name', 'location', 'capacity', 'event']
    
        list_filter = ['event', ('properties', EventRelatedFieldListFilter), 'location']
    
        list_editable = []
    
        ordering = ['location', 'name']
    
        change_form_template = "admin/AKModel/room_change_form.html"
    
    
        def add_view(self, request, form_url='', extra_context=None):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            # Override creation view
    
            # Use custom view for room creation (either room form or combined form if virtual rooms are supported)
            return redirect("admin:room-new")
    
    
        def get_form(self, request, obj=None, change=False, **kwargs):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            # Override form creation to use a form that allows to specify availabilites of the room once this room is
            # associated with an event (so not before the first saving) since the timezone information and event start
            # and end are needed to correclty render the calendar
    
            if obj is not None:
    
                return RoomFormWithAvailabilities
    
            return super().get_form(request, obj, change, **kwargs)
    
        def get_urls(self):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Add additional URLs/views
            This is currently used to adapt the creation form behavior, to allow the creation of virtual rooms in-place
            when the support for virtual rooms is turned on (AKOnline app active)
            """
            # pylint: disable=import-outside-toplevel
    
            if apps.is_installed("AKOnline"):
                from AKOnline.views import RoomCreationWithVirtualView as RoomCreationView
            else:
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                from .views.room import RoomCreationView
    
    
            urls = [
                path('new/', self.admin_site.admin_view(RoomCreationView.as_view()), name="room-new"),
            ]
            urls.extend(super().get_urls())
            return urls
    
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class EventTimezoneFormMixin:
        """
        Mixin to enforce the usage of the timezone of the associated event in forms
        """
        # pylint: disable=too-few-public-methods
    
        def get_form(self, request, obj=None, change=False, **kwargs):
            """
            Override form creation, use timezone of associated event
            """
            if obj is not None and obj.event.timezone:
                timezone.activate(obj.event.timezone)
            # No timezone available? Use UTC
            else:
                timezone.activate("UTC")
            return super().get_form(request, obj, change, **kwargs)
    
    
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Modified admin form for AKSlots, to be used in :class:`AKSlotAdmin`
        """
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # Filter possible values for foreign keys when event is specified
            if hasattr(self.instance, "event") and self.instance.event is not None:
                self.fields["ak"].queryset = AK.objects.filter(event=self.instance.event)
                self.fields["room"].queryset = Room.objects.filter(event=self.instance.event)
    
    
    
    @admin.register(AKSlot)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class AKSlotAdmin(EventTimezoneFormMixin, PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
        """
        Admin interface for AKSlots
    
        Uses a modified form (see :class:`AKSlotAdminForm`)
        """
    
        model = AKSlot
        list_display = ['id', 'ak', 'room', 'start', 'duration', 'event']
    
        list_filter = ['event', ('room', EventRelatedFieldListFilter)]
    
        ordering = ['start']
    
        readonly_fields = ['ak_details_link', 'updated']
    
        @display(description=_('AK Details'))
    
        def ak_details_link(self, akslot):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Define a read-only field to link the details of the associated AK
    
            :param obj: the AK to link
            :return: AK detail page page link (HTML)
            :rtype: str
            """
    
            if apps.is_installed("AKSubmission") and akslot.ak is not None:
    
                link = f"<a href='{ akslot.ak.detail_url }'>{str(akslot.ak)}</a>"
                return mark_safe(str(link))
    
        ak_details_link.short_description = _('AK Details')
    
    
    
    @admin.register(Availability)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class AvailabilityAdmin(EventTimezoneFormMixin, admin.ModelAdmin):
        """
        Admin interface for Availabilities
        """
        list_display = ['__str__', 'event']
        list_filter = ['event']
    
    
    
    @admin.register(AKOrgaMessage)
    class AKOrgaMessageAdmin(admin.ModelAdmin):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Admin interface for AKOrgaMessages
        """
    
        list_display = ['timestamp', 'ak', 'text', 'resolved']
    
        list_filter = ['ak__event']
        readonly_fields = ['timestamp', 'ak', 'text']
    
    class ConstraintViolationAdminForm(forms.ModelForm):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Adapted admin form for constraint violations for usage in :class:`ConstraintViolationAdmin`)
        """
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # Filter possible values for foreign keys & m2m when event is specified
            if hasattr(self.instance, "event") and self.instance.event is not None:
                self.fields["ak_owner"].queryset = AKOwner.objects.filter(event=self.instance.event)
                self.fields["room"].queryset = Room.objects.filter(event=self.instance.event)
                self.fields["requirement"].queryset = AKRequirement.objects.filter(event=self.instance.event)
                self.fields["category"].queryset = AKCategory.objects.filter(event=self.instance.event)
                self.fields["aks"].queryset = AK.objects.filter(event=self.instance.event)
                self.fields["ak_slots"].queryset = AKSlot.objects.filter(event=self.instance.event)
    
    
    
    @admin.register(ConstraintViolation)
    class ConstraintViolationAdmin(admin.ModelAdmin):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Admin interface for constraint violations
        Uses an adapted form (see :class:`ConstraintViolationAdminForm`)
        """
    
        list_display = ['type', 'level', 'get_details', 'manually_resolved']
    
        list_filter = ['event']
        readonly_fields = ['timestamp']
    
        actions = ['mark_resolved', 'set_violation', 'set_warning']
    
        def get_urls(self):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Add additional URLs/views to change status and severity of CVs
            """
    
            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
    
    
        @action(description=_("Mark Constraint Violations as manually resolved"))
    
        def mark_resolved(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Action: Mark CV as resolved
            """
    
            selected = queryset.values_list('pk', flat=True)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return HttpResponseRedirect(
                f"{reverse_lazy('admin:cv-mark-resolved')}?pks={','.join(str(pk) for pk in selected)}")
    
        @action(description=_('Set Constraint Violations to level "violation"'))
    
        def set_violation(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Action: Promote CV to level violation
            """
    
            selected = queryset.values_list('pk', flat=True)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return HttpResponseRedirect(
                f"{reverse_lazy('admin:cv-set-violation')}?pks={','.join(str(pk) for pk in selected)}")
    
        @action(description=_('Set Constraint Violations to level "warning"'))
    
        def set_warning(self, request, queryset):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            """
            Action: Set CV to level warning
            """
    
            selected = queryset.values_list('pk', flat=True)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return HttpResponseRedirect(
                f"{reverse_lazy('admin:cv-set-warning')}?pks={','.join(str(pk) for pk in selected)}")
    
    
    
    class DefaultSlotAdminForm(forms.ModelForm):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
        """
        Adapted admin form for DefaultSlot for usage in :class:`DefaultSlotAdmin`
        """
    
        class Meta:
            widgets = {
                'primary_categories': forms.CheckboxSelectMultiple
            }
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # Filter possible values for foreign keys & m2m when event is specified
            if hasattr(self.instance, "event") and self.instance.event is not None:
                self.fields["primary_categories"].queryset = AKCategory.objects.filter(event=self.instance.event)
    
    
    @admin.register(DefaultSlot)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
    class DefaultSlotAdmin(EventTimezoneFormMixin, admin.ModelAdmin):
        """
        Admin interface for default slots
        Uses an adapted form (see :class:`DefaultSlotAdminForm`)
        """
    
        list_display = ['start_simplified', 'end_simplified', 'event']
    
        list_filter = ['event']
        form = DefaultSlotAdminForm