Dokumentace aplikace prednasky

This commit is contained in:
Jonas Havelka 2025-01-29 01:05:08 +01:00
parent 34f0dffd79
commit 5125525238
5 changed files with 106 additions and 20 deletions

View file

@ -0,0 +1,3 @@
"""
Aplikace umožňující orgům vypisovat si přednášky a účastníkům o nich hlasovat.
"""

View file

@ -9,6 +9,10 @@ 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>`
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
"""
model = Prednaska.seznamy.through model = Prednaska.seznamy.through
extra = 0 extra = 0
@ -55,6 +59,7 @@ class Seznam_PrednaskaInline(admin.TabularInline):
class SeznamAdmin(VersionAdmin): class SeznamAdmin(VersionAdmin):
""" Admin pro :py:class:`Seznam <prednasky.models.Seznam>` """
list_display = ['soustredeni', 'stav'] list_display = ['soustredeni', 'stav']
inlines = [Seznam_PrednaskaInline] inlines = [Seznam_PrednaskaInline]
@ -62,6 +67,7 @@ admin.site.register(Seznam, SeznamAdmin)
class PrednaskaAdmin(VersionAdmin): class PrednaskaAdmin(VersionAdmin):
""" 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']
@ -70,6 +76,7 @@ class PrednaskaAdmin(VersionAdmin):
actions = ['move_to_soustredeni'] actions = ['move_to_soustredeni']
def move_to_soustredeni(self, request, queryset): def move_to_soustredeni(self, request, queryset):
""" Přidá dané přednášky do seznamu, o kterém se právě hlasuje """
sous = Soustredeni.objects.first() sous = Soustredeni.objects.first()
seznam = Seznam.objects.filter(soustredeni=sous, stav=Seznam.Stav.NAVRH) seznam = Seznam.objects.filter(soustredeni=sous, stav=Seznam.Stav.NAVRH)
if len(seznam) == 0: if len(seznam) == 0:
@ -100,6 +107,10 @@ 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>
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__",)
list_filter = () list_filter = ()

View file

@ -3,13 +3,29 @@ 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>`
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
"""
#: ID :py:class:`Přednášky <prednasky.models.Prednaska>`, o které se hlasuje
prednaska_id = forms.IntegerField(widget=forms.HiddenInput) prednaska_id = forms.IntegerField(widget=forms.HiddenInput)
#: :py:class:`Hodnocení (Body) <prednasky.models.Hlasovani.Body>` této přednášky
body = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=Hlasovani.Body.choices, initial=Hlasovani.Body.JEDNO) body = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=Hlasovani.Body.choices, initial=Hlasovani.Body.JEDNO)
#: Množina formulářů (:py:class:`formset <django.forms.formsets.BaseFormSet>` :py:class:`HlasovaniPrednaskaFormů <prednasky.forms.HlasovaniPrednaskaForm>`)
#: pro :py:class:`Hlasování <prednasky.models.Hlasovani>` o množině :py:class:`Přednášek <prednasky.models.Prednaska>`
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>`
(neobsahuje téměř nic, většina se musí doplnit jiným způsobem)
"""
#: ID :py:class:`Znalosti <prednasky.models.Znalost>`, o které hlasujeme
znalost_id = forms.IntegerField(widget=forms.HiddenInput) znalost_id = forms.IntegerField(widget=forms.HiddenInput)
#: :py:class:`Odpověď <prednasky.models.HlasovaniOZnalostech.Odpoved>` na tuto znalost
odpoved = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=HlasovaniOZnalostech.Odpoved.choices) odpoved = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=HlasovaniOZnalostech.Odpoved.choices)
#: Množina formulářů (:py:class:`formset <django.forms.formsets.BaseFormSet>` :py:class:`HlasovaniZnalostiFormů <prednasky.forms.HlasovaniZnalostiForm>`)
#: pro :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>` o množině :py:class:`Znalostí <prednasky.models.Znalost>`
HlasovaniZnalostiFormSet = forms.formset_factory(HlasovaniZnalostiForm, extra=0) HlasovaniZnalostiFormSet = forms.formset_factory(HlasovaniZnalostiForm, extra=0)

View file

