Browse Source

Merge branch 'data_migrations' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into data_migrations

export_seznamu_prednasek
Pavel "LEdoian" Turinsky 4 years ago
parent
commit
ade44ee560
  1. 249
      data/auth_groups.json
  2. 2441
      data/auth_permissions.json
  3. 74
      data/sitetree.json
  4. 24
      mamweb/static/css/mamweb.css
  5. 7
      seminar/admin.py
  6. 42
      seminar/models.py
  7. 41
      seminar/templates/seminar/archiv/cislo.html
  8. 34
      seminar/templates/seminar/clanky/resitelske_clanky.html
  9. 2
      seminar/templates/seminar/titulnistrana.html
  10. 25
      seminar/templates/seminar/zadani/AktualniZadani.html
  11. 11
      seminar/treelib.py
  12. 147
      seminar/views/views_all.py

249
data/auth_groups.json

@ -0,0 +1,249 @@
[
{
"fields": {
"name": "org",
"permissions": [
23,
24,
25,
1,
26,
72,
73,
74,
75,
76,
77,
78,
79,
51,
55,
52,
53,
54,
56,
57,
58,
59,
60,
61,
62,
63,
43,
44,
45,
46,
228,
229,
230,
231,
232,
233,
234,
235,
260,
261,
262,
263,
264,
265,
266,
267,
244,
245,
246,
247,
236,
237,
238,
239,
240,
241,
242,
243,
248,
249,
250,
251,
252,
253,
254,
255,
256,
257,
258,
259,
212,
213,
214,
215,
80,
81,
82,
83,
180,
181,
182,
183,
172,
173,
174,
175,
168,
169,
170,
171,
132,
133,
134,
135,
224,
225,
226,
227,
184,
185,
186,
187,
112,
113,
114,
115,
120,
121,
122,
123,
164,
165,
166,
167,
124,
125,
126,
127,
216,
217,
218,
219,
136,
137,
138,
139,
152,
153,
154,
155,
208,
209,
210,
211,
140,
141,
142,
143,
108,
109,
110,
111,
84,
85,
86,
87,
104,
105,
106,
107,
160,
161,
162,
163,
220,
221,
222,
223,
88,
89,
90,
91,
92,
93,
94,
95,
188,
189,
190,
191,
96,
97,
98,
99,
100,
101,
102,
103,
128,
129,
130,
131,
116,
117,
118,
119,
156,
157,
158,
159,
192,
193,
194,
195,
144,
145,
146,
147,
196,
197,
198,
199,
176,
177,
178,
179,
148,
149,
150,
151,
200,
201,
202,
203,
204,
205,
206,
207,
64,
65,
66,
67,
68,
69,
70,
71,
35,
36,
37,
38,
39,
40,
41,
42,
47,
48,
49,
50
]
},
"model": "auth.group",
"pk": 1
}
]

2441
data/auth_permissions.json

File diff suppressed because it is too large

74
data/sitetree_new.json → data/sitetree.json

@ -343,30 +343,6 @@
"model": "sitetree.treeitem",
"pk": 16
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [],
"access_restricted": false,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 3,
"sort_order": 34,
"title": "Články",
"tree": 1,
"url": "clanky_resitel",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 17
},
{
"fields": {
"access_guest": false,
@ -659,30 +635,6 @@
"model": "sitetree.treeitem",
"pk": 33
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [],
"access_restricted": false,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 3,
"sort_order": 17,
"title": "Témata",
"tree": 1,
"url": "seminar_temata",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 34
},
{
"fields": {
"access_guest": false,
@ -806,5 +758,29 @@
},
"model": "sitetree.treeitem",
"pk": 39
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [],
"access_restricted": false,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 5,
"sort_order": 40,
"title": "Řešitelské články",
"tree": 1,
"url": "/archiv/clanky",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 40
}
]
]

24
mamweb/static/css/mamweb.css

@ -326,11 +326,7 @@ div.novinky_name {
font-style: italic;
}
div.zadani_azad_termin {
text-align: center;
font-size: large;
font-weight: bold
}
/**********
* Footer
@ -915,6 +911,24 @@ div.cislo_odkazy ul {
margin-top: 15px;
}
div.zadani_termin {
text-align: center;
font-size: large;
font-weight: bold;
}
@media (max-width: 420px) {
div.zadani_termin {
font-size: small;
}
}
div.zadani_termin .datum {
color:#e84e10;
margin:0px;
}
/* galerie */

