from django.core.mail import send_mail from django.core.urlresolvers import reverse_lazy from django.shortcuts import get_object_or_404, redirect, render from django.template.loader import render_to_string from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from django.views.generic import FormView, TemplateView from django.contrib import messages from django.db.models import Q from django.views.generic import CreateView, DetailView, ListView, UpdateView from django.http import HttpResponse, Http404 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_select2.views import AutoResponseView from functools import reduce import csv from mailing.models import Message from mailing.forms import MessageForm 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 .forms import TalkForm, TalkStaffForm, TalkFilterForm, TalkActionForm, \ ParticipantForm, ParticipantFilterForm, NotifyForm, \ ConferenceForm, HomepageForm, CreateUserForm, TrackForm, RoomForm, \ VolunteerForm, VolunteerFilterForm, MailForm, \ 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() 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) #Message.objects.create( # thread=volunteer.conversation, # author=request.conference, # from_email=request.conference.contact_email, # content=body, #) send_mail( subject=_('Thank you for your help!'), message=body, from_email='%s <%s>' % (request.conference.name, request.conference.contact_email), recipient_list=['%s <%s>' % (volunteer.name, volunteer.email)], ) 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 = MailForm(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 }) #Message.objects.create( # thread=volunteer.conversation, # author=request.conference, # from_email=request.conference.contact_email, # content=body, #) send_mail( subject=_('Thank you for your help!'), message=body, from_email='%s <%s>' % (request.conference.name, request.conference.contact_email), recipient_list=['%s <%s>' % (volunteer.name, volunteer.email)], ) messages.success(request, _('A email have 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.html', { 'activities': Activity.objects.filter(site=request.conference.site), 'volunteer': volunteer, }) @volunteer_required def volunteer_update_activity(request, volunteer, activity, join): activity = get_object_or_404(Activity, slug=activity, site=request.conference.site) if join: activity.volunteers.add(volunteer) activity.save() messages.success(request, _('Thank you for your participation!')) else: activity.volunteers.remove(volunteer) activity.save() 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) 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, 'show_filters': show_filters, 'contact_link': contact_link, 'csv_link': csv_link, }) @staff_required def volunteer_details(request, volunteer_id): volunteer = get_object_or_404(Volunteer, site=request.conference.site, pk=volunteer_id) return render(request, 'cfp/staff/volunteer_details.html', { 'volunteer': volunteer, }) 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, ) Message.objects.create( thread=speaker.conversation, author=request.conference, from_email=request.conference.contact_email, content=body, ) messages.success(request, _('You proposition have 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 = MailForm(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, ask 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) Message.objects.create( thread=speaker.conversation, author=request.conference, from_email=request.conference.contact_email, content=body, ) messages.success(request, _('A email have 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, _('You proposition have 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: 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!') thread_note = _('Speaker %(speaker)s confirmed his/her participation.' % {'speaker': speaker}) else: confirmation_message = _('We have noted your unavailability.') thread_note = _('Speaker %(speaker)s CANCELLED his/her participation.' % {'speaker': speaker}) Message.objects.create(thread=talk.conversation, author=speaker, content=thread_note) messages.success(request, confirmation_message) return redirect(reverse('proposal-talk-details', kwargs=dict(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, ) Message.objects.create( thread=edited_speaker.conversation, author=request.conference, from_email=request.conference.contact_email, 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 not talk.accepted or talk.confirmed == confirm: raise PermissionDenied # TODO: handle multiple speakers case talk.confirmed = confirm talk.save() if confirm: confirmation_message= _('The speaker confirmation have been noted.') thread_note = _('The talk have been confirmed.') else: confirmation_message = _('The speaker unavailability have been noted.') thread_note = _('The talk have been cancelled.') Message.objects.create(thread=talk.conversation, author=request.user, 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']: note = _("The talk has been accepted.") else: note = _("The talk has been declined.") Message.objects.create(thread=talk.conversation, author=request.user, 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() 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', } 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, }) @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(): message = message_form.save(commit=False) message.author = request.user message.from_email = request.user.email message.thread = talk.conversation message.save() 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): 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 = int(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() # Does we need to send a notification to the proposer? m = request.POST.get('message', '').strip() if m: for participant in talk.speakers.all(): Message.objects.create(thread=talk.conversation, author=request.user, content=m) # Save the decision in the talk's conversation if accept: note = _("The talk has been accepted.") else: note = _("The talk has been declined.") Message.objects.create(thread=talk.conversation, author=request.user, content=note) 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 TODO WIP # def talk_speaker_add(request, talk_id, participant_id=None): # talk = get_object_or_404(Talk, token=talk_id, site=request.conference.site) @staff_required def talk_speaker_remove(request, talk_id, participant_id): talk = get_object_or_404(Talk, pk=talk_id, site=request.conference.site) participant = get_object_or_404(Participant, pk=participant_id, site=request.conference.site) talk.speakers.remove(participant) messages.success(request, _('Speaker removed from this talk')) return redirect(talk.get_absolute_url()) @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) 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, 'participant_list': participants, 'show_filters': show_filters, 'contact_link': contact_link, 'csv_link': csv_link, }) @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(): message = message_form.save(commit=False) message.author = request.user message.from_email = request.user.email message.thread = participant.conversation message.save() 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' def get_form_class(self): return modelform_factory( self.model, form=ParticipantForm, fields=['name', 'vip', 'email', 'phone_number', 'biography', 'notes'] + ParticipantForm.SOCIAL_FIELDS, ) @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 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 form_class = TalkStaffForm template_name = 'cfp/staff/talk_form.html' 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, cache=None): program = Program(site=request.conference.site, pending=pending, 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): 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_required def staff_schedule(request, program_format): return schedule(request, program_format=program_format, pending=True, template='cfp/staff/schedule.html', cache=False) class Select2View(StaffRequiredMixin, AutoResponseView): pass