Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proportional to results page #218

Merged
merged 16 commits into from
Jan 23, 2025
Merged
89 changes: 37 additions & 52 deletions approval_polls/templates/results.html
Original file line number Diff line number Diff line change
@@ -1,58 +1,43 @@
{% extends 'base.html' %}

Check failure on line 1 in approval_polls/templates/results.html

View check run for this annotation

Trunk.io / Trunk Check

djlint

Incorrect formatting, autoformat by running 'trunk fmt'
{% load filters %}
{% block content %}
<div class="container">
<h1 class="mb-4">{{ poll.question }}</h1>
<div class="mb-4">
<h2 class="h4 text-muted">
{{ poll.total_ballots }} ballot{{ poll.total_ballots|pluralize }}
{% if poll.is_closed and poll.total_votes == 0 %}<small class="d-block mt-2">No votes in this poll</small>{% endif %}
</h2>
</div>
<div class="mb-4">
{% for choice in choices %}
<div class="mb-4 {% if choice in leading_choices %}border border-success rounded p-3{% endif %}">
<h3 class="h5 {% if choice in leading_choices %}text-success font-weight-bold{% endif %}">
{{ choice.choice_text }}
{% if choice in leading_choices %}
<span class="badge bg-success ms-2">
{% if poll.is_closed %}
Winner
{% else %}
Leading
{% endif %}
<i class="bi bi-trophy-fill ms-1"></i>
</span>
{% endif %}
</h3>
<p class="text-muted">
{{ choice.vote_count }} vote{{ choice.vote_count|pluralize }}
({{ choice.percentage|to_percent_str }})
{% if choice in leading_choices %}
<span class="text-success font-weight-bold">
<i class="bi bi-arrow-up-circle-fill"></i>
</span>
{% endif %}
</p>
<div class="progress" style="height: 25px;">
<div class="progress-bar {% if choice in leading_choices %}bg-success{% endif %}"
role="progressbar"
style="width: {% widthratio choice.vote_count poll.total_ballots 100 %}%"
aria-valuenow="{% widthratio choice.vote_count poll.total_ballots 100 %}"
aria-valuemin="0"
aria-valuemax="{{ poll.total_ballots }}">
<span class="font-weight-bold">{% widthratio choice.vote_count poll.total_ballots 100 %}%</span>
</div>
<div class="container">
<h1>{{ poll.question }}</h1>
<p>Total Ballots: {{ poll.total_ballots }}</p>

<div class="choices">
{% for choice in choices %}
<div class="choice {% if choice in leading_choices %}border-success{% endif %}">
<h3>
{{ choice.choice_text }}
{% if choice in leading_choices %}
<span class="badge bg-success">Leading</span>
{% endif %}
</h3>
<p>
Approval Votes: {{ choice.vote_count }}
({{ choice.vote_count|divisibleby:poll.total_ballots|floatformat:2 }}%)
fsargent marked this conversation as resolved.
Show resolved Hide resolved
<br>
fsargent marked this conversation as resolved.
Show resolved Hide resolved
Proportional Votes: {{ choice.proportional_votes|floatformat:2 }}
({{ choice.proportional_percentage|floatformat:2 }}%)
</p>
<div class="progress">
<!-- Approval Votes Progress Bar -->
<div class="progress-bar bg-primary"
role="progressbar"
style="width: {{ choice.vote_count|divisibleby:poll.total_ballots|floatformat:2 }}%;">
Approval: {{ choice.vote_count|divisibleby:poll.total_ballots|floatformat:2 }}%
</div>

<!-- Proportional Votes Progress Bar -->
<div class="progress-bar bg-secondary"
role="progressbar"
style="width: {{ choice.proportional_percentage|floatformat:2 }}%;">
Proportional: {{ choice.proportional_percentage|floatformat:2 }}%
fsargent marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
{% endfor %}
</div>
<div class="text-center mt-4">
{% if 'invitation' in request.META.HTTP_REFERER %}
<a href="{{ request.META.HTTP_REFERER }}" class="btn btn-primary">Back to poll</a>
{% else %}
<a href="{% url 'detail' poll.id %}" class="btn btn-primary">Back to poll</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endblock %}
</div>
{% endblock %}
44 changes: 34 additions & 10 deletions approval_polls/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import datetime

Check failure on line 1 in approval_polls/views.py

View check run for this annotation

Trunk.io / Trunk Check

black

Incorrect formatting, autoformat by running 'trunk fmt'
import json
import re

Expand All @@ -8,7 +8,7 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Count
from django.db.models import Count, Prefetch
fsargent marked this conversation as resolved.
Show resolved Hide resolved
from django.http import HttpResponseRedirect, HttpResponseServerError
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
Expand Down Expand Up @@ -277,31 +277,55 @@
context = super().get_context_data(**kwargs)
poll = self.object

# Annotate choices with vote count and order by votes
choices = poll.choice_set.annotate(vote_count=Count("vote")).order_by(
"-vote_count"
)
# Annotate choices with vote count (approval logic)
choices = poll.choice_set.annotate(vote_count=Count("vote")).order_by("-vote_count")

# Calculate max votes
# Calculate max votes for leading choices (approval logic)
max_votes = choices.first().vote_count if choices.exists() else 0

# Determine leading choices
leading_choices = [
choice for choice in choices if choice.vote_count == max_votes
]

# Prefetch ballots and votes for proportional voting calculations
ballots = poll.ballot_set.prefetch_related(
Prefetch("vote_set", queryset=Vote.objects.select_related("choice"))

Check failure on line 291 in approval_polls/views.py

View check run for this annotation

Trunk.io / Trunk Check

flake8(F821)

[new] undefined name 'Vote'

Check failure on line 291 in approval_polls/views.py

View check run for this annotation

Trunk.io / Trunk Check

ruff(F821)

[new] Undefined name `Vote`
)

# Initialize proportional vote counts
proportional_votes = {choice.id: 0 for choice in poll.choice_set.all()}
total_proportional_votes = 0

# Calculate proportional votes
for ballot in ballots:
approved_choices = ballot.vote_set.all().values_list("choice_id", flat=True)
num_approved = len(approved_choices)
if num_approved > 0:
weight = 1 / num_approved
for choice_id in approved_choices:
proportional_votes[choice_id] += weight
total_proportional_votes += weight

# Add proportional data to choices
for choice in choices:
choice.proportional_votes = proportional_votes[choice.id]
choice.proportional_percentage = (
proportional_votes[choice.id] / total_proportional_votes * 100
if total_proportional_votes > 0
else 0
)

# Add data to context
context.update(
{
"choices": choices,
"leading_choices": leading_choices,
"max_votes": max_votes,
"total_proportional_votes": total_proportional_votes,
}
)

return context


@require_http_methods(["POST"])

Check failure on line 328 in approval_polls/views.py

View check run for this annotation

Trunk.io / Trunk Check

flake8(E302)

[new] expected 2 blank lines, found 1
def vote(request, poll_id):
poll = get_object_or_404(Poll, pk=poll_id)

Expand Down
Loading