Merge pull request 'ruzne exporty resitelu - zatím určitě ne merge xd spíš potřebuji zpětnou vazbu...' (!89) from export_resitelskych_dat into master

Reviewed-on: #89
This commit is contained in:
Jonas Havelka 2025-02-26 21:01:24 +01:00
commit ff7d36a965
5 changed files with 236 additions and 1 deletions

View file

@ -0,0 +1,88 @@
{% extends "base.html" %}
{% block content %}
<h2><strong>Export lidí</strong></h2>
<select name="select-one" id="select-one">
<option value="0">---</option>
<option value="1">Řešitelé čísla</option>
<option value="2">Řešitelé ročníku</option>
<option value="3">Všichni řešitelé, kteří ještě neodmaturovali</option>
<option value="4">Organizátoři soustředění</option>
</select>
<select name="select-two" id="select-two">
<!-- will be filled with ajax -->
</select>
<button id="download-button">Stáhnout</button>
<script defer>
const select_one = document.getElementById("select-one")
const select_two = document.getElementById("select-two")
const download_button = document.getElementById("download-button")
download_button.style.display = 'none'
select_two.style.display = 'none'
const fetch_dict_string = '{{ typy_exportu|safe }}'
const fetch_dict = JSON.parse(fetch_dict_string)
select_one.addEventListener('change', (e) => {
value = e.target.value
select_two.style.display = 'none'
select_two.innerHTML = ''
// puvodni stav
if (value == 0) {
download_button.style.display = 'none'
select_two.style.display = 'none'
return
}
// v tomto pripade muzeme rovnou stahnout
if (!(value in fetch_dict)) {
download_button.style.display = 'block'
select_two.style.display = 'none'
return
}
download_button.style.display = 'none'
fetch("/profil/exporty_lidi/get/" + value)
.then(response => response.json())
.then(data => {
const option = document.createElement('option')
option.value = 0
option.text = '---'
select_two.appendChild(option)
for (const [key, value] of Object.entries(data)) {
const option = document.createElement('option')
option.value = value["id"]
option.text = value["display"]
select_two.appendChild(option)
}
select_two.style.display = 'block'
})
})
select_two.addEventListener('change', (e) => {
value = e.target.value
if (value == 0) {
download_button.style.display = 'none'
return
}
download_button.style.display = 'block'
})
download_button.addEventListener('click', (e) => {
if (select_two.innerHTML == '') {
window.location.href = "/profil/exporty_lidi/get_csv_only_one_step/" + select_one.value
} else {
window.location.href = "/profil/exporty_lidi/get_csv/" + select_one.value + "/" + select_two.value
}
})
</script>
{% endblock %}

View file

@ -107,6 +107,13 @@
</li>
</ul>
<hr />
<h2><strong>Exporty dat lidí v semináří</strong></h2>
<ul>
<li><a href="{% url 'exporty_lidi' %}">dostupné exporty</a></li>
</ul>
<hr />
<p>Nemůžeš najít, co hledáš? Může to být v <a href="{% url 'admin:index' %}">administračním rozhraní webu</a>.</p>
{% endblock content %}

View file

@ -38,6 +38,28 @@ urlpatterns = [
'org/propagace/jak-se-dozvedeli/',
org_required(views.JakSeDozvedeliView.as_view()),
name='jak_se_dozvedeli'
),
# export dat o řešitelích
path(
'profil/exporty_lidi',
org_required(views.ExportLidiView.as_view()),
name='exporty_lidi',
),
path(
'profil/exporty_lidi/get/<int:type>',
org_required(views.get_export_options),
name='exporty_lidi_options',
),
path(
'profil/exporty_lidi/get_csv_only_one_step/<int:type>',
org_required(views.download_export_csv_only_first_step),
name='exporty_lidi_data',
),
path(
'profil/exporty_lidi/get_csv/<int:type>/<int:id>',
org_required(views.download_export_csv),
name='exporty_lidi_download',
)
]

View file

