On utilise django-translated-fields pour la traduction des champs

This commit is contained in:
Tom Hubrecht 2021-04-15 17:01:17 +02:00
parent ad78f30411
commit 7ebbb8d3e8
10 changed files with 232 additions and 132 deletions

View file

@ -1,13 +1,27 @@
from django import forms
from django.forms.models import inlineformset_factory
from django.utils import timezone
from django.utils.functional import keep_lazy_text
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy as _
from .models import Election, Option, Question
from .utils import check_csv
# En attendant que ce soit merge dans django-translated-fields
def language_code_formfield_callback(db_field, **kwargs):
language_code = getattr(db_field, "_translated_field_language_code", "")
if language_code:
kwargs["label"] = keep_lazy_text(lambda s: "%s [%s]" % (s, language_code))(
capfirst(db_field.verbose_name)
)
return db_field.formfield(**kwargs)
class ElectionForm(forms.ModelForm):
formfield_callback = language_code_formfield_callback
def clean(self):
cleaned_data = super().clean()
if cleaned_data["start_date"] < timezone.now():
@ -23,16 +37,18 @@ class ElectionForm(forms.ModelForm):
class Meta:
model = Election
fields = [
"name",
"description",
"vote_restrictions",
"restricted",
*Election.name.fields,
"start_date",
"end_date",
*Election.description.fields,
"restricted",
*Election.vote_restrictions.fields,
]
widgets = {
"description": forms.Textarea(attrs={"rows": 4}),
"vote_restrictions": forms.Textarea(attrs={"rows": 4}),
"description_en": forms.Textarea(attrs={"rows": 4}),
"description_fr": forms.Textarea(attrs={"rows": 4}),
"vote_restrictions_en": forms.Textarea(attrs={"rows": 4}),
"vote_restrictions_fr": forms.Textarea(attrs={"rows": 4}),
}
@ -59,17 +75,21 @@ class VoterMailForm(forms.Form):
class QuestionForm(forms.ModelForm):
formfield_callback = language_code_formfield_callback
class Meta:
model = Question
fields = ["text", "type"]
widgets = {"text": forms.TextInput}
fields = [*Question.text.fields, "type"]
widgets = {"text_fr": forms.TextInput, "text_en": forms.TextInput}
class OptionForm(forms.ModelForm):
formfield_callback = language_code_formfield_callback
class Meta:
model = Option
fields = ["text"]
widgets = {"text": forms.TextInput}
fields = [*Option.text.fields]
widgets = {"text_fr": forms.TextInput, "text_en": forms.TextInput}
class DeleteVoteForm(forms.Form):

View file

@ -0,0 +1,63 @@
# Generated by Django 3.2 on 2021-04-15 08:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("elections", "0021_election_vote_restrictions"),
]
operations = [
migrations.RenameField(
model_name="election",
old_name="description",
new_name="description_fr",
),
migrations.RenameField(
model_name="election",
old_name="name",
new_name="name_fr",
),
migrations.RenameField(
model_name="election",
old_name="vote_restrictions",
new_name="vote_restrictions_fr",
),
migrations.RenameField(
model_name="option",
old_name="text",
new_name="text_fr",
),
migrations.RenameField(
model_name="question",
old_name="text",
new_name="text_fr",
),
migrations.AddField(
model_name="election",
name="description_en",
field=models.TextField(blank=True, verbose_name="description"),
),
migrations.AddField(
model_name="election",
name="name_en",
field=models.CharField(blank=True, max_length=255, verbose_name="nom"),
),
migrations.AddField(
model_name="election",
name="vote_restrictions_en",
field=models.TextField(blank=True, verbose_name="conditions de vote"),
),
migrations.AddField(
model_name="option",
name="text_en",
field=models.TextField(blank=True, verbose_name="texte"),
),
migrations.AddField(
model_name="question",
name="text_en",
field=models.TextField(blank=True, verbose_name="question"),
),
]

View file

