Skip to content
Snippets Groups Projects
views.py 11.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • from django.contrib import messages
    
    from django.contrib.messages.views import SuccessMessageMixin
    
    from django.db.models import Count
    
    from django.urls import reverse_lazy
    
    from django.utils.translation import gettext_lazy as _
    
    from django.views.generic import ListView, DetailView, UpdateView
    
    from AKModel.models import AKSlot, AKTrack, Event, AK, AKCategory
    
    from AKModel.metaviews.admin import EventSlugMixin, FilterByEventSlugMixin, AdminViewMixin, IntermediateAdminView
    
    from AKScheduling.forms import AKInterestForm, AKAddSlotForm
    
    
    
    class UnscheduledSlotsAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
    
        """
        Admin view: Get a list of all unscheduled slots
        """
    
        template_name = "admin/AKScheduling/unscheduled.html"
        model = AKSlot
        context_object_name = "akslots"
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["title"] = f"{_('Unscheduled AK Slots')} for {context['event']}"
            return context
    
        def get_queryset(self):
            return super().get_queryset().filter(start=None)
    
    
    
    class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
    
        """
        Admin view: Scheduler
    
        View and adapt the schedule of an event. This view heavily uses JavaScript to display a calendar view plus
        a list of unscheduled slots and to allow dragging slots in and into the calendar
        """
    
        template_name = "admin/AKScheduling/scheduling.html"
        model = AKSlot
    
        context_object_name = "slots_unscheduled"
    
        def get_queryset(self):
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            return super().get_queryset().filter(start__isnull=True).select_related('event', 'ak').order_by('ak__track')
    
    
        def get_context_data(self, *, object_list=None, **kwargs):
            context = super().get_context_data(object_list=object_list, **kwargs)
            context["title"] = f"{_('Scheduling')} for {context['event']}"
    
            context["event"] = self.event
            context["start"] = self.event.start
            context["end"] = self.event.end
    
    
            context["akSlotAddForm"] = AKAddSlotForm(self.event)
    
    
    
    
    class TrackAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
    
        """
        Admin view: Distribute AKs to tracks
    
        Again using JavaScript, the user can here see a list of all AKs split-up by tracks and can move them to other or
        even new tracks using drag and drop. The state is then automatically synchronized via API calls in the background
        """
    
        template_name = "admin/AKScheduling/manage_tracks.html"
        model = AKTrack
        context_object_name = "tracks"
    
        def get_context_data(self, *, object_list=None, **kwargs):
            context = super().get_context_data(object_list=object_list, **kwargs)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            context["aks_without_track"] = self.event.ak_set.select_related('category').filter(track=None)
    
            return context
    
    
    
    class ConstraintViolationsAdminView(AdminViewMixin, DetailView):
    
        """
        Admin view: Inspect and adjust all constraint violations of the event
    
        This view populates a table of constraint violations via background API call (JavaScript), offers the option to
        see details or edit each of them and provides an auto-reload feature.
        """
    
        template_name = "admin/AKScheduling/constraint_violations.html"
        model = Event
        context_object_name = "event"
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["title"] = f"{_('Constraint violations for')} {context['event']}"
            return context
    
    
    
    class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
    
        """
        Admin view: List all AKs that require special attention via scheduling, e.g., because of free-form comments,
        since there are slots even though it is a wish, or no slots even though it is an AK etc.
        """
    
        template_name = "admin/AKScheduling/special_attention.html"
        model = Event
        context_object_name = "event"
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["title"] = f"{_('AKs requiring special attention for')} {context['event']}"
    
    
            # Load all "special" AKs from the database using annotations to reduce the amount of necessary queries
            aks = (AK.objects.filter(event=context["event"]).annotate(Count('owners', distinct=True))
                   .annotate(Count('akslot', distinct=True)).annotate(Count('availabilities', distinct=True)))
    
            aks_with_comment = []
            ak_wishes_with_slots = []
            aks_without_availabilities = []
            aks_without_slots = []
    
    
            # Loop over all AKs of this event and identify all relevant factors that make the AK "special" and add them to
            # the respective lists if the AK fullfills an condition
    
            for ak in aks:
                if ak.notes != "":
                    aks_with_comment.append(ak)
    
                if ak.owners__count == 0:
                    if ak.akslot__count > 0:
    
                        ak_wishes_with_slots.append(ak)
                else:
    
                    if ak.akslot__count == 0:
    
                        aks_without_slots.append(ak)
    
                    if ak.availabilities__count == 0:
    
                        aks_without_availabilities.append(ak)
    
    
            context["aks_with_comment"] = aks_with_comment
            context["ak_wishes_with_slots"] = ak_wishes_with_slots
            context["aks_without_slots"] = aks_without_slots
            context["aks_without_availabilities"] = aks_without_availabilities
    
            return context
    
    
    
    class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMixin, UpdateView):
    
        """
        Admin view: Form view to quickly store information about the interest in an AK
        (e.g., during presentation of the AK list)
    
        The view offers a field to update interest and manually set a comment for the current AK, but also features links
        to the AKs before and probably coming up next, as well as links to other AKs sorted by category, for quick
        and hazzle-free navigation during the AK presentation
        """
    
        template_name = "admin/AKScheduling/interest.html"
        model = AK
        context_object_name = "ak"
        form_class = AKInterestForm
        success_message = _("Interest updated")
    
        def get_success_url(self):
            return self.request.path
    
        def get_context_data(self, **kwargs):
            context = super().get_context_data(**kwargs)
            context["title"] = f"{_('Enter interest')}"
    
            # Sort AKs into different lists (by their category)
            ak_wishes = []
            categories_with_aks = []
    
            context["previous_ak"] = None
            context["next_ak"] = None
            last_ak = None
            next_is_next = False
    
            # Building the right navigation is a bit tricky since wishes have to be treated as an own category here
            # Hence, depending on the AK we are currently at (displaying the form for) we need to either:
    
            # Find other AK wishes (regardless of the category)...
            if context['ak'].wish:
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                other_aks = [ak for ak in context['event'].ak_set.prefetch_related('owners').all() if ak.wish]
    
            # or other AKs of this category
            else:
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                other_aks = [ak for ak in context['ak'].category.ak_set.prefetch_related('owners').all() if not ak.wish]
    
            # Use that list of other AKs belonging to this category to identify the previous and next AK (if any)
    
            for other_ak in other_aks:
    
                if next_is_next:
                    context['next_ak'] = other_ak
                    next_is_next = False
                elif other_ak.pk == context['ak'].pk :
                    context['previous_ak'] = last_ak
                    next_is_next = True
                last_ak = other_ak
    
    
            # Gather information for link lists for all categories (and wishes)
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
            for category in context['event'].akcategory_set.prefetch_related('ak_set').all():
    
    Benjamin Hättasch's avatar
    Benjamin Hättasch committed
                for ak in category.ak_set.prefetch_related('owners').all():
    
                    if ak.wish:
                        ak_wishes.append(ak)
                    else:
                        aks_for_category.append(ak)
                categories_with_aks.append((category, aks_for_category))
    
    
            # Make sure wishes have the right order (since the list was filled category by category before, this requires
            # explicitly reordering them by their primary key)
    
            ak_wishes.sort(key=lambda x: x.pk)
    
            categories_with_aks.append(
                    (AKCategory(name=_("Wishes"), pk=0, description="-"), ak_wishes))
    
            context["categories_with_aks"] = categories_with_aks
    
            return context
    
    
    
    class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView):
    
        """
        Admin action view: Allow to delete all unscheduled slots for wishes
    
        The view will render a preview of all slots that are affected by this. It is not possible to manually choose
        which slots should be deleted (either all or none) and the functionality will therefore delete slots that were
        created in the time between rendering of the preview and running the action ofter confirmation as well.
    
        Due to the automated slot cleanup functionality for wishes in the AKSubmission app, this functionality should be
        rarely needed/used
        """
    
        title = _('Cleanup: Delete unscheduled slots for wishes')
    
        def get_success_url(self):
            return reverse_lazy('admin:special-attention', kwargs={'slug': self.event.slug})
    
        def get_preview(self):
            slots = self.event.get_unscheduled_wish_slots()
            return _("The following {count} unscheduled slots of wishes will be deleted:\n\n {slots}").format(
                count=len(slots),
                slots=", ".join(str(s.ak) for s in slots)
            )
    
        def form_valid(self, form):
            self.event.get_unscheduled_wish_slots().delete()
            messages.add_message(self.request, messages.SUCCESS, _("Unscheduled slots for wishes successfully deleted"))
            return super().form_valid(form)
    
    
    
    class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView):
    
        """
        Admin action view: Allow to automatically create default availabilities (event start to end) for all AKs without
        any manually specified availability information
    
        The view will render a preview of all AKs that are affected by this. It is not possible to manually choose
        which AKs should be affected (either all or none) and the functionality will therefore create availability entries
        for AKs that were created in the time between rendering of the preview and running the action ofter confirmation
        as well.
        """
    
        title = _('Create default availabilities for AKs')
    
        def get_success_url(self):
            return reverse_lazy('admin:special-attention', kwargs={'slug': self.event.slug})
    
        def get_preview(self):
            aks = self.event.get_aks_without_availabilities()
            return _("The following {count} AKs don't have any availability information. "
                     "Create default availability for them:\n\n {aks}").format(
                count=len(aks),
                aks=", ".join(str(ak) for ak in aks)
            )
    
        def form_valid(self, form):
    
            # Local import to prevent cyclic imports
            # pylint: disable=import-outside-toplevel
    
            from AKModel.availability.models import Availability
    
            success_count = 0
            for ak in self.event.get_aks_without_availabilities():
                try:
                    availability = Availability.with_event_length(event=self.event, ak=ak)
                    availability.save()
                    success_count += 1
    
                except: # pylint: disable=bare-except
    
                    messages.add_message(
                        self.request, messages.WARNING,
                        _("Could not create default availabilities for AK: {ak}").format(ak=ak)
                    )
    
            messages.add_message(
                self.request, messages.SUCCESS,
                _("Created default availabilities for {count} AKs").format(count=success_count)
            )
            return super().form_valid(form)