diff --git a/cfp/decorators.py b/cfp/decorators.py index 40964bc..ffab13f 100644 --- a/cfp/decorators.py +++ b/cfp/decorators.py @@ -11,9 +11,12 @@ from cfp.models import Participant def speaker_required(view_func): def wrapped_view(request, **kwargs): speaker_token = kwargs.pop('speaker_token') - # TODO v3: if no speaker token is provided, we should check for a logged user, and if so, - # we should check if his/her participating at current conference - speaker = get_object_or_404(Participant, site=request.conference.site, token=speaker_token) + if 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 return view_func(request, **kwargs) return wraps(view_func)(wrapped_view) diff --git a/cfp/forms.py b/cfp/forms.py index ec862e1..09892a3 100644 --- a/cfp/forms.py +++ b/cfp/forms.py @@ -195,22 +195,12 @@ class TalkActionForm(forms.Form): 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?')) - 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: - model = Participant - fields = ['name', 'email', 'biography', 'twitter', 'linkedin', 'github', 'website', 'facebook', 'mastodon'] +class ParticipantForm(OnSiteNamedModelForm): + SOCIAL_FIELDS = ['twitter', 'linkedin', 'github', 'website', 'facebook', 'mastodon'] def clean_email(self): email = self.cleaned_data['email'] @@ -220,11 +210,6 @@ class ParticipantForm(OnSiteNamedModelForm): return email -class ParticipantStaffForm(ParticipantForm): - class Meta(ParticipantForm.Meta): - fields = ['name', 'vip', 'email', 'phone_number', 'notes'] + ParticipantForm.Meta.fields[3:] - - class ParticipantFilterForm(forms.Form): category = forms.MultipleChoiceField( label=_('Category'), diff --git a/cfp/models.py b/cfp/models.py index f35d182..7eefe79 100644 --- a/cfp/models.py +++ b/cfp/models.py @@ -115,6 +115,10 @@ class Participant(PonyConfModel): objects = ParticipantManager() + + def get_absolute_url(self): + return reverse('participant-details', kwargs=dict(participant_id=self.token)) + def get_secret_url(self, full=False): url = reverse('proposal-dashboard', kwargs={'speaker_token': self.token}) if full: diff --git a/cfp/templates/cfp/proposal_home.html b/cfp/templates/cfp/proposal_home.html index 6622729..88ade6f 100644 --- a/cfp/templates/cfp/proposal_home.html +++ b/cfp/templates/cfp/proposal_home.html @@ -15,11 +15,13 @@
+ {% if not request.user.is_authenticated %}
{% 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 here.{% endblocktrans %}
+ {% endif %}
{% csrf_token %} {{ speaker_form|crispy }} diff --git a/cfp/templates/cfp/proposal_speaker_form.html b/cfp/templates/cfp/proposal_speaker_form.html index baa8090..9ccd4df 100644 --- a/cfp/templates/cfp/proposal_speaker_form.html +++ b/cfp/templates/cfp/proposal_speaker_form.html @@ -42,7 +42,8 @@ {% endif %} {% csrf_token %} - {{ form|crispy }} + {{ speaker_form|crispy }} + {% if notify_form %}{{ notify_form|crispy }}{% endif %}
diff --git a/cfp/urls.py b/cfp/urls.py index 41067d8..c096d03 100644 --- a/cfp/urls.py +++ b/cfp/urls.py @@ -7,18 +7,19 @@ urlpatterns = [ # v1.1 url(r'^cfp/$', views.proposal_home, name='proposal-home'), url(r'^cfp/token/$', views.proposal_mail_token, name='proposal-mail-token'), - url(r'^cfp/(?P[\w\-]+)/$', views.proposal_dashboard, name='proposal-dashboard'), - url(r'^cfp/(?P[\w\-]+)/profile/$', views.proposal_speaker_edit, name='proposal-profile-edit'), - url(r'^cfp/(?P[\w\-]+)/talk/add/$', views.proposal_talk_edit, name='proposal-talk-add'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/$', views.proposal_talk_details, name='proposal-talk-details'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/edit/$', views.proposal_talk_edit, name='proposal-talk-edit'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/speaker/add/$', views.proposal_speaker_edit, name='proposal-speaker-add'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/speaker/add/(?P[0-9]+)/$', views.proposal_speaker_add, name='proposal-speaker-add-existing'), - #url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/speaker/(?P[0-9]+)/$', views.proposal_speaker_details, name='proposal-speaker-details'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/speaker/(?P[0-9]+)/edit/$', views.proposal_speaker_edit, name='proposal-speaker-edit'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/speaker/(?P[0-9]+)/remove/$', views.proposal_speaker_remove, name='proposal-speaker-remove'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/confirm/$', views.proposal_talk_acknowledgment, {'confirm': True}, name='proposal-talk-confirm'), - url(r'^cfp/(?P[\w\-]+)/talk/(?P[0-9]+)/desist/$', views.proposal_talk_acknowledgment, {'confirm': False}, name='proposal-talk-desist'), + url(r'^cfp/(?P[\w\-]+)/$', views.proposal_dashboard), # backward compatibility + url(r'^cfp/(?:(?P[\w\-]+)/)?dashboard/$', views.proposal_dashboard, name='proposal-dashboard'), + url(r'^cfp/(?:(?P[\w\-]+)/)?profile/$', views.proposal_speaker_edit, name='proposal-profile-edit'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/add/$', views.proposal_talk_edit, name='proposal-talk-add'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/$', views.proposal_talk_details, name='proposal-talk-details'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/edit/$', views.proposal_talk_edit, name='proposal-talk-edit'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/speaker/add/$', views.proposal_speaker_edit, name='proposal-speaker-add'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/speaker/add/(?P[0-9]+)/$', views.proposal_speaker_add, name='proposal-speaker-add-existing'), + #url(r'^cfp(?:/(?P[\w\-]+))?/talk/(?P[0-9]+)/speaker/(?P[0-9]+)/$', views.proposal_speaker_details, name='proposal-speaker-details'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/speaker/(?P[0-9]+)/edit/$', views.proposal_speaker_edit, name='proposal-speaker-edit'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/speaker/(?P[0-9]+)/remove/$', views.proposal_speaker_remove, name='proposal-speaker-remove'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/confirm/$', views.proposal_talk_acknowledgment, {'confirm': True}, name='proposal-talk-confirm'), + url(r'^cfp/(?:(?P[\w\-]+)/)?talk/(?P[0-9]+)/desist/$', views.proposal_talk_acknowledgment, {'confirm': False}, name='proposal-talk-desist'), # Backward compatibility url(r'^cfp/(?P[\w\-]+)/speaker/add/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-add'), url(r'^cfp/(?P[\w\-]+)/speaker/(?P[\w\-]+)/$', views.talk_proposal_speaker_edit, name='talk-proposal-speaker-edit'), diff --git a/cfp/views.py b/cfp/views.py index b02ad68..684d5c0 100644 --- a/cfp/views.py +++ b/cfp/views.py @@ -12,6 +12,7 @@ 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 @@ -25,7 +26,7 @@ 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, ParticipantStaffForm, ParticipantFilterForm, \ + ParticipantForm, ParticipantFilterForm, NotifyForm, \ ConferenceForm, CreateUserForm, TrackForm, RoomForm, \ VolunteerForm, VolunteerFilterForm, MailForm, \ ACCEPTATION_VALUES, CONFIRMATION_VALUES @@ -139,17 +140,29 @@ def volunteer_details(request, volunteer_id): def proposal_home(request): - if is_staff(request, request.user): - categories = TalkCategory.objects.filter(site=request.conference.site) - else: - categories = request.conference.opened_categories + categories = request.conference.opened_categories if not categories.exists(): 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) 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 @@ -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) else: co_speaker_candidates = speaker.co_speaker_set.exclude(pk__in=talk.speakers.values_list('pk')) - form = ParticipantForm(request.POST or None, conference=request.conference, - instance=co_speaker if talk else speaker, ask_notify=talk and not co_speaker) - if request.method == 'POST' and form.is_valid(): - edited_speaker = form.save() + EditSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=['name', 'email', 'biography'] + ParticipantForm.SOCIAL_FIELDS) + speaker_form = EditSpeakerForm(request.POST or None, conference=request.conference, instance=co_speaker if talk else speaker) + if talk and not co_speaker_id: + 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: talk.speakers.add(edited_speaker) if co_speaker_id: messages.success(request, _('Changes saved.')) 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 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)) @@ -383,7 +400,8 @@ Thanks! 'talk': talk, 'co_speaker': co_speaker, '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 slug_field = 'token' slug_url_kwarg = 'participant_id' - form_class = ParticipantStaffForm + #form_class = ParticipantStaffForm 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 def conference_edit(request):