diff --git a/cfp/admin.py b/cfp/admin.py
index c01570f..adadfab 100644
--- a/cfp/admin.py
+++ b/cfp/admin.py
@@ -2,7 +2,7 @@ from django.contrib import admin
from django.contrib.sites.models import Site
from .mixins import OnSiteAdminMixin
-from .models import Conference, Participant, Talk, TalkCategory, Track, Vote
+from .models import Conference, Participant, Talk, TalkCategory, Track, Vote, Volunteer, Activity
class ConferenceAdmin(OnSiteAdminMixin, admin.ModelAdmin):
@@ -41,8 +41,18 @@ class VoteAdmin(admin.ModelAdmin):
return super().get_queryset(request).filter(talk__site=request.conference.site)
+class VolunteerAdmin(OnSiteAdminMixin, admin.ModelAdmin):
+ pass
+
+
+class ActivityAdmin(OnSiteAdminMixin, admin.ModelAdmin):
+ pass
+
+
admin.site.register(Conference, ConferenceAdmin)
admin.site.register(Participant, ParticipantAdmin)
admin.site.register(Talk, TalkAdmin)
admin.site.register(TalkCategory, TalkCategoryAdmin)
admin.site.register(Vote, VoteAdmin)
+admin.site.register(Volunteer, VolunteerAdmin)
+admin.site.register(Activity, ActivityAdmin)
diff --git a/cfp/forms.py b/cfp/forms.py
index 0ea6d62..125cf1b 100644
--- a/cfp/forms.py
+++ b/cfp/forms.py
@@ -9,7 +9,7 @@ from django.utils.crypto import get_random_string
from django_select2.forms import ModelSelect2MultipleWidget
-from .models import Participant, Talk, TalkCategory, Track, Conference, Room
+from .models import Participant, Talk, TalkCategory, Track, Conference, Room, Volunteer
STATUS_CHOICES = [
@@ -175,7 +175,10 @@ class UsersWidget(ModelSelect2MultipleWidget):
class ConferenceForm(forms.ModelForm):
class Meta:
model = Conference
- fields = ['name', 'home', 'venue', 'city', 'contact_email', 'schedule_publishing_date', 'reply_email', 'secure_domain', 'staff',]
+ fields = [
+ 'name', 'home', 'venue', 'city', 'contact_email', 'schedule_publishing_date',
+ 'volunteers_opening_date', 'volunteers_closing_date', 'reply_email', 'secure_domain', 'staff',
+ ]
widgets = {
'staff': UsersWidget(),
}
@@ -248,3 +251,28 @@ class RoomForm(OnSiteNamedModelForm):
class Meta:
model = Room
fields = ['name', 'label', 'capacity']
+
+
+class VolunteerForm(forms.ModelForm):
+ def __init__(self, *args, **kwargs):
+ self.conference = kwargs.pop('conference')
+ super().__init__(*args, **kwargs)
+
+ # we should manually check (site, email) uniqueness as the site is not part of the form
+ def clean_email(self):
+ email = self.cleaned_data['email']
+ if (not self.instance or self.instance.email != email) \
+ and self._meta.model.objects.filter(site=self.conference.site, email=email).exists():
+ raise self.instance.unique_error_message(self._meta.model, ['email'])
+ return email
+
+ def save(self, commit=True):
+ obj = super().save(commit=False)
+ obj.site = self.conference.site
+ if commit:
+ obj.save()
+ return obj
+
+ class Meta:
+ model = Volunteer
+ fields = ['name', 'email', 'phone_number', 'notes']
diff --git a/cfp/migrations/0011_auto_20171005_2328.py b/cfp/migrations/0011_auto_20171005_2328.py
new file mode 100644
index 0000000..7e28547
--- /dev/null
+++ b/cfp/migrations/0011_auto_20171005_2328.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-10-05 23:28
+from __future__ import unicode_literals
+
+import autoslug.fields
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mailing', '0002_message_author'),
+ ('sites', '0002_alter_domain_unique'),
+ ('cfp', '0010_auto_20170927_1820'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Activity',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256, verbose_name='Name')),
+ ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
+ ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+ ],
+ options={
+ 'verbose_name': 'Activity',
+ 'verbose_name_plural': 'Activities',
+ },
+ ),
+ migrations.CreateModel(
+ name='Volunteer',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('updated', models.DateTimeField(auto_now=True)),
+ ('name', models.CharField(max_length=128, verbose_name='Your Name')),
+ ('email', models.EmailField(max_length=254)),
+ ('token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
+ ('phone_number', models.CharField(blank=True, default='', max_length=64, verbose_name='Phone number')),
+ ('language', models.CharField(blank=True, max_length=10)),
+ ('notes', models.TextField(blank=True, default='', help_text='If you have some constraints, you can indicate them here.', verbose_name='Notes')),
+ ('conversation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='mailing.MessageThread')),
+ ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='conference',
+ name='volunteers_closing_date',
+ field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Volunteers enrollment closing date'),
+ ),
+ migrations.AddField(
+ model_name='conference',
+ name='volunteers_opening_date',
+ field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Volunteers enrollment opening date'),
+ ),
+ migrations.AlterField(
+ model_name='talk',
+ name='description',
+ field=models.TextField(help_text='This field is only visible by organizers.', verbose_name='Description of your talk'),
+ ),
+ migrations.AddField(
+ model_name='activity',
+ name='volunteers',
+ field=models.ManyToManyField(blank=True, related_name='activities', to='cfp.Volunteer', verbose_name='Volunteer'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='volunteer',
+ unique_together=set([('site', 'email')]),
+ ),
+ migrations.AlterUniqueTogether(
+ name='activity',
+ unique_together=set([('site', 'name')]),
+ ),
+ ]
diff --git a/cfp/models.py b/cfp/models.py
index 3861f28..2f57337 100644
--- a/cfp/models.py
+++ b/cfp/models.py
@@ -22,8 +22,8 @@ from mailing.models import MessageThread
class Conference(models.Model):
-
site = models.OneToOneField(Site, on_delete=models.CASCADE)
+
name = models.CharField(blank=True, max_length=100, verbose_name=_('Conference name'))
home = models.TextField(blank=True, default="", verbose_name=_('Homepage (markdown)'))
venue = models.TextField(blank=True, default="", verbose_name=_('Venue information'))
@@ -33,6 +33,8 @@ class Conference(models.Model):
staff = models.ManyToManyField(User, blank=True, verbose_name=_('Staff members'))
secure_domain = models.BooleanField(default=True, verbose_name=_('Secure domain (HTTPS)'))
schedule_publishing_date = models.DateTimeField(null=True, blank=True, default=None, verbose_name=_('Schedule publishing date'))
+ volunteers_opening_date = models.DateTimeField(null=True, blank=True, default=None, verbose_name=_('Volunteers enrollment opening date'))
+ volunteers_closing_date = models.DateTimeField(null=True, blank=True, default=None, verbose_name=_('Volunteers enrollment closing date'))
custom_css = models.TextField(blank=True)
external_css_link = models.URLField(blank=True)
@@ -43,6 +45,12 @@ class Conference(models.Model):
# events = Event.objects.filter(site=self.site)
# return any(map(lambda x: x.is_open(), events))
+ def volunteers_enrollment_is_open(self):
+ now = timezone.now()
+ opening = self.volunteers_opening_date
+ closing = self.volunteers_closing_date
+ return opening and opening < now and (not closing or closing > now)
+
@property
def opened_categories(self):
now = timezone.now()
@@ -83,30 +91,22 @@ class ParticipantManager(models.Manager):
class Participant(PonyConfModel):
-
site = models.ForeignKey(Site, on_delete=models.CASCADE)
-
name = models.CharField(max_length=128, verbose_name=_('Your Name'))
email = models.EmailField()
-
biography = models.TextField(verbose_name=_('Biography'))
token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
-
twitter = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Twitter'))
linkedin = models.CharField(max_length=100, blank=True, default='', verbose_name=_('LinkedIn'))
github = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Github'))
website = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Website'))
facebook = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Facebook'))
mastodon = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Mastodon'))
-
phone_number = models.CharField(max_length=64, blank=True, default='', verbose_name=_('Phone number'))
-
language = models.CharField(max_length=10, blank=True)
-
- notes = models.TextField(default='', blank=True, verbose_name=_("Notes"), help_text=_('This field is only visible by organizers.'))
-
+ notes = models.TextField(default='', blank=True, verbose_name=_("Notes"),
+ help_text=_('This field is only visible by organizers.'))
vip = models.BooleanField(default=False)
-
conversation = models.OneToOneField(MessageThread)
objects = ParticipantManager()
@@ -133,9 +133,7 @@ class Participant(PonyConfModel):
class Track(PonyConfModel):
-
site = models.ForeignKey(Site, on_delete=models.CASCADE)
-
name = models.CharField(max_length=128, verbose_name=_('Name'))
slug = AutoSlugField(populate_from='name')
description = models.TextField(blank=True, verbose_name=_('Description'))
@@ -156,10 +154,9 @@ class Track(PonyConfModel):
class Room(models.Model):
-
site = models.ForeignKey(Site, on_delete=models.CASCADE)
- slug = AutoSlugField(populate_from='name')
name = models.CharField(max_length=256, blank=True, default="")
+ slug = AutoSlugField(populate_from='name')
label = models.CharField(max_length=256, blank=True, default="")
capacity = models.IntegerField(default=0)
@@ -187,7 +184,6 @@ class Room(models.Model):
class TalkCategory(models.Model): # type of talk (conf 30min, 1h, stand, …)
-
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=64)
duration = models.PositiveIntegerField(default=0, verbose_name=_('Default duration (min)'))
@@ -252,7 +248,6 @@ def talks_materials_destination(talk, filename):
class Talk(PonyConfModel):
-
LICENCES = (
('CC-Zero CC-BY', 'CC-Zero CC-BY'),
('CC-BY-SA', 'CC-BY-SA'),
@@ -268,12 +263,17 @@ class Talk(PonyConfModel):
title = models.CharField(max_length=128, verbose_name=_('Talk Title'))
slug = AutoSlugField(populate_from='title', unique=True)
#abstract = models.CharField(max_length=255, blank=True, verbose_name=_('Abstract'))
- description = models.TextField(verbose_name=_('Description of your talk'))
+ description = models.TextField(verbose_name=_('Description of your talk'),
+ help_text=_('This field is only visible by organizers.'))
track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Track'))
- notes = models.TextField(blank=True, verbose_name=_('Message to organizers'), help_text=_('If you have any constraint or if you have anything that may help you to select your talk, like a video or slides of your talk, please write it down here'))
+ notes = models.TextField(blank=True, verbose_name=_('Message to organizers'),
+ help_text=_('If you have any constraint or if you have anything that may '
+ 'help you to select your talk, like a video or slides of your'
+ ' talk, please write it down here'))
category = models.ForeignKey(TalkCategory, verbose_name=_('Talk Category'))
videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True)
- video_licence = models.CharField(choices=LICENCES, default='CC-BY-SA', max_length=10, verbose_name=_("Video licence"))
+ video_licence = models.CharField(choices=LICENCES, default='CC-BY-SA',
+ max_length=10, verbose_name=_("Video licence"))
sound = models.BooleanField(_("I need sound"), default=False)
accepted = models.NullBooleanField(default=None)
start_date = models.DateTimeField(null=True, blank=True, default=None, verbose_name=_('Beginning date and time'))
@@ -330,7 +330,6 @@ class Talk(PonyConfModel):
class Vote(PonyConfModel):
-
talk = models.ForeignKey(Talk)
user = models.ForeignKey(User)
vote = models.IntegerField(validators=[MinValueValidator(-2), MaxValueValidator(2)], default=0)
@@ -343,3 +342,41 @@ class Vote(PonyConfModel):
def get_absolute_url(self):
return self.talk.get_absolute_url()
+
+
+class Volunteer(PonyConfModel):
+ site = models.ForeignKey(Site, on_delete=models.CASCADE)
+ name = models.CharField(max_length=128, verbose_name=_('Your Name'))
+ email = models.EmailField()
+ token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
+ phone_number = models.CharField(max_length=64, blank=True, default='', verbose_name=_('Phone number'))
+ language = models.CharField(max_length=10, blank=True)
+ notes = models.TextField(default='', blank=True, verbose_name=_('Notes'),
+ help_text=_('If you have some constraints, you can indicate them here.'))
+ conversation = models.OneToOneField(MessageThread)
+
+ def get_absolute_url(self):
+ return reverse('volunteer-details', kwargs={'volunteer_id': self.token})
+
+ class Meta:
+ # A volunteer can participe only once to a Conference (= Site)
+ unique_together = ('site', 'email')
+
+ def __str__(self):
+ return str(self.name)
+
+
+class Activity(models.Model):
+ site = models.ForeignKey(Site, on_delete=models.CASCADE)
+ name = models.CharField(max_length=256, verbose_name=_('Name'))
+ slug = AutoSlugField(populate_from='name')
+ description = models.TextField(blank=True, verbose_name=_('Description'))
+ volunteers = models.ManyToManyField(Volunteer, blank=True, related_name='activities', verbose_name=_('Volunteer'))
+
+ class Meta:
+ unique_together = ('site', 'name')
+ verbose_name = _('Activity')
+ verbose_name_plural = _('Activities')
+
+ def __str__(self):
+ return self.name
diff --git a/cfp/signals.py b/cfp/signals.py
index 1675b8f..77e1426 100644
--- a/cfp/signals.py
+++ b/cfp/signals.py
@@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse
from ponyconf.decorators import disable_for_loaddata
from mailing.models import MessageThread, Message
-from .models import Participant, Talk, Conference
+from .models import Participant, Talk, Conference, Volunteer
@receiver(post_save, sender=Site, dispatch_uid="Create Conference for Site")
@@ -22,6 +22,7 @@ def create_conversation(sender, instance, **kwargs):
instance.conversation = MessageThread.objects.create()
pre_save.connect(create_conversation, sender=Participant)
pre_save.connect(create_conversation, sender=Talk)
+pre_save.connect(create_conversation, sender=Volunteer)
@receiver(pre_save, sender=Message, dispatch_uid="Set message author")
diff --git a/cfp/templates/cfp/volunteer.html b/cfp/templates/cfp/volunteer.html
new file mode 100644
index 0000000..572e3ed
--- /dev/null
+++ b/cfp/templates/cfp/volunteer.html
@@ -0,0 +1,29 @@
+{% extends 'base.html' %}
+
+{% load i18n %}
+
+{% block volunteerstab %} class="active"{% endblock %}
+
+{% block content %}
+
+
{% trans "Become a volunteers!" %}
+
+{% for activity in activities %}
+{% if forloop.first %}{% endif %}
+
+{% if forloop.last %}
{% endif %}
+{% endfor %}
+
+{% endblock %}
diff --git a/cfp/templates/cfp/volunteer_enrole.html b/cfp/templates/cfp/volunteer_enrole.html
new file mode 100644
index 0000000..86e69a3
--- /dev/null
+++ b/cfp/templates/cfp/volunteer_enrole.html
@@ -0,0 +1,36 @@
+{% extends 'base.html' %}
+
+{% load i18n %}
+
+{% block volunteerstab %} class="active"{% endblock %}
+
+{% block content %}
+
+{% trans "Become a volunteers!" %}
+
+
+{% blocktrans %}We need you! To participate, please enter your name and e-mail.{% endblocktrans %}
+
+
+{% include '_form.html' %}
+
+
+{% for activity in activities %}
+{% if forloop.first %}
+
+
+{% blocktrans %}We are looking for help with the following activities:{% endblocktrans %}
+
+
+{% endif %}
+ -
+ {{ activity.name }}
+ {{ activity.description }}
+
+{% if forloop.last %}
+
+
+{% endif %}
+{% endfor %}
+
+{% endblock %}
diff --git a/cfp/urls.py b/cfp/urls.py
index a2aadf6..e2e2978 100644
--- a/cfp/urls.py
+++ b/cfp/urls.py
@@ -8,6 +8,10 @@ urlpatterns = [
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'),
url(r'^cfp/(?P[\w\-]+)/(?P[\w\-]+)/$', views.talk_proposal, name='talk-proposal-edit'),
+ url(r'^volunteer/$', views.volunteer_enrole, name='volunteer-enrole'),
+ url(r'^volunteer/(?P[\w\-]+)/$', views.volunteer, name='volunteer'),
+ url(r'^volunteer/(?P[\w\-]+)/join/(?P[\w\-]+)/$', views.volunteer_activity, {'join': True}, name='volunteer-join'),
+ url(r'^volunteer/(?P[\w\-]+)/quit/(?P[\w\-]+)/$', views.volunteer_activity, {'join': False}, name='volunteer-quit'),
url(r'^staff/$', views.staff, name='staff'),
url(r'^staff/conference/$', views.conference, name='conference'),
url(r'^staff/talks/$', views.talk_list, name='talk-list'),
diff --git a/cfp/views.py b/cfp/views.py
index 91eee69..cb44e48 100644
--- a/cfp/views.py
+++ b/cfp/views.py
@@ -22,10 +22,11 @@ from .planning import Program
from .decorators import staff_required
from .mixins import StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin
from .utils import is_staff
-from .models import Participant, Talk, TalkCategory, Vote, Track, Room
+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
+ ConferenceForm, CreateUserForm, STATUS_VALUES, TrackForm, RoomForm, \
+ VolunteerForm
def home(request):
@@ -35,6 +36,46 @@ def home(request):
return redirect(reverse('talk-proposal'))
+def volunteer_enrole(request):
+ if not request.conference.volunteers_enrollment_is_open():
+ raise PermissionDenied
+ form = VolunteerForm(request.POST or None, conference=request.conference)
+ if request.method == 'POST' and form.is_valid():
+ volunteer = form.save(commit=False)
+ volunteer.language = request.LANGUAGE_CODE
+ volunteer.save()
+ messages.success(request, _('Thank you for your participation! You can now subscribe to some activities.'))
+ return redirect(reverse('volunteer', kwargs=dict(volunteer_id=volunteer.token)))
+ return render(request, 'cfp/volunteer_enrole.html', {
+ 'activities': Activity.objects.filter(site=request.conference.site),
+ 'form': form,
+ })
+
+
+def volunteer(request, volunteer_id):
+ volunteer = get_object_or_404(Volunteer, token=volunteer_id, site=request.conference.site)
+ return render(request, 'cfp/volunteer.html', {
+ 'activities': Activity.objects.filter(site=request.conference.site),
+ 'volunteer': volunteer,
+ })
+
+
+def volunteer_activity(request, volunteer_id, activity, join):
+ if not request.conference.volunteers_enrollment_is_open():
+ raise PermissionDenied
+ volunteer = get_object_or_404(Volunteer, token=volunteer_id, site=request.conference.site)
+ 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', kwargs=dict(volunteer_id=volunteer.token)))
+
+
def talk_proposal(request, talk_id=None, participant_id=None):
conference = request.conference
diff --git a/locale/fr/LC_MESSAGES/django.mo b/locale/fr/LC_MESSAGES/django.mo
index f6e10fd..a5be45e 100644
Binary files a/locale/fr/LC_MESSAGES/django.mo and b/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index d5797a5..8a7edb3 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-09-27 18:39+0000\n"
-"PO-Revision-Date: 2017-08-30 01:14+0200\n"
+"POT-Creation-Date: 2017-10-05 23:31+0000\n"
+"PO-Revision-Date: 2017-10-06 01:31+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
@@ -22,11 +22,11 @@ msgstr ""
msgid "Pending decision"
msgstr "Décision en attente"
-#: cfp/forms.py:17 cfp/templates/cfp/staff/talk_list.html:64
+#: cfp/forms.py:17 cfp/templates/cfp/staff/talk_list.html:66
msgid "Accepted"
msgstr "Accepté"
-#: cfp/forms.py:18 cfp/templates/cfp/staff/talk_list.html:66
+#: cfp/forms.py:18 cfp/templates/cfp/staff/talk_list.html:68
msgid "Declined"
msgstr "Décliné"
@@ -40,16 +40,16 @@ msgstr "Durée par défaut : %(duration)d min"
msgid "Category"
msgstr "Catégorie"
-#: cfp/forms.py:56 cfp/templates/cfp/staff/talk_list.html:39
+#: cfp/forms.py:56 cfp/templates/cfp/staff/talk_list.html:41
msgid "Title"
msgstr "Titre"
-#: cfp/forms.py:57 cfp/models.py:141
-#: cfp/templates/cfp/staff/talk_details.html:61 volunteers/models.py:14
+#: cfp/forms.py:57 cfp/models.py:139 cfp/models.py:373
+#: cfp/templates/cfp/staff/talk_details.html:61
msgid "Description"
msgstr "Description"
-#: cfp/forms.py:58 cfp/models.py:106
+#: cfp/forms.py:58 cfp/models.py:107 cfp/models.py:354
#: cfp/templates/cfp/staff/participant_details.html:16
#: cfp/templates/cfp/staff/talk_details.html:75
msgid "Notes"
@@ -62,13 +62,13 @@ msgstr "Visible par les orateurs"
#: cfp/forms.py:73 cfp/forms.py:149
#: cfp/templates/cfp/staff/talk_details.html:18
#: cfp/templates/cfp/staff/talk_details.html:81
-#: cfp/templates/cfp/staff/talk_list.html:43
+#: cfp/templates/cfp/staff/talk_list.html:45
msgid "Status"
msgstr "Statut"
-#: cfp/forms.py:79 cfp/forms.py:155 cfp/models.py:276
+#: cfp/forms.py:79 cfp/forms.py:155 cfp/models.py:268
#: cfp/templates/cfp/staff/talk_details.html:21
-#: cfp/templates/cfp/staff/talk_list.html:42
+#: cfp/templates/cfp/staff/talk_list.html:44
#: cfp/templates/cfp/staff/track_form.html:14
msgid "Track"
msgstr "Session"
@@ -100,7 +100,8 @@ msgstr "Programmé"
msgid "Filter talks already / not yet scheduled"
msgstr "Filtrer les exposés déjà / pas encore planifiées"
-#: cfp/forms.py:97 cfp/models.py:287
+#: cfp/forms.py:97 cfp/models.py:283
+#: cfp/templates/cfp/staff/talk_details.html:51
msgid "Materials"
msgstr "Supports"
@@ -108,7 +109,7 @@ msgstr "Supports"
msgid "Filter talks with / without materials"
msgstr "Filtrer les exposés avec / sans supports"
-#: cfp/forms.py:101
+#: cfp/forms.py:101 cfp/templates/cfp/staff/talk_details.html:55
msgid "Video"
msgstr "Vidéo"
@@ -132,22 +133,22 @@ msgstr "Assigner à une session"
msgid "Put in a room"
msgstr "Assigner à une salle"
-#: cfp/forms.py:137 cfp/models.py:139
-#: cfp/templates/cfp/staff/participant_list.html:34 volunteers/models.py:12
+#: cfp/forms.py:137 cfp/models.py:137 cfp/models.py:371
+#: cfp/templates/cfp/staff/participant_list.html:34
msgid "Name"
msgstr "Nom"
-#: cfp/forms.py:183
+#: cfp/forms.py:186
msgid "New staff members will be informed of their new position by e-mail."
msgstr ""
"Les nouveaux membres du staff seront informés de leur nouveau rôle par "
"courrier électronique."
-#: cfp/forms.py:203
+#: cfp/forms.py:206
msgid "An user with that firstname and that lastname already exists."
msgstr "Un utilisateur avec ce prénom et ce nom existe déjà."
-#: cfp/forms.py:208
+#: cfp/forms.py:211
msgid "A user with that email already exists."
msgstr "Un utilisateur avec cet email existe déjà."
@@ -187,7 +188,15 @@ msgstr "Domaine sécurisé (HTTPS)"
msgid "Schedule publishing date"
msgstr "Date de publication du programme"
-#: cfp/models.py:67
+#: cfp/models.py:36
+msgid "Volunteers enrollment opening date"
+msgstr "Date d’ouverture de l’appel à bénévole"
+
+#: cfp/models.py:37
+msgid "Volunteers enrollment closing date"
+msgstr "Date de fermeture de l’appel à bénévole"
+
+#: cfp/models.py:75
#, python-brace-format
msgid ""
"The reply email should be a formatable string accepting a token argument (e."
@@ -196,78 +205,78 @@ msgstr ""
"L’adresse de réponse doit être une chaine de texte formatable avec un "
"argument « token » (e.g. ponyconf+{token}@exemple.com)."
-#: cfp/models.py:89
+#: cfp/models.py:95 cfp/models.py:349
msgid "Your Name"
msgstr "Votre Nom"
-#: cfp/models.py:92 cfp/templates/cfp/staff/participant_details.html:12
+#: cfp/models.py:97 cfp/templates/cfp/staff/participant_details.html:12
msgid "Biography"
msgstr "Biographie"
-#: cfp/models.py:95
+#: cfp/models.py:99
msgid "Twitter"
msgstr "Twitter"
-#: cfp/models.py:96
+#: cfp/models.py:100
msgid "LinkedIn"
msgstr "LinkedIn"
-#: cfp/models.py:97
+#: cfp/models.py:101
msgid "Github"
msgstr "Github"
-#: cfp/models.py:98
+#: cfp/models.py:102
msgid "Website"
msgstr "Site web"
-#: cfp/models.py:99
+#: cfp/models.py:103
msgid "Facebook"
msgstr "Facebook"
-#: cfp/models.py:100
+#: cfp/models.py:104
msgid "Mastodon"
msgstr "Mastodon"
-#: cfp/models.py:102
+#: cfp/models.py:105 cfp/models.py:352
msgid "Phone number"
msgstr "Numéro de téléphone"
-#: cfp/models.py:106
+#: cfp/models.py:108 cfp/models.py:267
msgid "This field is only visible by organizers."
msgstr "Ce champs est uniquement visible par les organisateurs."
-#: cfp/models.py:193
+#: cfp/models.py:189
msgid "Default duration (min)"
msgstr "Durée par défaut (min)"
-#: cfp/models.py:194
+#: cfp/models.py:190
msgid "Color on program"
msgstr "Couleur sur le programme"
-#: cfp/models.py:195
+#: cfp/models.py:191
msgid "Label on program"
msgstr "Label dans le xml du programme"
-#: cfp/models.py:271 cfp/templates/cfp/staff/base.html:17
+#: cfp/models.py:262 cfp/templates/cfp/staff/base.html:17
#: cfp/templates/cfp/staff/participant_list.html:8
#: cfp/templates/cfp/staff/talk_details.html:65
-#: cfp/templates/cfp/staff/talk_list.html:41
+#: cfp/templates/cfp/staff/talk_list.html:43
msgid "Speakers"
msgstr "Orateurs"
-#: cfp/models.py:272
+#: cfp/models.py:263
msgid "Talk Title"
msgstr "Titre de la proposition"
-#: cfp/models.py:275
+#: cfp/models.py:266
msgid "Description of your talk"
msgstr "Description de votre proposition"
-#: cfp/models.py:277
+#: cfp/models.py:269
msgid "Message to organizers"
msgstr "Message aux organisateurs"
-#: cfp/models.py:277
+#: cfp/models.py:270
msgid ""
"If you have any constraint or if you have anything that may help you to "
"select your talk, like a video or slides of your talk, please write it down "
@@ -277,48 +286,64 @@ msgstr ""
"votre proposition, comme une vidéo, des slides, n'hésitez pas à les ajouter "
"ici."
-#: cfp/models.py:278
+#: cfp/models.py:273
msgid "Talk Category"
msgstr "Catégorie de proposition"
-#: cfp/models.py:279
+#: cfp/models.py:274
msgid "I'm ok to be recorded on video"
msgstr "J’accepte d’être enregistré en vidéo"
-#: cfp/models.py:280
+#: cfp/models.py:276
msgid "Video licence"
msgstr "Licence vidéo"
-#: cfp/models.py:281
+#: cfp/models.py:277
msgid "I need sound"
msgstr "J’ai besoin de son"
-#: cfp/models.py:283
+#: cfp/models.py:279
msgid "Beginning date and time"
msgstr "Date et heure de début"
-#: cfp/models.py:284
+#: cfp/models.py:280
msgid "Duration (min)"
msgstr "Durée (min)"
-#: cfp/models.py:288
+#: cfp/models.py:284
msgid ""
"You can use this field to share some materials related to your intervention."
msgstr ""
"Vous pouvez utiliser ce champs pour partager les supports de votre "
"intervention."
-#: cfp/signals.py:79
+#: cfp/models.py:355
+msgid "If you have some constraints, you can indicate them here."
+msgstr "Si vous avez des contraintes, vous pouvez les indiquer ici."
+
+#: cfp/models.py:374
+msgid "Volunteer"
+msgstr "Bénévole"
+
+#: cfp/models.py:378
+msgid "Activity"
+msgstr "Activité"
+
+#: cfp/models.py:379
+msgid "Activities"
+msgstr "Activités"
+
+#: cfp/signals.py:80
#, python-format
msgid "[%(prefix)s] Message from the staff"
msgstr "[%(prefix)s] Message du staff"
-#: cfp/signals.py:80
+#: cfp/signals.py:81
#, python-format
msgid "[%(prefix)s] Conversation with %(dest)s"
msgstr "[%(prefix)s] Conversation avec %(dest)s"
-#: cfp/signals.py:96
+#: cfp/signals.py:97
#, python-format
msgid "[%(prefix)s] Talk: %(talk)s"
msgstr "[%(prefix)s] Talk: %(talk)s"
@@ -458,7 +483,7 @@ msgstr "par"
#: cfp/templates/cfp/staff/participant_details.html:43
#: cfp/templates/cfp/staff/room_details.html:21
#: cfp/templates/cfp/staff/room_details.html:39
-#: cfp/templates/cfp/staff/talk_list.html:57
+#: cfp/templates/cfp/staff/talk_list.html:59
msgid "and"
msgstr "et"
@@ -489,19 +514,16 @@ msgstr "Éditer un orateur"
#: cfp/templates/cfp/staff/participant_list.html:10
#: cfp/templates/cfp/staff/talk_list.html:10
-#: volunteers/templates/volunteers/volunteer_list.html:11
msgid "Show filtering options…"
msgstr "Afficher les options de filtrage…"
#: cfp/templates/cfp/staff/participant_list.html:24
-#: cfp/templates/cfp/staff/talk_list.html:27
-#: volunteers/templates/volunteers/volunteer_list.html:19
+#: cfp/templates/cfp/staff/talk_list.html:29
msgid "Filter"
msgstr "Filtrer"
#: cfp/templates/cfp/staff/participant_list.html:30
-#: cfp/templates/cfp/staff/talk_list.html:35
-#: volunteers/templates/volunteers/volunteer_list.html:25
+#: cfp/templates/cfp/staff/talk_list.html:37
msgid "Total:"
msgstr "Total :"
@@ -568,7 +590,7 @@ msgid "Some talks are not scheduled yet."
msgstr "Certains exposés ne sont pas encore planifiés."
#: cfp/templates/cfp/staff/room_list.html:24
-#: cfp/templates/cfp/staff/talk_list.html:35
+#: cfp/templates/cfp/staff/talk_list.html:37
#: cfp/templates/cfp/staff/track_list.html:21
msgid "talk"
msgstr "exposé"
@@ -636,6 +658,10 @@ msgstr "Créneau"
msgid "not defined"
msgstr "non défini"
+#: cfp/templates/cfp/staff/talk_details.html:56
+msgid "download"
+msgstr "Télécharger"
+
#: cfp/templates/cfp/staff/talk_details.html:63
msgid "No description provided."
msgstr "Aucune description fournie."
@@ -672,20 +698,20 @@ msgstr ""
msgid "Edit a talk"
msgstr "Éditer un exposé"
-#: cfp/templates/cfp/staff/talk_list.html:40
+#: cfp/templates/cfp/staff/talk_list.html:42
msgid "Intervention kind"
msgstr "Type d’intervention"
-#: cfp/templates/cfp/staff/talk_list.html:68
+#: cfp/templates/cfp/staff/talk_list.html:70
#, python-format
msgid "Pending, score: %(score)s"
msgstr "En cours, score : %(score)s"
-#: cfp/templates/cfp/staff/talk_list.html:80
+#: cfp/templates/cfp/staff/talk_list.html:82
msgid "For selected talks:"
msgstr "Pour les exposés sélectionnés :"
-#: cfp/templates/cfp/staff/talk_list.html:85
+#: cfp/templates/cfp/staff/talk_list.html:87
msgid "Apply"
msgstr "Appliquer"
@@ -701,7 +727,44 @@ msgstr "responsable"
msgid "No tracks."
msgstr "Aucune session."
-#: cfp/views.py:76
+#: cfp/templates/cfp/volunteer.html:9 cfp/templates/cfp/volunteer_enrole.html:9
+msgid "Become a volunteers!"
+msgstr "Devenez un bénévole !"
+
+#: cfp/templates/cfp/volunteer.html:19
+msgid "I will be happy to help on that!"
+msgstr "Je serai heureux d’aider à cela !"
+
+#: cfp/templates/cfp/volunteer.html:21
+msgid "Sorry, I have a setback"
+msgstr "Désolé, j’ai un contretemps"
+
+#: cfp/templates/cfp/volunteer_enrole.html:12
+msgid "We need you! To participate, please enter your name and e-mail."
+msgstr ""
+"Nous avons besoin de vous ! Pour participer, veuillez indiquer votre nom et "
+"votre adresse e-mail."
+
+#: cfp/templates/cfp/volunteer_enrole.html:22
+msgid "We are looking for help with the following activities:"
+msgstr "Nous cherchons de l’aide pour les activités suivantes :"
+
+#: cfp/views.py:47
+msgid ""
+"Thank you for your participation! You can now subscribe to some activities."
+msgstr ""
+"Merci pour votre participation ! Vous pouvez maintenant vous inscrire à une "
+"ou plusieurs activités."
+
+#: cfp/views.py:71
+msgid "Thank you for your participation!"
+msgstr "Merci pour votre participation !"
+
+#: cfp/views.py:75
+msgid "Okay, no problem!"
+msgstr "Ok, pas de soucis !"
+
+#: cfp/views.py:117
msgid ""
"Hi {},\n"
"\n"
@@ -741,35 +804,35 @@ msgstr ""
"{}\n"
"\n"
-#: cfp/views.py:196 cfp/views.py:291
+#: cfp/views.py:237 cfp/views.py:332
msgid "The talk has been accepted."
msgstr "L’exposé a été accepté."
-#: cfp/views.py:198 cfp/views.py:293
+#: cfp/views.py:239 cfp/views.py:334
msgid "The talk has been declined."
msgstr "L’exposé a été décliné."
-#: cfp/views.py:261 cfp/views.py:349
+#: cfp/views.py:302 cfp/views.py:390
msgid "Message sent!"
msgstr "Message envoyé !"
-#: cfp/views.py:274
+#: cfp/views.py:315
msgid "Vote successfully created"
msgstr "A voté !"
-#: cfp/views.py:274
+#: cfp/views.py:315
msgid "Vote successfully updated"
msgstr "Vote mis à jour"
-#: cfp/views.py:295
+#: cfp/views.py:336
msgid "Decision taken in account"
msgstr "Décision enregistrée"
-#: cfp/views.py:377
+#: cfp/views.py:418
msgid "[{}] You have been added to the staff team"
msgstr "[{}] Vous avez été ajouté aux membres du staff"
-#: cfp/views.py:378
+#: cfp/views.py:419
msgid ""
"Hi {},\n"
"\n"
@@ -793,15 +856,15 @@ msgstr ""
"{}\n"
"\n"
-#: cfp/views.py:399
+#: cfp/views.py:440
msgid "Modifications successfully saved."
msgstr "Modification enregistrée avec succès."
-#: cfp/views.py:476
+#: cfp/views.py:517
msgid "User created successfully."
msgstr "Utilisateur créé avec succès."
-#: cfp/views.py:497
+#: cfp/views.py:538
#, python-format
msgid "Format '%s' not available"
msgstr "Format '%s' non disponible"
@@ -839,15 +902,19 @@ msgstr "Accueil"
msgid "Call for participation"
msgstr "Appel à participation"
-#: ponyconf/templates/base.html:39 ponyconf/templates/base.html:50
+#: ponyconf/templates/base.html:28
+msgid "Volunteers"
+msgstr "Bénévoles"
+
+#: ponyconf/templates/base.html:42 ponyconf/templates/base.html:53
msgid "Staff"
msgstr "Staff"
-#: ponyconf/templates/base.html:48
+#: ponyconf/templates/base.html:51
msgid "Logout"
msgstr "Déconnection"
-#: ponyconf/templates/base.html:67
+#: ponyconf/templates/base.html:70
msgid "Powered by"
msgstr "Propulsé par"
@@ -875,56 +942,25 @@ msgstr "Changement de mot de passe"
msgid "Email address"
msgstr "Adresse e-mail"
-#: volunteers/models.py:15
-msgid "Participants"
-msgstr "Participants"
+#~ msgid "Participants"
+#~ msgstr "Participants"
-#: volunteers/models.py:19
-msgid "Activity"
-msgstr "Activité"
+#~ msgid "We are not yet looking for volunteers … come back later!"
+#~ msgstr ""
+#~ "Nous ne sommes pas encore en recherche de bénévoles … mais revenez plus "
+#~ "tard !"
-#: volunteers/models.py:20
-#: volunteers/templates/volunteers/volunteer_list.html:31
-msgid "Activities"
-msgstr "Activités"
+#~ msgid "volunteer"
+#~ msgstr "bénévole"
-#: volunteers/templates/volunteers/volunteer_enrole.html:9
-msgid "Enrole as volunteer"
-msgstr "Devenir bénévole"
+#~ msgid "Username"
+#~ msgstr "Nom d’utilisateur"
-#: volunteers/templates/volunteers/volunteer_enrole.html:19
-msgid "Sorry, I have a setback"
-msgstr "Désolé, j’ai un contretemps"
+#~ msgid "Fullname"
+#~ msgstr "Prénom et nom"
-#: volunteers/templates/volunteers/volunteer_enrole.html:21
-msgid "I will be happy to help on that!"
-msgstr "Je serai heureux d’aider à cela !"
-
-#: volunteers/templates/volunteers/volunteer_enrole.html:28
-msgid "We are not yet looking for volunteers … come back later!"
-msgstr ""
-"Nous ne sommes pas encore en recherche de bénévoles … mais revenez plus "
-"tard !"
-
-#: volunteers/templates/volunteers/volunteer_list.html:9
-msgid "Volunteers"
-msgstr "Bénévoles"
-
-#: volunteers/templates/volunteers/volunteer_list.html:25
-msgid "volunteer"
-msgstr "bénévole"
-
-#: volunteers/templates/volunteers/volunteer_list.html:29
-msgid "Username"
-msgstr "Nom d’utilisateur"
-
-#: volunteers/templates/volunteers/volunteer_list.html:30
-msgid "Fullname"
-msgstr "Prénom et nom"
-
-#: volunteers/templates/volunteers/volunteer_list.html:48
-msgid "Contact"
-msgstr "Contacter"
+#~ msgid "Contact"
+#~ msgstr "Contacter"
#~ msgid ""
#~ "For example, you need to be back on saturday evening, you cannot eat meat."
@@ -1185,9 +1221,6 @@ msgstr "Contacter"
#~ msgid "required but unlimited"
#~ msgstr "requis mais non limité"
-#~ msgid "download"
-#~ msgstr "Télécharger"
-
#~ msgid "Assign to"
#~ msgstr "Assigner à"
diff --git a/ponyconf/templates/base.html b/ponyconf/templates/base.html
index 9002583..6c6c0a6 100644
--- a/ponyconf/templates/base.html
+++ b/ponyconf/templates/base.html
@@ -24,6 +24,9 @@
{% if conference.schedule_available %}
{% trans "Schedule" %}
{% endif %}
+ {% if conference.volunteers_enrolement_is_open %}
+ {% trans "Volunteers" %}
+ {% endif %}
{% comment %}
{% if request.user.is_authenticated %}
{% trans "Exhibitor" %}