Compare commits

...

39 commits

Author SHA1 Message Date
6dbfae34d0 Vyhození cachí prohlížečů 2025-03-19 18:59:09 +01:00
8ec06d0c90 Merge branch 'master' into korekturovatko 2025-03-19 18:54:26 +01:00
66ec9bb33f Fix bugu s location.hash (v chromu) #1557 2025-03-19 18:50:39 +01:00
4912349577 Merge branch 'korekturovatko' 2025-03-05 14:59:38 +01:00
2d6ad5f369 Merge remote-tracking branch 'origin/korekturovatko' 2025-03-05 14:55:23 +01:00
ad6bd0b519 Merge pull request 'odmeny jsou pekny' (!90) from odmeny into master
Reviewed-on: #90
2025-02-26 21:22:32 +01:00
777a36b1f7 posunuti dokumneasasasa 2025-02-26 21:19:39 +01:00
d34e551b71 – 2025-02-26 21:18:19 +01:00
c1ae550a78 dokumentace probody 2025-02-26 21:13:48 +01:00
1fdd3dcaaf Merge pull request 'grey na opacity - a zarovnani do stran' (!86) from zpristupneni_jak_jste_se_dozvedeli into master
Reviewed-on: #86
2025-02-26 21:03:57 +01:00
ff7d36a965 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
2025-02-26 21:01:24 +01:00
73dcb441ef Merge pull request 'Přednášky' (!87) from prednasky into master
Reviewed-on: #87
2025-02-26 20:58:49 +01:00
95e164650e cervena text 2025-02-26 20:58:06 +01:00
80aa01d76b random inline css 2025-02-26 20:51:52 +01:00
8e8b446e09 fixint tyyyypos 2025-02-26 20:45:57 +01:00
c853e06d71 random bulshit goes... 2025-02-26 20:43:47 +01:00
380a14299d dalsi random test 2025-02-26 20:38:14 +01:00
8e25a2eb4f bodydiff 2025-02-26 20:34:59 +01:00
b25c04bf42 odmena nastrel 2025-02-26 20:13:25 +01:00
d86bdf9218 tak odebrano 2025-02-26 19:35:00 +01:00
b7ac841760 komentar maturity 2025-02-26 19:21:01 +01:00
be4fda5e7f oprava url na spravny tvar 2025-02-26 19:17:06 +01:00
3814d292ad oprava na query set osob 2025-02-26 19:11:50 +01:00
MaM Web user
6e6b2deedb Když není jiného zbytí, tak budou předměty e-mailů šílené (by Jidáš) 2025-02-26 14:25:32 +01:00
283bb4247e Lepší? popis inline 2025-02-26 14:06:14 +01:00
ac71922472 Další dokumentace 2025-02-26 13:43:42 +01:00
b53da8c800 Okomentování hodnot enumu Stav 2025-02-26 13:36:50 +01:00
19448ce6c0 Hlasitější výstup při odstraněné znalosti/přednášce v exportu 2025-02-22 16:38:11 +01:00
46e2bb6b12 pro navíc 2025-02-21 17:17:14 +01:00
23e4f5257f Merge branch 'master' into prednasky 2025-02-21 17:16:37 +01:00
061a699f62 Chybějící backticky 2025-02-21 17:00:35 +01:00
a0ee238334 Závorky nechci jako součást odkazu 2025-02-21 16:42:57 +01:00
52c5d18595 Nekonzumovatelný kontext 2025-02-21 16:42:11 +01:00
4d5800f3b8 Pouze CSV export přednášek (smazání ostatních) 2025-02-19 18:54:25 +01:00
7a70b9dcff Pojmenované URL 2025-02-19 18:45:24 +01:00
a8e9f03cc1 <div>y kolem jednotlivých hlasování 2025-02-19 18:41:13 +01:00
dcba5b6b30 Hlasování má mít i osobu, nejen string 2025-02-19 18:38:38 +01:00
5a1eedb7b1 ruzne exporty resitelu 2025-01-29 18:17:00 +01:00
291d4e2d56 grey na opacity - a zarovnani do stran 2025-01-22 13:12:09 +01:00
19 changed files with 315 additions and 95 deletions

View file

@ -42,7 +42,11 @@
window.addEventListener("load", _ => {
aktualizuj_vse({}, true, () => {
if (location.hash !== "") location.hash = location.hash; // Po rozházení korektur sescrollujeme na kotvu v URL
if (location.hash !== "") { // Po rozházení korektur sescrollujeme na kotvu v URL
const h = location.hash.substring(1);
location.hash = "HACK";
location.hash = h;
}
});
});

