Compare commits

..

16 commits

Author SHA1 Message Date
5125525238 Dokumentace aplikace prednasky 2025-01-29 01:06:00 +01:00
34f0dffd79 Merge branch 'master' into prednasky 2025-01-29 00:57:24 +01:00
9e513bba9a Kopírování je častým zdrojem chyb 2025-01-29 00:30:55 +01:00
cb14e4a91e A očividně nevygeneroval migraci k přepsání stringů u Hlasovani.Body v commitu e933c697 2025-01-24 21:07:06 +01:00
1719e8be9a Zapomněl jsem přidat CSS místo smazaného <i> 2025-01-24 21:03:44 +01:00
0634cdad87 Očividně každý systém žere uvozovky v f-stringu jinak 2025-01-24 20:42:44 +01:00
90e7b97b85 Zakomentován starý export 2025-01-24 20:35:24 +01:00
4001822842 Oprava práv pro aplikaci přednášky 2025-01-24 20:32:28 +01:00
7ca7093371 Export hlasování do CSV 2025-01-24 20:22:38 +01:00
fbd75d2f72 Hlasování o přednáškách pomocí formsetů… 2025-01-24 19:40:54 +01:00
e12b614e1c ODPOVED -> Odpoved 2025-01-24 16:01:39 +01:00
bcda95f0b3 Stringifikace hlasování o znalostech 2025-01-24 15:51:06 +01:00
6c35a5b6f3 Uhlazení prednasky.models 2025-01-24 15:49:55 +01:00
e933c6978d Choices na Enum (u přednášek) 2025-01-24 15:36:33 +01:00
2f814956a7 Nepoužívaný kus kódu 2025-01-24 15:20:00 +01:00
f61533df0a Přidání Znalosti do modelu 2025-01-24 15:13:37 +01:00
13 changed files with 511 additions and 217 deletions

View file

