bornhack-website/src/program/views.py

1171 lines
38 KiB
Python

import logging
from collections import OrderedDict
import icalendar
from camps.mixins import CampViewMixin
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.template import Context, Engine
from django.urls import reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.views.generic import DetailView, ListView, TemplateView, View
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from utils.middleware import RedirectException
from utils.mixins import UserIsObjectOwnerMixin
from . import models
from .email import (
add_eventproposal_updated_email,
add_new_eventproposal_email,
add_new_speakerproposal_email,
add_speakerproposal_updated_email,
)
from .forms import EventProposalForm, SpeakerProposalForm
from .mixins import (
EnsureCFPOpenMixin,
EnsureUserOwnsProposalMixin,
EnsureWritableCampMixin,
EventFeedbackViewMixin,
EventViewMixin,
UrlViewMixin,
)
from .multiform import MultiModelForm
logger = logging.getLogger("bornhack.%s" % __name__)
###################################################################################################
# ical calendar
class ICSView(CampViewMixin, View):
def get(self, request, *args, **kwargs):
eventinstances = models.EventInstance.objects.filter(
event__track__camp=self.camp
)
# Type query
type_query = request.GET.get("type", None)
if type_query:
type_slugs = type_query.split(",")
types = models.EventType.objects.filter(slug__in=type_slugs)
eventinstances = eventinstances.filter(event__event_type__in=types)
# Location query
location_query = request.GET.get("location", None)
if location_query:
location_slugs = location_query.split(",")
locations = models.EventLocation.objects.filter(
slug__in=location_slugs, camp=self.camp
)
eventinstances = eventinstances.filter(location__in=locations)
# Video recording query
video_query = request.GET.get("video", None)
if video_query:
video_states = video_query.split(",")
query_kwargs = {}
if "has-recording" in video_states:
query_kwargs["event__video_url__isnull"] = False
if "to-be-recorded" in video_states:
query_kwargs["event__video_recording"] = True
if "not-to-be-recorded" in video_states:
if "event__video_recording" in query_kwargs:
del query_kwargs["event__video_recording"]
else:
query_kwargs["event__video_recording"] = False
eventinstances = eventinstances.filter(**query_kwargs)
cal = icalendar.Calendar()
for event_instance in eventinstances:
cal.add_component(event_instance.get_ics_event())
response = HttpResponse(cal.to_ical())
response["Content-Type"] = "text/calendar"
response["Content-Disposition"] = "inline; filename={}.ics".format(
self.camp.slug
)
return response
###################################################################################################
# proposals list view
class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
model = models.SpeakerProposal
template_name = "proposal_list.html"
context_object_name = "speakerproposal_list"
def get_queryset(self, **kwargs):
# only show speaker proposals for the current user
return super().get_queryset().filter(user=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# also add eventproposals to the context
context["eventproposal_list"] = models.EventProposal.objects.filter(
track__camp=self.camp, user=self.request.user
)
context["eventtype_list"] = models.EventType.objects.filter(public=True)
return context
###################################################################################################
# speakerproposal views
class SpeakerProposalCreateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
CreateView,
):
""" This view allows a user to create a new SpeakerProposal linked to an existing EventProposal """
model = models.SpeakerProposal
template_name = "speakerproposal_form.html"
form_class = SpeakerProposalForm
def dispatch(self, request, *args, **kwargs):
""" Get the eventproposal object """
self.eventproposal = get_object_or_404(
models.EventProposal, pk=kwargs["event_uuid"]
)
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
def get_form_kwargs(self):
"""
Set camp and eventtype for the form
"""
kwargs = super().get_form_kwargs()
kwargs.update({"camp": self.camp, "eventtype": self.eventproposal.event_type})
return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["eventproposal"] = self.eventproposal
return context
def form_valid(self, form):
# set user before saving
form.instance.user = self.request.user
form.instance.camp = self.camp
if not form.instance.email:
form.instance.email = self.request.user.email
speakerproposal = form.save()
# add speakerproposal to eventproposal
self.eventproposal.speakers.add(speakerproposal)
# send mail to content team
if not add_new_speakerproposal_email(speakerproposal):
logger.error(
"Unable to send email to content team after new speakerproposal"
)
return redirect(
reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
class SpeakerProposalUpdateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureUserOwnsProposalMixin,
EnsureCFPOpenMixin,
UpdateView,
):
"""
This view allows a user to update an existing SpeakerProposal.
"""
model = models.SpeakerProposal
template_name = "speakerproposal_form.html"
form_class = SpeakerProposalForm
def get_form_kwargs(self):
"""
Set camp and eventtype for the form
"""
kwargs = super().get_form_kwargs()
if self.get_object().eventproposals.count() == 1:
# determine which form to use based on the type of event associated with the proposal
eventtype = self.get_object().eventproposals.get().event_type
else:
# more than one eventproposal. If all events are the same type we can still show a non-generic form here
eventtypes = set()
for ep in self.get_object().eventproposals.all():
eventtypes.add(ep.event_type)
if len(eventtypes) == 1:
# only one eventtype found
eventtype = ep.event_type
else:
# more than one type of event for this person, return the generic speakerproposal form
eventtype = None
# add camp and eventtype to form kwargs
kwargs.update({"camp": self.camp, "eventtype": eventtype})
return kwargs
def form_valid(self, form):
"""
Change the speakerproposal status to pending
"""
# set proposal status to pending
form.instance.proposal_status = models.SpeakerProposal.PROPOSAL_PENDING
speakerproposal = form.save()
# send mail to content team
if not add_speakerproposal_updated_email(speakerproposal):
logger.error(
"Unable to send email to content team after speakerproposal update"
)
# message user and redirect
messages.info(
self.request, "Your proposal is now pending approval by the content team."
)
return redirect(
reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
class SpeakerProposalDeleteView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureUserOwnsProposalMixin,
EnsureCFPOpenMixin,
DeleteView,
):
"""
This view allows a user to delete an existing SpeakerProposal object, as long as it is not linked to any EventProposals
"""
model = models.SpeakerProposal
template_name = "proposal_delete.html"
def get(self, request, *args, **kwargs):
# do not permit deleting if this speakerproposal is linked to any eventproposals
if self.get_object().eventproposals.exists():
messages.error(
request,
"Cannot delete a person while it is associated with one or more eventproposals. Delete those first.",
)
return redirect(
reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
# continue with the request
return super().get(request, *args, **kwargs)
def get_success_url(self):
messages.success(
self.request, "Proposal '%s' has been deleted." % self.object.name
)
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
class SpeakerProposalDetailView(
LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView
):
model = models.SpeakerProposal
template_name = "speakerproposal_detail.html"
###################################################################################################
# eventproposal views
class EventProposalTypeSelectView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
ListView,
):
"""
This view is for selecting the type of event to submit (when adding a new eventproposal to an existing speakerproposal)
"""
model = models.EventType
template_name = "event_type_select.html"
def dispatch(self, request, *args, **kwargs):
""" Get the speakerproposal object """
self.speaker = get_object_or_404(
models.SpeakerProposal, pk=kwargs["speaker_uuid"]
)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self, **kwargs):
""" We only allow submissions of events with EventTypes where public=True """
return super().get_queryset().filter(public=True)
def get_context_data(self, *args, **kwargs):
""" Make speakerproposal object available in template """
context = super().get_context_data(**kwargs)
context["speaker"] = self.speaker
return context
class EventProposalSelectPersonView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
ListView,
):
"""
This view is for selecting an existing speakerproposal to add to an existing eventproposal
"""
model = models.SpeakerProposal
template_name = "event_proposal_select_person.html"
def dispatch(self, request, *args, **kwargs):
""" Get EventProposal from url kwargs """
self.eventproposal = get_object_or_404(
models.EventProposal, pk=kwargs["event_uuid"], user=request.user
)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self, **kwargs):
""" Filter out any speakerproposals already added to this eventproposal """
return self.eventproposal.get_available_speakerproposals().all()
def get_context_data(self, *args, **kwargs):
""" Make eventproposal object available in template """
context = super().get_context_data(**kwargs)
context["eventproposal"] = self.eventproposal
return context
class EventProposalAddPersonView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UpdateView,
):
"""
This view is for adding an existing speakerproposal to an existing eventproposal
"""
model = models.EventProposal
template_name = "event_proposal_add_person.html"
fields = []
pk_url_kwarg = "event_uuid"
def dispatch(self, request, *args, **kwargs):
""" Get the speakerproposal object """
self.speakerproposal = get_object_or_404(
models.SpeakerProposal, pk=kwargs["speaker_uuid"], user=request.user
)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
""" Make speakerproposal object available in template """
context = super().get_context_data(**kwargs)
context["speakerproposal"] = self.speakerproposal
return context
def form_valid(self, form):
form.instance.speakers.add(self.speakerproposal)
messages.success(
self.request,
"%s has been added as %s for %s"
% (
self.speakerproposal.name,
form.instance.event_type.host_title,
form.instance.title,
),
)
return redirect(self.get_success_url())
class EventProposalRemovePersonView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UpdateView,
):
"""
This view is for removing a speakerproposal from an existing eventproposal
"""
model = models.EventProposal
template_name = "event_proposal_remove_person.html"
fields = []
pk_url_kwarg = "event_uuid"
def dispatch(self, request, *args, **kwargs):
""" Get the speakerproposal object and check a few things """
# get the speakerproposal object from URL kwargs
self.speakerproposal = get_object_or_404(
models.SpeakerProposal, pk=kwargs["speaker_uuid"], user=request.user
)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
""" Make speakerproposal object available in template """
context = super().get_context_data(**kwargs)
context["speakerproposal"] = self.speakerproposal
return context
def form_valid(self, form):
""" Remove the speaker from the event """
if self.speakerproposal not in self.get_object().speakers.all():
# this speaker is not associated with this event
raise Http404
if self.get_object().speakers.count() == 1:
messages.error(
self.request, "Cannot delete the last person associalted with event!"
)
return redirect(self.get_success_url())
# remove speakerproposal from eventproposal
form.instance.speakers.remove(self.speakerproposal)
messages.success(
self.request,
"%s has been removed from %s"
% (self.speakerproposal.name, self.get_object().title),
)
return redirect(self.get_success_url())
def get_success_url(self):
return reverse(
"program:eventproposal_detail",
kwargs={"camp_slug": self.camp.slug, "pk": self.get_object().uuid},
)
class EventProposalCreateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
CreateView,
):
"""
This view allows a user to create a new eventproposal linked to an existing speakerproposal
"""
model = models.EventProposal
template_name = "eventproposal_form.html"
form_class = EventProposalForm
def dispatch(self, request, *args, **kwargs):
""" Get the speakerproposal object """
self.speakerproposal = get_object_or_404(
models.SpeakerProposal, pk=self.kwargs["speaker_uuid"]
)
self.event_type = get_object_or_404(
models.EventType, slug=self.kwargs["event_type_slug"]
)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
""" Make speakerproposal object available in template """
context = super().get_context_data(**kwargs)
context["speaker"] = self.speakerproposal
context["event_type"] = self.event_type
return context
def get_form_kwargs(self):
"""
Set camp and eventtype for the form
"""
kwargs = super().get_form_kwargs()
kwargs.update({"camp": self.camp, "eventtype": self.event_type})
return kwargs
def form_valid(self, form):
# set camp and user for this eventproposal
eventproposal = form.save(user=self.request.user, event_type=self.event_type)
# add the speakerproposal to the eventproposal
eventproposal.speakers.add(self.speakerproposal)
# send mail to content team
if not add_new_eventproposal_email(eventproposal):
logger.error("Unable to send email to content team after new eventproposal")
# all good
return redirect(self.get_success_url())
def get_success_url(self):
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
class EventProposalUpdateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureUserOwnsProposalMixin,
EnsureCFPOpenMixin,
UpdateView,
):
model = models.EventProposal
template_name = "eventproposal_form.html"
form_class = EventProposalForm
def get_form_kwargs(self):
"""
Set camp and eventtype for the form
"""
kwargs = super().get_form_kwargs()
kwargs.update({"camp": self.camp, "eventtype": self.get_object().event_type})
return kwargs
def get_context_data(self, *args, **kwargs):
""" Make speakerproposal and eventtype objects available in the template """
context = super().get_context_data(**kwargs)
context["event_type"] = self.get_object().event_type
return context
def form_valid(self, form):
# set status to pending and save eventproposal
form.instance.proposal_status = models.EventProposal.PROPOSAL_PENDING
eventproposal = form.save()
# send email to content team
if not add_eventproposal_updated_email(eventproposal):
logger.error(
"Unable to send email to content team after eventproposal update"
)
# message for the user and redirect
messages.info(
self.request, "Your proposal is now pending approval by the content team."
)
return redirect(
reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
class EventProposalDeleteView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureUserOwnsProposalMixin,
EnsureCFPOpenMixin,
DeleteView,
):
model = models.EventProposal
template_name = "proposal_delete.html"
def get_success_url(self):
messages.success(
self.request, "Proposal '%s' has been deleted." % self.object.title
)
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
class EventProposalDetailView(
LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView
):
model = models.EventProposal
template_name = "eventproposal_detail.html"
###################################################################################################
# combined proposal views
class CombinedProposalTypeSelectView(LoginRequiredMixin, CampViewMixin, ListView):
"""
A view which allows the user to select event type without anything else on the page
"""
model = models.EventType
template_name = "event_type_select.html"
def get_queryset(self, **kwargs):
""" We only allow submissions of events with EventTypes where public=True """
return super().get_queryset().filter(public=True)
class CombinedProposalPersonSelectView(LoginRequiredMixin, CampViewMixin, ListView):
"""
A view which allows the user to 1) choose between existing SpeakerProposals or
2) pressing a button to create a new SpeakerProposal.
Redirect straight to 2) if no existing SpeakerProposals exist.
"""
model = models.SpeakerProposal
template_name = "combined_proposal_select_person.html"
def dispatch(self, request, *args, **kwargs):
"""
Check that we have a valid EventType
"""
# get EventType from url kwargs
self.eventtype = get_object_or_404(
models.EventType, slug=self.kwargs["event_type_slug"]
)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self, **kwargs):
# only show speaker proposals for the current user
return super().get_queryset().filter(user=self.request.user)
def get_context_data(self, **kwargs):
"""
Add EventType to template context
"""
context = super().get_context_data(**kwargs)
context["eventtype"] = self.eventtype
return context
def get(self, request, *args, **kwargs):
""" If we don't have any existing SpeakerProposals just redirect directly to the combined submit view """
if not self.get_queryset().exists():
return redirect(
reverse_lazy(
"program:proposal_combined_submit",
kwargs={
"camp_slug": self.camp.slug,
"event_type_slug": self.eventtype.slug,
},
)
)
return super().get(request, *args, **kwargs)
class CombinedProposalSubmitView(LoginRequiredMixin, CampViewMixin, CreateView):
"""
This view is used by users to submit CFP proposals.
It allows the user to submit an EventProposal and a SpeakerProposal together.
It can also be used with a preselected SpeakerProposal uuid in url kwargs
"""
template_name = "combined_proposal_submit.html"
def dispatch(self, request, *args, **kwargs):
"""
Check that we have a valid EventType
"""
# get EventType from url kwargs
self.eventtype = get_object_or_404(
models.EventType, slug=self.kwargs["event_type_slug"]
)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""
Add EventType to template context
"""
context = super().get_context_data(**kwargs)
context["eventtype"] = self.eventtype
return context
def form_valid(self, form):
"""
Save the object(s) here before redirecting
"""
if hasattr(self, "speakerproposal"):
eventproposal = form.save(user=self.request.user, event_type=self.eventtype)
eventproposal.speakers.add(self.speakerproposal)
else:
# first save the SpeakerProposal
speakerproposal = form["speakerproposal"].save(commit=False)
speakerproposal.camp = self.camp
speakerproposal.user = self.request.user
if not speakerproposal.email:
speakerproposal.email = self.request.user.email
speakerproposal.save()
# then save the eventproposal
eventproposal = form["eventproposal"].save(
user=self.request.user, event_type=self.eventtype
)
eventproposal.user = self.request.user
eventproposal.event_type = self.eventtype
eventproposal.save()
# add the speakerproposal to the eventproposal
eventproposal.speakers.add(speakerproposal)
# send mail(s) to content team
if not add_new_eventproposal_email(eventproposal):
logger.error("Unable to send email to content team after new eventproposal")
if not hasattr(self, "speakerproposal"):
if not add_new_speakerproposal_email(speakerproposal):
logger.error(
"Unable to send email to content team after new speakerproposal"
)
# all good
return redirect(
reverse_lazy("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
def get_form_class(self):
"""
Unless we have an existing SpeakerProposal we must show two forms on the page.
We use betterforms.MultiModelForm to combine two forms on the page
"""
if hasattr(self, "speakerproposal"):
# we already have a speakerproposal, just show an eventproposal form
return EventProposalForm
# build our MultiModelForm
class CombinedProposalSubmitForm(MultiModelForm):
form_classes = OrderedDict(
(
("speakerproposal", SpeakerProposalForm),
("eventproposal", EventProposalForm),
)
)
# return the form class
return CombinedProposalSubmitForm
def get_form_kwargs(self):
"""
Set camp and eventtype for the form
"""
kwargs = super().get_form_kwargs()
kwargs.update({"camp": self.camp, "eventtype": self.eventtype})
return kwargs
###################################################################################################
# speaker views
class SpeakerDetailView(CampViewMixin, DetailView):
model = models.Speaker
template_name = "speaker_detail.html"
class SpeakerListView(CampViewMixin, ListView):
model = models.Speaker
template_name = "speaker_list.html"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related("events")
qs = qs.prefetch_related("events__event_type")
return qs
###################################################################################################
# event views
class EventListView(CampViewMixin, ListView):
model = models.Event
template_name = "event_list.html"
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
qs = qs.prefetch_related("event_type", "track", "instances", "speakers")
return qs
class EventDetailView(CampViewMixin, DetailView):
model = models.Event
template_name = "schedule_event_detail.html"
slug_url_kwarg = "event_slug"
###################################################################################################
# schedule views
class NoScriptScheduleView(CampViewMixin, TemplateView):
template_name = "noscript_schedule_view.html"
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
context["eventinstances"] = models.EventInstance.objects.filter(
event__track__camp=self.camp
).order_by("when")
return context
class ScheduleView(CampViewMixin, TemplateView):
template_name = "schedule_overview.html"
def dispatch(self, request, *args, **kwargs):
"""
If no events are scheduled redirect to the event page
"""
response = super().dispatch(request, *args, **kwargs)
events_exist = models.EventInstance.objects.filter(
event__track__camp=self.camp
).exists()
redirect_to_event_list = False
if not events_exist:
redirect_to_event_list = True
if not self.camp.show_schedule and not request.user.is_superuser:
redirect_to_event_list = True
if redirect_to_event_list:
return redirect(
reverse("program:event_index", kwargs={"camp_slug": self.camp.slug})
)
return response
def get_context_data(self, *args, **kwargs):
context = super(ScheduleView, self).get_context_data(**kwargs)
context[
"schedule_midnight_offset_hours"
] = settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS
return context
class CallForParticipationView(CampViewMixin, TemplateView):
template_name = "call_for_participation.html"
###################################################################################################
# control center csv
class ProgramControlCenter(CampViewMixin, TemplateView):
template_name = "control/index.html"
@method_decorator(staff_member_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
proposals = models.EventProposal.objects.filter(camp=self.camp).select_related(
"user", "event"
)
context["proposals"] = proposals
engine = Engine.get_default()
template = engine.get_template("control/proposal_overview.csv")
csv = template.render(Context(context))
context["csv"] = csv
return context
###################################################################################################
# URL views
class UrlCreateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UrlViewMixin,
CreateView,
):
model = models.Url
template_name = "url_form.html"
fields = ["urltype", "url"]
def form_valid(self, form):
"""
Set the proposal FK before saving
Set proposal as pending if it isn't already
"""
if hasattr(self, "eventproposal") and self.eventproposal:
# this URL belongs to an eventproposal
form.instance.eventproposal = self.eventproposal
form.save()
if (
self.eventproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.eventproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.eventproposal.save()
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.eventproposal.title,
)
else:
# this URL belongs to a speakerproposal
form.instance.speakerproposal = self.speakerproposal
form.save()
if (
self.speakerproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.speakerproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.speakerproposal.save()
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.speakerproposal.name,
)
messages.success(self.request, "URL saved.")
# all good
return redirect(
reverse_lazy("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
class UrlUpdateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UrlViewMixin,
UpdateView,
):
model = models.Url
template_name = "url_form.html"
fields = ["urltype", "url"]
pk_url_kwarg = "url_uuid"
def form_valid(self, form):
"""
Set proposal as pending if it isn't already
"""
if hasattr(self, "eventproposal") and self.eventproposal:
# this URL belongs to a speakerproposal
form.save()
if (
self.eventproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.eventproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.eventproposal.save()
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.eventproposal.title,
)
else:
# this URL belongs to a speakerproposal
form.save()
if (
self.speakerproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.speakerproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.speakerproposal.save()
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.speakerproposal.name,
)
messages.success(self.request, "URL saved.")
# all good
return redirect(
reverse_lazy("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
class UrlDeleteView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UrlViewMixin,
DeleteView,
):
model = models.Url
template_name = "url_delete.html"
pk_url_kwarg = "url_uuid"
def delete(self, request, *args, **kwargs):
"""
Set proposal as pending if it isn't already
"""
if hasattr(self, "eventproposal") and self.eventproposal:
# this URL belongs to a speakerproposal
if (
self.eventproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.eventproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.eventproposal.save()
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.eventproposal.title,
)
else:
# this URL belongs to a speakerproposal
if (
self.speakerproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.speakerproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.speakerproposal.save()
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.speakerproposal.name,
)
self.object = self.get_object()
self.object.delete()
messages.success(self.request, "URL deleted.")
# all good
return redirect(
reverse_lazy("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
###################################################################################################
# Feedback views
class FeedbackListView(LoginRequiredMixin, EventViewMixin, ListView):
"""
The FeedbackListView is used by the event owner to see approved Feedback for the Event.
"""
model = models.EventFeedback
template_name = "eventfeedback_list.html"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
if not self.event.proposal or not self.event.proposal.user == self.request.user:
messages.error(self.request, "Only the event owner can read feedback!")
raise RedirectException(
reverse(
"program:event_detail",
kwargs={"camp_slug": self.camp.slug, "event_slug": self.event.slug},
)
)
def get_queryset(self, *args, **kwargs):
return models.EventFeedback.objects.filter(event=self.event, approved=True)
class FeedbackCreateView(LoginRequiredMixin, EventViewMixin, CreateView):
"""
Used by users to create Feedback for an Event. Available to all logged in users.
"""
model = models.EventFeedback
fields = ["expectations_fulfilled", "attend_speaker_again", "rating", "comment"]
template_name = "eventfeedback_form.html"
def setup(self, *args, **kwargs):
super().setup(*args, **kwargs)
if models.EventFeedback.objects.filter(
event=self.event, user=self.request.user
).exists():
raise RedirectException(
reverse(
"program:eventfeedback_detail",
kwargs={"camp_slug": self.camp.slug, "event_slug": self.event.slug},
)
)
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs)
form.fields["expectations_fulfilled"].widget = forms.RadioSelect(
choices=models.EventFeedback.YESNO_CHOICES,
)
form.fields["attend_speaker_again"].widget = forms.RadioSelect(
choices=models.EventFeedback.YESNO_CHOICES,
)
form.fields["rating"].widget = forms.RadioSelect(
choices=models.EventFeedback.RATING_CHOICES,
)
return form
def form_valid(self, form):
feedback = form.save(commit=False)
feedback.user = self.request.user
feedback.event = self.event
feedback.save()
messages.success(
self.request, "Your feedback was submitted, it is now pending approval."
)
return redirect(feedback.get_absolute_url())
class FeedbackDetailView(
LoginRequiredMixin, EventFeedbackViewMixin, UserIsObjectOwnerMixin, DetailView
):
"""
Used by the EventFeedback owner to see their own feedback.
"""
model = models.EventFeedback
template_name = "eventfeedback_detail.html"
class FeedbackUpdateView(
LoginRequiredMixin, EventFeedbackViewMixin, UserIsObjectOwnerMixin, UpdateView
):
"""
Used by the EventFeedback owner to update their feedback.
"""
model = models.EventFeedback
fields = ["expectations_fulfilled", "attend_speaker_again", "rating", "comment"]
template_name = "eventfeedback_form.html"
def form_valid(self, form):
feedback = form.save(commit=False)
feedback.approved = False
feedback.save()
messages.success(self.request, "Your feedback was updated")
return redirect(feedback.get_absolute_url())
class FeedbackDeleteView(
LoginRequiredMixin, EventFeedbackViewMixin, UserIsObjectOwnerMixin, DeleteView
):
"""
Used by the EventFeedback owner to delete their own feedback.
"""
model = models.EventFeedback
template_name = "eventfeedback_delete.html"
def get_success_url(self):
messages.success(self.request, "Your feedback was deleted")
return self.event.get_absolute_url()