7
seminar/admin.py

@ -1,5 +1,5 @@
from django.contrib import admin
from django.contrib.auth.models import Permission
from django.contrib.auth.models import Group
from django.db import models
from django.forms import widgets
@ -32,11 +32,12 @@ class OsobaAdmin(admin.ModelAdmin):
synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů"
def udelej_orgem(self,request,queryset):
org_perm = Permission.objects.filter(codename__exact='org').first()
org_group = Group.objects.get(name='org')
print(queryset)
for o in queryset:
user = o.user
user.user_permissions.add(org_perm)
print(user)
user.groups.add(org_group)
user.is_staff = True
user.save()
org = m.Organizator.objects.create(osoba=o)

42
seminar/models.py

@ -26,6 +26,7 @@ from taggit.managers import TaggableManager
from reversion import revisions as reversion
from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode
from seminar import treelib
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
@ -869,13 +870,22 @@ class Problem(SeminarModelBase,PolymorphicModel):
stav_verejny = False
if self.stav == 'zadany' or self.stav == 'vyreseny':
stav_verejny = True
return stav_verejny
#cislo_verejne = False
#if (self.cislo_zadani and self.cislo_zadani.verejne()):
# cislo_verejne = True
#return (stav_verejny and cislo_verejne)
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):
@ -930,6 +940,16 @@ class Tema(Problem):
for tvcn in self.temavcislenode_set.all():
tvcn.save()
def cislo_node(self):
tema_node_set = self.temavcislenode_set.all()
tema_cisla_vyskyt = []
for tn in tema_node_set:
tema_cisla_vyskyt.append(
treelib.get_upper_node_of_type(tn, CisloNode).cislo)
tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani)
prvni_zadani = tema_cisla_vyskyt[0]
return prvni_zadani.cislonode
class Clanek(Problem):
class Meta:
db_table = 'seminar_clanky'
@ -946,6 +966,9 @@ class Clanek(Problem):
# return self.nadproblem.kod_v_rocniku()+".c{}".format(self.kod)
return "c{}".format(self.kod)
return '<Není zadaný>'
def node(self):
return None
class Text(SeminarModelBase):
class Meta:
@ -1019,6 +1042,9 @@ class Uloha(Problem):
# Neexistující *Node nemá smysl aktualizovat.
pass
def cislo_node(self):
zadani_node = self.ulohazadaninode
return treelib.get_upper_node_of_type(zadani_node, CisloNode)
@reversion.register(ignore_duplicates=True)
class Reseni(SeminarModelBase):
@ -1309,6 +1335,8 @@ class Konfera(Problem):
def __str__(self):
return "{}: ({})".format(self.nazev, self.soustredeni)
def cislo_node(self):
return None
# Vazebna tabulka. Mozna se generuje automaticky.
@reversion.register(ignore_duplicates=True)

41
seminar/templates/seminar/archiv/cislo.html

