diff --git a/data/sitetree.json b/data/sitetree.json index 3445dd59..1f58d744 100644 --- a/data/sitetree.json +++ b/data/sitetree.json @@ -337,8 +337,8 @@ "sort_order": 33, "title": "Výsledková listina", "tree": 1, - "url": "zadani/vysledkova-listina/", - "urlaspattern": false + "url": "seminar_aktualni_vysledky", + "urlaspattern": true }, "model": "sitetree.treeitem", "pk": 16 diff --git a/seminar/.~lock.profile_vysledkovka.txt# b/seminar/.~lock.profile_vysledkovka.txt# deleted file mode 100644 index cf1b89b4..00000000 --- a/seminar/.~lock.profile_vysledkovka.txt# +++ /dev/null @@ -1 +0,0 @@ -,anet,erebus,25.03.2020 22:21,file:///home/anet/.config/libreoffice/4; \ No newline at end of file diff --git a/seminar/admin.py b/seminar/admin.py index 4da32e96..849ca696 100644 --- a/seminar/admin.py +++ b/seminar/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin from django.contrib.auth.models import Group from django.db import models -from django.forms import widgets +from django.forms import widgets, ModelForm +from django.core.exceptions import ValidationError from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from reversion.admin import VersionAdmin @@ -12,11 +13,43 @@ from solo.admin import SingletonModelAdmin # Todo: reversion import seminar.models as m +import seminar.treelib as tl admin.site.register(m.Skola) admin.site.register(m.Prijemce) admin.site.register(m.Rocnik) -admin.site.register(m.Cislo) + +class CisloForm(ModelForm): + class Meta: + model = m.Cislo + fields = '__all__' + + def clean(self): + print("Cleaning...") + print(self.cleaned_data) + if self.cleaned_data.get('verejne_db') == False: + return self.cleaned_data + cn = m.CisloNode.objects.get(cislo=self.instance) + for ch in tl.all_children(cn): + if isinstance(ch, m.TemaVCisleNode): + if ch.tema.stav not in \ + (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): + raise ValidationError('Téma %(tema)s není zadané ani vyřešené', params={'tema':ch.tema}) + + if isinstance(ch, m.UlohaZadaniNode) or isinstance(ch, m.UlohaVzorakNode): + if ch.uloha.stav not in \ + (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): + raise ValidationError('Úloha %(uloha)s není zadaná ani vyřešená', params={'uloha':ch.uloha}) + if isinstance(ch, m.ReseniNode): + for problem in ch.reseni.problem_set: + if problem not in \ + (m.Problem.STAV_ZADANY, m.Problem.STAV_VYRESENY): + raise ValidationError('Problém %s není zadaný ani vyřešený', code=problem) + return self.cleaned_data + +@admin.register(m.Cislo) +class CisloAdmin(admin.ModelAdmin): + form = CisloForm @admin.register(m.Osoba) class OsobaAdmin(admin.ModelAdmin): diff --git a/seminar/forms.py b/seminar/forms.py index c64095c6..f26d11fa 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -316,3 +316,85 @@ class JednoHodnoceniForm(forms.ModelForm): OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm, extra = 0, ) + +# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat +DATE_FORMAT = '%Y-%m-%d' + +class OdevzdavatkoTabulkaFiltrForm(forms.Form): + """Form pro filtrování přehledové odevzdávátkové tabulky + + Inspirováno https://kam.mff.cuni.cz/mffzoom/""" + + # Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices) + + RESITELE_RELEVANTNI = 'relevantni' + RESITELE_LETOSNI = 'letosni' + RESITELE_CHOICES = [ + (RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky + (RESITELE_LETOSNI, 'Všichni letošní'), + # Možná: všechny vč. historických? + ] + + PROBLEMY_MOJE = 'moje' + PROBLEMY_LETOSNI = 'letosni' + PROBLEMY_CHOICES = [ + (PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga + (PROBLEMY_LETOSNI, 'Všechny letošní'), + # TODO: *hlavní problémy, možná všechny... + # XXX: Chtělo by to i "aktuálně zadané... + ] + + # TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)? + + + def gen_terminy(): + import datetime + from time import strftime + + from django.db.utils import OperationalError + try: + aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik + aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo + except OperationalError: + # django.db.utils.OperationalError: no such table: seminar_nastaveni + # Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál + logger = logging.getLogger(__name__) + logger.error("Rozbitá databáze (před počátečními migracemi?)") + return [('broken', 'Je to rozbitý'), ('fubar', 'Nefunguje to')] + + result = [] + + for cislo in m.Cislo.objects.filter( + rocnik=aktualni_rocnik, + poradi__lte=aktualni_cislo.poradi, + ).reverse(): # Standardně se řadí od nejnovějšího čísla + # Předem je mi líto kohokoliv, kdo tyhle řádky bude číst... + if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()), + f"Vydání {cislo.poradi}. čísla")) + if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()), + f"Předdeadline {cislo.poradi}. čísla")) + if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()), + f"Sous. deadline {cislo.poradi}. čísla")) + if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today(): + result.append(( + strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()), + f"Finální deadline {cislo.poradi}. čísla")) + result.append(( + strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes")) + + return result + + # NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views... + resitele = forms.ChoiceField(choices=RESITELE_CHOICES, initial=RESITELE_RELEVANTNI) + problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES, initial=PROBLEMY_MOJE) + + # choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem... + terminy = gen_terminy() + reseni_od = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-2]) + reseni_do = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-1]) diff --git a/seminar/models.py b/seminar/models.py index b85711e5..b7147106 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -864,31 +864,31 @@ class Problem(SeminarModelBase,PolymorphicModel): return str(self.kod) return '' - def verejne(self): - # aktuálně podle stavu problému - # FIXME pro některé problémy možná chceme override - # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je. - # Je to tak správně? Podle aktuální představy ano. - stav_verejny = False - if self.stav == 'zadany' or self.stav == 'vyreseny': - stav_verejny = True - print("stav_verejny: {}".format(stav_verejny)) - - cislo_verejne = False - cislonode = self.cislo_node() - if cislonode is None: - # problém nemá vlastní node, veřejnost posuzujeme jen podle stavu - print("empty node") - return stav_verejny - else: - cislo_zadani = cislonode.cislo - if (cislo_zadani and cislo_zadani.verejne()): - print("cislo: {}".format(cislo_zadani)) - cislo_verejne = True - print("stav_verejny: {}".format(stav_verejny)) - print("cislo_verejne: {}".format(cislo_verejne)) - return (stav_verejny and cislo_verejne) - verejne.boolean = True +# def verejne(self): +# # aktuálně podle stavu problému +# # FIXME pro některé problémy možná chceme override +# # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je. +# # Je to tak správně? Podle aktuální představy ano. +# stav_verejny = False +# if self.stav == 'zadany' or self.stav == 'vyreseny': +# stav_verejny = True +# print("stav_verejny: {}".format(stav_verejny)) +# +# cislo_verejne = False +# cislonode = self.cislo_node() +# if cislonode is None: +# # problém nemá vlastní node, veřejnost posuzujeme jen podle stavu +# print("empty node") +# return stav_verejny +# else: +# cislo_zadani = cislonode.cislo +# if (cislo_zadani and cislo_zadani.verejne()): +# print("cislo: {}".format(cislo_zadani)) +# cislo_verejne = True +# print("stav_verejny: {}".format(stav_verejny)) +# print("cislo_verejne: {}".format(cislo_verejne)) +# return (stav_verejny and cislo_verejne) +# verejne.boolean = True def verejne_url(self): return reverse('seminar_problem', kwargs={'pk': self.id}) @@ -962,6 +962,7 @@ class Clanek(Problem): cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT, verbose_name='číslo vydání', related_name='vydane_clanky') + @cached_property def kod_v_rocniku(self): if self.stav == 'zadany': # Nemělo by být potřeba diff --git a/seminar/templates/seminar/odevzdavatko/tabulka.html b/seminar/templates/seminar/odevzdavatko/tabulka.html index ff396ce4..26a6f922 100644 --- a/seminar/templates/seminar/odevzdavatko/tabulka.html +++ b/seminar/templates/seminar/odevzdavatko/tabulka.html @@ -4,6 +4,14 @@ {% block content %} +
+{{ filtr.resitele }} +{{ filtr.problemy }} +Od: {{ filtr.reseni_od }} +Do: {{ filtr.reseni_do }} + +
+ {# Prázdná buňka v levém horním rohu #} diff --git a/seminar/templates/seminar/zadani/AktualniVysledkovka.html b/seminar/templates/seminar/zadani/AktualniVysledkovka.html index ed89c87c..5874c983 100644 --- a/seminar/templates/seminar/zadani/AktualniVysledkovka.html +++ b/seminar/templates/seminar/zadani/AktualniVysledkovka.html @@ -24,7 +24,9 @@

Výsledky včetně neveřejných

{% with vysledkovka_s_neverejnymi as radky_vysledkovky %} + {% with cisla_s_neverejnymi as cisla %} {% include "seminar/vysledkovka_rocnik.html" %} + {% endwith %} {% endwith %}
{% endif %} diff --git a/seminar/utils.py b/seminar/utils.py index a02c317c..26d52665 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -230,9 +230,9 @@ def cisla_rocniku(rocnik, jen_verejne=True): seznam objektů typu Cislo """ if jen_verejne: - return rocnik.verejna_cisla() + return rocnik.verejne_vysledkovky_cisla() else: - return rocnik.cisla.all() + return rocnik.cisla.all().order_by('poradi') def hlavni_problem(problem): """ Pro daný problém vrátí jeho nejvyšší nadproblém.""" diff --git a/seminar/views/odevzdavatko.py b/seminar/views/odevzdavatko.py index e81f119f..d1cedb1b 100644 --- a/seminar/views/odevzdavatko.py +++ b/seminar/views/odevzdavatko.py @@ -12,6 +12,7 @@ import logging import seminar.models as m import seminar.forms as f +from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm from seminar.utils import aktivniResitele, resi_v_rocniku logger = logging.getLogger(__name__) @@ -41,28 +42,55 @@ class TabulkaOdevzdanychReseniView(ListView): def inicializuj_osy_tabulky(self): """Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů""" + # FIXME: jméno metody není vypovídající... # NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat # TODO: Prefetches, Select related, ... self.resitele = m.Resitel.objects.all() self.problemy = m.Problem.objects.all() + self.reseni = m.Reseni.objects.all() + + form = FiltrForm(self.request.GET) + if form.is_valid(): + fcd = form.cleaned_data + resitele = fcd["resitele"] + problemy = fcd["problemy"] + reseni_od = fcd["reseni_od"] + reseni_do = fcd["reseni_do"] + else: + resitele = FiltrForm.get_initial_for_field(FormFiltr.resitele, "resitele") + problemy = FiltrForm.get_initial_for_field(FormFiltr.problemy, "problemy") + resitele_od = FiltrForm.get_initial_for_field(FormFiltr.resitele_od, "resitele_od") + resitele_do = FiltrForm.get_initial_for_field(FormFiltr.resitele_do, "resitele_do") + - def get_queryset(self): - self.inicializuj_osy_tabulky() - self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... - self.resitele = resi_v_rocniku(self.akt_rocnik) + # Filtrujeme! + aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci + if resitele == FiltrForm.RESITELE_RELEVANTNI: + logger.warning("Někdo chtěl v tabulce jen relevantní řešitele a měl smůlu :-(") + resitele = FiltrForm.RESITELE_LETOSNI # Fall-through + elif resitele == FiltrForm.RESITELE_LETOSNI: + self.resitele = resi_v_rocniku(aktualni_rocnik) + + if problemy == FiltrForm.PROBLEMY_MOJE: + org = m.Organizator.objects.get(osoba__user=self.request.user) + from django.db.models import Q + self.problemy = self.problemy.filter(Q(autor=org)|Q(garant=org)|Q(opravovatele=org), stav=m.Problem.STAV_ZADANY) + elif problemy == FiltrForm.PROBLEMY_LETOSNI: + self.problemy = self.problemy.filter(stav=m.Problem.STAV_ZADANY) + #self.problemy = list(filter(lambda problem: problem.rocnik() == aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník.... # NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. - self.problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() + self.problemy = self.problemy.non_polymorphic() + + self.reseni = self.reseni.filter(cas_doruceni__date__gte=reseni_od, cas_doruceni__date__lte=reseni_do) + def get_queryset(self): + self.inicializuj_osy_tabulky() qs = super().get_queryset() - qs = qs.filter(problem__in=self.problemy).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') + qs = qs.filter(problem__in=self.problemy, reseni__in=self.reseni, reseni__resitele__in=self.resitele).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') return qs def get_context_data(self, *args, **kwargs): - # FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. - self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... - self.resitele = resi_v_rocniku(self.akt_rocnik) - # NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. - self.problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() + # self.resitele, self.reseni a self.problemy jsou již nastavené ctx = super().get_context_data(*args, **kwargs) ctx['problemy'] = self.problemy @@ -100,6 +128,10 @@ class TabulkaOdevzdanychReseniView(ListView): hodnoty.append(resiteluv_radek) ctx['radky'] = list(zip(self.resitele, hodnoty)) + ctx['filtr'] = FiltrForm(initial=self.request.GET) + # Pro použití hacku na automatické {{form.media}} v template: + ctx['form'] = ctx['filtr'] + return ctx # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 7c1d2370..04600d6a 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -29,7 +29,7 @@ from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm import seminar.forms as f import seminar.templatetags.treenodes as tnltt import seminar.views.views_rest as vr -from seminar.views.vysledkovka import vysledkovka_rocniku, vysledkovka_cisla +from seminar.views.vysledkovka import vysledkovka_rocniku, vysledkovka_cisla, body_resitelu from datetime import timedelta, date, datetime, MAXYEAR from django.utils import timezone @@ -185,7 +185,7 @@ class TNLData(object): return [cls.from_treenode(treenode)] else: found = [] - for tn in all_children(treenode): + for tn in treelib.all_children(treenode): result = cls.filter_treenode(tn, predicate) # Result by v tuhle chvíli měl být seznam TNLDat odpovídající treenodům, jež matchnuly predikát. for tnl in result: @@ -503,6 +503,7 @@ def ZadaniAktualniVysledkovkaView(request): pass # vysledkovka s neverejnyma vysledkama vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) + cisla_s_neverejnymi = cisla_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) return render( request, 'seminar/zadani/AktualniVysledkovka.html', @@ -511,6 +512,7 @@ def ZadaniAktualniVysledkovkaView(request): 'radky_vysledkovky': vysledkovka, 'cisla': cisla, 'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi, + 'cisla_s_neverejnymi': cisla_s_neverejnymi, } ) diff --git a/seminar/views/vysledkovka.py b/seminar/views/vysledkovka.py index f4fa5edb..6a5da6a9 100644 --- a/seminar/views/vysledkovka.py +++ b/seminar/views/vysledkovka.py @@ -48,7 +48,7 @@ def sloupec_s_poradim(setrizene_body): -def body_resitelu(resitele, za, odjakziva=True): +def body_resitelu(resitele, za, odjakziva=True, jen_verejne=False): """ Funkce počítající počty bodů pro zadané řešitele, buď odjakživa do daného ročníku/čísla anebo za daný ročník/číslo. Parametry: @@ -94,12 +94,22 @@ def body_resitelu(resitele, za, odjakziva=True): body_k_zapocteni = Sum('reseni__hodnoceni__body', filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok, reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) )) - elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně. - body_k_zapocteni = Sum('reseni__hodnoceni__body', - filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok)) + elif rocnik and odjakziva: # Spočítáme body za starší ročníky až do zadaného včetně. + if jen_verejne: + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok, + reseni__hodnoceni__cislo_body__verejna_vysledkovka=True)) + else: + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter= Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lte=rok)) elif rocnik and not odjakziva: # Spočítáme body za daný ročník. - body_k_zapocteni = Sum('reseni__hodnoceni__body', - filter= Q(reseni__hodnoceni__cislo_body__rocnik=rocnik)) + if jen_verejne: + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter=Q(reseni__hodnoceni__cislo_body__rocnik=rocnik, + reseni__hodnoceni__cislo_body__verejna_vysledkovka=True)) + else: + body_k_zapocteni = Sum('reseni__hodnoceni__body', + filter=Q(reseni__hodnoceni__cislo_body__rocnik=rocnik)) else: assert True, "body_resitelu: Neplatná kombinace za a odjakživa." @@ -149,14 +159,14 @@ def vysledkovka_rocniku(rocnik, jen_verejne=True): body_cisla_slov[cislo.id] = cislobody # získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně - resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele) + resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele, jen_verejne=jen_verejne) # setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší setrizeni_resitele_id, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn) poradi = sloupec_s_poradim(setrizene_body) # získáme body odjakživa - resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik) + resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, rocnik, jen_verejne=jen_verejne) # vytvoříme jednotlivé sloupce výsledkovky radky_vysledkovky = [] @@ -216,7 +226,7 @@ def pricti_body(slovnik, resitel, body): slovnik[resitel.id] += body -def secti_body_za_rocnik(za, aktivni_resitele): +def secti_body_za_rocnik(za, aktivni_resitele, jen_verejne): """ Spočítá body za ročník (celý nebo do daného čísla), setřídí je sestupně a vrátí jako seznam. Parametry: @@ -224,7 +234,7 @@ def secti_body_za_rocnik(za, aktivni_resitele): daného čísla """ # spočítáme všem řešitelům jejich body za ročník (False => ne odjakživa) - resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, za, False) + resitel_rocnikbody_slov = body_resitelu(aktivni_resitele, za, False, jen_verejne=jen_verejne) # zeptáme se na dvojice (řešitel, body) za ročník a setřídíme sestupně resitel_rocnikbody_sezn = sorted(resitel_rocnikbody_slov.items(), key = lambda x: x[1], reverse = True) @@ -380,10 +390,10 @@ def vysledkovka_cisla(cislo, context=None): hlavni_problemy_slovnik, cislobody = secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy) # získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně - resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele) + resitel_rocnikbody_sezn = secti_body_za_rocnik(cislo, aktivni_resitele, jen_verejne=True) # získáme body odjakživa - resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo) + resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo, jen_verejne=True) # řešitelé setřídění podle bodů za číslo sestupně setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn]