Fin de condorcet et affichage d'infos supplémentaires pour les résultats

This commit is contained in:
Tom Hubrecht 2021-03-29 20:35:34 +02:00
parent fea1ab495d
commit 81e345527b
8 changed files with 124 additions and 17 deletions

View file

@ -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
)

View file

@ -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"]

View file

@ -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 = {

View file

@ -124,16 +124,6 @@
{% for q in election.questions.all %}
<div class="panel" id="q_{{ q.pk }}">
<div class="panel-heading is-size-6">
{% comment %}
{% if can_vote and election.start_date < current_time and election.end_date > current_time %}
<a class="tag is-outlined is-light is-danger" href="{% url 'election.vote' q.pk %}">
<span class="icon">
<i class="fas fa-vote-yea"></i>
</span>
<span>{% trans "Voter" %}</span>
</a>
{% endif %}
{% endcomment %}
<span class="ml-2">{{ q.text }}</span>
{% if q in cast_questions %}
@ -145,16 +135,32 @@
{% for o in q.options.all %}
<div class="panel-block">
{% if election.tallied and election.results_public %}
{% if q.vote_type == "select" %}
<span class="tag {% if o.nb_votes == q.max_votes %}is-success{% else %}is-primary{% endif %}">
<span class="icon">
<i class="fas fa-vote-yea"></i>
</span>
<span>{{ o.nb_votes }}</span>
</span>
{% elif q.vote_type == "rank" %}
<span class="tag is-primary">
<span class="icon">
<i class="fas fa-layer-group"></i>
</span>
<span>{{ forloop.counter }}</span>
</span>
{% endif %}
{% endif %}
<span class="ml-2">{{ o.text }}</span>
</div>
{% endfor %}
{# Affiche plus d'informations sur le résultat #}
{% if election.tallied and election.results_public %}
{{ q.get_results_data }}
{% endif %}
</div>
{% endfor %}

View file

@ -0,0 +1,46 @@
{% load i18n %}
<div class="panel-block">
<div class="columns is-centered is-fullwidth">
<div class="column">
<table class="table is-bordered is-striped">
<thead>
<th class="has-text-centered">
<span class="icon">
<i class="fas fa-undo-alt"></i>
</span>
</th>
{% for i in range %}
<th class="has-text-centered">
<span class="icon">
<i class="fas fa-layer-group"></i>
</span>
<span>{{ i }}</span>
</th>
{% endfor %}
</thead>
<tbody>
{% for line in matrix %}
<tr>
<th class="has-text-centered">
<span class="icon">
<i class="fas fa-layer-group"></i>
</span>
<span>{{ forloop.counter }}</span>
</th>
{% for cell in line %}
<td class="has-text-centered">{{ cell }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="panel-block">
<i>{% 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. " %}</i>
</div>

View file

@ -0,0 +1,5 @@
{% load i18n %}
<div class="panel-block">
<i>{% trans "L'option majoritaire et gagnante est colorée en vert." %}</i>
</div>

View file

@ -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
# #############################################################################

View file

@ -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):