link participation to user account

This commit is contained in:
Élie Bouttier 2017-11-05 21:37:34 +01:00
parent bac3953763
commit 0565a9f217
7 changed files with 69 additions and 48 deletions

View File

@ -11,9 +11,12 @@ from cfp.models import Participant
def speaker_required(view_func): def speaker_required(view_func):
def wrapped_view(request, **kwargs): def wrapped_view(request, **kwargs):
speaker_token = kwargs.pop('speaker_token') speaker_token = kwargs.pop('speaker_token')
# TODO v3: if no speaker token is provided, we should check for a logged user, and if so, if speaker_token:
# we should check if his/her participating at current conference speaker = get_object_or_404(Participant, site=request.conference.site, token=speaker_token)
speaker = get_object_or_404(Participant, site=request.conference.site, token=speaker_token) elif request.user.is_authenticated():
speaker = get_object_or_404(Participant, site=request.conference.site, email=request.user.email)
else:
raise PermissionDenied
kwargs['speaker'] = speaker kwargs['speaker'] = speaker
return view_func(request, **kwargs) return view_func(request, **kwargs)
return wraps(view_func)(wrapped_view) return wraps(view_func)(wrapped_view)

View File

@ -195,22 +195,12 @@ class TalkActionForm(forms.Form):
self.fields['room'].choices = [(None, "---------")] + list(rooms.values_list('slug', 'name')) self.fields['room'].choices = [(None, "---------")] + list(rooms.values_list('slug', 'name'))
class ParticipantForm(OnSiteNamedModelForm): class NotifyForm(forms.Form):
notify = forms.BooleanField(initial=True, required=False, label=_('Notify by mail?')) notify = forms.BooleanField(initial=True, required=False, label=_('Notify by mail?'))
def __init__(self, *args, **kwargs):
social = kwargs.pop('social', True)
ask_notify = kwargs.pop('ask_notify', False)
super().__init__(*args, **kwargs)
if not social:
for field in ['twitter', 'linkedin', 'github', 'website', 'facebook', 'mastodon']:
self.fields.pop(field)
if not ask_notify:
self.fields.pop('notify')
class Meta: class ParticipantForm(OnSiteNamedModelForm):
model = Participant SOCIAL_FIELDS = ['twitter', 'linkedin', 'github', 'website', 'facebook', 'mastodon']
fields = ['name', 'email', 'biography', 'twitter', 'linkedin', 'github', 'website', 'facebook', 'mastodon']
def clean_email(self): def clean_email(self):
email = self.cleaned_data['email'] email = self.cleaned_data['email']
@ -220,11 +210,6 @@ class ParticipantForm(OnSiteNamedModelForm):
return email return email
class ParticipantStaffForm(ParticipantForm):
class Meta(ParticipantForm.Meta):
fields = ['name', 'vip', 'email', 'phone_number', 'notes'] + ParticipantForm.Meta.fields[3:]
class ParticipantFilterForm(forms.Form): class ParticipantFilterForm(forms.Form):
category = forms.MultipleChoiceField( category = forms.MultipleChoiceField(
label=_('Category'), label=_('Category'),

View File

@ -115,6 +115,10 @@ class Participant(PonyConfModel):
objects = ParticipantManager() objects = ParticipantManager()
def get_absolute_url(self):
return reverse('participant-details', kwargs=dict(participant_id=self.token))
def get_secret_url(self, full=False): def get_secret_url(self, full=False):
url = reverse('proposal-dashboard', kwargs={'speaker_token': self.token}) url = reverse('proposal-dashboard', kwargs={'speaker_token': self.token})
if full: if full:

View File

@ -15,11 +15,13 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if not request.user.is_authenticated %}
<div class="col-md-8 col-md-offset-2 alert alert-info"> <div class="col-md-8 col-md-offset-2 alert alert-info">
<span class="glyphicon glyphicon-exclamation-sign"></span> <span class="glyphicon glyphicon-exclamation-sign"></span>
{% url 'proposal-mail-token' as mail_token_url %} {% url 'proposal-mail-token' as mail_token_url %}
{% blocktrans %}If you already have submitted a talk and you want to edit it or submit another one, please click <a href="{{ mail_token_url }}">here</a>.{% endblocktrans %} {% blocktrans %}If you already have submitted a talk and you want to edit it or submit another one, please click <a href="{{ mail_token_url }}">here</a>.{% endblocktrans %}
</div> </div>
{% endif %}
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2"> <form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
{% csrf_token %} {% csrf_token %}
{{ speaker_form|crispy }} {{ speaker_form|crispy }}

View File

@ -42,7 +42,8 @@
{% endif %} {% endif %}
<form method="POST" class="form-horizontal col-md-8 col-md-offset-2"> <form method="POST" class="form-horizontal col-md-8 col-md-offset-2">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ speaker_form|crispy }}
{% if notify_form %}{{ notify_form|crispy }}{% endif %}
<div class="col-md-12 text-center"> <div class="col-md-12 text-center">
<button type="submit" class="btn btn-primary text-center">{% trans "Save" %} <i class="fa fa-check"></i></button> <button type="submit" class="btn btn-primary text-center">{% trans "Save" %} <i class="fa fa-check"></i></button>
</div> </div>