@ -1,3 +1,5 @@
from translated_fields import TranslatedFieldWithFallback
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models, transaction
@ -25,14 +27,18 @@ from .utils import (
class Election(models.Model):
name = models.CharField(_("nom"), max_length=255)
name = TranslatedFieldWithFallback(models.CharField(_("nom"), max_length=255))
short_name = models.SlugField(_("nom bref"), unique=True)
description = models.TextField(_("description"), blank=True)
description = TranslatedFieldWithFallback(
models.TextField(_("description"), blank=True)
)
start_date = models.DateTimeField(_("date et heure de début"))
end_date = models.DateTimeField(_("date et heure de fin"))
vote_restrictions = models.TextField(_("conditions de vote"), blank=True)
vote_restrictions = TranslatedFieldWithFallback(
models.TextField(_("conditions de vote"), blank=True)
)
restricted = models.BooleanField(
_("restreint le vote à une liste de personnes"), default=True
@ -69,7 +75,7 @@ class Question(models.Model):
election = models.ForeignKey(
Election, related_name="questions", on_delete=models.CASCADE
)
text = models.TextField(_("question"), blank=False)
text = TranslatedFieldWithFallback(models.TextField(_("question"), blank=False))
type = models.CharField(
_("type de question"),
choices=QUESTION_TYPES,
@ -125,7 +131,7 @@ class Option(models.Model):
question = models.ForeignKey(
Question, related_name="options", on_delete=models.CASCADE
)
text = models.TextField(_("texte"), blank=False)
text = TranslatedFieldWithFallback(models.TextField(_("texte"), blank=False))
winner = models.BooleanField(_("option gagnante"), default=False)
voters = models.ManyToManyField(

View file

@ -2,6 +2,24 @@
{% load i18n %}
{% block extra_head %}
<script>
document.addEventListener('DOMContentLoaded', () => {
var $modalButtons = document.querySelectorAll('.modal-button') || [];
$modalButtons.forEach(function($el) {
$el.addEventListener('click', function() {
var $target = document.getElementById($el.dataset.target);
$target_form = $target.querySelector("form");
$target_form.action = $el.dataset.post_url;
});
});
});
</script>
{% endblock %}
{% block content %}
<div class="level is-block-tablet is-block-desktop is-flex-fullhd">
@ -217,55 +235,38 @@
{# Rajout d'une option #}
{% if election.start_date > current_time %}
<form action="{% url 'election.add-option' q.pk %}" method="post">
<div class="panel-block field has-addons">
{% csrf_token %}
<div class="control has-icons-left is-expanded">
<input class="input is-success" type="text" name="text" id="id_text" placeholder="{% trans "Rajouter une option" %}">
<span class="icon is-left">
<i class="fas fa-plus"></i>
</span>
</div>
<div class="control">
<button class="button is-success">{% trans "Valider" %}</button>
</div>
</div>
</form>
<div class="panel-block">
<button class="button modal-button is-primary is-outlined is-fullwidth" data-post_url="{% url 'election.add-option' q.pk %}" data-target="modal-add_option">
<span class="icon">
<i class="fas fa-plus"></i>
</span>
<span>{% trans "Rajouter une option" %}</span>
</button>
</div>
{% endif %}
</div>
{% endfor %}
{# Rajout d'une question #}
{% if election.start_date > current_time %}
{# Rajout d'une option #}
{% trans "Rajouter une option" as modal_title %}
{% include "forms/modal-form.html" with modal_id="add_option" form=o_form %}
{# Rajout d'une question #}
{% trans "Rajouter une question" as modal_title %}
{% include "forms/modal-form.html" with modal_id="add_question" form=q_form %}
<div class="columns is-centered" id="q_add">
<div class="column is-two-thirds">
<form action="{% url 'election.add-question' election.pk %}" method="post">
{% csrf_token %}
<div class="field has-addons">
<div class="control has-icons-left is-expanded">
<input class="input is-primary" type="text" name="text" id="id_text" placeholder="{% trans "Rajouter une question" %}">
<span class="icon is-left">
<i class="fas fa-question"></i>
</span>
</div>
<div class="control">
<span class="select is-primary">
<select name="type" id="id_type">
{% for val, choice in question_types %}
<option value="{{ val }}">{{ choice }}</option>
{% endfor %}
</select>
</span>
</div>
<div class="control">
<button class="button is-primary is-outlined">{% trans "Valider" %}</button>
</div>
</div>
</form>
<button class="button modal-button is-primary is-outlined is-fullwidth" data-post_url="{% url 'election.add-question' election.pk %}" data-target="modal-add_question">
<span class="icon">
<i class="fas fa-question"></i>
</span>
<span>{% trans "Rajouter une question" %}</span>
</button>
</div>
</div>
{% endif %}

View file

@ -90,8 +90,16 @@ class ElectionAdminView(CreatorOnlyMixin, DetailView):
return reverse("election.view", args=[self.object.pk])
def get_context_data(self, **kwargs):
from django.utils.translation import get_language
print(get_language())
kwargs.update(
{"current_time": timezone.now(), "question_types": QUESTION_TYPES}
{
"current_time": timezone.now(),
"question_types": QUESTION_TYPES,
"o_form": OptionForm,
"q_form": QuestionForm,
}
)
return super().get_context_data(**kwargs)

View file

@ -1,4 +1,5 @@
django==3.2.*
django-translated-fields==0.11
authens>=0.1b2
numpy
networkx

View file

@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-04-14 03:31+0200\n"
"PO-Revision-Date: 2021-04-14 20:17+0200\n"
"Last-Translator: Tom Hubrecht <tom.hubrecht@ens.fr>\n"
"POT-Creation-Date: 2021-04-15 16:56+0200\n"
"PO-Revision-Date: 2021-04-15 16:57+0200\n"
"Last-Translator: Test Translator <test@translator>\n"
"Language-Team: \n"
"Language: en\n"
"MIME-Version: 1.0\n"
@ -18,125 +18,125 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.4.1\n"
#: elections/forms.py:15
#: elections/forms.py:29
msgid "Impossible de faire débuter l'élection dans le passé"
msgstr "Impossible to start the election in the past"
#: elections/forms.py:19
#: elections/forms.py:33
msgid "Impossible de terminer l'élection avant de la commencer"
msgstr "Impossible to end the election before it starts"
#: elections/forms.py:40
#: elections/forms.py:56
msgid "Sélectionnez un fichier .csv"
msgstr "Select a .csv file"
#: elections/forms.py:50
#: elections/forms.py:66
msgid "Extension de fichier invalide, il faut un fichier au format CSV."
msgstr "Invalid file extension, a CSV file is required."
#: elections/forms.py:80
#: elections/forms.py:100
msgid "Supprimer le vote de {} ({}) ?"
msgstr "Delete the vote of {} ({}) ?"
#: elections/forms.py:85 elections/templates/elections/election_admin.html:150
#: elections/templates/elections/election_admin.html:177
#: elections/forms.py:105 elections/templates/elections/election_admin.html:168
#: elections/templates/elections/election_admin.html:195
#: elections/templates/elections/election_voters.html:57
msgid "Supprimer"
msgstr "Delete"
#: elections/forms.py:85
#: elections/forms.py:105
msgid "Non"
msgstr "No"
#: elections/forms.py:85
#: elections/forms.py:105
msgid "Oui"
msgstr "Yes"
#: elections/models.py:28
#: elections/models.py:31
msgid "nom"
msgstr "name"
#: elections/models.py:29
#: elections/models.py:32
msgid "nom bref"
msgstr "short name"
#: elections/models.py:30
#: elections/models.py:34
msgid "description"
msgstr "description"
#: elections/models.py:32
#: elections/models.py:37
msgid "date et heure de début"
msgstr "start date and time"
#: elections/models.py:33
#: elections/models.py:38
msgid "date et heure de fin"
msgstr "end date and time"
#: elections/models.py:35
#: elections/models.py:41
msgid "conditions de vote"
msgstr "voting requirements"
#: elections/models.py:38
#: elections/models.py:45
msgid "restreint le vote à une liste de personnes"
msgstr "restricts the vote to a list of people"
#: elections/models.py:42
#: elections/models.py:49
msgid "mail avec les identifiants envoyé"
msgstr "mail with credentials sent"
#: elections/models.py:59
#: elections/models.py:66
msgid "résultats publics"
msgstr "results published"
#: elections/models.py:60
#: elections/models.py:67
msgid "dépouillée"
msgstr "counted"
#: elections/models.py:62
#: elections/models.py:69
msgid "archivée"
msgstr "archived"
#: elections/models.py:72
#: elections/models.py:79
msgid "question"
msgstr "question"
#: elections/models.py:74
#: elections/models.py:81
msgid "type de question"
msgstr "type of question"
#: elections/models.py:81
#: elections/models.py:88
msgid "nombre maximal de votes reçus"
msgstr "maximal number of votes received"
#: elections/models.py:128
#: elections/models.py:135
msgid "texte"
msgstr "text"
#: elections/models.py:130
#: elections/models.py:137
msgid "option gagnante"
msgstr "winning option"
#: elections/models.py:138
#: elections/models.py:145
msgid "nombre de votes reçus"
msgstr "number of votes received"
#: elections/models.py:157
#: elections/models.py:164
msgid "rang de l'option"
msgstr "option's ranking"
#: elections/models.py:173
#: elections/models.py:180
msgid "votes supplémentaires"
msgstr "extra votes"
#: elections/models.py:189
#: elections/models.py:196
msgid "Nom et Prénom"
msgstr "Name and surname"
#: elections/models.py:211 elections/tests/test_models.py:57
#: elections/models.py:218 elections/tests/test_models.py:57
msgid "identifiants spécifiques"
msgstr "dedicated credentials"
#: elections/models.py:215
#: elections/models.py:222
msgid "Peut administrer des élections"
msgstr "Can manage elections"
@ -285,52 +285,49 @@ msgstr "Login via CAS"
msgid "A voté"
msgstr "Voted"
#: elections/templates/elections/election_admin.html:23
#: elections/templates/elections/election_admin.html:41
msgid "Actions"
msgstr "Actions"
#: elections/templates/elections/election_admin.html:36
#: elections/templates/elections/election_admin.html:159
#: elections/templates/elections/election_admin.html:182
#: elections/templates/elections/election_admin.html:54
#: elections/templates/elections/election_admin.html:177
#: elections/templates/elections/election_admin.html:200
msgid "Modifier"
msgstr "Edit"
#: elections/templates/elections/election_admin.html:45
#: elections/templates/elections/election_admin.html:63
#: elections/templates/elections/upload_voters.html:25
msgid "Gestion de la liste de votant·e·s"
msgstr "Management of the voters' list"
#: elections/templates/elections/election_admin.html:57
#: elections/templates/elections/election_admin.html:75
#: elections/templates/elections/election_voters.html:27
msgid "Liste des votant·e·s"
msgstr "Voters' list"
#: elections/templates/elections/election_admin.html:65
#: elections/templates/elections/election_admin.html:83
msgid "Dépouiller"
msgstr "Count"
#: elections/templates/elections/election_admin.html:76
#: elections/templates/elections/election_admin.html:94
msgid "Publier"
msgstr "Publish"
#: elections/templates/elections/election_admin.html:78
#: elections/templates/elections/election_admin.html:96
msgid "Dépublier"
msgstr "De-publish"
#: elections/templates/elections/election_admin.html:88
#: elections/templates/elections/election_admin.html:106
msgid "Archiver"
msgstr "Archive"
#: elections/templates/elections/election_admin.html:225
#: elections/templates/elections/election_admin.html:243
#: elections/templates/elections/election_admin.html:254
msgid "Rajouter une option"
msgstr "Add an option"
#: elections/templates/elections/election_admin.html:231
#: elections/templates/elections/election_admin.html:265
msgid "Valider"
msgstr "Confirm"
#: elections/templates/elections/election_admin.html:248
#: elections/templates/elections/election_admin.html:258
#: elections/templates/elections/election_admin.html:268
msgid "Rajouter une question"
msgstr "Add a question"
@ -542,51 +539,51 @@ msgstr "Invalid e-mail address in line {}: '{}'."
msgid "Élection créée avec succès !"
msgstr "Election successfully created!"
#: elections/views.py:105
#: elections/views.py:113
msgid "Liste de votant·e·s importée avec succès !"
msgstr "Voters list successfully imported!"
#: elections/views.py:139
#: elections/views.py:147
msgid "Mail d'annonce envoyé avec succès !"
msgstr "Announcement e-mail sent successfully!"
#: elections/views.py:171
#: elections/views.py:179
msgid "Élection modifiée avec succès !"
msgstr "Election successfully modified!"
#: elections/views.py:241
#: elections/views.py:249
msgid "Élection dépouillée avec succès !"
msgstr "Election successfully counted!"
#: elections/views.py:267
#: elections/views.py:275
msgid "Élection publiée avec succès !"
msgstr "Election successfully published!"
#: elections/views.py:268
#: elections/views.py:276
msgid "Élection dépubliée avec succès !"
msgstr "Election successfully de-published!"
#: elections/views.py:280
#: elections/views.py:288
msgid "Élection archivée avec succès !"
msgstr "Election successfully archived!"
#: elections/views.py:312
#: elections/views.py:320
msgid "Question modifiée avec succès !"
msgstr "Question successfully modified!"
#: elections/views.py:324
#: elections/views.py:332
msgid "Question supprimée !"
msgstr "Question deleted!"
#: elections/views.py:362
#: elections/views.py:370
msgid "Option modifiée avec succès !"
msgstr "Option successfully modified!"
#: elections/views.py:374
#: elections/views.py:382
msgid "Option supprimée !"
msgstr "Option deleted!"
#: elections/views.py:514
#: elections/views.py:522
msgid "Votre choix a bien été enregistré !"
msgstr "Your choice has been recorded!"
@ -646,16 +643,16 @@ msgstr "Send an e-mail"
msgid "Élections"
msgstr "Elections"
#: shared/templates/base.html:163
#: shared/templates/base.html:125
#, 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:178
#: shared/templates/base.html:141
msgid "Se connecter"
msgstr "Log in"
#: shared/templates/base.html:219
#: shared/templates/base.html:224
msgid ""
"Développé par <a class=\"tag is-light is-danger\" href=\"https://www.eleves."
"ens.fr/kde\">KDEns</a>. En cas de pépin, contacter <span class=\"tag is-info "
@ -692,3 +689,9 @@ msgstr "Log back in"
#: shared/templates/registration/logged_out.html:31
msgid "Accueil"
msgstr "Home"
#~ msgid " [{}] :"
#~ msgstr " [{}]:"
#~ msgid "Valider"
#~ msgstr "Confirm"

View file

@ -137,12 +137,10 @@
{% else %}
<div class="level-item py-2">
<a class="tag has-text-primary is-size-5" href="{% url 'authens:login' %}?next={{ request.path }}">
<span class="icon-text">
<span>{% trans "Se connecter" %}</span>
<span class="icon">
<i class="fas fa-sign-in-alt"></i>
</span>
<a class="button" href="{% url 'authens:login' %}?next={{ request.path }}">
<span class="is-size-5">{% trans "Se connecter" %}</span>
<span class="icon is-size-4">
<i class="fas fa-sign-in-alt"></i>
</span>
</a>
</div>
@ -163,8 +161,8 @@
<div class="dropdown is-hoverable is-right">
<div class="dropdown-trigger">
<a class="button is-primary is-light is-large" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon has-text-primary is-size-2">
<a class="button is-primary is-large" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon is-size-2">
<i class="fas fa-language"></i>
</span>
</a>

View file

@ -25,7 +25,7 @@
<a class="button is-primary button-close">
<span class="icon">
<i class="fas fa-undo-alt"></i>
<i class="fas fa-times"></i>
</span>
<span>{% trans "Annuler" %}</span>
</a>