Merge pull request 'Přednášky' (!87) from prednasky into master

Reviewed-on: #87
This commit is contained in:
Jonas Havelka 2025-02-26 20:58:49 +01:00
commit 73dcb441ef
8 changed files with 54 additions and 86 deletions

View file

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

View file

@ -3,7 +3,7 @@ from django import forms
from .models import Hlasovani, HlasovaniOZnalostech from .models import Hlasovani, HlasovaniOZnalostech
class HlasovaniPrednaskaForm(forms.Form): 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) (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) HlasovaniPrednaskaFormSet = forms.formset_factory(HlasovaniPrednaskaForm, extra=0)
class HlasovaniZnalostiForm(forms.Form): 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) (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): 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>`, se :py:class:`Soustředěními <soustredeni.models.Soustredeni>`,
kde by mohly zaznít, nebo zazní/zazněly. kde by mohly zaznít, nebo zazní/zazněly.
""" """
@ -19,8 +19,8 @@ class Seznam(models.Model):
class Stav(models.IntegerChoices): class Stav(models.IntegerChoices):
""" Stav seznamu přednášek (NAVRH se používá k hlasování viz :py:func:`daný view <prednasky.views.newPrednaska>`). """ """ Stav seznamu přednášek (NAVRH se používá k hlasování viz :py:func:`daný view <prednasky.views.newPrednaska>`). """
NAVRH = 1, "Návrh" 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" 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) id = models.AutoField(primary_key=True)
soustredeni = models.ForeignKey(Soustredeni, null=True, default=None, on_delete=models.PROTECT) 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…) #: že všechna předchozí hlasování zde mají náhodný string…)
#: TODO Změnit to na Osobu* #: TODO Změnit to na Osobu*
ucastnik = models.CharField("Účastník", max_length=100) 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) seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL)
def __str__(self): 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). Reprezentuje znalost, na kterou se můžeme účastníka ptát (nechat je hlasovat).
(Viz :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`.) (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: class Meta:
db_table = "prednasky_znalost" db_table = "prednasky_znalost"
@ -117,6 +121,9 @@ class HlasovaniOZnalostech(models.Model):
Reprezentuje hlasování jednoho účastníka Reprezentuje hlasování jednoho účastníka
o jedné :py:class:`Znalosti <prednasky.models.Znalost>` 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 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): class Odpoved(models.IntegerChoices):
""" Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """ """ 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> <p>Obtížnost 1 je nejlehčí, 3 nejtěžší.</p>
{{ form_set_prednasky.management_form }} {{ form_set_prednasky.management_form }}
{% for f, p in formy_a_prednasky %} {% for f, p in formy_a_prednasky %}
<div class="hlasovani-prednaska">
<h4>{{p.nazev}} ({{p.org}})</h4> <h4>{{p.nazev}} ({{p.org}})</h4>
<p class="textprednasky">{{p.anotace | linebreaksbr}}</p> <p class="textprednasky">{{p.anotace | linebreaksbr}}</p>
<label>Obor: </label> {{p.obor}}<br> <label>Obor: </label> {{p.obor}}<br>
@ -22,17 +23,20 @@
<br> <br>
{{ f }} {{ f }}
<br> <br>
</div>
{% empty %} {% empty %}
Nejsou žádné přednášky o kterých by šlo hlasovat. Nejsou žádné přednášky o kterých by šlo hlasovat.
{% endfor %} {% endfor %}
{{ form_set_znalosti.management_form }} {{ form_set_znalosti.management_form }}
{% for f, z in formy_a_znalosti %} {% for f, z in formy_a_znalosti %}
<div class="hlasovani-znalost">
{% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %} {% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %}
<h4>{{z.nazev}}</h4> <h4>{{z.nazev}}</h4>
<p class="textznalosti">{{z.text | linebreaksbr}}</p> <p class="textznalosti">{{z.text | linebreaksbr}}</p>
{{ f }} {{ f }}
<br> <br>
</div>
{% endfor %} {% endfor %}
<input type="submit" value="Odeslat"/> <input type="submit" value="Odeslat"/>
</form> </form>

View file

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

View file

@ -12,19 +12,9 @@ urlpatterns = [
'prednasky/metaseznam_prednasek', 'prednasky/metaseznam_prednasek',
org_required(views.MetaSeznamListView.as_view()), org_required(views.MetaSeznamListView.as_view()),
name='metaseznam-list'), name='metaseznam-list'),
# path(
# 'prednasky/seznam_prednasek/<int:seznam>/export',
# org_required(views.SeznamExportView),
# name='seznam-export'
# ),
path( path(
'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv', 'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv',
org_required(views.PrednaskyExportView), org_required(views.PrednaskyExportView),
name='seznam-export-csv' 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, prednaska=prednaska,
body=form.cleaned_data['body'], body=form.cleaned_data['body'],
ucastnik=ucastnik, ucastnik=ucastnik,
ucastnik_osoba=osoba,
seznam=seznam, seznam=seznam,
) )
@ -116,8 +117,8 @@ def newPrednaska(request: HttpRequest) -> HttpResponse:
'prednasky/base.html', 'prednasky/base.html',
{ {
'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti, 'form_set_prednasky': form_set_prednasky, 'form_set_znalosti': form_set_znalosti,
'formy_a_prednasky': zip(form_set_prednasky, prednasky), 'formy_a_prednasky': list(zip(form_set_prednasky, prednasky)),
'formy_a_znalosti': zip(form_set_znalosti, znalosti), 'formy_a_znalosti': list(zip(form_set_znalosti, znalosti)),
} }
) )
@ -132,64 +133,6 @@ class MetaSeznamListView(generic.ListView):
template_name = 'prednasky/metaseznam_prednasek.html' 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: def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse:
""" """
Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>` 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 # A po inicializaci sloupců vyplníme tabulku
table: [str, list[str|Prednaska|Znalost,]] = {} table: [str, list[str|Prednaska|Znalost,]] = {}
errors = []
for h in hlasovani: for h in hlasovani:
if h.ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek 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) 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: if h.prednaska.id in prednasky_map:
table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body
else: 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: for h in hlasovani_o_znalostech:
ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen 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: if h.znalost.id in znalosti_map:
table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved
else: 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 = HttpResponse(content_type="text/csv", charset="utf-8")
response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"' response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"'