View File

@ -7,18 +7,19 @@ urlpatterns = [
# v1.1 # v1.1
url(r'^cfp/$', views.proposal_home, name='proposal-home'), url(r'^cfp/$', views.proposal_home, name='proposal-home'),
url(r'^cfp/token/$', views.proposal_mail_token, name='proposal-mail-token'), url(r'^cfp/token/$', views.proposal_mail_token, name='proposal-mail-token'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/$', views.proposal_dashboard, name='proposal-dashboard'), url(r'^cfp/(?P<speaker_token>[\w\-]+)/$', views.proposal_dashboard), # backward compatibility
url(r'^cfp/(?P<speaker_token>[\w\-]+)/profile/$', views.proposal_speaker_edit, name='proposal-profile-edit'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?dashboard/$', views.proposal_dashboard, name='proposal-dashboard'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/add/$', views.proposal_talk_edit, name='proposal-talk-add'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?profile/$', views.proposal_speaker_edit, name='proposal-profile-edit'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/$', views.proposal_talk_details, name='proposal-talk-details'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/add/$', views.proposal_talk_edit, name='proposal-talk-add'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/edit/$', views.proposal_talk_edit, name='proposal-talk-edit'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/$', views.proposal_talk_details, name='proposal-talk-details'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/speaker/add/$', views.proposal_speaker_edit, name='proposal-speaker-add'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/edit/$', views.proposal_talk_edit, name='proposal-talk-edit'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/speaker/add/(?P<speaker_id>[0-9]+)/$', views.proposal_speaker_add, name='proposal-speaker-add-existing'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/speaker/add/$', views.proposal_speaker_edit, name='proposal-speaker-add'),
#url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/speaker/(?P<co_speaker_id>[0-9]+)/$', views.proposal_speaker_details, name='proposal-speaker-details'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/speaker/add/(?P<speaker_id>[0-9]+)/$', views.proposal_speaker_add, name='proposal-speaker-add-existing'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/speaker/(?P<co_speaker_id>[0-9]+)/edit/$', views.proposal_speaker_edit, name='proposal-speaker-edit'), #url(r'^cfp(?:/(?P<speaker_token>[\w\-]+))?/talk/(?P<talk_id>[0-9]+)/speaker/(?P<co_speaker_id>[0-9]+)/$', views.proposal_speaker_details, name='proposal-speaker-details'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/speaker/(?P<co_speaker_id>[0-9]+)/remove/$', views.proposal_speaker_remove, name='proposal-speaker-remove'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/speaker/(?P<co_speaker_id>[0-9]+)/edit/$', views.proposal_speaker_edit, name='proposal-speaker-edit'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/confirm/$', views.proposal_talk_acknowledgment, {'confirm': True}, name='proposal-talk-confirm'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/speaker/(?P<co_speaker_id>[0-9]+)/remove/$', views.proposal_speaker_remove, name='proposal-speaker-remove'),
url(r'^cfp/(?P<speaker_token>[\w\-]+)/talk/(?P<talk_id>[0-9]+)/desist/$', views.proposal_talk_acknowledgment, {'confirm': False}, name='proposal-talk-desist'), url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/confirm/$', views.proposal_talk_acknowledgment, {'confirm': True}, name='proposal-talk-confirm'),
url(r'^cfp/(?:(?P<speaker_token>[\w\-]+)/)?talk/(?P<talk_id>[0-9]+)/desist/$', views.proposal_talk_acknowledgment, {'confirm': False}, name='proposal-talk-desist'),
# Backward compatibility # Backward compatibility
url(r'^cfp/(?P<talk_id>[\w\-]+)/speaker/add/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-add'), url(r'^cfp/(?P<talk_id>[\w\-]+)/speaker/add/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-add'),
url(r'^cfp/(?P<talk_id>[\w\-]+)/speaker/(?P<participant_id>[\w\-]+)/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-edit'), url(r'^cfp/(?P<talk_id>[\w\-]+)/speaker/(?P<participant_id>[\w\-]+)/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-edit'),

View File

@ -12,6 +12,7 @@ from django.http import HttpResponse, Http404
from django.utils import timezone from django.utils import timezone
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail from django.core.mail import send_mail
from django.forms import modelform_factory
from django_select2.views import AutoResponseView from django_select2.views import AutoResponseView
@ -25,7 +26,7 @@ from .mixins import StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin
from .utils import is_staff from .utils import is_staff
from .models import Participant, Talk, TalkCategory, Vote, Track, Tag, Room, Volunteer, Activity from .models import Participant, Talk, TalkCategory, Vote, Track, Tag, Room, Volunteer, Activity
from .forms import TalkForm, TalkStaffForm, TalkFilterForm, TalkActionForm, \ from .forms import TalkForm, TalkStaffForm, TalkFilterForm, TalkActionForm, \
ParticipantForm, ParticipantStaffForm, ParticipantFilterForm, \ ParticipantForm, ParticipantFilterForm, NotifyForm, \
ConferenceForm, CreateUserForm, TrackForm, RoomForm, \ ConferenceForm, CreateUserForm, TrackForm, RoomForm, \
VolunteerForm, VolunteerFilterForm, MailForm, \ VolunteerForm, VolunteerFilterForm, MailForm, \
ACCEPTATION_VALUES, CONFIRMATION_VALUES ACCEPTATION_VALUES, CONFIRMATION_VALUES
@ -139,17 +140,29 @@ def volunteer_details(request, volunteer_id):
def proposal_home(request): def proposal_home(request):
if is_staff(request, request.user): categories = request.conference.opened_categories
categories = TalkCategory.objects.filter(site=request.conference.site)
else:
categories = request.conference.opened_categories
if not categories.exists(): if not categories.exists():
return render(request, 'cfp/closed.html') return render(request, 'cfp/closed.html')
speaker_form = ParticipantForm(request.POST or None, conference=request.conference, social=False) if request.user.is_authenticated():
if Participant.objects.filter(site=request.conference.site, email=request.user.email).exists():
return redirect(reverse('proposal-dashboard'))
else:
NewSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=['name', 'biography'])
if request.POST:
data = request.POST
else:
# TODO: import biography from User profile
data = dict(name=request.user.get_full_name())
else:
NewSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=['name', 'email', 'biography'])
data = request.POST or None
speaker_form = NewSpeakerForm(data, conference=request.conference)
talk_form = TalkForm(request.POST or None, categories=categories) 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])): if request.method == 'POST' and all(map(lambda f: f.is_valid(), [speaker_form, talk_form])):
speaker = speaker_form.save(commit=False) speaker = speaker_form.save(commit=False)
speaker.site = request.conference.site speaker.site = request.conference.site
if request.user.is_authenticated():
speaker.email = request.user.email
speaker.save() speaker.save()
talk = talk_form.save(commit=False) talk = talk_form.save(commit=False)
talk.site = request.conference.site talk.site = request.conference.site
@ -328,16 +341,20 @@ def proposal_speaker_edit(request, speaker, talk_id=None, co_speaker_id=None):
co_speaker = get_object_or_404(Participant, site=request.conference.site, talk__pk=talk.pk, pk=co_speaker_id) co_speaker = get_object_or_404(Participant, site=request.conference.site, talk__pk=talk.pk, pk=co_speaker_id)
else: else:
co_speaker_candidates = speaker.co_speaker_set.exclude(pk__in=talk.speakers.values_list('pk')) co_speaker_candidates = speaker.co_speaker_set.exclude(pk__in=talk.speakers.values_list('pk'))
form = ParticipantForm(request.POST or None, conference=request.conference, EditSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=['name', 'email', 'biography'] + ParticipantForm.SOCIAL_FIELDS)
instance=co_speaker if talk else speaker, ask_notify=talk and not co_speaker) speaker_form = EditSpeakerForm(request.POST or None, conference=request.conference, instance=co_speaker if talk else speaker)
if request.method == 'POST' and form.is_valid(): if talk and not co_speaker_id:
edited_speaker = form.save() notify_form = NotifyForm(request.POST or None)
else:
notify_form = None
if request.method == 'POST' and all(map(lambda f: f.is_valid(), [speaker_form, notify_form])):
edited_speaker = speaker_form.save()
if talk: if talk:
talk.speakers.add(edited_speaker) talk.speakers.add(edited_speaker)
if co_speaker_id: if co_speaker_id:
messages.success(request, _('Changes saved.')) messages.success(request, _('Changes saved.'))
else: else:
if form.cleaned_data['notify']: if notify_form.cleaned_data['notify']:
base_url = ('https' if request.is_secure() else 'http') + '://' + request.conference.site.domain 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_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_talk_details = base_url + reverse('proposal-talk-details', kwargs=dict(speaker_token=edited_speaker.token, talk_id=talk.pk))
@ -383,7 +400,8 @@ Thanks!
'talk': talk, 'talk': talk,
'co_speaker': co_speaker, 'co_speaker': co_speaker,
'co_speaker_candidates': co_speaker_candidates, 'co_speaker_candidates': co_speaker_candidates,
'form': form, 'speaker_form': speaker_form,
'notify_form': notify_form,
}) })
@ -697,13 +715,20 @@ def participant_details(request, participant_id):
}) })
class ParticipantUpdate(StaffRequiredMixin, OnSiteMixin, UpdateView): class ParticipantUpdate(StaffRequiredMixin, OnSiteFormMixin, UpdateView):
model = Participant model = Participant
slug_field = 'token' slug_field = 'token'
slug_url_kwarg = 'participant_id' slug_url_kwarg = 'participant_id'
form_class = ParticipantStaffForm #form_class = ParticipantStaffForm
template_name = 'cfp/staff/participant_form.html' 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', 'notes'] + ParticipantForm.SOCIAL_FIELDS,
)
@staff_required @staff_required
def conference_edit(request): def conference_edit(request):