Skip to content
Snippets Groups Projects
Forked from KIF / AKPlanning
192 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
views.py 18.67 KiB
from datetime import timedelta
from math import floor

from django.apps import apps
from django.conf import settings
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.utils.datetime_safe import datetime
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView

from AKModel.availability.models import Availability
from AKModel.metaviews import status_manager
from AKModel.metaviews.status import TemplateStatusWidget
from AKModel.models import AK, AKCategory, AKOwner, AKSlot, AKTrack, AKOrgaMessage
from AKModel.metaviews.admin import EventSlugMixin, FilterByEventSlugMixin
from AKSubmission.api import ak_interest_indication_active
from AKSubmission.forms import AKWishForm, AKOwnerForm, AKSubmissionForm, AKDurationForm, AKOrgaMessageForm, \
    AKForm


class SubmissionErrorNotConfiguredView(EventSlugMixin, TemplateView):
    template_name = "AKSubmission/submission_not_configured.html"


class AKOverviewView(FilterByEventSlugMixin, ListView):
    model = AKCategory
    context_object_name = "categories"
    template_name = "AKSubmission/ak_overview.html"
    wishes_as_category = False

    def filter_aks(self, context, category):
        return category.ak_set.select_related('event').prefetch_related('owners').all()

    def get_active_category_name(self, context):
        return context["categories_with_aks"][0][0].name

    def get_table_title(self, context):
        return _("All AKs")

    def get(self, request, *args, **kwargs):
        self._load_event()
        self.object_list = self.get_queryset()

        # No categories yet? Redirect to configuration error page
        if self.object_list.count() == 0:
            return redirect(reverse_lazy("submit:error_not_configured", kwargs={'event_slug': self.event.slug}))

        context = self.get_context_data()
        return self.render_to_response(context)

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)

        # Sort AKs into different lists (by their category)
        ak_wishes = []
        categories_with_aks = []

        for category in context["categories"]:
            aks_for_category = []
            for ak in self.filter_aks(context, category):
                if self.wishes_as_category and ak.wish:
                    ak_wishes.append(ak)
                else:
                    aks_for_category.append(ak)
            categories_with_aks.append((category, aks_for_category))

        if self.wishes_as_category:
            categories_with_aks.append(
                (AKCategory(name=_("Wishes"), pk=0, description=_("AKs one would like to have")), ak_wishes))

        context["categories_with_aks"] = categories_with_aks
        context["active_category"] = self.get_active_category_name(context)
        context['table_title'] = self.get_table_title(context)

        # Display interest indication button?
        current_timestamp = datetime.now().astimezone(self.event.timezone)
        context['interest_indication_active'] = ak_interest_indication_active(self.event, current_timestamp)

        return context


class SubmissionOverviewView(AKOverviewView):
    model = AKCategory
    context_object_name = "categories"
    template_name = "AKSubmission/submission_overview.html"
    wishes_as_category = settings.WISHES_AS_CATEGORY

    def get_table_title(self, context):
        return _("Currently planned AKs")

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)

        # Get list of existing owners for event (for AK submission start)
        context["existingOwners"] = AKOwner.objects.filter(event=self.event)

        return context


class AKListByCategoryView(AKOverviewView):
    def dispatch(self, request, *args, **kwargs):
        self.category = get_object_or_404(AKCategory, pk=kwargs['category_pk'])
        return super().dispatch(request, *args, **kwargs)

    def get_active_category_name(self, context):
        return self.category.name


class AKListByTrackView(AKOverviewView):
    def dispatch(self, request, *args, **kwargs):
        self.track = get_object_or_404(AKTrack, pk=kwargs['track_pk'])
        return super().dispatch(request, *args, **kwargs)

    def filter_aks(self, context, category):
        return category.ak_set.filter(track=self.track)

    def get_table_title(self, context):
        return f"{_('AKs with Track')} = {self.track.name}"


class AKDetailView(EventSlugMixin, DetailView):
    model = AK
    context_object_name = "ak"
    template_name = "AKSubmission/ak_detail.html"

    def get_queryset(self):
        return super().get_queryset().select_related('event').prefetch_related('owners')

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)
        context["availabilities"] = Availability.objects.filter(ak=context["ak"])

        current_timestamp = datetime.now().astimezone(self.event.timezone)

        # Is this AK taking place now or soon (used for top page visualization)
        context["featured_slot_type"] = "NONE"
        if apps.is_installed("AKPlan"):
            in_two_hours = current_timestamp + timedelta(hours=2)
            slots = context["ak"].akslot_set.filter(start__isnull=False, room__isnull=False).select_related('room')
            for slot in slots:
                if slot.end > current_timestamp:
                    if slot.start <= current_timestamp:
                        context["featured_slot_type"] = "CURRENT"
                        remaining = slot.end - current_timestamp
                    elif slot.start <= in_two_hours:
                        context["featured_slot_type"] = "UPCOMING"
                        remaining = slot.start - current_timestamp
                    else:
                        continue

                    context["featured_slot"] = slot
                    context["featured_slot_remaining"] = floor(remaining.days * 24 * 60 + remaining.seconds / 60)
                    break

        # Display interest indication button?
        context['interest_indication_active'] = ak_interest_indication_active(self.event, current_timestamp)

        return context


class AKHistoryView(EventSlugMixin, DetailView):
    model = AK
    context_object_name = "ak"
    template_name = "AKSubmission/ak_history.html"


class AKListView(FilterByEventSlugMixin, ListView):
    model = AK
    context_object_name = "AKs"
    template_name = "AKSubmission/ak_overview.html"
    table_title = ""

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)
        context['categories'] = AKCategory.objects.filter(event=self.event)
        context['tracks'] = AKTrack.objects.filter(event=self.event)
        return context


class EventInactiveRedirectMixin:
    def get_error_message(self):
        return _("Event inactive. Cannot create or update.")

    def get(self, request, *args, **kwargs):
        s = super().get(request, *args, **kwargs)
        if not self.event.active:
            messages.add_message(self.request, messages.ERROR, self.get_error_message())
            return redirect(reverse_lazy('submit:submission_overview', kwargs={'event_slug': self.event.slug}))
        return s


class AKAndAKWishSubmissionView(EventSlugMixin, EventInactiveRedirectMixin, CreateView):
    model = AK
    template_name = 'AKSubmission/submit_new.html'
    form_class = AKSubmissionForm

    def get_success_url(self):
        messages.add_message(self.request, messages.SUCCESS, _("AK successfully created"))
        return self.object.detail_url

    def form_valid(self, form):
        if not form.cleaned_data["event"].active:
            messages.add_message(self.request, messages.ERROR, self.get_error_message())
            return redirect(reverse_lazy('submit:submission_overview',
                                         kwargs={'event_slug': form.cleaned_data["event"].slug}))

        # Try to save AK and get redirect URL
        super_form_valid = super().form_valid(form)

        # Generate slot(s) (but not for wishes)
        if "durations" in form.cleaned_data:
            for duration in form.cleaned_data["durations"]:
                new_slot = AKSlot(ak=self.object, duration=duration, event=self.object.event)
                new_slot.save()

        return super_form_valid


class AKSubmissionView(AKAndAKWishSubmissionView):
    def get_initial(self):
        initials = super(AKAndAKWishSubmissionView, self).get_initial()
        initials['owners'] = [AKOwner.get_by_slug(self.event, self.kwargs['owner_slug'])]
        initials['event'] = self.event
        return initials

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)
        context['owner'] = get_object_or_404(AKOwner, event=self.event, slug=self.kwargs['owner_slug'])
        return context


class AKWishSubmissionView(AKAndAKWishSubmissionView):
    template_name = 'AKSubmission/submit_new_wish.html'
    form_class = AKWishForm

    def get_initial(self):
        initials = super(AKAndAKWishSubmissionView, self).get_initial()
        initials['event'] = self.event
        return initials


class AKEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView):
    model = AK
    template_name = 'AKSubmission/ak_edit.html'
    form_class = AKForm

    def get_success_url(self):
        messages.add_message(self.request, messages.SUCCESS, _("AK successfully updated"))
        return self.object.detail_url

    def form_valid(self, form):
        if not form.cleaned_data["event"].active:
            messages.add_message(self.request, messages.ERROR, self.get_error_message())
            return redirect(reverse_lazy('submit:submission_overview',
                                         kwargs={'event_slug': form.cleaned_data["event"].slug}))

        previous_owner_count = self.object.owners.count()

        super_form_valid = super().form_valid(form)

        # Did this AK change from wish to AK or vice versa?
        new_owner_count = self.object.owners.count()
        # Now AK:
        if previous_owner_count == 0 and new_owner_count > 0 and self.object.akslot_set.count() == 0:
            # Create one slot with default length
            AKSlot.objects.create(ak=self.object, duration=self.object.event.default_slot, event=self.object.event)
        # Now wish:
        elif previous_owner_count > 0 and new_owner_count == 0:
            # Delete all unscheduled slots
            self.object.akslot_set.filter(start__isnull=True).delete()

        return super_form_valid


class AKOwnerCreateView(EventSlugMixin, EventInactiveRedirectMixin, CreateView):
    model = AKOwner
    template_name = 'AKSubmission/akowner_create_update.html'
    form_class = AKOwnerForm

    def get_success_url(self):
        if "add_to_existing_ak" in self.request.GET:
            ak_pk = self.request.GET['add_to_existing_ak']
            ak = get_object_or_404(AK, pk=ak_pk)
            ak.owners.add(self.object)
            messages.add_message(self.request, messages.SUCCESS,
                                 _("Added '{owner}' as new owner of '{ak.name}'").format(owner=self.object, ak=ak))
            return ak.detail_url
        return reverse_lazy('submit:submit_ak',
                            kwargs={'event_slug': self.kwargs['event_slug'], 'owner_slug': self.object.slug})

    def get_initial(self):
        initials = super(AKOwnerCreateView, self).get_initial()
        initials['event'] = self.event
        return initials

    def form_valid(self, form):
        if not form.cleaned_data["event"].active:
            messages.add_message(self.request, messages.ERROR, self.get_error_message())
            return redirect(reverse_lazy('submit:submission_overview',
                                         kwargs={'event_slug': form.cleaned_data["event"].slug}))
        return super().form_valid(form)


class AKOwnerSelectDispatchView(EventSlugMixin, View):
    """
    This view only serves as redirect to prepopulate the owners field in submission create view
    """

    def post(self, request, *args, **kwargs):
        if "owner_id" not in request.POST:
            return redirect('submit:submission_overview', event_slug=kwargs['event_slug'])
        owner_id = request.POST["owner_id"]

        if owner_id == "-1":
            return HttpResponseRedirect(
                reverse_lazy('submit:akowner_create', kwargs={'event_slug': kwargs['event_slug']}))

        owner = get_object_or_404(AKOwner, pk=request.POST["owner_id"])
        return HttpResponseRedirect(
            reverse_lazy('submit:submit_ak', kwargs={'event_slug': kwargs['event_slug'], 'owner_slug': owner.slug}))


class AKOwnerEditView(FilterByEventSlugMixin, EventSlugMixin, UpdateView):
    model = AKOwner
    template_name = "AKSubmission/akowner_create_update.html"
    form_class = AKOwnerForm

    def get_success_url(self):
        messages.add_message(self.request, messages.SUCCESS, _("Person Info successfully updated"))
        return reverse_lazy('submit:submission_overview', kwargs={'event_slug': self.kwargs['event_slug']})

    def form_valid(self, form):
        if not form.cleaned_data["event"].active:
            messages.add_message(self.request, messages.ERROR, self.get_error_message())
            return redirect(reverse_lazy('submit:submission_overview',
                                         kwargs={'event_slug': form.cleaned_data["event"].slug}))
        return super().form_valid(form)


