Commit bec16cae authored by krasnal's avatar krasnal
Browse files

Added table to live results. Added statistics

parent 24c58c6f
Loading
Loading
Loading
Loading
+7 −1
Changes for elections/admin.py: 7 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@ class OptionInline(admin.TabularInline):
    extra = 1
    
class ElectionAdmin(admin.ModelAdmin):
    list_display = ['name', 'start_date', 'end_date', 'is_active_status', 'view_live_results']
    list_display = ['name', 'start_date', 'end_date', 'is_active_status', 'view_live_results', 'view_statistics']
    ordering = ['-created_at', '-updated_at']

    # exclude = ['invalid_votes']
@@ -26,6 +26,12 @@ class ElectionAdmin(admin.ModelAdmin):
        url = reverse('stats:live_results', args=[obj.id])
        return format_html('<a href={}>WYŚWIETL</a>', url)
    
    @admin.display(description='Statystyki')
    def view_statistics(self, obj):
        if obj.is_active:
            return format_html('<span style="color: #999;">{}Dostępne po zakończeniu głosowania</span>', '')
        url = reverse('stats:statistics', args=[obj.id])
        return format_html('<a href={}>WYŚWIETL</a>', url)
    
# Register your models here.
admin.site.register(Election, ElectionAdmin)
 No newline at end of file
+46 −1
Changes for stats/admin.py: 46 added lines, 1 removed line.
Original line number Diff line number Diff line
from django.contrib import admin
from django.shortcuts import render
from django.shortcuts import render, get_object_or_404
from django.db.models import Count, Q

from elections.models import Election
from vote.models import Participation
from voters.models import Voter, Group

def live_results(request, election_id):
    context = admin.site.each_context(request)
@@ -11,3 +14,45 @@ def live_results(request, election_id):
    })
    
    return render(request, 'stats/live_results.html', context)

def statistics(request, election_id):
    election = get_object_or_404(Election, id=election_id)
    context = admin.site.each_context(request)

    total_eligible = Voter.objects.count()
    total_participated = Participation.objects.filter(election=election).count()

    overall_attendance = (total_participated / total_eligible * 100) if total_eligible > 0 else 0

    stats_raw = Voter.objects.values('group').annotate(
        total_voters=Count('id'),
        voted_count=Count(
            'participation',
            filter=Q(participation__election=election)
        )
    ).order_by('group')

    groups_map = {g.id: g.display_name for g in Group.objects.all()}

    stats_by_class = []
    for item in stats_raw:
        total = item['total_voters']
        voted = item['voted_count']
        stats_by_class.append({
            'class_name': groups_map.get(item['group']),
            'voted_count': voted,
            'total_voters': total,
            'percentage': round((voted / total * 100), 2) if total > 0 else 0
        })
    
    context.update({
        'request': request,
        'election_id': election_id,
        'election': election,
        'overall_attendance': round(overall_attendance, 2),
        'total_participated': total_participated,
        'total_eligible': total_eligible,
        'stats_by_class': stats_by_class,
    })

    return render(request, 'stats/statistics.html', context)
 No newline at end of file
+47 −4
Changes for stats/templates/stats/live_results.html: 47 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -3,7 +3,27 @@
{% block content %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>

<div style="height:40vh; width:80vw"><canvas id="chart"></canvas></div>
<div style="height:40vh; width:80vw">
    <canvas id="chart"></canvas>

    <div class="table-wrapper">
        <table class="results-table" id="data-table">
            <thead>
                <tr>
                    <th>Opcja</th>
                    <th style="text-align: right;">Liczba głosów</th>
                </tr>
            </thead>
            <tbody id="table-body"></tbody>
            <tfoot>
                <tr class="total-row">
                    <td>Suma głosów</td>
                    <td id="total-votes" style="text-align: right;">0</td>
                </tr>
            </tfoot>
        </table>
    </div>
</div>

<script>
    async function getElectionData() {
@@ -69,7 +89,29 @@
        );
    }

    async function updateChart() {
    function updateTable(data) {
        const tbody = document.getElementById('table-body');
        const totalCell = document.getElementById('total-votes');

        let html = '';
        let total = 0;

        data.labels.forEach((label, index) => {
            const voteCount = data.votes[index];
            total += voteCount;
            html += `
                <tr>
                    <td>${label}</td>
                    <td style="text-align: right;">${voteCount}</td>
                </tr>
            `;
        });

        tbody.innerHTML = html;
        totalCell.innerText = total;
    }

    async function updateUI() {
        const freshData = await getElectionData();
        
        if(freshData){
@@ -80,10 +122,11 @@
                resultsChart.data.datasets[0].data = freshData.votes;
                resultsChart.update(); 
            }
            updateTable(freshData);
        }
    }

    updateChart();
    setInterval(updateChart, 5000);
    updateUI();
    setInterval(updateUI, 5000);