View file

@ -4,8 +4,8 @@
<html lang='cs'>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" title="opraf-css" type="text/css" media="screen, projection" href="{% static "korektury/opraf.css"%}?version=2" />
<link href="{% static 'css/rozliseni.css' %}?version=2" rel="stylesheet">
<link rel="stylesheet" title="opraf-css" type="text/css" media="screen, projection" href="{% static "korektury/opraf.css"%}?version=3" />
<link href="{% static 'css/rozliseni.css' %}?version=3" rel="stylesheet">
<title>Korektury {{korekturovanepdf.nazev}}</title>
</head>
<body class="{{ LOCAL_TEST_PROD }}web" data-stav_pdf="{{ korekturovanepdf.status }}">

View file

@ -26,9 +26,10 @@
.tres {
flex: 1;
text-align: end;
}
.grey {
.half-opacity {
opacity: 0.5;
}
}

View file

@ -18,7 +18,7 @@
{% for osoba in object_list %}
<div class="osoba">
<div class="uno">{{ osoba.jmeno }} {{ osoba.prijmeni }}</div>
<div class="dos {% if not osoba.jak_se_dozvedeli %}grey{% endif %}">{% if osoba.jak_se_dozvedeli %} {{osoba.jak_se_dozvedeli}} {% else %} NEZADÁNO {% endif %}</div>
<div class="dos {% if not osoba.jak_se_dozvedeli %}half-opacity{% endif %}">{% if osoba.jak_se_dozvedeli %} {{osoba.jak_se_dozvedeli}} {% else %} NEZADÁNO {% endif %}</div>
<div class="tres">{{ osoba.datum_registrace }}</div>
</div>
{% endfor %}

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
@ -140,7 +143,55 @@ 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

@ -10,7 +10,7 @@ from soustredeni.models import Soustredeni
class Seznam_PrednaskaInline(admin.TabularInline):
"""
Pomůcka pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující hezky :py:class:`Přednášky <prednasky.models.Prednaska>`
:py:class:`Inline <django.contrib.admin.TabularInline>` pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující :py:class:`Přednášky <prednasky.models.Prednaska>`
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
"""
model = Prednaska.seznamy.through
@ -60,7 +60,7 @@ class Seznam_PrednaskaInline(admin.TabularInline):
class Seznam_ZnalostInline(admin.TabularInline):
"""
Pomůcka pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující hezky :py:class:`Znalosti <prednasky.models.Znalost>`
:py:class:`Inline <django.contrib.admin.TabularInline>` pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující :py:class:`Znalosti <prednasky.models.Znalost>`
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
"""
model = Znalost.seznamy.through
@ -97,7 +97,7 @@ admin.site.register(Seznam, SeznamAdmin)
class PrednaskaAdmin(VersionAdmin):
""" Admin pro :py:class:`Přednášku <prednasky.models.Prednaska> """
""" Admin pro :py:class:`Přednášku <prednasky.models.Prednaska>` """
list_display = ['nazev', 'org', 'obor']
list_filter = ['org', 'obor']
search_fields = ['nazev']
@ -138,7 +138,7 @@ admin.site.register(Prednaska, PrednaskaAdmin)
class ZnalostAdmin(PrednaskaAdmin): # Trochu hack, ať nemusím vypisovat všechno znovu
"""
Admin pro :py:class:`Znalost <prednasky.models.Znalost>
Admin pro :py:class:`Znalost <prednasky.models.Znalost>`
TODO předělat, aby nedědila z :py:class:`prednasky.admin.PrednaskaAdmin`, ale společné věci byly zvlášť
"""
list_display = ("__str__",)

View file

