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

result and attendance charts api for future pdf reports

parent 4b093fc0
Loading
Loading
Loading
Loading
+1 −0
Changes for .gitignore: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -4,3 +4,4 @@ db.sqlite3
.vscode
.env
deployment
.DS_Store
+4 −0
Changes for pyproject.toml: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ dependencies = [
    "django-rq~=4.0.1",
    "django-tasks-rq~=0.12.0",
    "gunicorn~=25.2.0",
    "matplotlib>=3.10.8",
    "psycopg[binary]~=3.3.3",
    "tzdata~=2025.3",
]
@@ -18,3 +19,6 @@ dependencies = [
dev = [
    "honcho~=2.0.0",
]

[tool.uv.sources]
django-admin-autocomplete = { git = "https://github.com/drorsoft/django_charfield_autocomplete" }
+68 −3
Changes for stats/admin.py: 68 added lines, 3 removed lines.
Original line number Diff line number Diff line
from django.contrib import admin, messages
from django.shortcuts import render, get_object_or_404
from django.db.models import Count, Q
from django.contrib.admin.views.decorators import staff_member_required
from django.urls import reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.urls import path
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, HttpResponse

import matplotlib
matplotlib.use('Agg')

import matplotlib.pyplot as plt
import io

from elections.models import Election
from vote.models import Participation
@@ -43,7 +48,9 @@ class ElectionStatsAdmin(admin.ModelAdmin):
        urls = super().get_urls()
        custom_urls = [
            path('live-results/<int:election_id>', self.admin_site.admin_view(self.live_results_view), name='live_results'),
            path('statistics/<int:election_id>', self.admin_site.admin_view(self.statistics_view), name='statistics')
            path('statistics/<int:election_id>', 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
@@ -124,6 +131,64 @@ 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.option_set.values_list('name', flat=True))
        values = list(election.option_set.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
    
+397 −0

File changed.

Preview size limit exceeded, changes collapsed.