confirmation of talk acceptance by speakers

This commit is contained in:
Élie Bouttier 2017-10-06 18:45:58 +02:00
parent 10db5642df
commit 69d4dbbb70
8 changed files with 145 additions and 30 deletions

View File

@ -12,17 +12,28 @@ from django_select2.forms import ModelSelect2MultipleWidget
from .models import Participant, Talk, TalkCategory, Track, Conference, Room, Volunteer
STATUS_CHOICES = [
ACCEPTATION_CHOICES = [
('pending', _('Pending decision')),
('accepted', _('Accepted')),
('declined', _('Declined')),
]
STATUS_VALUES = [
ACCEPTATION_VALUES = [
('pending', None),
('accepted', True),
('declined', False),
]
CONFIRMATION_CHOICES = [
('waiting', _('Waiting')),
('confirmed', _('Confirmed')),
('desisted', _('Desisted')),
]
CONFIRMATION_VALUES = [
('waiting', None),
('confirmed', True),
('desisted', False),
]
class TalkForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
@ -69,11 +80,17 @@ class TalkFilterForm(forms.Form):
widget=forms.CheckboxSelectMultiple,
choices=[],
)
status = forms.MultipleChoiceField(
label=_('Status'),
accepted = forms.MultipleChoiceField(
label=_('Accepted'),
required=False,
widget=forms.CheckboxSelectMultiple,
choices=STATUS_CHOICES,
choices=ACCEPTATION_CHOICES,
)
confirmed = forms.MultipleChoiceField(
label=_('Confirmed'),
required=False,
widget=forms.CheckboxSelectMultiple,
choices=CONFIRMATION_CHOICES,
)
track = forms.MultipleChoiceField(
label=_('Track'),
@ -145,11 +162,17 @@ class ParticipantFilterForm(forms.Form):
widget=forms.CheckboxSelectMultiple,
choices=[],
)
status = forms.MultipleChoiceField(
label=_('Status'),
accepted = forms.MultipleChoiceField(
label=_('Accepted'),
required=False,
widget=forms.CheckboxSelectMultiple,
choices=STATUS_CHOICES,
choices=ACCEPTATION_CHOICES,
)
confirmed = forms.MultipleChoiceField(
label=_('Confirmed'),
required=False,
widget=forms.CheckboxSelectMultiple,
choices=CONFIRMATION_CHOICES,
)
track = forms.MultipleChoiceField(
label=_('Track'),

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-10-06 16:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cfp', '0011_auto_20171005_2328'),
]
operations = [
migrations.AddField(
model_name='talk',
name='confirmed',
field=models.NullBooleanField(default=None),
),
]

View File

@ -243,6 +243,7 @@ class TalkManager(models.Manager):
qs = qs.annotate(score=Coalesce(Avg('vote__vote'), 0))
return qs
def talks_materials_destination(talk, filename):
return join(talk.site.name, talk.slug, filename)
@ -258,7 +259,6 @@ class Talk(PonyConfModel):
)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
speakers = models.ManyToManyField(Participant, verbose_name=_('Speakers'))
title = models.CharField(max_length=128, verbose_name=_('Talk Title'))
slug = AutoSlugField(populate_from='title', unique=True)
@ -276,6 +276,7 @@ class Talk(PonyConfModel):
max_length=10, verbose_name=_("Video licence"))
sound = models.BooleanField(_("I need sound"), default=False)
accepted = models.NullBooleanField(default=None)
confirmed = models.NullBooleanField(default=None)
start_date = models.DateTimeField(null=True, blank=True, default=None, verbose_name=_('Beginning date and time'))
duration = models.PositiveIntegerField(default=0, verbose_name=_('Duration (min)'))
room = models.ForeignKey(Room, blank=True, null=True, default=None)
@ -283,15 +284,11 @@ class Talk(PonyConfModel):
materials = models.FileField(null=True, upload_to=talks_materials_destination, verbose_name=_('Materials'),
help_text=_('You can use this field to share some materials related to your intervention.'))
video = models.URLField(max_length=1000, blank=True, default='', verbose_name='Video URL')
token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
conversation = models.OneToOneField(MessageThread)
objects = TalkManager()
class Meta:
ordering = ('title',)
@ -307,6 +304,32 @@ class Talk(PonyConfModel):
else:
return ', '.join(speakers[:-1]) + ' & ' + str(speakers[-1])
def get_status_str(self):
if self.accepted is True:
if self.confirmed is True:
return _('Confirmed')
elif self.confirmed is False:
return _('Cancelled')
else:
return _('Waiting confirmation')
elif self.accepted is False:
return _('Refused')
else:
return _('Pending decision, score: %(score).1f') % {'score': self.score}
def get_status_color(self):
if self.accepted is True:
if self.confirmed is True:
return 'success'
elif self.confirmed is False:
return 'danger'
else:
return 'info'
elif self.accepted is False:
return 'muted'
else:
return 'warning'
@property
def estimated_duration(self):
return self.duration or self.category.duration

View File

@ -15,7 +15,8 @@
<div class="row">
<div class="col-md-6">
{% bootstrap_field filter_form.category layout="horizontal" %}
{% bootstrap_field filter_form.status layout="horizontal" %}
{% bootstrap_field filter_form.accepted layout="horizontal" %}
{% bootstrap_field filter_form.confirmed layout="horizontal" %}
</div>
<div class="col-md-6">
{% bootstrap_field filter_form.track layout="horizontal" %}

View File

@ -16,7 +16,7 @@
<dd><a href="{{ talk.category.get_absolute_url }}">{{ talk.category }}</a></dd>
<dt>{% trans "Status" %}</dt>
<dd><span class="label label-{{ talk.accepted|yesno:"success,danger,warning" }}">{{ talk.accepted|yesno:"Accepted,Declined,Pending decision" }}</span></dd>
<dd><span class="label label-{{ talk.get_status_color }}">{{ talk.get_status_str }}</span></dd>
<dt>{% trans "Track" %}</dt>
<dd>{% if talk.track %}

View File

@ -14,7 +14,8 @@
<form class="form-horizontal" method="get">
<div class="row">
<div class="col-md-6 col-xs-6">
{% bootstrap_field filter_form.status layout="horizontal" %}
{% bootstrap_field filter_form.accepted layout="horizontal" %}
{% bootstrap_field filter_form.confirmed layout="horizontal" %}
{% bootstrap_field filter_form.category layout="horizontal" %}
{% bootstrap_field filter_form.vote layout="horizontal" %}
{% bootstrap_field filter_form.scheduled layout="horizontal" %}
@ -49,7 +50,7 @@
{% if forloop.first %}
<tbody>
{% endif %}
<tr class="{{ talk.accepted|yesno:"success,danger,warning" }}">
<tr class="{{ talk.get_status_color }}">
<td><input type="checkbox" name="talks" value="{{ talk.token }}"></td>
<td><a href="{% url 'talk-details' talk.token %}">{{ talk.title }}</a></td>
<td>{{ talk.category }}</td>
@ -62,13 +63,7 @@
</td>
<td>{{ talk.track|default:"" }}</td>
<td>
{% if talk.accepted == True %}
{% trans "Accepted" %}
{% elif talk.accepted == False %}
{% trans "Declined" %}
{% else %}
{% blocktrans with score=talk.score|floatformat:1 %}Pending, score: {{ score }}{% endblocktrans %}
{% endif %}
{{ talk.get_status_str }}
</td>
</tr>
{% if forloop.last%}

View File

@ -8,6 +8,8 @@ urlpatterns = [
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\-]+)/(?P<participant_id>[\w\-]+)/$', views.talk_proposal, name='talk-proposal-edit'),
url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/confirm/$', views.talk_acknowledgment, {'confirm': True}, name='talk-confirm'),
url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/desist/$', views.talk_acknowledgment, {'confirm': False}, name='talk-desist'),
url(r'^volunteer/$', views.volunteer_enrole, name='volunteer-enrole'),
url(r'^volunteer/(?P<volunteer_id>[\w\-]+)/$', views.volunteer, name='volunteer'),
url(r'^volunteer/(?P<volunteer_id>[\w\-]+)/join/(?P<activity>[\w\-]+)/$', views.volunteer_activity, {'join': True}, name='volunteer-join'),
@ -19,6 +21,8 @@ urlpatterns = [
url(r'^staff/talks/(?P<talk_id>[\w\-]+)/vote/(?P<score>[-+0-2]+)/$', views.talk_vote, name='talk-vote'),
url(r'^staff/talks/(?P<talk_id>[\w\-]+)/accept/$', views.talk_decide, {'accept': True}, name='talk-accept'),
url(r'^staff/talks/(?P<talk_id>[\w\-]+)/decline/$', views.talk_decide, {'accept': False}, name='talk-decline'),
url(r'^staff/talks/(?P<talk_id>[\w\-]+)/confirm/$', views.talk_acknowledgment, {'confirm': True}, name='talk-confirm-by-staff'),
url(r'^staff/talks/(?P<talk_id>[\w\-]+)/desist/$', views.talk_acknowledgment, {'confirm': False}, name='talk-cancel-by-staff'),
url(r'^staff/talks/(?P<talk_id>[\w\-]+)/edit/$', views.TalkUpdate.as_view(), name='talk-edit'),
url(r'^staff/speakers/$', views.participant_list, name='participant-list'),
url(r'^staff/speakers/(?P<participant_id>[\w\-]+)/$', views.participant_details, name='participant-details'),

View File

@ -25,8 +25,8 @@ from .utils import is_staff
from .models import Participant, Talk, TalkCategory, Vote, Track, Room, Volunteer, Activity
from .forms import TalkForm, TalkStaffForm, TalkFilterForm, TalkActionForm, \
ParticipantForm, ParticipantStaffForm, ParticipantFilterForm, \
ConferenceForm, CreateUserForm, STATUS_VALUES, TrackForm, RoomForm, \
VolunteerForm
ConferenceForm, CreateUserForm, TrackForm, RoomForm, VolunteerForm, \
ACCEPTATION_VALUES, CONFIRMATION_VALUES
def home(request):
@ -177,6 +177,47 @@ def talk_proposal_speaker_edit(request, talk_id, participant_id=None):
})
def talk_acknowledgment(request, talk_id, confirm, participant_id=None):
# TODO: handle multiple speakers case
talk = get_object_or_404(Talk, token=talk_id, site=request.conference.site)
if participant_id:
participant = get_object_or_404(Participant, token=participant_id, site=request.conference.site)
elif not is_staff(request, request.user):
raise PermissionDenied
else:
participant = None
if not talk.accepted:
raise PermissionDenied
if talk.confirmed != confirm:
talk.confirmed = confirm
talk.save()
if confirm:
confirmation_message= _('Your participation has been taken into account, thank you!')
if participant:
thread_note = _('Speaker %(speaker)s confirmed his/her participation.')
else:
thread_note = _('The talk have been confirmed.')
else:
confirmation_message = _('We have noted your unavailability.')
if participant:
thread_note = _('Speaker %(speaker)s CANCELLED his/her participation.')
else:
thread_note = _('The talk have been cancelled.')
if participant_id:
thread_note = thread_note % {'speaker': participant}
Message.objects.create(thread=talk.conversation, author=participant or request.user, content=thread_note)
messages.success(request, confirmation_message)
else:
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.'))
if participant:
return redirect(reverse('talk-proposal-edit', kwargs=dict(talk_id=talk_id, participant_id=participant_id)))
else:
return redirect(reverse('talk-details', kwargs=dict(talk_id=talk_id)))
@staff_required
def staff(request):
return render(request, 'cfp/staff/base.html')
@ -193,9 +234,13 @@ def talk_list(request):
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['status']):
if len(data['accepted']):
show_filters = True
talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(STATUS_VALUES)[status]) for status in data['status']]))
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'])
@ -355,9 +400,13 @@ def participant_list(request):
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['status']):
if len(data['accepted']):
show_filters = True
talks = talks.filter(reduce(lambda x, y: x | y, [Q(accepted=dict(STATUS_VALUES)[status]) for status in data['status']]))
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()