diff --git a/db_compare.py b/db_compare.py new file mode 100755 index 00000000..031bde86 --- /dev/null +++ b/db_compare.py @@ -0,0 +1,513 @@ +#!/usr/bin/env python3 + +import psycopg2 +import psycopg2.extras + +OLD_DB = "mam_old" +NEW_DB = "mamweb" + +oldconn = psycopg2.connect(f"dbname={OLD_DB}") +newconn = psycopg2.connect(f"dbname={NEW_DB}") + +oldcur = oldconn.cursor(cursor_factory=psycopg2.extras.DictCursor) +newcur = newconn.cursor(cursor_factory=psycopg2.extras.DictCursor) + + +# Uses global variables oldcur, newcur! +def execute_simple(old_query, new_query=None): + if new_query is None: + new_query = old_query + + oldcur.execute(old_query) + newcur.execute(new_query) + + if oldcur.rowcount != newcur.rowcount: + raise ValueError(f"Queries '{old_query}' and '{new_query}' returned different number of rows ({oldcur.rowcount} and {newcur.rowcount})") + + return(oldcur.fetchall(), newcur.fetchall()) + +def check_same(old_row, new_row, old_fields, new_fields=None): + if type(old_fields) != list: + old_fields = [old_fields] + + if new_fields is None: + new_fields = old_fields + + fields = zip(old_fields, new_fields) + + for old_field, new_field in fields: + if old_row[old_field] == new_row[new_field]: + continue + raise ValueError(f"Fields '{old_field}'({old_row[old_field]}) and '{new_field}'({new_row[new_field]}) differs for rows \n'{old_row}' and \n'{new_row}'") + return True + +def get_user_id_for_org_id(org_id): + query = """SELECT auth_user.id FROM auth_user + INNER JOIN seminar_osoby ON seminar_osoby.user_id = auth_user.id + INNER JOIN seminar_organizator ON seminar_organizator.osoba_id = seminar_osoby.id + WHERE seminar_organizator.id = %s """ + + newcur.execute(query,(org_id,)) + return newcur.fetchone()['id'] + + + + +def check_skola(): + old_query = "SELECT * FROM seminar_skoly ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','aesop_id','izo','nazev','kratky_nazev','ulice','mesto','psc','stat','je_zs','je_ss','poznamka']) + +def check_resitel(): + old_query = 'SELECT * FROM seminar_resitele ORDER BY id' + new_query = '''SELECT seminar_resitele.id, skola_id, rok_maturity, zasilat, seminar_resitele.poznamka, + o.jmeno AS jmeno, o.prijmeni AS prijmeni, o.user_id AS user_id, o.pohlavi_muz AS pohlavi_muz, o.email AS email, o.telefon AS telefon, o.datum_narozeni AS datum_narozeni, + o.datum_souhlasu_udaje AS datum_souhlasu_udaje, o.datum_souhlasu_zasilani AS datum_souhlasu_zasilani, o.datum_registrace AS datum_prihlaseni, o.ulice AS ulice, o.mesto AS mesto, o.psc AS psc, o.stat AS stat + FROM seminar_resitele JOIN seminar_osoby AS o ON seminar_resitele.osoba_id = o.id ORDER BY seminar_resitele.id''' + + old_res, new_res = execute_simple(old_query,new_query) + + res = zip(old_res,new_res) + + fields_osoba = [ + 'jmeno', + 'prijmeni', + 'user_id', + 'pohlavi_muz', + #'email', #vyreseno separatne + 'telefon', + 'datum_narozeni', + 'datum_souhlasu_udaje', + 'datum_souhlasu_zasilani', + 'datum_prihlaseni', + 'ulice', + 'mesto', + 'psc', + 'stat', + ] + fields_keep = [ + 'id', + 'skola_id', + 'rok_maturity', + 'zasilat', + 'poznamka', + ] + fields = fields_keep+fields_osoba + for o,n in res: + check_same(o,n,fields) + if o['email'] != n['email'] and o['email'] != '': + print(f"WARNING: Emails differ: old: {o['email']}, new: {n['email']}") + +def check_reseni(): + # Migrace 0058 zamerne meni (zmensuje) pocet reseni, aby kazdy clanek mel + # jen jedno reseni (s vice resiteli, coz postaru neslo) + # Kvuli tomu je potreba kontrolovat dve veci: + # 1) Ze kazdy resitel dostal za kazdy problem spravne bodu + # 2) Ze detaily reseni zustaly zachovany + + # Cast 1) + old_query = 'SELECT * FROM seminar_reseni ORDER BY problem_id, resitel_id, body, timestamp' + new_query = '''SELECT seminar_reseni.id, forma, seminar_reseni.poznamka, cas_doruceni, hodnoceni.problem_id AS problem_id, hodnoceni.body AS body, hodnoceni.cislo_body_id AS cislo_body_id, res.id AS resitel_id + FROM seminar_reseni + JOIN seminar_hodnoceni AS hodnoceni ON seminar_reseni.id = hodnoceni.reseni_id + JOIN seminar_reseni_resitele AS rr ON seminar_reseni.id = rr.reseni_id + JOIN seminar_resitele AS res ON res.id = rr.resitele_id + ORDER BY problem_id, resitel_id, body, cas_doruceni''' + + # Po spojeni nekterych problemu se lisi casy doruceni a poznamky, proto je nebudeme kontrolovat (jde v podstate o triviality, tak je to snad jedno) + same_fields = ['forma', 'problem_id', 'body', 'cislo_body_id', 'resitel_id'] + renamed_fields = [ + #('timestamp', 'cas_doruceni'), + ] + old_fields = same_fields + [f[0] for f in renamed_fields] + new_fields = same_fields + [f[1] for f in renamed_fields] + + old_res, new_res = execute_simple(old_query, new_query) + + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,old_fields, new_fields) + + # Cast 2) + # Query se lisi tim, ze uz nejoinujeme resitele. + old_query = 'SELECT * FROM seminar_reseni ORDER BY id' + new_query = '''SELECT seminar_reseni.id, forma, poznamka, cas_doruceni AS timestamp, h.problem_id AS problem_id, h.body AS body, h.cislo_body_id AS cislo_body_id + FROM seminar_reseni + JOIN seminar_hodnoceni AS h ON h.reseni_id = seminar_reseni.id + ORDER BY id''' + + # execute_simple kontroluje stejnost poctu radku, to nechceme. + oldcur.execute(old_query) + newcur.execute(new_query) + old_res, new_res = oldcur.fetchall(), newcur.fetchall() + # Zkontrolujeme, ze pro kazde nove reseni ma stare reseni spravna data. + new_ids = [n['id'] for n in new_res] + spravna_old = list(filter(lambda o: o['id'] in new_ids, old_res)) + res = zip(spravna_old,new_res) + for o,n in res: + # Tady by se poznamky i timestampy mely zachovat + # Z nejakeho duvodu se ale poznamky lisi ve whitespace, tak je zkontrolujeme separatne + check_same(o,n,['id', 'forma', 'timestamp', 'problem_id', 'body', 'cislo_body_id']) + old_pozn = o['poznamka'].strip() + new_pozn = n['poznamka'].strip() + if old_pozn != new_pozn: + raise ValueError('Poznamky se lisi pro radky {dict(o)} a {dict(n)}') + + + +def check_organizator(): + old_query = 'SELECT * FROM seminar_organizator ORDER BY id' + new_query = '''SELECT seminar_organizator.id AS id, studuje, strucny_popis_organizatora, users.id AS uid, osoba.prezdivka AS o_prezdivka, osoba.foto AS o_foto, organizuje_od, organizuje_do + FROM seminar_organizator + JOIN seminar_osoby AS osoba ON osoba_id = osoba.id + JOIN auth_user AS users ON osoba.user_id = users.id + ORDER BY seminar_organizator.id''' + + same_fields = ['studuje', 'strucny_popis_organizatora'] + renamed_fields = [ + ('user_id', 'uid'), + #('prezdivka', 'o_prezdivka'), + ('foto', 'o_foto'), + ] + old_fields = same_fields + [f[0] for f in renamed_fields] + new_fields = same_fields + [f[1] for f in renamed_fields] + + old_res, new_res = execute_simple(old_query,new_query) + res = zip(old_res, new_res) + for o,n in res: + check_same(o,n,old_fields, new_fields) + # organizuje od, do: + # Migrace prirazuje aktualni casovou zonu, takze chceme tady rucne vynutit CET. + from datetime import timedelta, timezone + cet = timezone(timedelta(hours=1)) + if o['organizuje_od_roku'] is None and n['organizuje_od'] is None: + pass + elif o['organizuje_od_roku'] != n['organizuje_od'].astimezone(cet).year: + raise ValueError(f'Not matching organizuje_od for org id={o["id"]}: old {o["organizuje_od_roku"]}, new {n["organizuje_od"]}') + if o['organizuje_do_roku'] is None and n['organizuje_do'] is None: + pass + elif o['organizuje_do_roku'] != n['organizuje_do'].astimezone(cet).year: + raise ValueError(f'Not matching organizuje_do for org id={o["id"]}: old {o["organizuje_do_roku"]}, new {n["organizuje_do"]}') + if o['prezdivka'] == n['o_prezdivka']: + continue + if o['prezdivka'] is None and n['o_prezdivka'] == '': + continue + raise ValueError(f'Not matching prezdivka for org id={o["id"]}: old {o["prezdivka"]}, new {n["o_prezdivka"]}') + + +def check_rocnik(): + old_query = "SELECT * FROM seminar_rocniky ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','prvni_rok', 'rocnik', 'exportovat']) + +def check_cislo(): + old_query = "SELECT * FROM seminar_cisla ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n, ['id','rocnik_id','cislo', 'datum_vydani','datum_deadline','verejne','poznamka','pdf'], + ['id','rocnik_id','poradi','datum_vydani','datum_deadline','verejne','poznamka','pdf']) + +def check_priloha_reseni(): + old_query = "SELECT * FROM seminar_priloha_reseni" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n, ['id','reseni_id', 'timestamp', 'soubor', 'poznamka'], + ['id','reseni_id', 'vytvoreno', 'soubor', 'poznamka']) + +def check_soustredeni(): + old_query = "SELECT * FROM seminar_soustredeni ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','rocnik_id','datum_zacatku','datum_konce','verejne','misto','text','typ','exportovat']) + #Kontrola ucasnici, organizatori v samostatnych funkcich + +def check_soustredeni_ucastnici(): + old_query = "SELECT * FROM seminar_soustredeni_ucastnici ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','resitel_id','soustredeni_id','poznamka']) + +def check_soustredeni_organizatori(): + old_query = "SELECT * FROM seminar_soustredeni_organizatori ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','organizator_id','soustredeni_id','poznamka']) + +def check_nastaveni(): + old_query = "SELECT * FROM seminar_nastaveni ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','aktualni_cislo_id']) + +def check_novinky(): + old_query = "SELECT * FROM seminar_novinky ORDER BY id" + + old_res, new_res = execute_simple(old_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','datum','text','obrazek','zverejneno']) + if get_user_id_for_org_id(n['autor_id']) != o['autor_id']: + raise ValueError("Nesedi autori u novinek") + +def check_pohadka(): + old_query = "SELECT * FROM seminar_pohadky ORDER BY id" + new_query = """SELECT sp.id AS id, sp.autor_id AS autor_id, sp.vytvoreno AS vytvoreno, snp.treenode_ptr_id AS treenode_ptr_id, st.na_web AS text, + zn_pred.uloha_id AS uloha_pred, zn_po.uloha_id AS uloha_po + FROM seminar_pohadky AS sp + -- Text pohádky + INNER JOIN seminar_nodes_pohadka AS snp ON sp.id = snp.pohadka_id + INNER JOIN seminar_nodes_treenode AS snt ON snt.id = snp.treenode_ptr_id + INNER JOIN seminar_nodes_obsah AS sno ON sno.treenode_ptr_id = snt.first_child_id + INNER JOIN seminar_texty AS st ON sno.text_id = st.id + -- Predchozí úloha + LEFT OUTER JOIN seminar_nodes_treenode AS ztn_pred ON ztn_pred.succ_id = snt.id + LEFT OUTER JOIN seminar_nodes_uloha_zadani AS zn_pred ON zn_pred.treenode_ptr_id = ztn_pred.id + -- Následující úloha + LEFT OUTER JOIN seminar_nodes_uloha_zadani AS zn_po ON zn_po.treenode_ptr_id = snt.succ_id + + ORDER BY sp.id""" + + old_res, new_res = execute_simple(old_query,new_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n,['id','timestamp','text'],['id','vytvoreno','text']) + if o['autor_id'] is not None: + if get_user_id_for_org_id(n['autor_id']) != o['autor_id']: + raise ValueError("Nesedi autori u pohadky") + # Správné úlohy + # NOTE: o['pred'] rika, zda je pohadka pred ulohou, nikoliv zda je relevantni uloha pred pohadkou! + spravny_klic = 'uloha_po' if o['pred'] else 'uloha_pred' + if o['uloha_id'] != n[spravny_klic]: + raise ValueError(f"Pohádka přidružená ke špatné úloze! old: {o['uloha_id']}, new: {n[spravny_klic]}, pozice: {spravny_klic}") + + +# Problémy jsou rozdělené podle typů: +def check_problem_common(): + old_query = "SELECT id, nazev, stav, kod, autor_id, text_org, timestamp, typ FROM seminar_problemy ORDER BY id" + new_query = """SELECT sp.id AS id, sp.nazev AS nazev, sp.stav AS stav, sp.kod AS kod, au.id AS autor_id, sp.poznamka AS poznamka, sp.vytvoreno AS vytvoreno + FROM seminar_problemy AS sp + LEFT OUTER JOIN seminar_organizator AS so ON sp.autor_id = so.id + LEFT OUTER JOIN seminar_osoby AS sos ON so.osoba_id = sos.id + LEFT OUTER JOIN auth_user AS au ON sos.user_id = au.id + ORDER BY sp.id""" + + same_fields = ['id', 'nazev', 'stav', 'autor_id', 'kod'] + renamed_fields = [ + ('text_org', 'poznamka'), + ('timestamp', 'vytvoreno'), + ] + old_fields = same_fields + [f[0] for f in renamed_fields] + new_fields = same_fields + [f[1] for f in renamed_fields] + + old_res, new_res = execute_simple(old_query,new_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n, old_fields, new_fields) + + # Opravovatelé + # Po staru byli opravovatele organizatori, takze je potreba je dohledat. + old_query = """SELECT seminar_problemy.id, org.id AS opravovatel_id FROM seminar_problemy + JOIN seminar_organizator AS org ON seminar_problemy.opravovatel_id = org.user_id;""" + new_query = "SELECT problem_id, organizator_id FROM seminar_problemy_opravovatele" + + # Simple cursors + #oldcur = oldconn.cursor() + oldcur.execute(old_query) + old_results = oldcur.fetchall() + #newcur = newconn.cursor() + newcur.execute(new_query) + new_results = newcur.fetchall() + + for oldr in old_results: + if oldr not in new_results: + raise ValueError(f'Opravovatel pair {oldr} not found in new db.') + + # Zaměření se vyřeší okometricky (#1186) + + +def check_uloha(): + old_query = "SELECT * FROM seminar_problemy WHERE typ = 'uloha' ORDER BY id" + new_query = """SELECT cislo_zadani_id, cislo_reseni_id, problem_ptr_id, max_body, COALESCE(uzt.na_web, '') AS text_zadani, COALESCE(uvt.na_web, '') AS text_reseni, cislo_deadline_id + FROM seminar_ulohy + -- Problém: + JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id + -- Text zadání: + -- ZadaniNode a VzorakNode maji existovat vzdy, ale obsah nemusi (pokud ho nemaji) + INNER JOIN seminar_nodes_uloha_zadani AS uzn ON problem.id = uzn.uloha_id + INNER JOIN seminar_nodes_treenode AS uztn ON uztn.id = uzn.treenode_ptr_id + LEFT OUTER JOIN seminar_nodes_obsah AS uzo ON uzo.treenode_ptr_id = uztn.first_child_id + LEFT OUTER JOIN seminar_texty AS uzt ON uzo.text_id = uzt.id + -- Text vzoráku: + INNER JOIN seminar_nodes_uloha_vzorak AS uvn ON problem.id = uvn.uloha_id + INNER JOIN seminar_nodes_treenode AS uvtn ON uvtn.id = uvn.treenode_ptr_id + LEFT OUTER JOIN seminar_nodes_obsah AS uvo ON uvo.treenode_ptr_id = uvtn.first_child_id + LEFT OUTER JOIN seminar_texty AS uvt ON uvo.text_id = uvt.id + + ORDER BY problem_ptr_id""" + + same_fields = ['cislo_zadani_id', 'cislo_reseni_id', 'text_zadani', 'text_reseni'] + renamed_fields = [ + ('id', 'problem_ptr_id'), + ('body', 'max_body'), + ] + old_fields = same_fields + [f[0] for f in renamed_fields] + new_fields = same_fields + [f[1] for f in renamed_fields] + + old_res, new_res = execute_simple(old_query, new_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n, old_fields, new_fields) + # Datum deadline vypadá prázdně, tak to budeme předpokládat. + if n['cislo_deadline_id'] is not None: + raise ValueError("Úloha má deadline.") + +def check_tema(): + old_query = """SELECT text_zadani, text_reseni, typ, c.rocnik_id AS rocnik_id + FROM seminar_problemy + LEFT OUTER JOIN seminar_cisla AS c ON c.id = cislo_zadani_id + WHERE typ IN ('tema', 'serial') + ORDER BY seminar_problemy.id""" + new_query = """SELECT tema_typ, COALESCE(zad_text.na_web, '') AS text_zadani, COALESCE(res_text.na_web, '') AS text_reseni, rn.rocnik_id AS rocnik_id + FROM seminar_temata + -- Problém: + JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id + -- Text: + -- TvCNode má dva potomky, oba TextNode. První drží původní text zadání, druhý řešení. + INNER JOIN seminar_nodes_temavcisle AS tvcn ON tvcn.tema_id = id + INNER JOIN seminar_nodes_treenode AS ttn ON tvcn.treenode_ptr_id = ttn.id + LEFT OUTER JOIN seminar_nodes_treenode AS zad_tn ON ttn.first_child_id = zad_tn.id -- jen 33 z nich ma zadani + LEFT OUTER JOIN seminar_nodes_treenode AS res_tn ON zad_tn.succ_id = res_tn.id -- jen 4 z nich ma reseni + LEFT OUTER JOIN seminar_nodes_obsah AS zad_on ON zad_on.treenode_ptr_id = zad_tn.id + LEFT OUTER JOIN seminar_nodes_obsah AS res_on ON res_on.treenode_ptr_id = res_tn.id + LEFT OUTER JOIN seminar_texty AS zad_text ON zad_on.text_id = zad_text.id + LEFT OUTER JOIN seminar_texty AS res_text ON res_on.text_id = res_text.id -- vsechny 4 + -- Ročník tématu: + -- Podle rootu TvCN + LEFT OUTER JOIN seminar_nodes_rocnik AS rn ON ttn.root_id = rn.treenode_ptr_id + + ORDER BY problem_ptr_id""" + same_fields = ['text_zadani', 'text_reseni', 'rocnik_id'] + renamed_fields = [ + ('typ', 'tema_typ'), + ] + old_fields = same_fields + [f[0] for f in renamed_fields] + new_fields = same_fields + [f[1] for f in renamed_fields] + + old_res, new_res = execute_simple(old_query, new_query) + res = zip(old_res,new_res) + + for o,n in res: + check_same(o,n, old_fields, new_fields) + +def check_konfera(): + old_query = "SELECT * FROM seminar_problemy WHERE typ = 'konfera'" + new_query = "SELECT * FROM seminar_konfera" + + oldcur.execute(old_query) + newcur.execute(new_query) + + if oldcur.rowcount != 0 or newcur.rowcount != 0: + raise ValueError('There exists a Konfera!') + +def check_org_clanek(): + old_query = "SELECT * FROM seminar_problemy WHERE typ = 'org-clanek'" + + oldcur.execute(old_query) + + if oldcur.rowcount != 0: + raise ValueError('There exists a Org-clanek!') + +def check_res_clanek(): + # Dva(!) články mají text (zadání), který se má zachovat. + old_query = "SELECT * FROM seminar_problemy WHERE typ = 'res-clanek' ORDER BY id" + new_query = """SELECT cislo_id, text.na_web AS text_zadani + FROM seminar_clanky + JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id + INNER JOIN seminar_hodnoceni AS hodn ON problem.id = hodn.problem_id + INNER JOIN seminar_reseni AS rese ON rese.id = hodn.reseni_id + INNER JOIN seminar_nodes_otistene_reseni AS rn ON rese.text_cely_id = rn.treenode_ptr_id -- Tenhle radek neni potreba, ale ujistuje se mj. o spravnem typu TreeNode. + INNER JOIN seminar_nodes_treenode AS tn ON rn.treenode_ptr_id = tn.id + -- Nektere clanky vubec nemely text, tak jim migr 0058 nevyrobila dalsi treenody + LEFT OUTER JOIN seminar_nodes_obsah AS son ON son.treenode_ptr_id = tn.first_child_id + LEFT OUTER JOIN seminar_texty AS text ON text.id = son.text_id + + ORDER BY problem_ptr_id""" + same_fields = ['text_zadani'] + renamed_fields = [ + ('cislo_zadani_id', 'cislo_id'), + ] + old_fields = same_fields + [f[0] for f in renamed_fields] + new_fields = same_fields + [f[1] for f in renamed_fields] + + old_res, new_res = execute_simple(old_query, new_query) + res = zip(old_res,new_res) + + for o,n in res: + # text_zadani po novu mohl byt None + if n['text_zadani'] is None: + n['text_zadani'] = '' + check_same(o,n, old_fields, new_fields) + assert(o['text_reseni'] == '') + +def check_untyped_problem(): + old_query = "SELECT * FROM seminar_problemy WHERE typ NOT IN ('uloha', 'tema', 'serial', 'konfera', 'org-clanek', 'res-clanek')" + + oldcur.execute(old_query) + + if oldcur.rowcount != 0: + raise ValueError('There exists a Problem without type!') + + + +check_skola() +check_resitel() +check_reseni() +check_organizator() +check_rocnik() +check_cislo() +check_priloha_reseni() +check_soustredeni() +check_soustredeni_ucastnici() +check_soustredeni_organizatori() +check_nastaveni() +check_novinky() +check_pohadka() + +check_problem_common() +check_uloha() +check_tema() +check_konfera() +check_org_clanek() +check_res_clanek() +check_untyped_problem() diff --git a/deploy_v2/README b/deploy_v2/README new file mode 100644 index 00000000..dec6d746 --- /dev/null +++ b/deploy_v2/README @@ -0,0 +1,3 @@ +Tahle slozka obsahuje vsechny detaily a popisy, jak nasadit "druhou verzi" M&M webu. + +TODO: chybi tu popis na zprovozneni flatpages, na loaddata &c. diff --git a/deploy_v2/pre_migration.py b/deploy_v2/pre_migration.py new file mode 100755 index 00000000..53f58d95 --- /dev/null +++ b/deploy_v2/pre_migration.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import os +import sys + +import django + +#### Inicializace Djanga +sys.path.append(os.path.dirname(os.path.realpath(__file__))+'/..') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mamweb.settings') +django.setup() + +## Pozor, nejde pouzit ORM, protoze kod je na jine verzi nez databaze a nejde namigrovat. +from django.db import connection + + +def smaz_zle_clanky(): + # Tyhle clanky vubec nejsou clanky, bude potreba je udelat cele jinak a spravne. + #m.Problem.objects.filter(id__in=[1981, 1970, 2222]).delete() + with connection.cursor() as cursor: + # Nejdriv musime smazat reseni: + cursor.execute('DELETE FROM seminar_reseni WHERE problem_id IN (1981, 1970, 2222);') + # Nakonec i ty clanky samotne + cursor.execute('DELETE FROM seminar_problemy WHERE id IN (1981, 1970, 2222);') + +def smaz_divne_uzivatele(): + # U techto uzivatelu neexistuje Organizator s nimi spojeny + # Takze pak delaji akorat neporadek + with connection.cursor() as cursor: + # Jeste je potreba zrusit vazby + cursor.execute('UPDATE django_comments SET user_id = NULL WHERE user_id = 34;') + cursor.execute('UPDATE seminar_problemy SET autor_id = NULL WHERE autor_id = 34;') + cursor.execute('DELETE FROM django_admin_log WHERE user_id = 34;') + cursor.execute('DELETE FROM auth_user_groups WHERE user_id = 34;') + cursor.execute('DELETE FROM auth_user WHERE id IN (34, 40, 30, 50, 54, 58, 43);') + +smaz_zle_clanky() +smaz_divne_uzivatele() diff --git a/seminar/forms.py b/seminar/forms.py index 2944ef52..03dc645d 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -404,6 +404,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form): 'problemy': cls.PROBLEMY_MOJE, 'reseni_od': terminy[-2], 'reseni_do': terminy[-1], + 'neobodovane': False, } return initial @@ -426,3 +427,4 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form): reseni_od = forms.DateField(input_formats=[DATE_FORMAT]) reseni_do = forms.DateField(input_formats=[DATE_FORMAT]) + neobodovane = forms.BooleanField(required=False) diff --git a/seminar/migrations/0001_squashed_0067_auto_20190814_0805.py b/seminar/migrations/0001_squashed_0067_auto_20190814_0805.py.bak similarity index 100% rename from seminar/migrations/0001_squashed_0067_auto_20190814_0805.py rename to seminar/migrations/0001_squashed_0067_auto_20190814_0805.py.bak diff --git a/seminar/migrations/0049_auto_20190430_2354.py b/seminar/migrations/0049_auto_20190430_2354.py index 985b3531..14fa23d8 100644 --- a/seminar/migrations/0049_auto_20190430_2354.py +++ b/seminar/migrations/0049_auto_20190430_2354.py @@ -63,7 +63,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(primary_key=True, serialize=False)), ('jmeno', models.CharField(max_length=256, verbose_name='jméno')), ('prijmeni', models.CharField(max_length=256, verbose_name='příjmení')), - ('prezdivka', models.CharField(max_length=256, verbose_name='přezdívka')), + ('prezdivka', models.CharField(max_length=256, verbose_name='přezdívka', blank=True, null=False)), ('pohlavi_muz', models.BooleanField(default=False, verbose_name='pohlaví (muž)')), ('email', models.EmailField(blank=True, default='', max_length=256, verbose_name='e-mail')), ('telefon', models.CharField(blank=True, default='', max_length=256, verbose_name='telefon')), @@ -480,11 +480,6 @@ class Migration(migrations.Migration): name='resitele', field=models.ManyToManyField(help_text='Seznam autorů řešení', through='seminar.Reseni_Resitele', to='seminar.Resitel', verbose_name='autoři řešení'), ), - migrations.AddField( - model_name='reseni', - name='text_cely', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reseni_cely_set', to='seminar.Text', verbose_name='Plná verze textu řešení'), - ), migrations.AddField( model_name='reseni', name='text_zkraceny', diff --git a/seminar/migrations/0050_auto_20190510_2228.py b/seminar/migrations/0050_auto_20190510_2228.py index c6ed9f66..a9afc764 100644 --- a/seminar/migrations/0050_auto_20190510_2228.py +++ b/seminar/migrations/0050_auto_20190510_2228.py @@ -23,11 +23,6 @@ class Migration(migrations.Migration): name='osoba', field=models.OneToOneField(help_text='osobní údaje organizátora', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='org', to='seminar.Osoba', verbose_name='osoba'), ), - migrations.AlterField( - model_name='reseni', - name='text_cely', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reseni_cely_set', to='seminar.Text', verbose_name='Plná verze textu řešení'), - ), migrations.AlterField( model_name='resitel', name='osoba', diff --git a/seminar/migrations/0051_resitel_to_osoba.py b/seminar/migrations/0051_resitel_to_osoba.py index d29f311b..6644ab7d 100644 --- a/seminar/migrations/0051_resitel_to_osoba.py +++ b/seminar/migrations/0051_resitel_to_osoba.py @@ -81,7 +81,7 @@ def osoba_to_resitel(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('seminar', '0050_auto_20190510_2228'), + ('seminar', '0066c_reseninode'), ] operations = [ diff --git a/seminar/migrations/0058_problem_to_uloha_tema_clanek.py b/seminar/migrations/0058_problem_to_uloha_tema_clanek.py index 7d651edb..4ef135c4 100644 --- a/seminar/migrations/0058_problem_to_uloha_tema_clanek.py +++ b/seminar/migrations/0058_problem_to_uloha_tema_clanek.py @@ -2,9 +2,10 @@ # Generated by Django 1.11.20 on 2019-05-17 17:44 from __future__ import unicode_literals -from django.db import migrations +from django.db import migrations, models from django.db.models import Q +import django.db.models.deletion def poskladej_strom(apps, rodic, *texty): Text = apps.get_model('seminar', 'Text') @@ -43,22 +44,22 @@ def uloha_to_Uloha(apps,schema_editor): ulohy = Problem.objects.filter(typ = 'uloha') for uold in ulohy: unew = Uloha.objects.create( - problem_ptr = uold, # Zakomentované fieldy by se už měly nacházet v příslušném problému - #nazev = uold.nazev, - #stav = uold.stav, - #zamereni = uold.zamereni, - #poznamka = uold.poznamka, - #autor = uold.autor, - #kod = uold.kod, + problem_ptr = uold, + nazev = uold.nazev, + stav = uold.stav, + zamereni = uold.zamereni, + poznamka = uold.poznamka, + autor = uold.autor, + kod = uold.kod, cislo_zadani = uold.cislo_zadani_old, cislo_reseni = uold.cislo_reseni_old, max_body = uold.body, - #vytvoreno = uold.vytvoreno, + vytvoreno = uold.vytvoreno, ) -# unew.opravovatele.add(*uold.opravovatele.all()) - unew.save() - + uold.save() # DULEZITE!!! Jinak Uloha.objects.create() přepíše všechny atributy Problému + unew.opravovatele.add(*uold.opravovatele.all()) + # Nody: zadani_node = UlohaZadaniNode.objects.create(uloha = unew) poskladej_strom(apps, zadani_node, uold.text_zadani) @@ -77,11 +78,12 @@ def konfery_rucne(apps, schema_editor): def clanek_to_Clanek(apps,schema_editor): Problem = apps.get_model('seminar', 'Problem') Clanek = apps.get_model('seminar', 'Clanek') - ClanekNode = apps.get_model('seminar', 'ClanekNode') + ReseniNode = apps.get_model('seminar', 'ReseniNode') Text = apps.get_model('seminar', 'Text') TextNode = apps.get_model('seminar', 'TextNode') - clanky = Problem.objects.filter(Q(typ='org-clanek') | Q(typ='res-clanek')) + # XXX: Org-clanky neexistuji, tak je migrace ani nepodporuje. + clanky = Problem.objects.filter(typ='res-clanek') for cl in clanky: # Vybereme vhodné číslo pro článek z čísla zadání a čísla řešení: if cl.cislo_zadani_old is None: @@ -99,18 +101,64 @@ def clanek_to_Clanek(apps,schema_editor): cislo = cislo, # Body ignorujeme, protože už jsou v hodnocení ) - clnew.save() + cl.save() # DULEZITE!!! Jinak Clanek.objects.create() přepíše všechny atributy Problému - # Aktuálně nemáme v modelu informaci o tom, jestli je to org-článek - # nebo řešitelský článek. Aby se neztratila informace, poznamenám to do - # poznámky. - cl.poznamka += "\nTyp:\t{}".format(cl.typ) - cl.save() +def Clanek_Treenody(apps, schema_editor): + Problem = apps.get_model('seminar', 'Problem') + Clanek = apps.get_model('seminar', 'Clanek') + ReseniNode = apps.get_model('seminar', 'ReseniNode') + Text = apps.get_model('seminar', 'Text') + TextNode = apps.get_model('seminar', 'TextNode') + for cl in Clanek.objects.all(): # Vyrobíme nody: - clnode = ClanekNode(clanek = clnew) - poskladej_strom(apps, clnode, cl.text_zadani, cl.text_reseni) - clnode.save() + # Clanek nema vlastni node, ma (prave jedno) Reseni a to ma text_cely -- ReseniNode + reseni = cl.reseni_set.all() + if len(reseni) != 1: + raise ValueError(f'Clanek {cl.id} ma vic reseni {len(reseni)} ({reseni})') + reseni = reseni[0] + resnode = ReseniNode(reseni=reseni) + poskladej_strom(apps, resnode, cl.text_zadani, cl.text_reseni) + resnode.save() + reseni.text_cely = resnode + reseni.save() + +def fix_Clanek_Reseni(apps, schema_editor): + Problem = apps.get_model('seminar', 'Problem') + Clanek = apps.get_model('seminar', 'Clanek') + Reseni = apps.get_model('seminar', 'Reseni') + Hodnoceni = apps.get_model('seminar', 'Hodnoceni') + Resitel = apps.get_model('seminar', 'Resitel') + + # Je potreba zajistit, ze clanky budou mit jen jedno reseni -- z pohledu + # modelu nic jineho nedava smysl. Ve stavajicim modelu ale naopak nelze + # reprezentovat vice resitelu jednoho clanku (coz je ale bezne -- clanky z + # konfer) Musime tedy opravit, aby misto nekolika reseni kazdeho resitele + # samostatne zustalo jen jedno reseni, spravne obodovane a s vice resiteli + # jako autory + + for cl in Clanek.objects.all(): + rr = cl.reseni_set.all() + if len(rr) == 1: continue + # Vice nez jedno reseni, jdeme je sjednotit. + resitele = [] + vzor_hodnoceni = rr[0].hodnoceni_set.first() + ostatni_hodnoceni = [] + for r in rr: + # Overime, ze nemame kolizi v datech: + h = r.hodnoceni_set.first() + if h.cislo_body != vzor_hodnoceni.cislo_body or h.body != vzor_hodnoceni.body: + raise ValueError(f'Clanek {cl.id} ma vice nekonzistentnich reseni') + if h.id != vzor_hodnoceni.id: + ostatni_hodnoceni.append(h) + resitele.extend(r.resitele.all()) + rr[0].resitele.set(resitele) + rr[0].save() + vzor_hodnoceni.save() + # Ted mame spravne databazi, jeste potrebujeme z databaze smazat po novu nepouzita hodnoceni + for h in ostatni_hodnoceni: + h.reseni.delete() + h.delete() def tema_to_Tema(apps, schema_editor): Problem = apps.get_model('seminar', 'Problem') @@ -138,7 +186,7 @@ def tema_to_Tema(apps, schema_editor): tema_typ = t.typ, rocnik = rocnik, ) - tnew.save() + t.save() # DULEZITE!!! Jinak Tema.objects.create() přepíše všechny atributy Problému # Nody: tnode = TemaVCisleNode(tema = tnew) @@ -149,7 +197,7 @@ def tema_to_Tema(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('seminar', '0057_reseni_to_reseni_hodnoceni'), + ('seminar', '0087_fix_polymorphism'), ] operations = [ @@ -157,5 +205,7 @@ class Migration(migrations.Migration): migrations.RunPython(uloha_to_Uloha, migrations.RunPython.noop), migrations.RunPython(tema_to_Tema, migrations.RunPython.noop), migrations.RunPython(clanek_to_Clanek, migrations.RunPython.noop), + migrations.RunPython(fix_Clanek_Reseni, migrations.RunPython.noop), + migrations.RunPython(Clanek_Treenody, migrations.RunPython.noop), migrations.RunPython(konfery_rucne, migrations.RunPython.noop), ] diff --git a/seminar/migrations/0060_spoj_stromy.py b/seminar/migrations/0060_spoj_stromy.py index c02b8d12..d0826ca1 100644 --- a/seminar/migrations/0060_spoj_stromy.py +++ b/seminar/migrations/0060_spoj_stromy.py @@ -6,6 +6,15 @@ from django.db import migrations from django.db.models import Q +def nastav_koren(koren, node): + node.root = koren + node.save() + + if node.succ: + nastav_koren(koren, node.succ) + if node.first_child: + nastav_koren(koren, node.first_child) + def pridej_potomka(rodic, potomek): # Daný vrchol bude posledním potomkem rodiče uz_ma_deti = False @@ -23,8 +32,7 @@ def pridej_potomka(rodic, potomek): posledni = posledni.succ # Nastavíme kořen: - potomek.root = rodic.root - potomek.save() + nastav_koren(rodic.root, potomek) # Připojíme vrchol: if uz_ma_deti: @@ -56,15 +64,19 @@ def pokacej_les(apps, schema_editor): relevantni_temata = Tema.objects.filter(Q(cislo_zadani_old = c) | Q(cislo_reseni_old = c)).order_by('kod') # Téma dáme do prvního čísla, kde se vyskytne for t in relevantni_temata: - tnode = t.temavcislenode + tnodes = t.temavcislenode_set.all() + # Migrujeme, TvCN je jen jedno dohromady + assert(len(tnodes) == 1) + tnode = tnodes[0] + # Zkontrolujeme a preskocime cislo_reseni if t.cislo_zadani_old and t.cislo_reseni_old: - assert(t.cislo_zadani_old <= t.cislo_reseni_old) + assert(t.cislo_zadani_old.rocnik == t.cislo_reseni_old.rocnik + and t.cislo_zadani_old.cislo <= t.cislo_reseni_old.cislo) if t.cislo_reseni_old == c: # Už by mělo být přidané do čísla zadání continue - else: - # Patří sem (buď je to jediné číslo, nebo je to číslo zadání) - pridej_potomka(cnode, tnode) + # Patří sem (buď je to jediné číslo, nebo je to číslo zadání) + pridej_potomka(cnode, tnode) # Úložky (zadání) a pohádky for u in Uloha.objects.filter(cislo_zadani = c).order_by('kod'): @@ -85,8 +97,12 @@ def pokacej_les(apps, schema_editor): # Články for cl in Clanek.objects.filter(cislo = c).order_by('kod'): - clnode = cl.claneknode - pridej_potomka(cnode, clnode) + # Zmena: Clanky nemaji vlastni Node, ale pouziva se ReseniNode v text_cely + reseni = cl.reseni_set.all() + if len(reseni) != 1: + raise ValueError('Clanek ma vic reseni') + resnode = reseni[0].text_cely + pridej_potomka(cnode, resnode) # Konfery for k in Konfera.objects.all(): diff --git a/seminar/migrations/0064_auto_20190610_2358.py b/seminar/migrations/0064_auto_20190610_2358.py index b72a08a0..78e62862 100644 --- a/seminar/migrations/0064_auto_20190610_2358.py +++ b/seminar/migrations/0064_auto_20190610_2358.py @@ -92,7 +92,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='reseni', name='text_cely', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reseni_cely_set', to='seminar.Text', verbose_name='Plná verze textu řešení'), + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reseni_cely_set', to='seminar.ReseniNode', verbose_name='Plná verze textu řešení'), ), migrations.AlterField( model_name='reseni_resitele', diff --git a/seminar/migrations/0065_treenode_polymorphic_ctype.py b/seminar/migrations/0065_treenode_polymorphic_ctype.py index cb65d8f1..88917d77 100644 --- a/seminar/migrations/0065_treenode_polymorphic_ctype.py +++ b/seminar/migrations/0065_treenode_polymorphic_ctype.py @@ -18,7 +18,7 @@ class Migration(migrations.Migration): dependencies = [ ('contenttypes', '0002_remove_content_type_name'), - ('seminar', '0064_auto_20190610_2358'), + ('seminar', '0057_reseni_to_reseni_hodnoceni'), ] operations = [ diff --git a/seminar/migrations/0066b_orgtextnode.py b/seminar/migrations/0066b_orgtextnode.py new file mode 100644 index 00000000..ecf509bb --- /dev/null +++ b/seminar/migrations/0066b_orgtextnode.py @@ -0,0 +1,31 @@ + +# Generated by Django 2.2.4 on 2019-08-13 19:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('seminar', '0066_problem_polymorphic_ctype'), + ] + + operations = [ + migrations.CreateModel( + name='OrgTextNode', + 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')), + ('org_verejny', models.BooleanField(default=True, help_text='Pokud ano, bude org pod článkem podepsaný', verbose_name='Org je veřejný?')), + ('organizator', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='seminar.Organizator', verbose_name='Organizátor')), + ], + options={ + 'verbose_name': 'Organizátorský článek (Node)', + 'verbose_name_plural': 'Organizátorské články (Node)', + 'db_table': 'seminar_nodes_orgtextnode', + }, + bases=('seminar.treenode',), + ), + + ] diff --git a/seminar/migrations/0066c_reseninode.py b/seminar/migrations/0066c_reseninode.py new file mode 100644 index 00000000..8e3bd3cd --- /dev/null +++ b/seminar/migrations/0066c_reseninode.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.4 on 2019-08-13 19:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('seminar', '0050_auto_20190510_2228'), + ] + + operations = [ + migrations.CreateModel( + name='ReseniNode', + 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',), + ), + migrations.AddField( + model_name='reseni', + name='text_cely', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reseni_cely_set', to='seminar.ReseniNode', verbose_name='Plná verze textu řešení'), + ), + + ] diff --git a/seminar/migrations/0067_auto_20190814_0805.py b/seminar/migrations/0067_auto_20190814_0805.py index 8a72a659..04d333a0 100644 --- a/seminar/migrations/0067_auto_20190814_0805.py +++ b/seminar/migrations/0067_auto_20190814_0805.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('seminar', '0066_problem_polymorphic_ctype'), + ('seminar', '0064_auto_20190610_2358'), ] operations = [ diff --git a/seminar/migrations/0077_auto_20200318_2146.py b/seminar/migrations/0077_auto_20200318_2146.py index 50053d9c..5f5f2e26 100644 --- a/seminar/migrations/0077_auto_20200318_2146.py +++ b/seminar/migrations/0077_auto_20200318_2146.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('seminar', '0076_auto_20200228_2013'), + ('seminar', '0066b_orgtextnode'), ] operations = [ diff --git a/seminar/migrations/0078_otistenereseninode.py b/seminar/migrations/0078_otistenereseninode.py deleted file mode 100644 index 2f426a17..00000000 --- a/seminar/migrations/0078_otistenereseninode.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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/migrations/0079_clanek_resitelsky.py b/seminar/migrations/0079_clanek_resitelsky.py index f41cdc51..88ad06eb 100644 --- a/seminar/migrations/0079_clanek_resitelsky.py +++ b/seminar/migrations/0079_clanek_resitelsky.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('seminar', '0078_otistenereseninode'), + ('seminar', '0076_auto_20200228_2013'), ] operations = [ diff --git a/seminar/migrations/0080_zruseni_claneknode_a_konferanode.py b/seminar/migrations/0080_zruseni_claneknode_a_konferanode.py index 34c22249..ddf3cc8e 100644 --- a/seminar/migrations/0080_zruseni_claneknode_a_konferanode.py +++ b/seminar/migrations/0080_zruseni_claneknode_a_konferanode.py @@ -31,28 +31,10 @@ class Migration(migrations.Migration): model_name='konfera', name='ucastnici', ), - migrations.CreateModel( - name='OrgTextNode', - 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')), - ('org_verejny', models.BooleanField(default=True, help_text='Pokud ano, bude org pod článkem podepsaný', verbose_name='Org je veřejný?')), - ('organizator', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='seminar.Organizator', verbose_name='Organizátor')), - ], - options={ - 'verbose_name': 'Organizátorský článek (Node)', - 'verbose_name_plural': 'Organizátorské články (Node)', - 'db_table': 'seminar_nodes_orgtextnode', - }, - bases=('seminar.treenode',), - ), migrations.RemoveField( model_name='konfera', name='id', ), - migrations.RenameModel( - old_name='OtisteneReseniNode', - new_name='ReseniNode', - ), migrations.RemoveField( model_name='clanek', name='cislo', diff --git a/seminar/migrations/0085_nepovinna_prezdivka.py b/seminar/migrations/0085_nepovinna_prezdivka.py_old similarity index 85% rename from seminar/migrations/0085_nepovinna_prezdivka.py rename to seminar/migrations/0085_nepovinna_prezdivka.py_old index b3bd19a4..4a8f02e0 100644 --- a/seminar/migrations/0085_nepovinna_prezdivka.py +++ b/seminar/migrations/0085_nepovinna_prezdivka.py_old @@ -1,5 +1,7 @@ # Generated by Django 2.2.13 on 2020-06-24 22:57 +# V současné době nepoužíván + from django.db import migrations, models def smaz_prezdivku(apps, schema_editor): @@ -26,7 +28,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='osoba', name='prezdivka', - field=models.CharField(blank=True, max_length=256, null=True, verbose_name='přezdívka'), + field=models.CharField(blank=True, max_length=256, verbose_name='přezdívka'), ), migrations.RunPython(smaz_prezdivku, pridej_prezdivku), ] diff --git a/seminar/migrations/0086_auto_20200819_0959.py b/seminar/migrations/0086_auto_20200819_0959.py index a5847e71..7b6b85a3 100644 --- a/seminar/migrations/0086_auto_20200819_0959.py +++ b/seminar/migrations/0086_auto_20200819_0959.py @@ -6,7 +6,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('seminar', '0085_nepovinna_prezdivka'), + ('seminar', '0084_clanek_cislo'), ] operations = [ diff --git a/seminar/migrations/0087_fix_polymorphism.py b/seminar/migrations/0087_fix_polymorphism.py index 40ce9adf..e7852c42 100644 --- a/seminar/migrations/0087_fix_polymorphism.py +++ b/seminar/migrations/0087_fix_polymorphism.py @@ -41,7 +41,7 @@ def fix_problem(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('seminar', '0086_auto_20200819_0959'), + ('seminar', '0077_auto_20200318_2146'), ] operations = [ migrations.RunPython(fix_treenode, migrations.RunPython.noop), diff --git a/seminar/migrations/0088_perm_org_a_ucastnik.py b/seminar/migrations/0088_perm_org_a_ucastnik.py index ca6190dd..febcf141 100644 --- a/seminar/migrations/0088_perm_org_a_ucastnik.py +++ b/seminar/migrations/0088_perm_org_a_ucastnik.py @@ -27,7 +27,7 @@ def add_perms(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('seminar', '0087_fix_polymorphism'), + ('seminar', '0086_auto_20200819_0959'), ] operations = [ diff --git a/seminar/migrations/0093_auto_20210330_2105.py b/seminar/migrations/0093_auto_20210330_2105.py new file mode 100644 index 00000000..06132eca --- /dev/null +++ b/seminar/migrations/0093_auto_20210330_2105.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.12 on 2021-03-30 19:05 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0092_auto_20210309_2049'), + ] + + operations = [ + migrations.DeleteModel( + name='VysledkyCelkemKCislu', + ), + migrations.DeleteModel( + name='VysledkyKCislu', + ), + migrations.DeleteModel( + name='VysledkyKCisluOdjakziva', + ), + migrations.DeleteModel( + name='VysledkyKCisluZaRocnik', + ), + migrations.DeleteModel( + name='VysledkyZaCislo', + ), + ] diff --git a/seminar/migrations/0094_auto_20210701_0149.py b/seminar/migrations/0094_auto_20210701_0149.py new file mode 100644 index 00000000..8a6b0515 --- /dev/null +++ b/seminar/migrations/0094_auto_20210701_0149.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2021-06-30 23:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0093_auto_20210330_2105'), + ] + + operations = [ + migrations.AlterField( + model_name='osoba', + name='prezdivka', + field=models.CharField(blank=True, max_length=256, null=True, verbose_name='přezdívka'), + ), + ] diff --git a/seminar/templates/seminar/odevzdavatko/tabulka.html b/seminar/templates/seminar/odevzdavatko/tabulka.html index 26a6f922..23b75d9c 100644 --- a/seminar/templates/seminar/odevzdavatko/tabulka.html +++ b/seminar/templates/seminar/odevzdavatko/tabulka.html @@ -9,6 +9,7 @@ {{ filtr.problemy }} Od: {{ filtr.reseni_od }} Do: {{ filtr.reseni_do }} +🔨? {{ filtr.neobodovane }} diff --git a/seminar/templates/seminar/orgorozcestnik.html b/seminar/templates/seminar/orgorozcestnik.html index 3ad954a1..c24ca05d 100644 --- a/seminar/templates/seminar/orgorozcestnik.html +++ b/seminar/templates/seminar/orgorozcestnik.html @@ -27,7 +27,12 @@
  • přidat pdf k opravám
  • -
  • zadávání bodů
  • +
  • + zadávání bodů + {% if pocet_neobodovanych_reseni > 0 or pocet_reseni_mimo_cislo > 0 %} + ({{pocet_neobodovanych_reseni}} řešení nemá body, {{pocet_reseni_mimo_cislo}} není v žádném čísle!) + {% endif %} +
  • poslední vydané číslo

  • diff --git a/seminar/views/odevzdavatko.py b/seminar/views/odevzdavatko.py index 09b4293f..e0d98184 100644 --- a/seminar/views/odevzdavatko.py +++ b/seminar/views/odevzdavatko.py @@ -59,12 +59,14 @@ class TabulkaOdevzdanychReseniView(ListView): problemy = fcd["problemy"] reseni_od = fcd["reseni_od"] reseni_do = fcd["reseni_do"] + jen_neobodovane = fcd["neobodovane"] else: initial = FiltrForm.gen_initial() resitele = initial['resitele'] problemy = initial['problemy'] reseni_od = initial['reseni_od'][0] reseni_do = initial['reseni_do'][0] + jen_neobodovane = initial["neobodovane"] # Filtrujeme! @@ -87,6 +89,8 @@ class TabulkaOdevzdanychReseniView(ListView): self.problemy = self.problemy.non_polymorphic() self.reseni = self.reseni.filter(cas_doruceni__date__gte=reseni_od, cas_doruceni__date__lte=reseni_do) + if jen_neobodovane: + self.reseni = self.reseni.filter(hodnoceni__body__isnull=True) def get_queryset(self): self.inicializuj_osy_tabulky() diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 5c51c517..e10aad6c 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -843,20 +843,23 @@ class OrgoRozcestnikView(TemplateView): # pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít # přes treenody (a dát si přitom pozor na MezicisloNode) + context['pocet_neobodovanych_reseni'] = s.Hodnoceni.objects.filter(body__isnull=True).count() + context['pocet_reseni_mimo_cislo'] = s.Hodnoceni.objects.filter(cislo_body__isnull=True).count() + u = self.request.user os = s.Osoba.objects.get(user=u) organizator = s.Organizator.objects.get(osoba=os) - temata_garant = s.Tema.objects.filter(garant=organizator, - rocnik=aktualni_rocnik) - #FIXME: přidat opravovatel, stav='STAV_ZADANY' - ulohy_garant = s.Uloha.objects.filter(garant=organizator, - cislo_zadani__rocnik=aktualni_rocnik) - clanky_garant = s.Clanek.objects.filter(garant=organizator, - cislo__rocnik=aktualni_rocnik) - - context['temata'] = temata_garant - context['ulohy'] = ulohy_garant - context['clanky'] = clanky_garant + #FIXME: přidat stav='STAV_ZADANY' + temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), + rocnik=aktualni_rocnik).distinct() + ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), + cislo_zadani__rocnik=aktualni_rocnik).distinct() + clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]), + cislo__rocnik=aktualni_rocnik).distinct() + + context['temata'] = temata + context['ulohy'] = ulohy + context['clanky'] = clanky context['organizator'] = organizator return context