workshop attendee: subscription

This commit is contained in:
Élie Bouttier 2016-11-08 21:05:56 +01:00
parent 71103d5f6b
commit 483a794d23
9 changed files with 171 additions and 16 deletions

View File

@ -29,9 +29,13 @@
</li> </li>
</ul> </ul>
</li> </li>
{% endif %}
{% if conference.subscriptions_open %}
<li{% block subscriptiontab %}{% endblock %}><a href="{% url 'subscriptions-list' %}"><span class="glyphicon glyphicon-check"></span>&nbsp;{% trans "Subscribe to a workshop" %}</a></li>
{% endif %}
{% if request.user.is_authenticated %}
{% if not request|staff %}
<li{% block topictab %}{% endblock %}><a href="{% url 'list-topics' %}">{% trans "Topics" %}</a></li> <li{% block topictab %}{% endblock %}><a href="{% url 'list-topics' %}">{% trans "Topics" %}</a></li>
{% if request|staff %}
<li{% block tracktab %}{% endblock %}><a href="{% url 'list-tracks' %}">{% trans "Tracks" %}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% block navbar-left %}{% endblock %} {% block navbar-left %}{% endblock %}
@ -51,6 +55,12 @@
<li role="presentation"> <li role="presentation">
<a role="menuitem" tabindex="-1" href="{% url 'list-volunteers' %}"><span class="glyphicon glyphicon-thumbs-up"></span>&nbsp;{% trans "Volunteers" %}</a> <a role="menuitem" tabindex="-1" href="{% url 'list-volunteers' %}"><span class="glyphicon glyphicon-thumbs-up"></span>&nbsp;{% trans "Volunteers" %}</a>
</li> </li>
<li role="presentation">
<a role="menuitem" tabindex="-1" href="{% url 'list-topics' %}"><span class="glyphicon glyphicon-tag"></span>&nbsp;{% trans "Topics" %}</a></li>
</li>
<li role="presentation">
<a role="menuitem" tabindex="-1" href="{% url 'list-tracks' %}"><span class="glyphicon glyphicon-screenshot"></span>&nbsp;{% trans "Tracks" %}</a></li>
</li>
</ul> </ul>
</li> </li>
<li class="dropdown{% block planningtab %}{% endblock %}"> <li class="dropdown{% block planningtab %}{% endblock %}">

View File

@ -1,7 +1,7 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.sites.shortcuts import get_current_site from django.contrib.sites.shortcuts import get_current_site
from proposals.models import Conference, Talk, Topic, Track, Event from proposals.models import Conference, Talk, Topic, Track, Event, Attendee
from planning.models import Room from planning.models import Room
from ponyconf.admin import SiteAdminMixin from ponyconf.admin import SiteAdminMixin
@ -35,8 +35,13 @@ class EventAdmin(SiteAdminMixin, admin.ModelAdmin):
pass pass
class AttendeeAdmin(admin.ModelAdmin):
list_display = ('get_name', 'get_email')
admin.site.register(Conference) admin.site.register(Conference)
admin.site.register(Topic, TopicAdmin) admin.site.register(Topic, TopicAdmin)
admin.site.register(Track, TrackAdmin) admin.site.register(Track, TrackAdmin)
admin.site.register(Talk, TalkAdmin) admin.site.register(Talk, TalkAdmin)
admin.site.register(Event, EventAdmin) admin.site.register(Event, EventAdmin)
admin.site.register(Attendee, AttendeeAdmin)

View File

