Fin de condorcet et affichage d'infos supplémentaires pour les résultats
This commit is contained in:
parent
fea1ab495d
commit
81e345527b
8 changed files with 124 additions and 17 deletions
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
46
elections/templates/elections/results/rank.html
Normal file
46
elections/templates/elections/results/rank.html
Normal 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>
|
5
elections/templates/elections/results/select.html
Normal file
5
elections/templates/elections/results/select.html
Normal 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>
|
|
@ -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
|
||||
# #############################################################################
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue