Loading pyproject.toml +0 −1 Changes for pyproject.toml: 0 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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", Loading stats/admin.py +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 Loading Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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 Loading uv.lock +0 −397 File changed.Preview size limit exceeded, changes collapsed. Show changes voters/forms.py +9 −2 Changes for voters/forms.py: 9 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -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( Loading Loading @@ -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 Loading Loading
pyproject.toml +0 −1 Changes for pyproject.toml: 0 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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", Loading
stats/admin.py +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 Loading Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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 Loading
voters/forms.py +9 −2 Changes for voters/forms.py: 9 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -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( Loading Loading @@ -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 Loading