bornhack-website/src/program/views.py

1040 lines
34 KiB
Python
Raw Normal View History

import logging
from collections import OrderedDict
from django.views.generic import ListView, TemplateView, DetailView, View
2018-08-19 16:21:04 +00:00
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.conf import settings
2017-03-12 14:43:41 +00:00
from django.http import Http404, HttpResponse
from django.utils.decorators import method_decorator
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.urls import reverse, reverse_lazy
2017-08-22 11:00:42 +00:00
from django.template import Engine, Context
from django.shortcuts import redirect
from django.shortcuts import get_object_or_404
from betterforms.multiform import MultiModelForm
import icalendar
2017-03-12 14:43:41 +00:00
from camps.mixins import CampViewMixin
2018-08-19 16:21:04 +00:00
from program.models import Url, UrlType
from .mixins import (
EnsureUserOwnsProposalMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UrlViewMixin,
)
from .email import (
add_new_eventproposal_email,
add_new_speakerproposal_email,
add_speakerproposal_updated_email,
2019-06-16 12:32:24 +00:00
add_eventproposal_updated_email,
)
2017-03-12 14:43:41 +00:00
from . import models
from .forms import SpeakerProposalForm, EventProposalForm
logger = logging.getLogger("bornhack.%s" % __name__)
2017-03-31 17:25:48 +00:00
###################################################################################################
2018-03-04 14:48:57 +00:00
# ical calendar
2017-03-31 17:25:48 +00:00
class ICSView(CampViewMixin, View):
2017-03-31 17:25:48 +00:00
def get(self, request, *args, **kwargs):
2018-05-26 18:14:48 +00:00
eventinstances = models.EventInstance.objects.filter(
event__track__camp=self.camp
)
2017-08-13 22:50:54 +00:00
# Type query
2019-06-16 12:32:24 +00:00
type_query = request.GET.get("type", None)
2017-08-13 22:50:54 +00:00
if type_query:
2019-06-16 12:32:24 +00:00
type_slugs = type_query.split(",")
types = models.EventType.objects.filter(slug__in=type_slugs)
2017-08-13 22:50:54 +00:00
eventinstances = eventinstances.filter(event__event_type__in=types)
# Location query
2019-06-16 12:32:24 +00:00
location_query = request.GET.get("location", None)
2017-08-13 22:50:54 +00:00
if location_query:
2019-06-16 12:32:24 +00:00
location_slugs = location_query.split(",")
2017-08-13 22:50:54 +00:00
locations = models.EventLocation.objects.filter(
2019-06-16 12:32:24 +00:00
slug__in=location_slugs, camp=self.camp
2017-08-13 22:50:54 +00:00
)
eventinstances = eventinstances.filter(location__in=locations)
# Video recording query
2019-06-16 12:32:24 +00:00
video_query = request.GET.get("video", None)
2017-08-13 22:50:54 +00:00
if video_query:
2019-06-16 12:32:24 +00:00
video_states = video_query.split(",")
2017-08-13 22:50:54 +00:00
query_kwargs = {}
2019-06-16 12:32:24 +00:00
if "has-recording" in video_states:
query_kwargs["event__video_url__isnull"] = False
2017-08-13 22:50:54 +00:00
2019-06-16 12:32:24 +00:00
if "to-be-recorded" in video_states:
query_kwargs["event__video_recording"] = True
2017-08-13 22:50:54 +00:00
2019-06-16 12:32:24 +00:00
if "not-to-be-recorded" in video_states:
if "event__video_recording" in query_kwargs:
del query_kwargs["event__video_recording"]
2017-08-13 22:50:54 +00:00
else:
2019-06-16 12:32:24 +00:00
query_kwargs["event__video_recording"] = False
2017-08-13 22:50:54 +00:00
eventinstances = eventinstances.filter(**query_kwargs)
cal = icalendar.Calendar()
for event_instance in eventinstances:
cal.add_component(event_instance.get_ics_event())
2017-04-13 12:14:44 +00:00
response = HttpResponse(cal.to_ical())
2019-06-16 12:32:24 +00:00
response["Content-Type"] = "text/calendar"
response["Content-Disposition"] = "inline; filename={}.ics".format(
self.camp.slug
)
2017-04-13 12:14:44 +00:00
return response
###################################################################################################
# proposals list view
2017-03-12 14:43:41 +00:00
class ProposalListView(LoginRequiredMixin, CampViewMixin, ListView):
model = models.SpeakerProposal
2019-06-16 12:32:24 +00:00
template_name = "proposal_list.html"
context_object_name = "speakerproposal_list"
2017-03-12 14:43:41 +00:00
def get_queryset(self, **kwargs):
# only show speaker proposals for the current user
2017-03-12 14:43:41 +00:00
return super().get_queryset().filter(user=self.request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
2017-03-14 17:06:23 +00:00
# also add eventproposals to the context
2019-06-16 12:32:24 +00:00
context["eventproposal_list"] = models.EventProposal.objects.filter(
track__camp=self.camp, user=self.request.user
)
context["eventtype_list"] = models.EventType.objects.filter(public=True)
2017-03-12 14:43:41 +00:00
return context
###################################################################################################
# speakerproposal views
2019-06-16 12:32:24 +00:00
class SpeakerProposalCreateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
CreateView,
):
""" This view allows a user to create a new SpeakerProposal linked to an existing EventProposal """
2019-06-16 12:32:24 +00:00
model = models.SpeakerProposal
2019-06-16 12:32:24 +00:00
template_name = "speakerproposal_form.html"
form_class = SpeakerProposalForm
2017-03-12 14:43:41 +00:00
def dispatch(self, request, *args, **kwargs):
""" Get the eventproposal object """
2019-06-16 12:32:24 +00:00
self.eventproposal = get_object_or_404(
models.EventProposal, pk=kwargs["event_uuid"]
)
return super().dispatch(request, *args, **kwargs)
2017-03-14 17:06:23 +00:00
def get_success_url(self):
2019-06-16 12:32:24 +00:00
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()
2019-06-16 12:32:24 +00:00
kwargs.update({"camp": self.camp, "eventtype": self.eventproposal.event_type})
return kwargs
2017-03-14 17:06:23 +00:00
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["eventproposal"] = self.eventproposal
return context
2017-03-12 14:43:41 +00:00
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):
2019-06-16 12:32:24 +00:00
logger.error(
"Unable to send email to content team after new speakerproposal"
)
return redirect(
2019-06-16 12:32:24 +00:00
reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
2019-06-16 12:32:24 +00:00
class SpeakerProposalUpdateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureUserOwnsProposalMixin,
EnsureCFPOpenMixin,
UpdateView,
):
"""
This view allows a user to update an existing SpeakerProposal.
"""
2019-06-16 12:32:24 +00:00
model = models.SpeakerProposal
2019-06-16 12:32:24 +00:00
template_name = "speakerproposal_form.html"
form_class = SpeakerProposalForm
2017-03-12 14:43:41 +00:00
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
2019-06-16 12:32:24 +00:00
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):
2019-06-16 12:32:24 +00:00
logger.error(
"Unable to send email to content team after speakerproposal update"
)
# message user and redirect
2019-06-16 12:32:24 +00:00
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})
)
2019-06-16 12:32:24 +00:00
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
"""
2019-06-16 12:32:24 +00:00
model = models.SpeakerProposal
2019-06-16 12:32:24 +00:00
template_name = "proposal_delete.html"
2017-03-12 14:43:41 +00:00
def get(self, request, *args, **kwargs):
# do not permit deleting if this speakerproposal is linked to any eventproposals
if self.get_object().eventproposals.exists():
2019-06-16 12:32:24 +00:00
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):
2019-06-16 12:32:24 +00:00
messages.success(
self.request, "Proposal '%s' has been deleted." % self.object.name
)
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
2019-06-16 12:32:24 +00:00
class SpeakerProposalDetailView(
LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView
):
model = models.SpeakerProposal
2019-06-16 12:32:24 +00:00
template_name = "speakerproposal_detail.html"
###################################################################################################
# eventproposal views
2019-06-16 12:32:24 +00:00
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)
"""
2019-06-16 12:32:24 +00:00
model = models.EventType
2019-06-16 12:32:24 +00:00
template_name = "event_type_select.html"
def dispatch(self, request, *args, **kwargs):
""" Get the speakerproposal object """
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
context["speaker"] = self.speaker
return context
2017-03-12 14:43:41 +00:00
2019-06-16 12:32:24 +00:00
class EventProposalSelectPersonView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
ListView,
):
"""
This view is for selecting an existing speakerproposal to add to an existing eventproposal
"""
2019-06-16 12:32:24 +00:00
model = models.SpeakerProposal
2019-06-16 12:32:24 +00:00
template_name = "event_proposal_select_person.html"
def dispatch(self, request, *args, **kwargs):
""" Get EventProposal from url kwargs """
2019-06-16 12:32:24 +00:00
self.eventproposal = get_object_or_404(
models.EventProposal, pk=kwargs["event_uuid"], user=request.user
)
return super().dispatch(request, *args, **kwargs)
2017-03-12 14:43:41 +00:00
def get_queryset(self, **kwargs):
""" Filter out any speakerproposals already added to this eventproposal """
return self.eventproposal.get_available_speakerproposals().all()
2017-03-29 22:20:14 +00:00
def get_context_data(self, *args, **kwargs):
""" Make eventproposal object available in template """
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["eventproposal"] = self.eventproposal
return context
2019-06-16 12:32:24 +00:00
class EventProposalAddPersonView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UpdateView,
):
"""
This view is for adding an existing speakerproposal to an existing eventproposal
"""
2019-06-16 12:32:24 +00:00
model = models.EventProposal
2019-06-16 12:32:24 +00:00
template_name = "event_proposal_add_person.html"
fields = []
2019-06-16 12:32:24 +00:00
pk_url_kwarg = "event_uuid"
2017-03-12 14:43:41 +00:00
def dispatch(self, request, *args, **kwargs):
""" Get the speakerproposal object """
2019-06-16 12:32:24 +00:00
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 """
2017-03-12 14:43:41 +00:00
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["speakerproposal"] = self.speakerproposal
2017-03-12 14:43:41 +00:00
return context
def form_valid(self, form):
form.instance.speakers.add(self.speakerproposal)
2019-06-16 12:32:24 +00:00
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())
2019-06-16 12:32:24 +00:00
class EventProposalRemovePersonView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UpdateView,
):
"""
This view is for removing a speakerproposal from an existing eventproposal
"""
2019-06-16 12:32:24 +00:00
model = models.EventProposal
2019-06-16 12:32:24 +00:00
template_name = "event_proposal_remove_person.html"
fields = []
2019-06-16 12:32:24 +00:00
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
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
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:
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
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(
2019-06-16 12:32:24 +00:00
"program:eventproposal_detail",
kwargs={"camp_slug": self.camp.slug, "pk": self.get_object().uuid},
)
2019-06-16 12:32:24 +00:00
class EventProposalCreateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
CreateView,
):
"""
This view allows a user to create a new eventproposal linked to an existing speakerproposal
"""
2019-06-16 12:32:24 +00:00
model = models.EventProposal
2019-06-16 12:32:24 +00:00
template_name = "eventproposal_form.html"
form_class = EventProposalForm
2017-03-12 15:16:24 +00:00
def dispatch(self, request, *args, **kwargs):
""" Get the speakerproposal object """
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
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()
2019-06-16 12:32:24 +00:00
kwargs.update({"camp": self.camp, "eventtype": self.event_type})
return kwargs
def form_valid(self, form):
# set camp and user for this eventproposal
2018-08-19 16:21:04 +00:00
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):
2019-06-16 12:32:24 +00:00
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
2019-06-16 12:32:24 +00:00
class EventProposalUpdateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureUserOwnsProposalMixin,
EnsureCFPOpenMixin,
UpdateView,
):
model = models.EventProposal
2019-06-16 12:32:24 +00:00
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()
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
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):
2019-06-16 12:32:24 +00:00
logger.error(
"Unable to send email to content team after eventproposal update"
)
# message for the user and redirect
2019-06-16 12:32:24 +00:00
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})
)
2019-06-16 12:32:24 +00:00
class EventProposalDeleteView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureUserOwnsProposalMixin,
EnsureCFPOpenMixin,
DeleteView,
):
model = models.EventProposal
2019-06-16 12:32:24 +00:00
template_name = "proposal_delete.html"
def get_success_url(self):
2019-06-16 12:32:24 +00:00
messages.success(
self.request, "Proposal '%s' has been deleted." % self.object.title
)
return reverse("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
2017-03-12 14:43:41 +00:00
2019-06-16 12:32:24 +00:00
class EventProposalDetailView(
LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, DetailView
):
model = models.EventProposal
2019-06-16 12:32:24 +00:00
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
"""
2019-06-16 12:32:24 +00:00
model = models.EventType
2019-06-16 12:32:24 +00:00
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.
"""
2019-06-16 12:32:24 +00:00
model = models.SpeakerProposal
2019-06-16 12:32:24 +00:00
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
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
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():
2019-06-16 12:32:24 +00:00
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
"""
2019-06-16 12:32:24 +00:00
template_name = "combined_proposal_submit.html"
def dispatch(self, request, *args, **kwargs):
"""
Check that we have a valid EventType
"""
# get EventType from url kwargs
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
context["eventtype"] = self.eventtype
return context
def form_valid(self, form):
"""
Save the object(s) here before redirecting
"""
2019-06-16 12:32:24 +00:00
if hasattr(self, "speakerproposal"):
2018-08-19 16:21:04 +00:00
eventproposal = form.save(user=self.request.user, event_type=self.eventtype)
eventproposal.speakers.add(self.speakerproposal)
else:
# first save the SpeakerProposal
2019-06-16 12:32:24 +00:00
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
2019-06-16 12:32:24 +00:00
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")
2019-06-16 12:32:24 +00:00
if not hasattr(self, "speakerproposal"):
if not add_new_speakerproposal_email(speakerproposal):
2019-06-16 12:32:24 +00:00
logger.error(
"Unable to send email to content team after new speakerproposal"
)
# all good
2019-06-16 12:32:24 +00:00
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
"""
2019-06-16 12:32:24 +00:00
if hasattr(self, "speakerproposal"):
# we already have a speakerproposal, just show an eventproposal form
return EventProposalForm
# build our MultiModelForm
class CombinedProposalSubmitForm(MultiModelForm):
2019-06-16 12:32:24 +00:00
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()
2019-06-16 12:32:24 +00:00
kwargs.update({"camp": self.camp, "eventtype": self.eventtype})
return kwargs
###################################################################################################
# speaker views
2016-07-13 20:37:20 +00:00
2016-08-08 17:45:32 +00:00
class SpeakerDetailView(CampViewMixin, DetailView):
2016-08-08 17:45:32 +00:00
model = models.Speaker
2019-06-16 12:32:24 +00:00
template_name = "speaker_detail.html"
2016-08-08 17:45:32 +00:00
class SpeakerListView(CampViewMixin, ListView):
2016-08-08 17:36:13 +00:00
model = models.Speaker
2019-06-16 12:32:24 +00:00
template_name = "speaker_list.html"
2016-08-08 17:36:13 +00:00
###################################################################################################
# event views
2017-01-23 17:57:30 +00:00
class EventListView(CampViewMixin, ListView):
2016-08-08 17:36:13 +00:00
model = models.Event
2019-06-16 12:32:24 +00:00
template_name = "event_list.html"
2016-07-13 20:37:20 +00:00
2017-03-12 14:43:41 +00:00
class EventDetailView(CampViewMixin, DetailView):
model = models.Event
2019-06-16 12:32:24 +00:00
template_name = "schedule_event_detail.html"
2017-03-12 14:43:41 +00:00
###################################################################################################
# schedule views
2017-03-12 14:43:41 +00:00
class NoScriptScheduleView(CampViewMixin, TemplateView):
template_name = "noscript_schedule_view.html"
2016-07-13 20:37:20 +00:00
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
2019-06-16 12:32:24 +00:00
context["eventinstances"] = models.EventInstance.objects.filter(
2018-05-26 18:14:48 +00:00
event__track__camp=self.camp
2019-06-16 12:32:24 +00:00
).order_by("when")
return context
class ScheduleView(CampViewMixin, TemplateView):
2019-06-16 12:32:24 +00:00
template_name = "schedule_overview.html"
2017-01-23 22:58:41 +00:00
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(
2019-06-16 12:32:24 +00:00
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:
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
context[
"schedule_midnight_offset_hours"
] = settings.SCHEDULE_MIDNIGHT_OFFSET_HOURS
return context
2016-08-07 13:49:30 +00:00
class CallForParticipationView(CampViewMixin, TemplateView):
2019-06-16 12:32:24 +00:00
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)
2019-06-16 12:32:24 +00:00
proposals = models.EventProposal.objects.filter(camp=self.camp).select_related(
"user", "event"
)
context["proposals"] = proposals
2017-08-22 11:00:42 +00:00
engine = Engine.get_default()
2019-06-16 12:32:24 +00:00
template = engine.get_template("control/proposal_overview.csv")
2017-08-22 11:00:42 +00:00
csv = template.render(Context(context))
2019-06-16 12:32:24 +00:00
context["csv"] = csv
2017-08-22 11:00:42 +00:00
return context
2019-06-16 12:32:24 +00:00
###################################################################################################
# URL views
2019-06-16 12:32:24 +00:00
class UrlCreateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UrlViewMixin,
CreateView,
):
model = models.Url
2019-06-16 12:32:24 +00:00
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
"""
2019-06-16 12:32:24 +00:00
if hasattr(self, "eventproposal") and self.eventproposal:
# this URL belongs to an eventproposal
form.instance.eventproposal = self.eventproposal
url = form.save()
2019-06-16 12:32:24 +00:00
if (
self.eventproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.eventproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.eventproposal.save()
2019-06-16 12:32:24 +00:00
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
url = form.save()
2019-06-16 12:32:24 +00:00
if (
self.speakerproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.speakerproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.speakerproposal.save()
2019-06-16 12:32:24 +00:00
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.speakerproposal.name,
)
messages.success(self.request, "URL saved.")
# all good
2019-06-16 12:32:24 +00:00
return redirect(
reverse_lazy("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
2019-06-16 12:32:24 +00:00
class UrlUpdateView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UrlViewMixin,
UpdateView,
):
model = models.Url
2019-06-16 12:32:24 +00:00
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
"""
2019-06-16 12:32:24 +00:00
if hasattr(self, "eventproposal") and self.eventproposal:
# this URL belongs to a speakerproposal
url = form.save()
2019-06-16 12:32:24 +00:00
if (
self.eventproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.eventproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.eventproposal.save()
2019-06-16 12:32:24 +00:00
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.eventproposal.title,
)
else:
# this URL belongs to a speakerproposal
url = form.save()
2019-06-16 12:32:24 +00:00
if (
self.speakerproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.speakerproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.speakerproposal.save()
2019-06-16 12:32:24 +00:00
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.speakerproposal.name,
)
messages.success(self.request, "URL saved.")
# all good
2019-06-16 12:32:24 +00:00
return redirect(
reverse_lazy("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)
2019-06-16 12:32:24 +00:00
class UrlDeleteView(
LoginRequiredMixin,
CampViewMixin,
EnsureWritableCampMixin,
EnsureCFPOpenMixin,
UrlViewMixin,
DeleteView,
):
model = models.Url
2019-06-16 12:32:24 +00:00
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
"""
2019-06-16 12:32:24 +00:00
if hasattr(self, "eventproposal") and self.eventproposal:
# this URL belongs to a speakerproposal
2019-06-16 12:32:24 +00:00
if (
self.eventproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.eventproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.eventproposal.save()
2019-06-16 12:32:24 +00:00
messages.success(
self.request,
"%s is now pending review by the Content Team."
% self.eventproposal.title,
)
else:
# this URL belongs to a speakerproposal
2019-06-16 12:32:24 +00:00
if (
self.speakerproposal.proposal_status
!= models.SpeakerProposal.PROPOSAL_PENDING
):
self.speakerproposal.proposal_status = (
models.SpeakerProposal.PROPOSAL_PENDING
)
self.speakerproposal.save()
2019-06-16 12:32:24 +00:00
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
2019-06-16 12:32:24 +00:00
return redirect(
reverse_lazy("program:proposal_list", kwargs={"camp_slug": self.camp.slug})
)