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