@ -3,7 +3,7 @@ from django import forms
from .models import Hlasovani, HlasovaniOZnalostech
class HlasovaniPrednaskaForm(forms.Form):
""" :py:class:`Formulář <django.forms.Form>` pro pro :py:class:`Hlasování <prednasky.models.Hlasovani>` o jedné :py:class:`Přednášce <prednasky.models.Prednaska>`
""" :py:class:`Formulář <django.forms.Form>` pro :py:class:`Hlasování <prednasky.models.Hlasovani>` o jedné :py:class:`Přednášce <prednasky.models.Prednaska>`
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
"""
@ -17,7 +17,7 @@ class HlasovaniPrednaskaForm(forms.Form):
HlasovaniPrednaskaFormSet = forms.formset_factory(HlasovaniPrednaskaForm, extra=0)
class HlasovaniZnalostiForm(forms.Form):
""" :py:class:`Formulář <django.forms.Form>` pro pro :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` o jedné :py:class:`Znalosti <prednasky.models.Znalost>`
""" :py:class:`Formulář <django.forms.Form>` pro :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` o jedné :py:class:`Znalosti <prednasky.models.Znalost>`
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
"""

View file

@ -0,0 +1,20 @@
# Generated by Django 4.2.16 on 2025-02-19 17:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'),
('prednasky', '0022_preklep_u_odpovedi_hlasovanioznalostech'),
]
operations = [
migrations.AddField(
model_name='hlasovani',
name='ucastnik_osoba',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='personalni.osoba'),
),
]

View file

@ -6,7 +6,7 @@ from personalni.models import Organizator, Osoba
class Seznam(models.Model):
"""
Spojuje :py:class:`Přednášky <prednasky.models.Prednaska>`
Spojuje :py:class:`Přednášky <prednasky.models.Prednaska>` a :py:class:`Znalosti <prednasky.models.Znalost>
se :py:class:`Soustředěními <soustredeni.models.Soustredeni>`,
kde by mohly zaznít, nebo zazní/zazněly.
"""
@ -19,8 +19,8 @@ class Seznam(models.Model):
class Stav(models.IntegerChoices):
""" Stav seznamu přednášek (NAVRH se používá k hlasování viz :py:func:`daný view <prednasky.views.newPrednaska>`). """
NAVRH = 1, "Návrh"
BUDE = 2, "Bude"
NAVRH = 1, "Návrh" #: odpovídá před-soustřeďkové představě o tom, jaké přednášky dělat (dá se o nich třeba hlasovat ap.)
BUDE = 2, "Bude" #: odpovídá definitivní představě o tom, co bude/bylo a dá se porovnávat s novými návrhy
id = models.AutoField(primary_key=True)
soustredeni = models.ForeignKey(Soustredeni, null=True, default=None, on_delete=models.PROTECT)
@ -88,6 +88,7 @@ class Hlasovani(models.Model):
#: že všechna předchozí hlasování zde mají náhodný string…)
#: TODO Změnit to na Osobu*
ucastnik = models.CharField("Účastník", max_length=100)
ucastnik_osoba = models.ForeignKey(Osoba, on_delete=models.CASCADE, blank=False, null=True)
seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL)
def __str__(self):
@ -98,6 +99,9 @@ class Znalost(models.Model):
"""
Reprezentuje znalost, na kterou se můžeme účastníka ptát (nechat je hlasovat).
(Viz :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`.)
(V podstatě :py:class:`Přednáška <prednasky.models.Prednaska>, jen neobsahuje
tolik detailů a v hlasování jiné odpovědi.)
"""
class Meta:
db_table = "prednasky_znalost"
@ -117,6 +121,9 @@ class HlasovaniOZnalostech(models.Model):
Reprezentuje hlasování jednoho účastníka
o jedné :py:class:`Znalosti <prednasky.models.Znalost>`
v jednom :py:class:`Seznamu <prednasky.models.Seznam>` (účastníkův pohled se totiž mezi sousy změnit)
(V podstatě totéž, co :py:class:`Hlasování <prednasky.models.Hlasovani>`, jen jiné komentáře
u odpovědí a místo přednášky odkazuje na znalost.)
"""
class Odpoved(models.IntegerChoices):
""" Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """

View file

@ -14,6 +14,7 @@
<p>Obtížnost 1 je nejlehčí, 3 nejtěžší.</p>
{{ form_set_prednasky.management_form }}
{% for f, p in formy_a_prednasky %}
<div class="hlasovani-prednaska">
<h4>{{p.nazev}} ({{p.org}})</h4>
<p class="textprednasky">{{p.anotace | linebreaksbr}}</p>
<label>Obor: </label> {{p.obor}}<br>
@ -22,17 +23,20 @@
<br>
{{ f }}
<br>
</div>
{% empty %}
Nejsou žádné přednášky o kterých by šlo hlasovat.
{% endfor %}
{{ form_set_znalosti.management_form }}
{% for f, z in formy_a_znalosti %}
<div class="hlasovani-znalost">
{% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %}
<h4>{{z.nazev}}</h4>
<p class="textznalosti">{{z.text | linebreaksbr}}</p>
{{ f }}
<br>
</div>
{% endfor %}
<input type="submit" value="Odeslat"/>
</form>

View file

@ -2,19 +2,19 @@
{% block content %}
<h1>{% block nadpis1a %}
Hlasování o přednáškách
Výsledky hlasování o přednáškách
{% endblock %}</h1>
{# Projdi vsechny seznamy #}
<div class="mam-org-only">
<ul>
{% for seznam in object_list %}
<li>
{% if seznam.stav == 1 %} {# STAV_NAHRH = 1 #}
<a href="/prednasky/seznam_prednasek/{{seznam.id}}">Návrh přednášek na soustředění {{seznam.soustredeni.misto}} </a>
{% if seznam.stav == seznam.Stav.NAVRH %}
Návrh přednášek na soustředění {{seznam.soustredeni.misto}}
{% else %}
<a href="/prednasky/seznam_prednasek/{{seznam.id}}">Seznam přednášek na soustředění {{seznam.soustredeni.misto}} </a>
Seznam přednášek na soustředění {{seznam.soustredeni.misto}}
{% endif %}
<a href="/prednasky/seznam_prednasek/{{seznam.id}}/hlasovani.csv">Export</a>
(<a href='{% url "seznam-export-csv" seznam=seznam.id %}'>CSV</a>)
</li>
{% endfor %}
</ul>

View file

@ -12,19 +12,9 @@ urlpatterns = [
'prednasky/metaseznam_prednasek',
org_required(views.MetaSeznamListView.as_view()),
name='metaseznam-list'),
# path(
# 'prednasky/seznam_prednasek/<int:seznam>/export',
# org_required(views.SeznamExportView),
# name='seznam-export'
# ),
path(
'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv',
org_required(views.PrednaskyExportView),
name='seznam-export-csv'
),
path(
'prednasky/seznam_prednasek/<int:seznam>/',
org_required(views.SeznamListView.as_view()),
name='seznam-list'
),
]

View file

@ -64,6 +64,7 @@ def newPrednaska(request: HttpRequest) -> HttpResponse:
prednaska=prednaska,
body=form.cleaned_data['body'],
ucastnik=ucastnik,
ucastnik_osoba=osoba,
seznam=seznam,
)
@ -116,8 +117,8 @@ def newPrednaska(request: HttpRequest) -> HttpResponse:
'prednasky/base.html',
{
'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti,
'formy_a_prednasky': zip(form_set_prednasky, prednasky),
'formy_a_znalosti': zip(form_set_znalosti, znalosti),
'formy_a_prednasky': list(zip(form_set_prednasky, prednasky)),
'formy_a_znalosti': list(zip(form_set_znalosti, znalosti)),
}
)
@ -132,64 +133,6 @@ class MetaSeznamListView(generic.ListView):
template_name = 'prednasky/metaseznam_prednasek.html'
class SeznamListView(generic.ListView):
"""
Náhled na to, kolik která přednáška v :py:class:`Seznamu <prednasky.models.Seznam>` :py:class:`hlasů <prednasky.models.Hlasovani.Body>`.
(Je otázka, zda tento View vůbec chceme. Pokud ano, hodilo by se do něj přidat i znalosti.)
"""
template_name = 'prednasky/seznam_prednasek.html'
def get_queryset(self):
self.seznam = get_object_or_404(Seznam, id=self.kwargs["seznam"])
prednasky = Prednaska.objects.filter(seznamy=self.seznam).order_by(
'org__osoba__user__first_name', 'org__osoba__user__last_name'
)
return prednasky
# FIXME nahradit anotaci s filtrem po prechodu na Django 2.2
def get_context_data(self,**kwargs):
context = super(SeznamListView, self).get_context_data(**kwargs)
# hlasovani se vztahuje k nejnovejsimu soustredeni
sous = Soustredeni.objects.first()
seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first()
for obj in self.object_list:
hlasovani_set = obj.hlasovani_set.filter(seznam=seznam).only('body')
obj.body = sum(map(lambda x: x.body,hlasovani_set))
return context
# def SeznamExportView(request, seznam):
# """Vypíše výsledky hlasování ve formátu pro prologovský optimalizátor"""
# # TODO zřejmě se nepoužívá, časem vyřadit? nahradit tabulkou vhodnější pro
# # lidi?
# hlasovani = Hlasovani.objects.filter(seznam=seznam)
# prednasky = Prednaska.objects.filter(seznamy=seznam)
# orgove = set(p.org for p in prednasky)
# ucastnici = set(h.ucastnik for h in hlasovani)
#
# for p in prednasky:
# p.body = []
# for u in ucastnici:
# try:
# p.body.append(hlasovani.get(ucastnik=u, prednaska=p).body)
# except ObjectDoesNotExist:
# # účastník nehlasoval
# p.body.append("?")
#
# for h in hlasovani:
# h.ucastnik = hash(h.ucastnik)
#
# return render(
# request,
# 'prednasky/seznam_prednasek_export.txt',
# {"hlasovani": hlasovani, "prednasky": prednasky, "orgove": orgove},
# content_type="text/plain"
# )
def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse:
"""
Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>`
@ -214,6 +157,8 @@ def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResp
# A po inicializaci sloupců vyplníme tabulku
table: [str, list[str|Prednaska|Znalost,]] = {}
errors = []
for h in hlasovani:
if h.ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek
table[h.ucastnik] = [h.ucastnik] + ([""] * width)
@ -221,7 +166,7 @@ def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResp
if h.prednaska.id in prednasky_map:
table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body
else:
pass # TODO Padat hlasitě?
errors.append(f"Přednáška {h.prednaska.id} ({h.prednaska}) dostala od Účastníka {h.ucastnik} následující hodnocení: {h.body}")
for h in hlasovani_o_znalostech:
ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen
@ -231,8 +176,10 @@ def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResp
if h.znalost.id in znalosti_map:
table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved
else:
pass # TODO Padat hlasitě?
errors.append(f"Znalost {h.znalost.id} ({h.znalost}) dostala od Účastníka {h.ucastnik.id} následující odpověď: {h.odpoved}")
if len(errors) > 0:
logger.error("Při exportování hlasování o přednáškách a znalostech se neexportovali hodnocení a přednášky (pravděpodobně se od hlasování vyškrtla nějaká znalost/přednáška ze seznamu):\n" + "\n".join(errors))
response = HttpResponse(content_type="text/csv", charset="utf-8")
response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"'

View file

@ -16,4 +16,11 @@
{% endfor %}
</ul>
<h2>Seznam účastníků &ndash; červená znamená že jim nechodí fyzické číslo</h2>
<ul>
{% for resitel in resitele %}
<li {% if resitel.neposilame %}style="color: white; background-color: red;"{% endif %}>{{ resitel.jmeno }}: {% if resitel.bodydiff > 3 %}🧦{% endif %} {% if resitel.ttitul != resitel.ftitul %} {{resitel.ftitul}} &rarr; {{resitel.ttitul}} {% endif %}</li>
{% endfor %}
</ul>
{% endblock content %}

View file

@ -27,6 +27,30 @@ def resi_v_rocniku(rocnik, cislo=None):
reseni__hodnoceni__deadline_body__cislo__rocnik=rocnik,
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):

