From 7332466a319db406410a6b5ff1b506130038a8f0 Mon Sep 17 00:00:00 2001 From: Anet Date: Tue, 11 Feb 2020 22:40:12 +0100 Subject: [PATCH 01/41] =?UTF-8?q?testutils:=20rozbit=C3=A1=20verze=20kde?= =?UTF-8?q?=20padaj=C3=AD=20vcn,=20nezn=C3=A1mo=20pro=C4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/testutils.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/seminar/testutils.py b/seminar/testutils.py index b81d09f8..82ec58fe 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -19,6 +19,13 @@ zlinska = None # tohle bude speciální škola, které později dodáme kontaktn logger = logging.getLogger(__name__) +# testuje unikátnost vygenerovaného jména +def __unikatni_jmeno(osoby, jmeno, prijmeni): + for os in osoby: + if os.jmeno == jmeno and os.prijmeni == prijmeni: + return 0 + else: return 1 + def gen_osoby(rnd, size): logger.info('Generuji osoby (size={})...'.format(size)) @@ -48,6 +55,19 @@ def gen_osoby(rnd, size): pohlavi = rnd.randint(0,1) jmeno = rnd.choice([jmena_m, jmena_f][pohlavi]) prijmeni = rnd.choice([prijmeni_m, prijmeni_f][pohlavi]) + pokusy = 0 + max_pokusy = 120*size + while (not __unikatni_jmeno and pokusy < max_pokusy): + # pokud jméno a příjmení není unikátní, zkoušíme generovat nová + # do daného limitu (abychom se nezacyklili do nekonečna při málo jménech a příjmeních + # ze kterých se generuje) + jmeno = rnd.choice([jmena_m, jmena_f][pohlavi]) + prijmeni = rnd.choice([prijmeni_m, prijmeni_f][pohlavi]) + pokusy = pokusy + 1 + if pokusy >= max_pokusy: + print("Chyba, na danou velikost testovacích dat příliš málo možných" + " jmen a příjmení") + exit prezdivka = rnd.choice(prezdivky) email = "@".join([unidecode.unidecode(jmeno), rnd.choice(domain)]) telefon = "".join([str(rnd.choice([k for k in range(10)])) for i in range(9)]) @@ -165,11 +185,12 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) k = 0 for rocnik in rocniky: k+=1 + print("Generuji {}. číslo.".format(k)) cisla = rocnik_cisla[k-1] for ci in range(3, len(cisla)+1): # pro všechna čísla resitele_size = round(7/8 * 30 * size) # očekáváný celkový počet řešitelů - poc_res = rnd.randint(round(resitele_size/8), round(3*resitele_size/4)) - # dané číslo řeší něco mezi osminou a tříčtvrtinou všech řešitelů + poc_res = rnd.randint(round(resitele_size/8), round(resitele_size/4)) + # dané číslo řeší něco mezi osminou a čtvrtinou všech řešitelů # (náhodná hausnumera, možno změnit) # účelem je, aby se řešení generovala z menší množiny řešitelů a tedy # bylo více řešení od jednoho řešitele daného čísla @@ -228,9 +249,10 @@ def gen_ulohy_do_cisla(rnd, organizatori, resitele, rocnik_cisla, rocniky, size) p.save() # generování řešení - poc_reseni = rnd.randint(size // 2, size * 2) - # generujeme náhodný počet řešení + poc_reseni = rnd.randint(poc_res, poc_res * 4) + # generujeme náhodný počet řešení vzhledem k počtu řešitelů čísla for ri in range(poc_reseni): + #print("Generuji {}-té řešení".format(ri)) if rnd.randint(1, 10) == 6: # cca desetina řešení od více řešitelů res_vyber = rnd.sample(resitele_cisla, rnd.randint(2, 5)) @@ -432,8 +454,10 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori) cisla = rocnik_cisla[k-1] temata = rocnik_temata[k-1] for ci in range(len(cisla)): + print("Generuji {}-té číslo".format(ci)) cislo = cisla[ci-1] mozna_tema_vcn = cislo.cislonode.first_child + # kdybyste nad tím někdo taky přemýšleli, tak vcn == VCisleNode :) while mozna_tema_vcn != None: if type(mozna_tema_vcn) != TemaVCisleNode: mozna_tema_vcn = mozna_tema_vcn.succ @@ -521,7 +545,7 @@ def gen_novinky(rnd, organizatori): def otec_syn(otec, syn): bratr = otec.first_child - syn.succ = bratr + syn.ucc = bratr otec.first_child = syn syn.save() otec.save() From f7e9b00613c16ad2d5ec21766e1ad2acebd14088 Mon Sep 17 00:00:00 2001 From: Anet Date: Tue, 11 Feb 2020 23:07:45 +0100 Subject: [PATCH 02/41] =?UTF-8?q?testdata:=20zm=C4=9Bna=20na=20men=C5=A1?= =?UTF-8?q?=C3=AD=20po=C4=8Det=20ro=C4=8Dn=C3=ADk=C5=AF=20a=20v=C3=ADce=20?= =?UTF-8?q?=C5=99e=C5=A1en=C3=AD=20->=20v=C3=ADce=20=C5=99e=C5=A1en=C3=AD?= =?UTF-8?q?=20od=20jednoho=20=C5=99e=C5=A1itele?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/management/commands/testdata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seminar/management/commands/testdata.py b/seminar/management/commands/testdata.py index d7c65367..dbbd908d 100644 --- a/seminar/management/commands/testdata.py +++ b/seminar/management/commands/testdata.py @@ -38,7 +38,8 @@ class Command(BaseCommand): if not options['no_migrate']: call_command('migrate', no_input=True) self.stdout.write('Vytvarim uzivatele "admin" (heslo "admin") a pseudo-nahodna data ...') - create_test_data(size=8) + create_test_data(size=5) + # menší počet ročníků, aby se zrychlilo generování dat a bylo dost úloh self.stdout.write('Vytvoreno {} uzivatelu, {} skol, {} resitelu, {} rocniku, {} cisel,' ' {} problemu, {} reseni.'.format(User.objects.count(), Skola.objects.count(), Resitel.objects.count(), Rocnik.objects.count(), Cislo.objects.count(), From d99e4064e83b14c97104ccdad68a0eba10e5196a Mon Sep 17 00:00:00 2001 From: Anet Date: Wed, 19 Feb 2020 22:51:23 +0100 Subject: [PATCH 03/41] pridani cisla do urls, zmena defaultniho parametru pro testovaci data, opravovani vysledkovky cisla ve views (aktualne nefunkcni) --- seminar/testutils.py | 2 +- seminar/urls.py | 2 +- seminar/views.py | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/seminar/testutils.py b/seminar/testutils.py index 82ec58fe..3ede409c 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -545,7 +545,7 @@ def gen_novinky(rnd, organizatori): def otec_syn(otec, syn): bratr = otec.first_child - syn.ucc = bratr + syn.succ = bratr otec.first_child = syn syn.save() otec.save() diff --git a/seminar/urls.py b/seminar/urls.py index bc1c89a8..c4f7f479 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -23,7 +23,7 @@ urlpatterns = [ path('archiv/temata/', views.ArchivTemataView.as_view()), path('rocnik//', views.RocnikView.as_view(), name='seminar_rocnik'), - #path('cislo/./', views.CisloView.as_view(), name='seminar_cislo'), + path('cislo/./', views.CisloView.as_view(), name='seminar_cislo'), path('problem//', views.ProblemView.as_view(), name='seminar_problem'), #path('problem/(?P\d+)/(?P\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'), diff --git a/seminar/views.py b/seminar/views.py index e174ab28..abb1ad77 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -430,11 +430,12 @@ def sloupec_s_poradim(seznam_s_body): def __soucet_resitele_problemu(problem, resitel, cislo, soucet): # sečteme body za daný problém přes všechna řešení daného problému # od daného řešitele - reseni_resitele = problem.hodnoceni_set.filter(reseni__resitele=resitel, + reseni_resitele = Reseni.objects.filter(resitele__in=resitel) + hodnoceni_resitele = problem.hodnoceni.filter(reseni__in=reseni_resitele, cislo_body=cislo) # XXX chyba na řádku výše - řešení může mít více řešitelů, asi chceme contains # nebo in - for r in reseni_resitele: + for r in hodnoceni_resitele: soucet += r.body # a přičteme k tomu hodnocení všech podproblémů @@ -450,7 +451,8 @@ def body_resitele_problemu_v_cisle(problem, resitel, cislo): # vrátí list všech problémů s body v daném čísle, které již nemají nadproblém def hlavni_problemy_cisla(cislo): - hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all() # hodnocení, která se vážou k danému číslu + hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all() + # hodnocení, která se vážou k danému číslu reseni = [h.reseni for h in hodnoceni] problemy = [h.problem for h in hodnoceni] @@ -585,7 +587,8 @@ class RocnikView(generic.DetailView): #context['vysledkovka'] = vysledkovka_rocniku(context["rocnik"]) #context['vysledkovka_s_neverejnymi'] = vysledkovka_rocniku(context["rocnik"], jen_verejne=False) - context['temata_v_rocniku'] = verejna_temata(context["rocnik"]) + #context['temata_v_rocniku'] = verejna_temata(context["rocnik"]) + # FIXME: opravit vylistování témat v ročníku return context From 85fa967a1571943dd8db9c8438032a082cc84c87 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 26 Feb 2020 20:08:35 +0100 Subject: [PATCH 04/41] Popis rocnik_temat --- seminar/testutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/seminar/testutils.py b/seminar/testutils.py index 82ec58fe..12ab6343 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -611,6 +611,7 @@ def create_test_data(size = 6, rnd = None): # generování témat, zatím v prvních třech číslech po jednom # FIXME: více témat + # rocnik_temata je pole polí trojic (první číslo :int, poslední číslo :int, téma:Tema), přičemž každé vnitřní pole odpovídá ročníku a FIXME: je to takhle fuj a když to někdo vidí poprvé, tak je z toho smutný, protože vůbec neví, co se děje a co má čekat. rocnik_temata = gen_temata(rnd, rocniky, rocnik_cisla, organizatori) # generování úloh k tématům ve všech číslech From c0f7c028f4f901fe33d1e012047c815213a60e54 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 26 Feb 2020 20:19:10 +0100 Subject: [PATCH 05/41] Popis rocnik_cisla --- seminar/testutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/seminar/testutils.py b/seminar/testutils.py index 12ab6343..2808d1eb 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -604,6 +604,7 @@ def create_test_data(size = 6, rnd = None): rocniky = gen_rocniky(last_rocnik, size) # cisla + # rocnik_cisla je pole polí čísel (typ Cislo), vnitřní pole odpovídají jednotlivým ročníkům. rocnik_cisla = gen_cisla(rnd, rocniky) # generování obyčejných úloh do čísel From 3e2238a3e19b587d8cb486c177d9fff6681ff9c2 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 26 Feb 2020 21:10:54 +0100 Subject: [PATCH 06/41] =?UTF-8?q?Koment=C3=A1=C5=99e=20a=20=C3=BAprava=20g?= =?UTF-8?q?en=5Ftemata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/testutils.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/seminar/testutils.py b/seminar/testutils.py index 2808d1eb..e1b3d6d8 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -390,17 +390,15 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): co = ["téma", "záření", "stavení", "jiskření", "jelito", "drama", "kuře", "moře", "klání", "proudění", "čekání"] poc_oboru = rnd.randint(1, 2) - poc_op = rnd.randint(1, 3) rocnik_temata = [] - k = 0 - for rocnik in rocniky: - k+=1 - n = 0 + # Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát vždycky jeden ročník a k němu příslušná čísla. + for rocnik, cisla in zip(rocniky, rocnik_cisla): + kod = 1 temata = [] - cisla = rocnik_cisla[k-1] - for ci in range(1, 3): - n+=1 + # Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno + for zacatek_tematu in range(1, 3): + # Vygenerujeme téma t = Tema.objects.create( # atributy třídy Problem nazev=" ".join([rnd.choice(jake), rnd.choice(co)]), @@ -408,21 +406,30 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): zamereni=rnd.sample(["M", "F", "I", "O", "B"], poc_oboru), autor=rnd.choice(organizatori), garant=rnd.choice(organizatori), - kod=str(n), + kod=str(kod), # atributy třídy Téma tema_typ=rnd.choice(Tema.TEMA_CHOICES)[0], rocnik=rocnik, - abstrakt = "Abstrakt tematka {}".format(n) + abstrakt = "Abstrakt tematka {}".format(kod) ) - konec_tematu = min(rnd.randint(ci, 7), len(cisla)) - for i in range(ci, konec_tematu+1): + kod += 1 + + # Vymyslíme, kdy skončí + konec_tematu = min(rnd.randint(zacatek_tematu, 7), len(cisla)) + + # Vyrobíme TemaVCisleNody pro obsah + for i in range(zacatek_tematu, konec_tematu+1): node = TemaVCisleNode.objects.create(tema = t) otec = cisla[i-1].cislonode otec_syn(otec, node) - t.opravovatele.set(rnd.sample(organizatori, poc_op)) + # Vymyslíme, kdo to bude opravovat + poc_opravovatelu = rnd.randint(1, 3) + t.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu)) + + # Uložíme všechno t.save() - temata.append((ci, konec_tematu, t)) + temata.append((zacatek_tematu, konec_tematu, t)) rocnik_temata.append(temata) return rocnik_temata From b17c4e45b2b4df1a1cedc5466f6a7737b1ea9b05 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 26 Feb 2020 23:40:02 +0100 Subject: [PATCH 07/41] =?UTF-8?q?Testdata:=20Hez=C4=8D=C3=AD=20gen=5Fulohy?= =?UTF-8?q?=5Fk=5Ftematum,=20p=C3=A1r=20zm=C4=9Bn=20v=20gen=5Ftemata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/testutils.py | 132 +++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 56 deletions(-) diff --git a/seminar/testutils.py b/seminar/testutils.py index e1b3d6d8..a1646861 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -9,7 +9,7 @@ from django.db import transaction import unidecode import logging -from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky +from seminar.models import Skola, Resitel, Rocnik, Cislo, Problem, Reseni, PrilohaReseni, Nastaveni, Soustredeni, Soustredeni_Ucastnici, Soustredeni_Organizatori, Osoba, Organizator, Prijemce, Tema, Uloha, Konfera, KonferaNode, TextNode, UlohaVzorakNode, RocnikNode, CisloNode, TemaVCisleNode, Text, Hodnoceni, UlohaZadaniNode, Novinky, TreeNode from django.contrib.flatpages.models import FlatPage from django.contrib.sites.models import Site @@ -395,7 +395,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): # Věříme, že rocnik_cisla je pole polí čísel podle ročníků, tak si necháme dát vždycky jeden ročník a k němu příslušná čísla. for rocnik, cisla in zip(rocniky, rocnik_cisla): kod = 1 - temata = [] + letosni_temata = [] # Do každého ročníku vymyslíme tři (zatím) témata, v každém z prvních čísel jedno for zacatek_tematu in range(1, 3): # Vygenerujeme téma @@ -420,6 +420,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): # Vyrobíme TemaVCisleNody pro obsah for i in range(zacatek_tematu, konec_tematu+1): node = TemaVCisleNode.objects.create(tema = t) + # FIXME: Není to off-by-one? otec = cisla[i-1].cislonode otec_syn(otec, node) @@ -429,8 +430,8 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori): # Uložíme všechno t.save() - temata.append((zacatek_tematu, konec_tematu, t)) - rocnik_temata.append(temata) + letosni_temata.append((zacatek_tematu, konec_tematu, t)) + rocnik_temata.append(letosni_temata) return rocnik_temata @@ -455,84 +456,103 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori) "netriviální aplikace diferenciálních rovnic", "zadání je vnitřně" "sporné", "nepopsatelně jednoduché", "pokud jste na to nepřišli," "tak jste fakt hloupí"] - k = 0 - for rocnik in rocniky: - k+=1 - cisla = rocnik_cisla[k-1] - temata = rocnik_temata[k-1] - for ci in range(len(cisla)): - print("Generuji {}-té číslo".format(ci)) - cislo = cisla[ci-1] - mozna_tema_vcn = cislo.cislonode.first_child - # kdybyste nad tím někdo taky přemýšleli, tak vcn == VCisleNode :) - while mozna_tema_vcn != None: - if type(mozna_tema_vcn) != TemaVCisleNode: - mozna_tema_vcn = mozna_tema_vcn.succ + # Ke každému ročníku si vezmeme příslušná čísla a témata + for rocnik, cisla, temata in zip(rocniky, rocnik_cisla, rocnik_temata): + # Do každého čísla nagenerujeme ke každému témátku pár úložek + for cislo in cisla: + print("Generuji úložky do {}-tého čísla".format(cislo.poradi)) + # Vzorák bude o dvě čísla dál + cislo_se_vzorakem = Cislo.objects.filter( + rocnik=rocnik, + poradi=str(int(cislo.poradi) + 2), + ) + # Pokud není číslo pro vzorák, tak se dá do posledního čísla (i kdyby tam mělo být zadání i řešení...) + # Tohle sice umožňuje vygenerovat vzorák do čísla dávno po konci témátka, ale to nám pro jednoduchost nevadí. + if len(cislo_se_vzorakem) == 0: + cislo_se_vzorakem = cisla[-1] + else: + cislo_se_vzorakem = cislo_se_vzorakem.first() + + # FIXME: Tenhle generátor dát asi někam jinam + def potomci(node): + if not isinstance(node, TreeNode): + raise ValueError("Typ {} nemá potomky", type(node)) + current_child = node.first_child + while current_child is not None: + yield current_child + current_child = current_child.succ + + for mozna_tema_node in potomci(cislo.cislonode): + if not isinstance(mozna_tema_node, TemaVCisleNode): continue - else: - tema = mozna_tema_vcn.tema + tema_node = mozna_tema_node + tema = tema_node.tema - if not temata[int(tema.kod)-1][1] >= ci+2: - mozna_tema_vcn = mozna_tema_vcn.succ + # Pokud už témátko skončilo, žádné úložky negenerujeme + # FIXME: Bylo by hezčí, kdyby se čísla předávala jako Cislo a ne jako int v té trojici (start, konec, tema) + if not temata[int(tema.kod)-1][1] >= int(cislo_se_vzorakem.poradi): continue - for i in range(1, rnd.randint(1, 4)): - poc_op = rnd.randint(1, 4) - poc_oboru = rnd.randint(1, 2) - p = Uloha.objects.create( + # Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla + for kod in range(1, rnd.randint(1, 4)): + u = Uloha.objects.create( nazev=": ".join([tema.nazev, - "úloha {}.".format(i)]), + "úloha {}.".format(kod)]), nadproblem=tema, stav=Problem.STAV_ZADANY, zamereni=tema.zamereni, autor=tema.autor, garant=tema.garant, - kod=str(i), + kod=str(kod), cislo_zadani=cislo, - cislo_reseni=cisla[ci+2-1], - cislo_deadline=cisla[ci+2-1], + cislo_reseni=cislo_se_vzorakem, + cislo_deadline=cislo_se_vzorakem, max_body = rnd.randint(1, 8) ) - p.opravovatele.set(rnd.sample(organizatori, poc_op)) + poc_opravovatelu = rnd.randint(1, 4) + u.opravovatele.set(rnd.sample(organizatori, poc_opravovatelu)) - text_zadani = Text.objects.create( - na_web = " ".join( - [rnd.choice(sloveso), - rnd.choice(koho), - rnd.choice(ceho), - rnd.choice(jmeno), - rnd.choice(kde)] - ), - do_cisla = " ".join( + # Samotný obsah následně vzniklého Textu zadání + obsah = " ".join( [rnd.choice(sloveso), rnd.choice(koho), rnd.choice(ceho), rnd.choice(jmeno), rnd.choice(kde)] ) - ) + text_zadani = Text.objects.create( + na_web = obsah, + do_cisla = obsah, + ) zad = TextNode.objects.create(text = text_zadani) - uloha_zadani = UlohaZadaniNode.objects.create(uloha=p, first_child = zad) - p.ulohazadaninode = uloha_zadani - otec_syn(mozna_tema_vcn, uloha_zadani) - # TODO dělá se podproblém takto??? TODO + uloha_zadani = UlohaZadaniNode.objects.create(uloha=u, first_child = zad) + u.ulohazadaninode = uloha_zadani + + # FIXME: Tohle dává zadání vždy jako prvního potomka témátka, spec. se naskládají v opačném pořadí a nemůže mezi nimi vzniknout žádný (orgo-)text + otec_syn(tema_node, uloha_zadani) + # Text vzoráku stejně + obsah = rnd.choice(reseni) text_vzoraku = Text.objects.create( - na_web = rnd.choice(reseni), - do_cisla = rnd.choice(reseni) + na_web = obsah, + do_cisla = obsah, ) vzorak = TextNode.objects.create(text = text_vzoraku) - uloha_vzorak = UlohaVzorakNode.objects.create(uloha=p, first_child = vzorak) - p.UlohaVzorakNode = uloha_vzorak - res_tema_vcn = cisla[ci+2-1].cislonode.first_child - while res_tema_vcn.tema != tema: - res_tema_vcn = res_tema_vcn.succ - otec_syn(res_tema_vcn, uloha_vzorak) + uloha_vzorak = UlohaVzorakNode.objects.create(uloha=u, first_child = vzorak) + u.UlohaVzorakNode = uloha_vzorak + + # Najdeme správný TemaVCisleNode pro vložení vzoráku + res_tema_node = None; + for node in potomci(cislo_se_vzorakem.cislonode): + if isinstance(node, TemaVCisleNode) and node.tema == tema: + res_tema_node = node + if res_tema_node is None: + raise LookupError("Nenalezen Node pro vložení vzoráku") + # FIXME: Stejný problém jako výše: vzoráky se dají na začátek v opačném pořadí. + otec_syn(res_tema_node, uloha_vzorak) - p.save() - - mozna_tema_vcn = mozna_tema_vcn.succ + u.save() return def gen_novinky(rnd, organizatori): @@ -552,7 +572,7 @@ def gen_novinky(rnd, organizatori): def otec_syn(otec, syn): bratr = otec.first_child - syn.ucc = bratr + syn.succ = bratr otec.first_child = syn syn.save() otec.save() From 2583ea117a40464eb5b7655758d73bf54037b676 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 27 Feb 2020 00:54:41 +0100 Subject: [PATCH 08/41] =?UTF-8?q?Zkop=C3=ADrov=C3=A1ny=20JSONy=20z=20new?= =?UTF-8?q?=5Fdesign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flat.json | 332 ++++++++++++++++++++++++++++++++++++++++++++++ sitetree_new.json | 1 + 2 files changed, 333 insertions(+) create mode 100644 flat.json create mode 100644 sitetree_new.json diff --git a/flat.json b/flat.json new file mode 100644 index 00000000..61a7a2db --- /dev/null +++ b/flat.json @@ -0,0 +1,332 @@ +[ +{ + "model": "flatpages.flatpage", + "pk": 1, + "fields": { + "url": "/o-seminari/", + "title": "O semin\u00e1\u0159i", + "content": "

Pro\u010d \u0159ešit práv\u011b M&M?

\r\n\r\n
    \r\n\t
  • Jako jediný koresponde\u010dní seminá\u0159 vypisujeme p\u0159ísp\u011bvková témata.\r\n\r\n\t
      \r\n\t\t
    • \u200bK nim nám m\u016f\u017eeš v pr\u016fb\u011bhu celého roku posílat prakticky jakékoliv související p\u0159ísp\u011bvky a reagovat na p\u0159ísp\u011bvky ostatních \u0159ešitel\u016f.
    • \r\n\t\t
    • Funguje to vlastn\u011b jako skute\u010dný výzkum a jeho publikování. 
    • \r\n\t\t
    • Pro ty, kte\u0159í cht\u011bjí více podn\u011bt\u016f, nebo si netroufnou na práci na tématu, máme i úlohy – stejn\u011b jako ostatní koresponden\u010dní seminá\u0159e.
    • \r\n\t
    \r\n\t
  • \r\n\t
  • Jsme mezioboroví, tak\u017ee si u nás ka\u017edý najde to svoje. Navíc je u nás dovoleno \u0159ešit problémy, a\u0165 u\u017e v tématech \u010di v úlohách, r\u016fznými prost\u0159edky. Tak\u017ee pokud t\u0159eba nevíš jak teoreticky vy\u0159ešit fyzikální úlohu, ale umíš \u0159ešení naprogramovat, tak sm\u011ble do toho!
  • \r\n\t
  • Na našich soust\u0159ed\u011bních ti m\u016f\u017eeme nabídnou skute\u010dn\u011b širokou škálu zá\u017eitk\u016f.\r\n\t
      \r\n\t\t
    • Máme p\u0159ipraveny úvodní, klasické i tajuplné p\u0159ednášky ze všech t\u0159í obor\u016f. Krom toho p\u0159ednášíme i na po\u017eádání – sta\u010dí, kdy\u017e si vybereš téma, které t\u011b zajímá.
    • \r\n\t\t
    • P\u0159ipravujeme konfery: Skupina ú\u010dastník\u016f pod vedením zkušeného organizátora \u0159eší zadaný problém a výsledky pak ostatním prezentuje na malé v\u011bdecké konferenci. Práce na konfe\u0159e je velmi podobná skute\u010dné v\u011bdecké práci. Podívej se na fotky ODKAZ
    • \r\n\t\t
    • Na ka\u017edém soust\u0159ed\u011bní na tebe \u010deká velké mno\u017eství denních i no\u010dních her ODKAZ NA FOTKY, uvnit\u0159 i venku. Jsme dob\u0159e materiálov\u011b i organiza\u010dn\u011b vybaveni na náro\u010dn\u011bjší outdoorové aktivity, jako je nap\u0159íklad lezení po skalách. A náš tým umí p\u0159ipravit i hry, které pro\u017eiješ všemi šesti smysly.
    • \r\n\t
    \r\n\t
  • \r\n
