bornhack-website/src/backoffice/views/program.py

942 lines
32 KiB
Python

import logging
from django import forms
from django.conf import settings
from django.contrib import messages
from django.db.models import Count, Q
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from camps.mixins import CampViewMixin
from program.autoscheduler import AutoScheduler
from program.email import add_event_scheduled_email
from program.mixins import AvailabilityMatrixViewMixin
from program.models import (
Event,
EventLocation,
EventProposal,
EventSession,
EventSlot,
EventType,
Speaker,
SpeakerProposal,
)
from program.utils import save_speaker_availability
from ..forms import (
AutoScheduleApplyForm,
AutoScheduleValidateForm,
EventScheduleForm,
SpeakerForm,
)
from ..mixins import ContentTeamPermissionMixin
logger = logging.getLogger("bornhack.%s" % __name__)
#######################################
# MANAGE SPEAKER/EVENT PROPOSAL VIEWS
class PendingProposalsView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This convenience view shows a list of pending proposals"""
model = SpeakerProposal
template_name = "pending_proposals.html"
context_object_name = "speaker_proposal_list"
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs).filter(proposal_status="pending")
qs = qs.prefetch_related("user", "urls", "speaker")
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["event_proposal_list"] = self.camp.event_proposals.filter(
proposal_status=EventProposal.PROPOSAL_PENDING
).prefetch_related("event_type", "track", "speakers", "tags", "user", "event")
return context
class ProposalApproveBaseView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
"""
Shared logic between SpeakerProposalApproveView and EventProposalApproveView
"""
fields = ["reason"]
def form_valid(self, form):
"""
We have two submit buttons in this form, Approve and Reject
"""
if "approve" in form.data:
# approve button was pressed
form.instance.mark_as_approved(self.request)
elif "reject" in form.data:
# reject button was pressed
form.instance.mark_as_rejected(self.request)
else:
messages.error(self.request, "Unknown submit action")
return redirect(
reverse(
"backoffice:pending_proposals", kwargs={"camp_slug": self.camp.slug}
)
)
class SpeakerProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This view permits Content Team members to list SpeakerProposals"""
model = SpeakerProposal
template_name = "speaker_proposal_list.html"
context_object_name = "speaker_proposal_list"
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs)
qs = qs.prefetch_related("user", "urls", "speaker")
return qs
class SpeakerProposalDetailView(
AvailabilityMatrixViewMixin,
ContentTeamPermissionMixin,
DetailView,
):
"""This view permits Content Team members to see SpeakerProposal details"""
model = SpeakerProposal
template_name = "speaker_proposal_detail_backoffice.html"
context_object_name = "speaker_proposal"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related("user", "urls")
return qs
class SpeakerProposalApproveRejectView(ProposalApproveBaseView):
"""This view allows ContentTeam members to approve/reject SpeakerProposals"""
model = SpeakerProposal
template_name = "speaker_proposal_approve_reject.html"
context_object_name = "speaker_proposal"
class EventProposalListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This view permits Content Team members to list EventProposals"""
model = EventProposal
template_name = "event_proposal_list.html"
context_object_name = "event_proposal_list"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related(
"user",
"urls",
"event",
"event_type",
"speakers__event_proposals",
"track",
"tags",
)
return qs
class EventProposalDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
"""This view permits Content Team members to see EventProposal details"""
model = EventProposal
template_name = "event_proposal_detail_backoffice.html"
context_object_name = "event_proposal"
class EventProposalApproveRejectView(ProposalApproveBaseView):
"""This view allows ContentTeam members to approve/reject EventProposals"""
model = EventProposal
template_name = "event_proposal_approve_reject.html"
context_object_name = "event_proposal"
################################
# MANAGE SPEAKER VIEWS
class SpeakerListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This view is used by the Content Team to see Speaker objects."""
model = Speaker
template_name = "speaker_list_backoffice.html"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related(
"proposal__user",
"events__event_slots",
"events__event_type",
"event_conflicts",
)
return qs
class SpeakerDetailView(
AvailabilityMatrixViewMixin, ContentTeamPermissionMixin, DetailView
):
"""This view is used by the Content Team to see details for Speaker objects"""
model = Speaker
template_name = "speaker_detail_backoffice.html"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related(
"event_conflicts", "events__event_slots", "events__event_type"
)
return qs
class SpeakerUpdateView(
AvailabilityMatrixViewMixin, ContentTeamPermissionMixin, UpdateView
):
"""This view is used by the Content Team to update Speaker objects"""
model = Speaker
template_name = "speaker_update.html"
form_class = SpeakerForm
def get_form_kwargs(self):
"""Set camp for the form"""
kwargs = super().get_form_kwargs()
kwargs.update({"camp": self.camp})
return kwargs
def form_valid(self, form):
"""Save object and availability"""
speaker = form.save()
save_speaker_availability(form, obj=speaker)
messages.success(self.request, "Speaker has been updated")
return redirect(
reverse(
"backoffice:speaker_detail",
kwargs={"camp_slug": self.camp.slug, "slug": self.get_object().slug},
)
)
class SpeakerDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
"""This view is used by the Content Team to delete Speaker objects"""
model = Speaker
template_name = "speaker_delete.html"
def delete(self, *args, **kwargs):
speaker = self.get_object()
# delete related objects first
speaker.availabilities.all().delete()
speaker.urls.all().delete()
return super().delete(*args, **kwargs)
def get_success_url(self):
messages.success(
self.request, f"Speaker '{self.get_object().name}' has been deleted"
)
return reverse("backoffice:speaker_list", kwargs={"camp_slug": self.camp.slug})
################################
# MANAGE EVENTTYPE VIEWS
class EventTypeListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This view is used by the Content Team to list EventTypes"""
model = EventType
template_name = "event_type_list.html"
context_object_name = "event_type_list"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.annotate(
# only count events for the current camp
event_count=Count(
"events", distinct=True, filter=Q(events__track__camp=self.camp)
),
# only count EventSessions for the current camp
event_sessions_count=Count(
"event_sessions",
distinct=True,
filter=Q(event_sessions__camp=self.camp),
),
# only count EventSlots for the current camp
event_slots_count=Count(
"event_sessions__event_slots",
distinct=True,
filter=Q(event_sessions__camp=self.camp),
),
)
return qs
class EventTypeDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
"""This view is used by the Content Team to see details for EventTypes"""
model = EventType
template_name = "event_type_detail.html"
context_object_name = "event_type"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["event_sessions"] = self.camp.event_sessions.filter(
event_type=self.get_object()
).prefetch_related("event_location", "event_slots")
context["events"] = self.camp.events.filter(
event_type=self.get_object()
).prefetch_related(
"speakers", "event_slots__event_session__event_location", "event_type"
)
return context
################################
# MANAGE EVENTLOCATION VIEWS
class EventLocationListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This view is used by the Content Team to list EventLocation objects."""
model = EventLocation
template_name = "event_location_list.html"
context_object_name = "event_location_list"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related("event_sessions__event_slots", "conflicts")
return qs
class EventLocationDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
"""This view is used by the Content Team to see details for EventLocation objects"""
model = EventLocation
template_name = "event_location_detail.html"
context_object_name = "event_location"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related(
"conflicts", "event_sessions__event_slots", "event_sessions__event_type"
)
return qs
class EventLocationCreateView(CampViewMixin, ContentTeamPermissionMixin, CreateView):
"""This view is used by the Content Team to create EventLocation objects"""
model = EventLocation
fields = ["name", "icon", "capacity", "conflicts"]
template_name = "event_location_form.html"
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs)
form.fields["conflicts"].queryset = self.camp.event_locations.all()
return form
def form_valid(self, form):
location = form.save(commit=False)
location.camp = self.camp
location.save()
form.save_m2m()
messages.success(
self.request, f"EventLocation {location.name} has been created"
)
return redirect(
reverse(
"backoffice:event_location_detail",
kwargs={"camp_slug": self.camp.slug, "slug": location.slug},
)
)
class EventLocationUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
"""This view is used by the Content Team to update EventLocation objects"""
model = EventLocation
fields = ["name", "icon", "capacity", "conflicts"]
template_name = "event_location_form.html"
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs)
form.fields["conflicts"].queryset = self.camp.event_locations.exclude(
pk=self.get_object().pk
)
return form
def get_success_url(self):
messages.success(
self.request, f"EventLocation {self.get_object().name} has been updated"
)
return reverse(
"backoffice:event_location_detail",
kwargs={"camp_slug": self.camp.slug, "slug": self.get_object().slug},
)
class EventLocationDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
"""This view is used by the Content Team to delete EventLocation objects"""
model = EventLocation
template_name = "event_location_delete.html"
context_object_name = "event_location"
def delete(self, *args, **kwargs):
slotsdeleted, slotdetails = self.get_object().event_slots.all().delete()
sessionsdeleted, sessiondetails = (
self.get_object().event_sessions.all().delete()
)
return super().delete(*args, **kwargs)
def get_success_url(self):
messages.success(
self.request, f"EventLocation '{self.get_object().name}' has been deleted."
)
return reverse(
"backoffice:event_location_list", kwargs={"camp_slug": self.camp.slug}
)
################################
# MANAGE EVENT VIEWS
class EventListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This view is used by the Content Team to see Event objects."""
model = Event
template_name = "event_list_backoffice.html"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related(
"speakers__events",
"event_type",
"event_slots__event_session__event_location",
"tags",
)
return qs
class EventDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
"""This view is used by the Content Team to see details for Event objects"""
model = Event
template_name = "event_detail_backoffice.html"
class EventUpdateView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
"""This view is used by the Content Team to update Event objects"""
model = Event
fields = [
"title",
"abstract",
"video_recording",
"duration_minutes",
"demand",
"tags",
]
template_name = "event_update.html"
def get_success_url(self):
messages.success(self.request, "Event has been updated")
return reverse(
"backoffice:event_detail",
kwargs={"camp_slug": self.camp.slug, "slug": self.get_object().slug},
)
class EventDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
"""This view is used by the Content Team to delete Event objects"""
model = Event
template_name = "event_delete.html"
def delete(self, *args, **kwargs):
self.get_object().urls.all().delete()
return super().delete(*args, **kwargs)
def get_success_url(self):
messages.success(
self.request,
f"Event '{self.get_object().title}' has been deleted!",
)
return reverse("backoffice:event_list", kwargs={"camp_slug": self.camp.slug})
class EventScheduleView(CampViewMixin, ContentTeamPermissionMixin, FormView):
"""This view is used by the Content Team to manually schedule Events.
It shows a table with radioselect buttons for the available slots for the
EventType of the Event"""
form_class = EventScheduleForm
template_name = "event_schedule.html"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
self.event = get_object_or_404(
Event, track__camp=self.camp, slug=kwargs["slug"]
)
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs)
self.slots = []
slotindex = 0
# loop over sessions, get free slots
for session in self.camp.event_sessions.filter(
event_type=self.event.event_type,
event_duration_minutes__gte=self.event.duration_minutes,
):
for slot in session.get_available_slots():
# loop over speakers to see if they are all available
for speaker in self.event.speakers.all():
if not speaker.is_available(slot.when):
# this speaker is not available, skip this slot
break
else:
# all speakers are available for this slot
self.slots.append({"index": slotindex, "slot": slot})
slotindex += 1
# add the slot choicefield
form.fields["slot"] = forms.ChoiceField(
widget=forms.RadioSelect,
choices=[(s["index"], s["index"]) for s in self.slots],
)
return form
def get_context_data(self, *args, **kwargs):
"""
Add event to context
"""
context = super().get_context_data(*args, **kwargs)
context["event"] = self.event
context["event_slots"] = self.slots
return context
def form_valid(self, form):
"""
Set needed values, save slot and return
"""
slot = self.slots[int(form.cleaned_data["slot"])]["slot"]
slot.event = self.event
slot.autoscheduled = False
slot.save()
messages.success(
self.request,
f"{self.event.title} has been scheduled to begin at {slot.when.lower} at location {slot.event_location.name} successfully!",
)
add_event_scheduled_email(slot)
return redirect(
reverse(
"backoffice:event_detail",
kwargs={"camp_slug": self.camp.slug, "slug": self.event.slug},
)
)
################################
# MANAGE EVENTSESSION VIEWS
class EventSessionCreateTypeSelectView(
CampViewMixin, ContentTeamPermissionMixin, ListView
):
"""
This view is shown first when creating a new EventSession
"""
model = EventType
template_name = "event_session_create_type_select.html"
context_object_name = "event_type_list"
class EventSessionCreateLocationSelectView(
CampViewMixin, ContentTeamPermissionMixin, ListView
):
"""
This view is shown second when creating a new EventSession
"""
model = EventLocation
template_name = "event_session_create_location_select.html"
context_object_name = "event_location_list"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
self.event_type = get_object_or_404(EventType, slug=kwargs["event_type_slug"])
def get_context_data(self, *args, **kwargs):
"""
Add event_type to context
"""
context = super().get_context_data(*args, **kwargs)
context["event_type"] = self.event_type
return context
class EventSessionFormViewMixin:
"""
A mixin with the stuff shared between EventSession{Create|Update}View
"""
def get_form(self, *args, **kwargs):
"""
The default range widgets are a bit shit because they eat the help_text and
have no indication of which field is for what. So we add a nice placeholder.
We also limit the event_location dropdown to only the current camps locations.
"""
form = super().get_form(*args, **kwargs)
form.fields["when"].widget.widgets[0].attrs = {
"placeholder": f"Start Date and Time (YYYY-MM-DD HH:MM). Time zone is {settings.TIME_ZONE}.",
}
form.fields["when"].widget.widgets[1].attrs = {
"placeholder": f"End Date and Time (YYYY-MM-DD HH:MM). Time zone is {settings.TIME_ZONE}.",
}
if hasattr(form.fields, "event_location"):
form.fields["event_location"].queryset = EventLocation.objects.filter(
camp=self.camp
)
return form
def get_context_data(self, *args, **kwargs):
"""
Add event_type and location and existing sessions to context
"""
context = super().get_context_data(*args, **kwargs)
if not hasattr(self, "event_type"):
self.event_type = self.get_object().event_type
context["event_type"] = self.event_type
if not hasattr(self, "event_location"):
self.event_location = self.get_object().event_location
context["event_location"] = self.event_location
context["sessions"] = self.event_type.event_sessions.filter(camp=self.camp)
return context
class EventSessionCreateView(
CampViewMixin, ContentTeamPermissionMixin, EventSessionFormViewMixin, CreateView
):
"""
This view is used by the Content Team to create EventSession objects
"""
model = EventSession
fields = ["description", "when", "event_duration_minutes"]
template_name = "event_session_form.html"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
self.event_type = get_object_or_404(EventType, slug=kwargs["event_type_slug"])
self.event_location = get_object_or_404(
EventLocation, camp=self.camp, slug=kwargs["event_location_slug"]
)
def form_valid(self, form):
"""
Set camp and event_type, check for overlaps and save
"""
session = form.save(commit=False)
session.event_type = self.event_type
session.event_location = self.event_location
session.camp = self.camp
session.save()
messages.success(self.request, f"{session} has been created successfully!")
return redirect(
reverse(
"backoffice:event_session_list", kwargs={"camp_slug": self.camp.slug}
)
)
class EventSessionListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""
This view is used by the Content Team to see EventSession objects.
"""
model = EventSession
template_name = "event_session_list.html"
context_object_name = "event_session_list"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related("event_type", "event_location", "event_slots")
return qs
class EventSessionDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
"""
This view is used by the Content Team to see details for EventSession objects
"""
model = EventSession
template_name = "event_session_detail.html"
context_object_name = "session"
class EventSessionUpdateView(
CampViewMixin, ContentTeamPermissionMixin, EventSessionFormViewMixin, UpdateView
):
"""
This view is used by the Content Team to update EventSession objects
"""
model = EventSession
fields = ["when", "description", "event_duration_minutes"]
template_name = "event_session_form.html"
def form_valid(self, form):
"""
Just save, we have a post_save signal which takes care of fixing EventSlots
"""
session = form.save()
messages.success(self.request, f"{session} has been updated successfully!")
return redirect(
reverse(
"backoffice:event_session_list", kwargs={"camp_slug": self.camp.slug}
)
)
class EventSessionDeleteView(CampViewMixin, ContentTeamPermissionMixin, DeleteView):
"""
This view is used by the Content Team to delete EventSession objects
"""
model = EventSession
template_name = "event_session_delete.html"
context_object_name = "session"
def get(self, *args, **kwargs):
"""Show a warning if we have something scheduled in this EventSession"""
if self.get_object().event_slots.filter(event__isnull=False).exists():
messages.warning(
self.request,
"NOTE: One or more EventSlots in this EventSession has an Event scheduled. Make sure you are deleting the correct session!",
)
return super().get(*args, **kwargs)
def delete(self, *args, **kwargs):
session = self.get_object()
session.event_slots.all().delete()
return super().delete(*args, **kwargs)
def get_success_url(self):
messages.success(
self.request,
"EventSession and related EventSlots was deleted successfully!",
)
return reverse(
"backoffice:event_session_list", kwargs={"camp_slug": self.camp.slug}
)
################################
# MANAGE EVENTSLOT VIEWS
class EventSlotListView(CampViewMixin, ContentTeamPermissionMixin, ListView):
"""This view is used by the Content Team to see EventSlot objects."""
model = EventSlot
template_name = "event_slot_list.html"
context_object_name = "event_slot_list"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related(
"event__speakers",
"event_session__event_location",
"event_session__event_type",
)
return qs
class EventSlotDetailView(CampViewMixin, ContentTeamPermissionMixin, DetailView):
"""This view is used by the Content Team to see details for EventSlot objects"""
model = EventSlot
template_name = "event_slot_detail.html"
context_object_name = "event_slot"
class EventSlotUnscheduleView(CampViewMixin, ContentTeamPermissionMixin, UpdateView):
"""This view is used by the Content Team to remove an Event from the schedule/EventSlot"""
model = EventSlot
template_name = "event_slot_unschedule.html"
fields = []
context_object_name = "event_slot"
def form_valid(self, form):
event_slot = self.get_object()
event = event_slot.event
event_slot.unschedule()
messages.success(
self.request,
f"The Event '{event.title}' has been removed from the slot {event_slot}",
)
return redirect(
reverse(
"backoffice:event_detail",
kwargs={"camp_slug": self.camp.slug, "slug": event.slug},
)
)
################################
# AUTOSCHEDULER VIEWS
class AutoScheduleManageView(CampViewMixin, ContentTeamPermissionMixin, TemplateView):
"""Just an index type view with links to the various actions"""
template_name = "autoschedule_index.html"
class AutoScheduleCrashCourseView(
CampViewMixin, ContentTeamPermissionMixin, TemplateView
):
"""A short crash course on the autoscheduler"""
template_name = "autoschedule_crash_course.html"
class AutoScheduleValidateView(CampViewMixin, ContentTeamPermissionMixin, FormView):
"""This view is used to validate schedules. It uses the AutoScheduler and can
either validate the currently applied schedule or a new similar schedule, or a
brand new schedule"""
template_name = "autoschedule_validate.html"
form_class = AutoScheduleValidateForm
def form_valid(self, form):
# initialise AutoScheduler
scheduler = AutoScheduler(camp=self.camp)
# get autoschedule
if form.cleaned_data["schedule"] == "current":
autoschedule = scheduler.build_current_autoschedule()
message = f"The currently scheduled Events form a valid schedule! AutoScheduler has {len(scheduler.autoslots)} Slots based on {scheduler.event_sessions.count()} EventSessions for {scheduler.event_types.count()} EventTypes. {scheduler.events.count()} Events in the schedule."
elif form.cleaned_data["schedule"] == "similar":
original_autoschedule = scheduler.build_current_autoschedule()
autoschedule, diff = scheduler.calculate_similar_autoschedule(
original_autoschedule
)
message = f"The new similar schedule is valid! AutoScheduler has {len(scheduler.autoslots)} Slots based on {scheduler.event_sessions.count()} EventSessions for {scheduler.event_types.count()} EventTypes. Differences to the current schedule: {len(diff['event_diffs'])} Event diffs and {len(diff['slot_diffs'])} Slot diffs."
elif form.cleaned_data["schedule"] == "new":
autoschedule = scheduler.calculate_autoschedule()
message = f"The new schedule is valid! AutoScheduler has {len(scheduler.autoslots)} Slots based on {scheduler.event_sessions.count()} EventSessions for {scheduler.event_types.count()} EventTypes. {scheduler.events.count()} Events in the schedule."
# check validity
valid, violations = scheduler.is_valid(autoschedule, return_violations=True)
if valid:
messages.success(self.request, message)
else:
messages.error(self.request, "Schedule is NOT valid!")
message = "Schedule violations:<br>"
for v in violations:
message += v + "<br>"
messages.error(self.request, mark_safe(message))
return redirect(
reverse(
"backoffice:autoschedule_validate", kwargs={"camp_slug": self.camp.slug}
)
)
class AutoScheduleDiffView(CampViewMixin, ContentTeamPermissionMixin, TemplateView):
template_name = "autoschedule_diff.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
scheduler = AutoScheduler(camp=self.camp)
autoschedule, diff = scheduler.calculate_similar_autoschedule()
context["diff"] = diff
context["scheduler"] = scheduler
return context
class AutoScheduleApplyView(CampViewMixin, ContentTeamPermissionMixin, FormView):
"""This view is used by the Content Team to apply a new schedules by unscheduling
all autoscheduled Events, and scheduling all Event/Slot combinations in the schedule.
TODO: see comment in program.autoscheduler.AutoScheduler.apply() method.
"""
template_name = "autoschedule_apply.html"
form_class = AutoScheduleApplyForm
def form_valid(self, form):
# initialise AutoScheduler
scheduler = AutoScheduler(camp=self.camp)
# get autoschedule
if form.cleaned_data["schedule"] == "similar":
autoschedule, diff = scheduler.calculate_similar_autoschedule()
elif form.cleaned_data["schedule"] == "new":
autoschedule = scheduler.calculate_autoschedule()
diff = None
# check validity
valid, violations = scheduler.is_valid(autoschedule, return_violations=True)
if valid:
# schedule is valid, apply it
deleted, created = scheduler.apply(autoschedule)
messages.success(
self.request,
f"Schedule has been applied! {deleted} Events removed from schedule, {created} new Events scheduled.",
)
if diff:
messages.success(
self.request,
"Differences to the previous schedule: {len(diff['event_diffs'])} Event diffs and {len(diff['slot_diffs'])} Slot diffs.",
)
else:
messages.error(self.request, "Schedule is NOT valid, cannot apply!")
return redirect(
reverse(
"backoffice:autoschedule_apply", kwargs={"camp_slug": self.camp.slug}
)
)
class AutoScheduleDebugEventSlotUnavailabilityView(
CampViewMixin, ContentTeamPermissionMixin, TemplateView
):
template_name = "autoschedule_debug_slots.html"
def get_context_data(self, **kwargs):
scheduler = AutoScheduler(camp=self.camp)
context = {
"scheduler": scheduler,
}
return context
class AutoScheduleDebugEventConflictsView(
CampViewMixin, ContentTeamPermissionMixin, TemplateView
):
template_name = "autoschedule_debug_events.html"
def get_context_data(self, **kwargs):
scheduler = AutoScheduler(camp=self.camp)
context = {
"scheduler": scheduler,
}
return context