@ -5,6 +5,12 @@ from personalni.models import Organizator, Osoba
class Seznam(models.Model): class Seznam(models.Model):
"""
Spojuje :py:class:`Přednášky <prednasky.models.Prednaska>`
se :py:class:`Soustředěními <soustredeni.models.Soustredeni>`,
kde by mohly zaznít, nebo zazní/zazněly.
"""
class Meta: class Meta:
db_table = "prednasky_seznam" db_table = "prednasky_seznam"
verbose_name = "Seznam přednášek" verbose_name = "Seznam přednášek"
@ -12,18 +18,23 @@ class Seznam(models.Model):
ordering = ["soustredeni", "stav"] ordering = ["soustredeni", "stav"]
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>`). """
NAVRH = 1, "Návrh" NAVRH = 1, "Návrh"
BUDE = 2, "Bude" BUDE = 2, "Bude"
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)
stav = models.IntegerField("Stav", choices=Stav.choices, default=Stav.NAVRH) stav = models.IntegerField("Stav", choices=Stav.choices, default=Stav.NAVRH) #: :py:class:`Stav <prednasky.models.Seznam.Stav>` Seznamu
def __str__(self): def __str__(self):
return f"Seznam {'návrhů ' if self.stav == Seznam.Stav.NAVRH else ''}přednášek na {self.soustredeni}" return f"Seznam {'návrhů ' if self.stav == Seznam.Stav.NAVRH else ''}přednášek na {self.soustredeni}"
class Prednaska(models.Model): class Prednaska(models.Model):
"""
Reprezentuje přednášku, kterou si org může vypsat a účastník o hlasovat.
(Viz :py:class:`Hlasování <prednasky.models.Hlasovani>`.)
"""
class Meta: class Meta:
db_table = "prednasky_prednaska" db_table = "prednasky_prednaska"
verbose_name = "Přednáška" verbose_name = "Přednáška"
@ -40,7 +51,7 @@ class Prednaska(models.Model):
org = models.ForeignKey(Organizator, on_delete=models.PROTECT) org = models.ForeignKey(Organizator, on_delete=models.PROTECT)
popis = models.TextField("Popis pro orgy", null=True, blank=True, help_text="Neveřejný popis pro ostatní orgy") popis = models.TextField("Popis pro orgy", null=True, blank=True, help_text="Neveřejný popis pro ostatní orgy")
anotace = models.TextField("Anotace", null=True, blank=True, help_text="Veřejná anotace v hlasování") anotace = models.TextField("Anotace", null=True, blank=True, help_text="Veřejná anotace v hlasování")
obtiznost = models.IntegerField("Obtížnost", choices=Obtiznost.choices) obtiznost = models.IntegerField("Obtížnost", choices=Obtiznost.choices) #: :py:class:`Obtížnost <prednasky.models.Prednaska.Obtiznost>` Přednášky
obor = models.CharField("Obor", max_length=5, help_text="Podmnožina MFIOB") obor = models.CharField("Obor", max_length=5, help_text="Podmnožina MFIOB")
klicova = models.CharField("Klíčová slova", max_length=200, null=True, blank=True) klicova = models.CharField("Klíčová slova", max_length=200, null=True, blank=True)
seznamy = models.ManyToManyField(Seznam) seznamy = models.ManyToManyField(Seznam)
@ -50,6 +61,11 @@ class Prednaska(models.Model):
class Hlasovani(models.Model): class Hlasovani(models.Model):
"""
Reprezentuje hlasování jednoho účastníka
o jedné :py:class:`Přednášce <prednasky.models.Prednaska>`
v jednom :py:class:`Seznamu <prednasky.models.Seznam>` (účastníkův pohled se totiž mezi sousy změnit)
"""
class Meta: class Meta:
db_table = "prednasky_hlasovani" db_table = "prednasky_hlasovani"
verbose_name = "Hlasování" verbose_name = "Hlasování"
@ -57,17 +73,20 @@ class Hlasovani(models.Model):
ordering = ["ucastnik", "prednaska"] ordering = ["ucastnik", "prednaska"]
class Body(models.IntegerChoices): class Body(models.IntegerChoices):
""" Ohodnocení přednášky v daném Hlasování (větší číslo = víc chci) """
NECHCI = -1, "rozhodně nechci" NECHCI = -1, "rozhodně nechci"
JEDNO = 0, "je mi to jedno" JEDNO = 0, "je mi to jedno"
CHCI = 1, "rozhodně chci" CHCI = 1, "rozhodně chci"
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
prednaska = models.ForeignKey(Prednaska, on_delete=models.CASCADE) prednaska = models.ForeignKey(Prednaska, on_delete=models.CASCADE)
#: Příslušné hlasování: :py:class:`Body <prednasky.models.Hlasovani.Body>`
body = models.IntegerField("Body", default=Body.JEDNO, choices=Body.choices) body = models.IntegerField("Body", default=Body.JEDNO, choices=Body.choices)
# (přechod z jména na objekt Osoby nějak kape na tom, #: Účastník, který hlasoval. Pouze string:
# že všechna předchozí hlasování zde mají náhodný string…) #: *(přechod z jména na objekt Osoby nějak kape na tom,
# TODO Změnit to na Osobu #: ž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 = models.CharField("Účastník", max_length=100)
seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL) seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL)
@ -76,6 +95,10 @@ class Hlasovani(models.Model):
class Znalost(models.Model): 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>`.)
"""
class Meta: class Meta:
db_table = "prednasky_znalost" db_table = "prednasky_znalost"
verbose_name = "Znalost k přednáškám" verbose_name = "Znalost k přednáškám"
@ -90,12 +113,17 @@ class Znalost(models.Model):
class HlasovaniOZnalostech(models.Model): 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)
"""
class Odpoved(models.IntegerChoices): class Odpoved(models.IntegerChoices):
UMIM = -1, "Tohle celkem umím" UMIM = -1, "Tohle celkem umím"
CIRCA = 0, "Už jsem o tom slyšel, ale neřekl bychm, že to úplně umím" CIRCA = 0, "Už jsem o tom slyšel, ale neřekl bychm, že to úplně umím"
NEUMIM = 1, "Tohle vůbec neznám" NEUMIM = 1, "Tohle vůbec neznám"
odpoved = models.CharField(u"odpověď", max_length=16, choices=Odpoved.choices, blank=False, null=False) odpoved = models.CharField(u"odpověď", max_length=16, choices=Odpoved.choices, blank=False, null=False) #: :py:class:`Odpověď <prednasky.models.Prednaska.Odpoved>` na HlasováníOZnalostech
znalost = models.ForeignKey(Znalost, on_delete=models.CASCADE, blank=False, null=False) znalost = models.ForeignKey(Znalost, on_delete=models.CASCADE, blank=False, null=False)
ucastnik = models.ForeignKey(Osoba, on_delete=models.CASCADE, blank=False, null=False) ucastnik = models.ForeignKey(Osoba, on_delete=models.CASCADE, blank=False, null=False)
seznam = models.ForeignKey(Seznam, on_delete=models.SET_NULL, blank=True, null=True) seznam = models.ForeignKey(Seznam, on_delete=models.SET_NULL, blank=True, null=True)

View file

@ -2,7 +2,7 @@ import csv
import http import http
import logging import logging
from django.http import HttpResponse from django.http import HttpResponse, HttpRequest
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.views import generic from django.views import generic
from django.shortcuts import HttpResponseRedirect from django.shortcuts import HttpResponseRedirect
@ -22,7 +22,14 @@ ZNALOSTI_PREFIX = "znalosti"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def newPrednaska(request): def newPrednaska(request: HttpRequest) -> HttpResponse:
"""
View zobrazující a ukládající účastnické hlasování
(:py:class:`Hlasování <prednasky.models.Hlasovani>`
a :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`)
o :py:class:`Přednáškách <prednasky.models.Prednaska>`
a :py:class:`Znalostech <prednasky.models.Znalost>`
"""
# hlasovani se vztahuje k nejnovejsimu soustredeni # hlasovani se vztahuje k nejnovejsimu soustredeni
sous = Nastaveni.get_solo().aktualni_sous sous = Nastaveni.get_solo().aktualni_sous
seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first() seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first()
@ -35,12 +42,14 @@ def newPrednaska(request):
osoba = Osoba.objects.filter(user=request.user).first() osoba = Osoba.objects.filter(user=request.user).first()
ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) # id, kvůli kolizi jmen ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) # id, kvůli kolizi jmen
if request.method == 'POST': if request.method == 'POST': # Když to byl POST, tak ukládáme.
# Načteme data do formsetů
form_set_prednasky = HlasovaniPrednaskaFormSet(request.POST, prefix=PREDNASKY_PREFIX) form_set_prednasky = HlasovaniPrednaskaFormSet(request.POST, prefix=PREDNASKY_PREFIX)
form_set_znalosti = HlasovaniZnalostiFormSet(request.POST, prefix=ZNALOSTI_PREFIX) form_set_znalosti = HlasovaniZnalostiFormSet(request.POST, prefix=ZNALOSTI_PREFIX)
if form_set_prednasky.is_valid() and form_set_znalosti.is_valid(): if form_set_prednasky.is_valid() and form_set_znalosti.is_valid():
with transaction.atomic(): with transaction.atomic():
# Místo updatování data prostě smažeme a vytvoříme nová
seznam.hlasovani_set.filter(ucastnik=ucastnik).delete() seznam.hlasovani_set.filter(ucastnik=ucastnik).delete()
seznam.hlasovanioznalostech_set.filter(ucastnik=osoba).delete() seznam.hlasovanioznalostech_set.filter(ucastnik=osoba).delete()
@ -73,17 +82,19 @@ def newPrednaska(request):
) )
return HttpResponseRedirect('./hotovo') return HttpResponseRedirect('./hotovo')
else:
else: # Pokud je nějaký formset nevalidní, vracíme je k přepracování
prednasky = seznam.prednaska_set.all() prednasky = seznam.prednaska_set.all()
znalosti = seznam.znalost_set.all() znalosti = seznam.znalost_set.all()
# Spadnout, pokud nesedí přednáška/znalost s formulářem. (Nějak se mi to nepovedlo.) # FIXME Spadnout, pokud nesedí přednáška/znalost s formulářem. (Nějak se mi to nepovedlo.)
# Může se totiž stát, že se mezitím změnily přednášky (nějaká byla přidána/odebrána)
else: else: # Když to nebyl POST, tak inicializujeme (pokud už o přednášce/znalosti účastník hlasoval, předvyplníme mu to).
def odpoved_prednasky(p): def odpoved_prednasky(p: Prednaska) -> Hlasovani.Body:
hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first() hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first()
return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO
def odpoved_znalosti(z): def odpoved_znalosti(z: Znalost) -> Znalost.Odpoved:
hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first() hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first()
return hlasovani.odpoved if hlasovani else Znalost.Odpoved.CIRCA return hlasovani.odpoved if hlasovani else Znalost.Odpoved.CIRCA
@ -99,6 +110,7 @@ def newPrednaska(request):
], prefix=ZNALOSTI_PREFIX) ], prefix=ZNALOSTI_PREFIX)
# V případě nePOSTu nebo chyby při ukládání vracíme hlasování
return render( return render(
request, request,
'prednasky/base.html', 'prednasky/base.html',
@ -110,15 +122,21 @@ def newPrednaska(request):
) )
def Prednaska_hotovo(request): def Prednaska_hotovo(request: HttpRequest) -> HttpResponse:
""" View po vyplnění :py:func:`hlasování <prednasky.views.newPrednaska>` """
return formularOKView(request, "Děkujeme za vyplnění hlasování o přednáškách a těšíme se na soustředění.") return formularOKView(request, "Děkujeme za vyplnění hlasování o přednáškách a těšíme se na soustředění.")
class MetaSeznamListView(generic.ListView): class MetaSeznamListView(generic.ListView):
""" Seznam všech :py:class:`Seznamů <prednasky.models.Seznam>` s odkazy na exporty """
model = Seznam model = Seznam
template_name = 'prednasky/metaseznam_prednasek.html' template_name = 'prednasky/metaseznam_prednasek.html'
class SeznamListView(generic.ListView): 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' template_name = 'prednasky/seznam_prednasek.html'
def get_queryset(self): def get_queryset(self):
@ -172,10 +190,19 @@ class SeznamListView(generic.ListView):
# ) # )
def PrednaskyExportView(request, seznam: int, **kwargs): def PrednaskyExportView(request: HttpRequest, seznam: int, **kwargs) -> HttpResponse:
"""
Vrátí všechna :py:class:`Hlasování <prednasky.models.Hlasovani>`
i :py:class:`HlasováníOZnalostech <prednasky.models.HlasovaniOZnalostech>`
v daném :py:class:`Seznamu <prednasky.models.Seznam>`
jako csv soubor (řádky = účastníci, sloupce = přednášky&znalosti).
:param seznam: ID daného :py:class:`Seznamu <prednasky.models.Seznam>`
"""
hlasovani = Hlasovani.objects.filter(seznam=seznam).select_related("prednaska") hlasovani = Hlasovani.objects.filter(seznam=seznam).select_related("prednaska")
hlasovani_o_znalostech = HlasovaniOZnalostech.objects.filter(seznam=seznam).select_related('ucastnik', 'znalost') hlasovani_o_znalostech = HlasovaniOZnalostech.objects.filter(seznam=seznam).select_related('ucastnik', 'znalost')
# Inicializujeme sloupce
prednasky = list(Prednaska.objects.filter(seznamy=seznam)) prednasky = list(Prednaska.objects.filter(seznamy=seznam))
znalosti = list(Znalost.objects.filter(seznamy=seznam)) znalosti = list(Znalost.objects.filter(seznamy=seznam))
@ -184,26 +211,27 @@ def PrednaskyExportView(request, seznam: int, **kwargs):
znalosti_map: dict[int, int] = {z.id: i for i, z in enumerate(znalosti, offset + 1)} znalosti_map: dict[int, int] = {z.id: i for i, z in enumerate(znalosti, offset + 1)}
width = offset + len(znalosti_map) width = offset + len(znalosti_map)
# A po inicializaci sloupců vyplníme tabulku
table: [str, list[str|Prednaska|Znalost,]] = {} table: [str, list[str|Prednaska|Znalost,]] = {}
for h in hlasovani: for h in hlasovani:
if h.ucastnik not in table: 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)
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 # Padat hlasitě? pass # TODO Padat hlasitě?
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
if ucastnik not in table: if ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek
table[ucastnik] = [ucastnik] + ([""] * width) table[ucastnik] = [ucastnik] + ([""] * width)
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 # Padat hlasitě? pass # TODO Padat hlasitě?
response = HttpResponse(content_type="text/csv", charset="utf-8") response = HttpResponse(content_type="text/csv", charset="utf-8")