Commit b3f7a239 authored by Staś Kycia's avatar Staś Kycia
Browse files

voters sorting on import

parent e67e4f15
Loading
Loading
Loading
Loading
+0 −1
Changes for pyproject.toml: 0 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -14,7 +14,6 @@ dependencies = [
    "djangorestframework~=3.17.1",
    "gunicorn~=25.2.0",
    "ldap3~=2.9.1",
    "matplotlib~=3.10.8",
    "psycopg[binary]~=3.3.3",
    "tzdata~=2025.3",
    "whitenoise~=6.12.0",
+8 −85
Changes for stats/admin.py: 8 added lines, 85 removed lines.
Original line number Diff line number Diff line
import matplotlib
import re

from django.contrib import admin, messages
from django.core.exceptions import PermissionDenied
from django.db.models import Count, Q
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import path, reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe

matplotlib.use("Agg")

import io
import re

import matplotlib.pyplot as plt

from elections.admin import OptionInline
from elections.models import Election, ElectionContributor
from vote.models import Participation
@@ -91,16 +85,6 @@ class ElectionStatsAdmin(admin.ModelAdmin):
                self.admin_site.admin_view(self.statistics_view),
                name="statistics",
            ),
            path(
                "chartapi/results/<int:election_id>",
                self.admin_site.admin_view(self.results_chart_view),
                name="results_chart",
            ),
            path(
                "chartapi/attendance/<int:election_id>",
                self.admin_site.admin_view(self.attendance_chart_view),
                name="attendance_chart",
            ),
        ]

        return custom_urls + urls
@@ -132,6 +116,11 @@ class ElectionStatsAdmin(admin.ModelAdmin):
            ).exists()
        ):
            raise PermissionDenied

        if Voter.objects.count() == 0:
            messages.warning(request, "Brak wyborców")
            return HttpResponseRedirect(reverse("admin:live_results"))

        context = admin.site.each_context(request)

        if election.is_active:
@@ -213,72 +202,6 @@ class ElectionStatsAdmin(admin.ModelAdmin):

        return render(request, "admin/statistics.html", context)

    def results_chart_view(self, request, election_id):
        election = get_object_or_404(Election, pk=election_id)

        labels = list(election.options.values_list("name", flat=True))
        values = list(election.options.values_list("votes", flat=True))

        if election.allow_invalid:
            labels.append("GŁOS NIEWAŻNY")
            values.append(election.invalid_votes)

        total = sum(values)
        value_iterator = iter(values)

        fig, ax = plt.subplots()
        ax.set_title("Wyniki głosowania")

        def display_value(p):
            value = next(value_iterator)
            return f"{round(100 * value / total, 1)}% ({value})"

        ax.pie(values, labels=labels, autopct=lambda p: display_value(p))

        buffer = io.BytesIO()
        plt.savefig(buffer, format="svg")
        plt.close(fig)
        buffer.seek(0)

        return HttpResponse(buffer.read(), "image/svg+xml")

    def attendance_chart_view(self, request, election_id):
        election = get_object_or_404(Election, pk=election_id)

        if election.is_active:
            return HttpResponse("Niedostępne dla trwających głosowań")

        groups = Group.objects.annotate(
            total_voters=Count("voter", distinct=True),
            voted_count=Count(
                "voter__participation",
                filter=Q(voter__participation__election=election),
                distinct=True,
            ),
        )

        labels = [group.display_name for group in groups]
        values = [
            (group.voted_count / group.total_voters if group.total_voters > 0 else 0)
            * 100
            for group in groups
        ]

        fig, ax = plt.subplots()

        bar = ax.bar(labels, values, label=labels)
        ax.bar_label(bar, label_type="center", fmt="%.1f%%")
        ax.set_xlabel("Klasa")
        ax.set_ylabel("Frekwencja [w %]")
        ax.set_title("Frekwencja według klasy")

        buffer = io.BytesIO()
        plt.savefig(buffer, format="svg")
        plt.close(fig)
        buffer.seek(0)

        return HttpResponse(buffer.read(), "image/svg+xml")

    def has_add_permission(self, request):
        return False

+0 −397

File changed.

Preview size limit exceeded, changes collapsed.

+9 −2
Changes for voters/forms.py: 9 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -39,6 +39,10 @@ def render_group_template(template: str, group: str) -> str:
    return template % {"group": ldap3.utils.conv.escape_filter_chars(group)}


def voter_sort_key(voter):
    return voter["name"]


def LDAPquery(groups: list[str]) -> dict[str, list[dict[str, str]] | None]:
    result = {}
    with ldap3.Connection(
@@ -76,13 +80,16 @@ def LDAPquery(groups: list[str]) -> dict[str, list[dict[str, str]] | None]:
            except ldap3.core.exceptions.LDAPNoSuchObjectResult:
                result[group] = None
            else:
                result[group] = [
                result[group] = sorted(
                    [
                        {
                            "name": get_ldap_attr(obj, settings.LDAP_NAME_FIELD),
                            "email": get_ldap_attr(obj, settings.LDAP_EMAIL_FIELD),
                        }
                        for obj in response
                ]
                    ],
                    key=voter_sort_key,
                )
    return result