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