</script>
{% endblock %}
 No newline at end of file
+151 −0
Changes for stats/templates/stats/statistics.html: 151 added lines, 0 removed lines.
Original line number Diff line number Diff line
{% extends 'admin/base_site.html' %}

{% block content %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>

<div style="padding: 20px;">
    <h2>Statystyki: {{ election.name }}</h2>

    <div style="display: flex; gap: 20px; margin-bottom: 30px;">
        <div style="padding: 20px; border-radius: 8px; flex: 1; text-align: center; border: 1px solid #ddd;">
            <h3>Wykres</h3>

            <div style="height: 40vh; width: 100%;">
                <canvas id="chart"></canvas>
            </div>
        </div>

        <div style="padding: 20px; border-radius: 8px; flex: 1; text-align: center; border: 1px solid #ddd;">
            <h3>Tabela</h3>
            
            <div class="table-wrapper">
                <table class="results-table" id="data-table">
                    <thead>
                        <tr>
                            <th>Opcja</th>
                            <th style="text-align: right;">Liczba głosów</th>
                        </tr>
                    </thead>
                    <tbody id="table-body"></tbody>
                    <tfoot>
                        <tr class="total-row">
                            <td>Suma głosów</td>
                            <td id="total-votes" style="text-align: right;">0</td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        </div>
    </div>

    <h3>Frekwencja w klasach</h3>
    <table style="width: 100%; border-collapse: collapse; margin-bottom: 30px;">
        <thead>
            <tr>
                <th>Klasa</th>
                <th>Głosy</th>
                <th>%</th>
            </tr>
        </thead>
        <tbody>
            {% for item in stats_by_class %}
            <tr style="border-bottom: 1px solid #eee;">
                <td>{{ item.class_name }}</td>
                <td>{{ item.voted_count }} / {{ item.total_voters }}</td>
                <td style="font-weight: bold;">{{ item.percentage }}%</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    <div style="border-radius: 8px; text-align: center; border: 1px solid #ddd;">
        <h3>Ogólna frekwencja</h3>
        <p style="font-size: 2.5em; font-weight: bold; color: #417690;">{{ overall_attendance }}%</p>
        <p>{{ total_participated }} / {{ total_eligible }} oddanych głosów</p>
    </div>
</div>

<script>
    async function getElectionData() {
        const apiUrl = "{{ request.scheme }}://{{ request.get_host }}{% url 'vote:api_internal_results' %}?election_id={{ election_id }}";

        try {
            const response = await fetch(apiUrl);

            const data = await response.json();

            const labels = data.options.map(option => option.name);
            const votes = data.options.map(option => option.votes);

            return {
                name: data.name,
                labels: labels,
                votes: votes
            };
        } catch (error) {
            console.error('Failed to fetch results', error);
        }
    }

    let resultsChart;

    function initializeChart(data) {
        const ctx = document.getElementById('chart').getContext('2d');
        resultsChart = new Chart(
            ctx,
            {
                type: 'pie',
                data:
                {
                    labels: data.labels,
                    datasets: [{
                        label: 'Liczba głosów',
                        data: data.votes,
                    }]
                },
                options:
                {
                    responsive: true,
                    plugins: {
                        legend: {
                            position: 'bottom'
                        }
                    }
                }
            }
        );
    }

    function initializeTable(data) {
        const tbody = document.getElementById('table-body');
        const totalCell = document.getElementById('total-votes');

        let html = '';
        let total = 0;

        data.labels.forEach((label, index) => {
            const voteCount = data.votes[index];
            total += voteCount;
            html += `
                <tr>
                    <td>${label}</td>
                    <td style="text-align: right;">${voteCount}</td>
                </tr>
            `;
        });

        tbody.innerHTML = html;
        totalCell.innerText = total;
    }

    async function displayResults() {
        const data = await getElectionData();

        initializeChart(data);
        initializeTable(data);
    }

    displayResults();
</script>

{% endblock %}
 No newline at end of file
+1 −0
Changes for stats/urls.py: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -6,4 +6,5 @@ app_name = 'stats'

urlpatterns = [
    path('admin/stats/live_results/<int:election_id>', admin.live_results, name='live_results'),
    path('admin/stats/statistics/<int:election_id>', admin.statistics, name='statistics'),
]
 No newline at end of file
Loading