class AKOwnerEditDispatchView(EventSlugMixin, View):
    """
    This view only serves as redirect choose the correct edit view
    """

    def post(self, request, *args, **kwargs):
        if "owner_id" not in request.POST:
            return redirect('submit:submission_overview', event_slug=kwargs['event_slug'])
        owner_id = request.POST["owner_id"]

        if owner_id == "-1":
            messages.add_message(self.request, messages.WARNING, _("No user selected"))
            return HttpResponseRedirect(
                reverse_lazy('submit:submission_overview', kwargs={'event_slug': kwargs['event_slug']}))

        owner = get_object_or_404(AKOwner, pk=request.POST["owner_id"])
        return HttpResponseRedirect(
            reverse_lazy('submit:akowner_edit', kwargs={'event_slug': kwargs['event_slug'], 'slug': owner.slug}))


class AKSlotAddView(EventSlugMixin, EventInactiveRedirectMixin, CreateView):
    model = AKSlot
    form_class = AKDurationForm
    template_name = "AKSubmission/akslot_add_update.html"

    def get_initial(self):
        initials = super(AKSlotAddView, self).get_initial()
        initials['event'] = self.event
        initials['ak'] = get_object_or_404(AK, pk=self.kwargs['pk'])
        initials['duration'] = self.event.default_slot
        return initials

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)
        context['ak'] = get_object_or_404(AK, pk=self.kwargs['pk'])
        return context

    def get_success_url(self):
        messages.add_message(self.request, messages.SUCCESS, _("AK Slot successfully added"))
        return self.object.ak.detail_url


class AKSlotEditView(EventSlugMixin, EventInactiveRedirectMixin, UpdateView):
    model = AKSlot
    form_class = AKDurationForm
    template_name = "AKSubmission/akslot_add_update.html"

    def get(self, request, *args, **kwargs):
        akslot = get_object_or_404(AKSlot, pk=kwargs["pk"])
        if akslot.start is not None:
            messages.add_message(self.request, messages.WARNING,
                                 _("You cannot edit a slot that has already been scheduled"))
            return HttpResponseRedirect(akslot.ak.detail_url)
        return super().get(request, *args, **kwargs)

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)
        context['ak'] = self.object.ak
        return context

    def get_success_url(self):
        messages.add_message(self.request, messages.SUCCESS, _("AK Slot successfully updated"))
        return self.object.ak.detail_url


class AKSlotDeleteView(EventSlugMixin, EventInactiveRedirectMixin, DeleteView):
    model = AKSlot
    template_name = "AKSubmission/akslot_delete.html"

    def get(self, request, *args, **kwargs):
        akslot = get_object_or_404(AKSlot, pk=kwargs["pk"])
        if akslot.start is not None:
            messages.add_message(self.request, messages.WARNING,
                                 _("You cannot delete a slot that has already been scheduled"))
            return HttpResponseRedirect(akslot.ak.detail_url)
        return super().get(request, *args, **kwargs)

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)
        context['ak'] = self.object.ak
        return context

    def get_success_url(self):
        messages.add_message(self.request, messages.SUCCESS, _("AK Slot successfully deleted"))
        return self.object.ak.detail_url


@status_manager.register(name="event_ak_messages")
class EventAKMessagesWidget(TemplateStatusWidget):
    required_context_type = "event"
    title = _("Messages")
    template_name = "admin/AKModel/render_ak_messages.html"

    def get_context_data(self, context) -> dict:
        context["ak_messages"] = AKOrgaMessage.objects.filter(ak__event=context["event"])
        return context

    def render_actions(self, context: {}) -> list[dict]:
        return [
            {
                "text": _("Delete all messages"),
                "url": reverse_lazy("admin:ak_delete_orga_messages", kwargs={"event_slug": context["event"].slug}),
            },
        ]


class AKAddOrgaMessageView(EventSlugMixin, CreateView):
    model = AKOrgaMessage
    form_class = AKOrgaMessageForm
    template_name = "AKSubmission/akmessage_add.html"

    def get_initial(self):
        initials = super(AKAddOrgaMessageView, self).get_initial()
        initials['ak'] = get_object_or_404(AK, pk=self.kwargs['pk'])
        initials['event'] = initials['ak'].event
        return initials

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(object_list=object_list, **kwargs)
        context['ak'] = get_object_or_404(AK, pk=self.kwargs['pk'])
        return context

    def get_success_url(self):
        messages.add_message(self.request, messages.SUCCESS, _("Message to organizers successfully saved"))
        return self.object.ak.detail_url