import json from typing import List from django.contrib import messages from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views.generic import ListView, DetailView from AKModel.availability.models import Availability from AKModel.metaviews.admin import AdminViewMixin, FilterByEventSlugMixin, EventSlugMixin, IntermediateAdminView, \ IntermediateAdminActionView from AKModel.models import AKRequirement, AKSlot, DefaultSlot, Event, AKOrgaMessage, AK, Room, AKOwner, merge_blocks class AKRequirementOverview(AdminViewMixin, FilterByEventSlugMixin, ListView): """ View: Display requirements for the given event """ model = AKRequirement context_object_name = "requirements" title = _("Requirements for Event") template_name = "admin/AKModel/requirements_overview.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["event"] = self.event context["site_url"] = reverse_lazy("dashboard:dashboard_event", kwargs={'slug': context["event"].slug}) return context class AKCSVExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): """ View: Export all AK slots of this event in CSV format ordered by tracks """ template_name = "admin/AKModel/ak_csv_export.html" model = AKSlot context_object_name = "slots" title = _("AK CSV Export") def get_queryset(self): return super().get_queryset().order_by("ak__track") class AKJSONExportView(AdminViewMixin, FilterByEventSlugMixin, ListView): """ View: Export all AK slots of this event in JSON format ordered by tracks """ template_name = "admin/AKModel/ak_json_export.html" model = AKSlot context_object_name = "slots" title = _("AK JSON Export") def _test_slot_contained(self, slot: Availability, availabilities: List[Availability]) -> bool: """Test if slot is contained in any member of availabilities.""" return any(availability.contains(slot) for availability in availabilities) def _test_event_not_covered(self, availabilities: List[Availability]) -> bool: """Test if event is not covered by availabilities.""" return not Availability.is_event_covered(self.event, availabilities) def _test_ak_fixed_in_slot(self, ak_id, slot: Availability, ak_fixed: dict) -> bool: """Test if AK defined by `ak_id` is fixed to happen during slot.""" if not ak_id in ak_fixed: return False fixed_slot = Availability(self.event, start=ak_fixed[ak_id].start, end=ak_fixed[ak_id].end) return fixed_slot.overlaps(slot, strict=True) def _test_add_constraint(self, slot: Availability, availabilities: List[Availability]) -> bool: """Test if object is not available for whole event and may happen during slot.""" return ( self._test_event_not_covered(availabilities) and self._test_slot_contained(slot, availabilities) ) def get_queryset(self): return super().get_queryset().order_by("ak__track") def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["participants"] = json.dumps([]) rooms = Room.objects.filter(event=self.event) context["rooms"] = rooms # TODO: Configure magic number in event SLOTS_IN_AN_HOUR = 1 timeslots = { "info": {"duration": (1.0 / SLOTS_IN_AN_HOUR), }, "blocks": [], } for slot in context["slots"]: slot.slots_in_an_hour = SLOTS_IN_AN_HOUR ak_availabilities = { slot.ak.pk: Availability.union(slot.ak.availabilities.all()) for slot in context["slots"] } room_availabilities = { room.pk: Availability.union(room.availabilities.all()) for room in rooms } person_availabilities = { person.pk: Availability.union(person.availabilities.all()) for person in AKOwner.objects.filter(event=self.event) } ak_fixed = { ak_id: values.get() for ak_id in ak_availabilities.keys() if (values := AKSlot.objects.select_related().filter(ak__pk=ak_id, fixed=True)).exists() } if DefaultSlot.objects.filter(event=self.event).exists(): # discretize default slots if they exists blocks = merge_blocks(self.event.default_time_slots(slots_in_an_hour=SLOTS_IN_AN_HOUR)) else: blocks = self.event.uniform_time_slots(slos_in_an_hour=SLOTS_IN_AN_HOUR) for block in blocks: current_block = [] for timeslot in block: time_constraints = [] # if reso_deadline is set and timeslot ends before it, # add fulfilled time constraint 'resolution' if self.event.reso_deadline is None or timeslot.avail.end < self.event.reso_deadline: time_constraints.append("resolution") # add fulfilled time constraints for all AKs that cannot happen during full event time_constraints.extend([ f"availability-ak-{ak_id}" for ak_id, availabilities in ak_availabilities.items() if ( self._test_add_constraint(timeslot.avail, availabilities) or self._test_ak_fixed_in_slot(ak_id, timeslot.avail, ak_fixed) ) ]) # add fulfilled time constraints for all persons that are not available for full event time_constraints.extend([ f"availability-person-{person_id}" for person_id, availabilities in person_availabilities.items() if self._test_add_constraint(timeslot.avail, availabilities) ]) # add fulfilled time constraints for all rooms that are not available for full event time_constraints.extend([ f"availability-room-{room_id}" for room_id, availabilities in room_availabilities.items() if self._test_add_constraint(timeslot.avail, availabilities) ]) time_constraints.extend(timeslot.constraints) current_block.append({ "id": str(timeslot.idx), "info": { "start": timeslot.avail.simplified, }, "fulfilled_time_constraints": time_constraints, }) timeslots["blocks"].append(current_block) context["timeslots"] = json.dumps(timeslots) info_dict = { "title": self.event.name, "slug": self.event.slug } for attr in ["contact_email", "place"]: if hasattr(self.event, attr) and getattr(self.event, attr): info_dict[attr] = getattr(self.event, attr) context["info_dict"] = json.dumps(info_dict) return context class AKWikiExportView(AdminViewMixin, DetailView): """ View: Export AKs of this event in wiki syntax This will show one text field per category, with a separate category/field for wishes """ template_name = "admin/AKModel/wiki_export.html" model = Event context_object_name = "event" title = _("AK Wiki Export") def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) categories_with_aks, ak_wishes = context["event"].get_categories_with_aks( wishes_seperately=True, filter_func=lambda ak: ak.include_in_export ) context["categories_with_aks"] = [(category.name, ak_list) for category, ak_list in categories_with_aks] context["categories_with_aks"].append((_("Wishes"), ak_wishes)) return context class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView): """ View: Confirmation page to delete confidential AK-related messages to orga Confirmation functionality provided by :class:`AKModel.metaviews.admin.IntermediateAdminView` """ template_name = "admin/AKModel/message_delete.html" title = _("Delete AK Orga Messages") def get_orga_messages_for_event(self, event): """ Get all orga messages for the given event """ return AKOrgaMessage.objects.filter(ak__event=event) def get_success_url(self): return reverse_lazy('admin:event_status', kwargs={'slug': self.event.slug}) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["ak_messages"] = self.get_orga_messages_for_event(self.event) return context def form_valid(self, form): self.get_orga_messages_for_event(self.event).delete() messages.add_message(self.request, messages.SUCCESS, _("AK Orga Messages successfully deleted")) return super().form_valid(form) class AKResetInterestView(IntermediateAdminActionView): """ View: Confirmation page to reset all manually specified interest values Confirmation functionality provided by :class:`AKModel.metaviews.admin.IntermediateAdminView` """ title = _("Reset interest in AKs") model = AK confirmation_message = _("Interest of the following AKs will be set to not filled (-1):") success_message = _("Reset of interest in AKs successful.") def action(self, form): self.entities.update(interest=-1) class AKResetInterestCounterView(IntermediateAdminActionView): """ View: Confirmation page to reset all interest counters (online interest indication) Confirmation functionality provided by :class:`AKModel.metaviews.admin.IntermediateAdminView` """ title = _("Reset AKs' interest counters") model = AK confirmation_message = _("Interest counter of the following AKs will be set to 0:") success_message = _("AKs' interest counters set back to 0.") def action(self, form): self.entities.update(interest_counter=0)