@ -179,6 +179,18 @@ class TrackForm(forms.ModelForm):
return name return name
class SubscribeForm(forms.Form):
email = forms.EmailField()
name = forms.CharField(max_length=128, label=_('Name or nickname'))
captcha = forms.IntegerField(label=_('How much is 3+4?'), help_text=_('Anti-bot'))
def clean_captcha(self):
value = self.cleaned_data['captcha']
if value != 7:
raise forms.ValidationError(_("Please re-do the maths."))
return value
ConferenceForm = modelform_factory(Conference, ConferenceForm = modelform_factory(Conference,
fields=['cfp_opening_date', 'cfp_closing_date', 'subscriptions_open', 'venue', 'city', 'home'], fields=['cfp_opening_date', 'cfp_closing_date', 'subscriptions_open', 'venue', 'city', 'home'],
widgets={ widgets={

View File

@ -112,16 +112,21 @@ class Attendee(PonyConfModel):
email = models.EmailField(blank=True, default="") email = models.EmailField(blank=True, default="")
def get_name(self): def get_name(self):
if user: if self.user:
return str(user.profile) return str(self.user.profile)
else: else:
return name return self.name
get_name.short_description = _('Name')
def get_email(self): def get_email(self):
if user: if self.user:
return user.email return self.user.email
else: else:
return self.email return self.email
get_email.short_description = _('Email')
def __str__(self):
return self.get_name()
class Talk(PonyConfModel): class Talk(PonyConfModel):

View File

@ -0,0 +1,36 @@
{% extends 'base.html' %}
{% load accounts_tags i18n %}
{% block subscriptiontab %} class="active"{% endblock %}
{% block content %}
<h1>{% trans "Subscribe to a workshop" %}</h1>
{% for talk in talks %}
{% if forloop.first %}<div class="list-group">{% endif %}
<div class="list-group-item{% if attendee in talk.attendees.all %} list-group-item-info{% endif %}">
<h4 clas="list-group-item-heading">{{ talk.title }}</h4>
<p class="list-group-item-text">
<p>{{ talk.description }}</p>
{% if talk.attendees_limit %}
<p><em>{% blocktrans count remaining=talk.remaining_attendees %}{{ remaining }} remaining seat{% plural %}{{ remaining }} remaining seats{% endblocktrans %}</em></p>
{% endif %}
{% if talk.remaining_attendees != 0 or attendee in talk.attendees.all %}
<p>
{% if attendee in talk.attendees.all %}
<a class="btn btn-danger" href="{% url 'subscribe-to-talk' talk=talk.slug %}">{% trans "Unregister" %}</a>
{% else %}
<a class="btn btn-primary" href="{% url 'subscribe-to-talk' talk=talk.slug %}">{% trans "Register" %}</a>
{% endif %}
</p>
{% endif %}
</p>
</div>
{% if forloop.last %}</div>{% endif %}
{% empty %}
<em>{% trans "There are no workshops requiring registration for now … come back later!" %}</em>
{% endfor %}
{% endblock %}

View File

@ -48,6 +48,10 @@
{% else %}<em>{% trans "not defined" %}</em> {% else %}<em>{% trans "not defined" %}</em>
{% endif %} {% endif %}
</dd> </dd>
{% if talk.registration_required %}
<dt>{% trans "Registrations" %}</dt>
<dd>{% if talk.attendees_limit %}{{ talk.attendees.count }} / {{ talk.attendees_limit }}{% else %}{% trans "required but unlimited" %}{% endif %}</dd>
{% endif %}
</dl> </dl>
@ -111,6 +115,19 @@
<a href="{% url 'decline-talk' talk.slug %}" class="btn btn-danger">Decline</a> <a href="{% url 'decline-talk' talk.slug %}" class="btn btn-danger">Decline</a>
{% endif %} {% endif %}
{% if talk.registration_required %}
<h3>{% trans "Attendees" %}</h3>
{% for attendee in talk.attendees.all %}
{% if forloop.first %}<ol>{% endif %}
<li><a href="mailto:{{ attendee.get_email }}">{{ attendee.get_name }}</a></li>
{% if forloop.last %}</ol>{% endif %}
{% empty %}
<em>{% trans "No attendees yet." %}</em>
{% endfor %}
{% endif %}
<h3>{% trans "Messages" %}</h3> <h3>{% trans "Messages" %}</h3>
{% trans "These messages are for organization team only." %}<br /><br /> {% trans "These messages are for organization team only." %}<br /><br />
{% for message in talk.conversation.messages.all %} {% for message in talk.conversation.messages.all %}

View File

@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% load bootstrap3 i18n %}
{% block subscriptiontab %} class="active"{% endblock %}
{% block css %}
{{ block.super }}
{{ form.media.css }}
{% endblock %}
{% block content %}
<h1>{% trans "Subscribe to a workshop" %}</h1>
{% include "_form.html" %}
{% endblock %}
{% block js_end %}
{{ block.super }}
{{ form.media.js }}
{% endblock %}

View File

@ -23,4 +23,6 @@ urlpatterns = [
url(r'^track/(?P<slug>[-\w]+)/edit/$', views.TrackUpdate.as_view(), name='edit-track'), url(r'^track/(?P<slug>[-\w]+)/edit/$', views.TrackUpdate.as_view(), name='edit-track'),
url(r'^speakers/$', views.speaker_list, name='list-speakers'), url(r'^speakers/$', views.speaker_list, name='list-speakers'),
url(r'^speaker/(?P<username>[\w.@+-]+)$', views.user_details, name='show-speaker'), url(r'^speaker/(?P<username>[\w.@+-]+)$', views.user_details, name='show-speaker'),
url(r'^subscribe/$', views.talk_subscriptions, name='subscriptions-list'),
url(r'^subscribe/(?P<talk>[-\w]+)$', views.talk_subscribe, name='subscribe-to-talk'),
] ]

View File

@ -12,7 +12,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.views.generic import CreateView, DetailView, ListView, UpdateView from django.views.generic import CreateView, DetailView, ListView, UpdateView
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.http import HttpResponse from django.http import HttpResponse, Http404
from ponyconf.mixins import OnSiteFormMixin from ponyconf.mixins import OnSiteFormMixin
@ -25,8 +25,8 @@ from conversations.models import ConversationWithParticipant, ConversationAboutT
from planning.models import Room from planning.models import Room
from .forms import TalkForm, TopicForm, TrackForm, ConferenceForm, TalkFilterForm, STATUS_VALUES, SpeakerFilterForm, TalkActionForm from .forms import TalkForm, TopicForm, TrackForm, ConferenceForm, TalkFilterForm, STATUS_VALUES, SpeakerFilterForm, TalkActionForm, SubscribeForm
from .models import Talk, Track, Topic, Vote, Conference from .models import Talk, Track, Topic, Vote, Conference, Attendee
from .signals import talk_added, talk_edited from .signals import talk_added, talk_edited
from .utils import allowed_talks, markdown_to_html from .utils import allowed_talks, markdown_to_html
@ -179,11 +179,11 @@ def talk_edit(request, talk=None):
if talk: # edit existing talk if talk: # edit existing talk
talk = get_object_or_404(Talk, slug=talk, site=site) talk = get_object_or_404(Talk, slug=talk, site=site)
if not talk.is_editable_by(request.user): if not talk.is_editable_by(request.user):
raise PermissionDenied() raise PermissionDenied
else: # add new talk else: # add new talk
conf = Conference.objects.get(site=site) conf = Conference.objects.get(site=site)
if not is_orga(request, request.user) and not conf.cfp_is_open(): if not is_orga(request, request.user) and not conf.cfp_is_open():
raise PermissionDenied() raise PermissionDenied
form = TalkForm(request.POST or None, instance=talk, site=site) form = TalkForm(request.POST or None, instance=talk, site=site)
if talk: if talk:
form.fields['topics'].disabled = True form.fields['topics'].disabled = True
@ -225,7 +225,7 @@ def talk_edit(request, talk=None):
def talk_assign_to_track(request, talk, track): def talk_assign_to_track(request, talk, track):
talk = get_object_or_404(Talk, slug=talk, site=get_current_site(request)) talk = get_object_or_404(Talk, slug=talk, site=get_current_site(request))
if not talk.is_moderable_by(request.user): if not talk.is_moderable_by(request.user):
raise PermissionDenied() raise PermissionDenied
track = get_object_or_404(Track, slug=track, site=get_current_site(request)) track = get_object_or_404(Track, slug=track, site=get_current_site(request))
talk.track = track talk.track = track
talk.save() talk.save()
@ -294,7 +294,7 @@ class TrackUpdate(OrgaRequiredMixin, TrackMixin, TrackFormMixin, UpdateView):
def vote(request, talk, score): def vote(request, talk, score):
talk = get_object_or_404(Talk, site=get_current_site(request), slug=talk) talk = get_object_or_404(Talk, site=get_current_site(request), slug=talk)
if not talk.is_moderable_by(request.user): if not talk.is_moderable_by(request.user):
raise PermissionDenied() raise PermissionDenied
vote, created = Vote.objects.get_or_create(talk=talk, user=request.user) vote, created = Vote.objects.get_or_create(talk=talk, user=request.user)
vote.vote = int(score) vote.vote = int(score)
vote.save() vote.save()
@ -307,7 +307,7 @@ def talk_decide(request, talk, accepted):
site = get_current_site(request) site = get_current_site(request)
talk = get_object_or_404(Talk, site=site, slug=talk) talk = get_object_or_404(Talk, site=site, slug=talk)
if not talk.is_moderable_by(request.user): if not talk.is_moderable_by(request.user):
raise PermissionDenied() raise PermissionDenied
if request.method == 'POST': if request.method == 'POST':
# Does we need to send a notification to the proposer? # Does we need to send a notification to the proposer?
m = request.POST.get('message', '').strip() m = request.POST.get('message', '').strip()
@ -401,3 +401,48 @@ def user_details(request, username):
'participation': participation, 'participation': participation,
'talk_list': allowed_talks(Talk.objects.filter(site=get_current_site(request), speakers=user), request), 'talk_list': allowed_talks(Talk.objects.filter(site=get_current_site(request), speakers=user), request),
}) })
def talk_subscriptions(request):
site = get_current_site(request)
if not Conference.objects.get(site=site).subscriptions_open:
raise Http404
talks = Talk.objects.filter(site=site, registration_required=True)
if request.user.is_authenticated():
attendee = Attendee.objects.filter(user=request.user).first() # None if it does not exists
else:
attendee = None
return render(request, 'proposals/subscriptions_list.html', {
'talks': talks,
'attendee': attendee,
})
def talk_subscribe(request, talk):
talk = get_object_or_404(Talk, site=get_current_site(request), registration_required=True, slug=talk)
form = SubscribeForm(request.POST or None)
if request.user.is_authenticated() or (request.method == 'POST' and form.is_valid()):
if request.user.is_authenticated():
attendee, created = Attendee.objects.get_or_create(user=request.user)
else:
attendee, created = Attendee.objects.get_or_create(email=form.cleaned_data['email'], name=form.cleaned_data['name'])
if attendee in talk.attendees.all():
if request.user.is_authenticated():
talk.attendees.remove(attendee)
messages.success(request, _("Unregistered :-("))
else:
messages.error(request, _("Already registered!"))
elif talk.remaining_attendees == 0:
raise PermissionDenied
else:
talk.attendees.add(attendee)
messages.success(request, _("Registered!"))
talk.save()
return redirect('subscriptions-list')
return render(request, 'proposals/talk_subscribe.html', {
'talk': talk,
'form': form,
})