From 83dec5e0dabce4de3d66e6f3b71932178db07e6c Mon Sep 17 00:00:00 2001 From: Jonas Havelka Date: Thu, 25 Jun 2020 19:50:30 +0200 Subject: [PATCH 1/4] =?UTF-8?q?Aktivn=C3=AD=20=C5=99e=C5=A1itel=C3=A9=20p?= =?UTF-8?q?=C5=99esunuti=20z=20views=20do=20utils=20a=20opraveni?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/export.py | 2 +- seminar/utils.py | 100 +++++++++++++++++++++++++++++++------ seminar/views/views_all.py | 69 +++---------------------- 3 files changed, 93 insertions(+), 78 deletions(-) diff --git a/seminar/export.py b/seminar/export.py index b2339786..e3d984c8 100644 --- a/seminar/export.py +++ b/seminar/export.py @@ -76,7 +76,7 @@ class ExportRocnikView(generic.View): rocnik = get_object_or_404(Rocnik, prvni_rok=pr, exportovat=True) cislo = rocnik.posledni_zverejnena_vysledkovka_cislo() - resitele = views.aktivniResitele(cislo.rocnik.rocnik, cislo.poradi, True) + resitele = views.aktivniResitele(cislo, True) slovnik_body = views.secti_body_za_rocnik(cislo, resitele) _, setrizeni_resitele, setrizene_body = views.setrid_resitele_a_body(slovnik_body) diff --git a/seminar/utils.py b/seminar/utils.py index f504ceb5..efa43ffe 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -2,22 +2,26 @@ import datetime from django.contrib.auth.decorators import user_passes_test -from html.parser import HTMLParser +from html.parser import HTMLParser from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist import seminar.models as m import seminar.treelib as t 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: @@ -27,8 +31,9 @@ def histogram(seznam): 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')) +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 = "" @@ -37,6 +42,7 @@ def roman(num): num %= i return res + def from_roman(rom): if not rom: return 0 @@ -60,9 +66,9 @@ def seznam_problemu(): except: url = None if url: - s += '%s, ' % (url, o.pk, ) + s += '%s, ' % (url, o.pk,) else: - s += '%s, ' % (o.pk, ) + s += '%s, ' % (o.pk,) s = s[:-2] + ']' problemy.append(s) @@ -75,7 +81,7 @@ def seznam_problemu(): jmena[j].append(r) for j in jmena: if len(jmena[j]) > 1: - prb(m.Resitel, 'Duplicitní jméno "%s"' % (j, ), jmena[j]) + prb(m.Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j]) # Data maturity a narození for r in m.Resitel.objects.all(): @@ -83,13 +89,14 @@ def seznam_problemu(): prb(m.Resitel, '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, 'Podezřelé datum maturity', [r]) - if r.osoba.datum_narozeni and (r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12): + if r.osoba.datum_narozeni and ( + r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12): prb(m.Resitel, 'Podezřelé datum narození', [r]) -# if not r.email: -# prb(Resitel, u'Neznámý email', [r]) + # if not r.email: + # prb(Resitel, u'Neznámý email', [r]) ## Kontroly konzistence databáze a TreeNodů - + # Články for clanek in m.Clanek.objects.all(): # získáme řešení svázané se článkem a z něj node ve stromě @@ -97,7 +104,7 @@ def seznam_problemu(): if (reseni.count() != 1): raise ValueError("Článek k sobě má nejedno řešení!") r = reseni.first() - clanek_node = r.text_cely # vazba na ReseniNode z Reseni + clanek_node = r.text_cely # vazba na ReseniNode z Reseni # content type je věc pomáhající rozeznávat různé typy objektů v django-polymorphic # protože isinstance vrátí vždy jen TreeNode # https://django-polymorphic.readthedocs.io/en/stable/migrating.html @@ -105,14 +112,77 @@ def seznam_problemu(): node = clanek_node while node is not None: node_ct = node.polymorphic_ctype - if node_ct == cislonode_ct: # dostali jsme se k CisloNode + if node_ct == cislonode_ct: # dostali jsme se k CisloNode # zkontrolujeme, že stromové číslo odpovídá atributu # .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali # CisloNode if clanek.cislo != node.cislonode.cislo: prb(m.Clanek, "Číslo otištění uložené u článku nesedí s " - "číslem otištění podle struktury treenodů.", [clanek]) + "číslem otištění podle struktury treenodů.", [clanek]) break node = t.get_parent(node) - + return problemy + + +### Generovani obalek +def resi_v_rocniku(rocnik, cislo=None): + """ Vrátí seznam řešitelů, co vyřešili nějaký problém v daném ročníku, do daného čísla. + Parametry: + rocnik (typu Rocnik) ročník, ze kterého chci řešitele, co něco odevzdali + cislo (typu Cislo) číslo, do kterého včetně se počítá, že v daném + ročníku řešitel něco poslal. + Pokud není zadané, počítají se všechna řešení z daného ročníku. + Výstup: + QuerySet objektů typu Resitel """ + + if cislo is None: + # filtrujeme pouze podle ročníku + letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik) + else: # filtrujeme podle ročníku i čísla + letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik, + hodnoceni__cislo_body__poradi__lte=cislo.poradi) + + # vygenerujeme queryset řešitelů, co letos něco poslali + letosni_resitele = m.Resitel.objects.none() + for reseni in letosni_reseni: + letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok()) + return letosni_resitele.distinct() + + +def aktivniResitele(cislo, pouze_letosni=False): + """ Vrací QuerySet aktivních řešitelů, což jsou ti, co ještě neodmaturovali + a letos něco poslali (anebo loni něco poslali, pokud jde o první tři čísla). + Parametry: + cislo (typu Cislo) číslo, o které se jedná + pouze_letosni jen řešitelé, kteří tento rok něco poslali + + """ + letos = cislo.rocnik + + # detekujeme, zda jde o první tři čísla či nikoli (tj. zda spamovat řešitele z minulého roku) + zacatek_rocniku = True + try: + if int(cislo.poradi) > 3: + zacatek_rocniku = False + except ValueError: + if cislo.poradi != '7-8': + raise ValueError(f'{cislo} je neplatné číslo čísla (není int a není 7-8)') + zacatek_rocniku = False + + # nehledě na číslo chceme jen řešitele, kteří letos něco odevzdali + if pouze_letosni: + zacatek_rocniku = False + + try: + loni = m.Rocnik.objects.get(rocnik=letos.rocnik - 1) + except ObjectDoesNotExist: + # Pro první ročník neexistuje ročník předchozí + zacatek_rocniku = False + + if not zacatek_rocniku: + return resi_v_rocniku(letos, cislo) + else: + # spojíme querysety s řešiteli loni a letos do daného čísla + + return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct() diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 48d54c22..12a7f733 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -41,6 +41,8 @@ import csv import logging import time +from seminar.utils import aktivniResitele, resi_v_rocniku + def verejna_temata(rocnik): """Vrací queryset zveřejněných témat v daném ročníku. @@ -51,7 +53,7 @@ def temata_v_rocniku(rocnik): return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik) def get_problemy_k_tematu(tema): - return Problemy.objects.filter(nadproblem = tema) + return Problem.objects.filter(nadproblem = tema) class VlozBodyView(generic.ListView): @@ -707,7 +709,7 @@ def vysledkovka_cisla(cislo, context=None): ## 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 = list(aktivniResitele(cislo.rocnik.rocnik, cislo.poradi)) + aktivni_resitele = list(aktivniResitele(cislo)) # získáme body za číslo hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy) @@ -758,7 +760,7 @@ class CisloView(generic.DetailView): model = Cislo template_name = 'seminar/archiv/cislo.html' - # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) + # Vlastni ziskavani objektu z databaze podle (Rocnik.rocnik) def get_object(self, queryset=None): if queryset is None: queryset = self.get_queryset() @@ -814,66 +816,9 @@ class RocnikVysledkovkaView(RocnikView): content_type = 'text/plain; charset=UTF8' #vypise na stranku textovy obsah vyTeXane vysledkovky k okopirovani -### Generovani obalek -def resi_v_rocniku(rocnik, cislo=None): - """ Vrátí seznam řešitelů, co vyřešili nějaký problém v daném ročníku, do daného čísla. - Parametry: - rocnik (typu Rocnik) ročník, ze kterého chci řešitele, co něco odevzdali - cislo (typu Cislo) číslo, do kterého včetně se počítá, že v daném - ročníku řešitel něco poslal. - Pokud není zadané, počítají se všechna řešení z daného ročníku. - Výstup: - QuerySet objektů typu Resitel """ - - if cislo is None: - # filtrujeme pouze podle ročníku - letosni_reseni = Reseni.objects.filter(hodnoceni__cislo_body__rocnik = rocnik) - else: # filtrujeme podle ročníku i čísla - letosni_reseni = Reseni.objects.filter(hodnoceni__cislo_body__rocnik = rocnik, - hodnoceni__cislo_body__poradi__lte=cislo.poradi) - - # vygenerujeme queryset řešitelů, co letos něco poslali - letosni_resitele = Resitel.objects.none() - for reseni in letosni_reseni: - letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok()) - return letosni_resitele.distinct() - - -def aktivniResitele(rocnik, cislo, pouze_realni=False): - """ Vrací QuerySet aktivních řešitelů, což jsou ti, co ještě neodmaturovali - a letos něco poslali (anebo loni něco poslali, pokud jde o první tři čísla). - Parametry: - rocnik (typu int) číslo ročníku, o který se jedná - cislo (typu int) pořadí čísla, o které se jedná - pouze_realni jen řešitelé, kteří tento rok něco poslali - - """ - letos = Rocnik.objects.get(rocnik = rocnik) - #TODO: co se stane, když zadané kombinace neexistují? ošetřit - aktualni_cislo = Cislo.objects.get(rocnik = rocnik, poradi = cislo) - loni = Rocnik.objects.get(rocnik = rocnik - 1) - - # detekujeme, zda jde o první tři čísla či nikoli - zacatek_rocniku = True - try: - if int(aktualni_cislo.poradi) > 3: - zacatek_rocniku = False - except ValueError: - # pravděpodobně se jedná o číslo 7-8 - zacatek_rocniku = False - - # nehledě na číslo chceme jen řešitele, kteří letos něco odevzdali - if pouze_realni: - zacatek_rocniku = False - - if not zacatek_rocniku: - return resi_v_rocniku(letos) - else: - # spojíme querysety s řešiteli loni a letos do daného čísla - return (resi_v_rocniku(loni) | resi_v_rocniku(letos, aktualni_cislo)).distinct() - def cisloObalkyView(request, rocnik, cislo): - return obalkyView(request, aktivniResitele(rocnik, cislo)) + realne_cislo = Cislo.objects.get(poradi=cislo, rocnik__rocnik=rocnik) + return obalkyView(request, aktivniResitele(realne_cislo)) def obalkyView(request, resitele): From 28322402b860bd620170f2e688adcfd70541581d Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Fri, 26 Jun 2020 13:02:09 +0200 Subject: [PATCH 2/4] =?UTF-8?q?Fix=20=C5=A1patn=C3=A9ho=20importu=20aktivn?= =?UTF-8?q?=C3=ADch=20=C5=99e=C5=A1itel=C5=AF=20v=20exportu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/export.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seminar/export.py b/seminar/export.py index e3d984c8..34dcad9a 100644 --- a/seminar/export.py +++ b/seminar/export.py @@ -9,6 +9,7 @@ from .models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from .ovvpfile import OvvpFile from seminar import views +from seminar.utils import aktivniResitele class ExportIndexView(generic.View): def get(self, request): @@ -76,7 +77,7 @@ class ExportRocnikView(generic.View): rocnik = get_object_or_404(Rocnik, prvni_rok=pr, exportovat=True) cislo = rocnik.posledni_zverejnena_vysledkovka_cislo() - resitele = views.aktivniResitele(cislo, True) + resitele = aktivniResitele(cislo, True) slovnik_body = views.secti_body_za_rocnik(cislo, resitele) _, setrizeni_resitele, setrizene_body = views.setrid_resitele_a_body(slovnik_body) From 17f2a5da6f111062607f50a90a3924fcfff88584 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Fri, 26 Jun 2020 13:04:55 +0200 Subject: [PATCH 3/4] =?UTF-8?q?Oprava=20zarovn=C3=A1n=C3=AD=20mapov=C3=A1n?= =?UTF-8?q?=C3=AD=20=C5=99=C3=ADmsk=C3=BDch=20=C4=8D=C3=ADsel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seminar/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/seminar/utils.py b/seminar/utils.py index efa43ffe..ca709d03 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -30,9 +30,9 @@ def histogram(seznam): 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')) +# Pozor: zarovnáno velmi netradičně pro přehlednost +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): From 4e7b7d10f99889a1ef7a16dbda937c26f0fac615 Mon Sep 17 00:00:00 2001 From: Pavel 'LEdoian' Turinsky Date: Fri, 26 Jun 2020 13:22:51 +0200 Subject: [PATCH 4/4] Utils: oprava whitespace --- seminar/utils.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/seminar/utils.py b/seminar/utils.py index ca709d03..9a442127 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -92,8 +92,8 @@ def seznam_problemu(): if r.osoba.datum_narozeni and ( r.osoba.datum_narozeni.year < 1970 or r.osoba.datum_narozeni.year > datetime.date.today().year - 12): prb(m.Resitel, 'Podezřelé datum narození', [r]) - # if not r.email: - # prb(Resitel, u'Neznámý email', [r]) +# if not r.email: +# prb(Resitel, u'Neznámý email', [r]) ## Kontroly konzistence databáze a TreeNodů @@ -104,7 +104,7 @@ def seznam_problemu(): if (reseni.count() != 1): raise ValueError("Článek k sobě má nejedno řešení!") r = reseni.first() - clanek_node = r.text_cely # vazba na ReseniNode z Reseni + clanek_node = r.text_cely # vazba na ReseniNode z Reseni # content type je věc pomáhající rozeznávat různé typy objektů v django-polymorphic # protože isinstance vrátí vždy jen TreeNode # https://django-polymorphic.readthedocs.io/en/stable/migrating.html @@ -112,13 +112,13 @@ def seznam_problemu(): node = clanek_node while node is not None: node_ct = node.polymorphic_ctype - if node_ct == cislonode_ct: # dostali jsme se k CisloNode + if node_ct == cislonode_ct: # dostali jsme se k CisloNode # zkontrolujeme, že stromové číslo odpovídá atributu # .cislonode je opačná vazba k treenode_ptr, abychom z TreeNode dostali # CisloNode if clanek.cislo != node.cislonode.cislo: prb(m.Clanek, "Číslo otištění uložené u článku nesedí s " - "číslem otištění podle struktury treenodů.", [clanek]) + "číslem otištění podle struktury treenodů.", [clanek]) break node = t.get_parent(node) @@ -184,5 +184,4 @@ def aktivniResitele(cislo, pouze_letosni=False): return resi_v_rocniku(letos, cislo) else: # spojíme querysety s řešiteli loni a letos do daného čísla - return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct()