From 6ea212cdf8159e42600dc04fc38204500fb23bed Mon Sep 17 00:00:00 2001 From: Karel Balej Date: Tue, 3 Dec 2024 20:01:19 +0100 Subject: [PATCH 01/37] =?UTF-8?q?odevzdavatko:=20odes=C3=ADl=C3=A1n=C3=AD?= =?UTF-8?q?=20emailu=20=C5=99e=C5=A1iteli=20p=C5=99i=20zm=C4=9Bn=C4=9B=20z?= =?UTF-8?q?p=C4=9Btn=C3=A9=20vazby?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Toto se rozbíjí, když dojde ke smazání hodnocení v pořadí dříve, než nějaké hodnocení s neprázdnou zpětnou vazbou, neboť řádky formsetu jsou přečíslovány a pak špatně spárovány s původními hodnotami, takže se nesprávně detekuje změna. --- odevzdavatko/models.py | 3 ++ .../templates/odevzdavatko/detail.html | 2 +- odevzdavatko/views.py | 48 +++++++++++++------ personalni/forms.py | 2 + .../migrations/0018_resitel_upozorneni.py | 18 +++++++ personalni/models.py | 2 + .../templates/personalni/udaje/udaje.html | 1 + personalni/views.py | 1 + 8 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 personalni/migrations/0018_resitel_upozorneni.py diff --git a/odevzdavatko/models.py b/odevzdavatko/models.py index 3b510fe7..6666fcb1 100644 --- a/odevzdavatko/models.py +++ b/odevzdavatko/models.py @@ -65,6 +65,9 @@ class Reseni(SeminarModelBase): def absolute_url(self): return "https://" + str(get_current_site(None)) + self.verejne_url() + def resitel_url(self): + return f'https://{get_current_site(None)}{reverse_lazy("odevzdavatko_resitel_reseni", args=[self.id])}' + # má OneToOneField s: # Konfera diff --git a/odevzdavatko/templates/odevzdavatko/detail.html b/odevzdavatko/templates/odevzdavatko/detail.html index 616ec8e0..4d2b780a 100644 --- a/odevzdavatko/templates/odevzdavatko/detail.html +++ b/odevzdavatko/templates/odevzdavatko/detail.html @@ -191,7 +191,7 @@ Sloupce:
  • Pokud nemáš důvod, deadline neměň. Sloupeček s deadlinem znamená, do kterého deadlinu se započítají body (nemusí se shodovat s deadlinem řešení).
  • -
  • Poslední sloupec je na zpětnou vazbu řešiteli, tedy (na rozdíl od Neveřejné poznámky, která je určena pro synchronizaci orgů) ji uvidí řešitelé. Zatím jen pasivně (nechodí e-mail). Pohled řešitele si můžete prohlédnout zde. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.
  • +
  • Poslední sloupec je na zpětnou vazbu řešiteli, tedy (na rozdíl od Neveřejné poznámky, která je určena pro synchronizaci orgů) ji uvidí řešitelé. Změníte-li u nějakého hodnocení toto políčko, řešitel bude upozorněn emailem, pokud si tuto možnost nevypl ve svém profilu. Pohled řešitele si můžete prohlédnout zde. Pokud chcete z nějakého důvodu napsat řešitelům e-mail, klikněte na „Poslat mail všem řešitelům“.
  • Další poznámky diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py index 2a213a2c..b5c3d54d 100644 --- a/odevzdavatko/views.py +++ b/odevzdavatko/views.py @@ -222,6 +222,17 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi ctx["problem_id"] = self.kwargs['problem'] return ctx +HODNOCENI_INITIAL_DATA = [ + "problem", + "body", + "body_celkem", + "body_neprepocitane", + "body_neprepocitane_celkem", + "body_max", + "body_neprepocitane_max", + "deadline_body", + "feedback", +] ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex class DetailReseniView(DetailView): """ Náhled na řešení. Editace je v :py:class:`EditReseniView`. """ @@ -232,18 +243,7 @@ class DetailReseniView(DetailView): self.reseni = get_object_or_404(Reseni, id=self.kwargs['pk']) result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet for hodn in Hodnoceni.objects.filter(reseni=self.reseni): - seznam_atributu = [ - "problem", - "body", - "body_celkem", - "body_neprepocitane", - "body_neprepocitane_celkem", - "body_max", - "body_neprepocitane_max", - "deadline_body", - "feedback", - ] - result.append({attr: getattr(hodn, attr) for attr in seznam_atributu}) + result.append({attr: getattr(hodn, attr) for attr in HODNOCENI_INITIAL_DATA}) return result def get_context_data(self, **kw): @@ -291,9 +291,9 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): reseni = get_object_or_404(Reseni, pk=pk) success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) - # FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově - # Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm - formset = f.OhodnoceniReseniFormSet(request.POST) + formset = f.OhodnoceniReseniFormSet(request.POST, initial=[ + {k: getattr(h, k) for k in HODNOCENI_INITIAL_DATA} for h in Hodnoceni.objects.filter(reseni=reseni) + ]) poznamka_form = f.PoznamkaReseniForm(request.POST, instance=reseni) # TODO: Napsat validaci formuláře a formsetu if not (formset.is_valid() and poznamka_form.is_valid()): @@ -309,7 +309,9 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): qs.delete() # Vyrobíme nová podle formsetu + notifikace = False for form in formset: + notifikace |= 'feedback' in form.changed_data data_for_hodnoceni = form.cleaned_data data_for_body = data_for_hodnoceni.copy() del(data_for_hodnoceni["body_celkem"]) @@ -330,6 +332,22 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): hodnoceni.body = -0.1 hodnoceni.save() + adresati = reseni.resitele.filter(upozorneni=True).values_list('osoba__email', flat=True) + if notifikace and adresati: + email = EmailMessage( + subject='Změna hodnocení odevzdaného řešení', + body=f"""Milá řešitelko, milý řešiteli, + +došlo ke změně zpětné vazby k Tebou odevzdanému řešení. Zobrazit si ji můžeš na {reseni.resitel_url()}. + +Tvoji organizátoři M&M +--- +Nechceš-li tato upozornění dostávat, můžeš si to nastavit ve svém profilu.""", + from_email='odevzdavatko@mam.mff.cuni.cz', + bcc=adresati, + ) + email.send() + return redirect(success_url) diff --git a/personalni/forms.py b/personalni/forms.py index ae08a8c9..2f6c79c8 100644 --- a/personalni/forms.py +++ b/personalni/forms.py @@ -71,6 +71,8 @@ class UdajeForm(forms.Form): zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False, initial=True) spam = forms.BooleanField(label='Souhlasím se zasíláním propagačních materiálů od MFF UK', required=False) + upozorneni = forms.BooleanField(label='Chci dostávat emailová upozornění na změnu zpětné vazby k mým řešením', required=False, initial=True) + def clean_prezdivka_resitele(self): prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele') if prezdivka_resitele == '': diff --git a/personalni/migrations/0018_resitel_upozorneni.py b/personalni/migrations/0018_resitel_upozorneni.py new file mode 100644 index 00000000..64aede5f --- /dev/null +++ b/personalni/migrations/0018_resitel_upozorneni.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-12-03 19:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('personalni', '0017_odstrel_treenode_post'), + ] + + operations = [ + migrations.AddField( + model_name='resitel', + name='upozorneni', + field=models.BooleanField(default=True, verbose_name='zasílat upozornění na změnu zpětné vazby k řešení emailem'), + ), + ] diff --git a/personalni/models.py b/personalni/models.py index 636b132e..930716a7 100644 --- a/personalni/models.py +++ b/personalni/models.py @@ -250,6 +250,8 @@ class Resitel(SeminarModelBase): poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k řešiteli (plain text)') + upozorneni = models.BooleanField('zasílat upozornění na změnu zpětné vazby k řešení emailem', default=True) + def export_row(self): "Slovnik pro pouziti v AESOP exportu" diff --git a/personalni/templates/personalni/udaje/udaje.html b/personalni/templates/personalni/udaje/udaje.html index 894ddaf9..5ed30b74 100644 --- a/personalni/templates/personalni/udaje/udaje.html +++ b/personalni/templates/personalni/udaje/udaje.html @@ -51,6 +51,7 @@ {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} + {% include "personalni/udaje/prihlaska_field.html" with field=form.upozorneni %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_papirove %} {% include "personalni/udaje/prihlaska_field.html" with field=form.spam %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} diff --git a/personalni/views.py b/personalni/views.py index 7c95325c..38f9bc57 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -230,6 +230,7 @@ def resitelEditView(request): resitel_edit.zasilat = fcd['zasilat'] resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove'] + resitel_edit.upozorneni = fcd['upozorneni'] if fcd.get('skola'): resitel_edit.skola = fcd['skola'] else: From 1bee36d9b6a1d9c6eccbeb2a824e03cd2628bd8b Mon Sep 17 00:00:00 2001 From: Karel Balej Date: Tue, 14 Jan 2025 21:00:33 +0100 Subject: [PATCH 02/37] =?UTF-8?q?personalni:=20p=C5=99ejmenov=C3=A1n=C3=AD?= =?UTF-8?q?=20sloupce=20pro=20upozorn=C4=9Bn=C3=AD=20na=20zp=C4=9Btnou=20v?= =?UTF-8?q?azbu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- odevzdavatko/views.py | 2 +- personalni/forms.py | 2 +- ...eni_resitel_upozornovat_na_opravy_reseni.py | 18 ++++++++++++++++++ personalni/models.py | 2 +- .../templates/personalni/udaje/udaje.html | 2 +- personalni/views.py | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 personalni/migrations/0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni.py diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py index b5c3d54d..255f48a9 100644 --- a/odevzdavatko/views.py +++ b/odevzdavatko/views.py @@ -332,7 +332,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): hodnoceni.body = -0.1 hodnoceni.save() - adresati = reseni.resitele.filter(upozorneni=True).values_list('osoba__email', flat=True) + adresati = reseni.resitele.filter(upozornovat_na_opravy_reseni=True).values_list('osoba__email', flat=True) if notifikace and adresati: email = EmailMessage( subject='Změna hodnocení odevzdaného řešení', diff --git a/personalni/forms.py b/personalni/forms.py index 2f6c79c8..39e1b6ab 100644 --- a/personalni/forms.py +++ b/personalni/forms.py @@ -71,7 +71,7 @@ class UdajeForm(forms.Form): zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False, initial=True) spam = forms.BooleanField(label='Souhlasím se zasíláním propagačních materiálů od MFF UK', required=False) - upozorneni = forms.BooleanField(label='Chci dostávat emailová upozornění na změnu zpětné vazby k mým řešením', required=False, initial=True) + upozornovat_na_opravy_reseni = forms.BooleanField(label='Chci dostávat emailová upozornění na změnu zpětné vazby k mým řešením', required=False, initial=True) def clean_prezdivka_resitele(self): prezdivka_resitele = self.cleaned_data.get('prezdivka_resitele') diff --git a/personalni/migrations/0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni.py b/personalni/migrations/0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni.py new file mode 100644 index 00000000..e5a4caaf --- /dev/null +++ b/personalni/migrations/0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.18 on 2025-01-14 19:48 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('personalni', '0018_resitel_upozorneni'), + ] + + operations = [ + migrations.RenameField( + model_name='resitel', + old_name='upozorneni', + new_name='upozornovat_na_opravy_reseni', + ), + ] diff --git a/personalni/models.py b/personalni/models.py index 930716a7..e04aca0b 100644 --- a/personalni/models.py +++ b/personalni/models.py @@ -250,7 +250,7 @@ class Resitel(SeminarModelBase): poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k řešiteli (plain text)') - upozorneni = models.BooleanField('zasílat upozornění na změnu zpětné vazby k řešení emailem', default=True) + upozornovat_na_opravy_reseni = models.BooleanField('zasílat upozornění na změnu zpětné vazby k řešení emailem', default=True) def export_row(self): diff --git a/personalni/templates/personalni/udaje/udaje.html b/personalni/templates/personalni/udaje/udaje.html index 5ed30b74..83bce161 100644 --- a/personalni/templates/personalni/udaje/udaje.html +++ b/personalni/templates/personalni/udaje/udaje.html @@ -51,7 +51,7 @@
    {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %} - {% include "personalni/udaje/prihlaska_field.html" with field=form.upozorneni %} + {% include "personalni/udaje/prihlaska_field.html" with field=form.upozornovat_na_opravy_reseni %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_papirove %} {% include "personalni/udaje/prihlaska_field.html" with field=form.spam %} {% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %} diff --git a/personalni/views.py b/personalni/views.py index 38f9bc57..c71ce418 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -230,7 +230,7 @@ def resitelEditView(request): resitel_edit.zasilat = fcd['zasilat'] resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] resitel_edit.zasilat_cislo_papirove = fcd['zasilat_cislo_papirove'] - resitel_edit.upozorneni = fcd['upozorneni'] + resitel_edit.upozornovat_na_opravy_reseni = fcd['upozornovat_na_opravy_reseni'] if fcd.get('skola'): resitel_edit.skola = fcd['skola'] else: From 071c66ee1091be11efebebd129916b170d4ea87e Mon Sep 17 00:00:00 2001 From: Karel Balej Date: Wed, 15 Jan 2025 18:14:39 +0100 Subject: [PATCH 03/37] =?UTF-8?q?personalni:=20zm=C4=9Bna=20nastaven=C3=AD?= =?UTF-8?q?=20upozor=C5=88ov=C3=A1n=C3=AD=20u=20existuj=C3=ADc=C3=ADch=20?= =?UTF-8?q?=C5=99e=C5=A1itel=C5=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- personalni/migrations/0018_resitel_upozorneni.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/personalni/migrations/0018_resitel_upozorneni.py b/personalni/migrations/0018_resitel_upozorneni.py index 64aede5f..1b5b7280 100644 --- a/personalni/migrations/0018_resitel_upozorneni.py +++ b/personalni/migrations/0018_resitel_upozorneni.py @@ -2,6 +2,9 @@ from django.db import migrations, models +def vypnuti_upozorneni_na_opravy_reseni(apps, schema_editor): + Resitel = apps.get_model('personalni', 'Resitel') + Resitel.objects.update(upozorneni=False) class Migration(migrations.Migration): @@ -15,4 +18,5 @@ class Migration(migrations.Migration): name='upozorneni', field=models.BooleanField(default=True, verbose_name='zasílat upozornění na změnu zpětné vazby k řešení emailem'), ), + migrations.RunPython(vypnuti_upozorneni_na_opravy_reseni), ] From 0724030befd9134bdd2cd5e7e45c258df52bfabc Mon Sep 17 00:00:00 2001 From: ticvac Date: Tue, 21 Jan 2025 20:39:31 +0100 Subject: [PATCH 04/37] nazev branche splnen --- .../static/personalni/jak_se_dozvedeli.css | 34 +++++++++++++++++++ .../personalni/jak_se_dozvedeli.html | 28 +++++++++++++++ personalni/urls.py | 7 ++++ personalni/views.py | 7 +++- 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 personalni/static/personalni/jak_se_dozvedeli.css create mode 100644 personalni/templates/personalni/jak_se_dozvedeli.html diff --git a/personalni/static/personalni/jak_se_dozvedeli.css b/personalni/static/personalni/jak_se_dozvedeli.css new file mode 100644 index 00000000..15a47b80 --- /dev/null +++ b/personalni/static/personalni/jak_se_dozvedeli.css @@ -0,0 +1,34 @@ +.seznam { + display: flex; + flex-direction: column; + gap: 0.3em; +} + +.hint { + border: 1px solid #ccc; + padding: 0.3em 1em; + border-radius: 5px; + margin-bottom: 1em; +} + +.osoba { + display: flex; + justify-content: space-between; + gap: 0.5em; + + .uno { + flex: 2; + } + + .dos { + flex: 2; + } + + .tres { + flex: 1; + } + + .grey { + opacity: 0.5; + } +} diff --git a/personalni/templates/personalni/jak_se_dozvedeli.html b/personalni/templates/personalni/jak_se_dozvedeli.html new file mode 100644 index 00000000..6695f94c --- /dev/null +++ b/personalni/templates/personalni/jak_se_dozvedeli.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} + +{% block custom_css %} +{% load static %} + +{% endblock %} + + + +{% block content %} +
    +
    +
    Jméno
    +
    Jak se dozvěděli
    +
    Datum registrace
    +
    + + {% for osoba in object_list %} +
    +
    {{ osoba.jmeno }} {{ osoba.prijmeni }}
    +
    {% if osoba.jak_se_dozvedeli %} {{osoba.jak_se_dozvedeli}} {% else %} NEZADÁNO {% endif %}
    +
    {{ osoba.datum_registrace }}
    +
    + {% endfor %} +
    +{% endblock%} + + diff --git a/personalni/urls.py b/personalni/urls.py index c4820b90..1805bbfe 100644 --- a/personalni/urls.py +++ b/personalni/urls.py @@ -33,4 +33,11 @@ urlpatterns = [ name='stari_organizatori' ), + # Zpřístupnění dat z "jak jste se o nás dozvěděli" pro orgy propagace + path( + 'org/propagace/jak-se-dozvedeli/', + org_required(views.JakSeDozvedeliView.as_view()), + name='jak_se_dozvedeli' + ) + ] diff --git a/personalni/views.py b/personalni/views.py index c71ce418..49442c2d 100644 --- a/personalni/views.py +++ b/personalni/views.py @@ -34,7 +34,7 @@ from various.autentizace.utils import posli_reset_hesla from django.forms.models import model_to_dict -from .models import Organizator +from .models import Organizator, Osoba def aktivniOrganizatori(datum=timezone.now()): @@ -62,6 +62,11 @@ class CojemamOrganizatoriStariView(generic.ListView): id__in=aktivniOrganizatori() ).order_by('-organizuje_do') +class JakSeDozvedeliView(generic.ListView): + model = Osoba + template_name = 'personalni/jak_se_dozvedeli.html' + queryset = Osoba.objects.order_by('-datum_registrace') + def obalkyView(request, resitele): if len(resitele) == 0: From a84df1909bbf3eeefb067497a044178e36104a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 21 Jan 2025 21:14:19 +0100 Subject: [PATCH 05/37] =?UTF-8?q?Lep=C5=A1=C3=AD=20hl=C3=A1=C5=A1ka=20po?= =?UTF-8?q?=20odesl=C3=A1n=C3=AD=20p=C5=99edn=C3=A1=C5=A1ek.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/templates/prednasky/hotovo.html | 11 ----------- prednasky/views.py | 4 +++- 2 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 prednasky/templates/prednasky/hotovo.html diff --git a/prednasky/templates/prednasky/hotovo.html b/prednasky/templates/prednasky/hotovo.html deleted file mode 100644 index f77ea6e1..00000000 --- a/prednasky/templates/prednasky/hotovo.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'base.html' %} - -{% load humanize %} -{% load static %} - - -{% block content %} - -

    Děkujeme.

    - -{% endblock %} diff --git a/prednasky/views.py b/prednasky/views.py index f0694dc5..52302262 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -5,6 +5,8 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models import Sum from django.forms import Form +from various.views.pomocne import formularOKView + from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH from soustredeni.models import Soustredeni from personalni.models import Osoba @@ -54,7 +56,7 @@ def newPrednaska(request): def Prednaska_hotovo(request): - return render(request, 'prednasky/hotovo.html') + 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): model = Seznam From 1a2bef328b9735c61681e8efd643c3221d9ddc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 21 Jan 2025 21:43:43 +0100 Subject: [PATCH 06/37] =?UTF-8?q?Inteligentn=C4=9Bj=C5=A1=C3=AD=20p=C5=99i?= =?UTF-8?q?=C5=99azov=C3=A1n=C3=AD=20seznamu=20p=C5=99edn=C3=A1=C5=A1ek=20?= =?UTF-8?q?hlasov=C3=A1tku=20a=20upozorn=C4=9Bn=C3=AD=20na=20neexistuj?= =?UTF-8?q?=C3=ADc=C3=AD=20seznam?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/views.py | 10 +++++++++- .../0007_nastaveni_aktualni_sous.py | 20 +++++++++++++++++++ various/models.py | 5 +++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 various/migrations/0007_nastaveni_aktualni_sous.py diff --git a/prednasky/views.py b/prednasky/views.py index 52302262..a2a2594c 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -1,3 +1,5 @@ +import http + from django.shortcuts import render, get_object_or_404 from django.views import generic from django.shortcuts import HttpResponseRedirect @@ -7,14 +9,20 @@ from django.forms import Form from various.views.pomocne import formularOKView +from various.models import Nastaveni from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH from soustredeni.models import Soustredeni from personalni.models import Osoba def newPrednaska(request): # hlasovani se vztahuje k nejnovejsimu soustredeni - sous = Soustredeni.objects.first() + sous = Nastaveni.get_solo().aktualni_sous seznam = Seznam.objects.filter(soustredeni = sous, stav = 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 diff --git a/various/migrations/0007_nastaveni_aktualni_sous.py b/various/migrations/0007_nastaveni_aktualni_sous.py new file mode 100644 index 00000000..718aabd4 --- /dev/null +++ b/various/migrations/0007_nastaveni_aktualni_sous.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.16 on 2025-01-21 20:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('soustredeni', '0013_alter_soustredeni_kontaktnicek_pdf_and_more'), + ('various', '0006_tvorba_post'), + ] + + operations = [ + migrations.AddField( + model_name='nastaveni', + name='aktualni_sous', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.soustredeni', verbose_name='Aktuálně připravovaný sous'), + ), + ] diff --git a/various/models.py b/various/models.py index 363c3137..b662b029 100644 --- a/various/models.py +++ b/various/models.py @@ -26,6 +26,11 @@ class Nastaveni(SingletonModel): verbose_name="Účastnický poplatek za soustředění", default=1000) + aktualni_sous = models.ForeignKey( + "soustredeni.Soustredeni", verbose_name='Aktuálně připravovaný sous', + null=True, blank=False, on_delete=models.PROTECT, + ) + @property def aktualni_rocnik(self): return self.aktualni_cislo.rocnik From aa997bfcd8111d092dd062e4b4a208ba352ac231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 21 Jan 2025 21:51:30 +0100 Subject: [PATCH 07/37] =?UTF-8?q?Aktu=C3=A1ln=C3=AD=20sous=20jsme=20cht?= =?UTF-8?q?=C4=9Bli=20blank=3DTrue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- various/migrations/0007_nastaveni_aktualni_sous.py | 2 +- various/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/various/migrations/0007_nastaveni_aktualni_sous.py b/various/migrations/0007_nastaveni_aktualni_sous.py index 718aabd4..b0013fe0 100644 --- a/various/migrations/0007_nastaveni_aktualni_sous.py +++ b/various/migrations/0007_nastaveni_aktualni_sous.py @@ -15,6 +15,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='nastaveni', name='aktualni_sous', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.soustredeni', verbose_name='Aktuálně připravovaný sous'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.soustredeni', verbose_name='Aktuálně připravovaný sous'), ), ] diff --git a/various/models.py b/various/models.py index b662b029..9f46d5c6 100644 --- a/various/models.py +++ b/various/models.py @@ -28,7 +28,7 @@ class Nastaveni(SingletonModel): aktualni_sous = models.ForeignKey( "soustredeni.Soustredeni", verbose_name='Aktuálně připravovaný sous', - null=True, blank=False, on_delete=models.PROTECT, + null=True, blank=True, on_delete=models.PROTECT, ) @property From 422caadb9ece5691b17449e9f77552efd333a08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 21 Jan 2025 21:58:42 +0100 Subject: [PATCH 08/37] =?UTF-8?q?Smaz=C3=A1n=C3=AD=20nadbyte=C4=8Dn=C3=A9?= =?UTF-8?q?=20vazebn=C3=A9=20tabulky=20(vazba=20bude=20zase=20viditeln?= =?UTF-8?q?=C3=A1=20v=20adminu)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...008_alter_problem_opravovatele_and_more.py | 27 +++++++++++++++++++ tvorba/models.py | 16 +---------- 2 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 tvorba/migrations/0008_alter_problem_opravovatele_and_more.py diff --git a/tvorba/migrations/0008_alter_problem_opravovatele_and_more.py b/tvorba/migrations/0008_alter_problem_opravovatele_and_more.py new file mode 100644 index 00000000..b0a587ba --- /dev/null +++ b/tvorba/migrations/0008_alter_problem_opravovatele_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.16 on 2025-01-21 20:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'), + ('tvorba', '0007_alter_deadline_typ'), + ] + + operations = [ + migrations.SeparateDatabaseAndState( + database_operations=[], + state_operations=[ + migrations.AlterField( + model_name='problem', + name='opravovatele', + field=models.ManyToManyField(blank=True, db_table='seminar_problemy_opravovatele', related_name='opravovatele_%(class)s', to='personalni.organizator', verbose_name='opravovatelé'), + ), + migrations.DeleteModel( + name='Problemy_Opravovatele', + ), + ] + ), + ] diff --git a/tvorba/models.py b/tvorba/models.py index 36f34312..d9540ba1 100644 --- a/tvorba/models.py +++ b/tvorba/models.py @@ -393,20 +393,6 @@ class ZmrazenaVysledkovka(SeminarModelBase): html = models.TextField(null=False, blank=False) -class Problemy_Opravovatele(SeminarModelBase): - """Jen vazebná tabulka pro opravovatele. - - Ona stejně existovala, při přesunu mezi aplikacemi jen potřebujeme zajistit nepřejmenování DB tabulky. - Proto taky nepotřebuje žádná specifika, ze :py:class:SeminarModelBase: dědí ze zvyku než že by to k něčemu kdy měo být. - """ - class Meta: - db_table = 'seminar_problemy_opravovatele' - - id = models.AutoField(primary_key = True) - - problem = models.ForeignKey('Problem', on_delete=models.CASCADE) - organizator = models.ForeignKey(Organizator, on_delete=models.CASCADE) - @reversion.register(ignore_duplicates=True) # Pozor na následující řádek. *Nekrmit, asi kouše!* class Problem(SeminarModelBase,PolymorphicModel): @@ -462,7 +448,7 @@ class Problem(SeminarModelBase,PolymorphicModel): on_delete=models.SET_NULL) opravovatele = models.ManyToManyField(Organizator, verbose_name='opravovatelé', - blank=True, related_name='opravovatele_%(class)s', through=Problemy_Opravovatele) + blank=True, related_name='opravovatele_%(class)s', db_table='seminar_problemy_opravovatele') kod = models.CharField('lokální kód', max_length=32, blank=True, default='', help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku') From 41032be9eb8f990c2854443ff0d4c02ba2f3a522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 21 Jan 2025 21:59:29 +0100 Subject: [PATCH 09/37] =?UTF-8?q?=C5=BD=C3=A1dn=C3=A9=20vzpom=C3=ADnky=20n?= =?UTF-8?q?a=20seminar.models!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/vsechno.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mamweb/vsechno.py b/mamweb/vsechno.py index efb5e48c..9130ebc2 100644 --- a/mamweb/vsechno.py +++ b/mamweb/vsechno.py @@ -1,6 +1,6 @@ # Tento soubor slouží pouze pro shell a podobné. Nikde neimportovat v kódu! -print("Naimportoval jsi `seminar.models`. Pevně věřím, že to nebylo nikde v kódu. Díky.") +print("Naimportoval jsi `mamweb.vsechno`. Pevně věřím, že to nebylo nikde v kódu. Díky.") from galerie.models import * from header_fotky.models import * From 4906f8236558735c27b4767bb2646013ee344579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 21 Jan 2025 22:05:04 +0100 Subject: [PATCH 10/37] =?UTF-8?q?Inteligentn=C3=AD=20hl=C3=A1=C5=A1ka,=20p?= =?UTF-8?q?okud=20nejsou=20=C5=BE=C3=A1dn=C3=A9=20p=C5=99edn=C3=A1=C5=A1ky?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/templates/prednasky/base.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prednasky/templates/prednasky/base.html b/prednasky/templates/prednasky/base.html index a915a04a..ce201cc6 100644 --- a/prednasky/templates/prednasky/base.html +++ b/prednasky/templates/prednasky/base.html @@ -30,6 +30,8 @@ Jak moc by ses chtěl(a) zúčastnit následujících přednášek? rozhodně chci
    + {% empty %} + Nejsou žádné přednášky o kterých by šlo hlasovat. {% endfor %}
     
    From f61533df0a076f0c458c98299101542bbb62e811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 15:13:37 +0100 Subject: [PATCH 11/37] =?UTF-8?q?P=C5=99id=C3=A1n=C3=AD=20Znalosti=20do=20?= =?UTF-8?q?modelu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/admin.py | 11 +++++- .../0019_znalost_hlasovanioznalostech.py | 39 +++++++++++++++++++ prednasky/models.py | 29 +++++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 prednasky/migrations/0019_znalost_hlasovanioznalostech.py diff --git a/prednasky/admin.py b/prednasky/admin.py index 19eace7f..d4bb925c 100644 --- a/prednasky/admin.py +++ b/prednasky/admin.py @@ -4,7 +4,7 @@ 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, STAV_NAVRH, Znalost from soustredeni.models import Soustredeni @@ -64,7 +64,7 @@ admin.site.register(Seznam, SeznamAdmin) class PrednaskaAdmin(VersionAdmin): list_display = ['nazev', 'org', 'obor'] list_filter = ['org', 'obor'] - search_fields = [] + search_fields = ['nazev'] filter_horizontal = ('seznamy', ) actions = ['move_to_soustredeni'] @@ -97,3 +97,10 @@ class PrednaskaAdmin(VersionAdmin): admin.site.register(Prednaska, PrednaskaAdmin) + + +class ZnalostAdmin(PrednaskaAdmin): # Trochu hack, ať nemusím vypisovat všechno znovu + list_display = [] + list_filter = [] + +admin.site.register(Znalost, ZnalostAdmin) diff --git a/prednasky/migrations/0019_znalost_hlasovanioznalostech.py b/prednasky/migrations/0019_znalost_hlasovanioznalostech.py new file mode 100644 index 00000000..916302c8 --- /dev/null +++ b/prednasky/migrations/0019_znalost_hlasovanioznalostech.py @@ -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')), + ], + ), + ] diff --git a/prednasky/models.py b/prednasky/models.py index 74b37403..da21b48d 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -1,7 +1,7 @@ from django.db import models from soustredeni.models import Soustredeni -from personalni.models import Organizator +from personalni.models import Organizator, Osoba STAV_NAVRH = 1 STAV_BUDE = 2 @@ -63,6 +63,33 @@ class Prednaska(models.Model): return "{} ({})".format(self.nazev, self.org) +class Znalost(models.Model): + 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): + 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) + 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) + + + class Hlasovani(models.Model): class Meta: db_table = 'prednasky_hlasovani' From 2f814956a7d89a8403f440db9a5b9bfd3cce24d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 15:20:00 +0100 Subject: [PATCH 12/37] =?UTF-8?q?Nepou=C5=BE=C3=ADvan=C3=BD=20kus=20k?= =?UTF-8?q?=C3=B3du?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/forms.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/prednasky/forms.py b/prednasky/forms.py index f095a64e..e79048bf 100644 --- a/prednasky/forms.py +++ b/prednasky/forms.py @@ -1,7 +1 @@ from django import forms - -class NewPrednaskyForm(forms.Form): - ucastnik = forms.CharField(label = 'Tvoje jméno', max_length = 100) - - - From e933c6978d93b8ebf4e134accbfdb31724a5c0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 15:36:33 +0100 Subject: [PATCH 13/37] =?UTF-8?q?Choices=20na=20Enum=20(u=20p=C5=99edn?= =?UTF-8?q?=C3=A1=C5=A1ek)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/admin.py | 4 ++-- prednasky/models.py | 45 +++++++++++++++++++-------------------------- prednasky/views.py | 6 +++--- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/prednasky/admin.py b/prednasky/admin.py index d4bb925c..f79c7b0a 100644 --- a/prednasky/admin.py +++ b/prednasky/admin.py @@ -4,7 +4,7 @@ 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, Znalost +from .models import Prednaska, Seznam, Znalost from soustredeni.models import Soustredeni @@ -71,7 +71,7 @@ class PrednaskaAdmin(VersionAdmin): def move_to_soustredeni(self, request, queryset): 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, diff --git a/prednasky/models.py b/prednasky/models.py index da21b48d..5f1e1a76 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -3,15 +3,6 @@ from django.db import models from soustredeni.models import Soustredeni from personalni.models import Organizator, Osoba -STAV_NAVRH = 1 -STAV_BUDE = 2 - - -STAV_CHOICES = ( -(STAV_NAVRH, 'Návrh'), -(STAV_BUDE, 'Bude') -) - class Seznam(models.Model): class Meta: @@ -20,27 +11,18 @@ class Seznam(models.Model): verbose_name_plural = 'Seznamy přednášek' ordering = ['soustredeni', 'stav'] + class Stav(models.IntegerChoices): + 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) + stav = models.IntegerField('Stav', choices=Stav.choices, default=Stav.NAVRH) def __str__(self): return "Seznam {}přednášek na {}".format("návrhů " - if self.stav == STAV_NAVRH else "", self.soustredeni) - - -CHOICES_OBTIZNOST = ( - (1, 'Lehká'), - (2, 'Střední'), - (3, 'Těžká'), - ) - -CHOICES_BODY = ( - (-1, '-1'), - (0, '0'), - (1, '1'), - ) + if self.stav == Seznam.Stav.NAVRH else "", self.soustredeni) class Prednaska(models.Model): class Meta: @@ -49,12 +31,17 @@ class Prednaska(models.Model): verbose_name_plural = 'Přednášky' ordering = ['org', 'nazev'] + 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) + obtiznost = models.IntegerField('Obtížnost', choices=Obtiznost.choices) 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) @@ -96,9 +83,15 @@ class Hlasovani(models.Model): verbose_name = 'Hlasování' verbose_name_plural = 'Hlasování' ordering = ['ucastnik', 'prednaska'] + + class Body(models.IntegerChoices): + 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) + body = models.IntegerField('Body', default = Body.JEDNO, choices = Body.choices) ucastnik = models.CharField('Účastník', max_length = 100) seznam = models.ForeignKey(Seznam,null=True,on_delete=models.SET_NULL) diff --git a/prednasky/views.py b/prednasky/views.py index a2a2594c..dd64047f 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -10,14 +10,14 @@ from django.forms import Form from various.views.pomocne import formularOKView from various.models import Nastaveni -from prednasky.models import Prednaska, Hlasovani, Seznam, STAV_NAVRH +from prednasky.models import Prednaska, Hlasovani, Seznam from soustredeni.models import Soustredeni from personalni.models import Osoba def newPrednaska(request): # 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", @@ -87,7 +87,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') From 6c35a5b6f3a7da3f75e6f7662fa692b82fa97dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 15:39:37 +0100 Subject: [PATCH 14/37] =?UTF-8?q?Uhlazen=C3=AD=20prednasky.models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/models.py | 109 +++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/prednasky/models.py b/prednasky/models.py index 5f1e1a76..f003878f 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -6,58 +6,79 @@ 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'] + db_table = "prednasky_seznam" + verbose_name = "Seznam přednášek" + verbose_name_plural = "Seznamy přednášek" + ordering = ["soustredeni", "stav"] class Stav(models.IntegerChoices): 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) + 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) def __str__(self): - return "Seznam {}přednášek na {}".format("návrhů " - if self.stav == Seznam.Stav.NAVRH else "", 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 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"] 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=Obtiznost.choices) - 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) + 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=Obtiznost.choices) + 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): + class Meta: + db_table = "prednasky_hlasovani" + verbose_name = "Hlasování" + verbose_name_plural = "Hlasování" + ordering = ["ucastnik", "prednaska"] + + class Body(models.IntegerChoices): + 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=Body.JEDNO, choices=Body.choices) + ucastnik = models.CharField("Účastník", max_length=100) + seznam = models.ForeignKey(Seznam, null=True, on_delete=models.SET_NULL) + + def __str__(self): + return f"{self.ucastnik} dal {self.body} bodů {self.prednaska} v seznamu {self.seznam}" class Znalost(models.Model): class Meta: - db_table = 'prednasky_znalost' - verbose_name = 'Znalost k přednáškám' - verbose_name_plural = 'Znalosti k přednáškám' + 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") + 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): @@ -66,36 +87,12 @@ class Znalost(models.Model): class HlasovaniOZnalostech(models.Model): 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' + 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) + odpoved = models.CharField(u"odpověď", max_length=16, choices=ODPOVED.choices, 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) seznam = models.ForeignKey(Seznam, on_delete=models.SET_NULL, blank=True, null=True) - - -class Hlasovani(models.Model): - class Meta: - db_table = 'prednasky_hlasovani' - verbose_name = 'Hlasování' - verbose_name_plural = 'Hlasování' - ordering = ['ucastnik', 'prednaska'] - - class Body(models.IntegerChoices): - 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 = Body.JEDNO, choices = Body.choices) - 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) - From bcda95f0b3cd52902ec35010e5577e824a5e7ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 15:51:06 +0100 Subject: [PATCH 15/37] =?UTF-8?q?Stringifikace=20hlasov=C3=A1n=C3=AD=20o?= =?UTF-8?q?=20znalostech?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prednasky/models.py b/prednasky/models.py index f003878f..f9e1e08f 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -96,3 +96,6 @@ class HlasovaniOZnalostech(models.Model): 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}" + From e12b614e1c23ad054cb300a5a10b53722222303f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 16:01:39 +0100 Subject: [PATCH 16/37] ODPOVED -> Odpoved --- prednasky/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prednasky/models.py b/prednasky/models.py index f9e1e08f..2b980042 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -86,12 +86,12 @@ class Znalost(models.Model): class HlasovaniOZnalostech(models.Model): - class ODPOVED(models.IntegerChoices): + 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) + odpoved = models.CharField(u"odpověď", max_length=16, choices=Odpoved.choices, 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) seznam = models.ForeignKey(Seznam, on_delete=models.SET_NULL, blank=True, null=True) From fbd75d2f7232b3cb6af0a88b9f2c82d43cb53d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 19:40:54 +0100 Subject: [PATCH 17/37] =?UTF-8?q?Hlasov=C3=A1n=C3=AD=20o=20p=C5=99edn?= =?UTF-8?q?=C3=A1=C5=A1k=C3=A1ch=20pomoc=C3=AD=20formset=C5=AF=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/settings_common.py | 1 + prednasky/admin.py | 4 +- prednasky/forms.py | 14 ++++ prednasky/models.py | 4 + prednasky/templates/prednasky/base.html | 54 ++++++------- prednasky/views.py | 103 +++++++++++++++++------- 6 files changed, 122 insertions(+), 58 deletions(-) diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index 3bfbcfc4..f8cac53f 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -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', diff --git a/prednasky/admin.py b/prednasky/admin.py index f79c7b0a..a6204d35 100644 --- a/prednasky/admin.py +++ b/prednasky/admin.py @@ -100,7 +100,7 @@ admin.site.register(Prednaska, PrednaskaAdmin) class ZnalostAdmin(PrednaskaAdmin): # Trochu hack, ať nemusím vypisovat všechno znovu - list_display = [] - list_filter = [] + list_display = ("__str__",) + list_filter = () admin.site.register(Znalost, ZnalostAdmin) diff --git a/prednasky/forms.py b/prednasky/forms.py index e79048bf..cee90f7f 100644 --- a/prednasky/forms.py +++ b/prednasky/forms.py @@ -1 +1,15 @@ from django import forms + +from .models import Hlasovani, HlasovaniOZnalostech + +class HlasovaniPrednaskaForm(forms.Form): + prednaska_id = forms.IntegerField(widget=forms.HiddenInput) + body = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=Hlasovani.Body.choices, initial=Hlasovani.Body.JEDNO) + +HlasovaniPrednaskaFormSet = forms.formset_factory(HlasovaniPrednaskaForm, extra=0) + +class HlasovaniZnalostiForm(forms.Form): + znalost_id = forms.IntegerField(widget=forms.HiddenInput) + odpoved = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=HlasovaniOZnalostech.Odpoved.choices) + +HlasovaniZnalostiFormSet = forms.formset_factory(HlasovaniZnalostiForm, extra=0) diff --git a/prednasky/models.py b/prednasky/models.py index 2b980042..32d8924e 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -64,6 +64,10 @@ class Hlasovani(models.Model): id = models.AutoField(primary_key=True) prednaska = models.ForeignKey(Prednaska, on_delete=models.CASCADE) body = models.IntegerField("Body", default=Body.JEDNO, choices=Body.choices) + + # (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) diff --git a/prednasky/templates/prednasky/base.html b/prednasky/templates/prednasky/base.html index ce201cc6..37ea178c 100644 --- a/prednasky/templates/prednasky/base.html +++ b/prednasky/templates/prednasky/base.html @@ -5,36 +5,36 @@ {% block content %} -

    -{% block nadpis1a %}Hlasování o přednáškách{% endblock %} -

    - -

    -Jak moc by ses chtěl(a) zúčastnit následujících přednášek? -
    -Obtížnost 1 je nejlehčí, 3 nejtěžší. -

    +

    {% block nadpis1a %}Hlasování o přednáškách{% endblock %}

    {% csrf_token %} - - {% for p, h in prednasky %} - - - - - {% if p.klicova %}{% endif%} - - - {% empty %} - Nejsou žádné přednášky o kterých by šlo hlasovat. - {% endfor %} - -

    {{p.anotace}}

    {{p.obor}}
    {{p.obtiznost}}
    {{p.klicova}}
    Hodnocení: - rozhodně nechci - je mi to jedno - rozhodně chci -
     
    + +

    Jak moc by ses chtěl(a) zúčastnit následujících přednášek?

    +

    Obtížnost 1 je nejlehčí, 3 nejtěžší.

    + {{ form_set_prednasky.management_form }} + {% for f, p in formy_a_prednasky %} +

    {{p.nazev}} ({{p.org}})

    +

    {{p.anotace}}

    + {{p.obor}}
    + {{p.obtiznost}}
    + {% if p.klicova %} {{p.klicova}}
    {% endif%} +
    + {{ f }} +
    + {% 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 %}

    Jak moc znáš následující?

    {% endif %} +

    {{z.nazev}}

    +

    {{z.text}}

    + {{ f }} +
    + {% endfor %} +
    {% endblock %} diff --git a/prednasky/views.py b/prednasky/views.py index dd64047f..c0951cec 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -1,19 +1,25 @@ import http +import logging 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 +from prednasky.models import Prednaska, Hlasovani, Znalost, HlasovaniOZnalostech, Seznam from soustredeni.models import Soustredeni from personalni.models import Osoba +PREDNASKY_PREFIX = "prednasky" +ZNALOSTI_PREFIX = "znalosti" + +logger = logging.getLogger(__name__) + def newPrednaska(request): # hlasovani se vztahuje k nejnovejsimu soustredeni sous = Nastaveni.get_solo().aktualni_sous @@ -23,43 +29,82 @@ def newPrednaska(request): '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 + form_set_prednasky = HlasovaniPrednaskaFormSet(request.POST, prefix=PREDNASKY_PREFIX) + form_set_znalosti = HlasovaniZnalostiFormSet(request.POST, prefix=ZNALOSTI_PREFIX) - # 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 form_set_prednasky.is_valid() and form_set_znalosti.is_valid(): + with transaction.atomic(): + 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 + prednasky = seznam.prednaska_set.all() + znalosti = seznam.znalost_set.all() + # Spadnout, pokud nesedí přednáška/znalost s formulářem. (Nějak se mi to nepovedlo.) + + else: + def odpoved_prednasky(p): + hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first() + return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO + + def odpoved_znalosti(z): + hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first() + return hlasovani.odpoved if hlasovani else Hlasovani.Body.JEDNO + + 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) + 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), + } ) From 7ca709337177d8b7e1f7552fb4b416528297ebdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 20:22:38 +0100 Subject: [PATCH 18/37] =?UTF-8?q?Export=20hlasov=C3=A1n=C3=AD=20do=20CSV?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/urls.py | 5 +++++ prednasky/views.py | 48 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/prednasky/urls.py b/prednasky/urls.py index eecc45ad..c22ea9f9 100644 --- a/prednasky/urls.py +++ b/prednasky/urls.py @@ -17,6 +17,11 @@ urlpatterns = [ org_required(views.SeznamExportView), name='seznam-export' ), + path( + 'prednasky/seznam_prednasek//hlasovani.csv', + org_required(views.PrednaskyExportView), + name='seznam-export-csv' + ), path( 'prednasky/seznam_prednasek//', org_required(views.SeznamListView.as_view()), diff --git a/prednasky/views.py b/prednasky/views.py index c0951cec..e61dfdef 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -1,6 +1,8 @@ +import csv import http import logging +from django.http import HttpResponse from django.shortcuts import render, get_object_or_404 from django.views import generic from django.shortcuts import HttpResponseRedirect @@ -31,7 +33,7 @@ def newPrednaska(request): }, status=http.HTTPStatus.NOT_FOUND) osoba = Osoba.objects.filter(user=request.user).first() - ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) + ucastnik = osoba.plne_jmeno() + ' ' + str(osoba.id) # id, kvůli kolizi jmen if request.method == 'POST': form_set_prednasky = HlasovaniPrednaskaFormSet(request.POST, prefix=PREDNASKY_PREFIX) @@ -170,3 +172,47 @@ def SeznamExportView(request, seznam): ) +def PrednaskyExportView(request, seznam: int, **kwargs): + hlasovani = Hlasovani.objects.filter(seznam=seznam).select_related("prednaska") + hlasovani_o_znalostech = HlasovaniOZnalostech.objects.filter(seznam=seznam).select_related('ucastnik', 'znalost') + + 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) + + table: [str, list[str|Prednaska|Znalost,]] = {} + + for h in hlasovani: + if h.ucastnik not in table: + table[h.ucastnik] = [h.ucastnik] + ([""] * width) + + if h.prednaska.id in prednasky_map: + table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body + else: + pass # 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: + table[ucastnik] = [ucastnik] + ([""] * width) + + if h.znalost.id in znalosti_map: + table[ucastnik][znalosti_map[h.znalost.id]] = h.odpoved + else: + pass # 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 + + From 4001822842ba05525f0636a542b15b26062bc137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 20:32:28 +0100 Subject: [PATCH 19/37] =?UTF-8?q?Oprava=20pr=C3=A1v=20pro=20aplikaci=20p?= =?UTF-8?q?=C5=99edn=C3=A1=C5=A1ky?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/groups.json | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/data/groups.json b/data/groups.json index df9516a0..93a6c8b2 100644 --- a/data/groups.json +++ b/data/groups.json @@ -248,26 +248,16 @@ "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", @@ -308,6 +298,26 @@ "prednasky", "seznam" ], + [ + "add_znalost", + "prednasky", + "znalost" + ], + [ + "change_znalost", + "prednasky", + "znalost" + ], + [ + "delete_znalost", + "prednasky", + "znalost" + ], + [ + "view_znalost", + "prednasky", + "znalost" + ], [ "add_konfera", "soustredeni", From 90e7b97b85402910c51bc1de99509b99b17e1257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 20:35:24 +0100 Subject: [PATCH 20/37] =?UTF-8?q?Zakomentov=C3=A1n=20star=C3=BD=20export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/groups.json | 104 +++++++++--------- .../prednasky/metaseznam_prednasek.html | 2 +- prednasky/urls.py | 10 +- prednasky/views.py | 54 ++++----- 4 files changed, 85 insertions(+), 85 deletions(-) diff --git a/data/groups.json b/data/groups.json index 93a6c8b2..cf10eb83 100644 --- a/data/groups.json +++ b/data/groups.json @@ -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" ], @@ -264,12 +264,12 @@ "prednaska" ], [ - "delete_prednaska", + "change_prednaska", "prednasky", "prednaska" ], [ - "change_prednaska", + "delete_prednaska", "prednasky", "prednaska" ], @@ -284,12 +284,12 @@ "seznam" ], [ - "delete_seznam", + "change_seznam", "prednasky", "seznam" ], [ - "change_seznam", + "delete_seznam", "prednasky", "seznam" ], @@ -324,12 +324,12 @@ "konfera" ], [ - "delete_konfera", + "change_konfera", "soustredeni", "konfera" ], [ - "change_konfera", + "delete_konfera", "soustredeni", "konfera" ], @@ -344,12 +344,12 @@ "konfery_ucastnici" ], [ - "delete_konfery_ucastnici", + "change_konfery_ucastnici", "soustredeni", "konfery_ucastnici" ], [ - "change_konfery_ucastnici", + "delete_konfery_ucastnici", "soustredeni", "konfery_ucastnici" ], @@ -364,12 +364,12 @@ "soustredeni" ], [ - "delete_soustredeni", + "change_soustredeni", "soustredeni", "soustredeni" ], [ - "change_soustredeni", + "delete_soustredeni", "soustredeni", "soustredeni" ], @@ -384,12 +384,12 @@ "soustredeni_organizatori" ], [ - "delete_soustredeni_organizatori", + "change_soustredeni_organizatori", "soustredeni", "soustredeni_organizatori" ], [ - "change_soustredeni_organizatori", + "delete_soustredeni_organizatori", "soustredeni", "soustredeni_organizatori" ], @@ -404,12 +404,12 @@ "soustredeni_ucastnici" ], [ - "delete_soustredeni_ucastnici", + "change_soustredeni_ucastnici", "soustredeni", "soustredeni_ucastnici" ], [ - "change_soustredeni_ucastnici", + "delete_soustredeni_ucastnici", "soustredeni", "soustredeni_ucastnici" ], @@ -424,12 +424,12 @@ "tag" ], [ - "delete_tag", + "change_tag", "taggit", "tag" ], [ - "change_tag", + "delete_tag", "taggit", "tag" ], @@ -444,12 +444,12 @@ "taggeditem" ], [ - "delete_taggeditem", + "change_taggeditem", "taggit", "taggeditem" ], [ - "change_taggeditem", + "delete_taggeditem", "taggit", "taggeditem" ], @@ -464,12 +464,12 @@ "cislo" ], [ - "delete_cislo", + "change_cislo", "tvorba", "cislo" ], [ - "change_cislo", + "delete_cislo", "tvorba", "cislo" ], @@ -484,12 +484,12 @@ "clanek" ], [ - "delete_clanek", + "change_clanek", "tvorba", "clanek" ], [ - "change_clanek", + "delete_clanek", "tvorba", "clanek" ], @@ -519,12 +519,12 @@ "pohadka" ], [ - "delete_pohadka", + "change_pohadka", "tvorba", "pohadka" ], [ - "change_pohadka", + "delete_pohadka", "tvorba", "pohadka" ], @@ -539,12 +539,12 @@ "problem" ], [ - "delete_problem", + "change_problem", "tvorba", "problem" ], [ - "change_problem", + "delete_problem", "tvorba", "problem" ], @@ -559,12 +559,12 @@ "rocnik" ], [ - "delete_rocnik", + "change_rocnik", "tvorba", "rocnik" ], [ - "change_rocnik", + "delete_rocnik", "tvorba", "rocnik" ], @@ -579,12 +579,12 @@ "tema" ], [ - "delete_tema", + "change_tema", "tvorba", "tema" ], [ - "change_tema", + "delete_tema", "tvorba", "tema" ], @@ -599,12 +599,12 @@ "uloha" ], [ - "delete_uloha", + "change_uloha", "tvorba", "uloha" ], [ - "change_uloha", + "delete_uloha", "tvorba", "uloha" ], @@ -619,12 +619,12 @@ "nastaveni" ], [ - "delete_nastaveni", + "change_nastaveni", "various", "nastaveni" ], [ - "change_nastaveni", + "delete_nastaveni", "various", "nastaveni" ], diff --git a/prednasky/templates/prednasky/metaseznam_prednasek.html b/prednasky/templates/prednasky/metaseznam_prednasek.html index 9db97b08..dfc13caf 100644 --- a/prednasky/templates/prednasky/metaseznam_prednasek.html +++ b/prednasky/templates/prednasky/metaseznam_prednasek.html @@ -14,7 +14,7 @@ {% else %} Seznam přednášek na soustředění {{seznam.soustredeni.misto}} {% endif %} - Export + Export {% endfor %} diff --git a/prednasky/urls.py b/prednasky/urls.py index c22ea9f9..24d8535a 100644 --- a/prednasky/urls.py +++ b/prednasky/urls.py @@ -12,11 +12,11 @@ urlpatterns = [ 'prednasky/metaseznam_prednasek', org_required(views.MetaSeznamListView.as_view()), name='metaseznam-list'), - path( - 'prednasky/seznam_prednasek//export', - org_required(views.SeznamExportView), - name='seznam-export' - ), + # path( + # 'prednasky/seznam_prednasek//export', + # org_required(views.SeznamExportView), + # name='seznam-export' + # ), path( 'prednasky/seznam_prednasek//hlasovani.csv', org_required(views.PrednaskyExportView), diff --git a/prednasky/views.py b/prednasky/views.py index e61dfdef..c154c455 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -143,33 +143,33 @@ 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) - - 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 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, seznam: int, **kwargs): From 0634cdad8714ab41d7d37163ef0c9b53ab2f0d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 20:42:44 +0100 Subject: [PATCH 21/37] =?UTF-8?q?O=C4=8Dividn=C4=9B=20ka=C5=BEd=C3=BD=20sy?= =?UTF-8?q?st=C3=A9m=20=C5=BEere=20uvozovky=20v=20f-stringu=20jinak?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prednasky/models.py b/prednasky/models.py index 32d8924e..fbeb6b21 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -20,7 +20,7 @@ class Seznam(models.Model): stav = models.IntegerField("Stav", choices=Stav.choices, default=Stav.NAVRH) 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): From 1719e8be9a1e7a9efffd1c6b1558487b36195ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 21:03:44 +0100 Subject: [PATCH 22/37] =?UTF-8?q?Zapomn=C4=9Bl=20jsem=20p=C5=99idat=20CSS?= =?UTF-8?q?=20m=C3=ADsto=20smazan=C3=A9ho=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/static/css/modules.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mamweb/static/css/modules.css b/mamweb/static/css/modules.css index 02a9b2bf..e698e2fb 100644 --- a/mamweb/static/css/modules.css +++ b/mamweb/static/css/modules.css @@ -503,5 +503,10 @@ label[for=id_skola] { font-weight: bold; } +/* Přednášky */ +.textznalosti, .textprednasky { + font-style: italic; +} + /*******************/ From cb14e4a91ecb445cb4c0807fa8280459753c6c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 21:07:06 +0100 Subject: [PATCH 23/37] =?UTF-8?q?A=20o=C4=8Dividn=C4=9B=20nevygeneroval=20?= =?UTF-8?q?migraci=20k=20p=C5=99eps=C3=A1n=C3=AD=20string=C5=AF=20u=20Hlas?= =?UTF-8?q?ovani.Body=20v=20commitu=20e933c697?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0020_alter_hlasovani_body.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 prednasky/migrations/0020_alter_hlasovani_body.py diff --git a/prednasky/migrations/0020_alter_hlasovani_body.py b/prednasky/migrations/0020_alter_hlasovani_body.py new file mode 100644 index 00000000..c3c348c2 --- /dev/null +++ b/prednasky/migrations/0020_alter_hlasovani_body.py @@ -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'), + ), + ] From 7563dd728ca9dd484a5396cdca3e0d6e97b8d97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Fri, 24 Jan 2025 22:51:09 +0100 Subject: [PATCH 24/37] Fix make/deploy --- make/lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/lib.sh b/make/lib.sh index dd56ef73..2e92d6e7 100644 --- a/make/lib.sh +++ b/make/lib.sh @@ -95,7 +95,7 @@ function safe_checkout_branch { echo >&2 "Změna v $SCRIPT, prosím pullni manuálně" exit 1 fi - git checkout "$BRANCH" + git checkout "$BRANCH" -- git pull git clean -f } From ca5e6728dd83525f80aa1af515d39ce133f050d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 28 Jan 2025 18:48:57 +0100 Subject: [PATCH 25/37] =?UTF-8?q?hotfix:=20Tohle=20by=20m=C4=9Blo=20opravi?= =?UTF-8?q?t=20probl=C3=A9m=20s=20ukl=C3=A1d=C3=A1n=C3=ADm=20bod=C5=AF.=20?= =?UTF-8?q?Nejsem=20si=20t=C3=ADm=20ale=20moc=20jist=C3=BD.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- odevzdavatko/views.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/odevzdavatko/views.py b/odevzdavatko/views.py index 255f48a9..1b626b3d 100644 --- a/odevzdavatko/views.py +++ b/odevzdavatko/views.py @@ -322,14 +322,26 @@ def hodnoceniReseniView(request, pk, *args, **kwargs): **form.cleaned_data, ) logger.info(f"Creating Hodnoceni: {hodnoceni}") + # FIXME následující kód má velmi vysokou šanci se rozbít, vymyslet, jak to udělat jinak zmeny_bodu = [it for it in form.changed_data if it.startswith("body")] - if len(zmeny_bodu) == 1: - hodnoceni.__setattr__(zmeny_bodu[0], data_for_body[zmeny_bodu[0]]) - # > jedna změna je špatně, ale 4 "změny" znamenají že nebylo nic zadáno - if len(zmeny_bodu) > 1 and len(zmeny_bodu) != 4 and len(zmeny_bodu) != 2: - # 4 znamená vše už vyplněno a nic nezměněno, 2 znamená předvyplnili se součty a nic se nezměnilo - logger.warning(f"Hodnocení {hodnoceni} mělo mít nastavené víc různých bodů: {zmeny_bodu}. Nastavuji -0.1.") - hodnoceni.body = -0.1 + if len(zmeny_bodu) != 0: + body_nastaveny: None | tuple[str, object] = None + def nastav_body(jake, na_kolik): + nonlocal body_nastaveny + if body_nastaveny is not None: + logger.warning(f"Hodnocení {hodnoceni} s id {hodnoceni.id} k řešení {reseni.id} mělo mít nastavené kromě {body_nastaveny[0]} na {body_nastaveny[1]} ještě další body: {jake} na {na_kolik}. Nastavuji -0.1.") + hodnoceni.body = -0.1 + else: + body_nastaveny = (jake, na_kolik) + hodnoceni.__setattr__(jake, na_kolik) + + for key, value in data_for_body.items(): + if key.startswith("body") and value is not None: + nastav_body(key, value) + + # Něco se změnilo, ale nic není nastavené = něco bylo smazáno + if body_nastaveny is None: + hodnoceni.body = None hodnoceni.save() adresati = reseni.resitele.filter(upozornovat_na_opravy_reseni=True).values_list('osoba__email', flat=True) From ef9d51d9229a3b30dfba8d0edc8a25c84a6c5c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 28 Jan 2025 19:03:51 +0100 Subject: [PATCH 26/37] =?UTF-8?q?hotfix:=20WTF=20se=20stalo=20v=20django-a?= =?UTF-8?q?utocomplete-light=3D3.12.0=20(%20=20m=C3=ADsto=20mezer=20apod.)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 96aa4d7d..63a4b8be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ django-solo # Singleton model (speciálně Nastavení) django-ckeditor-5 # Editor htmlka (hlavně v adminu u flatpages) django-cleanup # Uklízí media/ od smazaných „databázových“ souborů django-taggit # Taggy v djangu (speciálně zaměření problémů) -django-autocomplete-light>=3.9.0 # Automatické doplňování (problémů, účastníků, …) ve formulářích +django-autocomplete-light>=3.9.0,<3.12.0 # Automatické doplňování (problémů, účastníků, …) ve formulářích django-imagekit # Všechny možné obrázky v Djangu django-polymorphic # Polymorfismus na django modelech (hlavně Problém nebo treenode) django-sitetree # Struktura stránek, hlavně pro meníčko From 9e513bba9ac02f0cbedc6ffe9efe86d6c3112860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Wed, 29 Jan 2025 00:30:55 +0100 Subject: [PATCH 27/37] =?UTF-8?q?Kop=C3=ADrov=C3=A1n=C3=AD=20je=20=C4=8Das?= =?UTF-8?q?t=C3=BDm=20zdrojem=20chyb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prednasky/views.py b/prednasky/views.py index c154c455..4f6f28ae 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -85,7 +85,7 @@ def newPrednaska(request): def odpoved_znalosti(z): hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first() - return hlasovani.odpoved if hlasovani else Hlasovani.Body.JEDNO + return hlasovani.odpoved if hlasovani else Znalost.Odpoved.CIRCA prednasky = seznam.prednaska_set.all() znalosti = seznam.znalost_set.all() From 51255252386b4389571812adbd169d2f92abb9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Wed, 29 Jan 2025 01:05:08 +0100 Subject: [PATCH 28/37] Dokumentace aplikace `prednasky` --- prednasky/__init__.py | 3 +++ prednasky/admin.py | 11 +++++++++ prednasky/forms.py | 16 +++++++++++++ prednasky/models.py | 40 ++++++++++++++++++++++++++----- prednasky/views.py | 56 ++++++++++++++++++++++++++++++++----------- 5 files changed, 106 insertions(+), 20 deletions(-) diff --git a/prednasky/__init__.py b/prednasky/__init__.py index e69de29b..b34d6384 100644 --- a/prednasky/__init__.py +++ b/prednasky/__init__.py @@ -0,0 +1,3 @@ +""" +Aplikace umožňující orgům vypisovat si přednášky a účastníkům o nich hlasovat. +""" diff --git a/prednasky/admin.py b/prednasky/admin.py index a6204d35..7ab77d24 100644 --- a/prednasky/admin.py +++ b/prednasky/admin.py @@ -9,6 +9,10 @@ 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 ` + v adminu :py:class:`Seznamu `. + """ model = Prednaska.seznamy.through extra = 0 @@ -55,6 +59,7 @@ class Seznam_PrednaskaInline(admin.TabularInline): class SeznamAdmin(VersionAdmin): + """ Admin pro :py:class:`Seznam ` """ list_display = ['soustredeni', 'stav'] inlines = [Seznam_PrednaskaInline] @@ -62,6 +67,7 @@ admin.site.register(Seznam, SeznamAdmin) class PrednaskaAdmin(VersionAdmin): + """ Admin pro :py:class:`Přednášku """ list_display = ['nazev', 'org', 'obor'] list_filter = ['org', 'obor'] search_fields = ['nazev'] @@ -70,6 +76,7 @@ class PrednaskaAdmin(VersionAdmin): 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=Seznam.Stav.NAVRH) 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 + """ + Admin pro :py:class:`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 = () diff --git a/prednasky/forms.py b/prednasky/forms.py index cee90f7f..7b0e9739 100644 --- a/prednasky/forms.py +++ b/prednasky/forms.py @@ -3,13 +3,29 @@ from django import forms from .models import Hlasovani, HlasovaniOZnalostech class HlasovaniPrednaskaForm(forms.Form): + """ :py:class:`Formulář ` pro pro :py:class:`Hlasování ` o jedné :py:class:`Přednášce ` + (neobsahuje téměř nic, většina se musí doplnit jiným způsobem) + """ + + #: ID :py:class:`Přednášky `, o které se hlasuje prednaska_id = forms.IntegerField(widget=forms.HiddenInput) + #: :py:class:`Hodnocení (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 ` :py:class:`HlasovaniPrednaskaFormů `) +#: pro :py:class:`Hlasování ` o množině :py:class:`Přednášek ` HlasovaniPrednaskaFormSet = forms.formset_factory(HlasovaniPrednaskaForm, extra=0) class HlasovaniZnalostiForm(forms.Form): + """ :py:class:`Formulář ` pro pro :py:class:`HlasováníOZnalostech ` o jedné :py:class:`Znalosti ` + (neobsahuje téměř nic, většina se musí doplnit jiným způsobem) + """ + + #: ID :py:class:`Znalosti `, o které hlasujeme znalost_id = forms.IntegerField(widget=forms.HiddenInput) + #: :py:class:`Odpověď ` na tuto znalost odpoved = forms.ChoiceField(label=False, widget=forms.RadioSelect, choices=HlasovaniOZnalostech.Odpoved.choices) +#: Množina formulářů (:py:class:`formset ` :py:class:`HlasovaniZnalostiFormů `) +#: pro :py:class:`HlasováníOZnalostech ` o množině :py:class:`Znalostí ` HlasovaniZnalostiFormSet = forms.formset_factory(HlasovaniZnalostiForm, extra=0) diff --git a/prednasky/models.py b/prednasky/models.py index fbeb6b21..f6168fdd 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -5,6 +5,12 @@ from personalni.models import Organizator, Osoba class Seznam(models.Model): + """ + Spojuje :py:class:`Přednášky ` + se :py:class:`Soustředěními `, + kde by mohly zaznít, nebo zazní/zazněly. + """ + class Meta: db_table = "prednasky_seznam" verbose_name = "Seznam přednášek" @@ -12,18 +18,23 @@ class Seznam(models.Model): ordering = ["soustredeni", "stav"] class Stav(models.IntegerChoices): + """ Stav seznamu přednášek (NAVRH se používá k hlasování viz :py:func:`daný view `). """ 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) + stav = models.IntegerField("Stav", choices=Stav.choices, default=Stav.NAVRH) #: :py:class:`Stav ` Seznamu def __str__(self): return f"Seznam {'návrhů ' if self.stav == Seznam.Stav.NAVRH else ''}přednášek na {self.soustredeni}" class Prednaska(models.Model): + """ + Reprezentuje přednášku, kterou si org může vypsat a účastník o ní hlasovat. + (Viz :py:class:`Hlasování `.) + """ class Meta: db_table = "prednasky_prednaska" verbose_name = "Přednáška" @@ -40,7 +51,7 @@ class Prednaska(models.Model): 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=Obtiznost.choices) + obtiznost = models.IntegerField("Obtížnost", choices=Obtiznost.choices) #: :py:class:`Obtížnost ` 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) @@ -50,6 +61,11 @@ class Prednaska(models.Model): class Hlasovani(models.Model): + """ + Reprezentuje hlasování jednoho účastníka + o jedné :py:class:`Přednášce ` + v jednom :py:class:`Seznamu ` (účastníkův pohled se totiž mezi sousy změnit) + """ class Meta: db_table = "prednasky_hlasovani" verbose_name = "Hlasování" @@ -57,17 +73,20 @@ class Hlasovani(models.Model): 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) + #: Příslušné hlasování: :py:class:`Body ` body = models.IntegerField("Body", default=Body.JEDNO, choices=Body.choices) - # (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 + #: Úč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) @@ -76,6 +95,10 @@ class Hlasovani(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 `.) + """ class Meta: db_table = "prednasky_znalost" verbose_name = "Znalost k přednáškám" @@ -90,12 +113,17 @@ class Znalost(models.Model): class HlasovaniOZnalostech(models.Model): + """ + Reprezentuje hlasování jednoho účastníka + o jedné :py:class:`Znalosti ` + v jednom :py:class:`Seznamu ` (úč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) + odpoved = models.CharField(u"odpověď", max_length=16, choices=Odpoved.choices, blank=False, null=False) #: :py:class:`Odpověď ` 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) diff --git a/prednasky/views.py b/prednasky/views.py index 4f6f28ae..68d563cc 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -2,7 +2,7 @@ import csv import http import logging -from django.http import HttpResponse +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 @@ -22,7 +22,14 @@ ZNALOSTI_PREFIX = "znalosti" 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í ` + a :py:class:`HlasováníOZnalostech `) + o :py:class:`Přednáškách ` + a :py:class:`Znalostech ` + """ # hlasovani se vztahuje k nejnovejsimu soustredeni sous = Nastaveni.get_solo().aktualni_sous 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() 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_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() @@ -73,17 +82,19 @@ def newPrednaska(request): ) return HttpResponseRedirect('./hotovo') - else: + + else: # Pokud je nějaký formset nevalidní, vracíme je k přepracování prednasky = seznam.prednaska_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: - def odpoved_prednasky(p): + 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): + def odpoved_znalosti(z: Znalost) -> Znalost.Odpoved: hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first() return hlasovani.odpoved if hlasovani else Znalost.Odpoved.CIRCA @@ -99,6 +110,7 @@ def newPrednaska(request): ], prefix=ZNALOSTI_PREFIX) + # V případě nePOSTu nebo chyby při ukládání vracíme hlasování return render( request, '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í ` """ 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ů ` s odkazy na exporty """ model = Seznam template_name = 'prednasky/metaseznam_prednasek.html' class SeznamListView(generic.ListView): + """ + Náhled na to, kolik má která přednáška v :py:class:`Seznamu ` :py:class:`hlasů `. + (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): @@ -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í ` + i :py:class:`HlasováníOZnalostech ` + v daném :py:class:`Seznamu ` + jako csv soubor (řádky = účastníci, sloupce = přednášky&znalosti). + + :param seznam: ID daného :py:class:`Seznamu ` + """ 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)) @@ -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)} width = offset + len(znalosti_map) + # A po inicializaci sloupců vyplníme tabulku table: [str, list[str|Prednaska|Znalost,]] = {} 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) if h.prednaska.id in prednasky_map: table[h.ucastnik][prednasky_map[h.prednaska.id]] = h.body else: - pass # Padat hlasitě? + 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: + 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 # Padat hlasitě? + pass # TODO Padat hlasitě? response = HttpResponse(content_type="text/csv", charset="utf-8") From c1da67dbb4832181262496fbd92d86a79bb966b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 4 Feb 2025 20:33:03 +0100 Subject: [PATCH 29/37] =?UTF-8?q?Dob=C5=99e,=20p=C5=99=C3=AD=C5=A1t=C4=9B?= =?UTF-8?q?=20u=C5=BE=20p=C5=99i=20dokumentaci=20nebudu=20hrabat=20na=20ty?= =?UTF-8?q?pov=C3=A9=20anotace.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prednasky/views.py b/prednasky/views.py index 68d563cc..43ac983b 100644 --- a/prednasky/views.py +++ b/prednasky/views.py @@ -94,9 +94,9 @@ def newPrednaska(request: HttpRequest) -> HttpResponse: hlasovani = p.hlasovani_set.filter(ucastnik=ucastnik).first() return hlasovani.body if hlasovani else Hlasovani.Body.JEDNO - def odpoved_znalosti(z: Znalost) -> Znalost.Odpoved: + def odpoved_znalosti(z: Znalost) -> HlasovaniOZnalostech.Odpoved: hlasovani = z.hlasovanioznalostech_set.filter(ucastnik=osoba).first() - return hlasovani.odpoved if hlasovani else Znalost.Odpoved.CIRCA + return hlasovani.odpoved if hlasovani else HlasovaniOZnalostech.Odpoved.CIRCA prednasky = seznam.prednaska_set.all() znalosti = seznam.znalost_set.all() From 5d4b600b00c79864f39a4f03f5f0afdd71e5973a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 4 Feb 2025 21:21:15 +0100 Subject: [PATCH 30/37] =?UTF-8?q?Oto=C4=8Den=C3=AD=20v=C3=BDznamu=20odpov?= =?UTF-8?q?=C4=9Bd=C3=AD=20na=20hlasov=C3=A1n=C3=AD=20o=20znalostech=20+?= =?UTF-8?q?=20WTF=20pro=C4=8D=20to=20byl=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0021_alter_hlasovanioznalostech_odpoved.py | 24 +++++++++++++++++++ prednasky/models.py | 7 +++--- 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 prednasky/migrations/0021_alter_hlasovanioznalostech_odpoved.py diff --git a/prednasky/migrations/0021_alter_hlasovanioznalostech_odpoved.py b/prednasky/migrations/0021_alter_hlasovanioznalostech_odpoved.py new file mode 100644 index 00000000..ff0c63b9 --- /dev/null +++ b/prednasky/migrations/0021_alter_hlasovanioznalostech_odpoved.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.16 on 2025-02-04 20:09 + +from django.db import migrations, models + +def zmena_bodu(apps, _schema_editor): + HlasovaniOZnalostech = apps.get_model('prednasky','HlasovaniOZnalostech') + for h in HlasovaniOZnalostech.objects.all(): + h.odpoved = -int(h.odpoved) + h.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('prednasky', '0020_alter_hlasovani_body'), + ] + + operations = [ + migrations.AlterField( + model_name='hlasovanioznalostech', + name='odpoved', + field=models.IntegerField(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')], verbose_name='odpověď'), + ), + migrations.RunPython(zmena_bodu, reverse_code=zmena_bodu), + ] diff --git a/prednasky/models.py b/prednasky/models.py index f6168fdd..a0597be2 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -119,11 +119,12 @@ class HlasovaniOZnalostech(models.Model): v jednom :py:class:`Seznamu ` (účastníkův pohled se totiž mezi sousy změnit) """ class Odpoved(models.IntegerChoices): - UMIM = -1, "Tohle celkem umím" + """ Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """ + 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" + 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ěď ` na HlasováníOZnalostech + odpoved = models.IntegerField(u"odpověď", choices=Odpoved.choices, blank=False, null=False) #: :py:class:`Odpověď ` 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) From 42c651ceb74534c2494129b34f44f492c92a6a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 4 Feb 2025 22:47:18 +0100 Subject: [PATCH 31/37] =?UTF-8?q?Zlep=C5=A1en=C3=AD=20dokumentace=20Djanga?= =?UTF-8?q?=20ve=20Sphinxu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 75bca8d3..8c8c2df0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,7 @@ extensions = [ 'sphinx.ext.intersphinx', 'sphinx.ext.autosectionlabel', 'myst_parser', + 'sphinxcontrib_django', ] # Add any paths that contain templates here, relative to this directory. diff --git a/requirements.txt b/requirements.txt index 63a4b8be..0788c744 100644 --- a/requirements.txt +++ b/requirements.txt @@ -49,4 +49,5 @@ lorem sphinx sphinx_rtd_theme +sphinxcontrib-django myst_parser From 9460c484f72c98648e4a0f623c34a5fa74965c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Tue, 4 Feb 2025 23:09:47 +0100 Subject: [PATCH 32/37] =?UTF-8?q?Zobrazen=C3=AD=20Znalost=C3=AD=20(stejn?= =?UTF-8?q?=C4=9B=20jako=20P=C5=99edn=C3=A1=C5=A1ek)=20u=20dan=C3=A9ho=20s?= =?UTF-8?q?eznamu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/admin.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/prednasky/admin.py b/prednasky/admin.py index 7ab77d24..07615f43 100644 --- a/prednasky/admin.py +++ b/prednasky/admin.py @@ -58,10 +58,40 @@ class Seznam_PrednaskaInline(admin.TabularInline): def has_add_permission(self, req, obj): return False +class Seznam_ZnalostInline(admin.TabularInline): + """ + Pomůcka pro :py:class:`prednasky.admin.SeznamAdmin` zobrazující hezky :py:class:`Znalosti ` + v adminu :py:class:`Seznamu `. + """ + model = Znalost.seznamy.through + extra = 0 + + def znalost__nazev(self, obj): + return mark_safe( + f"{obj.znalost.nazev}" + ) + + def znalost__text(self, obj): + return mark_safe( + f"
    {escape(obj.znalost.text)}
    " + ) + + znalost__nazev.short_description = u'Přednáška' + znalost__text.short_description = u'Popis pro orgy' + + readonly_fields = [ + 'znalost__nazev', + 'znalost__text', + ] + exclude = ['znalost'] + + def has_add_permission(self, req, obj): return False + + class SeznamAdmin(VersionAdmin): """ Admin pro :py:class:`Seznam ` """ list_display = ['soustredeni', 'stav'] - inlines = [Seznam_PrednaskaInline] + inlines = [Seznam_PrednaskaInline, Seznam_ZnalostInline] admin.site.register(Seznam, SeznamAdmin) From 84ed9e09a7836fd0f6caf2a0333abc9d4f0fb3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Thu, 6 Feb 2025 13:50:01 +0100 Subject: [PATCH 33/37] =?UTF-8?q?Pr=C5=AFhledn=C3=A9=20pruhy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/static/css/layout.css | 1 + 1 file changed, 1 insertion(+) diff --git a/mamweb/static/css/layout.css b/mamweb/static/css/layout.css index bd139780..bb3f98e9 100644 --- a/mamweb/static/css/layout.css +++ b/mamweb/static/css/layout.css @@ -435,6 +435,7 @@ body.localweb, body.testweb, body.suprodweb { height: 100%; top: 0; z-index: -1000; + opacity: 0.7; } &:before { left: 0; } From 4a771b802b226c8810230e57c1936b435613eff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Thu, 6 Feb 2025 14:48:32 +0100 Subject: [PATCH 34/37] =?UTF-8?q?Pr=C5=AFhledn=C3=A9=20pruhy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mamweb/static/css/rozliseni.css | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 mamweb/static/css/rozliseni.css diff --git a/mamweb/static/css/rozliseni.css b/mamweb/static/css/rozliseni.css new file mode 100644 index 00000000..93c11668 --- /dev/null +++ b/mamweb/static/css/rozliseni.css @@ -0,0 +1,20 @@ +/**** ROZLIŠENÍ MEZI LOKÁLNÍM, TESTOVACÍM A PRODUKČNÍM WEBEM ****/ +body.localweb, body.testweb, body.suprodweb { + &:before, &:after { + content: ""; + position: fixed; + width: 20px; + height: 100%; + top: 0; + z-index: -1000; + opacity: 0.7; + } + + &:before { left: 0; } + &:after { right: 0; } +} + +body.localweb { &:before, &:after { background: greenyellow; } } +body.testweb { &:before, &:after { background: darkorange; } } +body.suprodweb { &:before, &:after { background: red; } } +/****************************************************************/ From d1ba5057f1b922324ab933d1b18d2eb2b0367c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 9 Feb 2025 22:11:46 +0100 Subject: [PATCH 35/37] =?UTF-8?q?P=C5=99eklep=20(HlasovaniOZnalostech.Odpo?= =?UTF-8?q?ved)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._preklep_u_odpovedi_hlasovanioznalostech.py | 18 ++++++++++++++++++ prednasky/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 prednasky/migrations/0022_preklep_u_odpovedi_hlasovanioznalostech.py diff --git a/prednasky/migrations/0022_preklep_u_odpovedi_hlasovanioznalostech.py b/prednasky/migrations/0022_preklep_u_odpovedi_hlasovanioznalostech.py new file mode 100644 index 00000000..32c82d62 --- /dev/null +++ b/prednasky/migrations/0022_preklep_u_odpovedi_hlasovanioznalostech.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2025-02-09 21:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('prednasky', '0021_alter_hlasovanioznalostech_odpoved'), + ] + + operations = [ + migrations.AlterField( + model_name='hlasovanioznalostech', + name='odpoved', + field=models.IntegerField(choices=[(1, 'Tohle celkem umím'), (0, 'Už jsem o tom slyšel, ale neřekl bych, že to úplně umím'), (-1, 'Tohle vůbec neznám')], verbose_name='odpověď'), + ), + ] diff --git a/prednasky/models.py b/prednasky/models.py index a0597be2..f508ca7e 100644 --- a/prednasky/models.py +++ b/prednasky/models.py @@ -121,7 +121,7 @@ class HlasovaniOZnalostech(models.Model): class Odpoved(models.IntegerChoices): """ Na kolik danou znalost účastník ovládá v daném Hlasování (větší číslo = víc zná) """ 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 bych, že to úplně umím" NEUMIM = -1, "Tohle vůbec neznám" odpoved = models.IntegerField(u"odpověď", choices=Odpoved.choices, blank=False, null=False) #: :py:class:`Odpověď ` na HlasováníOZnalostech From 0a587511554e3031bcc24f384d4a64226f472063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 9 Feb 2025 22:32:16 +0100 Subject: [PATCH 36/37] =?UTF-8?q?P=C5=99edn=C3=A1=C5=A1ky=20od=C5=99=C3=A1?= =?UTF-8?q?dkov=C3=A1n=C3=AD=20(odstavce)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prednasky/templates/prednasky/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prednasky/templates/prednasky/base.html b/prednasky/templates/prednasky/base.html index 37ea178c..326370f1 100644 --- a/prednasky/templates/prednasky/base.html +++ b/prednasky/templates/prednasky/base.html @@ -15,7 +15,7 @@ {{ form_set_prednasky.management_form }} {% for f, p in formy_a_prednasky %}

    {{p.nazev}} ({{p.org}})

    -

    {{p.anotace}}

    +

    {{p.anotace | linebreaksbr}}

    {{p.obor}}
    {{p.obtiznost}}
    {% if p.klicova %} {{p.klicova}}
    {% endif%} @@ -30,7 +30,7 @@ {% for f, z in formy_a_znalosti %} {% if forloop.first %}

    Jak moc znáš následující?

    {% endif %}

    {{z.nazev}}

    -

    {{z.text}}

    +

    {{z.text | linebreaksbr}}

    {{ f }}
    {% endfor %} From 955dd60235793eabf0c1978e2861c4b026c9b999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Havelka?= Date: Sun, 9 Feb 2025 22:46:29 +0100 Subject: [PATCH 37/37] =?UTF-8?q?Aktualizace=20p=C5=99id=C3=A1v=C3=A1tka?= =?UTF-8?q?=20=C3=BAloh=20a=20probl=C3=A9m=C5=AF=20(desetinn=C3=A1=20?= =?UTF-8?q?=C4=8D=C3=ADsla=20a=20=C4=8D=C3=ADslo=20m=C3=ADsto=20d=C3=ADlu)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tvorba/views/docasne.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tvorba/views/docasne.py b/tvorba/views/docasne.py index 453909ca..9b2a435f 100644 --- a/tvorba/views/docasne.py +++ b/tvorba/views/docasne.py @@ -27,7 +27,7 @@ class HromadnePridaniForm(Form): """ Formulář pro hromadné přidání úložek a problémů """ tema = CharField(label="Název tématu:") - dil = IntegerField(label="Díl:", min_value=1) + cislo = IntegerField(label="Číslo:", min_value=1) body = CharField(label="Počty bodů (0 pro problém) oddělené čárkami:") def clean_tema(self): @@ -41,7 +41,7 @@ class HromadnePridaniForm(Form): def clean_body(self): """ Kontrola, že `body` je seznam čísel """ try: - list(map(int, self.cleaned_data["body"].split(","))) + list(map(float, self.cleaned_data["body"].split(","))) except ValueError: raise ValidationError("Špatný formát bodů") return self.cleaned_data['body'] @@ -64,21 +64,21 @@ class HromadnePridaniView(FormView): """ Upravený Pavlův skript na hromadné přidání úložek a problémů. """ cd = form.cleaned_data tema = cd["tema"] - dil = cd["dil"] - body = list(map(int, cd["body"].split(","))) + cislo = cd["cislo"] + body = list(map(float, cd["body"].split(","))) t = Problem.objects.get(nazev__exact=tema, nadproblem=None) with transaction.atomic(): - pfx = f"{t.nazev}, díl {dil}, " + pfx = f"{t.nazev}, " for k, b in enumerate(body, 1): u = Uloha.objects.create( nadproblem=t, - nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {k}", + nazev=pfx + f"{'úloha' if b > 0 else 'problém'} {cislo}.{k}", autor=t.autor, garant=t.garant, max_body=b, - cislo_zadani=Cislo.get(t.rocnik.rocnik, dil), + cislo_zadani=Cislo.get(t.rocnik.rocnik, cislo), kod=k, stav=Problem.STAV_ZADANY, )