@ -20,13 +20,16 @@ from django.utils import timezone
import personalni.models as m
from soustredeni.models import Soustredeni
from odevzdavatko.models import Hodnoceni
from tvorba.models import Clanek, Uloha, Tema
from tvorba.models import Clanek, Uloha, Tema, Cislo, Rocnik
import tvorba.utils as tvorba_utils
from various.models import Nastaveni
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
from datetime import date
import logging
import csv
from enum import Enum
import json
from various.views.pomocne import formularOKView
from various.autentizace.views import LoginView
@ -141,6 +144,54 @@ class OrgoRozcestnikView(TemplateView):
#content_type = 'text/plain; charset=UTF8'
#XXX
class PrvniTypExportu(Enum):
CISLA = 1
ROCNIKU = 2
SOUSTREDENI = 4
class ExportLidiView(TemplateView):
template_name = 'personalni/profil/export_lidi.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['typy_exportu'] = json.dumps({member.value: member.name.lower().capitalize() for member in PrvniTypExportu})
return context
def get_export_options(request, type):
if type == PrvniTypExportu.CISLA.value:
data = [{"id": c.id, "display": str(c)} for c in Cislo.objects.all()]
if type == PrvniTypExportu.ROCNIKU.value:
data = [{"id": r.id, "display": str(r)} for r in Rocnik.objects.all()]
if type == PrvniTypExportu.SOUSTREDENI.value:
data = [{"id": s.id, "display": str(s)} for s in Soustredeni.objects.all()]
return HttpResponse(json.dumps(data), content_type='application/json')
def download_export_csv_only_first_step(request, type):
if type == 3:
response = dataResiteluCsvResponse(tvorba_utils.resitele_co_neodmaturovali())
response['Content-Disposition'] = 'attachment; filename="resitele_co_neodmaturovali.csv"'
return response
def download_export_csv(request, type, id):
if type == PrvniTypExportu.CISLA.value:
response = dataResiteluCsvResponse(tvorba_utils.resi_cislo(Cislo.objects.get(id=id)))
name = str(Cislo.objects.get(id=id)).replace(" ", "_") + "_resitele_cisla.csv"
response['Content-Disposition'] = 'attachment; filename="' + name + '"'
return response
if type == PrvniTypExportu.ROCNIKU.value:
response = dataResiteluCsvResponse(tvorba_utils.resi_v_rocniku(Rocnik.objects.get(id=id)))
name = str(Rocnik.objects.get(id=id)).replace(" ", "_") + "_resitele_rocniku.csv"
response['Content-Disposition'] = 'attachment; filename="' + name + '"'
return response
if type == PrvniTypExportu.SOUSTREDENI.value:
soustredeni = Soustredeni.objects.get(id=id)
organizatori = soustredeni.organizatori.all()
organizatoriOsoby = Osoba.objects.filter(org__in=organizatori)
response = dataOsobCsvResponse(organizatoriOsoby, columns=("jmeno", "prijmeni", "email", "telefon",))
name = str(soustredeni).replace(" ", "_") + "_organizatori_soustredeni.csv"
response['Content-Disposition'] = 'attachment; filename="' + name + '"'
return response
class ResitelView(LoginRequiredMixin,generic.DetailView):
model = m.Resitel
@ -470,3 +521,46 @@ def dataResiteluCsvResponse(queryset, columns=None, with_header=True):
writer.writerows(queryset_list)
return response
def dataOsobCsvResponse(queryset, columns=None, with_header=True):
"""Pomocná funkce pro vracení dat osob jako CSV. Musí dostat správný QuerySet, který dává Ososby"""
default_columns = (
'id',
'jmeno',
'prijmeni',
'prezdivka',
'email',
'telefon',
'datum_narozeni',
'osloveni',
'ulice',
'mesto',
'psc',
'stat',
'jak_se_dozvedeli',
'poznamka',
'datum_registrace',
'datum_souhlasu_udaje',
'datum_souhlasu_zasilani',
)
if columns is None: columns = default_columns
def get_field_name(column_name):
return column_name
response = HttpResponse(content_type='text/csv')
writer = csv.writer(response)
# První řádek je záhlaví
if with_header:
writer.writerow(map(get_field_name, columns))
# Data:
queryset_list = queryset.values_list(*columns)
writer.writerows(queryset_list)
return response

View file

@ -28,6 +28,30 @@ def resi_v_rocniku(rocnik, cislo=None):
reseni__hodnoceni__deadline_body__cislo__poradi__lte=cislo.poradi
).distinct()
def resi_cislo(cislo):
""" Vrátí seznam řešitelů, co vyřešili nějaký problém v daném čísle.
Parametry:
cislo (typu Cislo) číslo, ve kterém chci řešitele, co něco odevzdali
Výstup:
QuerySet objektů typu Resitel
"""
return personalni.models.Resitel.objects.filter(
reseni__hodnoceni__deadline_body__cislo=cislo
).distinct()
def resitele_co_neodmaturovali():
""" Vrátí seznam řešitelů, co ještě neodmaturovali.
Pokud ještě není srpen, tak zahrnuje i ty, kteří odmaturovali letos.
Výstup:
QuerySet objektů typu Resitel """
from datetime import datetime
current_year = datetime.now().year
if datetime.now().month < 8:
current_year -= 1
return personalni.models.Resitel.objects.filter(rok_maturity__gte=current_year)
def aktivniResitele(cislo, pouze_letosni=False):
""" Vrací QuerySet aktivních řešitelů, což jsou ti, co ještě neodmaturovali