diff --git a/cfp/emails.py b/cfp/emails.py
new file mode 100644
index 0000000..78b6c30
--- /dev/null
+++ b/cfp/emails.py
@@ -0,0 +1,36 @@
+from django.utils.translation import ugettext as _
+from django.utils.html import escape
+
+from pprint import pformat
+from textwrap import indent
+
+from mailing.utils import send_message
+from .environment import TalkEnvironment
+
+
+def talk_email_render_preview(talk, speaker, subject, body):
+ env = TalkEnvironment(talk, speaker)
+ try:
+ subject = env.from_string(subject).render()
+ except Exception:
+ return _('There is an error in your subject template.')
+ try:
+ body = env.from_string(body).render()
+ except Exception:
+ return _('There is an error in your body template.')
+ context = {'talk': env.globals['talk'], 'speaker': env.globals['speaker']}
+ preview = '' + _('Environment:') + '\n\n' + escape(indent(pformat(context, indent='2'), ' '))
+ preview += '\n\n' + _('Subject:') + ' ' + escape(subject) + '\n' + _('Body:') + '\n' + escape(body)
+ return preview
+
+
+def talk_email_send(talks, subject, body):
+ sent = 0
+ for talk in talks.all():
+ for speaker in talk.speakers.all():
+ env = TalkEnvironment(talk, speaker)
+ s = env.from_string(subject).render()
+ c = env.from_string(body).render()
+ send_message(speaker.conversation, talk.site.conference, subject=s, content=c)
+ sent += 1
+ return sent
diff --git a/cfp/environment.py b/cfp/environment.py
new file mode 100644
index 0000000..ac8247a
--- /dev/null
+++ b/cfp/environment.py
@@ -0,0 +1,36 @@
+from django.conf import settings
+
+from jinja2.sandbox import SandboxedEnvironment
+
+import pytz
+
+
+def talk_to_dict(talk):
+ return {
+ 'title': talk.title,
+ 'description': talk.description,
+ 'category': str(talk.category),
+ 'accepted': talk.accepted,
+ 'confirmed': talk.confirmed,
+ 'start_date': talk.start_date.astimezone(tz=pytz.timezone(settings.TIME_ZONE)) if talk.start_date else None,
+ 'duration': talk.estimated_duration,
+ 'track': str(talk.track) if talk.track else '',
+ 'video': talk.video,
+ 'speakers': list(map(speaker_to_dict, talk.speakers.all())),
+ }
+
+
+def speaker_to_dict(speaker):
+ return {
+ 'name': speaker.name,
+ 'email': speaker.email,
+ }
+
+
+class TalkEnvironment(SandboxedEnvironment):
+ def __init__(self, talk, speaker, **options):
+ super().__init__(**options)
+ self.globals.update({
+ 'talk': talk_to_dict(talk),
+ 'speaker': speaker_to_dict(speaker),
+ })
diff --git a/cfp/forms.py b/cfp/forms.py
index 83ec3f7..1030e0d 100644
--- a/cfp/forms.py
+++ b/cfp/forms.py
@@ -11,6 +11,7 @@ from django_select2.forms import ModelSelect2MultipleWidget
from .models import Participant, Talk, TalkCategory, Track, Tag, \
Conference, Room, Volunteer, Activity
+from .environment import TalkEnvironment
ACCEPTATION_CHOICES = [
@@ -182,6 +183,7 @@ class TalkActionForm(forms.Form):
track = forms.ChoiceField(required=False, choices=[], label=_('Assign to a track'))
tag = forms.ChoiceField(required=False, choices=[], label=_('Add a tag'))
room = forms.ChoiceField(required=False, choices=[], label=_('Put in a room'))
+ email = forms.BooleanField(label=_('Send a email'))
def __init__(self, *args, **kwargs):
site = kwargs.pop('site')
@@ -246,10 +248,45 @@ class ParticipantFilterForm(forms.Form):
self.fields['track'].choices = [('none', _('Not assigned'))] + list(tracks.values_list('slug', 'name'))
-class MailForm(forms.Form):
+class EmailForm(forms.Form):
email = forms.EmailField(required=True, label=_('Email'))
+class PreviewMailForm(forms.Form):
+ speaker = forms.IntegerField()
+ talk = forms.IntegerField()
+ subject = forms.CharField(required=False)
+ body = forms.CharField(required=False)
+
+
+class SendMailForm(forms.Form):
+ subject = forms.CharField()
+ body = forms.CharField(widget=forms.Textarea)
+ confirm = forms.BooleanField(required=False, label=_('I read my self twice, confirm sending'))
+
+ def __init__(self, *args, **kwargs):
+ self._talks = kwargs.pop('talks')
+ super().__init__(*args, **kwargs)
+ self._env = dict()
+
+ def clean_subject(self):
+ return self.clean_template('subject')
+
+ def clean_body(self):
+ return self.clean_template('body')
+
+ def clean_template(self, template):
+ try:
+ for talk in self._talks.all():
+ for speaker in talk.speakers.all():
+ env = self._env.get((talk, speaker), TalkEnvironment(talk, speaker))
+ env.from_string(self.cleaned_data.get(template)).render()
+ except Exception as e:
+ raise forms.ValidationError(_("Your template does not compile (at least) with talk '%(talk)s' and speaker '%(speaker)s'.") %
+ {'talk': talk, 'speaker': speaker})
+ return self.cleaned_data.get(template)
+
+
class UsersWidget(ModelSelect2MultipleWidget):
model = User
search_fields = [ '%s__icontains' % field for field in UserAdmin.search_fields ]
diff --git a/cfp/templates/cfp/staff/talk_email.html b/cfp/templates/cfp/staff/talk_email.html
new file mode 100644
index 0000000..fb91066
--- /dev/null
+++ b/cfp/templates/cfp/staff/talk_email.html
@@ -0,0 +1,114 @@
+{% extends 'cfp/staff/base.html' %}
+{% load i18n bootstrap3 staticfiles %}
+
+{% block talkstab %} class="active"{% endblock %}
+
+{% block content %}
+
+
+
+
+
+{% endblock %}
+
+{% block js_end %}
+{{ block.super }}
+{{ form.media.js }}
+
+
+{% endblock %}
+
+{% block css %}
+{{ block.super }}
+{{ form.media.css }}
+{% endblock %}
diff --git a/cfp/templates/cfp/staff/talk_list.html b/cfp/templates/cfp/staff/talk_list.html
index d639bc0..01c95d0 100644
--- a/cfp/templates/cfp/staff/talk_list.html
+++ b/cfp/templates/cfp/staff/talk_list.html
@@ -5,6 +5,14 @@
{% block content %}
+{% if pending_email %}
+
+
+ {% url 'talk-email' as email_url %}
+ {% blocktrans %}You have a pending e-mail. To continue its edition, click
here.{% endblocktrans %}
+
+{% endif %}
+
{% trans "Talks" %}
{% trans "Show filtering options…" %}
diff --git a/cfp/urls.py b/cfp/urls.py
index c676c01..062cbde 100644
--- a/cfp/urls.py
+++ b/cfp/urls.py
@@ -37,6 +37,8 @@ urlpatterns = [
path('staff/talks//confirm/', views.talk_acknowledgment, {'confirm': True}, name='talk-confirm-by-staff'),
path('staff/talks//desist/', views.talk_acknowledgment, {'confirm': False}, name='talk-desist-by-staff'),
path('staff/talks//edit/', views.TalkUpdate.as_view(), name='talk-edit'),
+ path('staff/talks/email/', views.talk_email, name='talk-email'),
+ path('staff/talks/email/preview/', views.talk_email_preview, name='talk-email-preview'),
path('staff/speakers/', views.participant_list, name='participant-list'),
path('staff/speakers/add/', views.ParticipantCreate.as_view(), name='participant-add'),
path('staff/speakers//', views.participant_details, name='participant-details'),
diff --git a/cfp/views.py b/cfp/views.py
index d1c8854..1c01648 100644
--- a/cfp/views.py
+++ b/cfp/views.py
@@ -5,13 +5,15 @@ from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DeleteView, FormView, TemplateView
from django.contrib import messages
-from django.db.models import Q
+from django.db.models import Q, Count, Sum
from django.views.generic import CreateView, DetailView, ListView, UpdateView
-from django.http import HttpResponse, Http404
+from django.http import HttpResponse, Http404, HttpResponseServerError
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 import forms
+from django.views.decorators.http import require_http_methods
from django_select2.views import AutoResponseView
@@ -25,10 +27,11 @@ from .decorators import speaker_required, volunteer_required, staff_required
from .mixins import StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin
from .utils import is_staff
from .models import Participant, Talk, TalkCategory, Vote, Track, Tag, Room, Volunteer, Activity
+from .emails import talk_email_send, talk_email_render_preview
from .forms import TalkForm, TalkStaffForm, TalkFilterForm, TalkActionForm, get_talk_speaker_form_class, \
ParticipantForm, ParticipantFilterForm, NotifyForm, \
ConferenceForm, HomepageForm, CreateUserForm, TrackForm, RoomForm, \
- VolunteerForm, VolunteerFilterForm, MailForm, \
+ VolunteerForm, VolunteerFilterForm, EmailForm, PreviewMailForm, SendMailForm, \
TagForm, TalkCategoryForm, ActivityForm, \
ACCEPTATION_VALUES, CONFIRMATION_VALUES
@@ -90,7 +93,7 @@ Thanks!
def volunteer_mail_token(request):
- form = MailForm(request.POST or None)
+ form = EmailForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
try:
volunteer = Volunteer.objects.get(site=request.conference.site, email=form.cleaned_data['email'])
@@ -272,7 +275,7 @@ Thanks!
def proposal_mail_token(request):
- form = MailForm(request.POST or None)
+ form = EmailForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
try:
speaker = Participant.objects.get(site=request.conference.site, email=form.cleaned_data['email'])
@@ -642,6 +645,9 @@ def talk_list(request):
if data['room']:
talk.room = Room.objects.get(site=request.conference.site, slug=data['room'])
talk.save()
+ if data['email']:
+ request.session['talk-email-list'] = data['talks']
+ return redirect(reverse('talk-email'))
return redirect(request.get_full_path())
# Sorting
if request.GET.get('order') == 'desc':
@@ -687,6 +693,7 @@ def talk_list(request):
'sort_urls': sort_urls,
'sort_glyphicons': sort_glyphicons,
'csv_link': csv_link,
+ 'pending_email': bool(request.session.get('talk-email-list', None)),
})
@@ -777,6 +784,45 @@ def talk_decide(request, talk_id, accept):
})
+@staff_required
+def talk_email(request):
+ talks = Talk.objects.filter(pk__in=request.session.get('talk-email-list', []))
+ count = talks.annotate(speakers_count=Count('speakers', distinct=True)).aggregate(Sum('speakers_count'))['speakers_count__sum']
+ if not talks.exists():
+ messages.error(request, _('Please select some talks.'))
+ return redirect('talk-list')
+ form = SendMailForm(request.POST or None, initial=request.session.get('talk-email-stored'), talks=talks)
+ if request.method == 'POST' and form.is_valid():
+ subject = form.cleaned_data['subject']
+ body = form.cleaned_data['body']
+ request.session['talk-email-stored'] = {'subject': subject, 'body': body}
+ if form.cleaned_data['confirm']:
+ sent = talk_email_send(talks, subject, body)
+ messages.success(request, _('%(count)d mails have been sent.') % {'count': sent})
+ del request.session['talk-email-list']
+ return redirect('talk-list')
+ else:
+ messages.info(request, _('Your ready to send %(count)d emails.') % {'count': count})
+ else:
+ form.fields.pop('confirm')
+ return render(request, 'cfp/staff/talk_email.html', {
+ 'talks': talks,
+ 'form': form,
+ })
+
+
+@require_http_methods(['POST'])
+@staff_required
+def talk_email_preview(request):
+ form = PreviewMailForm(request.POST or None)
+ if not form.is_valid():
+ return HttpResponseServerError()
+ speaker = get_object_or_404(Participant, site=request.conference.site, pk=form.cleaned_data['speaker'])
+ talk = get_object_or_404(Talk, site=request.conference.site, pk=form.cleaned_data['talk'])
+ preview = talk_email_render_preview(talk, speaker, form.cleaned_data['subject'], form.cleaned_data['body'])
+ return HttpResponse(preview)
+
+
@staff_required
def participant_list(request):
participants = Participant.objects.filter(site=request.conference.site) \
diff --git a/locale/fr/LC_MESSAGES/django.mo b/locale/fr/LC_MESSAGES/django.mo
index ce4146d..b193baa 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 4c362a7..53cc2a2 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-12-01 20:44+0000\n"
-"PO-Revision-Date: 2017-12-01 21:44+0100\n"
+"POT-Creation-Date: 2017-12-10 15:23+0000\n"
+"PO-Revision-Date: 2017-12-10 16:25+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
@@ -100,167 +100,204 @@ msgstr "Profil modifié avec succès !"
msgid "Please correct those errors."
msgstr "Merci de corriger ces erreurs."
-#: cfp/forms.py:17
+#: cfp/emails.py:28
+msgid "There is an error in your subject template."
+msgstr "Il y a une erreur dans le gabarit du sujet."
+
+#: cfp/emails.py:32
+msgid "There is an error in your body template."
+msgstr "Il y a une erreur dans le gabarit du corps."
+
+#: cfp/emails.py:34
+msgid "Environment:"
+msgstr "Environnement :"
+
+#: cfp/emails.py:35
+msgid "Subject:"
+msgstr "Sujet :"
+
+#: cfp/emails.py:35
+msgid "Body:"
+msgstr "Corps :"
+
+#: cfp/forms.py:18
msgid "Pending decision"
msgstr "Décision en attente"
-#: cfp/forms.py:18 cfp/forms.py:125 cfp/forms.py:222
+#: cfp/forms.py:19 cfp/forms.py:126 cfp/forms.py:224
msgid "Accepted"
msgstr "Accepté"
-#: cfp/forms.py:19
+#: cfp/forms.py:20
msgid "Declined"
msgstr "Décliné"
-#: cfp/forms.py:28
+#: cfp/forms.py:29
msgid "Waiting"
msgstr "En attente"
-#: cfp/forms.py:29 cfp/forms.py:131 cfp/forms.py:228 cfp/models.py:379
+#: cfp/forms.py:30 cfp/forms.py:132 cfp/forms.py:230 cfp/models.py:379
msgid "Confirmed"
msgstr "Confirmé"
-#: cfp/forms.py:30 cfp/models.py:381
+#: cfp/forms.py:31 cfp/models.py:381
msgid "Cancelled"
msgstr "Annulé"
-#: cfp/forms.py:62 cfp/models.py:470
+#: cfp/forms.py:63 cfp/models.py:470
msgid "Activity"
msgstr "Activité"
-#: cfp/forms.py:72
+#: cfp/forms.py:73
msgctxt "activity"
msgid "None"
msgstr "Aucune"
-#: cfp/forms.py:99
+#: cfp/forms.py:100
#, python-format
msgid "Default duration: %(duration)d min"
msgstr "Durée par défaut : %(duration)d min"
-#: cfp/forms.py:107 cfp/forms.py:119 cfp/forms.py:216
+#: cfp/forms.py:108 cfp/forms.py:120 cfp/forms.py:218
#: cfp/templates/cfp/staff/talk_details.html:15
msgid "Category"
msgstr "Catégorie"
-#: cfp/forms.py:108 cfp/templates/cfp/staff/talk_list.html:43
+#: cfp/forms.py:109 cfp/templates/cfp/staff/talk_list.html:51
msgid "Title"
msgstr "Titre"
-#: cfp/forms.py:109 cfp/models.py:163 cfp/models.py:466
+#: cfp/forms.py:110 cfp/models.py:163 cfp/models.py:466
#: cfp/templates/cfp/proposal_talk_details.html:75
#: cfp/templates/cfp/staff/talk_details.html:64
msgid "Description"
msgstr "Description"
-#: cfp/forms.py:110 cfp/models.py:117 cfp/models.py:491
+#: cfp/forms.py:111 cfp/models.py:117 cfp/models.py:491
#: cfp/templates/cfp/staff/participant_details.html:24
#: cfp/templates/cfp/staff/talk_details.html:83
#: cfp/templates/cfp/staff/volunteer_details.html:22
msgid "Notes"
msgstr "Notes"
-#: cfp/forms.py:113
+#: cfp/forms.py:114
msgid "Visible by speakers"
msgstr "Visible par les orateurs"
-#: cfp/forms.py:137 cfp/forms.py:234 cfp/models.py:335
+#: cfp/forms.py:138 cfp/forms.py:236 cfp/models.py:335
#: cfp/templates/cfp/staff/talk_details.html:21
-#: cfp/templates/cfp/staff/talk_list.html:46
+#: cfp/templates/cfp/staff/talk_list.html:54
#: cfp/templates/cfp/staff/track_form.html:14
msgid "Track"
msgstr "Session"
-#: cfp/forms.py:143
+#: cfp/forms.py:144
msgid "Tag"
msgstr "Étiquette"
-#: cfp/forms.py:149 cfp/templates/cfp/staff/talk_details.html:88
+#: cfp/forms.py:150 cfp/templates/cfp/staff/talk_details.html:88
msgid "Vote"
msgstr "Vote"
-#: cfp/forms.py:150
+#: cfp/forms.py:151
msgid "Filter talks you already / not yet voted for"
msgstr ""
"Filtrer les propositions pour lesquelles vous avez déjà voté / pas encore "
"voté"
-#: cfp/forms.py:153 cfp/templates/cfp/staff/room_form.html:14
+#: cfp/forms.py:154 cfp/templates/cfp/staff/room_form.html:14
#: cfp/templates/cfp/staff/talk_details.html:38
msgid "Room"
msgstr "Salle"
-#: cfp/forms.py:154
+#: cfp/forms.py:155
msgid "Filter talks already / not yet affected to a room"
msgstr "Filtrer les exposés déjà / pas encore affectées à une salle"
-#: cfp/forms.py:157
+#: cfp/forms.py:158
msgid "Scheduled"
msgstr "Programmé"
-#: cfp/forms.py:158
+#: cfp/forms.py:159
msgid "Filter talks already / not yet scheduled"
msgstr "Filtrer les exposés déjà / pas encore planifiées"
-#: cfp/forms.py:161 cfp/models.py:353
+#: cfp/forms.py:162 cfp/models.py:353
#: cfp/templates/cfp/proposal_talk_details.html:89
#: cfp/templates/cfp/staff/talk_details.html:54
msgid "Materials"
msgstr "Supports"
-#: cfp/forms.py:162
+#: cfp/forms.py:163
msgid "Filter talks with / without materials"
msgstr "Filtrer les exposés avec / sans supports"
-#: cfp/forms.py:165 cfp/templates/cfp/proposal_talk_details.html:93
+#: cfp/forms.py:166 cfp/templates/cfp/proposal_talk_details.html:93
#: cfp/templates/cfp/staff/talk_details.html:58
msgid "Video"
msgstr "Vidéo"
-#: cfp/forms.py:166
+#: cfp/forms.py:167
msgid "Filter talks with / without video"
msgstr "Filtrer les exposés avec / sans vidéo"
-#: cfp/forms.py:175 cfp/forms.py:246
+#: cfp/forms.py:176 cfp/forms.py:248
msgid "Not assigned"
msgstr "Pas encore assignée"
-#: cfp/forms.py:181
+#: cfp/forms.py:182
msgid "Accept talk?"
msgstr "Accepter la proposition ?"
-#: cfp/forms.py:182
+#: cfp/forms.py:183
msgid "Assign to a track"
msgstr "Assigner à une session"
-#: cfp/forms.py:183 cfp/templates/cfp/admin/tag_list.html:11
+#: cfp/forms.py:184 cfp/templates/cfp/admin/tag_list.html:11
msgid "Add a tag"
msgstr "Ajouter une étiquette"
-#: cfp/forms.py:184
+#: cfp/forms.py:185
msgid "Put in a room"
msgstr "Assigner à une salle"
-#: cfp/forms.py:200
+#: cfp/forms.py:186
+msgid "Send a email"
+msgstr "Envoyer un e-mail"
+
+#: cfp/forms.py:202
msgid "Notify by mail?"
msgstr "Notifier par e-mail ?"
-#: cfp/forms.py:250 cfp/models.py:486
+#: cfp/forms.py:252 cfp/models.py:486
#: cfp/templates/cfp/staff/volunteer_list.html:30
msgid "Email"
msgstr "E-mail"
-#: cfp/forms.py:269
+#: cfp/forms.py:258
+msgid "I read my self twice, confirm sending"
+msgstr "Je me suis relu 2 fois, confirmer l’envoi"
+
+#: cfp/forms.py:278
+#, python-format
+msgid ""
+"Your template does not compile (at least) with talk '%(talk)s' and speaker "
+"'%(speaker)s'."
+msgstr ""
+"Vos gabarits ne compile pas avec (au moins) l’exposé « %(talk)s » et "
+"l’intervenant « %(speaker)s »."
+
+#: cfp/forms.py:299
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:295
+#: cfp/forms.py:325
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:300
+#: cfp/forms.py:330
msgid "A user with that email already exists."
msgstr "Un utilisateur avec cet email existe déjà."
@@ -383,7 +420,7 @@ msgstr "Label dans le xml du programme"
#: cfp/templates/cfp/staff/base.html:10
#: cfp/templates/cfp/staff/participant_list.html:8
#: cfp/templates/cfp/staff/talk_details.html:68
-#: cfp/templates/cfp/staff/talk_list.html:45
+#: cfp/templates/cfp/staff/talk_list.html:53
msgid "Speakers"
msgstr "Orateurs"
@@ -462,7 +499,7 @@ msgstr "En cours, score : %(score).1f"
#: cfp/templates/cfp/admin/base.html:14
#: cfp/templates/cfp/staff/volunteer_details.html:27
#: cfp/templates/cfp/staff/volunteer_list.html:32
-#: cfp/templates/cfp/volunteer.html:43
+#: cfp/templates/cfp/volunteer_dashboard.html:43
msgid "Activities"
msgstr "Activités"
@@ -514,7 +551,7 @@ msgstr "Catégories"
#: cfp/templates/cfp/admin/base.html:13 cfp/templates/cfp/admin/tag_list.html:9
#: cfp/templates/cfp/staff/talk_details.html:28
-#: cfp/templates/cfp/staff/talk_list.html:47
+#: cfp/templates/cfp/staff/talk_list.html:55
msgid "Tags"
msgstr "Étiquettes"
@@ -597,6 +634,35 @@ msgstr ""
"Si vous avez déjà soumis une proposition et que vous souhaitez l’éditer, "
"cliquez ici."
+#: cfp/templates/cfp/mails/speaker_send_token.txt:1
+msgid ""
+"Hi {},\n"
+"\n"
+"Someone, probably you, ask to access your profile.\n"
+"You can edit your talks or add new ones following this url:\n"
+"\n"
+" {}\n"
+"\n"
+"If you have any question, your can answer to this email.\n"
+"\n"
+"Sincerely,\n"
+"\n"
+"{}\n"
+msgstr ""
+"Bonjour {},\n"
+"\n"
+"Quelqu’un, sans doute vous, a demandé à accéder à votre profil.\n"
+"Vous pouvez modifier vos propositions ou en soumettre de nouvelles à l’url "
+"suivante :\n"
+"\n"
+" {}\n"
+"\n"
+"Si vous avez une question, vous pouvez répondre à ce mail.\n"
+"\n"
+"Sincèrement,\n"
+"\n"
+"{}\n"
+
#: cfp/templates/cfp/mails/volunteer_send_token.txt:1
#, python-format
msgid ""
@@ -641,19 +707,19 @@ msgid "Nope, Abort"
msgstr "Non, annuler"
#: cfp/templates/cfp/proposal_dashboard.html:11
-#: cfp/templates/cfp/volunteer.html:12
+#: cfp/templates/cfp/volunteer_dashboard.html:12
#, python-format
msgid "Welcome %(name)s!"
msgstr "Bienvenue %(name)s !"
#: cfp/templates/cfp/proposal_dashboard.html:13
#: cfp/templates/cfp/proposal_speaker_form.html:21
-#: cfp/templates/cfp/volunteer.html:14
+#: cfp/templates/cfp/volunteer_dashboard.html:14
msgid "Edit your profile"
msgstr "Éditer votre profil"
#: cfp/templates/cfp/proposal_dashboard.html:18
-#: cfp/templates/cfp/volunteer.html:20
+#: cfp/templates/cfp/volunteer_dashboard.html:20
msgid "Your informations"
msgstr "Vos informations"
@@ -695,7 +761,7 @@ msgstr "Mastodon :"
#: cfp/templates/cfp/proposal_dashboard.html:29
#: cfp/templates/cfp/staff/participant_details.html:38
#: cfp/templates/cfp/staff/volunteer_details.html:14
-#: cfp/templates/cfp/volunteer.html:27
+#: cfp/templates/cfp/volunteer_dashboard.html:27
msgid "Phone number:"
msgstr "Numéro de téléphone :"
@@ -715,7 +781,7 @@ msgstr "avec"
#: cfp/templates/cfp/staff/participant_details.html:53
#: cfp/templates/cfp/staff/room_details.html:21
#: cfp/templates/cfp/staff/room_details.html:39
-#: cfp/templates/cfp/staff/talk_list.html:69
+#: cfp/templates/cfp/staff/talk_list.html:77
msgid "and"
msgstr "et"
@@ -723,13 +789,13 @@ msgstr "et"
msgid "you must confirm you participation"
msgstr "vous devez confirmer votre participation"
-#: cfp/templates/cfp/proposal_dashboard.html:61 cfp/views.py:624
-#: cfp/views.py:743
+#: cfp/templates/cfp/proposal_dashboard.html:61 cfp/views.py:626
+#: cfp/views.py:751
msgid "accepted"
msgstr "accepté"
-#: cfp/templates/cfp/proposal_dashboard.html:63 cfp/views.py:378
-#: cfp/views.py:525
+#: cfp/templates/cfp/proposal_dashboard.html:63 cfp/views.py:380
+#: cfp/views.py:527
msgid "cancelled"
msgstr "annulé"
@@ -793,7 +859,7 @@ msgstr "Éditer cette proposition"
#: cfp/templates/cfp/proposal_talk_details.html:28
#: cfp/templates/cfp/staff/talk_details.html:18
-#: cfp/templates/cfp/staff/talk_list.html:48
+#: cfp/templates/cfp/staff/talk_list.html:56
msgid "Status"
msgstr "Statut"
@@ -867,7 +933,7 @@ msgstr "Programme"
#: cfp/templates/cfp/staff/base.html:11
#: cfp/templates/cfp/staff/participant_details.html:43
-#: cfp/templates/cfp/staff/talk_list.html:8
+#: cfp/templates/cfp/staff/talk_list.html:16
msgid "Talks"
msgstr "Exposés"
@@ -945,19 +1011,19 @@ msgid "Add a speaker"
msgstr "Ajouter un intervenant"
#: cfp/templates/cfp/staff/participant_list.html:12
-#: cfp/templates/cfp/staff/talk_list.html:10
+#: cfp/templates/cfp/staff/talk_list.html:18
#: cfp/templates/cfp/staff/volunteer_list.html:11
msgid "Show filtering options…"
msgstr "Afficher les options de filtrage…"
#: cfp/templates/cfp/staff/participant_list.html:32
-#: cfp/templates/cfp/staff/talk_list.html:31
+#: cfp/templates/cfp/staff/talk_list.html:39
#: cfp/templates/cfp/staff/volunteer_list.html:19
msgid "Filter"
msgstr "Filtrer"
#: cfp/templates/cfp/staff/participant_list.html:38
-#: cfp/templates/cfp/staff/talk_list.html:39
+#: cfp/templates/cfp/staff/talk_list.html:47
#: cfp/templates/cfp/staff/volunteer_list.html:25
msgid "Total:"
msgstr "Total :"
@@ -976,7 +1042,7 @@ msgid "contact by email"
msgstr "contacter par e-mail"
#: cfp/templates/cfp/staff/participant_list.html:53
-#: cfp/templates/cfp/staff/talk_list.html:54
+#: cfp/templates/cfp/staff/talk_list.html:62
#: cfp/templates/cfp/staff/volunteer_list.html:40
msgid "download as csv"
msgstr "télécharger au format CSV"
@@ -1028,7 +1094,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:39
+#: cfp/templates/cfp/staff/talk_list.html:47
#: cfp/templates/cfp/staff/track_list.html:21
msgid "talk"
msgstr "exposé"
@@ -1145,19 +1211,66 @@ msgstr ""
"Commenter cette proposition – ce message sera reçu uniquement par "
"l’équipe d’organisation"
+#: cfp/templates/cfp/staff/talk_email.html:9
+msgid "Send an email to each speaker of each talk"
+msgstr "Envoyer un e-mail à chaque intervenant de chaque exposé"
+
+#: cfp/templates/cfp/staff/talk_email.html:15
+msgid "Please write your email bellow:"
+msgstr "Veuillez écrire votre e-mail ci-dessous :"
+
+#: cfp/templates/cfp/staff/talk_email.html:18
+msgid ""
+"You can use Jinja2 "
+"templating language."
+msgstr ""
+"Vous pouvez utiliser le langage de gabarit Jinja2."
+
+#: cfp/templates/cfp/staff/talk_email.html:19
+msgid ""
+"To see available environment variables, please click on a talk and speaker "
+"combination."
+msgstr ""
+"Pour voir les variables d’environnement disponibles, veuillez cliquer sur "
+"une combinaison d’intervenant et exposé."
+
+#: cfp/templates/cfp/staff/talk_email.html:28
+msgid "To preview your email, click on a speaker and talk combination:"
+msgstr ""
+"Pour voir un aperçu de votre e-mail, cliquez sur une combinaison "
+"d'intervenant et exposé."
+
+#: cfp/templates/cfp/staff/talk_email.html:52
+msgid "Send!"
+msgstr "Envoyer !"
+
+#: cfp/templates/cfp/staff/talk_email.html:56
+msgid "Check template validity"
+msgstr "Vérifier la validité des gabarits"
+
#: cfp/templates/cfp/staff/talk_form.html:10
msgid "Edit a talk"
msgstr "Éditer un exposé"
-#: cfp/templates/cfp/staff/talk_list.html:44
+#: cfp/templates/cfp/staff/talk_list.html:12
+#, python-format
+msgid ""
+"You have a pending e-mail. To continue its edition, click here."
+msgstr ""
+"Vous avez un e-mail en attente d’envoi. Pour continuer son édition, cliquez "
+"ici."
+
+#: cfp/templates/cfp/staff/talk_list.html:52
msgid "Intervention kind"
msgstr "Type d’intervention"
-#: cfp/templates/cfp/staff/talk_list.html:87
+#: cfp/templates/cfp/staff/talk_list.html:95
msgid "For selected talks:"
msgstr "Pour les exposés sélectionnés :"
-#: cfp/templates/cfp/staff/talk_list.html:92
+#: cfp/templates/cfp/staff/talk_list.html:100
msgid "Apply"
msgstr "Appliquer"
@@ -1178,7 +1291,7 @@ msgid "Volunteer"
msgstr "Bénévole"
#: cfp/templates/cfp/staff/volunteer_details.html:11
-#: cfp/templates/cfp/volunteer.html:25
+#: cfp/templates/cfp/volunteer_dashboard.html:25
msgid "Email:"
msgstr "E-mail :"
@@ -1202,20 +1315,20 @@ msgstr "bénévole"
msgid "Phone"
msgstr "Téléphone"
-#: cfp/templates/cfp/volunteer.html:32
+#: cfp/templates/cfp/volunteer_dashboard.html:32
msgctxt "phone number"
msgid "not provided"
msgstr "non fourni"
-#: cfp/templates/cfp/volunteer.html:36
+#: cfp/templates/cfp/volunteer_dashboard.html:36
msgid "Notes:"
msgstr "Notes :"
-#: cfp/templates/cfp/volunteer.html:53
+#: cfp/templates/cfp/volunteer_dashboard.html:53
msgid "I will be happy to help on that!"
msgstr "Je serai heureux d’aider à cela !"
-#: cfp/templates/cfp/volunteer.html:55
+#: cfp/templates/cfp/volunteer_dashboard.html:55
msgid "Sorry, I have a setback"
msgstr "Désolé, j’ai un contretemps"
@@ -1241,7 +1354,7 @@ msgstr ""
"mettre à jour vos disponibilités, cliquez ici."
-#: cfp/views.py:66
+#: cfp/views.py:68
msgid ""
"Hi {},\n"
"\n"
@@ -1269,44 +1382,44 @@ msgstr ""
"{}\n"
"\n"
-#: cfp/views.py:82
+#: cfp/views.py:84
#, python-format
msgid "[%(conference)s] Thank you for your help!"
msgstr "[%(conference)s] Merci pour votre aide !"
-#: cfp/views.py:85
+#: cfp/views.py:87
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:99 cfp/views.py:281
+#: cfp/views.py:101 cfp/views.py:283
msgid "Sorry, we do not know this email."
msgstr "Désolé, nous ne connaissons pas cette e-mail."
-#: cfp/views.py:112 cfp/views.py:303
+#: cfp/views.py:114 cfp/views.py:305
#, python-format
msgid "[%(conference)s] Someone asked to access your profil"
msgstr "[%(conference)s] Quelqu’un a demandé à accéder à votre profil"
-#: cfp/views.py:115 cfp/views.py:308
+#: cfp/views.py:117 cfp/views.py:310
msgid "A email have been sent with a link to access to your profil."
msgstr "Un e-mail vous a été envoyé avec un lien pour accéder à votre profil."
-#: cfp/views.py:135 cfp/views.py:346 cfp/views.py:433
+#: cfp/views.py:137 cfp/views.py:348 cfp/views.py:435
msgid "Changes saved."
msgstr "Modifications sauvegardées."
-#: cfp/views.py:148
+#: cfp/views.py:150
msgid "Thank you for your participation!"
msgstr "Merci pour votre participation !"
-#: cfp/views.py:151
+#: cfp/views.py:153
msgid "Okay, no problem!"
msgstr "Ok, pas de soucis !"
-#: cfp/views.py:234
+#: cfp/views.py:236
msgid ""
"Hi {},\n"
"\n"
@@ -1346,16 +1459,16 @@ msgstr ""
"{}\n"
"\n"
-#: cfp/views.py:261
+#: cfp/views.py:263
#, python-format
msgid "[%(conference)s] Thank you for your proposition '%(talk)s'"
msgstr "[%(conference)s] Merci pour votre proposition « %(talk)s »"
-#: cfp/views.py:267 cfp/views.py:350
+#: cfp/views.py:269 cfp/views.py:352
msgid "You proposition have been successfully submitted!"
msgstr "Votre proposition a été transmise avec succès !"
-#: cfp/views.py:286
+#: cfp/views.py:288
msgid ""
"Hi {},\n"
"\n"
@@ -1386,37 +1499,37 @@ msgstr ""
"{}\n"
"\n"
-#: cfp/views.py:367
+#: cfp/views.py:369
msgid "You already confirmed your participation to this talk."
msgstr "Vous avez déjà confirmé votre participation à cet exposé."
-#: cfp/views.py:369
+#: cfp/views.py:371
msgid "You already cancelled your participation to this talk."
msgstr "Vous avez déjà annulé votre participation à cet exposé."
-#: cfp/views.py:374
+#: cfp/views.py:376
msgid "Your participation has been taken into account, thank you!"
msgstr "Votre participation a été prise en compte, merci !"
-#: cfp/views.py:375 cfp/views.py:521
+#: cfp/views.py:377 cfp/views.py:523
msgid "confirmed"
msgstr "confirmé"
-#: cfp/views.py:377
+#: cfp/views.py:379
msgid "We have noted your unavailability."
msgstr "Nous avons enregistré votre indisponibilité."
-#: cfp/views.py:379
+#: cfp/views.py:381
#, python-format
msgid "Speaker %(speaker)s %(action)s his/her participation for %(talk)s."
msgstr "L’orateur %(speaker)s a %(action) sa participation pour %(talk)s."
-#: cfp/views.py:387
+#: cfp/views.py:389
#, python-format
msgid "[%(conference)s] %(speaker)s %(action)s his/her participation"
msgstr "[%(conference)s] %(speaker)s a %(action) sa participation"
-#: cfp/views.py:440
+#: cfp/views.py:442
msgid ""
"Hi {},\n"
"\n"
@@ -1456,87 +1569,101 @@ msgstr ""
"{}\n"
"\n"
-#: cfp/views.py:468
+#: cfp/views.py:470
#, python-format
msgid "[%(conference)s] You have been added as co-speaker to '%(talk)s'"
msgstr ""
"[%(conference)s] Vous avez été ajouté comme co-intervenant pour « %(talk)s »"
-#: cfp/views.py:474 cfp/views.py:494
+#: cfp/views.py:476 cfp/views.py:496
msgid "Co-speaker successfully added to the talk."
msgstr "Co-intervenant ajouté à l’exposé avec succès."
-#: cfp/views.py:507
+#: cfp/views.py:509
msgid "Co-speaker successfully removed from the talk."
msgstr "Co-intervenant supprimé de l’exposé avec succès."
-#: cfp/views.py:520
+#: cfp/views.py:522
msgid "The speaker confirmation have been noted."
msgstr "La confirmation de l’orateur a été notée."
-#: cfp/views.py:522
+#: cfp/views.py:524
msgid "The talk have been confirmed."
msgstr "L’exposé a été confirmé."
-#: cfp/views.py:524
+#: cfp/views.py:526
msgid "The speaker unavailability have been noted."
msgstr "L’indisponibilité de l’intervenant a été notée."
-#: cfp/views.py:526
+#: cfp/views.py:528
#, python-format
msgid "The talk have been %(action)s."
msgstr "L’exposé a été %(action)s."
-#: cfp/views.py:530
+#: cfp/views.py:532
#, python-format
msgid "[%(conference)s] The talk '%(talk)s' have been %(action)s."
msgstr "[%(conference)s] L’exposé « %(talk)s » a été %(action)s."
-#: cfp/views.py:626 cfp/views.py:745
+#: cfp/views.py:628 cfp/views.py:753
msgid "declined"
msgstr "décliné"
-#: cfp/views.py:627 cfp/views.py:769
+#: cfp/views.py:629 cfp/views.py:777
#, python-format
msgid "The talk has been %(action)s."
msgstr "L’exposé a été %(action)s."
-#: cfp/views.py:631 cfp/views.py:764
+#: cfp/views.py:633 cfp/views.py:772
#, python-format
msgid "[%(conference)s] The talk '%(talk)s' have been %(action)s"
msgstr "[%(conference)s] L’exposé « %(talk)s » a été %(action)s"
-#: cfp/views.py:704
+#: cfp/views.py:710
#, python-format
msgid "[%(conference)s] New comment about '%(talk)s'"
msgstr "[%(conference)s] Nouveau commentaire sur « %(talk)s »"
-#: cfp/views.py:718 cfp/views.py:841
+#: cfp/views.py:724 cfp/views.py:893
msgid "Message sent!"
msgstr "Message envoyé !"
-#: cfp/views.py:732
+#: cfp/views.py:740
msgid "Vote successfully created"
msgstr "A voté !"
-#: cfp/views.py:732
+#: cfp/views.py:740
msgid "Vote successfully updated"
msgstr "Vote mis à jour"
-#: cfp/views.py:753
+#: cfp/views.py:761
#, python-format
msgid "[%(conference)s] Your talk '%(talk)s' have been %(action)s"
msgstr "[%(conference)s] Votre exposé « %(talk)s » a été %(action)s"
-#: cfp/views.py:771
+#: cfp/views.py:779
msgid "Decision taken in account"
msgstr "Décision enregistrée"
-#: cfp/views.py:911
+#: cfp/views.py:792
+msgid "Please select some talks."
+msgstr "Veuillez sélectionner un ou plusieurs exposés."
+
+#: cfp/views.py:801
+#, python-format
+msgid "%(count)d mails have been sent."
+msgstr "%(count)d e-mails ont été envoyés."
+
+#: cfp/views.py:805
+#, python-format
+msgid "Your ready to send %(count)d emails."
+msgstr "Vous êtes prêt pour envoyer %(count)d e-mails."
+
+#: cfp/views.py:963
msgid "[{}] You have been added to the staff team"
msgstr "[{}] Vous avez été ajouté aux membres du staff"
-#: cfp/views.py:912
+#: cfp/views.py:964
msgid ""
"Hi {},\n"
"\n"
@@ -1560,15 +1687,15 @@ msgstr ""
"{}\n"
"\n"
-#: cfp/views.py:933 cfp/views.py:945
+#: cfp/views.py:985 cfp/views.py:997
msgid "Modifications successfully saved."
msgstr "Modification enregistrée avec succès."
-#: cfp/views.py:1109
+#: cfp/views.py:1161
msgid "User created successfully."
msgstr "Utilisateur créé avec succès."
-#: cfp/views.py:1130
+#: cfp/views.py:1182
#, python-format
msgid "Format '%s' not available"
msgstr "Format '%s' non disponible"
@@ -1586,11 +1713,11 @@ msgstr "Envoyer"
msgid "No messages."
msgstr "Aucun message."
-#: ponyconf/settings.py:142
+#: ponyconf/settings.py:141
msgid "English"
msgstr "Anglais"
-#: ponyconf/settings.py:143
+#: ponyconf/settings.py:142
msgid "French"
msgstr "Français"
@@ -1642,33 +1769,13 @@ msgstr "Mot de passe oublié ?"
msgid "Password Change"
msgstr "Changement de mot de passe"
-#~ msgid ""
-#~ "Hi {},\n"
-#~ "\n"
-#~ "Someone, probably you, ask to access your profile.\n"
-#~ "You can edit your talks or add new ones following this url:\n"
-#~ "\n"
-#~ " {}\n"
-#~ "\n"
-#~ "If you have any question, your can answer to this email.\n"
-#~ "\n"
-#~ "Sincerely,\n"
-#~ "\n"
-#~ "{}\n"
-#~ msgstr ""
-#~ "Bonjour {},\n"
-#~ "\n"
-#~ "Quelqu’un, sans doute vous, a demandé à accéder à votre profil.\n"
-#~ "Vous pouvez modifier vos propositions ou en soumettre de nouvelles à "
-#~ "l’url suivante :\n"
-#~ "\n"
-#~ " {}\n"
-#~ "\n"
-#~ "Si vous avez une question, vous pouvez répondre à ce mail.\n"
-#~ "\n"
-#~ "Sincèrement,\n"
-#~ "\n"
-#~ "{}\n"
+#~ msgid "There is an error in your content template."
+#~ msgstr "Il y a une erreur dans le gabarit du corps."
+
+#, fuzzy
+#~| msgid "Contact:"
+#~ msgid "Content:"
+#~ msgstr "Contacter :"
#~ msgid "We are looking for help with the following activities:"
#~ msgstr "Nous cherchons de l’aide pour les activités suivantes :"
@@ -1694,9 +1801,6 @@ msgstr "Changement de mot de passe"
#~ msgid "The talk has been declined."
#~ msgstr "L’exposé a été décliné."
-#~ msgid "Contact:"
-#~ msgstr "Contacter :"
-
#~ msgid "link"
#~ msgstr "lien"
diff --git a/requirements.in b/requirements.in
index a1a51ed..c63d306 100644
--- a/requirements.in
+++ b/requirements.in
@@ -11,3 +11,4 @@ markdown
bleach
chardet
icalendar
+jinja2