@ -74,9 +74,24 @@
<th class='border-r'>#
<th class='border-r'>Jméno
{% for p in problemy %}
<th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a>
<th class='border-r' id="problem{{ p.kod_v_rocniku }}"><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a>
{# TODELETE #}
{% for podproblemy in podproblemy_iter.next %}
<th class='border-r podproblem{{ forloop.parentloop.counter }}'><a href="{{ podproblemy.verejne_url }}">{{ podproblemy.kod_v_rocniku }}</a>
{% endfor %}
{# TODELETE #}
{% endfor %}
{% if ostatni %}<th class='border-r'>Ostatní {% endif %}
{# TODELETE #}
{% for podproblemy in podproblemy_iter.next %}
<th class='border-r podproblem{{ problemy.len }}'><a href="{{ podproblemy.verejne_url }}">{{ podproblemy.kod_v_rocniku }}</a>
{% endfor %}
{# TODELETE #}
<th class='border-r'>Za číslo
<th class='border-r'>Za ročník
<th class='border-r'>Odjakživa
@ -90,6 +105,13 @@
{{ rv.resitel.osoba.plne_jmeno }}
{% for b in rv.body_problemy_sezn %}
<td class='border-r'>{{ b }}
{# TODELETE #}
{% for body_podproblemu in rv.body_podproblemy_iter.next %}
<td class='border-r podproblem{{ forloop.parentloop.counter }}'>{{ body_podproblemu }}
{% endfor %}
{# TODELETE #}
{% endfor %}
<td class='border-r'>{{ rv.body_cislo }}
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
@ -97,6 +119,23 @@
</tr>
{% endfor %}
</table>
{# TODELETE #}
<script>
{% for p in problemy %}
$(".podproblem{{ forloop.counter }}").css("display", "none")
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }})
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end)
function podproblem{{ forloop.counter }}(event) {
$(".podproblem{{ forloop.counter }}").css("display", "")
}
function podproblem{{ forloop.counter }}end(event) {
$(".podproblem{{ forloop.counter }}").css("display", "none")
}
{% endfor %}
</script>
{# TODELETE #}
{% endif %}
{% if not cislo.verejna_vysledkovka and user.je_org %}

34
seminar/templates/seminar/clanky/resitelske_clanky.html

@ -9,15 +9,31 @@
</h1>
{% for clanek in object_list %}
{% with clanek.cislo.rocnik.rocnik as rocnik %}
{% ifchanged rocnik %}
{% if not forloop.first %}</ul>{% endif %}
<h2>{{ rocnik }}. ročník</h2>
<ul>
{% endifchanged %}
<li>
<a href="{{ clanek.verejne_url }}">{{ clanek.nazev }}</a>
{% endwith %}
{% with clanek.cislo.rocnik.rocnik as rocnik %}
{% ifchanged rocnik %}
{% if not forloop.first %}</ul>{% endif %}
<h2>{{ rocnik }}. ročník</h2>
<ul>
{% endifchanged %}
<li>
{% if clanek.cislo.pdf %}
<a href="{{ clanek.cislo.pdf.url }}">
{{ clanek.nazev }}
({% for r in clanek.reseni_set.first.resitele.all %}
{{r}}
{% if not forloop.last %},{% endif %}
{% endfor %}
</a>
)
{% else %}
{{ clanek.nazev }}
({% for r in clanek.reseni_set.first.resitele.all %}
{{r}},
{% endfor %}
vyšlo v čísle {{clanek.cislo}})
{% endif %}
</li>
{% endwith %}
{% endfor %}
</ul>

2
seminar/templates/seminar/titulnistrana.html

@ -16,6 +16,7 @@ function sousdeadline() {
{% block content %}
{% if nejblizsi_deadline %}
<hr>
<div class="odpocet">
<p><b><big>Do
{% if typ_deadline == 'soustredeni' %}
@ -30,6 +31,7 @@ function sousdeadline() {
{% endif %}zbývá:
{{nejblizsi_deadline|timeuntil}}</big></b></p>
</div>
<hr>
{% endif %}
<div class=titulnistrana>

25
seminar/templates/seminar/zadani/AktualniZadani.html

@ -12,25 +12,26 @@
{# Zobrazovani neverejnych zadani jen organizatorum #}
{% if user.je_org or verejne %}
{% if user.je_org and not verejne %}<div class="mam-org-only">{% endif %}
<hr>
<div class="zadani_termin">
Termíny pro odeslání řešení {{ac.poradi}}. série:<br>
{% if ac.datum_deadline_soustredeni %}
<div class="zadani_azad_termin">
Termín odeslání {{ac.poradi}}. série pro účast na soustředění:
{{ac.datum_deadline_soustredeni}}
</div>
{% if ac.datum_deadline_soustredeni %}
<span class="datum">{{ac.datum_deadline_soustredeni}}</span> pro účast na soustředění<br>
{% endif %}
{% if ac.datum_preddeadline %}
<div class="zadani_azad_termin">
Termín odeslání {{ac.poradi}}. série pro otištění v dlaším čísle:
{{ac.datum_preddeadline}}
</div>
<span class="datum">{{ac.datum_preddeadline}}</span> pro otištění v dalším čísle<br>
{% endif %}
{% if ac.datum_deadline %}
<div class="zadani_azad_termin">
Termín odeslání {{ac.poradi}}. série: {{ac.datum_deadline}}
</div>
<span class="datum">{{ac.datum_deadline}}</span> definitivní deadline<br>
{% endif %}
</div>
<hr>
{% if ac.titulka_nahled and ac.pdf %}
<a href="{{ac.pdf.url}}"><img id="azad_obrazek" src="{{ac.titulka_nahled.url}}" alt=Titulní strana {{ac.poradi}}. čísla></img></a>
{% endif %}

11
seminar/treelib.py

@ -217,6 +217,17 @@ def get_prev_node_of_type(node, type):
return current
return None
def get_upper_node_of_type(node, type):
# vrací první vyšší node daného typu (ignoruje sourozence)
if node is None:
return
current = node
while get_parent(current) is not None:
current = get_parent(current)
if isinstance(current, type):
return current
return None
# Exception, kterou některé metody při špatném použití mohou házet

147
seminar/views/views_all.py

@ -707,15 +707,24 @@ def hlavni_problemy_rocniku(rocnik, jen_verejne=True):
return hlavni_problemy
def hlavni_problemy_cisla(cislo):
""" Vrátí seznam všech problémů s body v daném čísle, které již nemají nadproblém. """
hodnoceni = cislo.hodnoceni.select_related('problem', 'reseni').all()
def problemy_cisla(cislo):
""" Vrátí seznam všech problémů s body v daném čísle. """
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]
problemy_set = set(problemy) # chceme každý problém unikátně,
problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí
problemy_set = set(problemy) # chceme každý problém unikátně,
problemy = (list(problemy_set)) # převedení na množinu a zpět to zaručí
return problemy
def hlavni_problemy_cisla(cislo, problemy=None):
""" Vrátí seznam všech problémů s body v daném čísle, které již nemají nadproblém. """
if problemy is None:
problemy = problemy_cisla(cislo)
# hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
@ -730,6 +739,27 @@ def hlavni_problemy_cisla(cislo):
return hlavni_problemy
def podproblemy_v_cislu(cislo, problemy=None, hlavni_problemy=None):
""" Vrátí seznam všech problémů s body v daném čísle v poli 'indexovaném' tématy. """
if problemy is None:
problemy = problemy_cisla(cislo)
if hlavni_problemy is None:
hlavni_problemy = hlavni_problemy_cisla(cislo, problemy)
podproblemy = dict((hp.id, []) for hp in hlavni_problemy)
hlavni_problemy = set(hlavni_problemy)
podproblemy[-1] = []
for problem in problemy:
h_problem = hlavni_problem(problem)
if h_problem in hlavni_problemy:
podproblemy[h_problem.id].append(problem)
else:
podproblemy[-1].append(problem)
return podproblemy
def body_resitelu(resitele, za, odjakziva=True):
""" 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.
@ -926,8 +956,8 @@ class RadekVysledkovkyCisla(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, body_odjakziva, rok):
def __init__(self, poradi, resitel, body_problemy_sezn,
body_cislo, body_rocnik, body_odjakziva, rok, body_podproblemy, body_podproblemy_iter):
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_cislo = body_cislo
@ -936,7 +966,9 @@ class RadekVysledkovkyCisla(object):
self.poradi = poradi
self.body_problemy_sezn = body_problemy_sezn
self.titul = resitel.get_titul(body_odjakziva)
self.body_podproblemy = body_podproblemy
self.body_podproblemy_iter = body_podproblemy_iter # TODELETE
def pricti_body(slovnik, resitel, body):
""" Přiřazuje danému řešiteli body do slovníku. """
@ -1025,11 +1057,81 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None):
pricti_body(nadproblem_slovnik, resitel, body)
return hlavni_problemy_slovnik, cislobody
def secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy=None, temata=None):
""" Spočítá u řešitelů body za číslo za úlohy v jednotlivých hlavních problémech (témata)."""
if temata is None:
temata = hlavni_problemy_cisla(cislo)
if podproblemy is None:
podproblemy_v_cislu(cislo, hlavni_problemy=temata)
body_slovnik = {}
for tema in temata:
body_slovnik[tema.id] = {}
for problem in podproblemy[tema.id]:
body_slovnik[tema.id][problem.id] = {}
body_slovnik[-1] = {}
for problem in podproblemy[-1]:
body_slovnik[-1][problem.id] = {}
# zakládání prázdných záznamů pro řešitele
for ar in aktivni_resitele:
for tema in temata:
for problem in podproblemy[tema.id]:
body_slovnik[tema.id][problem.id][ar.id] = ""
for problem in podproblemy[-1]:
body_slovnik[-1][problem.id][ar.id] = ""
temata = set(t.id for t in temata)
# vezmeme všechna řešení s body do daného čísla
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
for reseni in reseni_do_cisla:
# řešení může řešit více problémů
for prob in list(reseni.problem.all()):
nadproblem = hlavni_problem(prob)
if nadproblem.id in temata:
nadproblem_slovnik = body_slovnik[nadproblem.id]
else:
nadproblem_slovnik = body_slovnik[-1]
problem_slovnik = nadproblem_slovnik[prob.id]
# a mít více hodnocení
for hodn in list(reseni.hodnoceni_set.all()):
body = hodn.body
# a mít více řešitelů
for resitel in list(reseni.resitele.all()):
if resitel not in aktivni_resitele:
print("Skipping {}".format(resitel.id))
continue
pricti_body(problem_slovnik, resitel, body)
return body_slovnik
# TODELETE
class FixedIterator:
def next(self):
return self.niter.__next__()
def __init__(self, niter):
self.niter = niter
# TODELETE
def vysledkovka_cisla(cislo, context=None):
if context is None:
context = {}
hlavni_problemy = hlavni_problemy_cisla(cislo)
problemy = problemy_cisla(cislo)
hlavni_problemy = hlavni_problemy_cisla(cislo, problemy)
## 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
@ -1061,6 +1163,10 @@ def vysledkovka_cisla(cislo, context=None):
temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
# získáme body u jednotlivých témat
podproblemy = podproblemy_v_cislu(cislo, problemy, temata_a_spol)
problemy_slovnik = secti_body_za_cislo_podle_temat(cislo, aktivni_resitele, podproblemy, temata_a_spol)
# def not_empty(value):
# return value != ''
#
@ -1070,20 +1176,26 @@ def vysledkovka_cisla(cislo, context=None):
for ar_id in setrizeni_resitele_id:
# získáme seznam bodů za problémy pro daného řešitele
problemy = []
body_problemy = []
body_podproblemy = []
for hp in temata_a_spol:
problemy.append(hlavni_problemy_slovnik[hp.id][ar_id])
body_problemy.append(hlavni_problemy_slovnik[hp.id][ar_id])
body_podproblemy.append([problemy_slovnik[hp.id][it.id][ar_id] for it in podproblemy[hp.id]])
if je_nejake_ostatni:
problemy.append(hlavni_problemy_slovnik[-1][ar_id])
body_problemy.append(hlavni_problemy_slovnik[-1][ar_id])
body_podproblemy.append([problemy_slovnik[-1][it.id][ar_id] for it in podproblemy[-1]])
# vytáhneme informace pro daného řešitele
radek = RadekVysledkovkyCisla(
poradi[i], # pořadí
Resitel.objects.get(id=ar_id), # řešitel (z id)
problemy, # seznam bodů za hlavní problémy čísla
body_problemy, # seznam bodů za hlavní problémy čísla
cislobody[ar_id], # body za číslo
setrizeni_resitele_body[i], # body za ročník (spočítané výše s pořadím)
resitel_odjakzivabody_slov[ar_id], # body odjakživa
cislo.rocnik) # ročník semináře pro zjištění ročníku řešitele
cislo.rocnik,
body_podproblemy, # body všech podproblémů
FixedIterator(body_podproblemy.__iter__()) # TODELETE
) # ročník semináře pro zjištění ročníku řešitele
radky_vysledkovky.append(radek)
i += 1
@ -1092,6 +1204,9 @@ def vysledkovka_cisla(cislo, context=None):
context['radky_vysledkovky'] = radky_vysledkovky
context['problemy'] = temata_a_spol
context['ostatni'] = je_nejake_ostatni
pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]]
context['podproblemy'] = pt
context['podproblemy_iter'] = FixedIterator(pt.__iter__()) # TODELETE
#context['v_cisle_zadane'] = TODO
#context['resene_problemy'] = resene_problemy
return context
@ -1368,7 +1483,7 @@ class ClankyResitelView(generic.ListView):
# FIXME: QuerySet není pole!
def get_queryset(self):
clanky = Clanek.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
clanky = Clanek.objects.filter(stav=Problem.STAV_VYRESENY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
queryset = []
skupiny_clanku = group_by_rocnik(clanky)
for skupina in skupiny_clanku:

Loading…
Cancel
Save