import datetime import decimal from html.parser import HTMLParser from django import views as DjangoViews from django.contrib.contenttypes.models import ContentType import logging from personalni.models import Resitel from tvorba.models import Clanek from treenode.models import CisloNode import treenode.treelib as t logger = logging.getLogger(__name__) def vzorecek_na_prepocet(body, resitelu): """ Vzoreček na přepočet plných bodů na parciálni, když má řešení více řešitelů. """ return body * 3 / (resitelu + 2) def inverze_vzorecku_na_prepocet(body: decimal.Decimal, resitelu) -> decimal.Decimal: """ Vzoreček na přepočet parciálních bodů na plné, když má řešení více řešitelů. """ return round(body * (resitelu + 2) / 3, 1) 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 # 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): 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(): """Funkce pro hledání nekonzistencí v databázi a dalších nežádoucích stavů webu/databáze. Nijak nesouvisí s Problémy zadanými řešitelům.""" # FIXME: přejmenovat funkci? # FIXME: Tak, jak je napsaná, asi spíš patří někam k views a ne do utils (?) problemy = [] # Pomocna fce k formatovani problemovych hlasek def prb(cls, msg, objs=None): s = '%s: %s' % (cls.__name__, msg) if objs: s += ' [' for o in objs: try: url = o.admin_url() except: url = None if url: s += '%s, ' % (url, o.pk,) else: s += '%s, ' % (o.pk,) s = s[:-2] + ']' problemy.append(s) # Duplicita jmen jmena = {} for r in Resitel.objects.all(): j = r.osoba.plne_jmeno() if j not in jmena: jmena[j] = [] jmena[j].append(r) for j in jmena: if len(jmena[j]) > 1: prb(Resitel, 'Duplicitní jméno "%s"' % (j,), jmena[j]) # Data maturity a narození for r in Resitel.objects.all(): if not r.rok_maturity: prb(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(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): prb(Resitel, 'Podezřelé datum narození', [r]) # if not r.email: # prb(Resitel, u'Neznámý email', [r]) ## Kontroly konzistence databáze a TreeNodů # Články for clanek in Clanek.objects.all(): # získáme řešení svázané se článkem a z něj node ve stromě reseni = clanek.reseni_set 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 # 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 cislonode_ct = ContentType.objects.get_for_model(CisloNode) node = clanek_node while node is not None: node_ct = node.polymorphic_ctype 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(Clanek, "Číslo otištění uložené u článku nesedí s " "číslem otištění podle struktury treenodů.", [clanek]) break node = t.get_parent(node) return problemy def viewMethodSwitch(get, post): """ Vrátí view, který zavolá různé jiné views podle toho, kterou metodou je zavolán. Inspirováno https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#an-alternative-better-solution, jen jsem to udělal genericky. Parametry: post view pro metodu POST get view pro metodu GET V obou případech se míní už view jakožto funkce, takže u class-based views se už má použít .as_view() TODO: Podpora i pro metodu HEAD? A možná i pro FILES? """ theGetView = get thePostView = post class NewView(DjangoViews.View): def get(self, request, *args, **kwargs): return theGetView(request, *args, **kwargs) def post(self, request, *args, **kwargs): return thePostView(request, *args, **kwargs) return NewView.as_view()