@ -14,12 +14,12 @@
"flatpage"
],
[
"delete_flatpage",
"change_flatpage",
"flatpages",
"flatpage"
],
[
"change_flatpage",
"delete_flatpage",
"flatpages",
"flatpage"
],
@ -34,12 +34,12 @@
"galerie"
],
[
"delete_galerie",
"change_galerie",
"galerie",
"galerie"
],
[
"change_galerie",
"delete_galerie",
"galerie",
"galerie"
],
@ -54,12 +54,12 @@
"obrazek"
],
[
"delete_obrazek",
"change_obrazek",
"galerie",
"obrazek"
],
[
"change_obrazek",
"delete_obrazek",
"galerie",
"obrazek"
],
@ -104,12 +104,12 @@
"komentar"
],
[
"delete_komentar",
"change_komentar",
"korektury",
"komentar"
],
[
"change_komentar",
"delete_komentar",
"korektury",
"komentar"
],
@ -124,12 +124,12 @@
"korekturovanepdf"
],
[
"delete_korekturovanepdf",
"change_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"change_korekturovanepdf",
"delete_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
@ -144,12 +144,12 @@
"oprava"
],
[
"delete_oprava",
"change_oprava",
"korektury",
"oprava"
],
[
"change_oprava",
"delete_oprava",
"korektury",
"oprava"
],
@ -164,12 +164,12 @@
"novinky"
],
[
"delete_novinky",
"change_novinky",
"novinky",
"novinky"
],
[
"change_novinky",
"delete_novinky",
"novinky",
"novinky"
],
@ -204,12 +204,12 @@
"prijemce"
],
[
"delete_prijemce",
"change_prijemce",
"personalni",
"prijemce"
],
[
"change_prijemce",
"delete_prijemce",
"personalni",
"prijemce"
],
@ -234,12 +234,12 @@
"skola"
],
[
"delete_skola",
"change_skola",
"personalni",
"skola"
],
[
"change_skola",
"delete_skola",
"personalni",
"skola"
],
@ -248,38 +248,28 @@
"personalni",
"skola"
],
[
"add_hlasovani",
"prednasky",
"hlasovani"
],
[
"delete_hlasovani",
"prednasky",
"hlasovani"
],
[
"change_hlasovani",
"prednasky",
"hlasovani"
],
[
"view_hlasovani",
"prednasky",
"hlasovani"
],
[
"view_hlasovanioznalostech",
"prednasky",
"hlasovanioznalostech"
],
[
"add_prednaska",
"prednasky",
"prednaska"
],
[
"delete_prednaska",
"change_prednaska",
"prednasky",
"prednaska"
],
[
"change_prednaska",
"delete_prednaska",
"prednasky",
"prednaska"
],
@ -294,12 +284,12 @@
"seznam"
],
[
"delete_seznam",
"change_seznam",
"prednasky",
"seznam"
],
[
"change_seznam",
"delete_seznam",
"prednasky",
"seznam"
],
@ -308,18 +298,38 @@
"prednasky",
"seznam"
],
[
"add_znalost",
"prednasky",
"znalost"
],
[
"change_znalost",
"prednasky",
"znalost"
],
[
"delete_znalost",
"prednasky",
"znalost"
],
[
"view_znalost",
"prednasky",
"znalost"
],
[
"add_konfera",
"soustredeni",
"konfera"
],
[
"delete_konfera",
"change_konfera",
"soustredeni",
"konfera"
],
[
"change_konfera",
"delete_konfera",
"soustredeni",
"konfera"
],
@ -334,12 +344,12 @@
"konfery_ucastnici"
],
[
"delete_konfery_ucastnici",
"change_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"change_konfery_ucastnici",
"delete_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
@ -354,12 +364,12 @@
"soustredeni"
],
[
"delete_soustredeni",
"change_soustredeni",
"soustredeni",
"soustredeni"
],
[
"change_soustredeni",
"delete_soustredeni",
"soustredeni",
"soustredeni"
],
@ -374,12 +384,12 @@
"soustredeni_organizatori"
],
[
"delete_soustredeni_organizatori",
"change_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"change_soustredeni_organizatori",
"delete_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
@ -394,12 +404,12 @@
"soustredeni_ucastnici"
],
[
"delete_soustredeni_ucastnici",
"change_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"change_soustredeni_ucastnici",
"delete_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
@ -414,12 +424,12 @@
"tag"
],
[
"delete_tag",
"change_tag",
"taggit",
"tag"
],
[
"change_tag",
"delete_tag",
"taggit",
"tag"
],
@ -434,12 +444,12 @@
"taggeditem"
],
[
"delete_taggeditem",
"change_taggeditem",
"taggit",
"taggeditem"
],
[
"change_taggeditem",
"delete_taggeditem",
"taggit",
"taggeditem"
],
@ -454,12 +464,12 @@
"cislo"
],
[
"delete_cislo",
"change_cislo",
"tvorba",
"cislo"
],
[
"change_cislo",
"delete_cislo",
"tvorba",
"cislo"
],
@ -474,12 +484,12 @@
"clanek"
],
[
"delete_clanek",
"change_clanek",
"tvorba",
"clanek"
],
[
"change_clanek",
"delete_clanek",
"tvorba",
"clanek"
],
@ -509,12 +519,12 @@
"pohadka"
],
[
"delete_pohadka",
"change_pohadka",
"tvorba",
"pohadka"
],
[
"change_pohadka",
"delete_pohadka",
"tvorba",
"pohadka"
],
@ -529,12 +539,12 @@
"problem"
],
[
"delete_problem",
"change_problem",
"tvorba",
"problem"
],
[
"change_problem",
"delete_problem",
"tvorba",
"problem"
],
@ -549,12 +559,12 @@
"rocnik"
],
[
"delete_rocnik",
"change_rocnik",
"tvorba",
"rocnik"
],
[
"change_rocnik",
"delete_rocnik",
"tvorba",
"rocnik"
],
@ -569,12 +579,12 @@
"tema"
],
[
"delete_tema",
"change_tema",
"tvorba",
"tema"
],
[
"change_tema",
"delete_tema",
"tvorba",
"tema"
],
@ -589,12 +599,12 @@
"uloha"
],
[
"delete_uloha",
"change_uloha",
"tvorba",
"uloha"
],
[
"change_uloha",
"delete_uloha",
"tvorba",
"uloha"
],
@ -609,12 +619,12 @@
"nastaveni"
],
[
"delete_nastaveni",
"change_nastaveni",
"various",
"nastaveni"
],
[
"change_nastaveni",
"delete_nastaveni",
"various",
"nastaveni"
],