View file

@ -375,7 +375,8 @@ class OdmenyView(generic.TemplateView):
tocislo = get_object_or_404(Cislo, rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo'))
resitele = utils.aktivniResitele(tocislo)
def get_diff(from_deadline: Deadline, to_deadline: Deadline):
def get_diff(from_deadline: Deadline, to_deadline: Deadline, probody=False):
"""Co je probody? pokud True, funkce vrací všechny rešitele a k nim potřebné informace, pokud False, vrací jen ty, kteří mají změnu v titulu."""
frombody = body_resitelu(resitele=resitele, jen_verejne=False, do=from_deadline)
tobody = body_resitelu(resitele=resitele, jen_verejne=False, do=to_deadline)
outlist = []
@ -384,8 +385,11 @@ class OdmenyView(generic.TemplateView):
tbody = tobody.get(resitel.id, 0)
ftitul = resitel.get_titul(fbody)
ttitul = resitel.get_titul(tbody)
if ftitul != ttitul:
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
if probody:
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'fbody': fbody, 'tbody': tbody, 'ftitul': ftitul, 'ttitul': ttitul, 'bodydiff': tbody - fbody, "neposilame": not(resitel.zasilat_cislo_papirove)})
else:
if ftitul != ttitul:
outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul})
return outlist
def posledni_deadline_oprava(cislo: Cislo) -> Deadline:
@ -401,6 +405,7 @@ class OdmenyView(generic.TemplateView):
context["from_deadline"] = from_deadline
context["to_deadline"] = to_deadline
context["zmeny"] = get_diff(from_deadline, to_deadline)
context["resitele"] = get_diff(from_deadline, to_deadline, probody=resitele.order_by("osoba__prijmeni"))
return context