PonyConf/cfp/views.py

1326 lines
52 KiB
Python

from django.core.mail import send_mail
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import DeleteView, FormView, TemplateView
from django.contrib import messages
from django.db.models import Q, Count, Sum
from django.views.generic import CreateView, DetailView, ListView, UpdateView
from django.http import HttpResponse, Http404, HttpResponseServerError
from django.utils import timezone
from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.forms import modelform_factory
from django import forms
from django.views.decorators.http import require_http_methods
from django.core.cache import cache
from django_select2.views import AutoResponseView
from functools import reduce
import csv
from mailing.forms import MessageForm
from mailing.utils import send_message
from .planning import Program
from .decorators import speaker_required, volunteer_required, staff_required
from .mixins import StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin
from .utils import is_staff
from .models import Participant, Talk, TalkCategory, Vote, Track, Tag, Room, Volunteer, Activity
from .emails import talk_email_send, talk_email_render_preview, \
speaker_email_send, speaker_email_render_preview, \
volunteer_email_send, volunteer_email_render_preview
from .forms import TalkForm, TalkStaffForm, TalkFilterForm, get_talk_speaker_form_class, \
TalkActionForm, SpeakerActionForm, VolunteerActionForm, \
ParticipantForm, ParticipantFilterForm, NotifyForm, \
ConferenceForm, HomepageForm, CreateUserForm, TrackForm, RoomForm, \
VolunteerForm, VolunteerFilterForm, EmailForm, \
PreviewTalkMailForm, PreviewSpeakerMailForm, PreviewVolunteerMailForm, \
SendTalkMailForm, SendSpeakerMailForm, SendVolunteerMailForm, \
TagForm, TalkCategoryForm, ActivityForm, \
ACCEPTATION_VALUES, CONFIRMATION_VALUES
def home(request):
if request.conference.home:
return render(request, 'cfp/home.html')
else:
return redirect(reverse('proposal-home'))
def volunteer_enrole(request):
if request.user.is_authenticated and Volunteer.objects.filter(site=request.conference.site, email=request.user.email).exists():
return redirect(reverse('volunteer-dashboard'))
if not request.conference.volunteers_enrollment_is_open():
raise PermissionDenied
initial = {}
if request.user.is_authenticated and not request.POST:
initial.update({
'name': request.user.get_full_name(),
'phone_number': request.user.profile.phone_number,
'sms_prefered': request.user.profile.sms_prefered,
})
form = VolunteerForm(request.POST or None, initial=initial, conference=request.conference)
if request.user.is_authenticated:
form.fields.pop('email')
if request.method == 'POST' and form.is_valid():
volunteer = form.save(commit=False)
volunteer.language = request.LANGUAGE_CODE
if request.user.is_authenticated:
volunteer.email = request.user.email
volunteer.save()
form.save_m2m()
body = _("""Hi {},
Thank your for your help in the organization of the conference {}!
You can update your availability at anytime:
{}
Thanks!
{}
""").format(volunteer.name, request.conference.name, volunteer.get_secret_url(full=True), request.conference.name)
send_message(
thread=volunteer.conversation,
author=request.conference,
subject=_('[%(conference)s] Thank you for your help!') % {'conference': request.conference},
content=body,
)
messages.success(request, _('Thank you for your participation! You can now subscribe to some activities.'))
return redirect(reverse('volunteer-dashboard', kwargs={'volunteer_token': volunteer.token}))
return render(request, 'cfp/volunteer_enrole.html', {
'activities': Activity.objects.filter(site=request.conference.site),
'form': form,
})
def volunteer_mail_token(request):
form = EmailForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
try:
volunteer = Volunteer.objects.get(site=request.conference.site, email=form.cleaned_data['email'])
except Volunteer.DoesNotExist:
messages.error(request, _('Sorry, we do not know this email.'))
else:
base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
url = base_url + reverse('volunteer-dashboard', kwargs=dict(volunteer_token=volunteer.token))
body = render_to_string('cfp/mails/volunteer_send_token.txt', {
'volunteer': volunteer,
'url': url,
'conf': request.conference
})
send_message(
thread=volunteer.conversation,
author=request.conference,
subject=_("[%(conference)s] Someone asked to access your profil") % {'conference': request.conference},
content=body,
)
messages.success(request, _('An email has been sent with a link to access to your profil.'))
return redirect(reverse('volunteer-mail-token'))
return render(request, 'cfp/volunteer_mail_token.html', {
'form': form,
})
@volunteer_required
def volunteer_dashboard(request, volunteer):
return render(request, 'cfp/volunteer_dashboard.html', {
'activities': Activity.objects.filter(site=request.conference.site),
'volunteer': volunteer,
})
@volunteer_required
def volunteer_profile(request, volunteer):
form = VolunteerForm(request.POST or None, instance=volunteer, conference=request.conference)
if request.method == 'POST' and form.is_valid():
form.save()
messages.success(request, _('Changes saved.'))
return redirect(reverse('volunteer-dashboard', kwargs={'volunteer_token': volunteer.token}))
return render(request, 'cfp/volunteer_profile.html', {
'volunteer': volunteer,
'form': form,
})
@volunteer_required
def volunteer_update_activity(request, volunteer, activity, join):
activity = get_object_or_404(Activity, slug=activity, site=request.conference.site)
if join:
volunteer.activities.add(activity)
messages.success(request, _('Thank you for your participation!'))
else:
volunteer.activities.remove(activity)
messages.success(request, _('Okay, no problem!'))
return redirect(reverse('volunteer-dashboard', kwargs=dict(volunteer_token=volunteer.token)))
@staff_required
def volunteer_list(request):
site = request.conference.site
filter_form = VolunteerFilterForm(request.GET or None, site=site)
# Filtering
show_filters = False
volunteers = Volunteer.objects.filter(site=site).order_by('pk').distinct().prefetch_related('activities')
if filter_form.is_valid():
data = filter_form.cleaned_data
if len(data['activity']):
show_filters = True
q = Q()
if 'none' in data['activity']:
data['activity'].remove('none')
q |= Q(activities__isnull=True)
if len(data['activity']):
q |= Q(activities__slug__in=data['activity'])
volunteers = volunteers.filter(q)
# Action
action_form = VolunteerActionForm(request.POST or None, volunteers=volunteers)
if request.method == 'POST' and action_form.is_valid():
data = action_form.cleaned_data
if data['email']:
request.session['volunteer-email-list'] = data['volunteers']
return redirect(reverse('volunteer-email'))
return redirect(request.get_full_path())
if request.GET.get('format') == 'csv':
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="volunteers.csv"'
writer = csv.writer(response)
for volunteer in volunteers:
writer.writerow(volunteer.get_csv_row())
return response
else:
contact_link = 'mailto:' + ','.join([volunteer.email for volunteer in volunteers.all()])
csv_query_dict = request.GET.copy()
csv_query_dict['format'] = 'csv'
csv_link = '?' + csv_query_dict.urlencode()
return render(request, 'cfp/staff/volunteer_list.html', {
'volunteer_list': volunteers,
'filter_form': filter_form,
'action_form': action_form,
'show_filters': show_filters,
'contact_link': contact_link,
'csv_link': csv_link,
'pending_email': bool(request.session.get('volunteer-email-list', None)),
})
@staff_required
def volunteer_details(request, volunteer_id):
volunteer = get_object_or_404(Volunteer, site=request.conference.site, pk=volunteer_id)
message_form = MessageForm(request.POST or None)
if request.method == 'POST' and message_form.is_valid():
in_reply_to = volunteer.conversation.message_set.last()
send_message(
thread=volunteer.conversation,
author=request.user,
subject='',
content=message_form.cleaned_data['content'],
in_reply_to=in_reply_to,
)
messages.success(request, _('Message sent!'))
return redirect(reverse('volunteer-details', args=[volunteer.pk]))
return render(request, 'cfp/staff/volunteer_details.html', {
'volunteer': volunteer,
})
@staff_required
def volunteer_email(request):
volunteers = Volunteer.objects.filter(pk__in=request.session.get('volunteer-email-list', []))
if not volunteers.exists():
messages.error(request, _('Please select some volunteers.'))
return redirect('volunteer-list')
form = SendVolunteerMailForm(request.POST or None, initial=request.session.get('volunteer-email-stored'), volunteers=volunteers)
if request.method == 'POST' and form.is_valid():
subject = form.cleaned_data['subject']
body = form.cleaned_data['body']
request.session['volunteer-email-stored'] = {'subject': subject, 'body': body}
if form.cleaned_data['confirm']:
sent = volunteer_email_send(volunteers, subject, body)
messages.success(request, _('%(count)d mails have been sent.') % {'count': sent})
del request.session['volunteer-email-list']
return redirect('volunteer-list')
else:
messages.info(request, _('You are ready to send %(count)d emails.') % {'count': volunteers.count()})
else:
form.fields.pop('confirm')
return render(request, 'cfp/staff/volunteer_email.html', {
'volunteers': volunteers,
'form': form,
})
@require_http_methods(['POST'])
@staff_required
def volunteer_email_preview(request):
form = PreviewVolunteerMailForm(request.POST or None)
if not form.is_valid():
return HttpResponseServerError()
volunteer = get_object_or_404(Volunteer, site=request.conference.site, pk=form.cleaned_data['volunteer'])
preview = volunteer_email_render_preview(volunteer, form.cleaned_data['subject'], form.cleaned_data['body'])
return HttpResponse(preview)
def proposal_home(request):
categories = request.conference.opened_categories
if not categories.exists():
return render(request, 'cfp/closed.html')
initial = {}
fields = ['name', 'email', 'biography']
if request.user.is_authenticated:
if Participant.objects.filter(site=request.conference.site, email=request.user.email).exists():
return redirect(reverse('proposal-dashboard'))
elif not request.POST:
initial.update({
'name': request.user.get_full_name(),
'biography': request.user.profile.biography,
})
fields.remove('email')
NewSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=fields)
speaker_form = NewSpeakerForm(request.POST or None, initial=initial, conference=request.conference)
talk_form = TalkForm(request.POST or None, categories=categories)
if request.method == 'POST' and all(map(lambda f: f.is_valid(), [speaker_form, talk_form])):
speaker = speaker_form.save(commit=False)
speaker.site = request.conference.site
if request.user.is_authenticated:
speaker.email = request.user.email
speaker.save()
talk = talk_form.save(commit=False)
talk.site = request.conference.site
talk.save()
talk.speakers.add(speaker)
base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
url_dashboard = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token))
url_talk_details = base_url + reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk))
url_speaker_add = base_url + reverse('proposal-speaker-add', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk))
body = _("""Hi {},
Your talk has been submitted for {}.
Here are the details of your talk:
Title: {}
Description: {}
You can at anytime:
- review and edit your profile: {}
- review and edit your talk: {}
- add a new co-speaker: {}
If you have any question, your can answer to this email.
Thanks!
{}
""").format(
speaker.name, request.conference.name, talk.title, talk.description,
url_dashboard, url_talk_details, url_speaker_add,
request.conference.name,
)
send_message(
thread=speaker.conversation,
author=request.conference,
subject=_("[%(conference)s] Thank you for your proposition '%(talk)s'") % {
'conference': request.conference.name,
'talk': talk,
},
content=body,
)
messages.success(request, _('Your proposition has been successfully submitted!'))
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
return render(request, 'cfp/proposal_home.html', {
'speaker_form': speaker_form,
'talk_form': talk_form,
})
def proposal_mail_token(request):
form = EmailForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
try:
speaker = Participant.objects.get(site=request.conference.site, email=form.cleaned_data['email'])
except Participant.DoesNotExist:
messages.error(request, _('Sorry, we do not know this email.'))
else:
base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
dashboard_url = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token))
body = _("""Hi {},
Someone, probably you, asked to access your profile.
You can edit your talks or add new ones following this url:
{}
If you have any question, your can answer to this email.
Sincerely,
{}
""").format(speaker.name, dashboard_url, request.conference.name)
send_message(
thread=speaker.conversation,
author=request.conference,
subject=_("[%(conference)s] Someone asked to access your profil") % {
'conference': request.conference.name,
},
content=body,
)
messages.success(request, _('An email has been sent with a link to access to your profil.'))
return redirect(reverse('proposal-mail-token'))
return render(request, 'cfp/proposal_mail_token.html', {
'form': form,
})
@speaker_required
def proposal_dashboard(request, speaker):
return render(request, 'cfp/proposal_dashboard.html', {
'speaker': speaker,
'talks': speaker.talk_set.all(),
})
@speaker_required
def proposal_talk_details(request, speaker, talk_id):
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
return render(request, 'cfp/proposal_talk_details.html', {
'speaker': speaker,
'talk': talk,
})
@speaker_required
def proposal_talk_edit(request, speaker, talk_id=None):
if talk_id:
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
else:
talk = None
categories = request.conference.opened_categories
form = TalkForm(request.POST or None, request.FILES or None, categories=categories, instance=talk)
if request.method == 'POST' and form.is_valid():
talk = form.save(commit=False)
talk.site = request.conference.site
talk.save()
talk.speakers.add(speaker)
if talk_id:
messages.success(request, _('Changes saved.'))
else:
# TODO: it could be great to receive the proposition by mail
# but this is not crucial as the speaker already have a link in its mailbox
messages.success(request, _('Your proposition has been successfully submitted!'))
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
return render(request, 'cfp/proposal_talk_form.html', {
'speaker': speaker,
'talk': talk,
'form': form,
})
@speaker_required
def proposal_talk_acknowledgment(request, speaker, talk_id, confirm):
# TODO: handle multiple speakers case
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
if not request.conference.disclosed_acceptances or not talk.accepted or request.conference.completed:
raise PermissionDenied
if talk.confirmed == confirm:
if confirm:
messages.warning(request, _('You already confirmed your participation to this talk.'))
else:
messages.warning(request, _('You already cancelled your participation to this talk.'))
else:
talk.confirmed = confirm
talk.save()
if confirm:
confirmation_message= _('Your participation has been taken into account, thank you!')
action = _('confirmed')
else:
confirmation_message = _('We have noted your unavailability.')
action = _('cancelled')
content = _('Speaker %(speaker)s %(action)s his/her participation for %(talk)s.') % {
'speaker': speaker,
'action': action,
'talk': talk,
}
send_message(
thread=talk.conversation,
author=speaker,
subject=_('[%(conference)s] %(speaker)s %(action)s his/her participation') % {
'conference': request.conference,
'speaker': speaker,
'action': action,
},
content=content,
)
messages.success(request, confirmation_message)
return redirect(reverse('proposal-talk-details', kwargs={'speaker_token': speaker.token, 'talk_id': talk.pk}))
# FIXME his this view really useful?
#@speaker_required
#def proposal_speaker_details(request, speaker, talk_id, co_speaker_id):
# talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
# co_speaker = get_object_or_404(Participant, site=request.conference.site, talk_set__pk=talk.pk, pk=co_speaker_id)
# return render(request, 'cfp/proposal_speaker_details.html', {
# 'speaker': speaker,
# 'talk': talk,
# 'co_speaker': co_speaker,
# })
@speaker_required
def proposal_speaker_edit(request, speaker, talk_id=None, co_speaker_id=None):
talk, co_speaker, co_speaker_candidates = None, None, None
if talk_id:
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
if co_speaker_id:
co_speaker = get_object_or_404(Participant, site=request.conference.site, talk__pk=talk.pk, pk=co_speaker_id)
else:
co_speaker_candidates = speaker.co_speaker_set.exclude(pk__in=talk.speakers.values_list('pk'))
EditSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=['name', 'email', 'biography'] + ParticipantForm.SOCIAL_FIELDS)
all_forms = []
speaker_form = EditSpeakerForm(request.POST or None, conference=request.conference, instance=co_speaker if talk else speaker)
all_forms.append(speaker_form)
if talk and not co_speaker_id:
notify_form = NotifyForm(request.POST or None)
all_forms.append(notify_form)
else:
notify_form = None
if request.method == 'POST' and all(map(lambda f: f.is_valid(), all_forms)):
edited_speaker = speaker_form.save()
if talk:
talk.speakers.add(edited_speaker)
if co_speaker_id:
messages.success(request, _('Changes saved.'))
else:
if notify_form.cleaned_data['notify']:
base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain
url_dashboard = base_url + reverse('proposal-dashboard', kwargs=dict(speaker_token=edited_speaker.token))
url_talk_details = base_url + reverse('proposal-talk-details', kwargs=dict(speaker_token=edited_speaker.token, talk_id=talk.pk))
url_speaker_add = base_url + reverse('proposal-speaker-add', kwargs=dict(speaker_token=edited_speaker.token, talk_id=talk.pk))
body = _("""Hi {},
{} add you as a co-speaker for the conference {}.
Here is a summary of the talk:
Title: {}
Description: {}
You can at anytime:
- review and edit your profile: {}
- review and edit the talk: {}
- add another co-speaker: {}
If you have any question, your can answer to this email.
Thanks!
{}
""").format(
edited_speaker.name, speaker.name, request.conference.name,
talk.title, talk.description,
url_dashboard, url_talk_details, url_speaker_add,
request.conference.name,
)
send_message(
thread=edited_speaker.conversation,
author=request.conference,
subject=_("[%(conference)s] You have been added as co-speaker to '%(talk)s'") % {
'conference': request.conference,
'talk': talk,
},
content=body,
)
messages.success(request, _('Co-speaker successfully added to the talk.'))
#return redirect(reverse('proposal-speaker-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk.pk)))
else:
return redirect(reverse('proposal-dashboard', kwargs=dict(speaker_token=speaker.token)))
return render(request, 'cfp/proposal_speaker_form.html', {
'speaker': speaker,
'talk': talk,
'co_speaker': co_speaker,
'co_speaker_candidates': co_speaker_candidates,
'speaker_form': speaker_form,
'notify_form': notify_form,
})
@speaker_required
def proposal_speaker_add(request, speaker, talk_id, speaker_id):
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
co_speaker = get_object_or_404(Participant, pk__in=speaker.co_speaker_set.values_list('pk'), pk=speaker_id)
talk.speakers.add(co_speaker)
messages.success(request, _('Co-speaker successfully added to the talk.'))
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk_id)))
# TODO: ask for confirmation (with POST request needed)
@speaker_required
def proposal_speaker_remove(request, speaker, talk_id, co_speaker_id):
talk = get_object_or_404(Talk, site=request.conference.site, speakers__pk=speaker.pk, pk=talk_id)
co_speaker = get_object_or_404(Participant, site=request.conference.site, talk__pk=talk.pk, pk=co_speaker_id)
# prevent speaker from removing his/her self
if co_speaker.pk == speaker.pk:
raise PermissionDenied
talk.speakers.remove(co_speaker)
messages.success(request, _('Co-speaker successfully removed from the talk.'))
return redirect(reverse('proposal-talk-details', kwargs=dict(speaker_token=speaker.token, talk_id=talk_id)))
@staff_required
def talk_acknowledgment(request, talk_id, confirm):
talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
if talk.accepted is not True or talk.confirmed == confirm:
raise PermissionDenied
# TODO: handle multiple speakers case
talk.confirmed = confirm
talk.save()
if confirm:
confirmation_message= _('The speaker confirmation has been noted.')
action = _('confirmed')
thread_note = _('The talk has been confirmed.')
else:
confirmation_message = _('The speaker unavailability has been noted.')
action = _('cancelled')
thread_note = _('The talk has been %(action)s.') % {'action': action}
send_message(
thread=talk.conversation,
author=request.user,
subject=_("[%(conference)s] The talk '%(talk)s' has been %(action)s.") % {
'conference': request.conference,
'talk': talk,
'action': action,
},
content=thread_note,
)
messages.success(request, confirmation_message)
return redirect(reverse('talk-details', kwargs=dict(talk_id=talk_id)))
@staff_required
def staff(request):
return render(request, 'cfp/staff/base.html')
@staff_required
def admin(request):
return render(request, 'cfp/admin/base.html')
@staff_required
def talk_list(request):
talks = Talk.objects.filter(site=request.conference.site)
# Filtering
show_filters = False
filter_form = TalkFilterForm(request.GET or None, site=request.conference.site)
if filter_form.is_valid():
data = filter_form.cleaned_data
if len(data['category']):
show_filters = True
talks = talks.filter(reduce(lambda x, y: x | y, [Q(category__pk=pk) for pk in data['category']]))
if len(data['accepted']):
show_filters = True
talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(ACCEPTATION_VALUES)[status]) for status in data['accepted']]))
if len(data['confirmed']):
show_filters = True
talks = talks.filter(accepted=True)
talks = talks.filter(reduce(lambda x, y: x | y, [Q(confirmed=dict(CONFIRMATION_VALUES)[status]) for status in data['confirmed']]))
if data['room'] != None:
show_filters = True
talks = talks.filter(room__isnull=not data['room'])
if data['scheduled'] != None:
show_filters = True
talks = talks.filter(start_date__isnull=not data['scheduled'])
if len(data['tag']):
show_filters = True
talks = talks.filter(tags__slug__in=data['tag'])
if len(data['track']):
show_filters = True
q = Q()
if 'none' in data['track']:
data['track'].remove('none')
q |= Q(track__isnull=True)
if len(data['track']):
q |= Q(track__slug__in=data['track'])
talks = talks.filter(q)
if data['vote'] != None:
show_filters = True
if data['vote']:
talks = talks.filter(vote__user=request.user)
else:
talks = talks.exclude(vote__user=request.user)
if data['materials'] != None:
show_filters = True
materials_filter = Q(materials__isnull=False) & ~Q(materials__exact='')
if data['materials']:
talks = talks.filter(materials_filter)
else:
talks = talks.filter(~materials_filter)
if data['video'] != None:
show_filters = True
if data['video']:
talks = talks.exclude(video__exact='')
else:
talks = talks.filter(video__exact='')
talks = talks.prefetch_related('category', 'speakers', 'track', 'tags')
if request.GET.get('format') == 'csv':
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="talks.csv"'
writer = csv.writer(response)
for talk in talks:
writer.writerow(talk.get_csv_row())
return response
# Action
action_form = TalkActionForm(request.POST or None, talks=talks, site=request.conference.site)
if request.method == 'POST' and action_form.is_valid():
data = action_form.cleaned_data
for talk_id in data['talks']:
talk = Talk.objects.get(site=request.conference.site, pk=talk_id)
if data['decision'] != None and data['decision'] != talk.accepted:
if data['decision']:
action = _('accepted')
else:
action = _('declined')
note = _('The talk has been %(action)s.') % {'action': action}
send_message(
thread=talk.conversation,
author=request.user,
subject=_("[%(conference)s] The talk '%(talk)s' has been %(action)s") % {
'conference': request.conference,
'talk': talk,
'action': action,
},
content=note,
)
talk.accepted = data['decision']
if data['track']:
talk.track = Track.objects.get(site=request.conference.site, slug=data['track'])
if data['tag']:
talk.tags.add(Tag.objects.get(site=request.conference.site, slug=data['tag']))
if data['room']:
talk.room = Room.objects.get(site=request.conference.site, slug=data['room'])
talk.save()
if data['email']:
email = int(data['email'])
if email == TalkActionForm.EMAIL_TALKS:
request.session['talk-email-list'] = data['talks']
return redirect(reverse('talk-email'))
elif email == TalkActionForm.EMAIL_SPEAKERS:
selected_talks = Talk.objects.filter(pk__in=data['talks'])
speakers = Participant.objects.filter(pk__in=selected_talks.values('speakers__pk')).distinct()
request.session['speaker-email-list'] = list(speakers.values_list('pk', flat=True))
return redirect(reverse('speaker-email'))
return redirect(request.get_full_path())
# Sorting
if request.GET.get('order') == 'desc':
sort_reverse = True
else:
sort_reverse = False
SORT_MAPPING = {
'title': 'title',
'category': 'category',
'status': 'accepted',
'score': 'score',
}
sort = request.GET.get('sort')
if sort in SORT_MAPPING.keys():
if sort_reverse:
talks = talks.order_by('-' + SORT_MAPPING[sort])
else:
talks = talks.order_by(SORT_MAPPING[sort])
# Sorting URLs
sort_urls = dict()
sort_glyphicons = dict()
for c in SORT_MAPPING.keys():
url = request.GET.copy()
url['sort'] = c
if c == sort:
if sort_reverse:
del url['order']
glyphicon = 'sort-by-attributes-alt'
else:
url['order'] = 'desc'
glyphicon = 'sort-by-attributes'
else:
glyphicon = 'sort'
sort_urls[c] = url.urlencode()
sort_glyphicons[c] = glyphicon
csv_query_dict = request.GET.copy()
csv_query_dict['format'] = 'csv'
csv_link = '?' + csv_query_dict.urlencode()
return render(request, 'cfp/staff/talk_list.html', {
'show_filters': show_filters,
'talk_list': talks,
'filter_form': filter_form,
'action_form': action_form,
'sort_urls': sort_urls,
'sort_glyphicons': sort_glyphicons,
'csv_link': csv_link,
'pending_email': bool(request.session.get('talk-email-list', None)),
})
@staff_required
def talk_details(request, talk_id):
talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
try:
vote = talk.vote_set.get(user=request.user).vote
except Vote.DoesNotExist:
vote = None
message_form = MessageForm(request.POST or None)
if request.method == 'POST' and message_form.is_valid():
in_reply_to = talk.conversation.message_set.last()
subject=_("[%(conference)s] New comment about '%(talk)s'") % {
'conference': request.conference,
'talk': talk,
}
if in_reply_to:
# Maybe use in_reply_to.subject?
subject = 'Re: ' + subject
send_message(
thread=talk.conversation,
author=request.user,
subject=subject,
content=message_form.cleaned_data['content'],
in_reply_to=in_reply_to,
)
messages.success(request, _('Message sent!'))
return redirect(reverse('talk-details', args=[talk.pk]))
return render(request, 'cfp/staff/talk_details.html', {
'talk': talk,
'vote': vote,
})
@staff_required
def talk_vote(request, talk_id, score):
if score not in [-2, -1, 0, 1, 2]:
raise Http404
talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
vote, created = Vote.objects.get_or_create(talk=talk, user=request.user)
vote.vote = score
vote.save()
messages.success(request, _('Vote successfully created') if created else _('Vote successfully updated'))
return redirect(talk.get_absolute_url())
@staff_required
def talk_decide(request, talk_id, accept):
talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site)
if request.method == 'POST':
talk.accepted = accept
talk.save()
if accept:
action = _('accepted')
else:
action = _('declined')
# Does we need to send a notification to the proposer?
m = request.POST.get('message', '').strip()
if m:
for participant in talk.speakers.all():
send_message(
thread=participant.conversation,
author=request.conference,
subject=_("[%(conference)s] Your talk '%(talk)s' has been %(action)s") % {
'conference': request.conference,
'talk': talk,
'action': action,
},
content=m,
)
# Save the decision in the talk's conversation
send_message(
thread=talk.conversation,
author=request.user,
subject=_("[%(conference)s] The talk '%(talk)s' has been %(action)s") % {
'conference': request.conference,
'talk': talk,
'action': action,
},
content=_('The talk has been %(action)s.') % {'action': action},
)
messages.success(request, _('Decision taken in account'))
return redirect(talk.get_absolute_url())
return render(request, 'cfp/staff/talk_decide.html', {
'talk': talk,
'accept': accept,
})
@staff_required
def talk_email(request):
talks = Talk.objects.filter(pk__in=request.session.get('talk-email-list', []))
count = talks.annotate(speakers_count=Count('speakers', distinct=True)).aggregate(Sum('speakers_count'))['speakers_count__sum']
if not talks.exists():
messages.error(request, _('Please select some talks.'))
return redirect('talk-list')
form = SendTalkMailForm(request.POST or None, initial=request.session.get('talk-email-stored'), talks=talks)
if request.method == 'POST' and form.is_valid():
subject = form.cleaned_data['subject']
body = form.cleaned_data['body']
request.session['talk-email-stored'] = {'subject': subject, 'body': body}
if form.cleaned_data['confirm']:
sent = talk_email_send(talks, subject, body)
messages.success(request, _('%(count)d mails have been sent.') % {'count': sent})
del request.session['talk-email-list']
return redirect('talk-list')
else:
messages.info(request, _('You are ready to send %(count)d emails.') % {'count': count})
else:
form.fields.pop('confirm')
return render(request, 'cfp/staff/talk_email.html', {
'talks': talks,
'form': form,
})
@require_http_methods(['POST'])
@staff_required
def talk_email_preview(request):
form = PreviewTalkMailForm(request.POST or None)
if not form.is_valid():
return HttpResponseServerError()
speaker = get_object_or_404(Participant, site=request.conference.site, pk=form.cleaned_data['speaker'])
talk = get_object_or_404(Talk, site=request.conference.site, pk=form.cleaned_data['talk'])
preview = talk_email_render_preview(talk, speaker, form.cleaned_data['subject'], form.cleaned_data['body'])
return HttpResponse(preview)
@staff_required
def participant_list(request):
participants = Participant.objects.filter(site=request.conference.site) \
.extra(select={'lower_name': 'lower(name)'}) \
.order_by('lower_name')
# Filtering
show_filters = False
filter_form = ParticipantFilterForm(request.GET or None, site=request.conference.site)
if filter_form.is_valid():
data = filter_form.cleaned_data
talks = Talk.objects.filter(site=request.conference.site)
if len(data['category']):
show_filters = True
talks = talks.filter(reduce(lambda x, y: x | y, [Q(category__pk=pk) for pk in data['category']]))
if len(data['accepted']):
show_filters = True
talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(ACCEPTATION_VALUES)[status]) for status in data['accepted']]))
if len(data['confirmed']):
show_filters = True
talks = talks.filter(accepted=True)
talks = talks.filter(reduce(lambda x, y: x | y, [Q(confirmed=dict(CONFIRMATION_VALUES)[status]) for status in data['confirmed']]))
if len(data['track']):
show_filters = True
q = Q()
if 'none' in data['track']:
data['track'].remove('none')
q |= Q(track__isnull=True)
if len(data['track']):
q |= Q(track__slug__in=data['track'])
talks = talks.filter(q)
participants = participants.filter(talk__in=talks)
# Action
action_form = SpeakerActionForm(request.POST or None, speakers=participants)
if request.method == 'POST' and action_form.is_valid():
data = action_form.cleaned_data
if data['email']:
request.session['speaker-email-list'] = data['speakers']
return redirect(reverse('speaker-email'))
return redirect(request.get_full_path())
if request.GET.get('format') == 'csv':
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="participants.csv"'
writer = csv.writer(response)
for participant in participants:
writer.writerow(participant.get_csv_row())
return response
else:
contact_link = 'mailto:' + ','.join([participant.email for participant in participants.all()])
csv_query_dict = request.GET.copy()
csv_query_dict['format'] = 'csv'
csv_link = '?' + csv_query_dict.urlencode()
return render(request, 'cfp/staff/participant_list.html', {
'filter_form': filter_form,
'action_form': action_form,
'participant_list': participants,
'show_filters': show_filters,
'contact_link': contact_link,
'csv_link': csv_link,
'pending_email': bool(request.session.get('speaker-email-list', None)),
})
@staff_required
def participant_details(request, participant_id):
participant = get_object_or_404(Participant, pk=participant_id, site=request.conference.site)
message_form = MessageForm(request.POST or None)
if request.method == 'POST' and message_form.is_valid():
in_reply_to = participant.conversation.message_set.last()
send_message(
thread=participant.conversation,
author=request.user,
subject='',
content=message_form.cleaned_data['content'],
in_reply_to=in_reply_to,
)
messages.success(request, _('Message sent!'))
return redirect(reverse('participant-details', args=[participant.pk]))
return render(request, 'cfp/staff/participant_details.html', {
'participant': participant,
})
class ParticipantCreate(StaffRequiredMixin, OnSiteFormMixin, CreateView):
model = Participant
template_name = 'cfp/staff/participant_form.html'
def get_form_class(self):
return modelform_factory(
self.model,
form=ParticipantForm,
fields=['name', 'vip', 'email', 'phone_number', 'biography', 'notes'] + ParticipantForm.SOCIAL_FIELDS,
)
class ParticipantUpdate(StaffRequiredMixin, OnSiteFormMixin, UpdateView):
model = Participant
template_name = 'cfp/staff/participant_form.html'
slug_field = 'pk'
slug_url_kwarg = 'participant_id'
def get_form_class(self):
return modelform_factory(
self.model,
form=ParticipantForm,
fields=['name', 'vip', 'email', 'phone_number', 'biography', 'notes'] + ParticipantForm.SOCIAL_FIELDS,
)
class ParticipantRemove(StaffRequiredMixin, OnSiteFormMixin, DeleteView):
slug_field = 'pk'
slug_url_kwarg = 'participant_id'
success_url = reverse_lazy('participant-list')
def get_queryset(self):
return Participant.objects.filter(talk__isnull=True)
@staff_required
def participant_add_talk(request, participant_id):
participant = get_object_or_404(Participant, site=request.conference.site, pk=participant_id)
form = TalkForm(request.POST or None, categories=TalkCategory.objects.filter(site=request.conference.site))
if request.method == 'POST' and form.is_valid():
talk = form.save(commit=False)
talk.site = request.conference.site
talk.save()
talk.speakers.add(participant)
return redirect(reverse('talk-details', kwargs={'talk_id': talk.pk}))
return render(request, 'cfp/staff/talk_form.html', {
'form': form,
'participant': participant,
})
@staff_required
def speaker_email(request):
speakers = Participant.objects.filter(pk__in=request.session.get('speaker-email-list', []))
if not speakers.exists():
messages.error(request, _('Please select some speakers.'))
return redirect('participant-list')
form = SendSpeakerMailForm(request.POST or None, initial=request.session.get('speaker-email-stored'), speakers=speakers)
if request.method == 'POST' and form.is_valid():
subject = form.cleaned_data['subject']
body = form.cleaned_data['body']
request.session['speaker-email-stored'] = {'subject': subject, 'body': body}
if form.cleaned_data['confirm']:
sent = speaker_email_send(speakers, subject, body)
messages.success(request, _('%(count)d mails have been sent.') % {'count': sent})
del request.session['speaker-email-list']
return redirect('participant-list')
else:
messages.info(request, _('You are ready to send %(count)d emails.') % {'count': speakers.count()})
else:
form.fields.pop('confirm')
return render(request, 'cfp/staff/speaker_email.html', {
'speakers': speakers,
'form': form,
})
@require_http_methods(['POST'])
@staff_required
def speaker_email_preview(request):
form = PreviewSpeakerMailForm(request.POST or None)
if not form.is_valid():
return HttpResponseServerError()
speaker = get_object_or_404(Participant, site=request.conference.site, pk=form.cleaned_data['speaker'])
preview = speaker_email_render_preview(speaker, form.cleaned_data['subject'], form.cleaned_data['body'])
return HttpResponse(preview)
@staff_required
def conference_edit(request):
form = ConferenceForm(request.POST or None, instance=request.conference)
if request.method == 'POST' and form.is_valid():
old_staff = set(request.conference.staff.all())
new_conference = form.save()
new_staff = set(new_conference.staff.all())
added_staff = new_staff - old_staff
protocol = 'https' if request.is_secure() else 'http'
base_url = protocol+'://'+request.conference.site.domain
url_login = base_url + reverse('login')
url_password_reset = base_url + reverse('password_reset')
msg_title = _('[{}] You have been added to the staff team').format(request.conference.name)
msg_body_template = _("""Hi {},
You have been added to the staff team.
You can now:
- login: {}
- reset your password: {}
{}
""")
# TODO: send bulk emails
for user in added_staff:
msg_body = msg_body_template.format(user.get_full_name(), url_login, url_password_reset, request.conference.name)
send_mail(
msg_title,
msg_body,
request.conference.from_email(),
[user.email],
fail_silently=False,
)
messages.success(request, _('Modifications successfully saved.'))
return redirect(reverse('conference-edit'))
return render(request, 'cfp/admin/conference.html', {
'form': form,
})
@staff_required
def homepage_edit(request):
form = HomepageForm(request.POST or None, instance=request.conference)
if request.method == 'POST' and form.is_valid():
form.save()
messages.success(request, _('Modifications successfully saved.'))
return redirect(reverse('homepage-edit'))
return render(request, 'cfp/admin/homepage.html', {
'form': form,
})
class TalkUpdate(StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin, UpdateView):
model = Talk
template_name = 'cfp/staff/talk_form.html'
pk_url_kwarg = 'talk_id'
def get_form_class(self):
return get_talk_speaker_form_class(self.object.site)
class TrackMixin(OnSiteMixin):
model = Track
class TrackList(StaffRequiredMixin, TrackMixin, ListView):
template_name = 'cfp/staff/track_list.html'
class TrackFormMixin(OnSiteFormMixin, TrackMixin):
template_name = 'cfp/staff/track_form.html'
form_class = TrackForm
success_url = reverse_lazy('track-list')
class TrackCreate(StaffRequiredMixin, TrackFormMixin, CreateView):
pass
class TrackUpdate(StaffRequiredMixin, TrackFormMixin, UpdateView):
pass
class RoomMixin(OnSiteMixin):
model = Room
class RoomList(StaffRequiredMixin, RoomMixin, ListView):
template_name = 'cfp/staff/room_list.html'
class RoomDetail(StaffRequiredMixin, RoomMixin, DetailView):
template_name = 'cfp/staff/room_details.html'
class RoomFormMixin(RoomMixin):
template_name = 'cfp/staff/room_form.html'
form_class = RoomForm
success_url = reverse_lazy('room-list')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({
'conference': self.request.conference,
})
return kwargs
class RoomCreate(StaffRequiredMixin, RoomFormMixin, CreateView):
pass
class RoomUpdate(StaffRequiredMixin, RoomFormMixin, UpdateView):
pass
class TalkCategoryMixin(OnSiteMixin):
model = TalkCategory
class TalkCategoryList(StaffRequiredMixin, TalkCategoryMixin, ListView):
template_name = 'cfp/admin/category_list.html'
class TalkCategoryFormMixin(TalkCategoryMixin):
template_name = 'cfp/admin/category_form.html'
form_class = TalkCategoryForm
success_url = reverse_lazy('category-list')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({
'conference': self.request.conference,
})
return kwargs
class TalkCategoryCreate(StaffRequiredMixin, TalkCategoryFormMixin, CreateView):
pass
class TalkCategoryUpdate(StaffRequiredMixin, TalkCategoryFormMixin, UpdateView):
pass
class TagMixin(OnSiteMixin):
model = Tag
class TagList(StaffRequiredMixin, TagMixin, ListView):
template_name = 'cfp/admin/tag_list.html'
class TagFormMixin(TagMixin):
template_name = 'cfp/admin/tag_form.html'
form_class = TagForm
success_url = reverse_lazy('tag-list')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({
'conference': self.request.conference,
})
return kwargs
class TagCreate(StaffRequiredMixin, TagFormMixin, CreateView):
pass
class TagUpdate(StaffRequiredMixin, TagFormMixin, UpdateView):
pass
class ActivityMixin(OnSiteMixin):
model = Activity
class ActivityList(StaffRequiredMixin, ActivityMixin, ListView):
template_name = 'cfp/admin/activity_list.html'
class ActivityFormMixin(ActivityMixin):
template_name = 'cfp/admin/activity_form.html'
form_class = ActivityForm
success_url = reverse_lazy('activity-list')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({
'conference': self.request.conference,
})
return kwargs
class ActivityCreate(StaffRequiredMixin, ActivityFormMixin, CreateView):
pass
class ActivityUpdate(StaffRequiredMixin, ActivityFormMixin, UpdateView):
pass
@staff_required
def create_user(request):
form = CreateUserForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.save()
messages.success(request, _('User created successfully.'))
return redirect(reverse('create-user'))
return render(request, 'cfp/admin/create_user.html', {
'form': form,
})
def schedule(request, program_format, pending, template, staff, cache=None):
program = Program(site=request.conference.site, pending=pending, staff=staff, cache=cache)
if program_format is None:
return render(request, template, {'program': program.render('html')})
elif program_format == 'html':
return HttpResponse(program.render('html'))
elif program_format == 'xml':
return HttpResponse(program.render('xml'), content_type="application/xml")
elif program_format in ['ics', 'citymeo']:
response = HttpResponse(program.render('ics', citymeo=bool(program_format == 'citymeo')), content_type='text/calendar')
response['Content-Disposition'] = 'attachment; filename="planning.ics"'
return response
else:
raise Http404(_("Format '%s' not available" % program_format))
def public_schedule(request, program_format=None):
if not request.conference.schedule_available and not is_staff(request, request.user):
raise PermissionDenied
if request.conference.schedule_redirection_url and program_format is None:
return redirect(request.conference.schedule_redirection_url)
else:
return schedule(request, program_format=program_format, pending=False, template='cfp/schedule.html', staff=False)
@staff_required
def staff_schedule(request, program_format=None):
return schedule(request, program_format=program_format, pending=True, template='cfp/staff/schedule.html', staff=True, cache=False)
@staff_required
def schedule_evict(request):
cache.clear()
messages.success(request, _('Schedule evicted from cache.'))
return redirect('/')
class Select2View(StaffRequiredMixin, AutoResponseView):
pass