diff --git a/faqs/__init__.py b/faqs/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/faqs/forms.py b/faqs/forms.py
new file mode 100644
index 0000000..3c8aa41
--- /dev/null
+++ b/faqs/forms.py
@@ -0,0 +1,28 @@
+from translated_fields import language_code_formfield_callback
+
+from django import forms
+
+from .models import Faq
+
+
+class FaqForm(forms.ModelForm):
+ formfield_callback = language_code_formfield_callback
+
+ class Meta:
+ model = Faq
+ fields = [
+ *Faq.title.fields,
+ "anchor",
+ *Faq.description.fields,
+ *Faq.content.fields,
+ ]
+ widgets = {
+ "description_en": forms.Textarea(
+ attrs={"rows": 4, "class": "is-family-monospace"}
+ ),
+ "description_fr": forms.Textarea(
+ attrs={"rows": 4, "class": "is-family-monospace"}
+ ),
+ "content_en": forms.Textarea(attrs={"class": "is-family-monospace"}),
+ "content_fr": forms.Textarea(attrs={"class": "is-family-monospace"}),
+ }
diff --git a/faqs/migrations/0001_initial.py b/faqs/migrations/0001_initial.py
new file mode 100644
index 0000000..a326379
--- /dev/null
+++ b/faqs/migrations/0001_initial.py
@@ -0,0 +1,66 @@
+# Generated by Django 3.2.4 on 2021-06-15 08:34
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Faq",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("title_fr", models.CharField(max_length=255, verbose_name="titre")),
+ (
+ "title_en",
+ models.CharField(blank=True, max_length=255, verbose_name="titre"),
+ ),
+ ("description_fr", models.TextField(verbose_name="description")),
+ (
+ "description_en",
+ models.TextField(blank=True, verbose_name="description"),
+ ),
+ ("content_fr", models.TextField(blank=True, verbose_name="contenu")),
+ ("content_en", models.TextField(blank=True, verbose_name="contenu")),
+ (
+ "last_modified",
+ models.DateField(auto_now=True, verbose_name="mise à jour"),
+ ),
+ ("anchor", models.CharField(max_length=20, verbose_name="ancre")),
+ (
+ "author",
+ models.ForeignKey(
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="faqs",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ options={
+ "permissions": [("is_author", "Can create faqs")],
+ },
+ ),
+ migrations.AddConstraint(
+ model_name="faq",
+ constraint=models.UniqueConstraint(
+ fields=("anchor",), name="unique_faq_anchor"
+ ),
+ ),
+ ]
diff --git a/faqs/migrations/__init__.py b/faqs/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/faqs/mixins.py b/faqs/mixins.py
new file mode 100644
index 0000000..8a09157
--- /dev/null
+++ b/faqs/mixins.py
@@ -0,0 +1,14 @@
+from django.contrib.auth.mixins import PermissionRequiredMixin
+
+
+class AdminOnlyMixin(PermissionRequiredMixin):
+ """Restreint l'accès aux admins"""
+
+ permission_required = "faqs.is_author"
+
+
+class CreatorOnlyMixin(AdminOnlyMixin):
+ """Restreint l'accès à l'auteur"""
+
+ def get_queryset(self):
+ return super().get_queryset().filter(author=self.request.user)
diff --git a/faqs/models.py b/faqs/models.py
new file mode 100644
index 0000000..f972c61
--- /dev/null
+++ b/faqs/models.py
@@ -0,0 +1,32 @@
+from translated_fields import TranslatedFieldWithFallback
+
+from django.contrib.auth import get_user_model
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+User = get_user_model()
+
+
+class Faq(models.Model):
+ title = TranslatedFieldWithFallback(
+ models.CharField(_("titre"), blank=False, max_length=255)
+ )
+ description = TranslatedFieldWithFallback(
+ models.TextField(_("description"), blank=False)
+ )
+ content = TranslatedFieldWithFallback(models.TextField(_("contenu"), blank=True))
+
+ author = models.ForeignKey(
+ User, related_name="faqs", null=True, on_delete=models.SET_NULL
+ )
+ last_modified = models.DateField(_("mise à jour"), auto_now=True)
+
+ anchor = models.CharField(_("ancre"), max_length=20)
+
+ class Meta:
+ permissions = [
+ ("is_author", "Can create faqs"),
+ ]
+ constraints = [
+ models.UniqueConstraint(fields=["anchor"], name="unique_faq_anchor")
+ ]
diff --git a/faqs/templates/faqs/faq.html b/faqs/templates/faqs/faq.html
new file mode 100644
index 0000000..79d5c5c
--- /dev/null
+++ b/faqs/templates/faqs/faq.html
@@ -0,0 +1,43 @@
+{% extends "base.html" %}
+{% load i18n markdown %}
+
+
+{% block content %}
+
+
+ {# Titre de la FAQ #}
+
+
{{ faq.title }}
+
+
+
+ {# Date de dernière modification #}
+
+ {% blocktrans with maj=faq.last_modified|date:'d/m/Y' %}Mis à jour le {{ maj }}{% endblocktrans %}
+
+
+ {# Lien vers la page d'édition #}
+ {% if faq.author == user %}
+
+ {% endif %}
+
+
+
+
+{# Description #}
+
+
{{ faq.description|markdown|safe }}
+
+
+{# Contenu #}
+
+ {{ faq.content|markdown|safe }}
+
+
+{% endblock %}
diff --git a/faqs/templates/faqs/faq_create.html b/faqs/templates/faqs/faq_create.html
new file mode 100644
index 0000000..c1c52b4
--- /dev/null
+++ b/faqs/templates/faqs/faq_create.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+
+{% block content %}
+
+{% for error in form.non_field_errors %}
+
+ {{ error }}
+
+{% endfor %}
+
+{% trans "Nouvelle FAQ" %}
+
+
+{% url 'faq.list' as r_url %}
+{% include "forms/common-form.html" with c_size="is-9" errors=False %}
+
+{% endblock %}
+
diff --git a/faqs/templates/faqs/faq_edit.html b/faqs/templates/faqs/faq_edit.html
new file mode 100644
index 0000000..4b31145
--- /dev/null
+++ b/faqs/templates/faqs/faq_edit.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+
+{% block content %}
+
+{% for error in form.non_field_errors %}
+
+ {{ error }}
+
+{% endfor %}
+
+{% trans "Modification de la FAQ" %}
+
+
+{% url 'faq.view' faq.anchor as r_url %}
+{% include "forms/common-form.html" with c_size="is-9" errors=False %}
+
+{% endblock %}
+
diff --git a/faqs/templates/faqs/faq_list.html b/faqs/templates/faqs/faq_list.html
new file mode 100644
index 0000000..5eefaf0
--- /dev/null
+++ b/faqs/templates/faqs/faq_list.html
@@ -0,0 +1,48 @@
+{% extends "base.html" %}
+{% load i18n markdown %}
+
+
+{% block content %}
+
+
+
+
+
{% trans "Liste des FAQ" %}
+
+
+
+ {% if perms.faqs.is_author %}
+
+ {% endif %}
+
+
+
+{% for f in faq_list %}
+
+
+
+ {% if f.description %}
+
+
+ {{ f.description|markdown|safe }}
+
+
+ {% endif %}
+
+{% if not forloop.last %}
+
+{% endif %}
+{% endfor %}
+
+{% endblock %}
diff --git a/faqs/urls.py b/faqs/urls.py
new file mode 100644
index 0000000..e89febf
--- /dev/null
+++ b/faqs/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+ # Admin views
+ path("create", views.FaqCreateView.as_view(), name="faq.create"),
+ path("edit/", views.FaqEditView.as_view(), name="faq.edit"),
+ # Public views
+ path("", views.FaqListView.as_view(), name="faq.list"),
+ path("view/", views.FaqView.as_view(), name="faq.view"),
+]
diff --git a/faqs/views.py b/faqs/views.py
new file mode 100644
index 0000000..7857e12
--- /dev/null
+++ b/faqs/views.py
@@ -0,0 +1,54 @@
+from django.contrib.messages.views import SuccessMessageMixin
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+from django.views.generic import CreateView, DetailView, ListView, UpdateView
+
+from .forms import FaqForm
+from .mixins import AdminOnlyMixin, CreatorOnlyMixin
+from .models import Faq
+
+# #############################################################################
+# Administration Views
+# #############################################################################
+
+
+class FaqCreateView(AdminOnlyMixin, SuccessMessageMixin, CreateView):
+ model = Faq
+ form_class = FaqForm
+ success_message = _("Faq créée avec succès !")
+ template_name = "faqs/faq_create.html"
+
+ def get_success_url(self):
+ return reverse("faq.view", args=[self.object.anchor])
+
+ def form_valid(self, form):
+ form.instance.author = self.request.user
+
+ return super().form_valid(form)
+
+
+class FaqEditView(CreatorOnlyMixin, SuccessMessageMixin, UpdateView):
+ model = Faq
+ form_class = FaqForm
+ slug_field = "anchor"
+ success_message = _("Faq modifiée avec succès !")
+ template_name = "faqs/faq_edit.html"
+
+ def get_success_url(self):
+ return reverse("faq.view", args=[self.object.anchor])
+
+
+# #############################################################################
+# Public Views
+# #############################################################################
+
+
+class FaqListView(ListView):
+ model = Faq
+ template_name = "faqs/faq_list.html"
+
+
+class FaqView(DetailView):
+ model = Faq
+ template_name = "faqs/faq.html"
+ slug_field = "anchor"
diff --git a/kadenios/settings/common.py b/kadenios/settings/common.py
index bbbb328..6143d80 100644
--- a/kadenios/settings/common.py
+++ b/kadenios/settings/common.py
@@ -55,6 +55,7 @@ INSTALLED_APPS = [
"kadenios.apps.IgnoreSrcStaticFilesConfig",
"shared",
"elections",
+ "faqs",
"authens",
]
diff --git a/kadenios/urls.py b/kadenios/urls.py
index 4c81a39..e8ace9b 100644
--- a/kadenios/urls.py
+++ b/kadenios/urls.py
@@ -8,6 +8,7 @@ urlpatterns = [
path("", HomeView.as_view(), name="kadenios"),
path("admin/", admin.site.urls),
path("elections/", include("elections.urls")),
+ path("faqs/", include("faqs.urls")),
path("auth/", include("shared.auth.urls")),
path("authens/", include("authens.urls")),
path("i18n/", include("django.conf.urls.i18n")),
diff --git a/shared/locale/en/LC_MESSAGES/django.mo b/shared/locale/en/LC_MESSAGES/django.mo
index bd0732d..087532a 100644
Binary files a/shared/locale/en/LC_MESSAGES/django.mo and b/shared/locale/en/LC_MESSAGES/django.mo differ
diff --git a/shared/locale/en/LC_MESSAGES/django.po b/shared/locale/en/LC_MESSAGES/django.po
index 51af167..5d4b062 100644
--- a/shared/locale/en/LC_MESSAGES/django.po
+++ b/shared/locale/en/LC_MESSAGES/django.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-05-28 10:58+0200\n"
-"PO-Revision-Date: 2021-05-28 10:59+0200\n"
+"POT-Creation-Date: 2021-06-18 19:34+0200\n"
+"PO-Revision-Date: 2021-06-18 21:19+0200\n"
"Last-Translator: Test Translator \n"
"Language-Team: \n"
"Language: en\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 2.4.3\n"
+"X-Generator: Poedit 3.0\n"
#: elections/forms.py:19
msgid "Impossible de faire débuter l'élection dans le passé"
@@ -31,20 +31,22 @@ msgid ""
"Le mail d'annonce a déjà été envoyé, il n'est pas possible d'ouvrir "
"l'élection à tout le monde"
msgstr ""
+"The announcement email has already been sent, it is not possible to open the "
+"election to everyone"
-#: elections/forms.py:55
+#: elections/forms.py:63
msgid "Sélectionnez un fichier .csv"
msgstr "Select a .csv file"
-#: elections/forms.py:65
+#: elections/forms.py:73
msgid "Extension de fichier invalide, il faut un fichier au format CSV."
msgstr "Invalid file extension, a CSV file is required."
-#: elections/forms.py:72
+#: elections/forms.py:80
msgid "Objet"
msgstr "Subject"
-#: elections/forms.py:94
+#: elections/forms.py:102
msgid ""
"L'abréviation est optionnelle et sert à identifier plus facilement les "
"différentes options. Elle est affiché sans espaces et en majuscules."
@@ -52,116 +54,120 @@ msgstr ""
"The abbreviation is optional and serves to identify the different options "
"more easily. It is displayed without spaces and in capital letters."
-#: elections/forms.py:105
+#: elections/forms.py:113
msgid "Supprimer le vote de {} ({}) ?"
msgstr "Delete the vote of {} ({}) ?"
-#: elections/forms.py:110 elections/templates/elections/election_admin.html:191
-#: elections/templates/elections/election_admin.html:218
+#: elections/forms.py:118 elections/templates/elections/election_admin.html:243
+#: elections/templates/elections/election_admin.html:270
#: elections/templates/elections/election_voters.html:82
msgid "Supprimer"
msgstr "Delete"
-#: elections/forms.py:110
+#: elections/forms.py:118
msgid "Non"
msgstr "No"
-#: elections/forms.py:110
+#: elections/forms.py:118
msgid "Oui"
msgstr "Yes"
-#: elections/models.py:31
+#: elections/models.py:33
msgid "nom"
msgstr "name"
-#: elections/models.py:32
+#: elections/models.py:34
msgid "nom bref"
msgstr "short name"
-#: elections/models.py:34
+#: elections/models.py:36 faqs/models.py:15
msgid "description"
msgstr "description"
-#: elections/models.py:37
+#: elections/models.py:39
msgid "date et heure de début"
msgstr "start date and time"
-#: elections/models.py:38
+#: elections/models.py:40
msgid "date et heure de fin"
msgstr "end date and time"
-#: elections/models.py:41
+#: elections/models.py:42
+msgid "visible au public"
+msgstr "visible to everyone"
+
+#: elections/models.py:45
msgid "conditions de vote"
msgstr "voting requirements"
-#: elections/models.py:45
+#: elections/models.py:49
msgid "restreint le vote à une liste de personnes"
msgstr "restricts the vote to a list of people"
-#: elections/models.py:49
+#: elections/models.py:53
msgid "mail avec les identifiants envoyé"
msgstr "mail with credentials sent"
-#: elections/models.py:66
+#: elections/models.py:70
msgid "résultats publics"
msgstr "results published"
-#: elections/models.py:67
+#: elections/models.py:71
msgid "dépouillée"
msgstr "counted"
-#: elections/models.py:69
+#: elections/models.py:73
msgid "archivée"
msgstr "archived"
-#: elections/models.py:79
+#: elections/models.py:77
+msgid "Peut administrer des élections"
+msgstr "Can manage elections"
+
+#: elections/models.py:87
msgid "question"
msgstr "question"
-#: elections/models.py:81
+#: elections/models.py:90
msgid "type de question"
msgstr "type of question"
-#: elections/models.py:88
+#: elections/models.py:97
msgid "nombre maximal de votes reçus"
msgstr "maximal number of votes received"
-#: elections/models.py:139
+#: elections/models.py:154
msgid "texte"
msgstr "text"
-#: elections/models.py:140
+#: elections/models.py:155
msgid "abréviation"
msgstr "abbreviation"
-#: elections/models.py:142
+#: elections/models.py:157
msgid "option gagnante"
msgstr "winning option"
-#: elections/models.py:150
+#: elections/models.py:165
msgid "nombre de votes reçus"
msgstr "number of votes received"
-#: elections/models.py:177
+#: elections/models.py:192
msgid "rang de l'option"
msgstr "option's ranking"
-#: elections/models.py:193
+#: elections/models.py:208
msgid "votes supplémentaires"
msgstr "extra votes"
-#: elections/models.py:209
+#: elections/models.py:224
msgid "Nom et Prénom"
msgstr "Name and surname"
-#: elections/models.py:232 elections/tests/test_models.py:57
+#: elections/models.py:247 elections/tests/test_models.py:57
msgid "identifiants spécifiques"
msgstr "dedicated credentials"
-#: elections/models.py:236
-msgid "Peut administrer des élections"
-msgstr "Can manage elections"
-
#: elections/staticdefs.py:26 elections/tests/test_models.py:56
msgid "mot de passe"
msgstr "password"
@@ -235,7 +241,7 @@ msgid "Élection en cours"
msgstr "Election in progress"
#: elections/templates/elections/election.html:52
-#: elections/templates/elections/election_list.html:72
+#: elections/templates/elections/election_list.html:83
msgid "Administrer"
msgstr "Manage"
@@ -280,70 +286,92 @@ msgstr "Login with credentials"
msgid "Connexion via CAS"
msgstr "Login via CAS"
-#: elections/templates/elections/election.html:189
+#: elections/templates/elections/election.html:194
msgid "A voté"
msgstr "Voted"
-#: elections/templates/elections/election_admin.html:21
-#: elections/templates/elections/election_admin.html:282
-#: elections/templates/elections/election_admin.html:291
-msgid "Rajouter une question"
-msgstr "Add a question"
+#: elections/templates/elections/election_admin.html:51
+#: elections/templates/elections/election_list.html:58
+msgid "Élection invisible"
+msgstr "Invisible election"
-#: elections/templates/elections/election_admin.html:21
-msgid "Modifier la question"
-msgstr "Change the question"
+#: elections/templates/elections/election_admin.html:58
+msgid "Élection visible"
+msgstr "Visible election"
-#: elections/templates/elections/election_admin.html:26
-#: elections/templates/elections/election_admin.html:267
-#: elections/templates/elections/election_admin.html:278
-msgid "Rajouter une option"
-msgstr "Add an option"
-
-#: elections/templates/elections/election_admin.html:26
-msgid "Modifier l'option"
-msgstr "Change the option"
-
-#: elections/templates/elections/election_admin.html:53
+#: elections/templates/elections/election_admin.html:72
msgid "Actions"
msgstr "Actions"
-#: elections/templates/elections/election_admin.html:65
+#: elections/templates/elections/election_admin.html:84
+msgid "Vue classique"
+msgstr "Classic view"
+
+#: elections/templates/elections/election_admin.html:95
+msgid "Rendre l'élection visible"
+msgstr "Make the election visible"
+
+#: elections/templates/elections/election_admin.html:104
msgid "Exporter les votant·e·s"
msgstr "Export the list of voters"
-#: elections/templates/elections/election_admin.html:74
-#: elections/templates/elections/election_admin.html:200
-#: elections/templates/elections/election_admin.html:224
+#: elections/templates/elections/election_admin.html:113
+#: elections/templates/elections/election_admin.html:252
+#: elections/templates/elections/election_admin.html:276
+#: faqs/templates/faqs/faq.html:22
msgid "Modifier"
msgstr "Edit"
-#: elections/templates/elections/election_admin.html:83
+#: elections/templates/elections/election_admin.html:122
#: elections/templates/elections/upload_voters.html:27
msgid "Gestion de la liste de votant·e·s"
msgstr "Management of the voters' list"
-#: elections/templates/elections/election_admin.html:95
+#: elections/templates/elections/election_admin.html:134
#: elections/templates/elections/election_voters.html:48
msgid "Liste des votant·e·s"
msgstr "Voters' list"
-#: elections/templates/elections/election_admin.html:103
+#: elections/templates/elections/election_admin.html:142
msgid "Dépouiller"
msgstr "Count"
-#: elections/templates/elections/election_admin.html:115
+#: elections/templates/elections/election_admin.html:154
msgid "Publier"
msgstr "Publish"
-#: elections/templates/elections/election_admin.html:117
+#: elections/templates/elections/election_admin.html:156
msgid "Dépublier"
msgstr "De-publish"
-#: elections/templates/elections/election_admin.html:128
+#: elections/templates/elections/election_admin.html:166
+msgid "Télécharger les résultats"
+msgstr "Download the results"
+
+#: elections/templates/elections/election_admin.html:175
msgid "Archiver"
msgstr "Archive"
+#: elections/templates/elections/election_admin.html:247
+msgid "Modifier la question"
+msgstr "Change the question"
+
+#: elections/templates/elections/election_admin.html:276
+msgid "Modifier l'option"
+msgstr "Change the option"
+
+#: elections/templates/elections/election_admin.html:315
+#: elections/templates/elections/election_admin.html:319
+#: elections/templates/elections/election_admin.html:330
+msgid "Rajouter une option"
+msgstr "Add an option"
+
+#: elections/templates/elections/election_admin.html:334
+#: elections/templates/elections/election_admin.html:339
+#: elections/templates/elections/election_admin.html:343
+msgid "Rajouter une question"
+msgstr "Add a question"
+
#: elections/templates/elections/election_ballots.html:18
#: elections/templates/elections/election_voters.html:40
#: elections/templates/elections/upload_voters.html:48
@@ -359,7 +387,7 @@ msgstr "Back"
msgid "Liste des bulletins"
msgstr "List of ballots"
-#: elections/templates/elections/election_create.html:31
+#: elections/templates/elections/election_create.html:32
msgid "Création d'une élection"
msgstr "Creating an election"
@@ -371,19 +399,19 @@ msgstr "List of elections"
msgid "Créer une élection"
msgstr "Create an election"
-#: elections/templates/elections/election_list.html:54
+#: elections/templates/elections/election_list.html:65
msgid "Élection dépouillée"
msgstr "Election counted"
-#: elections/templates/elections/election_list.html:60
+#: elections/templates/elections/election_list.html:71
msgid "Élection publiée"
msgstr "Published election"
-#: elections/templates/elections/election_list.html:66
+#: elections/templates/elections/election_list.html:77
msgid "Élection archivée"
msgstr "Archived election"
-#: elections/templates/elections/election_update.html:31
+#: elections/templates/elections/election_update.html:32
msgid "Modification d'une élection"
msgstr "Editing an election"
@@ -519,50 +547,50 @@ msgstr "Confirm"
msgid "Annuler"
msgstr "Cancel"
-#: elections/templates/elections/vote/rank.html:150
+#: elections/templates/elections/vote/rank.html:154
msgid "Classement"
msgstr "Ranking"
-#: elections/templates/elections/vote/rank.html:151
+#: elections/templates/elections/vote/rank.html:155
#: elections/templates/elections/vote/select.html:24
msgid "Option(s) selectionnée(s)"
msgstr "Selected option(s)"
-#: elections/templates/elections/vote/rank.html:171
-#: elections/templates/elections/vote/rank.html:224
+#: elections/templates/elections/vote/rank.html:175
+#: elections/templates/elections/vote/rank.html:228
msgid "Utiliser le formulaire classique"
msgstr "Use the traditional form"
-#: elections/templates/elections/vote/rank.html:176
+#: elections/templates/elections/vote/rank.html:180
msgid "Utiliser le cliquer-déposer"
msgstr "Use Drag & Drop"
-#: elections/templates/elections/vote/rank.html:234
+#: elections/templates/elections/vote/rank.html:238
#, python-format
msgid "Rang %(i)s"
msgstr "Rank %(i)s"
-#: elections/templates/elections/vote/rank.html:245
+#: elections/templates/elections/vote/rank.html:249
msgid "Ajouter un rang"
msgstr "Add an rank"
-#: elections/utils.py:207
+#: elections/utils.py:195
msgid "Vous devez sélectionnner une option."
msgstr "You must select an option."
-#: elections/utils.py:212
+#: elections/utils.py:200
msgid "Vous ne pouvez pas sélectionner plus d'une option."
msgstr "You cannot select more than one option."
-#: elections/utils.py:229
+#: elections/utils.py:217
msgid "Le classement maximal est {}."
msgstr "The maximum ranking is {}."
-#: elections/utils.py:233
+#: elections/utils.py:221
msgid "Le classement minimal est 1."
msgstr "The minimum ranking is 1."
-#: elections/utils.py:347
+#: elections/utils.py:335
msgid ""
"Format invalide. Vérifiez que le fichier est bien formé (i.e. chaque ligne "
"de la forme 'login,nom,email')."
@@ -570,83 +598,132 @@ msgstr ""
"Invalid format. Check that the file is properly formed (i.e. each line of "
"the form 'login,name,e-mail')."
-#: elections/utils.py:361
+#: elections/utils.py:349
msgid "La ligne {} n'a pas le bon nombre d'éléments."
msgstr "The line {} has the wrong number of elements."
-#: elections/utils.py:366
+#: elections/utils.py:354
msgid "Valeur manquante dans la ligne {} : 'login'."
msgstr "Missing value in line {}: 'login'."
-#: elections/utils.py:371
+#: elections/utils.py:359
msgid "Doublon dans les logins : lignes {} et {}."
msgstr "Duplicate logins: lines {} and {}."
-#: elections/utils.py:379
+#: elections/utils.py:367
msgid "Valeur manquante dans la ligne {} : 'nom'."
msgstr "Missing value in line {}: 'name'."
-#: elections/utils.py:385
+#: elections/utils.py:373
msgid "Adresse mail invalide à la ligne {} : '{}'."
msgstr "Invalid e-mail address in line {}: '{}'."
-#: elections/views.py:73
+#: elections/views.py:58
msgid "Élection créée avec succès !"
msgstr "Election successfully created!"
-#: elections/views.py:130
+#: elections/views.py:99
+msgid "Élection visible !"
+msgstr "Election now visible!"
+
+#: elections/views.py:127
msgid "Liste de votant·e·s importée avec succès !"
msgstr "Voters list successfully imported!"
-#: elections/views.py:164
+#: elections/views.py:161
msgid "Mail d'annonce envoyé avec succès !"
msgstr "Announcement e-mail sent successfully!"
-#: elections/views.py:196
+#: elections/views.py:193
msgid "Élection modifiée avec succès !"
msgstr "Election successfully modified!"
-#: elections/views.py:279
+#: elections/views.py:276
msgid "Élection dépouillée avec succès !"
msgstr "Election successfully counted!"
-#: elections/views.py:305
+#: elections/views.py:302
msgid "Élection publiée avec succès !"
msgstr "Election successfully published!"
-#: elections/views.py:306
+#: elections/views.py:303
msgid "Élection dépubliée avec succès !"
msgstr "Election successfully de-published!"
-#: elections/views.py:318
+#: elections/views.py:330
msgid "Élection archivée avec succès !"
msgstr "Election successfully archived!"
-#: elections/views.py:350
+#: elections/views.py:362
msgid "Question modifiée avec succès !"
msgstr "Question successfully modified!"
-#: elections/views.py:362
+#: elections/views.py:374
msgid "Question supprimée !"
msgstr "Question deleted!"
-#: elections/views.py:400
+#: elections/views.py:412
msgid "Option modifiée avec succès !"
msgstr "Option successfully modified!"
-#: elections/views.py:412
+#: elections/views.py:424
msgid "Option supprimée !"
msgstr "Option deleted!"
-#: elections/views.py:566
+#: elections/views.py:578
msgid "Votre choix a bien été enregistré !"
msgstr "Your choice has been recorded!"
-#: kadenios/settings/common.py:138
+#: faqs/models.py:12
+msgid "titre"
+msgstr "title"
+
+#: faqs/models.py:17
+msgid "contenu"
+msgstr "content"
+
+#: faqs/models.py:22
+msgid "mise à jour"
+msgstr "updated"
+
+#: faqs/models.py:24
+msgid "ancre"
+msgstr "anchor"
+
+#: faqs/templates/faqs/faq.html:16
+#, python-format
+msgid "Mis à jour le %(maj)s"
+msgstr "Updated on %(maj)s"
+
+#: faqs/templates/faqs/faq_create.html:13
+msgid "Nouvelle FAQ"
+msgstr "New FAQ"
+
+#: faqs/templates/faqs/faq_edit.html:13
+msgid "Modification de la FAQ"
+msgstr "Editing an FAQ"
+
+#: faqs/templates/faqs/faq_list.html:10
+msgid "Liste des FAQ"
+msgstr "List of FAQs"
+
+#: faqs/templates/faqs/faq_list.html:21
+msgid "Créer une FAQ"
+msgstr "Create a FAQ"
+
+#: faqs/views.py:18
+msgid "Faq créée avec succès !"
+msgstr "FAQ successfully created!"
+
+#: faqs/views.py:34
+msgid "Faq modifiée avec succès !"
+msgstr "FAQ successfully modified!"
+
+#: kadenios/settings/common.py:140
msgid "Français"
msgstr "French"
-#: kadenios/settings/common.py:139
+#: kadenios/settings/common.py:141
msgid "Anglais"
msgstr "English"
@@ -698,20 +775,43 @@ msgstr "Password reset"
msgid "Envoyer un mail"
msgstr "Send an e-mail"
-#: shared/templates/base.html:124
+#: shared/templates/authens/pwd_reset_email.txt:2
+#, python-format
+msgid ""
+"Quelqu'un (probablement vous) a demandé la réinitialisation du mot de passe "
+"associé à cette addresse sur %(site_name)s."
+msgstr ""
+
+#: shared/templates/authens/pwd_reset_email.txt:4
+msgid ""
+"S'il s'agit bien de vous, vous pouvez vous rendre à l'adresse suivante pour "
+"en choisir un nouveau : "
+msgstr ""
+
+#: shared/templates/authens/pwd_reset_email.txt:8
+#, python-format
+msgid "Pour information, votre nom d'utilisateur est le suivant : %(username)s"
+msgstr ""
+
+#: shared/templates/authens/pwd_reset_email.txt:10
+#, python-format
+msgid "L'équipe %(site_name)s"
+msgstr ""
+
+#: shared/templates/base.html:122
msgid "Élections"
msgstr "Elections"
-#: shared/templates/base.html:136
+#: shared/templates/base.html:143
#, python-format
msgid "Connecté·e en tant que %(name)s par %(connection)s"
msgstr "Logged in as %(name)s via %(connection)s"
-#: shared/templates/base.html:152
+#: shared/templates/base.html:159
msgid "Se connecter"
msgstr "Log in"
-#: shared/templates/base.html:235
+#: shared/templates/base.html:242
msgid ""
"Développé par KDEns. En cas de pépin, contacter