View file

@ -57,6 +57,7 @@ DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok
CSRF_FAILURE_VIEW = 'various.views.csrf.csrf_error'
# Modules configuration
FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',

View file

@ -503,5 +503,10 @@ label[for=id_skola] {
font-weight: bold;
}
/* Přednášky */
.textznalosti, .textprednasky {
font-style: italic;
}
/*******************/

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

@ -4,11 +4,15 @@ from reversion.admin import VersionAdmin
from django.utils.safestring import mark_safe
from django.utils.html import escape
from .models import Prednaska, Seznam, STAV_NAVRH
from .models import Prednaska, Seznam, Znalost
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>`
v adminu :py:class:`Seznamu <prednasky.models.Seznam>`.
"""
model = Prednaska.seznamy.through
extra = 0
@ -55,6 +59,7 @@ class Seznam_PrednaskaInline(admin.TabularInline):
class SeznamAdmin(VersionAdmin):
""" Admin pro :py:class:`Seznam <prednasky.models.Seznam>` """
list_display = ['soustredeni', 'stav']
inlines = [Seznam_PrednaskaInline]
@ -62,16 +67,18 @@ admin.site.register(Seznam, SeznamAdmin)
class PrednaskaAdmin(VersionAdmin):
""" Admin pro :py:class:`Přednášku <prednasky.models.Prednaska> """
list_display = ['nazev', 'org', 'obor']
list_filter = ['org', 'obor']
search_fields = []
search_fields = ['nazev']
filter_horizontal = ('seznamy', )
actions = ['move_to_soustredeni']
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()
seznam = Seznam.objects.filter(soustredeni=sous, stav=STAV_NAVRH)
seznam = Seznam.objects.filter(soustredeni=sous, stav=Seznam.Stav.NAVRH)
if len(seznam) == 0:
self.message_user(
request,
@ -97,3 +104,14 @@ class PrednaskaAdmin(VersionAdmin):
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>
TODO předělat, aby nedědila z :py:class:`prednasky.admin.PrednaskaAdmin`, ale společné věci byly zvlášť
"""
list_display = ("__str__",)
list_filter = ()
admin.site.register(Znalost, ZnalostAdmin)

View file

@ -1,7 +1,31 @@
from django import forms
class NewPrednaskyForm(forms.Form):
ucastnik = forms.CharField(label = 'Tvoje jméno', max_length = 100)
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>`
(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)
#: :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)
#: 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)
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)
#: :py:class:`Odpověď <prednasky.models.HlasovaniOZnalostech.Odpoved>` na tuto znalost
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)

View file

@ -0,0 +1,39 @@
# Generated by Django 4.2.16 on 2025-01-24 13:41
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', '0018_post_split_soustredeni'),
]
operations = [
migrations.CreateModel(
name='Znalost',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nazev', models.CharField(help_text='Např. Neuronové sítě', max_length=200, verbose_name='Nadpis')),
('text', models.TextField(blank=True, help_text='Např. Perceptron, vrstevnatá síť, forward a backward propagation', null=True, verbose_name='Detailní popis')),
('seznamy', models.ManyToManyField(to='prednasky.seznam')),
],
options={
'verbose_name': 'Znalost k přednáškám',
'verbose_name_plural': 'Znalosti k přednáškám',
'db_table': 'prednasky_znalost',
},
),
migrations.CreateModel(
name='HlasovaniOZnalostech',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('odpoved', models.CharField(choices=[(-1, 'Tohle celkem umím'), (0, 'Už jsem o tom slyšel, ale neřekl bychm, že to úplně umím'), (1, 'Tohle vůbec neznám')], max_length=16, verbose_name='odpověď')),
('seznam', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='prednasky.seznam')),
('ucastnik', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='personalni.osoba')),
('znalost', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='prednasky.znalost')),
],
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2025-01-24 20:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('prednasky', '0019_znalost_hlasovanioznalostech'),
]
operations = [
migrations.AlterField(
model_name='hlasovani',
name='body',
field=models.IntegerField(choices=[(-1, 'rozhodně nechci'), (0, 'je mi to jedno'), (1, 'rozhodně chci')], default=0, verbose_name='Body'),
),
]

View file

@ -1,81 +1,133 @@
from django.db import models
from soustredeni.models import Soustredeni
from personalni.models import Organizator
STAV_NAVRH = 1
STAV_BUDE = 2
STAV_CHOICES = (
(STAV_NAVRH, 'Návrh'),
(STAV_BUDE, 'Bude')
)
from personalni.models import Organizator, Osoba
class Seznam(models.Model):
class Meta:
db_table = 'prednasky_seznam'
verbose_name = 'Seznam přednášek'
verbose_name_plural = 'Seznamy přednášek'
ordering = ['soustredeni', 'stav']
"""
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.
"""
id = models.AutoField(primary_key = True)
soustredeni = models.ForeignKey(Soustredeni,null = True, default = None,
on_delete=models.PROTECT)
stav = models.IntegerField('Stav',choices=STAV_CHOICES,default = STAV_NAVRH)
class Meta:
db_table = "prednasky_seznam"
verbose_name = "Seznam přednášek"
verbose_name_plural = "Seznamy přednášek"
ordering = ["soustredeni", "stav"]
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"
id = models.AutoField(primary_key=True)
soustredeni = models.ForeignKey(Soustredeni, null=True, default=None, on_delete=models.PROTECT)
stav = models.IntegerField("Stav", choices=Stav.choices, default=Stav.NAVRH) #: :py:class:`Stav <prednasky.models.Seznam.Stav>` Seznamu
def __str__(self):
return "Seznam {}přednášek na {}".format("návrhů "
if self.stav == STAV_NAVRH else "", self.soustredeni)
return f"Seznam {'návrhů ' if self.stav == Seznam.Stav.NAVRH else ''}přednášek na {self.soustredeni}"
CHOICES_OBTIZNOST = (
(1, 'Lehká'),
(2, 'Střední'),
(3, 'Těžká'),
)
CHOICES_BODY = (
(-1, '-1'),
(0, '0'),
(1, '1'),
)
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:
db_table = 'prednasky_prednaska'
verbose_name = 'Přednáška'
verbose_name_plural = 'Přednášky'
ordering = ['org', 'nazev']
db_table = "prednasky_prednaska"
verbose_name = "Přednáška"
verbose_name_plural = "Přednášky"
ordering = ["org", "nazev"]
id = models.AutoField(primary_key = True)
nazev = models.CharField('Název', max_length = 300)
class Obtiznost(models.IntegerChoices):
LEHKA = 1, "Lehká"
STREDNI = 2, "Střední"
TEZKA = 3, "Těžká"
id = models.AutoField(primary_key=True)
nazev = models.CharField("Název", max_length=300)
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')
anotace = models.TextField('Anotace',null = True, blank = True, help_text = 'Veřejná anotace v hlasování')
obtiznost = models.IntegerField('Obtížnost', choices=CHOICES_OBTIZNOST)
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)
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í")
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")
klicova = models.CharField("Klíčová slova", max_length=200, null=True, blank=True)
seznamy = models.ManyToManyField(Seznam)
def __str__(self):
return "{} ({})".format(self.nazev, self.org)
return f"{self.nazev} ({self.org})"
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:
db_table = 'prednasky_hlasovani'
verbose_name = 'Hlasování'
verbose_name_plural = 'Hlasování'
ordering = ['ucastnik', 'prednaska']
id = models.AutoField(primary_key = True)
db_table = "prednasky_hlasovani"
verbose_name = "Hlasování"
verbose_name_plural = "Hlasování"
ordering = ["ucastnik", "prednaska"]
class Body(models.IntegerChoices):
""" Ohodnocení přednášky v daném Hlasování (větší číslo = víc chci) """
NECHCI = -1, "rozhodně nechci"
JEDNO = 0, "je mi to jedno"
CHCI = 1, "rozhodně chci"
id = models.AutoField(primary_key=True)
prednaska = models.ForeignKey(Prednaska, on_delete=models.CASCADE)
body = models.IntegerField('Body', default = 0, choices = CHOICES_BODY)
ucastnik = models.CharField('Účastník', max_length = 100)
seznam = models.ForeignKey(Seznam,null=True,on_delete=models.SET_NULL)
#: Příslušné hlasování: :py:class:`Body <prednasky.models.Hlasovani.Body>`
body = models.IntegerField("Body", default=Body.JEDNO, choices=Body.choices)
#: Účastník, který hlasoval. Pouze string:
#: *(přechod z jména na objekt Osoby nějak kape na tom,
#: ž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)
seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL)
def __str__(self):
return "{} dal {} bodů {} v seznamu {}".format(self.ucastnik,
self.body, self.prednaska, self.seznam)
return f"{self.ucastnik} dal {self.body} bodů {self.prednaska} v seznamu {self.seznam}"
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:
db_table = "prednasky_znalost"
verbose_name = "Znalost k přednáškám"
verbose_name_plural = "Znalosti k přednáškám"
nazev = models.CharField("Nadpis", max_length=200, blank=False, null=False, help_text="Např. Neuronové sítě")
text = models.TextField("Detailní popis", blank=True, null=True, help_text="Např. Perceptron, vrstevnatá síť, forward a backward propagation")
seznamy = models.ManyToManyField(Seznam)
def __str__(self):
return self.nazev
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):
UMIM = -1, "Tohle celkem 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"
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)
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)
def __str__(self):
return f"{self.ucastnik} dal {self.znalost} bodů {self.znalost} v seznamu {self.seznam}"

View file

@ -5,36 +5,36 @@
{% block content %}
<h1>
{% block nadpis1a %}Hlasování o přednáškách{% endblock %}
</h1>
<p>
Jak moc by ses chtěl(a) zúčastnit následujících přednášek?
<br>
<span style="font-size: 75%">Obtížnost 1 je nejlehčí, 3 nejtěžší.</span>
</p>
<h1>{% block nadpis1a %}Hlasování o přednáškách{% endblock %}</h1>
<form enctype="multipart/form-data" action="." method="post">
{% csrf_token %}
<table>
{% for p, h in prednasky %}
<tr><td><label>{{p.org}}: <span style="font-size: 175%">{{p.nazev}}</span></label></td></tr>
<tr><td><p><i>{{p.anotace}}</i></p></td></tr>
<tr><td><label>Obor: </label> {{p.obor}}</td></tr>
<tr><td><label>Obtížnost: </label> {{p.obtiznost}}</td> </tr>
{% if p.klicova %}<tr><td><label>Klíčová slova: </label> {{p.klicova}}</td></tr>{% endif%}
<tr><td>Hodnocení:
<INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="-1" {% if h == -1 %} CHECKED="checked" {% endif %} > rozhodně nechci
<INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="0" {% if h == 0 %} CHECKED="checked" {% endif %}> je mi to jedno
<INPUT TYPE="radio" NAME="q{{p.pk}}" VALUE="1" {% if h == 1 %} CHECKED="checked" {% endif %}> rozhodně chci
</td></tr>
<tr><td>&nbsp;</td></tr>
{% empty %}
Nejsou žádné přednášky o kterých by šlo hlasovat.
{% endfor %}
<tr><td><input name="odeslat" type="submit" value="Odeslat"></td><tr>
</table>
<h3>Jak moc by ses chtěl(a) zúčastnit následujících přednášek?</h3>
<p>Obtížnost 1 je nejlehčí, 3 nejtěžší.</p>
{{ form_set_prednasky.management_form }}
{% for f, p in formy_a_prednasky %}
<h4>{{p.nazev}} ({{p.org}})</h4>
<p class="textprednasky">{{p.anotace}}</p>
<label>Obor: </label> {{p.obor}}<br>
<label>Obtížnost: </label> {{p.obtiznost}}<br>
{% if p.klicova %}<label>Klíčová slova: </label> {{p.klicova}}<br>{% endif%}
<br>
{{ f }}
<br>
{% 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 %}
{% if forloop.first %}<hr/><h3>Jak moc znáš následující?</h3>{% endif %}
<h4>{{z.nazev}}</h4>
<p class="textznalosti">{{z.text}}</p>
{{ f }}
<br>
{% endfor %}
<input type="submit" value="Odeslat"/>
</form>
{% endblock %}

View file

@ -14,7 +14,7 @@
{% else %}
<a href="/prednasky/seznam_prednasek/{{seznam.id}}">Seznam přednášek na soustředění {{seznam.soustredeni.misto}} </a>
{% endif %}
<a href="/prednasky/seznam_prednasek/{{seznam.id}}/export">Export</a>
<a href="/prednasky/seznam_prednasek/{{seznam.id}}/hlasovani.csv">Export</a>
</li>
{% endfor %}
</ul>

View file

@ -12,10 +12,15 @@ 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>/export',
org_required(views.SeznamExportView),
name='seznam-export'
'prednasky/seznam_prednasek/<int:seznam>/hlasovani.csv',
org_required(views.PrednaskyExportView),
name='seznam-export-csv'
),
path(
'prednasky/seznam_prednasek/<int:seznam>/',

View file

@ -1,77 +1,142 @@
import csv
import http
import logging
from django.http import HttpResponse, HttpRequest
from django.shortcuts import render, get_object_or_404
from django.views import generic
from django.shortcuts import HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Sum
from django.forms import Form
from django.db import transaction
from various.views.pomocne import formularOKView
from .forms import HlasovaniPrednaskaFormSet, HlasovaniZnalostiFormSet
from various.models import Nastaveni
from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH
from prednasky.models import Prednaska, Hlasovani, Znalost, HlasovaniOZnalostech, Seznam
from soustredeni.models import Soustredeni
from personalni.models import Osoba
def newPrednaska(request):
PREDNASKY_PREFIX = "prednasky"
ZNALOSTI_PREFIX = "znalosti"
logger = logging.getLogger(__name__)
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
sous = Nastaveni.get_solo().aktualni_sous
seznam = Seznam.objects.filter(soustredeni = sous, stav = STAV_NAVRH).first()
seznam = Seznam.objects.filter(soustredeni = sous, stav=Seznam.Stav.NAVRH).first()
if sous is None or seznam is None:
return render(request, 'universal.html', {
'title': "Nelze hlasovat",
'text': "Není žádný seznam přednášek, o kterém by se dalo hlasovat.",
}, status=http.HTTPStatus.NOT_FOUND)
osoba = Osoba.objects.filter(user=request.user).first()
ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id)
# obsluha formulare
if request.method == 'POST':
form = Form(request.POST, request.FILES)
if form.is_valid():
# id z důvodu duplicitních jmen (přechod z jména na objekt Osoby nějak kape na tom,
# že všechna předchozí hlasování zde mají náhodný string…)
# TODO Změnit to na Osobu
ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) # id, kvůli kolizi jmen
# TODO v následujících řádcích je zbytečně mnoho dotazů na QuerySet (pokud účastník hlasoval, hlasoval u všech)
for i in request.POST:
if i[0] == 'q':
prednaska = Prednaska.objects.filter(pk=int(i[1:]))[0]
hlasovani = Hlasovani.objects.filter(ucastnik=ucastnik, prednaska=prednaska).first()
if not hlasovani:
hlasovani = Hlasovani()
hlasovani.prednaska = prednaska
hlasovani.ucastnik = ucastnik
hlasovani.seznam = seznam
hlasovani.body = int(request.POST[i])
hlasovani.save()
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_znalosti = HlasovaniZnalostiFormSet(request.POST, prefix=ZNALOSTI_PREFIX)
if form_set_prednasky.is_valid() and form_set_znalosti.is_valid():
with transaction.atomic():
# Místo updatování data prostě smažeme a vytvoříme nová
seznam.hlasovani_set.filter(ucastnik=ucastnik).delete()
seznam.hlasovanioznalostech_set.filter(ucastnik=osoba).delete()
for form in form_set_prednasky:
prednaska_id = form.cleaned_data['prednaska_id']
prednaska = Prednaska.objects.filter(id=prednaska_id).first()
if prednaska is None:
logger.error(f"Účastník {ucastnik} hodnotil neexistující přednášku {prednaska_id} číslem {form.cleaned_data['body']}")
continue
Hlasovani.objects.create(
prednaska=prednaska,
body=form.cleaned_data['body'],
ucastnik=ucastnik,
seznam=seznam,
)
for form in form_set_znalosti:
znalost_id = form.cleaned_data['znalost_id']
znalost = Znalost.objects.filter(id=znalost_id).first()
if znalost is None:
logger.error(f"Účastník {ucastnik} hodnotil neexistující znalost {znalost_id} číslem {form.cleaned_data['odpoved']}")
continue
HlasovaniOZnalostech.objects.create(
odpoved=form.cleaned_data['odpoved'],
znalost=znalost,
ucastnik=osoba,
seznam=seznam,
)
# presmerovani na prave vzniklou galerii
return HttpResponseRedirect('./hotovo')
def prednaska_hodnoceni(prednaska):
h = Hlasovani.objects.filter(ucastnik=ucastnik, prednaska=prednaska).first()
if h:
return prednaska, h.body
else:
return prednaska, 0
else: # Pokud je nějaký formset nevalidní, vracíme je k přepracování
prednasky = seznam.prednaska_set.all()
znalosti = seznam.znalost_set.all()
# 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: # 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: Prednaska) -> Hlasovani.Body:
hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first()
return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO
def odpoved_znalosti(z: Znalost) -> Znalost.Odpoved:
hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first()
return hlasovani.odpoved if hlasovani else Znalost.Odpoved.CIRCA
prednasky = seznam.prednaska_set.all()
znalosti = seznam.znalost_set.all()
form_set_prednasky = HlasovaniPrednaskaFormSet(initial=[
{"prednaska_id": p.id, "body": odpoved_prednasky(p)} for p in prednasky
], prefix=PREDNASKY_PREFIX)
form_set_znalosti = HlasovaniZnalostiFormSet(initial=[
{"znalost_id": z.id, "odpoved": odpoved_znalosti(z)} for z in znalosti
], prefix=ZNALOSTI_PREFIX)
# V případě nePOSTu nebo chyby při ukládání vracíme hlasování
return render(
request,
'prednasky/base.html',
{'prednasky': map(prednaska_hodnoceni, seznam.prednaska_set.all())}
{
'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),
}
)
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í.")
class MetaSeznamListView(generic.ListView):
""" Seznam všech :py:class:`Seznamů <prednasky.models.Seznam>` s odkazy na exporty """
model = Seznam
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):
@ -87,7 +152,7 @@ class SeznamListView(generic.ListView):
# hlasovani se vztahuje k nejnovejsimu soustredeni
sous = Soustredeni.objects.first()
seznam = Seznam.objects.filter(soustredeni = sous, stav = STAV_NAVRH).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')
@ -96,32 +161,86 @@ class SeznamListView(generic.ListView):
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)
# 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"
# )
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("?")
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_o_znalostech = HlasovaniOZnalostech.objects.filter(seznam=seznam).select_related('ucastnik', 'znalost')
# Inicializujeme sloupce
prednasky = list(Prednaska.objects.filter(seznamy=seznam))
znalosti = list(Znalost.objects.filter(seznamy=seznam))
prednasky_map: dict[int, int] = {p.id: i for i, p in enumerate(prednasky, 1)}
offset = len(prednasky_map)
znalosti_map: dict[int, int] = {z.id: i for i, z in enumerate(znalosti, offset + 1)}
width = offset + len(znalosti_map)
# A po inicializaci sloupců vyplníme tabulku
table: [str, list[str|Prednaska|Znalost,]] = {}
for h in hlasovani:
h.ucastnik = hash(h.ucastnik)
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)
return render(
request,
'prednasky/seznam_prednasek_export.txt',
{"hlasovani": hlasovani, "prednasky": prednasky, "orgove": orgove},
content_type="text/plain"
)
if h.prednaska.id in prednasky_map:
table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body
else:
pass # TODO Padat hlasitě?
for h in hlasovani_o_znalostech:
ucastnik = str(h.ucastnik) + ' ' + str(h.ucastnik.id) # id, kvůli kolizi jmen
if ucastnik not in table: # Pokud jsme účastníka ještě neviděli, předgenerujeme si jeho řádek
table[ucastnik] = [ucastnik] + ([""] * width)
if h.znalost.id in znalosti_map:
table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved
else:
pass # TODO Padat hlasitě?
response = HttpResponse(content_type="text/csv", charset="utf-8")
response["Content-Disposition"] = 'attachment; filename="hlasovani.csv"'
writer = csv.writer(response)
writer.writerow(["jména \\ přednáška|znalost"] + list(map(str, prednasky + znalosti)))
for row in table.values():
writer.writerow(list(map(str, row)))
return response