\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 2, + "fields": { + "url": "/", + "title": "Semin\u00e1\u0159 M&M", + "content": "

Vítejte na stránce seminá\u0159e MaM!

\r\n", + "enable_comments": false, + "template_name": "home.html", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 3, + "fields": { + "url": "/co-je-MaM/uvod/", + "title": "Co je M&M?", + "content": "

M&M je mezioborov\u00fd koresponden\u010dn\u00ed semin\u00e1\u0159 pro studenty st\u0159edn\u00edch \u0161kol zam\u011b\u0159en\u00fd na matematiku, fyziku a informatiku. Ro\u010dn\u011b vyd\u00e1v\u00e1me p\u0159ibli\u017en\u011b sedm \u010d\u00edsel \u010dasopisu, ve kter\u00e9m najde\u0161 t\u00e9mata k b\u00e1d\u00e1n\u00ed, dopln\u011bn\u00e9 o zaj\u00edmav\u00e9 \u00falohy a o \u010dl\u00e1nky jak od n\u00e1s, organiz\u00e1tor\u016f, tak od v\u00e1s, \u0159e\u0161itel\u016f. \u010casopis je zdarma.

\r\n\r\n

M&M je taky sout\u011b\u017e. Za v\u0161echny p\u0159\u00edsp\u011bvky k t\u00e9mat\u016fm, \u0159e\u0161en\u00ed \u00faloh i \u010dl\u00e1nky ud\u011blujeme body. Na z\u00e1klad\u011b z\u00edskan\u00fdch bod\u016f sestavujeme v\u00fdsledkovou listinu. Na nejlep\u0161\u00ed \u0159e\u0161itele \u010dekaj\u00ed knihy a deskovky. Autor nejlep\u0161\u00edho p\u0159\u00edsp\u011bvku do t\u00e9m\u00e1tka si bude moci smlsnout na dortu. Taky se m\u016f\u017ee\u0161 dostat na Matfyz bez p\u0159ij\u00edma\u010dek (viz n\u00ed\u017ee). A hlavn\u011b, p\u0159ibli\u017en\u011b dvacet p\u011bt nej\u00fasp\u011b\u0161n\u011bj\u0161\u00edch \u0159e\u0161itel\u016f zveme dvakr\u00e1t do roka na soust\u0159ed\u011bn\u00ed.

\r\n\r\n

Jak se zapojit

\r\n\r\n

V pr\u016fb\u011bhu \u0161koln\u00edho roku vych\u00e1z\u00ed zpravidla sedm \u010d\u00edsel \u010dasopisu. V nich jsou zadan\u00e9 r\u016fzn\u00e9 podn\u011bty k p\u0159em\u00fd\u0161len\u00ed. Pokud t\u011b n\u011bkter\u00fd zaujme, pokus se k n\u011bmu n\u011bco napsat a poslat n\u00e1m to \u2013 podrobnosti najde\u0161 v sekci Jak \u0159e\u0161it.

\r\n\r\n

Soust\u0159ed\u011bn\u00ed

\r\n\r\n

V\u017edy na podzim a na ja\u0159e p\u0159ipravujeme pro na\u0161e nejlep\u0161\u00ed \u0159e\u0161itele t\u00fddenn\u00ed soust\u0159ed\u011bn\u00ed. To se obvykle kon\u00e1 n\u011bkde v bl\u00edzkosti p\u011bkn\u00e9 p\u0159\u00edrody. Soust\u0159ed\u011bn\u00ed je \u010d\u00e1ste\u010dn\u011b odborn\u00e9, m\u00e1me pro v\u00e1s p\u0159ipraveny p\u0159edn\u00e1\u0161ky ze v\u0161elijak\u00fdch tradi\u010dn\u00edch i netradi\u010dn\u00edch z\u00e1kout\u00ed nejen matematiky, fyziky i informatiky. Dost \u010dasu je v\u011bnov\u00e1no i zaj\u00edmav\u00fdm a z\u00e1bavn\u00fdm hr\u00e1m, venku i uvnit\u0159. P\u0159edev\u0161\u00edm je ale soust\u0159ed\u011bn\u00ed p\u0159\u00edle\u017eitost, jak potkat fajn lidi s podobn\u00fdmi z\u00e1jmy! M\u016f\u017ee\u0161 si prohl\u00e9dnout fotky z p\u0159ede\u0161l\u00fdch soust\u0159ed\u011bn\u00ed.

\r\n\r\n

\r\n\r\n

P\u0159ij\u00edmac\u00ed zkou\u0161ky na MFF

\r\n\r\n

Matematicko-fyzik\u00e1ln\u00ed fakulta Univerzity Karlovy se rozhodla \u00fasp\u011b\u0161n\u00fdm \u0159e\u0161itel\u016fm na\u0161eho koresponden\u010dn\u00edho semin\u00e1\u0159e odpustit p\u0159ij\u00edmac\u00ed zkou\u0161ky. Konkr\u00e9tn\u011b se to t\u00fdk\u00e1 t\u011bch \u0159e\u0161itel\u016f, kte\u0159\u00ed z\u00edskaj\u00ed za rok alespo\u0148 65 bod\u016f. Ti od n\u00e1s dostanou \u201eosv\u011bd\u010den\u00ed \u00fasp\u011b\u0161n\u00e9ho \u0159e\u0161itele\u201c, kter\u00e9 pak mohou p\u0159edlo\u017eit fakult\u011b.

", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 4, + "fields": { + "url": "/clanky/uvod/", + "title": "\u010cl\u00e1nky", + "content": "

V M&M publikujeme \u010dlánky – stejn\u011b jako v opravdovém v\u011bdeckém \u010dasopise. Setkat se u nás m\u016f\u017eeš jednak s \u010dlánky od organizátor\u016f (v rámci n\u011bjakého seriálu, nebo jen tak), jednak nám m\u016f\u017eeš poslat \u010dlánek ty sám.

\r\n\r\n

Organizátorské \u010dlánky

\r\n\r\n

Organizáto\u0159i t\u011b ve svých \u010dláncích obvykle cht\u011bjí nau\u010dit n\u011bco, co by se ti mohlo hodit nebo líbit.

\r\n\r\n

\u010clánky od \u0159ešitel\u016f

\r\n\r\n

Ty sám nám také m\u016f\u017eeš zaslat \u010dlánek k publikování. Krom \u010dlánku k tématu (který spadá do jiné sekce) to nej\u010dast\u011bji bude nejspíš \u010dlánek o tvé konfe\u0159e ze soust\u0159ed\u011bní. M\u016f\u017eeš nám ale poslat i pojednání o \u010demkoli jiném, o \u010dem si myslíš, \u017ee by to mohlo ostatní \u0159ešitele zajímat. My pak tvoji práci zredigujeme a otiskneme. 

\r\n\r\n

Proto\u017ee víc hlav víc ví, m\u016f\u017eete na \u010dláncích pracovat i ve skupinách – stejn\u011b jako p\u0159i \u0159ešení témat!

\r\n\r\n

P\u0159i psaní \u010dlánku do M&M je dobré mít na pam\u011bti základní zásady pro psaní v\u011bdeckého \u010dlánku.

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 5, + "fields": { + "url": "/archiv/temata/", + "title": "T\u00e9mata", + "content": "

Tady se pracuje

\r\n

\r\n Na t\u00e9to str\u00e1nce velmi intenzivn\u011b pracujeme.\r\n Za do\u010dasnou nedostupnost se omlouv\u00e1me.\r\n Zkuste p\u0159ej\u00edt na tituln\u00ed str\u00e1nku\r\n nebo se pod\u00edvat na aktu\u00e1ln\u00ed zad\u00e1n\u00ed.\r\n

\r\n ", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 6, + "fields": { + "url": "/archiv/ulohy/", + "title": "\u00dalohy", + "content": "

Tady se pracuje

\r\n

\r\n Na t\u00e9to str\u00e1nce velmi intenzivn\u011b pracujeme.\r\n Za do\u010dasnou nedostupnost se omlouv\u00e1me.\r\n Zkuste p\u0159ej\u00edt na tituln\u00ed str\u00e1nku\r\n nebo se pod\u00edvat na aktu\u00e1ln\u00ed zad\u00e1n\u00ed.\r\n

\r\n ", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 7, + "fields": { + "url": "/co-je-MaM/jak-resit/", + "title": "Jak \u0159e\u0161it?", + "content": "

\u010c\u00edm se n\u00e1\u0161 semin\u00e1\u0159 li\u0161\u00ed od v\u011bt\u0161iny ostatn\u00edch sout\u011b\u017e\u00ed, jsou p\u0159\u00edsp\u011bvkov\u00e1 t\u00e9mata. Jedn\u00e1 se o simulaci v\u011bdeck\u00e9 pr\u00e1ce ve smyslu, \u017ee zad\u00e1me jist\u00fd okruh probl\u00e9m\u016f, nad kter\u00fdm m\u016f\u017ee\u0161 n\u00e1sleduj\u00edc\u00ed rok b\u00e1dat. O v\u00fdsledky sv\u00e9 pr\u00e1ce se pak pod\u011bl\u00ed\u0161 s n\u00e1mi a s ostatn\u00edmi \u0159e\u0161iteli a my tvou pr\u00e1ci ohodnot\u00edme. V ka\u017ed\u00e9 s\u00e9rii b\u00fdvaj\u00ed t\u00e9mata roz\u0161i\u0159ov\u00e1na, nav\u00edc jsou n\u011bkter\u00e9 zadan\u00e9 probl\u00e9my omezeny term\u00ednem odesl\u00e1n\u00ed, proto je pot\u0159eba sv\u00e9 v\u00fdsledky pos\u00edlat u\u017e pr\u016fb\u011b\u017en\u011b. Na ka\u017ed\u00fd ro\u010dn\u00edk t\u00e9mat vypisujeme p\u011bt a\u017e \u0161est.

\r\n\r\n

\u0158e\u0161en\u00ed, kter\u00e9 n\u00e1m po\u0161le\u0161, m\u016f\u017ee b\u00fdt dvou r\u016fzn\u00fdch typ\u016f:

\r\n\r\n

\u00dalohy

\r\n\r\n

V r\u00e1mci ka\u017ed\u00e9ho t\u00e9matu b\u00fdv\u00e1 zad\u00e1na jedna nebo dv\u011b men\u0161\u00ed \u00falohy, kter\u00e9 dan\u00fd probl\u00e9m ur\u010dit\u00fdm zp\u016fsobem rozv\u00edj\u00ed. Tyto \u00falohy b\u00fdvaj\u00ed trochu t\u011b\u017e\u0161\u00ed ne\u017e obvykl\u00e9 \u0161koln\u00ed, jejich \u0159e\u0161en\u00ed \u010dasto vy\u017eaduje bu\u010f hlub\u0161\u00ed zamy\u0161len\u00ed nebo n\u011bjak\u00fd trik. St\u0159edo\u0161kolsk\u00e9 znalosti by na n\u011b ale m\u011bly sta\u010dit. Odevzd\u00e1v\u00e1n\u00ed \u00faloh se omezuje term\u00ednem odesl\u00e1n\u00ed, pot\u00e9 b\u00fdv\u00e1 zve\u0159ejn\u011bno vzorov\u00e9 \u0159e\u0161en\u00ed. U ka\u017ed\u00e9 \u00falohy je uveden po\u010det bod\u016f za spr\u00e1vn\u00e9 \u0159e\u0161en\u00ed. P\u0159im\u011b\u0159enou \u010d\u00e1st z t\u011bchto bod\u016f lze z\u00edskat i za ne\u00fapln\u00e9 \u0159e\u0161en\u00ed. A naopak za velmi zaj\u00edmav\u00e9 nebo elegantn\u00ed \u0159e\u0161en\u00ed m\u016f\u017ee\u0161 dostat i bodovou pr\u00e9mii.

\r\n\r\n

\u010cl\u00e1nky

\r\n\r\n

Syst\u00e9m t\u00e9mat poskytuje p\u0159\u00edle\u017eitost pro podrobn\u011bj\u0161\u00ed rozbor dan\u00e9ho okruhu probl\u00e9m\u016f. V\u00fdsledek takov\u00e9 pr\u00e1ce v\u0161ak vy\u017eaduje o n\u011bco v\u00edc prostoru ne\u017eli klasick\u00e9 \u0159e\u0161en\u00ed, proto je tv\u00fdm \u00fakolem napsat \u010dl\u00e1nek zab\u00fdvaj\u00edc\u00ed se probl\u00e9mem, kter\u00fd sis vybral. M\u016f\u017ee to b\u00fdt z\u00e1znam o proveden\u00e9m experimentu, teoretick\u00e9m v\u00fdpo\u010dtu \u010di \u00favaze, napsan\u00e9m programu \u010di o dal\u0161\u00edch (podle tebe zaj\u00edmav\u00fdch) ot\u00e1zk\u00e1ch z dan\u00e9ho okruhu. 

\r\n\r\n

Jak na to?

\r\n\r\n

Vy\u0159e\u0161 podprobl\u00e9m

\r\n\r\n

Vyber si n\u011bkter\u00fd z navrhovan\u00fdch podprobl\u00e9m\u016f, kter\u00fdm se chce\u0161 zab\u00fdvat, p\u0159\u00edpadn\u011b si navrhni podprobl\u00e9m vlastn\u00ed (to b\u00fdv\u00e1 ohodnoceno bodov\u00fdm bonusem). Podprobl\u00e9m pak vy\u0159e\u0161 podobn\u011b jako \u00falohu z \u010d\u00edsla. Podrobn\u00e9 \u0159e\u0161en\u00ed n\u011bkter\u00e9ho z podprobl\u00e9m\u016f bude bodov\u011b hodnoceno v\u00fdrazn\u011b l\u00e9pe ne\u017e souhrnn\u00fd \u010dl\u00e1nek kr\u00e1tce zmi\u0148uj\u00edc\u00ed kdeco. I \u010d\u00e1ste\u010dn\u00e9 \u0159e\u0161en\u00ed je lep\u0161\u00ed ne\u017e \u017e\u00e1dn\u00e9. Sv\u00e9 \u0159e\u0161en\u00ed pak hezky sepi\u0161, aby m\u011blo formu \u010dl\u00e1nku, a \u010dl\u00e1nek n\u00e1m po\u0161li. Vedouc\u00ed t\u00e9matu ho pak ohodnot\u00ed, p\u0159\u00edpadn\u011b zkoriguje a publikuje na webu \u010di dokonce v \u010d\u00edsle. \u010c\u00edm bl\u00ed\u017ee bude forma tv\u00e9ho p\u0159\u00edsp\u011bvku publikovateln\u00e9mu \u010dl\u00e1nku, t\u00edm lep\u0161\u00edho bodov\u00e9ho ohodnocen\u00ed dos\u00e1hne\u0161. Moder\u00e1tor t\u00e9matu ho pak ohodnot\u00ed, p\u0159\u00edpadn\u011b zkoriguje a publikuje na webu \u010di dokonce v \u010d\u00edsle. \u010c\u00edm bl\u00ed\u017ee bude forma tv\u00e9ho p\u0159\u00edsp\u011bvku publikovateln\u00e9mu \u010dl\u00e1nku, t\u00edm lep\u0161\u00edho bodov\u00e9ho ohodnocen\u00ed dos\u00e1hne\u0161.

\r\n\r\n

Inspiruj se

\r\n\r\n

Velmi d\u016fle\u017eitou vlastnost\u00ed t\u00e9mat je, \u017ee m\u016f\u017ee\u0161 na \u010dl\u00e1nky ostatn\u00edch reagovat \u2013 rozv\u00edjet je, nebo naopak bo\u0159it jejich p\u0159edstavy. Proto se hod\u00ed poslat tak\u00e9 \u010d\u00e1ste\u010dn\u00e1 \u0159e\u0161en\u00ed, post\u0159ehy, nebo n\u00e1pady na dal\u0161\u00ed podprobl\u00e9my, kter\u00e9 t\u0159eba nezvl\u00e1dne\u0161 vy\u0159e\u0161it s\u00e1m. Takov\u00e9to p\u0159\u00edsp\u011bvky d\u00e1vaj\u00ed prostor ostatn\u00edm a mohou je d\u00e1l inspirovat \u2013 ostatn\u00ed zase sv\u00fdmi p\u0159\u00edsp\u011bvky mohou inspirovat tebe.

\r\n\r\n

Proto\u017ee v\u00edc hlav v\u00edc v\u00ed, m\u016f\u017eete na t\u00e9matech pracovat i ve skupin\u00e1ch.

\r\n\r\n\r\n

Nad n\u00e1pady k t\u00e9mat\u016fm m\u016f\u017ee\u0161 p\u0159em\u00fd\u0161let cel\u00fd rok a\u017e do term\u00ednu odevzd\u00e1n\u00ed posledn\u00ed s\u00e9rie \u00faloh. Samoz\u0159ejm\u011b, \u010d\u00edm d\u0159\u00edve n\u00e1m \u010dl\u00e1nek po\u0161le\u0161, t\u00edm d\u0159\u00edve na n\u011bj ostatn\u00ed budou moci zareagovat.

\r\n\r\n

Pi\u0161 hezk\u00e9 \u010dl\u00e1nky

\r\n\r\n

\u010cl\u00e1nek k t\u00e9matu by m\u011bl po obsahov\u00e9 a form\u00e1ln\u00ed str\u00e1nce odpov\u00eddat v\u011bdeck\u00e9mu \u010dl\u00e1nku. M\u016f\u017ee\u0161 se pod\u00edvat na stru\u010dn\u00fd p\u0159ehled toho, jak by m\u011bl takov\u00fd v\u011bdeck\u00fd \u010dl\u00e1nek vypadat.

\r\n\r\n

Pro\u010d t\u00e9mata?

\r\n\r\n

T\u00e9mata vytv\u00e1\u0159ej\u00ed prostor pro vlastn\u00ed tv\u016fr\u010d\u00ed (\u010dasto v\u011bdeckou) \u010dinnost, jej\u00edm\u017e smyslem je krom jin\u00e9ho p\u0159in\u00e9st ostatn\u00edm \u0159e\u0161itel\u016fm nov\u00e9 podn\u011bty a inspirovat je k dal\u0161\u00edm n\u00e1pad\u016fm. Krom\u011b samotn\u00e9ho b\u00e1d\u00e1n\u00ed je d\u016fle\u017eit\u00e1 i komunikace mezi lidmi, kter\u00e1 je obvykle zprost\u0159edkov\u00e1na odborn\u00fdmi \u010dasopisy a v\u011bdeck\u00fdmi konferencemi. \u010casopis M&M je ur\u010den pr\u00e1v\u011b pro p\u00edsemnou komunikaci. \u00dastn\u00ed formu prezentace si m\u016f\u017ee\u0161 vyzkou\u0161et na na\u0161em soust\u0159ed\u011bn\u00ed, kde se tradi\u010dn\u011b kon\u00e1 mal\u00e1 v\u011bdeck\u00e1 konference.

\r\n\r\n

Jak poslat \u0159e\u0161en\u00ed

\r\n\r\n

Sv\u00e1 \u0159e\u0161en\u00ed m\u016f\u017ee\u0161 poslat bu\u010f elektronicky na n\u00e1\u0161 e-mail mam@matfyz.cz, nebo po\u0161tou na na\u0161i adresu. Pokud pot\u0159ebuje\u0161 k \u0159e\u0161en\u00ed p\u0159ilo\u017eit n\u011bjak\u00fd hodn\u011b velk\u00fd soubor, pou\u017eij n\u011bkterou voln\u011b dostupnou slu\u017ebu pro sd\u00edlen\u00ed soubor\u016f (DropboxGoogle Drive, \u2026) nebo n\u00e1s kontaktuj na e-mailu mam@matfyz.cz a p\u0159ed\u00e1n\u00ed domluv\u00edme.

\r\n\r\n

Pokud pos\u00edl\u00e1\u0161 \u0159e\u0161en\u00ed elektronicky, v\u011bz, \u017ee n\u00e1s daleko v\u00edce pot\u011b\u0161\u00ed pdfko s textem ne\u017e vyfocen\u00e9 ru\u010dn\u011b psan\u00e9 \u0159e\u0161en\u00ed. Ka\u017edou \u00falohu pros\u00edm po\u0161li v samostatn\u00e9m souboru resp. na samostatn\u00e9m list\u011b A4, aby si \u00falohy mohli rozd\u011blit r\u016fzn\u00ed opravuj\u00edc\u00ed. Na ka\u017ed\u00fd list uve\u010f svoje jm\u00e9no a \u010d\u00edslo \u00falohy \u010di t\u00e9matu. Na tvou po\u0161tovn\u00ed adresu ti pak budou zdarma chodit dal\u0161\u00ed \u010d\u00edsla na\u0161eho \u010dasopisu.

\r\n\r\n

Ke sv\u00e9mu prvn\u00edmu \u0159e\u0161en\u00ed p\u0159ilo\u017e pros\u00edm tak\u00e9 sv\u00e9 jm\u00e9no, adresu, e-mail, \u0161kolu a rok maturity. Pokud chce\u0161 jet na soust\u0159ed\u011bn\u00ed, uve\u010f pros\u00edm i telefon. A neboj, tyto \u00fadaje budeme vyu\u017e\u00edvat pouze pro pot\u0159eby M&M. Mimo \u00fadaj\u016f na v\u00fdsledkov\u00e9 listin\u011b (jm\u00e9no, \u0161kola, ro\u010dn\u00edk) je nebudeme nikde zve\u0159ej\u0148ovat.

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 8, + "fields": { + "url": "/co-je-MaM/FAQ/", + "title": "\u010casto kladen\u00e9 dotazy", + "content": "

Jaký je smysl M&M?

\r\n\r\n

Smyslem M&M je pomáhat student\u016fm s nadáním na matematiku, fyziku \u010di informatiku rozvíjet jejich schopnosti a zprost\u0159edkovávat jejich setkávání a navazování p\u0159átelství.

\r\n\r\n

Jsem student st\u0159ední školy, posledních ro\u010dník\u016f základní školy \u010di ekvivalentních ro\u010dník\u016f víceletého gymnázia: Co mi M&M m\u016f\u017ee p\u0159inést?

\r\n\r\n

Pokud t\u011b zajímá matematika, fyzika nebo informatika, m\u016f\u017eeme ti nabídnout zajímavé podn\u011bty k p\u0159emýšlení. A pokud se zú\u010dastníš n\u011bkterého z našich soust\u0159ed\u011bní, pak i p\u0159átelský kolektiv fajn lidí podobných tob\u011b.

\r\n\r\n

Jsem u\u010ditel matematiky, fyziky \u010di informatiky na st\u0159ední škole: K \u010demu mi M&M m\u016f\u017ee být dobré?

\r\n\r\n

Jestli\u017ee nabídnete svým student\u016fm M&M k \u0159ešení, m\u016f\u017ee je náš \u010dasopis motivovat k dalšímu rozvoji. Náš archiv úloh a témat m\u016f\u017eete pou\u017eít jako zdroj náro\u010dn\u011bjších úloh pro nadané \u017eáky. Naše úlohy jsou \u0159ešitelné se st\u0159edoškolskými znalostmi – s trochou p\u0159emýšlení.

\r\n\r\n

Jsem rodi\u010d studenta nadaného na matematiku, fyziku \u010di informatiku: Je M&M pro moje dít\u011b dobré?

\r\n\r\n

Jednozna\u010dn\u011b ano – pokud o nás samo bude stát. Primárn\u011b m\u016f\u017eeme Vaše dít\u011b odborn\u011b rozvíjet. Náš kolektiv je navíc velmi otev\u0159ený a dovede p\u0159ijmout i mláde\u017e, která má problém s integrací do b\u011b\u017eného školního kolektivu.

\r\n\r\n

Ješt\u011b n\u011bjaké dotazy?

\r\n\r\n

Sem s nimi!

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 9, + "fields": { + "url": "/soustredeni/", + "title": "Informace", + "content": "

Pro na\u0161e nejlep\u0161\u00ed \u0159e\u0161itele po\u0159\u00e1d\u00e1me dvakr\u00e1t do roka t\u00fddenn\u00ed soust\u0159ed\u011bn\u00ed pln\u00e9 odborn\u00e9ho programu i nejr\u016fzn\u011bj\u0161\u00ed z\u00e1bavy.

\r\n\r\n\r\n

Pro\u010d jet na soust\u0159ed\u011bn\u00ed?

\r\n\r\n

P\u0159edn\u00e1\u0161ky

\r\n\r\n

V\u011bt\u0161inou t\u011b ka\u017ed\u00fd den \u010dekaj\u00ed dv\u011b devades\u00e1timinutov\u00e9 p\u0159edn\u00e1\u0161ky. Vybrat si obvykle m\u016f\u017ee\u0161 mezi matematikou, fyzikou a informatikou, proto\u017ee se konaj\u00ed t\u0159i p\u0159edn\u00e1\u0161ky z\u00e1rove\u0148. N\u011bkter\u00e9 p\u0159edn\u00e1\u0161ky jsou leh\u010d\u00ed, jin\u00e9 t\u011b\u017e\u0161\u00ed, obecn\u011b je ale jejich \u00farove\u0148 vhodn\u00e1 pr\u00e1v\u011b pro zv\u00eddav\u00e9 st\u0159edo\u0161kol\u00e1ky. P\u0159edn\u00e1\u0161\u00edme jak klasick\u00e1 t\u00e9mata, tak t\u00e9mata nev\u0161edn\u00ed, z\u00e1kulisn\u00ed \u010di dokonce obskurn\u00ed. Kdy\u017e bude\u0161 organiz\u00e1tory hodn\u011b prosit, mo\u017en\u00e1 se dostane i na n\u011bjakou \u010dernou magii!

\r\n\r\n

Krom toho p\u0159edn\u00e1\u0161\u00edme i na po\u017e\u00e1d\u00e1n\u00ed \u2013 sta\u010d\u00ed, kdy\u017e si vybere\u0161 t\u00e9ma, kter\u00e9 t\u011b zaj\u00edm\u00e1, a oslov\u00ed\u0161 toho spr\u00e1vn\u00e9ho organiz\u00e1tora.

\r\n\r\n

Lid\u00e9

\r\n\r\n

Pozn\u00e1\u0161 lidi, pro kter\u00e9 je p\u0159em\u00fd\u0161len\u00ed obl\u00edbenou \u010dinnost\u00ed a pro kter\u00e9 matematika nen\u00ed sprost\u00e9 slovo. P\u0159edev\u0161\u00edm to jsou ale lidi, kte\u0159\u00ed se r\u00e1di bav\u00ed a se kter\u00fdmi si u\u017eije\u0161 mnoho legrace u j\u00eddla, b\u011bhem her, na v\u00fdlet\u011b, jen tak, p\u0159i hran\u00ed na kytaru nebo p\u0159i \u0161ar\u00e1d\u011bn\u00ed (pokud nev\u00ed\u0161, co tohle slovo znamen\u00e1, je na \u010dase to zjistit!).

\r\n\r\n

Konfery

\r\n\r\n

Konfery jsou na\u0161\u00ed specialitou. Ve skupin\u011b \u00fa\u010dastn\u00edk\u016f a pod veden\u00edm zku\u0161en\u00e9ho organiz\u00e1tora m\u016f\u017ee\u0161 zkusit pracovat na zadan\u00e9m probl\u00e9mu a v\u00fdsledky pak ostatn\u00edm prezentovat na mal\u00e9 v\u011bdeck\u00e9 konferenci. Pr\u00e1ce na konfe\u0159e je velmi podobn\u00e1 skute\u010dn\u00e9 v\u011bdeck\u00e9 pr\u00e1ci. M\u00e1me za sebou nap\u0159\u00edklad stavbu katapultu, po\u010d\u00edta\u010dovou synt\u00e9zu zvuku \u010di tropickou geometrii.

\r\n\r\n

Hry

\r\n\r\n

M\u00e1me pro tebe p\u0159ipravenou celou \u0159adu denn\u00edch i no\u010dn\u00edch her, uvnit\u0159 i venku, strategick\u00fdch i ak\u010dn\u00edch. A n\u011bkdy tohle v\u0161echno dohromady. Chceme, aby sis mohl/a zkusit \u010dinnosti, ke kter\u00fdm se b\u011b\u017en\u011b nedostane\u0161. St\u0159elba z luku, lezen\u00ed po skal\u00e1ch, slackline \u010di celono\u010dn\u00ed \u0161ifrova\u010dka? Nen\u00ed probl\u00e9m!

\r\n\r\n

Legenda

\r\n\r\n

Ka\u017ed\u00e9 soust\u0159ed\u011bn\u00ed m\u00e1 sv\u00e9 vlastn\u00ed prost\u0159ed\u00ed \u010di p\u0159\u00edb\u011bh, kter\u00fd j\u00edm prov\u00e1z\u00ed. U\u017e jsme byli ve starov\u011bk\u00e9m \u0158ecku \u010di pod podlahou obcho\u010f\u00e1ku, cestovali jsme \u010dasem a tak\u00e9 jsme bojovali s krvela\u010dn\u00fdmi zmutovan\u00fdmi tule\u0148\u00e1tky, kter\u00e1 se nakonec uk\u00e1zala b\u00fdt filma\u0159sk\u00fdm trikem. Co n\u00e1s \u010dek\u00e1 p\u0159\u00ed\u0161t\u011b?

\r\n\r\n

Absence ve \u0161kole

\r\n\r\n

Ne\u017e pojede\u0161 na soust\u0159ed\u011bn\u00ed, po\u0161leme ti ofici\u00e1ln\u00ed omluvenku od MFF UK. Jeliko\u017e je soust\u0159ed\u011bn\u00ed pln\u00e9 odborn\u00e9ho programu, v\u011bt\u0161ina \u0161kol na\u0161e \u0159e\u0161itele bez probl\u00e9mu uvol\u0148uje. N\u011bkter\u00e9 \u0161koly dokonce \u00fa\u010dast na soust\u0159ed\u011bn\u00ed nezapo\u010d\u00edt\u00e1vaj\u00ed do absence. V\u017edy je ale dobr\u00e9 se informovat, jak \u00fa\u010dast na podobn\u00fdch akc\u00edch \u0159e\u0161\u00ed tvoje \u0161kola, a p\u0159\u00edpadn\u011b se osobn\u011b domluvit s \u0159editelem \u010di \u0159editelkou.

\r\n\r\n

Kapacita soust\u0159ed\u011bn\u00ed

\r\n\r\n

Proto\u017ee chceme zachovat p\u0159\u00e1telskou a komorn\u00ed atmosf\u00e9ru soust\u0159ed\u011bn\u00ed, zveme na soust\u0159ed\u011bn\u00ed zhruba dvacet nej\u00fasp\u011b\u0161n\u011bj\u0161\u00edch \u0159e\u0161itel\u016f koresponden\u010dn\u00edho semin\u00e1\u0159e. N\u011bkolik dal\u0161\u00edch \u0159e\u0161itel\u016f zveme jako n\u00e1hradn\u00edky pro p\u0159\u00edpad, \u017ee by n\u011bkte\u0159\u00ed pozvan\u00ed nemohli. Pokud t\u011b na soust\u0159ed\u011bn\u00ed nepozveme, nezoufej a zkus v p\u0159\u00ed\u0161t\u00edm p\u016flroce v\u00edc \u0159e\u0161it t\u00e9mata. Dostat se mezi nejlep\u0161\u00edch dvacet \u0159e\u0161itel\u016f je s trochou p\u00edle hra\u010dka.

\r\n\r\n

 

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 11, + "fields": { + "url": "/soustredeni/pripravujeme/", + "title": "P\u0159ipravujeme", + "content": "

Pozdimn\u00ed soust\u0159ed\u011bn\u00ed

\r\n\r\n

se bude konat v term\u00ednu od 12. do 20. \u0159\u00edjna 2019.

", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 12, + "fields": { + "url": "/soustredeni/probehlo/", + "title": "Prob\u011bhlo", + "content": "

Tady se pracuje

\r\n\r\n

Na této stránce velmi intenzivn\u011b pracujeme. Za do\u010dasnou nedostupnost se omlouváme. Zkuste p\u0159ejít na titulní stránku nebo se podívat na aktuální zadání.

\r\n\r\n

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 16, + "fields": { + "url": "/zadani/temata/", + "title": "T\u00e9mata", + "content": "

T\u00e9mata jsou texty nejen z oblasti matematiky, fyziky a informatiky, kter\u00e9 popisuj\u00ed n\u011bjak\u00fd probl\u00e9m a jsou doprov\u00e1zeny n\u00e1vodn\u00fdmi \u00falohami. Va\u0161\u00edm \u00fakolem je zamyslet se nad dan\u00fdm probl\u00e9mem a sepsat va\u0161e \u00favahy ve form\u011b kr\u00e1tk\u00e9ho textu.

\r\n\r\n

Jak \u0159e\u0161it t\u00e9ma?

\r\n\r\n

 

\r\n\r\n

AKTU\u00c1LN\u00cd T\u00c9MATA

", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 17, + "fields": { + "url": "/zadani/aktualni-cislo/", + "title": "Aktu\u00e1ln\u00ed \u010d\u00edslo", + "content": "

Tady pat\u0159í aktuální \u010díslo.

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 18, + "fields": { + "url": "/zadani/vysledkova-listina/", + "title": "V\u00fdsledkov\u00e1 listina", + "content": "

Tady se pracuje

\r\n\r\n

Na této stránce velmi intenzivn\u011b pracujeme. Za do\u010dasnou nedostupnost se omlouváme. Zkuste p\u0159ejít na titulní stránku nebo se podívat na aktuální zadání.

\r\n\r\n

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 19, + "fields": { + "url": "/clanky/org/", + "title": "Organiz\u00e1torsk\u00e9 \u010dl\u00e1nky", + "content": "

Tady se pracuje

\r\n

\r\n Na t\u00e9to str\u00e1nce velmi intenzivn\u011b pracujeme.\r\n Za do\u010dasnou nedostupnost se omlouv\u00e1me.\r\n Zkuste p\u0159ej\u00edt na tituln\u00ed str\u00e1nku\r\n nebo se pod\u00edvat na aktu\u00e1ln\u00ed zad\u00e1n\u00ed.\r\n

\r\n ", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 20, + "fields": { + "url": "/clanky/resitel/", + "title": "\u0158e\u0161itelsk\u00e9 \u010dl\u00e1nky", + "content": "

\u0158e\u0161itelsk\u00e9 \u010dl\u00e1nky

\r\n\r\n

Na t\u00e9to str\u00e1nce se budou postupn\u011b objevovat r\u016fznorod\u00e9 \u010dl\u00e1nky od na\u0161ich \u0159e\u0161itel\u016f.

\r\n\r\n

Obsah:

\r\n\r\n

Ond\u0159ej Knopp: Flatland (ji\u017e brzy)

\r\n\r\n

 

\r\n\r\n

 

\r\n\r\n
\r\n

Ond\u0159ej Knopp: Flatland (15b)

\r\n\r\n

 

\r\n\r\n
\r\n

 

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 21, + "fields": { + "url": "/clanky/jak-psat-vedecky-clanek/", + "title": "Jak ps\u00e1t v\u011bdeck\u00fd \u010dl\u00e1nek", + "content": "

Pro psan\u00ed v\u011bdeck\u00fdch \u010dl\u00e1nk\u016f byla vytvo\u0159ena n\u011bkter\u00e1 obecn\u00e1 pravidla, kter\u00e1 usnad\u0148uj\u00ed jejich \u010ditelnost a mo\u017enost vyhledat pot\u0159ebn\u00e9 informace. Na tomto m\u00edst\u011b bychom ti cht\u011bli uk\u00e1zat, jak by m\u011bl takov\u00fd v\u011bdeck\u00fd \u010dl\u00e1nek vypadat po obsahov\u00e9 i form\u00e1ln\u00ed str\u00e1nce.

\r\n\r\n

O \u010dem ps\u00e1t?

\r\n\r\n

Kvalita v\u011bdeck\u00e9ho \u010dl\u00e1nku z\u00e1vis\u00ed hlavn\u011b na tom, kolik nov\u00fdch poznatk\u016f p\u0159in\u00e1\u0161\u00ed. Je velmi vhodn\u00e9, aby obsahem bylo jen to, co m\u016f\u017ee zaj\u00edmat ostatn\u00ed \u0159e\u0161itele dan\u00e9ho t\u00e9matu.

\r\n\r\n

D\u00e1le plat\u00ed pravidlo, \u017ee jeden \u010dl\u00e1nek by se m\u011bl t\u00fdkat pr\u00e1v\u011b jednoho probl\u00e9mu. Pokud p\u00ed\u0161e\u0161 o v\u00edce probl\u00e9mech najednou, zamysli se nad t\u00edm, jestli by nebylo mo\u017en\u00e9 napsat v\u00edce \u010dl\u00e1nk\u016f. \u010c\u00edm stru\u010dn\u011bj\u0161\u00ed a p\u0159ehledn\u011bj\u0161\u00ed \u010dl\u00e1nek je a \u010d\u00edm v\u00edce nov\u00fdch poznatk\u016f na tak omezen\u00e9m prostoru obsahuje, t\u00edm \u010diteln\u011bj\u0161\u00ed a zaj\u00edmav\u011bj\u0161\u00ed je pro ostatn\u00ed \u0159e\u0161itele.

\r\n\r\n

Struktura \u010dl\u00e1nku

\r\n\r\n

Ka\u017ed\u00fd \u010dl\u00e1nek by m\u011bl m\u00edt vhodn\u00fd n\u00e1zev (titulek), kter\u00fd dok\u00e1\u017ee p\u0159it\u00e1hnout \u010dten\u00e1\u0159e, kte\u0159\u00ed se zaj\u00edmaj\u00ed o dan\u00e9 t\u00e9ma. Nadpis je ta \u010d\u00e1st \u010dl\u00e1nku, kter\u00e9 si ka\u017ed\u00fd v\u0161imne hned na prvn\u00ed pohled. Nepodce\u0148uj jeho zn\u011bn\u00ed a dej pozor na to, aby p\u0159esn\u011b vystihoval to, \u010demu se v \u010dl\u00e1nku v\u011bnuje\u0161.

\r\n\r\n

\u00favodu bys m\u011bl popsat probl\u00e9m, kter\u00fdm ses zab\u00fdval, d\u00e1le na co a na koho jsi navazoval a tak\u00e9 pro\u010d ses dan\u00fdm probl\u00e9mem zab\u00fdval. V\u011bt\u0161inou je zde uvedena hypot\u00e9za, kter\u00e1 je dokazov\u00e1na v samotn\u00e9m \u010dl\u00e1nku. Sou\u010d\u00e1st\u00ed \u00favodu je i motivace \u010dten\u00e1\u0159e a prvn\u00ed p\u0159ibl\u00ed\u017een\u00ed podstaty probl\u00e9mu.

\r\n\r\n

Za \u00favodem pak pokra\u010duje podrobn\u011bj\u0161\u00ed popis postup\u016f, kter\u00e9 vyu\u017e\u00edv\u00e1\u0161, a zd\u016fvodn\u011bn\u00ed tv\u00fdch tvrzen\u00ed.

\r\n\r\n

Kvalita v\u011bdeck\u00e9ho \u010dl\u00e1nku je d\u00e1na p\u0159edev\u0161\u00edm t\u00edm, jakou m\u00e1 informa\u010dn\u00ed hodnotu. Informacemi v \u010dl\u00e1nku mohou b\u00fdt jak v\u00fdsledky vlastn\u00edho v\u00fdzkumu, tak p\u0159ehledn\u00e9 shrnut\u00ed a zpracov\u00e1n\u00ed jinde uve\u0159ejn\u011bn\u00fdch v\u00fdsledk\u016f t\u00fdkaj\u00edc\u00edch se zkouman\u00e9ho probl\u00e9mu. Takov\u00e9mu zpracov\u00e1n\u00ed se \u0159\u00edk\u00e1 re\u0161er\u0161e a pokud podobn\u00e9 shrnut\u00ed je\u0161t\u011b neexistuje, m\u016f\u017ee b\u00fdt p\u0159\u00ednosn\u00e9 stejn\u011b jako vlastn\u00ed nov\u00fd v\u00fdzkum.

\r\n\r\n

Je d\u016fle\u017eit\u00e9, aby v\u0161echna tvrzen\u00ed uveden\u00e1 v \u010dl\u00e1nku byla dostate\u010dn\u011b podlo\u017eena fakty. \u010cl\u00e1nek tedy nelze zalo\u017eit na n\u011b\u010dem, o \u010dem si jen mysl\u00ed\u0161, \u017ee by to mohlo platit. Jak\u00e1koliv slo\u017eit\u011bj\u0161\u00ed tvrzen\u00ed v \u010dl\u00e1nku by m\u011bla b\u00fdt podpo\u0159ena bu\u010f odkazem na literaturu, anebo d\u016fkazem \u010di experimentem. M\u011bly by b\u00fdt tak\u00e9 uvedeny v\u0161echny p\u0159edpoklady pou\u017eit\u00fdch tvrzen\u00ed a teori\u00ed a v p\u0159\u00edpad\u011b experimentu podm\u00ednky, za kter\u00fdch byl prov\u00e1d\u011bn.

\r\n\r\n

z\u00e1v\u011bru je pak vhodn\u00e9 znovu shrnout hlavn\u00ed v\u00fdsledky sv\u00e9 pr\u00e1ce a p\u0159\u00edpadn\u011b polo\u017eit n\u011bjak\u00e9 dal\u0161\u00ed ot\u00e1zky souvisej\u00edc\u00ed s t\u00e9matem, jejich\u017e \u0159e\u0161en\u00ed by mohlo navazovat na tv\u016fj \u010dl\u00e1nek.

\r\n\r\n

Na \u00fapln\u00e9m konci se pak uv\u00e1d\u00ed seznam pou\u017eit\u00e9 literatury a literatury, na kterou bylo v \u010dl\u00e1nku odkazov\u00e1no. Obvykle se v \u010dl\u00e1nku pou\u017eije jen odkaz, nap\u0159. \u201e... Jak je uvedeno v [1], m\u016f\u017eeme v\u00fdraz upravit...\u201c V seznamu literatury se pak uvedou pln\u00e9 \u00fadaje o knize, \u010dl\u00e1nku \u010di internetov\u00e9 adrese. Dodr\u017euje se n\u00e1sleduj\u00edc\u00ed sch\u00e9ma:

\r\n\r\n

 

\r\n\r\n

[#] Autor. N\u00e1zev d\u00edla: podn\u00e1zev d\u00edla. \u010c\u00edslo vyd\u00e1n\u00ed. M\u00edsto vyd\u00e1n\u00ed: Ozna\u010den\u00ed nakladatele, Rok vyd\u00e1n\u00ed. Po\u010det str\u00e1nek. ISBN.

\r\n\r\n

Nap\u0159\u00edklad:
\r\n[1] O. Lepil, Z. Krupka. Fyzika pro gymn\u00e1zia: Optika. 2. vyd\u00e1n\u00ed. Praha: Prometheus, 1996. 167 s. ISBN 80\u201385849\u201371\u20132.

\r\n\r\n

Jednotliv\u00e9 \u010d\u00e1sti kr\u00e1tk\u00e9ho \u010dl\u00e1nku sta\u010d\u00ed odd\u011blit odstavci. Pokud se jedn\u00e1 o rozs\u00e1hlej\u0161\u00ed p\u0159\u00edsp\u011bvek, je lep\u0161\u00ed rozd\u011blit ho na n\u011bkolik sekc\u00ed s vhodn\u00fdmi podnadpisy.

\r\n\r\n

Jazyk a styl \u010dl\u00e1nku

\r\n\r\n

V odborn\u00e9m \u010dl\u00e1nku je nutn\u00e9 se vyjad\u0159ovat p\u0159esn\u011b a jednozna\u010dn\u011b. Je pot\u0159eba pou\u017e\u00edvat spr\u00e1vn\u00e9 term\u00edny, aby byl \u010dl\u00e1nek spr\u00e1vn\u011b pochopen ostatn\u00edmi \u010dten\u00e1\u0159i. Pokud pou\u017e\u00edv\u00e1\u0161 pom\u011brn\u011b neobvykl\u00fd pojem nebo zav\u00e1d\u00ed\u0161 n\u011bjak\u00fd nov\u00fd, tak je pot\u0159eba jej p\u0159esn\u011b definovat. V\u017edy si po\u0159\u00e1dn\u011b uv\u011bdom, kdo bude tv\u016fj \u010dl\u00e1nek \u010d\u00edst. Na tom tak\u00e9 z\u00e1le\u017e\u00ed, jak\u00e9 term\u00edny pou\u017eije\u0161 a jak moc podrobn\u011b je bude\u0161 vysv\u011btlovat.

\r\n\r\n

Aby byl \u010dl\u00e1nek v\u016fbec publikovateln\u00fd, m\u011bl by b\u00fdt naps\u00e1n bez jazykov\u00fdch a stylistick\u00fdch chyb. V \u010dl\u00e1nku, kter\u00fd je publikov\u00e1n, a tedy jej \u010dte \u0161ir\u0161\u00ed skupina lid\u00ed, pou\u017e\u00edv\u00e1me spisovn\u00fd jazyk. V M&Mku m\u016f\u017ee\u0161 ps\u00e1t \u010desky nebo slovensky.

\r\n\r\n

 

\r\n\r\n

Douf\u00e1me, \u017ee ti tento n\u00e1vod p\u0159i psan\u00ed \u010dl\u00e1nk\u016f pom\u016f\u017ee, a \u017ee i d\u00edky n\u011bmu na\u0161e t\u00e9m\u00e1tka z\u016fstanou p\u0159ehledn\u00e1, \u010diteln\u00e1 a zaj\u00edmav\u00e1.

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 22, + "fields": { + "url": "/co-je-MaM/kontakt/", + "title": "Kontakt", + "content": "

Sv\u00e1 \u0159e\u0161en\u00ed \u010di p\u0159\u00edpadn\u00e9 dotazy n\u00e1m m\u016f\u017eete pos\u00edlat bu\u010f klasickou, nebo elektronickou po\u0161tou:

\r\n\r\n\r\n\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n
\r\n\t\t\t

Adresa redakce:

\r\n\r\n\t\t\t

M&M, OPMK MFF UK
\r\n\t\t\tKe Karlovu 3
\r\n\t\t\t121 16 Praha 2

\r\n\t\t\t
\r\n\t\t\t

E-mailmam@matfyz.cz

\r\n\r\n\t\t\t

Facebook: Koresponden\u010dn\u00ed semin\u00e1\u0159 M&M

\r\n\r\n\t\t\t

Google Kalend\u00e1\u0159: casopis.mam@gmail.com

\r\n\t\t\t
\r\n\r\n

 

\r\n\r\n

B\u011bhem \u0161koln\u00edho roku je velk\u00e1 \u010d\u00e1st organiz\u00e1tor\u016f k zasti\u017een\u00ed na koleji 17. listopadu \u010di jinde po Praze.

\r\n\r\n

Adresa koleje:

\r\n\r\n

P\u00e1tkova 3
\r\n182 00, Praha 8

", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 23, + "fields": { + "url": "/odevzdat-reseni/muj-ucet/", + "title": "Odevzdat \u0159e\u0161en\u00ed", + "content": "

Svoje \u0159ešení nám prosím pošli na náš e-mail mam@matfyz.cz, nebo poštou na adresu redakce.

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 24, + "fields": { + "url": "/archiv/vysledky/", + "title": "V\u00fdsledkov\u00e9 listiny", + "content": "

Tady se pracuje

\r\n\r\n

Na této stránce velmi intenzivn\u011b pracujeme. Za do\u010dasnou nedostupnost se omlouváme. Zkuste p\u0159ejít na titulní stránku nebo se podívat na aktuální zadání.

\r\n\r\n

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 25, + "fields": { + "url": "/co-je-MaM/odmeny/", + "title": "Odm\u011bny", + "content": "

Odm\u011bny za um\u00edst\u011bn\u00ed v semin\u00e1\u0159i

\r\n\r\n

Ka\u017ed\u00fd rok oce\u0148ujeme 5 nejlep\u0161\u00edch \u0159e\u0161itel\u016f knihou a deskovou hrou dle jejich v\u00fdb\u011bru.
\r\nLetos m\u016f\u017ee\u0161 b\u00fdt mezi nimi i ty, sta\u010d\u00ed piln\u011b \u0159e\u0161it! :-)
\r\nN\u00e1sleduj\u00edc\u00ed knihy a deskovky si vybralo p\u011bt nej\u00fasp\u011b\u0161n\u011bj\u0161\u00edch \u0159e\u0161itel\u016f 23. ro\u010dn\u00edku semin\u00e1\u0159e:

\r\n\r\n

\"Odm\u011bny

\r\n\r\n

M\u016f\u017ee\u0161 se pod\u00edvat i na odm\u011bny z 22. a 21. ro\u010dn\u00edku.

\r\n\r\n

Odm\u011bny za tituly

\r\n\r\n

 Bc.MM (10 bod\u016f) \u2013 propiska
\r\n\"\"

\r\n\r\n

Mgr.MM (20 bod\u016f) \u2013 reflexn\u00ed p\u00e1ska
\r\n\"\"

\r\n\r\n

Dr.MM (50 bod\u016f) \u2013 hrne\u010dek
\r\n\"\"

\r\n\r\n

Doc.MM (100 bod\u016f) \u2013 deka
\r\n\"\"

\r\n\r\n

Prof.MM (200 bod\u016f) \u2013 mikina

\r\n\r\n

\"\"

\r\n\r\n

Akad.MM (500 bod\u016f) \u2013 tabule s n\u00e1pisem \u201eJsi fakt borec\u201c podepsan\u00e1 v\u0161emi organiz\u00e1tory

\r\n", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +}, +{ + "model": "flatpages.flatpage", + "pk": 27, + "fields": { + "url": "/co-je-MaM/mam-moc-casu/", + "title": "M\u00e1m moc \u010dasu!", + "content": "

Z\u00e1\u017eitkov\u00e9 akce

\r\n\r\n

Letn\u00ed a Zimn\u00ed \u0160kola Matematiky a Fyziky (L\u0160MF, Z\u0160MF) - \u0160MFko je v zim\u011b t\u00fddenn\u00ed, v l\u00e9t\u011b dvout\u00fddenn\u00ed, z\u00e1\u017eitkov\u00e1 akce ur\u010den\u00e1 st\u0159edo\u0161kol\u00e1k\u016fm se z\u00e1jmem o dal\u0161\u00ed sebevzd\u011bl\u00e1n\u00ed. Krom popul\u00e1rn\u011b nau\u010dn\u00fdch p\u0159edn\u00e1\u0161ek se na \u0161mfku prob\u011bhne\u0161 venku, vy\u0159\u00e1d\u00ed\u0161 se ve sn\u011bhu, u\u017eije\u0161 si vesel\u00fd vnit\u0159n\u00ed program a taky se pobav\u00ed\u0161 b\u011bhem spole\u010dn\u00e9ho \u0161ar\u00e1d\u011bn\u00ed, leno\u0161en\u00ed nebo hran\u00ed na kytaru.

\r\n\r\n

InterSoB - InterSoB je z\u00e1bavn\u00e1 a pou\u010dn\u00e1 jednodenn\u00ed sout\u011b\u017e st\u0159edo\u0161kolsk\u00fdch student\u016f, p\u0159i kter\u00e9 m\u00e1te mo\u017enost pod\u00edvat se netradi\u010dn\u00edm zp\u016fsobem do z\u00e1kulis\u00ed Masarykovy univerzity, vyzkou\u0161et si sv\u00e9 schopnosti v mnoha r\u016fzn\u00fdch oblastech, ud\u011blat si s kamar\u00e1dy zaj\u00edmav\u00fd v\u00fdlet po Brn\u011b a v neposledn\u00ed \u0159ad\u011b tak\u00e9 pom\u011b\u0159it svoje s\u00edly s dal\u0161\u00edmi t\u00fdmy.

\r\n\r\n

Dal\u0161\u00ed semin\u00e1\u0159e

\r\n\r\n

Koresponden\u010dn\u00ed Semin\u00e1\u0159 z Programov\u00e1n\u00ed (KSP) - KSP je semin\u00e1\u0159 ur\u010den\u00fd pro studenty st\u0159edn\u00edch a z\u00e1kladn\u00edch \u0161kol, kte\u0159\u00ed maj\u00ed z\u00e1jem nau\u010dit se n\u011bco z oblasti algoritm\u016f, logick\u00fdch \u00faloh, programov\u00e1n\u00ed a informatiky v\u016fbec. Na sv\u00e9 si v\u0161ak p\u0159ijdou i p\u0159\u00edznivci matematiky (a vlastn\u011b libovoln\u00e9ho p\u0159em\u00fd\u0161len\u00ed), je\u017eto oba obory maj\u00ed mnoho spole\u010dn\u00e9ho.

\r\n\r\n

FYzik\u00e1ln\u00ed KOresponden\u010dn\u00ed Semin\u00e1\u0159 (FYKOS) - FYKOS pro v\u00e1s p\u0159edstavuje mo\u017enost si zaj\u00edmav\u00fdm zp\u016fsobem roz\u0161\u00ed\u0159it ch\u00e1p\u00e1n\u00ed fyziky a proniknout do dal\u0161\u00edch, dosud nepoznan\u00fdch, oblast\u00ed t\u00e9to v\u011bdy. C\u00edlem FYKOSu je rozv\u00edjet fyzik\u00e1ln\u00ed my\u0161len\u00ed, proto\u017ee \u010dlov\u011bk, kter\u00fd se um\u00ed nad (nejen fyzik\u00e1ln\u00edmi) probl\u00e9my zamyslet a c\u00edt\u00ed touhu dobrat se k n\u011bjak\u00e9mu \u0159e\u0161en\u00ed, se uplatn\u00ed v\u0161ude, kde si schopnost\u00ed lidsk\u00e9ho mozku cen\u00ed.

\r\n\r\n

Matematick\u00fd koresponden\u010dn\u00ed semin\u00e1\u0159 PraSe (PRA\u017esk\u00fd SEmin\u00e1\u0159) - \u0158e\u0161en\u00edm \u00faloh tohoto semin\u00e1\u0159e z\u00edsk\u00e1\u0161 mnoho matematick\u00fdch znalost\u00ed a nau\u010d\u00ed\u0161 p\u0159esn\u011bji a srozumiteln\u011bji formulovat sv\u00e9 my\u0161lenky a z\u00e1v\u011bry. Semin\u00e1\u0159 je dobrou p\u0159\u00edpravou pro \u00fa\u010dast v nejr\u016fzn\u011bj\u0161\u00edch matematick\u00fdch sout\u011b\u017e\u00edch i pro dal\u0161\u00ed studium matematiky, ale schopnost logick\u00e9ho my\u0161len\u00ed, kterou si m\u016f\u017ee\u0161 procvi\u010dit, se ti v \u017eivot\u011b bude hodit, i kdy\u017e se v n\u011bm t\u0159eba pr\u00e1v\u011b matematice v\u011bnovat nehodl\u00e1\u0161.

\r\n\r\n

Pro mlad\u0161\u00ed sourozence

\r\n\r\n

Pikomat - Pikomat je matematick\u00fd semin\u00e1\u0159 ur\u010den\u00fd \u017e\u00e1k\u016fm \u0161est\u00fdch a\u017e dev\u00e1t\u00fdch t\u0159\u00edd z\u00e1kladn\u00edch \u0161kol a student\u016fm odpov\u00eddaj\u00edc\u00edch ro\u010dn\u00edk\u016f v\u00edcelet\u00fdch gymn\u00e1zi\u00ed. Spo\u010d\u00edv\u00e1 v \u0159e\u0161en\u00ed n\u011bkolika \u00faloh propojen\u00fdch p\u0159\u00edb\u011bhem. Sout\u011b\u017e\u00edc\u00ed ode\u0161le v dan\u00fdch term\u00ednech jednotliv\u00e9 p\u0159\u00edklady dan\u00e9 s\u00e9rie na adresu Pikomatu nebo je odevzd\u00e1 elektronicky. Organiz\u00e1to\u0159i oprav\u00ed do\u0161l\u00e9 \u00falohy, vypracuj\u00ed jejich vzorov\u00e1 \u0159e\u0161en\u00ed a sestav\u00ed v\u00fdsledkovou listinu. Na ja\u0159e se kon\u00e1 soust\u0159ed\u011bn\u00ed pro nejlep\u0161\u00ed \u0159e\u0161itele, v l\u00e9t\u011b pak t\u00e1bor pro v\u0161echny z\u00e1jemce.

\r\n\r\n

V\u00fdfuk (V\u00ddpo\u010dty Fyzik\u00e1ln\u00edch UKol\u016f) - V\u00fdfuk je samostatn\u00fd koresponden\u010dn\u00ed semin\u00e1\u0159 Matfyzu, kter\u00fd spad\u00e1 pod Katedru didaktiky fyziky. B\u011bhem \u0161koln\u00edho roku krom\u011b \u0161esti s\u00e9ri\u00ed semin\u00e1\u0159e organiz\u00e1to\u0159i p\u0159ipravuj\u00ed i podzimn\u00ed a jarn\u00ed setk\u00e1n\u00ed, letn\u00ed t\u00e1bor a N\u00e1boj junior.

\r\n\r\n

 

", + "enable_comments": false, + "template_name": "", + "registration_required": false, + "sites": [ + 1 + ] + } +} +] diff --git a/sitetree_new.json b/sitetree_new.json new file mode 100644 index 00000000..d1b7cfed --- /dev/null +++ b/sitetree_new.json @@ -0,0 +1 @@ +[{"model": "sitetree.tree", "pk": 1, "fields": {"title": "Hlavn\u00ed menu", "alias": "main_menu"}}, {"model": "sitetree.treeitem", "pk": 1, "fields": {"title": "Co je M&M", "hint": "", "url": "/co-je-MaM/uvod/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": null, "sort_order": 1, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 2, "fields": {"title": "Jak \u0159e\u0161it", "hint": "", "url": "/co-je-MaM/jak-resit/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": null, "sort_order": 2, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 3, "fields": {"title": "Aktu\u00e1ln\u00ed
ro\u010dn\u00edk", "hint": "", "url": "/zadani/aktualni/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": null, "sort_order": 3, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 4, "fields": {"title": "Soust\u0159ed\u011bn\u00ed", "hint": "", "url": "/soustredeni/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": null, "sort_order": 4, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 5, "fields": {"title": "Archiv", "hint": "", "url": "/archiv/cisla/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": null, "sort_order": 5, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 6, "fields": {"title": "P\u0159ihl\u00e1sit", "hint": "", "url": "/login/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": true, "access_restricted": false, "access_perm_type": 1, "parent": null, "sort_order": 6, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 21, "fields": {"title": "Profil", "hint": "", "url": "/profil/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": true, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": null, "sort_order": 21, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 7, "fields": {"title": "\u00davod", "hint": "", "url": "/co-je-MaM/uvod/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 1, "sort_order": 7, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 8, "fields": {"title": "Organiz\u00e1to\u0159i", "hint": "", "url": "/co-je-MaM/organizatori/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 1, "sort_order": 8, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 9, "fields": {"title": "FAQ", "hint": "", "url": "/co-je-MaM/FAQ/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 1, "sort_order": 9, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 10, "fields": {"title": "Kontakt", "hint": "", "url": "/co-je-MaM/kontakt/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 1, "sort_order": 10, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 11, "fields": {"title": "T\u00e9mata", "hint": "", "url": "/jak-resit/temata/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 2, "sort_order": 11, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 12, "fields": {"title": "Jak ps\u00e1t p\u0159\u00edsp\u011bvek", "hint": "", "url": "/jak-resit/jak-psat-prispevek/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 2, "sort_order": 12, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 13, "fields": {"title": "Odm\u011bny", "hint": "", "url": "/co-je-MaM/odmeny/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 2, "sort_order": 13, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 14, "fields": {"title": "Jak poslat \u0159e\u0161en\u00ed", "hint": "", "url": "/jak-resit/jak-poslat-reseni", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 2, "sort_order": 14, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 15, "fields": {"title": "Aktu\u00e1ln\u00ed t\u00e9mata", "hint": "", "url": "/zadani/temata/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 3, "sort_order": 15, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 16, "fields": {"title": "V\u00fdsledkov\u00e1 listina", "hint": "", "url": "zadani/vysledkova-listina/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 3, "sort_order": 16, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 17, "fields": {"title": "\u010cl\u00e1nky", "hint": "", "url": "/clanky/resitel/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 3, "sort_order": 17, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 18, "fields": {"title": "\u00davod", "hint": "", "url": "/soustredeni/uvod/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 4, "sort_order": 18, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 19, "fields": {"title": "P\u0159ipravujeme", "hint": "", "url": "/soustredeni/pripravujeme/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 4, "sort_order": 19, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 20, "fields": {"title": "Prob\u011bhlo", "hint": "", "url": "/soustredeni/probehlo/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 4, "sort_order": 20, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 22, "fields": {"title": "Osobn\u00ed \u00fadaje", "hint": "", "url": "/profil/osobni-udaje", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 21, "sort_order": 22, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 23, "fields": {"title": "Poslat \u0159e\u0161en\u00ed", "hint": "", "url": "/odeslat-reseni/", "urlaspattern": false, "tree": 1, "hidden": false, "alias": null, "description": "", "inmenu": true, "inbreadcrumbs": true, "insitetree": true, "access_loggedin": false, "access_guest": false, "access_restricted": false, "access_perm_type": 1, "parent": 21, "sort_order": 23, "access_permissions": []}}] From 62b0b4a62f0b76b04b1aa7e7e5ef7fa6fe95b39b Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Fri, 28 Feb 2020 21:34:53 +0100 Subject: [PATCH 09/41] Submitovatko: pomerne funkcni verze. --- seminar/forms.py | 24 ++++++++- seminar/migrations/0074_auto_20200228_1401.py | 24 +++++++++ seminar/migrations/0075_auto_20200228_2010.py | 18 +++++++ seminar/migrations/0076_auto_20200228_2013.py | 19 +++++++ seminar/models.py | 30 ++++++------ seminar/static/seminar/dynamic_formsets.js | 49 +++++++++++++++++++ seminar/templates/seminar/nahraj_reseni.html | 44 +++++++++++++++++ seminar/urls.py | 2 + seminar/views.py | 49 ++++++++++++++++++- 9 files changed, 242 insertions(+), 17 deletions(-) create mode 100644 seminar/migrations/0074_auto_20200228_1401.py create mode 100644 seminar/migrations/0075_auto_20200228_2010.py create mode 100644 seminar/migrations/0076_auto_20200228_2013.py create mode 100644 seminar/static/seminar/dynamic_formsets.js create mode 100644 seminar/templates/seminar/nahraj_reseni.html diff --git a/seminar/forms.py b/seminar/forms.py index 6a0e7911..fb313272 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -2,6 +2,7 @@ from django import forms from dal import autocomplete from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.models import User +from django.forms.models import inlineformset_factory from .models import Skola, Resitel, Osoba, Problem import seminar.models as m @@ -252,6 +253,25 @@ class VlozReseniForm(forms.Form): super().__init__(*args, **kwargs) #self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()]) - - +class NahrajReseniForm(forms.ModelForm): + class Meta: + model = m.Reseni + fields = ('problem',) + + widgets = {'problem': + autocomplete.ModelSelect2Multiple( + url='autocomplete_problem_odevzdatelny', + attrs = {'data-placeholder--id': '-1', + 'data-placeholder--text' : '---', + 'data-allow-clear': 'true'}, + ) + } + +ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni, + form = NahrajReseniForm, + fields = ('soubor','res_poznamka'), + widgets = {'res_poznamka':forms.TextInput()}, + extra = 1, + + ) diff --git a/seminar/migrations/0074_auto_20200228_1401.py b/seminar/migrations/0074_auto_20200228_1401.py new file mode 100644 index 00000000..447d5aab --- /dev/null +++ b/seminar/migrations/0074_auto_20200228_1401.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.9 on 2020-02-28 13:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0073_copy_osoba_email_to_user_email'), + ] + + operations = [ + migrations.AddField( + model_name='prilohareseni', + name='res_poznamka', + field=models.TextField(blank=True, help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje', verbose_name='poznámka řešitele'), + ), + migrations.AlterField( + model_name='hodnoceni', + name='problem', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.Problem', verbose_name='problém'), + ), + ] diff --git a/seminar/migrations/0075_auto_20200228_2010.py b/seminar/migrations/0075_auto_20200228_2010.py new file mode 100644 index 00000000..2e703e4e --- /dev/null +++ b/seminar/migrations/0075_auto_20200228_2010.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.9 on 2020-02-28 19:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0074_auto_20200228_1401'), + ] + + operations = [ + migrations.AlterField( + model_name='hodnoceni', + name='body', + field=models.DecimalField(decimal_places=1, max_digits=8, null=True, verbose_name='body'), + ), + ] diff --git a/seminar/migrations/0076_auto_20200228_2013.py b/seminar/migrations/0076_auto_20200228_2013.py new file mode 100644 index 00000000..04aaea1e --- /dev/null +++ b/seminar/migrations/0076_auto_20200228_2013.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.9 on 2020-02-28 19:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0075_auto_20200228_2010'), + ] + + operations = [ + migrations.AlterField( + model_name='hodnoceni', + name='cislo_body', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='seminar.Cislo', verbose_name='číslo pro body'), + ), + ] diff --git a/seminar/models.py b/seminar/models.py index f8c41ed0..6f1c89f5 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -920,10 +920,10 @@ class Hodnoceni(SeminarModelBase): body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', - blank=False, null=False) + blank=False, null=True) cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body', - related_name='hodnoceni', blank=False, null=False, on_delete=models.PROTECT) + related_name='hodnoceni', blank=False, null=True, on_delete=models.PROTECT) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) @@ -935,17 +935,16 @@ class Hodnoceni(SeminarModelBase): -## FIXME: Budeme řešit později, pokud to bude potřeba. -#def aux_generate_filename(self, filename): -# """Pomocná funkce generující ošetřený název souboru v adresáři s datem""" -# clean = get_valid_filename( -# unidecode(filename.replace('/', '-').replace('\0', '')) -# ) -# datedir = timezone.now().strftime('%Y-%m') -# fname = "%s_%s" % ( -# timezone.now().strftime('%Y-%m-%d-%H:%M'), -# clean) -# return os.path.join(datedir, fname) +def aux_generate_filename(self, filename): + """Pomocná funkce generující ošetřený název souboru v adresáři s datem""" + clean = get_valid_filename( + unidecode(filename.replace('/', '-').replace('\0', '')) + ) + datedir = timezone.now().strftime('%Y-%m') + fname = "{}_{}".format( + timezone.now().strftime('%Y-%m-%d-%H:%M'), + clean) + return os.path.join(datedir, fname) # Django neumí jednoduše serializovat partial nebo třídu s __call__ # (https://docs.djangoproject.com/en/1.8/topics/migrations/), @@ -988,9 +987,12 @@ class PrilohaReseni(SeminarModelBase): poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu') + + res_poznamka = models.TextField('poznámka řešitele', blank=True, + help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje') def __str__(self): - return self.soubor + return str(self.soubor) class Pohadka(SeminarModelBase): diff --git a/seminar/static/seminar/dynamic_formsets.js b/seminar/static/seminar/dynamic_formsets.js new file mode 100644 index 00000000..a0d99d0a --- /dev/null +++ b/seminar/static/seminar/dynamic_formsets.js @@ -0,0 +1,49 @@ +// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0 +function updateElementIndex(el, prefix, ndx) { + var id_regex = new RegExp('(' + prefix + '-\\d+)'); + var replacement = prefix + '-' + ndx; + if ($(el).attr("for")) { + $(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); + } + if (el.id) { + el.id = el.id.replace(id_regex, replacement); + } + if (el.name) { + el.name = el.name.replace(id_regex, replacement); + } +} + +// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0 +function deleteForm(prefix, btn) { + var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val()); + if (total >= 1){ + btn.closest('div').remove(); + var forms = $('.attachment'); + $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length); + for (var i=0, formCount=forms.length; i + {{form.media}} + +{% endblock %} + +{% block content %} +

+ {% block nadpis1a %}{% block nadpis1b %} + Vložit řešení + {% endblock %}{% endblock %} +

+
+ {% csrf_token %} +{{form}} +{{prilohy.management_form}} +
+{% for form in prilohy.forms %} +
+ {{form.non_field_errors}} + {{form.errors}} + + {{ form }} +
+ +
+{% endfor %} +
+ + + +
+ +{% endblock %} diff --git a/seminar/urls.py b/seminar/urls.py index bc1c89a8..1d5348da 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -109,6 +109,7 @@ urlpatterns = [ path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), path('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'), + path('autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'), path('auth/change_password/', views.PasswordChangeView.as_view(), name='change_password'), path('auth/reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), @@ -118,6 +119,7 @@ urlpatterns = [ path('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'), + path('temp/submit_solution', views.SubmitSolutionView.as_view(),name='seminar_nahraj_reseni'), path('', views.TitulniStranaView.as_view(), name='titulni_strana'), diff --git a/seminar/views.py b/seminar/views.py index e174ab28..0136dfb2 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _ from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect from django.db.models import Q, Sum, Count from django.views.decorators.csrf import ensure_csrf_cookie -from django.views.generic.edit import FormView +from django.views.generic.edit import FormView, CreateView from django.contrib.auth import authenticate, login, get_user_model, logout from django.contrib.auth import views as auth_views from django.contrib.auth.models import User @@ -1157,6 +1157,40 @@ class AddSolutionView(LoginRequiredMixin, FormView): form_class = f.VlozReseniForm success_url = '/' +class SubmitSolutionView(LoginRequiredMixin, CreateView): + model = s.Reseni + template_name = 'seminar/nahraj_reseni.html' + form_class = f.NahrajReseniForm + + def get_context_data(self,**kwargs): + data = super().get_context_data(**kwargs) + if self.request.POST: + data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES) + else: + data['prilohy'] = f.ReseniSPrilohamiFormSet() + return data + + # FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni + # Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset + def form_valid(self,form): + context = self.get_context_data() + prilohy = context['prilohy'] + with transaction.atomic(): + self.object = form.save() + self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) + self.object.cas_doruceni = datetime.now() + self.object.forma = s.Reseni.FORMA_UPLOAD + if prilohy.is_valid(): + prilohy.instance = self.object + prilohy.save() + else: + raise Exception("Uploadovane soubory nebyly validni") + + return HttpResponseRedirect(self.get_success_url()) + + + + def resetPasswordView(request): pass @@ -1349,6 +1383,19 @@ class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetVie ) return qs +class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): + def get_queryset(self): + nastaveni = get_object_or_404(Nastaveni) + rocnik = nastaveni.aktualni_rocnik + temata = s.Tema.objects.filter(rocnik=rocnik, stav=s.Problem.STAV_ZADANY) + ulohy = s.Uloha.objects.filter(cislo_deadline__rocnik = rocnik) + ulohy.union(temata) + qs = ulohy + if self.q: + qs = qs.filter( + Q(nazev__startswith=self.q)) + return qs + # Ceka na autocomplete v3 # class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): From fe5cf93ed8a0198601a87e9294c1184b69bc345b Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Fri, 28 Feb 2020 21:42:06 +0100 Subject: [PATCH 10/41] Submitovatko: bugfixy --- seminar/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seminar/views.py b/seminar/views.py index 0136dfb2..b1bacc48 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -1178,8 +1178,9 @@ class SubmitSolutionView(LoginRequiredMixin, CreateView): with transaction.atomic(): self.object = form.save() self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) - self.object.cas_doruceni = datetime.now() + self.object.cas_doruceni = timezone.now() self.object.forma = s.Reseni.FORMA_UPLOAD + self.object.save() if prilohy.is_valid(): prilohy.instance = self.object prilohy.save() From f147318df575494d0bf38b82d156a89641579f88 Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Sat, 29 Feb 2020 18:05:49 +0100 Subject: [PATCH 11/41] Submitovatko: opravena reakce u nevalidnich formularu, bugfix --- seminar/views.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/seminar/views.py b/seminar/views.py index b1bacc48..36320ba3 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -1161,6 +1161,7 @@ class SubmitSolutionView(LoginRequiredMixin, CreateView): model = s.Reseni template_name = 'seminar/nahraj_reseni.html' form_class = f.NahrajReseniForm + success_url = '/' def get_context_data(self,**kwargs): data = super().get_context_data(**kwargs) @@ -1175,17 +1176,17 @@ class SubmitSolutionView(LoginRequiredMixin, CreateView): def form_valid(self,form): context = self.get_context_data() prilohy = context['prilohy'] + if not prilohy.is_valid(): + return super().form_invalid(form) with transaction.atomic(): self.object = form.save() self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user)) self.object.cas_doruceni = timezone.now() self.object.forma = s.Reseni.FORMA_UPLOAD self.object.save() - if prilohy.is_valid(): - prilohy.instance = self.object - prilohy.save() - else: - raise Exception("Uploadovane soubory nebyly validni") + + prilohy.instance = self.object + prilohy.save() return HttpResponseRedirect(self.get_success_url()) From de24a5029ae1e0f0bbd72091722e57a1a1da771a Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Tue, 3 Mar 2020 21:48:55 +0100 Subject: [PATCH 12/41] views.py rozdeleny na vice souboru --- seminar/utils.py | 16 ++--- seminar/views/__init__.py | 2 + seminar/views/autocomplete.py | 63 ++++++++++++++++ seminar/views/helpers.py | 8 +++ seminar/{ => views}/unicodecsv.py | 0 seminar/views/utils.py | 91 ++++++++++++++++++++++++ seminar/{views.py => views/views_all.py} | 69 +----------------- 7 files changed, 175 insertions(+), 74 deletions(-) create mode 100644 seminar/views/__init__.py create mode 100644 seminar/views/autocomplete.py create mode 100644 seminar/views/helpers.py rename seminar/{ => views}/unicodecsv.py (100%) create mode 100644 seminar/views/utils.py rename seminar/{views.py => views/views_all.py} (95%) diff --git a/seminar/utils.py b/seminar/utils.py index d910a5b6..3869ffd4 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -4,6 +4,8 @@ import datetime from django.contrib.auth.decorators import user_passes_test from html.parser import HTMLParser +import seminar.models as m + staff_member_required = user_passes_test(lambda u: u.is_staff) class FirstTagParser(HTMLParser): @@ -43,7 +45,6 @@ def from_roman(rom): def seznam_problemu(): - from .models import Problem, Resitel, Rocnik, Reseni, Cislo problemy = [] # Pomocna fce k formatovani problemovych hlasek @@ -65,27 +66,26 @@ def seznam_problemu(): # Duplicita jmen jmena = {} - for r in Resitel.objects.all(): + for r in m.Resitel.objects.all(): j = r.plne_jmeno() if j not in jmena: jmena[j] = [] jmena[j].append(r) for j in jmena: if len(jmena[j]) > 1: - prb(Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j]) + prb(m.Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j]) # Data maturity a narození - for r in Resitel.objects.all(): + for r in m.Resitel.objects.all(): if not r.rok_maturity: - prb(Resitel, u'Neznámý rok maturity', [r]) + prb(m.Resitel, u'Neznámý rok maturity', [r]) if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): - prb(Resitel, u'Podezřelé datum maturity', [r]) + prb(m.Resitel, u'Podezřelé datum maturity', [r]) if r.datum_narozeni and (r.datum_narozeni.year < 1970 or r.datum_narozeni.year > datetime.date.today().year - 12): - prb(Resitel, u'Podezřelé datum narození', [r]) + prb(m.Resitel, u'Podezřelé datum narození', [r]) # if not r.email: # prb(Resitel, u'Neznámý email', [r]) return problemy - diff --git a/seminar/views/__init__.py b/seminar/views/__init__.py new file mode 100644 index 00000000..976a34fe --- /dev/null +++ b/seminar/views/__init__.py @@ -0,0 +1,2 @@ +from .views_all import * +from .autocomplete import * diff --git a/seminar/views/autocomplete.py b/seminar/views/autocomplete.py new file mode 100644 index 00000000..04ddca83 --- /dev/null +++ b/seminar/views/autocomplete.py @@ -0,0 +1,63 @@ +from dal import autocomplete +from django.shortcuts import get_object_or_404 + +import seminar.models as m +from .helpers import LoginRequiredAjaxMixin + +class SkolaAutocomplete(autocomplete.Select2QuerySetView): + def get_queryset(self): + # Don't forget to filter out results depending on the visitor ! + qs = m.Skola.objects.all() + if self.q: + qs = qs.filter( + Q(nazev__istartswith=self.q)| + Q(kratky_nazev__istartswith=self.q)| + Q(ulice__istartswith=self.q)| + Q(mesto__istartswith=self.q)) + + return qs + +class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView): + def get_queryset(self): + qs = m.Resitel.objects.all() + if self.q: + qs = qs.filter( + Q(osoba__jmeno__startswith=self.q)| + Q(osoba__prijmeni__startswith=self.q)| + Q(osoba__prezdivka__startswith=self.q) + ) + return qs + +class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): + def get_queryset(self): + nastaveni = get_object_or_404(m.Nastaveni) + rocnik = nastaveni.aktualni_rocnik + temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY) + ulohy = m.Uloha.objects.filter(cislo_deadline__rocnik = rocnik) + ulohy.union(temata) + qs = ulohy + if self.q: + qs = qs.filter( + Q(nazev__startswith=self.q)) + return qs + +# Ceka na autocomplete v3 +# class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): +# def get_queryset(self): +# if not self.request.user.is_authenticated(): +# return Organizator.objects.none() +# +# qs = aktivniOrganizatori() +# +# if self.q: +# if self.q[0] == "!": +# qs = Organizator.objects.all() +# query = self.q[1:] +# else: +# query = self.q +# qs = qs.filter( +# Q(prezdivka__isstartswith=query)| +# Q(user__first_name__isstartswith=query)| +# Q(user__last_name__isstartswith=query)) +# +# return qs diff --git a/seminar/views/helpers.py b/seminar/views/helpers.py new file mode 100644 index 00000000..0b06c0eb --- /dev/null +++ b/seminar/views/helpers.py @@ -0,0 +1,8 @@ +from dal import autocomplete + +class LoginRequiredAjaxMixin(object): + def dispatch(self, request, *args, **kwargs): + #if request.is_ajax() and not request.user.is_authenticated: # Pokud to otevřu jako stránku, tak se omezení neuplatní, takže to asi nechceme + if not request.user.is_authenticated: + return JsonResponse(data={'results': [], 'pagination': {}}, status=401) + return super(LoginRequiredAjaxMixin, self).dispatch(request, *args, **kwargs) diff --git a/seminar/unicodecsv.py b/seminar/views/unicodecsv.py similarity index 100% rename from seminar/unicodecsv.py rename to seminar/views/unicodecsv.py diff --git a/seminar/views/utils.py b/seminar/views/utils.py new file mode 100644 index 00000000..3869ffd4 --- /dev/null +++ b/seminar/views/utils.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +import datetime +from django.contrib.auth.decorators import user_passes_test +from html.parser import HTMLParser + +import seminar.models as m + +staff_member_required = user_passes_test(lambda u: u.is_staff) + +class FirstTagParser(HTMLParser): + def __init__(self, *args, **kwargs): + self.firstTag = None + super().__init__(*args, **kwargs) + def handle_data(self, data): + if self.firstTag == None: + self.firstTag = data + +def histogram(seznam): + d = {} + for i in seznam: + if i not in d: + d[i] = 0 + d[i] += 1 + return d + + +roman_numerals = zip((1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1), + ('M', 'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I')) + +def roman(num): + res = "" + for i, n in roman_numerals: + res += n * (num // i) + num %= i + return res + +def from_roman(rom): + if not rom: + return 0 + for i, n in roman_numerals: + if rom.upper().startswith(n): + return i + from_roman(rom[len(n):]) + raise Exception('Invalid roman numeral: "%s"', rom) + + +def seznam_problemu(): + problemy = [] + + # Pomocna fce k formatovani problemovych hlasek + def prb(cls, msg, objs=None): + s = u'%s: %s' % (cls.__name__, msg) + if objs: + s += u' [' + for o in objs: + try: + url = o.admin_url() + except: + url = None + if url: + s += u'%s, ' % (url, o.pk, ) + else: + s += u'%s, ' % (o.pk, ) + s = s[:-2] + u']' + problemy.append(s) + + # Duplicita jmen + jmena = {} + for r in m.Resitel.objects.all(): + j = r.plne_jmeno() + if j not in jmena: + jmena[j] = [] + jmena[j].append(r) + for j in jmena: + if len(jmena[j]) > 1: + prb(m.Resitel, u'Duplicitní jméno "%s"' % (j, ), jmena[j]) + + # Data maturity a narození + for r in m.Resitel.objects.all(): + if not r.rok_maturity: + prb(m.Resitel, u'Neznámý rok maturity', [r]) + if r.rok_maturity and (r.rok_maturity < 1990 or r.rok_maturity > datetime.date.today().year + 10): + prb(m.Resitel, u'Podezřelé datum maturity', [r]) + if r.datum_narozeni and (r.datum_narozeni.year < 1970 or r.datum_narozeni.year > datetime.date.today().year - 12): + prb(m.Resitel, u'Podezřelé datum narození', [r]) +# if not r.email: +# prb(Resitel, u'Neznámý email', [r]) + + return problemy + + diff --git a/seminar/views.py b/seminar/views/views_all.py similarity index 95% rename from seminar/views.py rename to seminar/views/views_all.py index 36320ba3..3cfb0535 100644 --- a/seminar/views.py +++ b/seminar/views/views_all.py @@ -15,14 +15,13 @@ from django.contrib.auth import views as auth_views from django.contrib.auth.models import User from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction -from dal import autocomplete import seminar.models as s -from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci +from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva -from . import utils +from seminar import utils from .unicodecsv import UnicodeWriter -from .forms import PrihlaskaForm, LoginForm, ProfileEditForm +from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm import seminar.forms as f from datetime import timedelta, date, datetime @@ -1354,71 +1353,9 @@ def prihlaskaView(request): return render(request, 'seminar/prihlaska.html', {'form': form}) -class SkolaAutocomplete(autocomplete.Select2QuerySetView): - def get_queryset(self): - # Don't forget to filter out results depending on the visitor ! - qs = Skola.objects.all() - if self.q: - qs = qs.filter( - Q(nazev__istartswith=self.q)| - Q(kratky_nazev__istartswith=self.q)| - Q(ulice__istartswith=self.q)| - Q(mesto__istartswith=self.q)) - - return qs -class LoginRequiredAjaxMixin(object): - def dispatch(self, request, *args, **kwargs): - #if request.is_ajax() and not request.user.is_authenticated: # Pokud to otevřu jako stránku, tak se omezení neuplatní, takže to asi nechceme - if not request.user.is_authenticated: - return JsonResponse(data={'results': [], 'pagination': {}}, status=401) - return super(LoginRequiredAjaxMixin, self).dispatch(request, *args, **kwargs) -class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView): - def get_queryset(self): - qs = Resitel.objects.all() - if self.q: - qs = qs.filter( - Q(osoba__jmeno__startswith=self.q)| - Q(osoba__prijmeni__startswith=self.q)| - Q(osoba__prezdivka__startswith=self.q) - ) - return qs -class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): - def get_queryset(self): - nastaveni = get_object_or_404(Nastaveni) - rocnik = nastaveni.aktualni_rocnik - temata = s.Tema.objects.filter(rocnik=rocnik, stav=s.Problem.STAV_ZADANY) - ulohy = s.Uloha.objects.filter(cislo_deadline__rocnik = rocnik) - ulohy.union(temata) - qs = ulohy - if self.q: - qs = qs.filter( - Q(nazev__startswith=self.q)) - return qs - - -# Ceka na autocomplete v3 -# class OrganizatorAutocomplete(autocomplete.Select2QuerySetView): -# def get_queryset(self): -# if not self.request.user.is_authenticated(): -# return Organizator.objects.none() -# -# qs = aktivniOrganizatori() -# -# if self.q: -# if self.q[0] == "!": -# qs = Organizator.objects.all() -# query = self.q[1:] -# else: -# query = self.q -# qs = qs.filter( -# Q(prezdivka__isstartswith=query)| -# Q(user__first_name__isstartswith=query)| -# Q(user__last_name__isstartswith=query)) -# -# return qs # FIXME: Tohle asi vlastně vůbec nepatří do aplikace 'seminar' class LoginView(auth_views.LoginView): From 2632b90e5d80b28a19e7febf47933c0467ed16ab Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 4 Mar 2020 02:41:02 +0100 Subject: [PATCH 13/41] =?UTF-8?q?TreeLib:=20n=C3=A1st=C5=99el=20rozhran?= =?UTF-8?q?=C3=AD,=20a=C5=BE=20na=20print=5Ftree=20neimplementov=C3=A1no?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/models.py | 11 ++------- seminar/treelib.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 seminar/treelib.py diff --git a/seminar/models.py b/seminar/models.py index 6f1c89f5..d1040739 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -1244,6 +1244,7 @@ class TreeNode(PolymorphicModel): on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku verbose_name="kořen stromu") first_child = models.ForeignKey('TreeNode', + related_name='father_of_first', null = True, blank = True, on_delete=models.SET_NULL, @@ -1264,15 +1265,6 @@ class TreeNode(PolymorphicModel): srolovatelne = models.BooleanField(null = True, blank = True, verbose_name = "Srolovatelné", help_text = "Bude na stránce témátka možnost tuto položku skrýt") - - # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. - def print_tree(self,indent=0): - # FIXME: Tady se spoléháme na to, že nedeklarovaný primární klíč se jmenuje by default 'id', což není úplně správně - print("{}{} (id: {})".format(" "*indent,self, self.id)) - if self.first_child: - self.first_child.print_tree(indent=indent+2) - if self.succ: - self.succ.print_tree(indent=indent) def getOdkazStr(self): # String na rozcestník return self.first_child.getOdkazStr() @@ -1341,6 +1333,7 @@ class MezicisloNode(TreeNode): verbose_name = 'Mezičíslo (Node)' verbose_name_plural = 'Mezičísla (Node)' + # TODO: Využít TreeLib def aktualizuj_nazev(self): if self.prev: if (self.prev.get_real_instance_class() != CisloNode and diff --git a/seminar/treelib.py b/seminar/treelib.py new file mode 100644 index 00000000..a0ef1e08 --- /dev/null +++ b/seminar/treelib.py @@ -0,0 +1,61 @@ +# NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode + +# Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. +def print_tree(node,indent=0): + # FIXME: Tady se spoléháme na to, že nedeklarovaný primární klíč se jmenuje by default 'id', což není úplně správně + print("{}{} (id: {})".format(" "*indent,node, node.id)) + if node.first_child: + node.first_child.print_tree(indent=indent+2) + if node.succ: + node.succ.print_tree(indent=indent) + +## Rodinné vztahy +def get_parent(node): + # Nejdřív získáme prvního potomka... + + # ... a z prvního potomka umíme najít rodiče + +# Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) +def general_next(node) +def general_prev(node) + +# Generátor potomků +# TODO: copy-pasta + +# Generátor bratrů + +# Generátor následníků v "the-right-order" + +## Filtrační hledání +# Najdi dalšího bratra nějakého typu, nebo None. +# hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. +def get_next_brother_of_type(current, type): +def get_prev_brother_of_type(current, type): + +# Totéž pro "the-right-order" pořadí +def get_next_node_of_type(current, type): +def get_next_node_of_type(current, type): + + + + +# Editace stromu: +def create_node_after(predecessor, type, **kwargs): + +# Vyrábí prvního syna, ostatní nalepí za (existují-li) +def create_child(parent, type, **kwargs): + +def create_node_before(...): + # Tohle bude hell. + +# ValueError, pokud je (aspoň) jeden parametr None +def swap(node, other): + +def swap_pred +def swap_succ + +# Rotace stromu +# Dokumentace viz wiki: +# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) +def raise(node) +def lower(node) From 6b104d39f9837998a6b2f442ef525a264620a7c0 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 4 Mar 2020 02:50:00 +0100 Subject: [PATCH 14/41] =?UTF-8?q?TreeLib:=20pot=C5=99ebujeme=20syntakticko?= =?UTF-8?q?u=20korektnost,=20i=20kdy=C5=BE=20tu=20nic=20nen=C3=AD...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/treelib.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index a0ef1e08..0d25fa5f 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -11,13 +11,16 @@ def print_tree(node,indent=0): ## Rodinné vztahy def get_parent(node): + pass # Nejdřív získáme prvního potomka... # ... a z prvního potomka umíme najít rodiče # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) -def general_next(node) -def general_prev(node) +def general_next(node): + pass +def general_prev(node): + pass # Generátor potomků # TODO: copy-pasta @@ -30,32 +33,44 @@ def general_prev(node) # Najdi dalšího bratra nějakého typu, nebo None. # hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. def get_next_brother_of_type(current, type): + pass def get_prev_brother_of_type(current, type): + pass # Totéž pro "the-right-order" pořadí def get_next_node_of_type(current, type): + pass def get_next_node_of_type(current, type): + pass # Editace stromu: def create_node_after(predecessor, type, **kwargs): + pass # Vyrábí prvního syna, ostatní nalepí za (existují-li) def create_child(parent, type, **kwargs): + pass -def create_node_before(...): +def create_node_before(some, arguments, but, i, dont, know, which, yet): + pass # Tohle bude hell. # ValueError, pokud je (aspoň) jeden parametr None def swap(node, other): + pass -def swap_pred -def swap_succ +def swap_pred(node): + pass +def swap_succ(node): + pass # Rotace stromu # Dokumentace viz wiki: # (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) -def raise(node) -def lower(node) +def raise_node(node): + pass +def lower_node(node): + pass From 98bad2099af9a6e7facdb447c4bef428d29f7ce8 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 4 Mar 2020 02:50:27 +0100 Subject: [PATCH 15/41] TreeLib: fix print_tree --- seminar/treelib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index 0d25fa5f..bf696545 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -5,9 +5,9 @@ def print_tree(node,indent=0): # FIXME: Tady se spoléháme na to, že nedeklarovaný primární klíč se jmenuje by default 'id', což není úplně správně print("{}{} (id: {})".format(" "*indent,node, node.id)) if node.first_child: - node.first_child.print_tree(indent=indent+2) + print_tree(node.first_child, indent=indent+2) if node.succ: - node.succ.print_tree(indent=indent) + print_tree(node.succ, indent=indent) ## Rodinné vztahy def get_parent(node): From 989e998e03d491f663771e8890af56aab90b22a5 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 11 Mar 2020 21:30:00 +0100 Subject: [PATCH 16/41] Treelib: get_parent --- seminar/treelib.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index bf696545..fa2f7807 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -1,3 +1,4 @@ +from django.core.exceptions import ObjectDoesNotExist # NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. @@ -13,8 +14,19 @@ def print_tree(node,indent=0): def get_parent(node): pass # Nejdřív získáme prvního potomka... - + # Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist + while True: + try: + node = node.prev + except ObjectDoesNotExist: + # We rely here on the fact that the assignment will not happen if RHS throws an exception + break # ... a z prvního potomka umíme najít rodiče + # Django je pořád hloupé... + try: + return node.father_of_first + except ObjectDoesNotExist: + return None # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) def general_next(node): From 9efb684cc1c1e15c2f4f66eac3762024d92464f1 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 11 Mar 2020 21:39:26 +0100 Subject: [PATCH 17/41] Treelib: general_next --- seminar/treelib.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index fa2f7807..ce956219 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -30,7 +30,17 @@ def get_parent(node): # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) def general_next(node): - pass + # Máme potomka? + if node.first_child is not None: + return node.first_child + # Nemáme potomka. + # Chceme najít následníka sebe, nebo některého (toho nejblíž příbuzného) z našich předků (tatínka, dědečka, ...) + while node.succ is None: + node = get_parent(node) + if node is None: + return None # žádný z předků nemá následníka, takže žádny vrchol nenásleduje. + return node.succ + def general_prev(node): pass From caa274460d79ca9033c10242f752ab1c412759f1 Mon Sep 17 00:00:00 2001 From: Anet Date: Wed, 11 Mar 2020 23:00:54 +0100 Subject: [PATCH 18/41] =?UTF-8?q?nepadaj=C3=ADc=C3=AD=20(ale=20nezobrazuj?= =?UTF-8?q?=C3=ADc=C3=AD=20se)=20verze=20v=C3=BDsledkovky=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/templates/seminar/archiv/cislo.html | 16 +- seminar/views.py | 235 +++++++++++++------- 2 files changed, 157 insertions(+), 94 deletions(-) diff --git a/seminar/templates/seminar/archiv/cislo.html b/seminar/templates/seminar/archiv/cislo.html index d8b030c8..63788e06 100644 --- a/seminar/templates/seminar/archiv/cislo.html +++ b/seminar/templates/seminar/archiv/cislo.html @@ -67,21 +67,21 @@ {% endfor %} Za číslo Za ročník - Odjakživa - {% for rv in vysledkovka %} + {#Odjakživa#} + {% for rv in radky_vysledkovky %} {% autoescape off %}{{ rv.poradi }}{% endautoescape %} - {% if rv.titul %} - {{ rv.titul }}MM + {% if rv.resitel.get_titul != "" %} + {{ rv.resitel.get_titul }}MM {% endif %} - {{ rv.resitel.plne_jmeno }} - {% for b in rv.body_ulohy %} + {{ rv.resitel.osoba.plne_jmeno }} + {% for b in rv.hlavni_problemy_body %} {{ b }} {% endfor %} {{ rv.body_cislo }} - {{ rv.body_celkem_rocnik }} - {{ rv.body_celkem_odjakziva }} + {{ rv.body_rocnik }} + {# {{ rv.body_celkem_odjakziva }}#} {% endfor %} diff --git a/seminar/views.py b/seminar/views.py index abb1ad77..298e8e0e 100644 --- a/seminar/views.py +++ b/seminar/views.py @@ -426,28 +426,34 @@ def sloupec_s_poradim(seznam_s_body): aktualni_poradi = aktualni_poradi + velikost_skupiny return sloupec_s_poradim -# spočítá součet bodů získaných daným řešitelem za zadaný problém a všechny jeho podproblémy -def __soucet_resitele_problemu(problem, resitel, cislo, soucet): - # sečteme body za daný problém přes všechna řešení daného problému - # od daného řešitele - reseni_resitele = Reseni.objects.filter(resitele__in=resitel) - hodnoceni_resitele = problem.hodnoceni.filter(reseni__in=reseni_resitele, - cislo_body=cislo) - # XXX chyba na řádku výše - řešení může mít více řešitelů, asi chceme contains - # nebo in - for r in hodnoceni_resitele: - soucet += r.body - - # a přičteme k tomu hodnocení všech podproblémů - for p in problem.podproblem.all(): - # i přes jméno by to měla být množina jeho podproblémů - soucet += __soucet_resitele_problemu(p, resitel, soucet) - return soucet - -# spočítá součet všech bodů ze všech podproblémů daného problému daného řešitele -def body_resitele_problemu_v_cisle(problem, resitel, cislo): - # probably FIXED: nezohledňuje číslo, do kterého se body počítají - return __soucet_resitele_problemu(problem, resitel, cislo, 0) +## spočítá součet bodů získaných daným řešitelem za zadaný problém a všechny jeho podproblémy +#def __soucet_resitele_problemu(problem, resitel, cislo, soucet): +# # sečteme body za daný problém přes všechna řešení daného problému +# # od daného řešitele +# reseni_resitele = s.Reseni_Resitele.objects.filter(resitele=resitel) +# hodnoceni_resitele = problem.hodnoceni.filter(reseni__in=reseni_resitele, +# cislo_body=cislo) +# # XXX chyba na řádku výše - řešení může mít více řešitelů, asi chceme contains +# # nebo in +# for r in hodnoceni_resitele: +# soucet += r.body +# +# # a přičteme k tomu hodnocení všech podproblémů +# for p in problem.podproblem.all(): +# # i přes jméno by to měla být množina jeho podproblémů +# soucet += __soucet_resitele_problemu(p, resitel, soucet) +# return soucet + +## spočítá součet všech bodů ze všech podproblémů daného problému daného řešitele +#def body_resitele_problemu_v_cisle(problem, resitel, cislo): +# # probably FIXED: nezohledňuje číslo, do kterého se body počítají +# return __soucet_resitele_problemu(problem, resitel, cislo, 0) + +# pro daný problém vrátí jeho nejvyšší nadproblém +def hlavni_problem(problem): + while not(problem.nadproblem == None): + problem = problem.nadproblem + return problem # vrátí list všech problémů s body v daném čísle, které již nemají nadproblém def hlavni_problemy_cisla(cislo): @@ -463,9 +469,7 @@ def hlavni_problemy_cisla(cislo): # (mají vlastní sloupeček ve výsledkovce, nemají nadproblém) hlavni_problemy = [] for p in problemy: - while not(p.nadproblem == None): - p = p.nadproblem - hlavni_problemy.append(p) + hlavni_problemy.append(hlavni_problem(p)) # zunikátnění hlavni_problemy_set = set(hlavni_problemy) @@ -474,38 +478,54 @@ def hlavni_problemy_cisla(cislo): return hlavni_problemy -def body_resitele_odjakziva(resitel): - body = 0 - resitelova_hodnoceni = Hodnoceni.objects.select_related('body').all().filter(reseni_resitele=resitel) - # TODO: v radku nahore chceme _in nebo _contains - for hodnoceni in resitelova_hodnoceni: - body = body + hodnoceni.body - return body +# vrátí slovník řešitel:body obsahující počty bodů zadaných řešitelů za daný ročník +def body_resitelu_za_rocnik(rocnik, aktivni_resitele): + body_za_rocnik = {} + # inicializujeme na 0 pro všechny aktivní řešitele + for ar in aktivni_resitele: + body_za_rocnik[str(ar.id)] = 0 + + # spočítáme body řešitelům přes všechna řešení s hodnocením v daném ročníku + reseni = Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik) + for res in reseni: + for resitel in res.resitele.all(): + for hodn in res.hodnoceni.all(): + body_za_rocnik[str(resitel.id)] += hodn.body + return body_za_rocnik + +#def body_resitele_odjakziva(resitel): +# body = 0 +# resitelova_hodnoceni = Hodnoceni.objects.select_related('body').all().filter(reseni_resitele=resitel) +# # TODO: v radku nahore chceme _in nebo _contains +# for hodnoceni in resitelova_hodnoceni: +# body = body + hodnoceni.body +# return body # spočítá součet všech bodů řešitele za dané číslo -def body_resitele_v_cisle(resitel, cislo): - hlavni_problemy = hlavni_problemy_cisla(cislo) - body_resitele = 0 - for h in hlavni_problemy: - body_resitele = body_resitele + body_resitele_problemu_v_cisle(h, resitel, cislo) - # TODO: je rozdíl mezi odevzdanou úlohou za 0 a tím, když řešitel nic neodevzdal - # řešit přes kontrolu velikosti množiny řešení daného problému do daného čísla? - # Tady to ale nevadí, tady se počítá součet za číslo. - return body_resitele +#def body_resitele_v_cisle(resitel, cislo): +# hlavni_problemy = hlavni_problemy_cisla(cislo) +# body_resitele = 0 +# for h in hlavni_problemy: +# body_resitele = body_resitele + body_resitele_problemu_v_cisle(h, resitel, cislo) +# # TODO: je rozdíl mezi odevzdanou úlohou za 0 a tím, když řešitel nic neodevzdal +# # řešit přes kontrolu velikosti množiny řešení daného problému do daného čísla? +# # Tady to ale nevadí, tady se počítá součet za číslo. +# return body_resitele # spočítá součet všech bodů řešitele za daný rok (nebo jen do daného čísla včetně) -def body_resitele_v_rocniku(resitel, rocnik, do_cisla=None): - # pokud do_cisla=None, tak do posledního čísla v ročníku - # do_cisla je objekt Cislo - cisla = rocnik.cisla.all() # funkce vrátí pole objektů - # Cislo už lexikograficky setřízené, viz models - body = 0 - for cislo in cisla: - if cislo.poradi == do_cisla.poradi: break - # druhá část zaručuje, že máme výsledky do daného čísla včetně - body = body + body_resitele_v_cisle(resitel, cislo) - return body - +#def body_resitele_v_rocniku(resitel, rocnik, do_cisla=None): +# # pokud do_cisla=None, tak do posledního čísla v ročníku +# # do_cisla je objekt Cislo +# cisla = rocnik.cisla.all() # funkce vrátí pole objektů +# # Cislo už lexikograficky setřízené, viz models +# body = 0 +# for cislo in cisla: +# if cislo.poradi == do_cisla.poradi: break +# # druhá část zaručuje, že máme výsledky do daného čísla včetně +# body = body + body_resitele_v_cisle(resitel, cislo) +# return body + +# TODO: předělat na nový model #def vysledkovka_rocniku(rocnik, jen_verejne=True): # """Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve # formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html" @@ -612,19 +632,17 @@ class ProblemView(generic.DetailView): return context -class VysledkyResitele(object): - """Pro daného řešitele ukládá počet bodů za jednotlivé úlohy a celkový - počet bodů za konkrétní ročník do daného čísla a za dané číslo.""" +class RadekVysledkovky(object): + """Obsahuje věci, které se hodí vědět při konstruování výsledkovky. + Umožňuje snazší práci v templatu (lepší, než seznam).""" - def __init__(self, resitel, cislo, rocnik): + def __init__(self, poradi, resitel, body_problemy_sezn, body_cislo, body_rocnik): self.resitel = resitel - self.cislo = cislo - self.body_cislo = body_resitele_v_cisle(resitel, cislo) - self.body = [] - self.rocnik = rocnik - self.body_rocnik = body_resitele_v_rocniku(resitel, rocnik, cislo) - self.body_celkem_odjakziva = resitel.vsechny_body() - self.poradi = 0 + self.body_cislo = body_cislo + self.body_rocnik = body_rocnik +# TODO self.body_celkem_odjakziva = odjakziva + self.poradi = poradi + self.body_problemy_sezn = body_problemy_sezn class CisloView(generic.DetailView): model = Cislo @@ -648,48 +666,93 @@ class CisloView(generic.DetailView): def get_context_data(self, **kwargs): context = super(CisloView, self).get_context_data(**kwargs) - ## TODO upravit dle nového modelu cislo = context['cislo'] hlavni_problemy = hlavni_problemy_cisla(cislo) + # TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé + # pro každý hlavní problém zavedeme slovník s body za daný hlavní problém + # pro jednotlivé řešitele (slovník slovníků hlavních problémů) + hlavni_problemy_slovnik = {} + for hp in hlavni_problemy: + hlavni_problemy_slovnik[str(hp.id)] = {} ## TODO dostat pro tyto problémy součet v daném čísle pro daného řešitele ## TODO možná chytřeji vybírat aktivní řešitele - ## chceme letos něco poslal + # aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají + # u alespoň jedné hodnoty něco jiného než NULL aktivni_resitele = Resitel.objects.filter( rok_maturity__gte=cislo.rocnik.druhy_rok()) # TODO: zkusit hodnoceni__rocnik... #.filter(hodnoceni_set__rocnik__eq=cislo_rocnik) - radky_vysledkovky = [] + # zakládání prázdných záznamů pro řešitele + cislobody = {} for ar in aktivni_resitele: - # získáme výsledky řešitele - součty přes číslo a ročník - vr = VysledkyResitele(ar, cislo, cislo.rocnik) + # řešitele převedeme na řetězec pomocí unikátního id + cislobody[str(ar.id)] = "" for hp in hlavni_problemy: - vr.body.append( - body_resitele_problemu_v_cisle(hp, ar, cislo)) - radky_vysledkovky.append(vr) - - # setřídíme řádky výsledkovky/objekty VysledkyResitele podle bodů - radky_vysledkovky.sort(key=lambda vr: vr.body_rocnik, reverse=True) - - # generujeme sloupec s pořadím pomocí stejně zvané funkce - pocty_bodu = [rv.body_rocnik for rv in radky_vysledkovky] - sloupec_poradi = sloupec_s_poradim(pocty_bodu) + slovnik = hlavni_problemy_slovnik[str(hp.id)] + slovnik[str(ar.id)] = "" + + # vezmeme všechna řešení s body do daného čísla + reseni_do_cisla = Reseni.objects.filter(hodnoceni__cislo_body=cislo) - # každému řádku výsledkovky přidáme jeho pořadí - i = 0 - for rv in radky_vysledkovky: - rv.poradi = sloupec_poradi[i] - i = i + 1 + # projdeme všechna řešení do čísla a přičteme body každému řešiteli do celkových + # bodů i do bodů za problém + for reseni in reseni_do_cisla: + body = reseni.hodnoceni.body + problem = reseni.problem + nadproblem = hlavni_problem(problem) + nadproblem_slovnik = hlavni_problemy_slovnik[str(nadproblem.id)] + for resitel in reseni.resitele: + # testujeme na None, pokud první řešení daného řešitele, předěláme na 0 + # (v dalším kroku přičteme reálný počet bodů), rozlišujeme tím mezi 0 a + # neodevzdaným řešením + if cislobody[str(resitel.id)] == "": + cislobody[str(resitel.id)] = 0 + cislobody[str(resitel.id)] += body + + if nadproblem_slovnik[str(resitel.id)] == "": + nadproblem_slovnik[str(resitel.id)] = 0 + nadproblem_slovnik[str(resitel.id)] += body + + # zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně + resitel_rocnikbody_slov = body_resitelu_za_rocnik(cislo.rocnik, aktivni_resitele) + resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), + key = lambda x: x[1], reverse = True) + + # řešitelé setřídění podle bodů za číslo sestupně + setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] + setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id] + + # vytvoříme jednotlivé sloupce výsledkovky + radky_vysledkovky = [] + rocnik_body = [] + cislo_body = [] + hlavni_problemy_body = [] + for ar_id in setrizeni_resitele_id: + # vytáhneme ze slovníků body pro daného řešitele + rocnik_body.append(resitel_rocnikbody_slov[ar_id]) + cislo_body.append(cislobody[ar_id]) + problemy = [] + for hp in hlavni_problemy: + problemy.append(hlavni_problemy_slovnik[str(hp.id)][ar_id]) + hlavni_problemy_body.append(problemy) + # pořadí určíme pomocí funkce, které dáme celkové body za ročník vzestupně + poradi = sloupec_s_poradim(rocnik_body) + + radky_vysledkovky = [] + for i in range(0, len(setrizeni_resitele_id)): + radek = RadekVysledkovky(poradi[i], setrizeni_resitele[i], + hlavni_problemy_body[i], cislo_body[i], rocnik_body[i]) + radky_vysledkovky.append(radek) # vytahané informace předáváme do kontextu context['cislo'] = cislo - context['radky_vysledkovky'] = radky_vysledkovky + context['radky_vysledkovky'] = radky_vysledkovky context['problemy'] = hlavni_problemy # context['v_cisle_zadane'] = TODO # context['resene_problemy'] = resene_problemy #XXX testovat #XXX opravit to, že se nezobrazují body za jednotlivé úlohy - return context # problemy = sorted(set(r.problem for r in reseni), key=lambda x:(poradi_typu[x.typ], x.kod_v_rocniku())) From 9a0664f6c2fd1d3376c027eaf87592242c806e12 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 11 Mar 2020 23:07:58 +0100 Subject: [PATCH 19/41] =?UTF-8?q?TreeLib:=20Bezpe=C4=8Dn=C3=A9=20varianty?= =?UTF-8?q?=20.pred=20a=20.father=5Fof=5Ffirst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/treelib.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index ce956219..b2b106b2 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -10,23 +10,26 @@ def print_tree(node,indent=0): if node.succ: print_tree(node.succ, indent=indent) +# Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist +def safe_pred(node): + try: + return node.prev + except ObjectDoesNotExist: + return None + +# A to samé pro .father_of_first +def safe_father_of_first(node): + return node.prev + except ObjectDoesNotExist: + return None + ## Rodinné vztahy def get_parent(node): - pass # Nejdřív získáme prvního potomka... - # Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist - while True: - try: - node = node.prev - except ObjectDoesNotExist: - # We rely here on the fact that the assignment will not happen if RHS throws an exception - break + while safe_pred(node) is not None: + node = safe_pred(node) # ... a z prvního potomka umíme najít rodiče - # Django je pořád hloupé... - try: - return node.father_of_first - except ObjectDoesNotExist: - return None + return safe_father_of_first(node) # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) def general_next(node): From 0323b46113de435d86a7d0803d57f78ce700e6f4 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 11 Mar 2020 23:34:36 +0100 Subject: [PATCH 20/41] =?UTF-8?q?TreeLib:=20hleda=C4=8D=20pre-order=20p?= =?UTF-8?q?=C5=99edch=C5=AFdc=C5=AF,=20gener=C3=A1tor=20bratr=C5=AF=20a=20?= =?UTF-8?q?potomk=C5=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/treelib.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index b2b106b2..2b7699a0 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -1,5 +1,6 @@ from django.core.exceptions import ObjectDoesNotExist # NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode +# TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None) # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. def print_tree(node,indent=0): @@ -44,13 +45,38 @@ def general_next(node): return None # žádný z předků nemá následníka, takže žádny vrchol nenásleduje. return node.succ +def last_brother(node): + while node.succ is not None: + node = node.succ + return node + def general_prev(node): - pass + # Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec. + # Otce vyřešíme nejdřív: + if safe_pred(node) is None: + return safe_father_of_first(node) + pred = safe_pred(node) + while pred.first_child is not None: + pred = last_brother(pred.first_child) + # pred nyní nemá žádné potomky, takže je to poslední rekurzivní potomek původního předchůdce + return pred # Generátor potomků # TODO: copy-pasta # Generátor bratrů +# Generátor potomků níže spoléhá na to, že se tohle dá volat i s parametrem None. +def all_brothers(node): + current = node + while current is not None: + yield current + current = current.succ + +# Generátor potomků +def all_children(node): + brothers = all_brothers(node.first_child) + for br in brothers: + yield br # Generátor následníků v "the-right-order" From 007774c81b04c6236dbd1aaf1813a5859931f040 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 11 Mar 2020 23:44:50 +0100 Subject: [PATCH 21/41] =?UTF-8?q?TreeLib:=20Smaz=C3=A1n=20zapomenut=C3=BD?= =?UTF-8?q?=20koment=C3=A1=C5=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Napálil jsem se při přidávání věcí přes Vimový plugin :-) --- seminar/treelib.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index 2b7699a0..cfb49d86 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -61,9 +61,6 @@ def general_prev(node): # pred nyní nemá žádné potomky, takže je to poslední rekurzivní potomek původního předchůdce return pred -# Generátor potomků -# TODO: copy-pasta - # Generátor bratrů # Generátor potomků níže spoléhá na to, že se tohle dá volat i s parametrem None. def all_brothers(node): From f18f5c900da6d60f570f90cba407bcc96313654c Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Wed, 18 Mar 2020 21:49:01 +0100 Subject: [PATCH 22/41] Update schematu a modelu dle aktualni situace. Pridan CastNode --- Schema_new.dia | Bin 14607 -> 15640 bytes seminar/migrations/0077_auto_20200318_2146.py | 32 ++++++++++++++++++ seminar/models.py | 10 ++++++ 3 files changed, 42 insertions(+) create mode 100644 seminar/migrations/0077_auto_20200318_2146.py diff --git a/Schema_new.dia b/Schema_new.dia index 0df8a9018d5c64a844d6ac11ccfc28a98f7cd134..ad987cfada8fe4457c5825ff13c012e2febbe492 100644 GIT binary patch literal 15640 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 diff --git a/seminar/migrations/0077_auto_20200318_2146.py b/seminar/migrations/0077_auto_20200318_2146.py new file mode 100644 index 00000000..50053d9c --- /dev/null +++ b/seminar/migrations/0077_auto_20200318_2146.py @@ -0,0 +1,32 @@ +# Generated by Django 2.2.9 on 2020-03-18 20:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0076_auto_20200228_2013'), + ] + + operations = [ + migrations.CreateModel( + name='CastNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('nadpis', models.CharField(help_text='Nadpis podvěšené části obsahu', max_length=100, verbose_name='Nadpis')), + ], + options={ + 'verbose_name': 'Část (Node)', + 'verbose_name_plural': 'Části (Node)', + 'db_table': 'seminar_nodes_cast', + }, + bases=('seminar.treenode',), + ), + migrations.AlterField( + model_name='treenode', + name='first_child', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='father_of_first', to='seminar.TreeNode', verbose_name='první potomek'), + ), + ] diff --git a/seminar/models.py b/seminar/models.py index d1040739..25e4130d 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -1230,6 +1230,8 @@ class Obrazek(SeminarModelBase): help_text = 'Černobílá verze obrázku do čísla', upload_to = 'obrazky/%Y/%m/%d/', blank=True, null=True) + # TODO placement hint - chci ho tady / pred textem / za textem + class TreeNode(PolymorphicModel): class Meta: db_table = "seminar_nodes_treenode" @@ -1464,6 +1466,14 @@ class TextNode(TreeNode): def getOdkazStr(self): return str(self.text) +class CastNode(TreeNode): + class Meta: + db_table = 'seminar_nodes_cast' + verbose_name = 'Část (Node)' + verbose_name_plural = 'Části (Node)' + + nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu') + ## FIXME: Logiku přesunout do views. #class VysledkyBase(SeminarModelBase): From ab69ca3fe2936532c80e51ad734d3f72039a1700 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 21:56:25 +0100 Subject: [PATCH 23/41] Fix safe_father_of_first --- seminar/treelib.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index cfb49d86..def0f6da 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -20,7 +20,11 @@ def safe_pred(node): # A to samé pro .father_of_first def safe_father_of_first(node): - return node.prev + first_brother = node + while safe_pred(first_brother) is not None: + first_brother = safe_pred(first_brother) + try: + return first_brother.father_of_first except ObjectDoesNotExist: return None From 8fcb1b0871018ddf6387288d0881f5094cb1c521 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 22:32:02 +0100 Subject: [PATCH 24/41] Treelib: Fix all_brothers, all_proper_brothers --- seminar/treelib.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index def0f6da..946e61fd 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -65,14 +65,29 @@ def general_prev(node): # pred nyní nemá žádné potomky, takže je to poslední rekurzivní potomek původního předchůdce return pred -# Generátor bratrů +# Generátor pravých bratrů (konkrétně sebe a následujících potomků) # Generátor potomků níže spoléhá na to, že se tohle dá volat i s parametrem None. -def all_brothers(node): +def me_and_right_brothers(node): current = node while current is not None: yield current current = current.succ +# Generátor všech sourozenců (vč. sám sebe) +def all_brothers(node): + # Najdeme prvního bratra + fb = first_brother(node) + marb = me_and_all_brothers(fb) + for cur in marb: + yield cur + +def all_proper_brothers(node): + all = all_brothers(node) + for br in all: + if br is node: + continue + yield br + # Generátor potomků def all_children(node): brothers = all_brothers(node.first_child) From 63dd0da97c524f2582aef836eed1fbd090404688 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 22:36:07 +0100 Subject: [PATCH 25/41] Treelib: Add first_brother --- seminar/treelib.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index 946e61fd..8cf01660 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -18,13 +18,17 @@ def safe_pred(node): except ObjectDoesNotExist: return None +def first_brother(node): + brother = node + while safe_pred(brother) is not None: + brother = safe_pred(brother) + return brother + # A to samé pro .father_of_first def safe_father_of_first(node): - first_brother = node - while safe_pred(first_brother) is not None: - first_brother = safe_pred(first_brother) + first = first_brother(node) try: - return first_brother.father_of_first + return first.father_of_first except ObjectDoesNotExist: return None From bf96cac66f3cdc7e2e1fbf6e6783721865611556 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 22:40:06 +0100 Subject: [PATCH 26/41] Treelib: get_next_brother_of_type --- seminar/treelib.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index 8cf01660..c1b5531b 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -77,6 +77,11 @@ def me_and_right_brothers(node): yield current current = current.succ +def right_brothers(node): + generator = me_and_right_brothers(node.succ) + for item in generator: + yield item + # Generátor všech sourozenců (vč. sám sebe) def all_brothers(node): # Najdeme prvního bratra @@ -103,9 +108,12 @@ def all_children(node): ## Filtrační hledání # Najdi dalšího bratra nějakého typu, nebo None. # hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. -def get_next_brother_of_type(current, type): - pass -def get_prev_brother_of_type(current, type): +def get_next_brother_of_type(node, type): + for current in right_brothers(node) + if isinstance(current, type): + return current + +def get_prev_brother_of_type(node, type): pass # Totéž pro "the-right-order" pořadí From 95f3b9b120ec594c87c677a8f32bb8b902e3de42 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 22:44:27 +0100 Subject: [PATCH 27/41] Treelib missing : --- seminar/treelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index c1b5531b..1aa096bb 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -109,7 +109,7 @@ def all_children(node): # Najdi dalšího bratra nějakého typu, nebo None. # hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. def get_next_brother_of_type(node, type): - for current in right_brothers(node) + for current in right_brothers(node): if isinstance(current, type): return current From 91c2490d01a08ea249567592659a13b97c7986c9 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 23:02:14 +0100 Subject: [PATCH 28/41] Treelib typo --- seminar/treelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index 1aa096bb..709e6d98 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -86,7 +86,7 @@ def right_brothers(node): def all_brothers(node): # Najdeme prvního bratra fb = first_brother(node) - marb = me_and_all_brothers(fb) + marb = me_and_right_brothers(fb) for cur in marb: yield cur From 2888975eab470be12273db8aae5778c9bc977659 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 23:18:32 +0100 Subject: [PATCH 29/41] Treelib fix --- seminar/treelib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/seminar/treelib.py b/seminar/treelib.py index 709e6d98..8ad33c9c 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -19,6 +19,8 @@ def safe_pred(node): return None def first_brother(node): + if node is None: + return None brother = node while safe_pred(brother) is not None: brother = safe_pred(brother) From 8071cc255468b214213d4e84b3abf18aaee36bfc Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 23:22:46 +0100 Subject: [PATCH 30/41] =?UTF-8?q?Treelib=20dal=C5=A1=C3=AD=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/treelib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/seminar/treelib.py b/seminar/treelib.py index 8ad33c9c..f9ba04df 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -1,6 +1,7 @@ from django.core.exceptions import ObjectDoesNotExist # NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode # TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None) +# TODO: Chceme, aby všechno nějak zvládlo None jako parametr. # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. def print_tree(node,indent=0): From 78a667b42c4798eee73c4fd6f592026be67e0982 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Wed, 18 Mar 2020 23:33:59 +0100 Subject: [PATCH 31/41] =?UTF-8?q?Treelib:=20gener=C3=A1tor=20a=20funkce=20?= =?UTF-8?q?na=20hled=C3=A1n=C3=AD=20n=C3=A1sleduj=C3=ADc=C3=ADch=20nod?= =?UTF-8?q?=C5=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/treelib.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index f9ba04df..6e531c63 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -107,6 +107,12 @@ def all_children(node): yield br # Generátor následníků v "the-right-order" +# Bez tohoto vrcholu +def all_following(node): + current = general_next(node) + while current is not None: + yield current + current = general_next(current) ## Filtrační hledání # Najdi dalšího bratra nějakého typu, nebo None. @@ -115,15 +121,32 @@ def get_next_brother_of_type(node, type): for current in right_brothers(node): if isinstance(current, type): return current + return None def get_prev_brother_of_type(node, type): - pass + # Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. + current = node + while safe_pred(current) is not None: + current = safe_pred(current) + if isinstance(current, type): + return current + return None # Totéž pro "the-right-order" pořadí -def get_next_node_of_type(current, type): - pass -def get_next_node_of_type(current, type): - pass +def get_next_node_of_type(node, type): + for cur in all_folowing(node): + if isinstance(cur, type): + return cur + return None + +def get_prev_node_of_type(node, type): + # Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. + current = node + while general_prev(current) is not None: + current = general_prev(current) + if isinstance(current, type): + return current + return None From 62d6c0df02130a0102d8da72d6a347a548f8b11b Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Mar 2020 00:22:18 +0100 Subject: [PATCH 32/41] =?UTF-8?q?Treelib:=20vyr=C3=A1b=C3=ADme=20nov=C3=A9?= =?UTF-8?q?=20uzly...=20snad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/treelib.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index 6e531c63..0f21454c 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -153,11 +153,25 @@ def get_prev_node_of_type(node, type): # Editace stromu: def create_node_after(predecessor, type, **kwargs): - pass + new_node = type.objects.create(**kwargs) + new_node.save() + succ = predecessor.succ + predecessor.succ = new_node + predecessor.save() + new_node.succ = succ + new_node.save() # Vyrábí prvního syna, ostatní nalepí za (existují-li) def create_child(parent, type, **kwargs): - pass + new_node = type.objects.create(**kwargs) + new_node.save() + orig_child = parent.first_child + parent.first_child = new_node + parent.save() + if orig_child is not None: + # Přidáme původního prvního syna jako potomka nového vrcholu + new_node.succ = orig_child + new_node.save() def create_node_before(some, arguments, but, i, dont, know, which, yet): pass From ac0c616e1314e8208083864c392c9fb19c134b79 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Mar 2020 00:37:08 +0100 Subject: [PATCH 33/41] =?UTF-8?q?Treelib:=20obecn=C3=BD=20swap=20implement?= =?UTF-8?q?ovat=20nebudu.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moc edge-cases. --- seminar/treelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index 0f21454c..f890868b 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -179,7 +179,7 @@ def create_node_before(some, arguments, but, i, dont, know, which, yet): # ValueError, pokud je (aspoň) jeden parametr None def swap(node, other): - pass + raise NotImplementedError("YAGNI (You aren't gonna need it).") def swap_pred(node): pass From d84cce71f32fd1a2aaa780b6d4c5b01467696065 Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Thu, 19 Mar 2020 00:50:39 +0100 Subject: [PATCH 34/41] seminar | Prvni fungujici verze renderovani treenode struktury. --- seminar/templates/seminar/treenode.html | 10 + .../templates/seminar/treenode_recursive.html | 28 +++ seminar/templatetags/treenodes.py | 49 ++++ seminar/urls.py | 8 +- seminar/views/views_all.py | 219 ++++++++---------- 5 files changed, 191 insertions(+), 123 deletions(-) create mode 100644 seminar/templates/seminar/treenode.html create mode 100644 seminar/templates/seminar/treenode_recursive.html create mode 100644 seminar/templatetags/treenodes.py diff --git a/seminar/templates/seminar/treenode.html b/seminar/templates/seminar/treenode.html new file mode 100644 index 00000000..0fd734ef --- /dev/null +++ b/seminar/templates/seminar/treenode.html @@ -0,0 +1,10 @@ +{% extends "seminar/archiv/base_ulohy.html" %} + +{% load comments %} + +{% block content %} + +{%with obj=tnldata depth=1 template_name="seminar/treenode_recursive.html" %} + {%include template_name%} +{%endwith%} +{% endblock content %} diff --git a/seminar/templates/seminar/treenode_recursive.html b/seminar/templates/seminar/treenode_recursive.html new file mode 100644 index 00000000..0cf37d9a --- /dev/null +++ b/seminar/templates/seminar/treenode_recursive.html @@ -0,0 +1,28 @@ +{% load treenodes %} +{# {{depth}} #} +
+{% if obj.node|isRocnik %} + Ročník {{obj.node.rocnik}} +{% elif obj.node|isCislo %} + Číslo {{obj.node.cislo}} +{% elif obj.node|isTemaVCisle %} + Téma {{obj.node.tema.nazev}} +{% elif obj.node|isUlohaZadani %} +Úloha {{obj.node.uloha.kod_v_rocniku}} ({{obj.node.uloha.max_body}} b) +{% elif obj.node|isUlohaVzorak %} +Řešení: {{obj.node.uloha.kod_v_rocniku}} +{% elif obj.node|isText %} +{{obj.node.text.na_web}} +{% else %} +Objekt jiného typu {{obj.node}} +{% endif %} + {%if obj.children %} +
+ {%for ch in obj.children %} + {%with obj=ch depth=depth|add:"1" template_name="seminar/treenode_recursive.html" %} + {%include template_name%} + {%endwith%} + {%endfor%} +
+ {%endif%} +
diff --git a/seminar/templatetags/treenodes.py b/seminar/templatetags/treenodes.py new file mode 100644 index 00000000..0d60765e --- /dev/null +++ b/seminar/templatetags/treenodes.py @@ -0,0 +1,49 @@ +from django import template +import seminar.models as m + +register = template.Library() + +@register.filter +def isRocnik(value): + return isinstance(value, m.RocnikNode) + +@register.filter +def isCislo(value): + return isinstance(value, m.CisloNode) + +@register.filter +def isCast(value): + return isinstance(value, m.CastNode) + +@register.filter +def isText(value): + return isinstance(value, m.TextNode) + +@register.filter +def isTemaVCisle(value): + return isinstance(value, m.TemaVCisleNode) + +@register.filter +def isKonfera(value): + return isinstance(value, m.KonferaNode) + +@register.filter +def isClanek(value): + return isinstance(value, m.ClanekNode) + +@register.filter +def isUlohaVzorak(value): + return isinstance(value, m.UlohaVzorakNode) + +@register.filter +def isUlohaZadani(value): + return isinstance(value, m.UlohaZadaniNode) + +@register.filter +def isPohadka(value): + return isinstance(value, m.PohadkaNode) + +#@register.filter +#def isOtisteneReseniNode(value): +# return isinstance(value, m.OtisteneReseniNode) + diff --git a/seminar/urls.py b/seminar/urls.py index 1d5348da..002170c4 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -25,6 +25,7 @@ urlpatterns = [ path('rocnik//', views.RocnikView.as_view(), name='seminar_rocnik'), #path('cislo/./', views.CisloView.as_view(), name='seminar_cislo'), path('problem//', views.ProblemView.as_view(), name='seminar_problem'), + path('treenode//', views.TreeNodeView.as_view(), name='seminar_treenode'), #path('problem/(?P\d+)/(?P\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'), # Soustredeni @@ -107,9 +108,6 @@ urlpatterns = [ path('auth/login/', views.LoginView.as_view(), name='login'), path('auth/logout/', views.LogoutView.as_view(), name='logout'), path('auth/resitel/', views.ResitelView.as_view(), name='seminar_resitel'), - path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), - path('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'), - path('autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), path('auth/reset_password/', views.PasswordResetView.as_view(), name='reset_password'), path('auth/change_password/', views.PasswordChangeView.as_view(), name='change_password'), path('auth/reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), @@ -117,6 +115,10 @@ urlpatterns = [ path('auth/reset_password_complete/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'), path('auth/resitel_edit', views.resitelEditView, name='seminar_resitel_edit'), + # Autocomplete + path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), + path('autocomplete/resitel/',views.ResitelAutocomplete.as_view(), name='autocomplete_resitel'), + path('autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), path('temp/add_solution', views.AddSolutionView.as_view(),name='seminar_vloz_reseni'), path('temp/submit_solution', views.SubmitSolutionView.as_view(),name='seminar_nahraj_reseni'), diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 3cfb0535..40357769 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -19,7 +19,7 @@ from django.db import transaction import seminar.models as s from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva -from seminar import utils +from seminar import utils,treelib from .unicodecsv import UnicodeWriter from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm import seminar.forms as f @@ -83,131 +83,110 @@ class ObalkovaniView(generic.ListView): context['cislo'] = self.cislo return context +class TNLData(object): + def __init__(self,anode): + self.node = anode + self.children = [] +def treenode_strom_na_seznamy(node): + out = TNLData(node) + for ch in treelib.all_children(node): + outitem = treenode_strom_na_seznamy(ch) + out.children.append(outitem) + return out -def AktualniZadaniView(request): - nastaveni = get_object_or_404(Nastaveni) - verejne = nastaveni.aktualni_cislo.verejne() - problemy = Problem.objects.filter(cislo_zadani=nastaveni.aktualni_cislo).filter(stav = 'zadany') - ulohy = problemy.filter(typ = 'uloha').order_by('kod') - serialy = problemy.filter(typ = 'serial').order_by('kod') - jednorazove_problemy = [ulohy, serialy] - return render(request, 'seminar/zadani/AktualniZadani.html', - {'nastaveni': nastaveni, - 'jednorazove_problemy': jednorazove_problemy, - 'temata': verejna_temata(nastaveni.aktualni_rocnik), - 'verejne': verejne, - }, - ) - -def ZadaniTemataView(request): - nastaveni = get_object_or_404(Nastaveni) - temata = verejna_temata(nastaveni.aktualni_rocnik) - for t in temata: - if request.user.is_staff: - t.prispevky = t.prispevek_set.filter(problem=t) - else: - t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True) - return render(request, 'seminar/zadani/Temata.html', - { - 'temata': temata, - } - ) +class TreeNodeView(generic.DetailView): + model = s.TreeNode + template_name = 'seminar/treenode.html' -# TODO Napsat tuto funkci znovu rekurzivně podle Jethrorad. Potom se podívat, jak lehce se dá modifikovat pro Rozcestník. Pokud lehce, rozšířit ji. Pokud složitě - použít tuhle -def vytahniZLesaSeznam(tematko, koren, pouze_zajimave=False): - returnVal = [] - - stack = [] - stack.append((koren.first_child, 0, False)) #Tuple of node, depth and relevance - - while len(stack) > 0: - wn, wd, wr = stack.pop() - - if wn.succ != None: - stack.append((wn.succ, wd, wr)) - if isinstance(wn, s.TemaVCisleNode): - print("TEMA") - print(wn.tema.id) - print(tematko.id) - if wn.tema.id == tematko.id: - returnVal.append((posledni_cislo, 0)) - print("PRIDANO") - wr = True - wd = 1 - - if wn.srolovatelne: - tagOpen = s.Text(na_web = "Otevírací srolovací tag") - tagOpenNode = s.TextNode(text = tagOpen) - tagClose = s.Text(na_web = "Zavírací srolovací tag") - tagCloseNode = s.TextNode(text = tagClose) - stack.append((tagCloseNode, wd, True)) - - if wn.first_child != None: - stack.append((wn.first_child, wd + 1, wr)) - - if isinstance(wn, s.CisloNode): - posledni_cislo = wn - print(wn) - - if wr: - print("ZAJIMAVE") - if pouze_zajimave: - if not wn.zajimave: - continue - returnVal.append((wn, wd)) - return returnVal - -def TematkoView(request, rocnik, tematko): - nastaveni = s.Nastaveni.objects.first() - rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik) - tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko) - seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode) - for node, depth in seznam: - if node.isinstance(node, s.KonferaNode): - raise Exception("Not implemented yet") - if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou - pass - - return render(request, 'seminar/tematka/toaletak.html', {}) + def get_context_data(self,**kwargs): + context = super().get_context_data(**kwargs) + context['tnldata'] = treenode_strom_na_seznamy(self.object) + return context -def TemataRozcestnikView(request): - print("=============================================") - nastaveni = s.Nastaveni.objects.first() - tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik()) - tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku - for tematko_object in tematka_objects: - print("AKTUALNI TEMATKO") - print(tematko_object.id) - odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu - print(odkazy) - cisla = [] # List tuplů (nazev cisla, list odkazů) - vcisle = [] - cislo = None - for odkaz in odkazy: - if odkaz[1] == 0: - if cislo != None: - cisla.append((cislo, vcisle)) - cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()) - vcisle = [] - else: - print(odkaz[0].getOdkaz()) - vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())) - if cislo != None: - cisla.append((cislo, vcisle)) - - print(cisla) - tematka.append({ - "kod" : tematko_object.kod, - "nazev" : tematko_object.nazev, - "abstrakt" : tematko_object.abstrakt, - "obrazek": tematko_object.obrazek, - "cisla" : cisla - }) - return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) - + +#def AktualniZadaniView(request): +# nastaveni = get_object_or_404(Nastaveni) +# verejne = nastaveni.aktualni_cislo.verejne() +# problemy = Problem.objects.filter(cislo_zadani=nastaveni.aktualni_cislo).filter(stav = 'zadany') +# ulohy = problemy.filter(typ = 'uloha').order_by('kod') +# serialy = problemy.filter(typ = 'serial').order_by('kod') +# jednorazove_problemy = [ulohy, serialy] +# return render(request, 'seminar/zadani/AktualniZadani.html', +# {'nastaveni': nastaveni, +# 'jednorazove_problemy': jednorazove_problemy, +# 'temata': verejna_temata(nastaveni.aktualni_rocnik), +# 'verejne': verejne, +# }, +# ) +# +#def ZadaniTemataView(request): +# nastaveni = get_object_or_404(Nastaveni) +# temata = verejna_temata(nastaveni.aktualni_rocnik) +# for t in temata: +# if request.user.is_staff: +# t.prispevky = t.prispevek_set.filter(problem=t) +# else: +# t.prispevky = t.prispevek_set.filter(problem=t, zverejnit=True) +# return render(request, 'seminar/zadani/Temata.html', +# { +# 'temata': temata, +# } +# ) +# +# +# +#def TematkoView(request, rocnik, tematko): +# nastaveni = s.Nastaveni.objects.first() +# rocnik_object = s.Rocnik.objects.filter(rocnik=rocnik) +# tematko_object = s.Tema.objects.filter(rocnik=rocnik_object[0], kod=tematko) +# seznam = vytahniZLesaSeznam(tematko_object[0], nastaveni.aktualni_rocnik().rocniknode) +# for node, depth in seznam: +# if node.isinstance(node, s.KonferaNode): +# raise Exception("Not implemented yet") +# if node.isinstance(node, s.PohadkaNode): # Mohu ignorovat, má pod sebou +# pass +# +# return render(request, 'seminar/tematka/toaletak.html', {}) +# +# +#def TemataRozcestnikView(request): +# print("=============================================") +# nastaveni = s.Nastaveni.objects.first() +# tematka_objects = s.Tema.objects.filter(rocnik=nastaveni.aktualni_rocnik()) +# tematka = [] #List tematka obsahuje pro kazde tematko object a list vsech TemaVCisleNodu - implementované pomocí slovníku +# for tematko_object in tematka_objects: +# print("AKTUALNI TEMATKO") +# print(tematko_object.id) +# odkazy = vytahniZLesaSeznam(tematko_object, nastaveni.aktualni_rocnik().rocniknode, pouze_zajimave = True) #Odkazy jsou tuply (node, depth) v listu +# print(odkazy) +# cisla = [] # List tuplů (nazev cisla, list odkazů) +# vcisle = [] +# cislo = None +# for odkaz in odkazy: +# if odkaz[1] == 0: +# if cislo != None: +# cisla.append((cislo, vcisle)) +# cislo = (odkaz[0].getOdkazStr(), odkaz[0].getOdkaz()) +# vcisle = [] +# else: +# print(odkaz[0].getOdkaz()) +# vcisle.append((odkaz[0].getOdkazStr(), odkaz[0].getOdkaz())) +# if cislo != None: +# cisla.append((cislo, vcisle)) +# +# print(cisla) +# tematka.append({ +# "kod" : tematko_object.kod, +# "nazev" : tematko_object.nazev, +# "abstrakt" : tematko_object.abstrakt, +# "obrazek": tematko_object.obrazek, +# "cisla" : cisla +# }) +# return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) +# #def ZadaniAktualniVysledkovkaView(request): # nastaveni = get_object_or_404(Nastaveni) From 7cfb2c414e18bc16acbc671c37d4fa6cf154161b Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Thu, 19 Mar 2020 00:59:31 +0100 Subject: [PATCH 35/41] seminar | Pridan OtisknuteReseniNode, zakomentovany stare views. --- seminar/migrations/0078_otistenereseninode.py | 27 +++++++++++++++++++ seminar/models.py | 20 ++++++++++++++ seminar/urls.py | 8 +++--- 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 seminar/migrations/0078_otistenereseninode.py diff --git a/seminar/migrations/0078_otistenereseninode.py b/seminar/migrations/0078_otistenereseninode.py new file mode 100644 index 00000000..2f426a17 --- /dev/null +++ b/seminar/migrations/0078_otistenereseninode.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.9 on 2020-03-18 23:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0077_auto_20200318_2146'), + ] + + operations = [ + migrations.CreateModel( + name='OtisteneReseniNode', + fields=[ + ('treenode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='seminar.TreeNode')), + ('reseni', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='seminar.Reseni', verbose_name='reseni')), + ], + options={ + 'verbose_name': 'Otištěné řešení (Node)', + 'verbose_name_plural': 'Otištěná řešení (Node)', + 'db_table': 'seminar_nodes_otistene_reseni', + }, + bases=('seminar.treenode',), + ), + ] diff --git a/seminar/models.py b/seminar/models.py index 25e4130d..82b09944 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -1474,6 +1474,26 @@ class CastNode(TreeNode): nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu') + def aktualizuj_nazev(self): + self.nazev = "CastNode: "+str(self.nadpis) + + def getOdkazStr(self): + return str(self.nadpis) + +class OtisteneReseniNode(TreeNode): + class Meta: + db_table = 'seminar_nodes_otistene_reseni' + verbose_name = 'Otištěné řešení (Node)' + verbose_name_plural = 'Otištěná řešení (Node)' + reseni = models.ForeignKey(Reseni, + on_delete=models.PROTECT, + verbose_name = 'reseni') + + def aktualizuj_nazev(self): + self.nazev = "OtisteneReseniNode: "+str(self.reseni) + + def getOdkazStr(self): + return str(self.reseni) ## FIXME: Logiku přesunout do views. #class VysledkyBase(SeminarModelBase): diff --git a/seminar/urls.py b/seminar/urls.py index 002170c4..4ec060ce 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -8,8 +8,8 @@ from django.contrib.auth import views as auth_views staff_member_required = user_passes_test(lambda u: u.is_staff) urlpatterns = [ - path('aktualni/temata/', views.TemataRozcestnikView), - path('/t/', views.TematkoView), +# path('aktualni/temata/', views.TemataRozcestnikView), +# path('/t/', views.TematkoView), # REDIRECTy path('jak-resit/', RedirectView.as_view(url='/co-je-MaM/jak-resit/')), @@ -60,8 +60,8 @@ urlpatterns = [ ), # Zadani - path('zadani/aktualni/', views.AktualniZadaniView, name='seminar_aktualni_zadani'), - path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'), +# path('zadani/aktualni/', views.AktualniZadaniView, name='seminar_aktualni_zadani'), +# path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'), #path('zadani/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'), path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), From db5f0fd0c57837c88abe739f91a252c0af0e055a Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Mar 2020 01:17:09 +0100 Subject: [PATCH 36/41] TreeLib: Swap left and right --- seminar/treelib.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index f890868b..a64c86d9 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -181,10 +181,44 @@ def create_node_before(some, arguments, but, i, dont, know, which, yet): def swap(node, other): raise NotImplementedError("YAGNI (You aren't gonna need it).") -def swap_pred(node): +# Exception, kterou některé metody při špatném použití mohou házet +# Hlavní důvod je možnost informovat o selhání, aby se příslušný problém dal zobrazit na frontendu, +class TreeLibError(RuntimeError): pass + +def swap_pred(node): + if node is None: + raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") + pred = safe_pred(node) + if pred is None: + raise TreeLibError("Nelze posunout vlevo, není tam žádný další uzel.") + pre_pred = safe_pred(pred) + succ = node.succ + + if pre_pred is not None: + pre_pred.succ = node + pre_pred.save() + node.succ = pred + node.save() + pred.succ = succ + pred.save() + def swap_succ(node): - pass + if node is None: + raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") + succ = node.succ + if succ is None: + raise TreeLibError("Nelze posunout vpravo, není tam žádný další uzel") + pred = safe_pred(node) + post_succ = succ.succ + + if pred is not None: + pred.succ = succ + pred.save() + succ.succ = node + succ.save() + node.succ = post_succ + node.save() # Rotace stromu # Dokumentace viz wiki: From c1e107c89e9ea193918c60c150d07a27f41d9e7f Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Mar 2020 01:24:51 +0100 Subject: [PATCH 37/41] Treelib: create_node_before --- seminar/treelib.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/seminar/treelib.py b/seminar/treelib.py index a64c86d9..26b77d1d 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -173,9 +173,20 @@ def create_child(parent, type, **kwargs): new_node.succ = orig_child new_node.save() -def create_node_before(some, arguments, but, i, dont, know, which, yet): - pass - # Tohle bude hell. +def create_node_before(successor, type, **kwargs): + if safe_pred(successor) is not None: + # Easy: přidáme za předchůdce + create_node_after(successor.prev, type, **kwargs) + # Nemáme předchůdce, jsme tedy první z bratrů. Máme otce? + if safe_father_of_first(successor) is not None: + # Ano -> Easy: vyrobíme nového potomka + # NOTE: Tohle je možná trošku abuse implementace výše, ale to nevadí moc... + create_child(successor.father_of_first, type, **kwargs) + # Teď už easy: Jsme sirotci, takže se vyrobíme a našeho následníka si přidáme jako succ + new = type.objects.create(**kwargs) + new.succ = successor + new.save() + # ValueError, pokud je (aspoň) jeden parametr None def swap(node, other): From 64074d5dc6cbd82daebd3c6591d6e090cae88857 Mon Sep 17 00:00:00 2001 From: Anet Date: Thu, 19 Mar 2020 01:31:00 +0100 Subject: [PATCH 38/41] =?UTF-8?q?views:=20skoro=20funk=C4=8Dn=C3=AD=20v?= =?UTF-8?q?=C3=BDsledkovka,=20probl=C3=A9my=20s=20body=20odjak=C5=BEiva,?= =?UTF-8?q?=20dotazy=20do=20datab=C3=A1ze=20u=C5=BE=20jinak=20rozumn=C4=9B?= =?UTF-8?q?=20rychl=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/views/views_all.py | 104 +++++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 26 deletions(-) diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 3065e037..3dcf1331 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -477,6 +477,35 @@ def hlavni_problemy_cisla(cislo): return hlavni_problemy +# vrátí slovník řešitel:body obsahující počty bodů zadaných řešitelů za daný ročník +# POZOR! Aktuálně počítá jen za posledních 10 let od zadaného ročníku +def body_resitelu_odjakziva(rocnik, resitele): + body_odjakziva = {} + + for r in resitele: + body_odjakziva[str(r.id)] = 0 +# # Body za posledních 10 let je dobrá aproximace pro naše potřeby (výsledkovka +# # s aktivními řešiteli) +# +# body_pred_roky = [] +# for i in range(0, 10): +# body_pred_roky.append(body_resitelu_za_rocnik(rocnik-i, resitele)) +# +# for r in resitele: +# for i in range(0,10): +# body_odjakziva[str(r.id)] += body_pred_roky[i][str(r.id)] + + +# Nasledující řešení je sice správné, ale moc pomalé: + for res in Reseni.objects.prefetch_related('resitele', 'hodnoceni').all(): + for r in res.resitele.all(): + # daný řešitel nemusí být v naší podmnožině + if r not in resitele: continue + + for hodn in res.hodnoceni.all(): + pricti_body(body_odjakziva, r, hodn.body) + return body_odjakziva + # vrátí slovník řešitel:body obsahující počty bodů zadaných řešitelů za daný ročník def body_resitelu_za_rocnik(rocnik, aktivni_resitele): body_za_rocnik = {} @@ -485,11 +514,11 @@ def body_resitelu_za_rocnik(rocnik, aktivni_resitele): body_za_rocnik[str(ar.id)] = 0 # spočítáme body řešitelům přes všechna řešení s hodnocením v daném ročníku - reseni = Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik) + reseni = Reseni.objects.prefetch_related('resitele', 'hodnoceni').filter(hodnoceni__cislo_body__rocnik=rocnik) for res in reseni: for resitel in res.resitele.all(): for hodn in res.hodnoceni.all(): - body_za_rocnik[str(resitel.id)] += hodn.body + pricti_body(body_za_rocnik, resitel, hodn.body) return body_za_rocnik #def body_resitele_odjakziva(resitel): @@ -635,13 +664,26 @@ class RadekVysledkovky(object): """Obsahuje věci, které se hodí vědět při konstruování výsledkovky. Umožňuje snazší práci v templatu (lepší, než seznam).""" - def __init__(self, poradi, resitel, body_problemy_sezn, body_cislo, body_rocnik): + def __init__(self, poradi, resitel, body_problemy_sezn, + body_cislo, body_rocnik, body_odjakziva): self.resitel = resitel self.body_cislo = body_cislo self.body_rocnik = body_rocnik -# TODO self.body_celkem_odjakziva = odjakziva + self.body_celkem_odjakziva = body_odjakziva self.poradi = poradi self.body_problemy_sezn = body_problemy_sezn + + +# přiřazuje danému řešiteli body do slovníku +def pricti_body(slovnik, resitel, body): + # testujeme na None (""), pokud je to první řešení + # daného řešitele, předěláme na 0 + # (v dalším kroku přičteme reálný počet bodů), + # rozlišujeme tím mezi 0 a neodevzdaným řešením + if slovnik[str(resitel.id)] == "": + slovnik[str(resitel.id)] = 0 + + slovnik[str(resitel.id)] += body class CisloView(generic.DetailView): model = Cislo @@ -678,8 +720,8 @@ class CisloView(generic.DetailView): ## TODO možná chytřeji vybírat aktivní řešitele # aktivní řešitelé - chceme letos něco poslal, TODO později vyfiltrujeme ty, kdo mají # u alespoň jedné hodnoty něco jiného než NULL - aktivni_resitele = Resitel.objects.filter( - rok_maturity__gte=cislo.rocnik.druhy_rok()) + aktivni_resitele = list(Resitel.objects.filter( + rok_maturity__gte=cislo.rocnik.druhy_rok())) # TODO: zkusit hodnoceni__rocnik... #.filter(hodnoceni_set__rocnik__eq=cislo_rocnik) # zakládání prázdných záznamů pro řešitele @@ -692,66 +734,76 @@ class CisloView(generic.DetailView): slovnik[str(ar.id)] = "" # vezmeme všechna řešení s body do daného čísla - reseni_do_cisla = Reseni.objects.filter(hodnoceni__cislo_body=cislo) + reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'hodnoceni', 'resitele').filter(hodnoceni__cislo_body=cislo) # projdeme všechna řešení do čísla a přičteme body každému řešiteli do celkových # bodů i do bodů za problém for reseni in reseni_do_cisla: - body = reseni.hodnoceni.body - problem = reseni.problem - nadproblem = hlavni_problem(problem) - nadproblem_slovnik = hlavni_problemy_slovnik[str(nadproblem.id)] - for resitel in reseni.resitele: - # testujeme na None, pokud první řešení daného řešitele, předěláme na 0 - # (v dalším kroku přičteme reálný počet bodů), rozlišujeme tím mezi 0 a - # neodevzdaným řešením - if cislobody[str(resitel.id)] == "": - cislobody[str(resitel.id)] = 0 - cislobody[str(resitel.id)] += body - - if nadproblem_slovnik[str(resitel.id)] == "": - nadproblem_slovnik[str(resitel.id)] = 0 - nadproblem_slovnik[str(resitel.id)] += body + + # řešení může řešit více problémů + for prob in list(reseni.problem.all()): + nadproblem = hlavni_problem(prob) + nadproblem_slovnik = hlavni_problemy_slovnik[str(nadproblem.id)] + + # a více hodnocení + for hodn in list(reseni.hodnoceni.all()): + body = hodn.body + + # a více řešitelů + for resitel in list(reseni.resitele.all()): + pricti_body(cislobody, resitel, body) + pricti_body(nadproblem_slovnik, resitel, body) # zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně resitel_rocnikbody_slov = body_resitelu_za_rocnik(cislo.rocnik, aktivni_resitele) resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), key = lambda x: x[1], reverse = True) - + + # získáme body odjakživa + resitel_odjakzivabody_slov = body_resitelu_odjakziva(cislo.rocnik.druhy_rok(), + aktivni_resitele) + # řešitelé setřídění podle bodů za číslo sestupně setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn] setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id] # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] + odjakziva_body = [] rocnik_body = [] cislo_body = [] hlavni_problemy_body = [] for ar_id in setrizeni_resitele_id: # vytáhneme ze slovníků body pro daného řešitele + odjakziva_body.append(resitel_odjakzivabody_slov[ar_id]) rocnik_body.append(resitel_rocnikbody_slov[ar_id]) cislo_body.append(cislobody[ar_id]) problemy = [] for hp in hlavni_problemy: problemy.append(hlavni_problemy_slovnik[str(hp.id)][ar_id]) hlavni_problemy_body.append(problemy) + print("{}: body za problémy - {}, číslobody - {}, ročníkbody - {}, odjakživabody - ".format(ar_id, problemy, cislobody[ar_id], resitel_rocnikbody_slov[ar_id])) # pořadí určíme pomocí funkce, které dáme celkové body za ročník vzestupně poradi = sloupec_s_poradim(rocnik_body) radky_vysledkovky = [] for i in range(0, len(setrizeni_resitele_id)): radek = RadekVysledkovky(poradi[i], setrizeni_resitele[i], - hlavni_problemy_body[i], cislo_body[i], rocnik_body[i]) + hlavni_problemy_body[i], cislo_body[i], rocnik_body[i], + odjakziva_body[i]) radky_vysledkovky.append(radek) + print("Přikládám {}-tý řádek.".format(i)) + print("Následuje předávání do kontextu.") # vytahané informace předáváme do kontextu context['cislo'] = cislo context['radky_vysledkovky'] = radky_vysledkovky context['problemy'] = hlavni_problemy # context['v_cisle_zadane'] = TODO # context['resene_problemy'] = resene_problemy - #XXX testovat - #XXX opravit to, že se nezobrazují body za jednotlivé úlohy + #XXX nefungují body odjakživa - asi typový problém + #XXX nefungují tituly - možná korelace s výše uvedeným problémem + print("Předávám kontext.") return context # problemy = sorted(set(r.problem for r in reseni), key=lambda x:(poradi_typu[x.typ], x.kod_v_rocniku())) From 0e2784d621dd6f3867d7b595ae33922e279a7f25 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Thu, 19 Mar 2020 01:47:19 +0100 Subject: [PATCH 39/41] Treelib: rotace stromu --- seminar/treelib.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/seminar/treelib.py b/seminar/treelib.py index 26b77d1d..260a8bac 100644 --- a/seminar/treelib.py +++ b/seminar/treelib.py @@ -235,6 +235,52 @@ def swap_succ(node): # Dokumentace viz wiki: # (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku) def raise_node(node): + if node is None: + raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") + # Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) + # FIXME: Velmi naivní, chybí error checky + D = node + C = get_parent(D) + E = C.succ + subtree4_head = D.first_child + subtree4_tail = last_brother(subtree4_head) + subtree3P_head = D.succ + subtree3L_head = C.first_child + subtree3L_tail = safe_pred(D) + + # Prostor pro motlitbu... pass + + # Amen. + C.succ = D + C.save() + D.succ = E + D.save() + subtree3L_tail.succ = None + subtree3L_tail.save() + subtree4_tail.succ = subtree3P.head + subtree4_tail.save() + + # To by mělo být všechno... + def lower_node(node): + if node is None: + raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") + # Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) + # FIXME: Velmi naivní, chybí error checky + C = node + D = C.succ + B = safe_pred(C) + subtree2_head = B.first_child + subtree2_tail = last_brother(subtree2_head) + + # Prostor pro motlitbu... pass + + # Amen. + B.succ = D + B.save() + subtree2_tail.succ = C + subtree2_tail.save() + + # To by mělo být všechno... From 0b7f0c2e426623878dc3e86e9ca24aeffa105635 Mon Sep 17 00:00:00 2001 From: "Tomas \"Jethro\" Pokorny" Date: Wed, 25 Mar 2020 21:01:14 +0100 Subject: [PATCH 40/41] seminar | clanek ma flag, zda je resitelsky, opraveno aktualni zadani --- Schema_new.dia | Bin 15640 -> 15640 bytes seminar/migrations/0079_clanek_resitelsky.py | 18 ++++++++++++ seminar/models.py | 2 ++ seminar/urls.py | 2 +- seminar/views/views_all.py | 28 ++++++++----------- 5 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 seminar/migrations/0079_clanek_resitelsky.py diff --git a/Schema_new.dia b/Schema_new.dia index ad987cfada8fe4457c5825ff13c012e2febbe492..f09c0589bd3e2de024ed79fd1c89b52b5e1e2bb4 100644 GIT binary patch literal 15640 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 he*xuCr0M_w literal 15640 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: From 5ca3b11f321b7255ddd0fc4da32fd41770e7ebac Mon Sep 17 00:00:00 2001 From: Anet Date: Wed, 25 Mar 2020 21:01:36 +0100 Subject: [PATCH 41/41] =?UTF-8?q?V=C3=BDsledkovka=20=C4=8D=C3=ADsla=20funk?= =?UTF-8?q?=C4=8Dn=C3=AD,=20ale=20pomal=C3=A1=20verze.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/models.py | 8 ++++-- seminar/templates/seminar/archiv/cislo.html | 31 +++++++++++---------- seminar/views/views_all.py | 15 +++++----- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/seminar/models.py b/seminar/models.py index 82b09944..83a9ea61 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -304,9 +304,10 @@ class Resitel(SeminarModelBase): return sum(h.body for h in list(vsechna_hodnoceni)) - def get_titul(self): + def get_titul(self, celkove_body=None): "Vrati titul" - celkove_body = self.vsechny_body() + if celkove_body is None: + celkove_body = self.vsechny_body() if celkove_body < 10: return '' @@ -898,6 +899,9 @@ class Reseni(SeminarModelBase): # má OneToOneField s: # Konfera + # má ForeignKey s: + # Hodnoceni + def __str__(self): return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all())) # NOTE: Potenciální DB HOG (bez select_related) diff --git a/seminar/templates/seminar/archiv/cislo.html b/seminar/templates/seminar/archiv/cislo.html index 63788e06..d3cc50bb 100644 --- a/seminar/templates/seminar/archiv/cislo.html +++ b/seminar/templates/seminar/archiv/cislo.html @@ -1,7 +1,8 @@ {% extends "seminar/archiv/base_cisla.html" %} -{% block content %} -

+ {% block content %} +
+

{% block nadpis1a %}{% block nadpis1b %} Číslo {{ cislo }} @@ -45,14 +46,15 @@
  • Obálkování
  • - {% endif %} + {% endif %} {% if cislo.verejna_vysledkovka %} -

    Výsledkovka

    +

    Výsledkovka ({% now "jS F Y H:i" %})

    + {% else %} {% if user.is_staff %}
    -

    Výsledkovka (neveřejná)

    +

    Výsledkovka (neveřejná, {% now "jS F Y H:i:s" %})

    {% endif %} {% endif %} @@ -61,36 +63,37 @@ # Jméno - {# problémy by měly být veřejné, když je veřejná výsledkovka #} {% for p in problemy %} {{ p.kod_v_rocniku }} {% endfor %} Za číslo Za ročník - {#Odjakživa#} + Odjakživa {% for rv in radky_vysledkovky %} {% autoescape off %}{{ rv.poradi }}{% endautoescape %} - {% if rv.resitel.get_titul != "" %} - {{ rv.resitel.get_titul }}MM + {% if rv.titul is not '' %} + {{ rv.titul }}MM {% endif %} {{ rv.resitel.osoba.plne_jmeno }} - {% for b in rv.hlavni_problemy_body %} + {% for b in rv.body_problemy_sezn %} {{ b }} {% endfor %} {{ rv.body_cislo }} {{ rv.body_rocnik }} - {# {{ rv.body_celkem_odjakziva }}#} + {{ rv.body_celkem_odjakziva }} {% endfor %} - {% endif %} + {% endif %} {% if not cislo.verejna_vysledkovka and user.is_staff %}
    {% endif %} -
    -{% endblock content %} + Čas: {% now "jS F Y H:i:s" %} + + +{% endblock content %} diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 945d283e..39722e46 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -476,12 +476,12 @@ def body_resitelu_odjakziva(rocnik, resitele): # Nasledující řešení je sice správné, ale moc pomalé: - for res in Reseni.objects.prefetch_related('resitele', 'hodnoceni').all(): + for res in Reseni.objects.prefetch_related('resitele', 'hodnoceni_set').all(): for r in res.resitele.all(): # daný řešitel nemusí být v naší podmnožině if r not in resitele: continue - for hodn in res.hodnoceni.all(): + for hodn in res.hodnoceni_set.all(): pricti_body(body_odjakziva, r, hodn.body) return body_odjakziva @@ -493,10 +493,10 @@ def body_resitelu_za_rocnik(rocnik, aktivni_resitele): body_za_rocnik[str(ar.id)] = 0 # spočítáme body řešitelům přes všechna řešení s hodnocením v daném ročníku - reseni = Reseni.objects.prefetch_related('resitele', 'hodnoceni').filter(hodnoceni__cislo_body__rocnik=rocnik) + reseni = Reseni.objects.prefetch_related('resitele', 'hodnoceni_set').filter(hodnoceni__cislo_body__rocnik=rocnik) for res in reseni: for resitel in res.resitele.all(): - for hodn in res.hodnoceni.all(): + for hodn in res.hodnoceni_set.all(): pricti_body(body_za_rocnik, resitel, hodn.body) return body_za_rocnik @@ -651,6 +651,7 @@ class RadekVysledkovky(object): self.body_celkem_odjakziva = body_odjakziva self.poradi = poradi self.body_problemy_sezn = body_problemy_sezn + self.titul = resitel.get_titul(body_odjakziva) # přiřazuje danému řešiteli body do slovníku @@ -713,7 +714,7 @@ class CisloView(generic.DetailView): slovnik[str(ar.id)] = "" # vezmeme všechna řešení s body do daného čísla - reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'hodnoceni', 'resitele').filter(hodnoceni__cislo_body=cislo) + reseni_do_cisla = Reseni.objects.prefetch_related('problem', 'resitele', 'hodnoceni_set').filter(hodnoceni__cislo_body=cislo) # projdeme všechna řešení do čísla a přičteme body každému řešiteli do celkových # bodů i do bodů za problém @@ -725,7 +726,7 @@ class CisloView(generic.DetailView): nadproblem_slovnik = hlavni_problemy_slovnik[str(nadproblem.id)] # a více hodnocení - for hodn in list(reseni.hodnoceni.all()): + for hodn in list(reseni.hodnoceni_set.all()): body = hodn.body # a více řešitelů @@ -780,8 +781,6 @@ class CisloView(generic.DetailView): context['problemy'] = hlavni_problemy # context['v_cisle_zadane'] = TODO # context['resene_problemy'] = resene_problemy - #XXX nefungují body odjakživa - asi typový problém - #XXX nefungují tituly - možná korelace s výše uvedeným problémem print("Předávám kontext.") return context