diff --git a/elections/forms.py b/elections/forms.py index 35ec9ba..9d3c56c 100644 --- a/elections/forms.py +++ b/elections/forms.py @@ -94,10 +94,10 @@ class RankVoteForm(forms.ModelForm): class BallotFormset: - select_formset = inlineformset_factory( + select = inlineformset_factory( Question, Option, extra=0, form=SelectVoteForm, can_delete=False ) - rank_formset = inlineformset_factory( + rank = inlineformset_factory( Question, Option, extra=0, form=RankVoteForm, can_delete=False ) diff --git a/elections/models.py b/elections/models.py index 4419eaa..fb05ee0 100644 --- a/elections/models.py +++ b/elections/models.py @@ -11,7 +11,13 @@ from .staticdefs import ( TALLY_FUNCTIONS, VALIDATE_FUNCTIONS, ) -from .utils import CastFunctions, TallyFunctions, ValidateFunctions, choices_length +from .utils import ( + CastFunctions, + ResultsData, + TallyFunctions, + ValidateFunctions, + choices_length, +) # ############################################################################# # Models regarding an election @@ -96,6 +102,17 @@ class Question(models.Model): return getattr(BallotFormset, BALLOT_TYPE[self.type]) + def get_results_data(self): + results_function = getattr(ResultsData, BALLOT_TYPE[self.type]) + return results_function(self) + + @property + def vote_type(self): + return BALLOT_TYPE[self.type] + + def __str__(self): + return self.text + class Meta: ordering = ["id"] @@ -115,6 +132,9 @@ class Option(models.Model): # For now, we store the amount of votes received after the election is tallied nb_votes = models.PositiveSmallIntegerField(_("nombre de votes reçus"), default=0) + def __str__(self): + return self.text + class Meta: ordering = ["id"] diff --git a/elections/staticdefs.py b/elections/staticdefs.py index 29fffd2..8f8a7c5 100644 --- a/elections/staticdefs.py +++ b/elections/staticdefs.py @@ -25,9 +25,9 @@ QUESTION_TYPES = [ ] BALLOT_TYPE = { - "assentiment": "select_formset", - "uninominal": "select_formset", - "condorcet": "rank_formset", + "assentiment": "select", + "uninominal": "select", + "condorcet": "rank", } VOTE_RULES = { diff --git a/elections/templates/elections/election.html b/elections/templates/elections/election.html index 2e85b33..59c3e26 100644 --- a/elections/templates/elections/election.html +++ b/elections/templates/elections/election.html @@ -124,16 +124,6 @@ {% for q in election.questions.all %}
- {% comment %} - {% if can_vote and election.start_date < current_time and election.end_date > current_time %} - - - - - {% trans "Voter" %} - - {% endif %} - {% endcomment %} {{ q.text }} {% if q in cast_questions %} @@ -145,16 +135,32 @@ {% for o in q.options.all %}
{% if election.tallied and election.results_public %} + {% if q.vote_type == "select" %} {{ o.nb_votes }} + + {% elif q.vote_type == "rank" %} + + + + + {{ forloop.counter }} + {% endif %} + {% endif %} + {{ o.text }}
{% endfor %} + + {# Affiche plus d'informations sur le résultat #} + {% if election.tallied and election.results_public %} + {{ q.get_results_data }} + {% endif %}
{% endfor %} diff --git a/elections/templates/elections/results/rank.html b/elections/templates/elections/results/rank.html new file mode 100644 index 0000000..c3a4de8 --- /dev/null +++ b/elections/templates/elections/results/rank.html @@ -0,0 +1,46 @@ +{% load i18n %} + +
+
+
+ + + + {% for i in range %} + + {% endfor %} + + + + {% for line in matrix %} + + + + {% for cell in line %} + + {% endfor %} + + {% endfor %} + +
+ + + + + + + + {{ i }} +
+ + + + {{ forloop.counter }} + {{ cell }}
+
+
+
+ +
+ {% trans "La matrice des résultats montre les points d'avance, l'option gagnante est affichée sur la ligne et la perdante sur la colonne. " %} +
diff --git a/elections/templates/elections/results/select.html b/elections/templates/elections/results/select.html new file mode 100644 index 0000000..0010328 --- /dev/null +++ b/elections/templates/elections/results/select.html @@ -0,0 +1,5 @@ +{% load i18n %} + +
+ {% trans "L'option majoritaire et gagnante est colorée en vert." %} +
diff --git a/elections/utils.py b/elections/utils.py index 1c8ad60..56b05c1 100644 --- a/elections/utils.py +++ b/elections/utils.py @@ -8,6 +8,7 @@ from django.contrib.auth.hashers import make_password from django.core.exceptions import ValidationError from django.core.mail import EmailMessage, get_connection from django.core.validators import validate_email +from django.template.loader import render_to_string from django.utils.translation import gettext_lazy as _ # ############################################################################# @@ -185,6 +186,31 @@ class ValidateFunctions: return valid +class ResultsData: + """Classe pour afficher des informations supplémentaires après la fin d'une élection""" + + def select(question): + """On renvoie l'explication des couleurs""" + return render_to_string("elections/results/select.html") + + def rank(question): + """On récupère la matrice des résultats et on l'affiche""" + duels = question.duels.all() + options = list(question.options.all()) + n = len(options) + + matrix = np.zeros((n, n), dtype=int) + + for d in duels: + i, j = options.index(d.winner), options.index(d.loser) + matrix[i, j] = d.amount + print(matrix) + return render_to_string( + "elections/results/rank.html", + {"q": question, "matrix": matrix, "range": range(1, n + 1)}, + ) + + # ############################################################################# # Fonctions pour importer une liste de votant·e·s # ############################################################################# diff --git a/elections/views.py b/elections/views.py index eeb3676..1179c49 100644 --- a/elections/views.py +++ b/elections/views.py @@ -355,7 +355,11 @@ class ElectionView(NotArchivedMixin, DetailView): return context def get_queryset(self): - return super().get_queryset().prefetch_related("questions__options") + return ( + super() + .get_queryset() + .prefetch_related("questions__options", "questions__duels") + ) class ElectionVotersView(NotArchivedMixin, DetailView):