Browse Source

Merge branch 'data_migrations' into test

middleware_test
Pavel "LEdoian" Turinsky 4 years ago
parent
commit
e15425d60a
  1. 4
      Makefile
  2. 249
      data/auth_groups.json
  3. 2441
      data/auth_permissions.json
  4. 302
      data/flat.json
  5. 786
      data/sitetree.json
  6. 816
      data/sitetree_new.json
  7. 12
      fix_json.py
  8. 2
      korektury/views.py
  9. 2
      mamweb/settings_common.py
  10. 2
      mamweb/settings_prod.py
  11. 55
      mamweb/static/css/mamweb.css
  12. BIN
      mamweb/static/images/header/beh.jpg
  13. 31
      mamweb/templates/base.html
  14. 4
      mamweb/templates/graph.svg
  15. 2
      mamweb/templates/menu.html
  16. 84
      seminar/admin.py
  17. 91
      seminar/forms.py
  18. 92
      seminar/models.py
  19. BIN
      seminar/static/images/no-picture.png
  20. 6
      seminar/templates/seminar/archiv/cisla.html
  21. 61
      seminar/templates/seminar/archiv/cislo.html
  22. 11
      seminar/templates/seminar/archiv/rocnik.html
  23. 4
      seminar/templates/seminar/archiv/temata.html
  24. 15
      seminar/templates/seminar/clanky/resitelske_clanky.html
  25. 87
      seminar/templates/seminar/odevzdavatko/detail.html
  26. 8
      seminar/templates/seminar/odevzdavatko/tabulka.html
  27. 2
      seminar/templates/seminar/org/vloz_reseni.html
  28. 4
      seminar/templates/seminar/orgorozcestnik.html
  29. 2
      seminar/templates/seminar/profil/edit.html
  30. 2
      seminar/templates/seminar/profil/nahraj_reseni.html
  31. 2
      seminar/templates/seminar/profil/prihlaska.html
  32. 2
      seminar/templates/seminar/tematka/rozcestnik.html
  33. 25
      seminar/templates/seminar/titulnistrana.html
  34. 6
      seminar/templates/seminar/zadani/AktualniVysledkovka.html
  35. 70
      seminar/templates/seminar/zadani/AktualniZadani.html
  36. 12
      seminar/testutils.py
  37. 11
      seminar/treelib.py
  38. 67
      seminar/urls.py
  39. 99
      seminar/utils.py
  40. 1
      seminar/views/autocomplete.py
  41. 144
      seminar/views/odevzdavatko.py
  42. 559
      seminar/views/views_all.py
  43. 456
      seminar/views/vysledkovka.py

4
Makefile

@ -34,8 +34,8 @@ install_web: venv_check
pip install --upgrade setuptools pip install --upgrade setuptools
# Instalace závislostí webu # Instalace závislostí webu
pip install -r requirements.txt --upgrade pip install -r requirements.txt --upgrade
# Po vygenerování testdat spusť ./manage.py loaddata sitetree_new.json, ať máš menu # Po vygenerování testdat spusť ./manage.py loaddata data/*, ať máš menu a další modely
# Pro synchronizaci flatpages spusť make sync_prod_flatpages :x
install_venv: install_venv:
${VENV} ${VENV_PATH} ${VENV} ${VENV_PATH}

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

302
data/flat.json

File diff suppressed because one or more lines are too long

786
data/sitetree.json

@ -0,0 +1,786 @@
[
{
"fields": {
"alias": "main_menu",
"title": "Hlavní menu"
},
"model": "sitetree.tree",
"pk": 1
},
{
"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": null,
"sort_order": 1,
"title": "Co je M&M",
"tree": 1,
"url": "/co-je-MaM/uvod/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 1
},
{
"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": null,
"sort_order": 2,
"title": "Jak řešit",
"tree": 1,
"url": "/jak-resit/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 2
},
{
"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": null,
"sort_order": 3,
"title": "Aktuální<br/> ročník",
"tree": 1,
"url": "seminar_aktualni_zadani",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 3
},
{
"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": null,
"sort_order": 4,
"title": "Soustředění",
"tree": 1,
"url": "/soustredeni/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 4
},
{
"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": null,
"sort_order": 5,
"title": "Archiv",
"tree": 1,
"url": "seminar_archiv_rocniky",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 5
},
{
"fields": {
"access_guest": true,
"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": null,
"sort_order": 6,
"title": "Přihlásit",
"tree": 1,
"url": "login",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 6
},
{
"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": 1,
"sort_order": 7,
"title": "Úvod",
"tree": 1,
"url": "/co-je-MaM/uvod/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 7
},
{
"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": 1,
"sort_order": 8,
"title": "Organizátoři",
"tree": 1,
"url": "organizatori",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 8
},
{
"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": 1,
"sort_order": 9,
"title": "FAQ",
"tree": 1,
"url": "/co-je-MaM/FAQ/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 9
},
{
"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": 1,
"sort_order": 10,
"title": "Kontakt",
"tree": 1,
"url": "/co-je-MaM/kontakt/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 10
},
{
"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": 2,
"sort_order": 11,
"title": "Témata",
"tree": 1,
"url": "/jak-resit/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 11
},
{
"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": 2,
"sort_order": 12,
"title": "Jak psát příspěvek",
"tree": 1,
"url": "/jak-resit/jak-psat-prispevek/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 12
},
{
"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": 2,
"sort_order": 13,
"title": "Odměny",
"tree": 1,
"url": "/co-je-MaM/odmeny/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 13
},
{
"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": 33,
"title": "Výsledková listina",
"tree": 1,
"url": "seminar_aktualni_vysledky",
"urlaspattern": true
},
"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": 4,
"sort_order": 18,
"title": "Úvod",
"tree": 1,
"url": "/soustredeni/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 18
},
{
"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": 4,
"sort_order": 19,
"title": "Připravujeme",
"tree": 1,
"url": "/soustredeni/pripravujeme/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 19
},
{
"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": 4,
"sort_order": 20,
"title": "Proběhlo",
"tree": 1,
"url": "seminar_seznam_soustredeni",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 20
},
{
"fields": {
"access_guest": false,
"access_loggedin": true,
"access_perm_type": 1,
"access_permissions": [],
"access_restricted": false,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": null,
"sort_order": 21,
"title": "Profil",
"tree": 1,
"url": "profil",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 21
},
{
"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": 21,
"sort_order": 23,
"title": "Osobní údaje",
"tree": 1,
"url": "seminar_resitel_edit",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 22
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [
2
],
"access_restricted": true,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 21,
"sort_order": 36,
"title": "Poslat řešení",
"tree": 1,
"url": "seminar_nahraj_reseni",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 23
},
{
"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": 35,
"title": "Témata",
"tree": 1,
"url": "seminar_archiv_temata",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 24
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [
1
],
"access_restricted": true,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": null,
"sort_order": 28,
"title": "HIDDEN",
"tree": 1,
"url": "/korektury/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 28
},
{
"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": 28,
"sort_order": 30,
"title": "Aktuální",
"tree": 1,
"url": "korektury_list",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 30
},
{
"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": 28,
"sort_order": 31,
"title": "Zastaralé",
"tree": 1,
"url": "korektury_stare_list",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 31
},
{
"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": 28,
"sort_order": 32,
"title": "Nápověda",
"tree": 1,
"url": "/korektury/help/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 32
},
{
"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": 15,
"title": "Aktuální číslo",
"tree": 1,
"url": "seminar_aktualni_zadani",
"urlaspattern": true
},
"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": 5,
"sort_order": 24,
"title": "Čísla",
"tree": 1,
"url": "seminar_archiv_rocniky",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 35
},
{
"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": 21,
"sort_order": 22,
"title": "Úvod",
"tree": 1,
"url": "profil",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 36
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [
1
],
"access_restricted": true,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 21,
"sort_order": 37,
"title": "Odevzdaná řešení",
"tree": 1,
"url": "odevzdavatko_tabulka",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 37
},
{
"fields": {
"access_guest": false,
"access_loggedin": true,
"access_perm_type": 1,
"access_permissions": [],
"access_restricted": false,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 21,
"sort_order": 38,
"title": "Odhlásit se",
"tree": 1,
"url": "/logout/",
"urlaspattern": false
},
"model": "sitetree.treeitem",
"pk": 38
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [
1
],
"access_restricted": true,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 21,
"sort_order": 36,
"title": "Nahrát řešení",
"tree": 1,
"url": "seminar_vloz_reseni",
"urlaspattern": true
},
"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
}
]

816
data/sitetree_new.json

@ -1,816 +0,0 @@
[
{
"model": "sitetree.tree",
"pk": 1,
"fields": {
"title": "Hlavní menu",
"alias": "main_menu"
}
},
{
"model": "sitetree.treeitem",
"pk": 1,
"fields": {
"title": "Co je M&M",
"hint": "",
"url": "/co-je-MaM/uvod/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": null,
"sort_order": 1,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 2,
"fields": {
"title": "Jak řešit",
"hint": "",
"url": "/jak-resit/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": null,
"sort_order": 2,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 3,
"fields": {
"title": "Aktuální<br/> ročník",
"hint": "",
"url": "seminar_aktualni_zadani",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": null,
"sort_order": 3,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 4,
"fields": {
"title": "Soustředění",
"hint": "",
"url": "/soustredeni/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": null,
"sort_order": 4,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 5,
"fields": {
"title": "Archiv",
"hint": "",
"url": "seminar_archiv_rocniky",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": null,
"sort_order": 5,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 6,
"fields": {
"title": "Přihlásit",
"hint": "",
"url": "login",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": true,
"access_restricted": false,
"access_perm_type": 1,
"parent": null,
"sort_order": 6,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 7,
"fields": {
"title": "Úvod",
"hint": "",
"url": "/co-je-MaM/uvod/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 1,
"sort_order": 7,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 8,
"fields": {
"title": "Organizátoři",
"hint": "",
"url": "organizatori",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 1,
"sort_order": 8,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 9,
"fields": {
"title": "FAQ",
"hint": "",
"url": "/co-je-MaM/FAQ/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 1,
"sort_order": 9,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 10,
"fields": {
"title": "Kontakt",
"hint": "",
"url": "/co-je-MaM/kontakt/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 1,
"sort_order": 10,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 11,
"fields": {
"title": "Témata",
"hint": "",
"url": "/jak-resit/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 2,
"sort_order": 11,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 12,
"fields": {
"title": "Jak psát příspěvek",
"hint": "",
"url": "/jak-resit/jak-psat-prispevek/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 2,
"sort_order": 12,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 13,
"fields": {
"title": "Odměny",
"hint": "",
"url": "/co-je-MaM/odmeny/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 2,
"sort_order": 13,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 16,
"fields": {
"title": "Výsledková listina",
"hint": "",
"url": "zadani/vysledkova-listina/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 3,
"sort_order": 33,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 17,
"fields": {
"title": "Články",
"hint": "",
"url": "clanky_resitel",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 3,
"sort_order": 34,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 18,
"fields": {
"title": "Úvod",
"hint": "",
"url": "/soustredeni/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 4,
"sort_order": 18,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 19,
"fields": {
"title": "Připravujeme",
"hint": "",
"url": "/soustredeni/pripravujeme/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 4,
"sort_order": 19,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 20,
"fields": {
"title": "Proběhlo",
"hint": "",
"url": "seminar_seznam_soustredeni",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 4,
"sort_order": 20,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 21,
"fields": {
"title": "Profil",
"hint": "",
"url": "profil",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": true,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": null,
"sort_order": 21,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 22,
"fields": {
"title": "Osobní údaje",
"hint": "",
"url": "seminar_resitel_edit",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 21,
"sort_order": 23,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 23,
"fields": {
"title": "Poslat řešení",
"hint": "",
"url": "seminar_nahraj_reseni",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 21,
"sort_order": 36,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 24,
"fields": {
"title": "Témata",
"hint": "",
"url": "seminar_temata",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 5,
"sort_order": 35,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 28,
"fields": {
"title": "HIDDEN",
"hint": "",
"url": "/korektury/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": true,
"access_perm_type": 1,
"parent": null,
"sort_order": 28,
"access_permissions": [
1
]
}
},
{
"model": "sitetree.treeitem",
"pk": 30,
"fields": {
"title": "Aktuální",
"hint": "",
"url": "korektury_list",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 28,
"sort_order": 30,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 31,
"fields": {
"title": "Zastaralé",
"hint": "",
"url": "korektury_stare_list",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 28,
"sort_order": 31,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 32,
"fields": {
"title": "Nápověda",
"hint": "",
"url": "/korektury/help/",
"urlaspattern": false,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 28,
"sort_order": 32,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 33,
"fields": {
"title": "Aktuální číslo",
"hint": "",
"url": "seminar_aktualni_zadani",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 3,
"sort_order": 15,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 34,
"fields": {
"title": "Témata",
"hint": "",
"url": "seminar_temata",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 3,
"sort_order": 17,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 35,
"fields": {
"title": "Čísla",
"hint": "",
"url": "seminar_archiv_rocniky",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 5,
"sort_order": 24,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 36,
"fields": {
"title": "Úvod",
"hint": "",
"url": "profil",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": false,
"access_perm_type": 1,
"parent": 21,
"sort_order": 22,
"access_permissions": [
]
}
},
{
"model": "sitetree.treeitem",
"pk": 37,
"fields": {
"title": "Odevzdaná řešení",
"hint": "",
"url": "odevzdavatko_tabulka",
"urlaspattern": true,
"tree": 1,
"hidden": false,
"alias": null,
"description": "",
"inmenu": true,
"inbreadcrumbs": true,
"insitetree": true,
"access_loggedin": false,
"access_guest": false,
"access_restricted": true,
"access_perm_type": 1,
"parent": 21,
"sort_order": 37,
"access_permissions": [
1
]
}
}
]

12
fix_json.py

@ -0,0 +1,12 @@
#!/usr/bin/python3
import json
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("input", type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('output', type=argparse.FileType('w', encoding='utf-8'))
args = parser.parse_args()
data = json.load(args.input)
json.dump(data, args.output, ensure_ascii=False, sort_keys=True, indent='\t')

2
korektury/views.py

@ -186,7 +186,7 @@ class KorekturyView(generic.TemplateView):
if email: if email:
emails.discard(email) emails.discard(email)
if not settings.SEND_EMAIL_NOTIFICATIONS: if not settings.POSLI_MAILOVOU_NOTIFIKACI:
print("Poslal bych upozornění na tyto adresy: ", " ".join(emails)) print("Poslal bych upozornění na tyto adresy: ", " ".join(emails))
return return

2
mamweb/settings_common.py

@ -308,4 +308,4 @@ CISLO_IMG_DIR = os.path.join('cislo', 'img')
# E-MAIL NOTIFICATIONS # E-MAIL NOTIFICATIONS
SEND_EMAIL_NOTIFICATIONS = False POSLI_MAILOVOU_NOTIFIKACI = False

2
mamweb/settings_prod.py

@ -66,4 +66,4 @@ LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/
# E-MAIL NOTIFICATIONS # E-MAIL NOTIFICATIONS
SEND_EMAIL_NOTIFICATIONS = True POSLI_MAILOVOU_NOTIFIKACI = True

55
mamweb/static/css/mamweb.css

@ -9,10 +9,15 @@ body {
background-color: #fffbf6; background-color: #fffbf6;
min-height: 100%; min-height: 100%;
} }
div.content-wrapper {
padding-bottom: 200px; /* Footer height */
}
div.container { div.container {
width: 970px; width: 970px;
margin: auto; margin: auto;
min-height: 100vh;
position: relative;
} }
.org-logged-in div.container { .org-logged-in div.container {
@ -321,11 +326,7 @@ div.novinky_name {
font-style: italic; font-style: italic;
} }
div.zadani_azad_termin {
text-align: center;
font-size: large;
font-weight: bold
}
/********** /**********
* Footer * Footer
@ -333,6 +334,9 @@ div.zadani_azad_termin {
#footer { #footer {
position: absolute;
bottom: 0;
width: 100%;
background: url("../images/mozaika-footer.svg") no-repeat top center; background: url("../images/mozaika-footer.svg") no-repeat top center;
height: 200px; height: 200px;
background-position: relative; background-position: relative;
@ -353,6 +357,7 @@ div.zadani_azad_termin {
p.license-mobile { p.license-mobile {
display: none; display: none;
margin-bottom: 0px;
} }
/*********************/ /*********************/
@ -390,8 +395,8 @@ input[type="file"] {
border-width:1px; border-width:1px;
border-radius: 5px; border-radius: 5px;
padding:3px; padding:3px;
top:20px; top:50px;
left:20px; left:10px;
} }
.field-with-comment:hover span.field-comment{ .field-with-comment:hover span.field-comment{
@ -763,7 +768,7 @@ div.odpocet {
/*stránka organizátorů*/ /*stránka organizátorů*/
div.seznam_orgu, div.rozcestnik_temat { div.seznam_orgu, div.rozcestnik_temat, div.seznam_archiv {
text-align: center; text-align: center;
padding-bottom: 10px; padding-bottom: 10px;
} }
@ -897,6 +902,38 @@ div.cislo_odkazy ul {
padding: 0px; padding: 0px;
} }
/* aktuální zadání */
.stranka_aktualni_zadani {
text-align: center;
}
#azad_obrazek {
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;
}
#obrazek_cisla_archiv {
text-align: center;
margin: 10px;
}
/* galerie */ /* galerie */
@ -961,8 +998,6 @@ div.cislo_odkazy ul {
} }
/* titulní obrázek hlavní galerie soustředění */ /* titulní obrázek hlavní galerie soustředění */
.titulni_obrazek {
}
.galerie_nahledy{ .galerie_nahledy{
/*margin: 1em 0;*/ /*margin: 1em 0;*/

BIN
mamweb/static/images/header/beh.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

After

Width:  |  Height:  |  Size: 476 KiB

31
mamweb/templates/base.html

@ -31,6 +31,10 @@
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script> </script>
{# Případné skripty widgetů formulářů #}
{% if form %}
{{form.media}}
{% endif %}
{# script specifický pro stránku #} {# script specifický pro stránku #}
{% block script %}{% endblock %} {% block script %}{% endblock %}
@ -52,9 +56,8 @@
{% endif %} {% endif %}
<div class="container"> <div class="container">
<div class="content-wrapper">
<div class='row'>
<div class='col-md-12'>
<a href='/'> <a href='/'>
<div id="title" >M&M - korespondenční seminář a časopis MFF&nbspUK</div> <div id="title" >M&M - korespondenční seminář a časopis MFF&nbspUK</div>
<div id="header"> <div id="header">
@ -65,11 +68,7 @@
<img class="logo-mobile" src="{% static 'images/logo-mobile.svg' %}" /> <img class="logo-mobile" src="{% static 'images/logo-mobile.svg' %}" />
</div> </div>
</a> </a>
</div>
</div>
<div class='row'>
<div class='col-md-12'>
{# ========= MENU ========== #} {# ========= MENU ========== #}
@ -77,8 +76,6 @@
{# ========= MENU MOBILE ========== #} {# ========= MENU MOBILE ========== #}
</div>
</div>
<!--Navbar--> <!--Navbar-->
<nav class="nav-button"> <nav class="nav-button">
@ -102,25 +99,23 @@
{# ========= END MENU ========== #} {# ========= END MENU ========== #}
<div class='row'>
<div class='row content'> <div class='content'>
<div class='col-md-12'>
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
</div> </div>
</div>
</div>
<div class='row'>
<div class='col-md-12'> </div> <!-- content-wrapper -->
<!-- Credit: https://www.freecodecamp.org/news/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/ -->
<div id="footer"> <div id="footer">
<p class="license">S obsahem webu M&amp;M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p> <p class="license">S obsahem webu M&amp;M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p>
</div> </div>
<p class="license-mobile">Korespondenční seminář M&M organizují převážně studenti <a href="https://www.mff.cuni.cz/">MFF UK</a>. Organizaci semináře a vydávání časopisu podporuje <a href="https://jcmf.cz/">Jednota českých matematiků a fyziků</a>. S obsahem webu M&amp;M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p> <p class="license-mobile">Korespondenční seminář M&M organizují převážně studenti <a href="https://www.mff.cuni.cz/">MFF UK</a>. Organizaci semináře a vydávání časopisu podporuje <a href="https://jcmf.cz/">Jednota českých matematiků a fyziků</a>. S obsahem webu M&amp;M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p>
</div>
</div>
</div>
<script src="{% static 'js/bootstrap.js' %}"></script> <script src="{% static 'js/bootstrap.js' %}"></script>
<script src="{% static 'js/jquery.jcarousel-core.js' %}" type="text/javascript"></script> <script src="{% static 'js/jquery.jcarousel-core.js' %}" type="text/javascript"></script>

4
mamweb/templates/graph.svg

@ -350,7 +350,7 @@
</a> </a>
<a <a
id="casopis" id="casopis"
href="/archiv/cisla/" href="/archiv/rocniky"
transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)"> transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)">
<g <g
id="g959"> id="g959">
@ -379,7 +379,7 @@
</g> </g>
</a> </a>
<a <a
href="/clanky/uvod/" href="/jak-resit"
id="clanky" id="clanky"
transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)"> transform="matrix(0.70138313,0,0,0.7462289,-192.38886,20.298351)">
<g <g

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

2
mamweb/templates/menu.html

@ -3,7 +3,7 @@
{% autoescape off %} {% autoescape off %}
<ul class="menu"> <ul class="menu">
{% for item in sitetree_items %} {% for item in sitetree_items %}
<li class="{% if item.has_children %}dropdown{% endif %} {% if item.is_current or item.in_current_branch %}active{% endif %}" <li class="dropdown {% if item.is_current or item.in_current_branch %}active{% endif %}"
style="{% if item.title == "HIDDEN" %}display:none{% endif %}" style="{% if item.title == "HIDDEN" %}display:none{% endif %}"
> >
<a href="{% sitetree_url for item %}" > <a href="{% sitetree_url for item %}" >

84
seminar/admin.py

@ -1,11 +1,14 @@
from django.contrib import admin 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
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
from django_reverse_admin import ReverseModelAdmin from django_reverse_admin import ReverseModelAdmin
from solo.admin import SingletonModelAdmin from solo.admin import SingletonModelAdmin
# Todo: reversion # Todo: reversion
import seminar.models as m import seminar.models as m
@ -14,8 +17,6 @@ admin.site.register(m.Skola)
admin.site.register(m.Prijemce) admin.site.register(m.Prijemce)
admin.site.register(m.Rocnik) admin.site.register(m.Rocnik)
admin.site.register(m.Cislo) admin.site.register(m.Cislo)
admin.site.register(m.Organizator)
admin.site.register(m.Soustredeni)
@admin.register(m.Osoba) @admin.register(m.Osoba)
class OsobaAdmin(admin.ModelAdmin): class OsobaAdmin(admin.ModelAdmin):
@ -31,18 +32,26 @@ class OsobaAdmin(admin.ModelAdmin):
synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů" synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů"
def udelej_orgem(self,request,queryset): def udelej_orgem(self,request,queryset):
org_perm = Permission.objects.filter(codename__exact='org').first() org_group = Group.objects.get(name='org')
print(queryset) print(queryset)
for o in queryset: for o in queryset:
user = o.user user = o.user
user.user_permissions.add(org_perm) print(user)
user.groups.add(org_group)
user.is_staff = True user.is_staff = True
user.save() user.save()
org = m.Organizator.objects.create(osoba=o) org = m.Organizator.objects.create(osoba=o)
org.save() org.save()
udelej_orgem.short_description = "Udělej vybraných osob organizátory" udelej_orgem.short_description = "Udělej vybraných osob organizátory"
@admin.register(m.Organizator)
class OrganizatorAdmin(admin.ModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
@admin.register(m.Resitel)
class ResitelAdmin(admin.ModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
ordering = ('osoba__jmeno','osoba__prijmeni')
@admin.register(m.Problem) @admin.register(m.Problem)
class ProblemAdmin(PolymorphicParentModelAdmin): class ProblemAdmin(PolymorphicParentModelAdmin):
@ -53,36 +62,81 @@ class ProblemAdmin(PolymorphicParentModelAdmin):
m.Uloha, m.Uloha,
m.Konfera, m.Konfera,
] ]
# Pokud chceme orezavat na aktualni rocnik, musime do modelu pridat odkaz na rocnik. Zatim bere vse.
search_fields = ['nazev']
# V ProblemAdmin to nejde, protoze se to nepropise do deti
class ProblemAdminMixin(object):
show_in_index = True
autocomplete_fields = ['nadproblem','autor','garant']
filter_horizontal = ['opravovatele']
@admin.register(m.Tema) @admin.register(m.Tema)
class TemaAdmin(PolymorphicChildModelAdmin): class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Tema base_model = m.Tema
show_in_index = True
@admin.register(m.Clanek) @admin.register(m.Clanek)
class ClanekAdmin(PolymorphicChildModelAdmin): class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Clanek base_model = m.Clanek
show_in_index = True
@admin.register(m.Uloha) @admin.register(m.Uloha)
class UlohaAdmin(PolymorphicChildModelAdmin): class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Uloha base_model = m.Uloha
show_in_index = True
@admin.register(m.Konfera) @admin.register(m.Konfera)
class KonferaAdmin(PolymorphicChildModelAdmin): class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Konfera base_model = m.Konfera
show_in_index = True
class TextAdminInline(admin.TabularInline): class TextAdminInline(admin.TabularInline):
model = m.Text model = m.Text
formfield_overrides = {
models.TextField: {'widget': widgets.TextInput}
}
exclude = ['text_zkraceny_set','text_zkraceny'] exclude = ['text_zkraceny_set','text_zkraceny']
admin.site.register(m.Text) admin.site.register(m.Text)
class ResitelInline(admin.TabularInline): class ResitelInline(admin.TabularInline):
model = m.Resitel model = m.Resitel
extra = 1 extra = 1
admin.site.register(m.Resitel)
class SoustredeniUcastniciInline(admin.TabularInline):
model = m.Soustredeni_Ucastnici
extra = 1
fields = ['resitel','poznamka']
autocomplete_fields = ['resitel']
ordering = ['resitel__osoba__jmeno', 'resitel__osoba__prijmeni']
formfield_overrides = {
models.TextField: {'widget': widgets.TextInput}
}
def get_queryset(self,request):
qs = super().get_queryset(request)
return qs.select_related('resitel','soustredeni')
class SoustredeniOrganizatoriInline(admin.TabularInline):
model = m.Soustredeni.organizatori.through
extra = 1
fields = ['organizator','poznamka']
autocomplete_fields = ['organizator']
ordering = ['organizator__osoba__jmeno','organizator__prijmeni']
formfield_overrides = {
models.TextField: {'widget': widgets.TextInput}
}
def get_queryset(self,request):
qs = super().get_queryset(request)
return qs.select_related('organizator', 'soustredeni')
@admin.register(m.Soustredeni)
class SoustredeniAdmin(admin.ModelAdmin):
model = m.Soustredeni
inline_type = 'tabular'
inlines = [SoustredeniUcastniciInline, SoustredeniOrganizatoriInline]
class PrilohaReseniInline(admin.TabularInline): class PrilohaReseniInline(admin.TabularInline):
model = m.PrilohaReseni model = m.PrilohaReseni
@ -92,6 +146,7 @@ admin.site.register(m.PrilohaReseni)
class Reseni_ResiteleInline(admin.TabularInline): class Reseni_ResiteleInline(admin.TabularInline):
model = m.Reseni_Resitele model = m.Reseni_Resitele
@admin.register(m.Reseni) @admin.register(m.Reseni)
class ReseniAdmin(ReverseModelAdmin): class ReseniAdmin(ReverseModelAdmin):
base_model = m.Reseni base_model = m.Reseni
@ -106,7 +161,6 @@ admin.site.register(m.Hodnoceni)
admin.site.register(m.Pohadka) admin.site.register(m.Pohadka)
admin.site.register(m.Obrazek) admin.site.register(m.Obrazek)
# Polymorfismus pro stromy # Polymorfismus pro stromy
# TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html # TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html

91
seminar/forms.py

@ -2,6 +2,7 @@ from django import forms
from dal import autocomplete from dal import autocomplete
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.forms import formset_factory
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from .models import Skola, Resitel, Osoba, Problem from .models import Skola, Resitel, Osoba, Problem
@ -184,7 +185,7 @@ class ProfileEditForm(forms.Form):
max_value=date.today().year+8, max_value=date.today().year+8,
required=True) required=True)
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True) zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=True) zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=False)
spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False)
# def clean_username(self): # def clean_username(self):
@ -301,3 +302,91 @@ class NahrajObrazekKTreeNoduForm(forms.ModelForm):
model = m.Obrazek model = m.Obrazek
fields = ('na_web',) fields = ('na_web',)
class JednoHodnoceniForm(forms.ModelForm):
class Meta:
model = m.Hodnoceni
fields = ('problem', 'body', 'cislo_body')
widgets = {
'problem': autocomplete.ModelSelect2(
url='autocomplete_problem_odevzdatelny', # FIXME: Dovolit i starší?
)
}
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
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik
aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo
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])

92
seminar/models.py

@ -18,6 +18,7 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.text import get_valid_filename from django.utils.text import get_valid_filename
from imagekit.models import ImageSpecField, ProcessedImageField from imagekit.models import ImageSpecField, ProcessedImageField
from imagekit.processors import ResizeToFit, Transpose from imagekit.processors import ResizeToFit, Transpose
from django.utils.functional import cached_property
from django_countries.fields import CountryField from django_countries.fields import CountryField
from solo.models import SingletonModel from solo.models import SingletonModel
@ -26,11 +27,14 @@ from taggit.managers import TaggableManager
from reversion import revisions as reversion from reversion import revisions as reversion
from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode 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é) from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from django.core.mail import EmailMessage
from seminar.utils import aktivniResitele
class SeminarModelBase(models.Model): class SeminarModelBase(models.Model):
@ -624,9 +628,43 @@ class Cislo(SeminarModelBase):
return None return None
return c return c
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__original_verejne = self.verejne_db
def posli_cislo_mailem(self):
# parametry e-mailu
odkaz = self.get_absolute_url()
poslat_z_mailu = 'zadani@mam.mff.cuni.cz'
predmet = 'Vyšlo číslo {}'.format(self.kod())
text_mailu = 'Ahoj,\n' \
'na adrese {} najdete nejnovější číslo.\n' \
'Vaše M&M\n'.format(odkaz)
# Prijemci e-mailu
emaily = map(lambda r: r.osoba.email, filter(lambda r: r.zasilat_cislo_emailem, aktivniResitele(self)))
if not settings.POSLI_MAILOVOU_NOTIFIKACI:
print("Poslal bych upozornění na tyto adresy: ", " ".join(emaily))
return
email = EmailMessage(
subject=predmet,
body=text_mailu,
from_email=poslat_z_mailu,
bcc=list(emaily)
#bcc = příjemci skryté kopie
)
email.send()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)
self.vygeneruj_nahled() self.vygeneruj_nahled()
# Při zveřejnění pošle mail
if self.verejne_db and not self.__original_verejne:
self.posli_cislo_mailem()
# *Node.save() aktualizuje název *Nodu. # *Node.save() aktualizuje název *Nodu.
try: try:
self.cislonode.save() self.cislonode.save()
@ -818,10 +856,11 @@ class Problem(SeminarModelBase,PolymorphicModel):
return self.nazev return self.nazev
# Implicitini implementace, jednotlivé dědící třídy si přepíšou # Implicitini implementace, jednotlivé dědící třídy si přepíšou
@cached_property
def kod_v_rocniku(self): def kod_v_rocniku(self):
if self.stav == 'zadany': if self.stav == 'zadany':
if self.nadproblem: if self.nadproblem:
return self.nadproblem.kod_v_rocniku()+".{}".format(self.kod) return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
return str(self.kod) return str(self.kod)
return '<Není zadaný>' return '<Není zadaný>'
@ -829,17 +868,26 @@ class Problem(SeminarModelBase,PolymorphicModel):
# aktuálně podle stavu problému # aktuálně podle stavu problému
# FIXME pro některé problémy možná chceme override # 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. # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
# Je to tak správně? # Je to tak správně? Podle aktuální představy ano.
stav_verejny = False stav_verejny = False
if self.stav == 'zadany' or self.stav == 'vyreseny': if self.stav == 'zadany' or self.stav == 'vyreseny':
stav_verejny = True stav_verejny = True
return stav_verejny print("stav_verejny: {}".format(stav_verejny))
#cislo_verejne = False
#if (self.cislo_zadani and self.cislo_zadani.verejne()):
# cislo_verejne = True
#return (stav_verejny and cislo_verejne) 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 verejne.boolean = True
def verejne_url(self): def verejne_url(self):
@ -875,16 +923,17 @@ class Tema(Problem):
tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES, tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,
blank=False, default=TEMA_TEMA) blank=False, default=TEMA_TEMA)
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',blank=True, null=True, rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True,
on_delete=models.PROTECT) on_delete=models.PROTECT)
abstrakt = models.TextField('Abstrakt na rozcestník', blank=True) abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
obrazek = models.ImageField('Obrázek na rozcestník', null=True) obrazek = models.ImageField('Obrázek na rozcestník', null=True)
@cached_property
def kod_v_rocniku(self): def kod_v_rocniku(self):
if self.stav == 'zadany': if self.stav == 'zadany':
if self.nadproblem: if self.nadproblem:
return self.nadproblem.kod_v_rocniku()+".t{}".format(self.kod) return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
return "t{}".format(self.kod) return "t{}".format(self.kod)
return '<Není zadaný>' return '<Není zadaný>'
@ -894,6 +943,16 @@ class Tema(Problem):
for tvcn in self.temavcislenode_set.all(): for tvcn in self.temavcislenode_set.all():
tvcn.save() 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 Clanek(Problem):
class Meta: class Meta:
db_table = 'seminar_clanky' db_table = 'seminar_clanky'
@ -907,10 +966,13 @@ class Clanek(Problem):
if self.stav == 'zadany': if self.stav == 'zadany':
# Nemělo by být potřeba # Nemělo by být potřeba
# if self.nadproblem: # if self.nadproblem:
# return self.nadproblem.kod_v_rocniku()+".c{}".format(self.kod) # return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
return "c{}".format(self.kod) return "c{}".format(self.kod)
return '<Není zadaný>' return '<Není zadaný>'
def node(self):
return None
class Text(SeminarModelBase): class Text(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_texty' db_table = 'seminar_texty'
@ -961,11 +1023,12 @@ class Uloha(Problem):
# UlohaZadaniNode # UlohaZadaniNode
# UlohaVzorakNode # UlohaVzorakNode
@cached_property
def kod_v_rocniku(self): def kod_v_rocniku(self):
if self.stav == 'zadany': if self.stav == 'zadany':
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod) name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
if self.nadproblem: if self.nadproblem:
return self.nadproblem.kod_v_rocniku()+name return self.nadproblem.kod_v_rocniku+name
return name return name
return '<Není zadaný>' return '<Není zadaný>'
@ -983,6 +1046,9 @@ class Uloha(Problem):
# Neexistující *Node nemá smysl aktualizovat. # Neexistující *Node nemá smysl aktualizovat.
pass pass
def cislo_node(self):
zadani_node = self.ulohazadaninode
return treelib.get_upper_node_of_type(zadani_node, CisloNode)
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Reseni(SeminarModelBase): class Reseni(SeminarModelBase):
@ -1273,6 +1339,8 @@ class Konfera(Problem):
def __str__(self): def __str__(self):
return "{}: ({})".format(self.nazev, self.soustredeni) return "{}: ({})".format(self.nazev, self.soustredeni)
def cislo_node(self):
return None
# Vazebna tabulka. Mozna se generuje automaticky. # Vazebna tabulka. Mozna se generuje automaticky.
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)

BIN
seminar/static/images/no-picture.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

6
seminar/templates/seminar/archiv/cisla.html

@ -1,12 +1,13 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div>
<h2> <h2>
{% block nadpis1a %}{% block nadpis1b %} {% block nadpis1a %}{% block nadpis1b %}
Archiv čísel Archiv čísel
{% endblock %}{% endblock %} {% endblock %}{% endblock %}
</h2> </h2>
<div class="seznam_archiv">
{% for rocnik, url_png in object_list.items %} {% for rocnik, url_png in object_list.items %}
@ -54,6 +55,7 @@
{% empty %} {% empty %}
Nejsou žádné ročníky Nejsou žádné ročníky
{% endfor %} {% endfor %}
</div> </div>
{% endblock content %} {% endblock content %}

61
seminar/templates/seminar/archiv/cislo.html

@ -9,11 +9,7 @@
Číslo {{ cislo }} Číslo {{ cislo }}
{% endblock %}{% endblock %} {% endblock %}{% endblock %}
</h1> </h1>
<a href='{{ cislo.rocnik.verejne_url }}'>Zpět na ročník {{ cislo.rocnik }}</a>
{% if cislo.pdf %}
<p><a href='{{ cislo.pdf.url }}'>Číslo v pdf</a>
{% endif %}
<p><a href='{{ cislo.rocnik.verejne_url }}'>Ročník {{ cislo.rocnik }}</a>
{% if v_cisle_zadane %} {% if v_cisle_zadane %}
<h2>Zadané problémy</h2> <h2>Zadané problémy</h2>
@ -50,12 +46,26 @@
</div> </div>
{% endif %} {% endif %}
{% if cislo.titulka_nahled %}
<hr>
<div id="obrazek_cisla_archiv">
<a href="{{ cislo.pdf.url }}"><img src="{{ cislo.titulka_nahled.url }}" alt=Titulní strana {{ cislo.poradi }}. čísla></a>
{% elif cislo.pdf %}
<a href='{{ cislo.pdf.url }}'>Číslo v pdf</a>
</div>
<hr>
{% endif %}
{% comment %}
<script id="vuedata" type="application/json">{"treenode":{{cislo.cislonode.id}}}</script> <script id="vuedata" type="application/json">{"treenode":{{cislo.cislonode.id}}}</script>
<div id="app"> <div id="app">
<app></app> <app></app>
</div> </div>
{% render_bundle 'chunk-vendors' %} {% render_bundle 'chunk-vendors' %}
{% render_bundle 'vue_app_01' %} {% render_bundle 'vue_app_01' %}
{% endcomment %}
{% if cislo.verejna_vysledkovka %} {% if cislo.verejna_vysledkovka %}
@ -74,9 +84,24 @@
<th class='border-r'># <th class='border-r'>#
<th class='border-r'>Jméno <th class='border-r'>Jméno
{% for p in problemy %} {% for p in problemy %}
<th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> <th class='border-r' id="problem{{ forloop.counter }}"><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 %} {% endfor %}
{% if ostatni %}<th class='border-r'>Ostatní {% endif %} {% 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 číslo
<th class='border-r'>Za ročník <th class='border-r'>Za ročník
<th class='border-r'>Odjakživa <th class='border-r'>Odjakživa
@ -90,6 +115,13 @@
{{ rv.resitel.osoba.plne_jmeno }} {{ rv.resitel.osoba.plne_jmeno }}
{% for b in rv.body_problemy_sezn %} {% for b in rv.body_problemy_sezn %}
<td class='border-r'>{{ b }} <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 %} {% endfor %}
<td class='border-r'>{{ rv.body_cislo }} <td class='border-r'>{{ rv.body_cislo }}
<td class='border-r'><b>{{ rv.body_rocnik }}</b> <td class='border-r'><b>{{ rv.body_rocnik }}</b>
@ -97,6 +129,23 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </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 %} {% endif %}
{% if not cislo.verejna_vysledkovka and user.je_org %} {% if not cislo.verejna_vysledkovka and user.je_org %}

11
seminar/templates/seminar/archiv/rocnik.html

@ -8,15 +8,17 @@
{% endblock %}{% endblock %} {% endblock %}{% endblock %}
</h2> </h2>
{% if temata_v_rocniku %} {% if rocnik.temata %}
<h2>Témata</h2> <h2>Témata</h2>
<ul> <ul>
{% for tema in temata_v_rocniku %} {% for tema in rocnik.temata.all %}
<li>{% if tema.text_zadani %}<a href="{{ tema.verejne_url }}">{% endif %}{{ tema.kod_v_rocniku }}: {{ tema.nazev }}{% if tema.text_zadani %}</a>{% endif %} <li>{#<a href="{{ tema.verejne_url }}">#}{{ tema.nazev }}{#</a>#}
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
<h2>Čísla</h2>
<div class="cisla-v-rocniku"> <div class="cisla-v-rocniku">
{% for c in rocnik.verejna_cisla %} {% for c in rocnik.verejna_cisla %}
<div class="cislo_pole"> <div class="cislo_pole">
@ -32,7 +34,7 @@
{% if c.titulka_nahled %} {% if c.titulka_nahled %}
<img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px> <img src="{{ c.titulka_nahled.url }}" alt="{{ c.kod }}" height=180px>
{% else %} {% else %}
<img src="" alt="no image" height=180px> {% load static %} <img src="{% static 'images/no-picture.png' %}" height=180px alt="no-picture">
{% endif %} {% endif %}
</div> </div>
@ -61,6 +63,7 @@
{% endfor %} {% endfor %}
</div> </div>
{% if vysledkovka %} {% if vysledkovka %}
{% if user.je_org %} {% if user.je_org %}
<div class='mam-org-only'> <div class='mam-org-only'>

4
seminar/templates/seminar/archiv/temata.html

@ -8,10 +8,10 @@
</h1> </h1>
{% for rocnik, temata in rocniky.items %} {% for rocnik, temata in rocniky.items %}
<h2>Ročník {{ rocnik }}</h2> <h2><a href="{{ rocnik.verejne_url }}">Ročník {{ rocnik }}</a></h2>
<ul> <ul>
{% for tema in temata %} {% for tema in temata %}
<li><a href="{{ tema.verejne_url }}"> {{ tema.kod_v_rocniku }}: {{ tema.nazev }} </a></li> <li>{# <a href="{{ tema.verejne_url }}"> #}{{ tema.nazev }}{# </a> #}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endfor %} {% endfor %}

15
seminar/templates/seminar/clanky/resitelske_clanky.html

@ -16,7 +16,20 @@
<ul> <ul>
{% endifchanged %} {% endifchanged %}
<li> <li>
<a href="{{ clanek.verejne_url }}">{{ clanek.nazev }}</a> {% 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 %} {% endwith %}
{% endfor %} {% endfor %}
</ul> </ul>

87
seminar/templates/seminar/odevzdavatko/detail.html

@ -2,6 +2,59 @@
{% block content %} {% block content %}
{# FIXME: Necopypastovat! Tohle je zkopírované ze static/seminar/dynamic_formsets.js #}
<script type='text/javascript'>
// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) {
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
}
// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total >= 1){
btn.closest('tr').remove();
var forms = $('.hodnoceni');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;
}
// Credit: https://simpleit.rocks/python/django/dynamic-add-form-with-add-button-in-django-modelformset-template/
$(document).ready(function(){
$('#pridat_hodnoceni').click(function() {
var form_idx = $('#id_form-TOTAL_FORMS').val();
var new_form = $('#empty_form').html().replace(/__prefix__/g, form_idx);
$('#form_set').append(new_form);
// Newly created form has not the binding between remove button and remove function
// We need to add it manually
$('.smazat_hodnoceni').click(function(){
deleteForm("form",this);
});
$('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
});
$('.smazat_hodnoceni').click(function(){
deleteForm("form",this);
});
});
</script>
<p>Řešené problémy: {{ object.problem.all | join:", " }}</p> <p>Řešené problémy: {{ object.problem.all | join:", " }}</p>
<p>Řešitelé: {{ object.resitele.all | join:", " }}</p> <p>Řešitelé: {{ object.resitele.all | join:", " }}</p>
@ -27,21 +80,33 @@
{% endif %} {% endif %}
{# Hodnocení: #} {# Hodnocení: #}
{# FIXME: Udělat jako formulář #}
<h3>Hodnocení:</h3> <h3>Hodnocení:</h3>
{% if object.hodnoceni_set.all %} <form method=post><table>
<table> {% csrf_token %}
{{ form.management_form }}
<table id="form_set">
<tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr> <tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr>
{% for h in object.hodnoceni_set.all %} {% for subform in form %}
<tr> <tr class="hodnoceni">
<td>{{ h.problem }}</a></td> <td>{{ subform.problem }}</td>
<td>{{ h.body }}</td> <td>{{ subform.body }}</td>
<td>{{ h.cislo_body }}</td></tr> <td>{{ subform.cislo_body }}</td>
<td><input type=button class="smazat_hodnoceni" value="Smazat" id="id_{{subform.prefix}}-jsremove"></td>
</tr>
{% endfor %} {% endfor %}
</table> </table>
{% else %}
<p>Ještě nebylo hodnoceno</p>
{% endif %}
<input type=button id="pridat_hodnoceni" value="Přidat hodnocení">
<input type=submit></form>
<table id="empty_form" style="display: none;">
<tr class="hodnoceni">
<td>{{ form.empty_form.problem }}</td>
<td>{{ form.empty_form.body }}</td>
<td>{{ form.empty_form.cislo_body }}</td>
<td><input type=button class="smazat_hodnoceni" value="Smazat" id="id_{{form.empty_form.prefix}}-jsremove"></td>
</tr>
</table>
{% endblock %} {% endblock %}

8
seminar/templates/seminar/odevzdavatko/tabulka.html

@ -4,6 +4,14 @@
{% block content %} {% block content %}
<form method=get action=.>
{{ filtr.resitele }}
{{ filtr.problemy }}
Od: {{ filtr.reseni_od }}
Do: {{ filtr.reseni_do }}
<input type=submit value="→">
</form>
<table> <table>
<tr> <tr>
<td></td> {# Prázdná buňka v levém horním rohu #} <td></td> {# Prázdná buňka v levém horním rohu #}

2
seminar/templates/seminar/org/vloz_reseni.html

@ -1,4 +1,4 @@
{% extends "seminar/zadani/base.html" %} {% extends "base.html" %}
{% load staticfiles %} {% load staticfiles %}
{% block script %} {% block script %}
<!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!--> <!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!-->

4
seminar/templates/seminar/orgorozcestnik.html

@ -20,13 +20,14 @@
<h2><strong>Tvorba čísla</strong></h2> <h2><strong>Tvorba čísla</strong></h2>
<ul> <ul>
<li><a href="/admin/seminar/problemnavrh/add/"><strong>přidat téma</strong></a></li> <li><a href="/admin/seminar/problem/add/"><strong>přidat téma</strong></a></li>
<li><strong>korektury</strong> <li><strong>korektury</strong>
<ul> <ul>
<li><a href="/korektury/">korekturování</a></li> <li><a href="/korektury/">korekturování</a></li>
<li><a href="/admin/korektury/korekturovanepdf/add/">přidat pdf k opravám</a></li> <li><a href="/admin/korektury/korekturovanepdf/add/">přidat pdf k opravám</a></li>
</ul> </ul>
</li> </li>
<li><a href="{% url 'odevzdavatko_tabulka' %}"><strong>zadávání bodů</strong></a></li>
<li><a href='{{ posledni_cislo_url }}'><strong>poslední vydané číslo </strong></a></li> <li><a href='{{ posledni_cislo_url }}'><strong>poslední vydané číslo </strong></a></li>
</ul> </ul>
<hr /> <hr />
@ -58,6 +59,7 @@
<h2><strong>Soustředění</strong></h2> <h2><strong>Soustředění</strong></h2>
<ul> <ul>
<li><a href="/admin/seminar/soustredeni/add/">přidat soustředění</a></li>
<li><strong>přednášky</strong> <li><strong>přednášky</strong>
<ul> <ul>

2
seminar/templates/seminar/profil/edit.html

@ -2,8 +2,6 @@
{% load staticfiles %} {% load staticfiles %}
{% block script %} {% block script %}
<!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!-->
{{form.media}}
<script src="{% static 'seminar/prihlaska.js' %}"></script> <script src="{% static 'seminar/prihlaska.js' %}"></script>
{% endblock %} {% endblock %}

2
seminar/templates/seminar/profil/nahraj_reseni.html

@ -1,8 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load staticfiles %} {% load staticfiles %}
{% block script %} {% block script %}
<!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!-->
{{form.media}}
<script src="{% static 'seminar/dynamic_formsets.js' %}"></script> <script src="{% static 'seminar/dynamic_formsets.js' %}"></script>
{% endblock %} {% endblock %}

2
seminar/templates/seminar/profil/prihlaska.html

@ -2,8 +2,6 @@
{% load staticfiles %} {% load staticfiles %}
{% block script %} {% block script %}
<!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!-->
{{form.media}}
<script src="{% static 'seminar/prihlaska.js' %}"></script> <script src="{% static 'seminar/prihlaska.js' %}"></script>
{% endblock %} {% endblock %}

2
seminar/templates/seminar/tematka/rozcestnik.html

@ -24,7 +24,7 @@
<div class="tema_pole"> <div class="tema_pole">
<h3> <h3>
<a href='{{ rocnik.verejne_url }}'>Téma {{ tematko.nazev }}</a> <a href='{{ tematko.verejne_url }}'>Téma {{ tematko.nazev }}</a>
</h3> </h3>
<div class="flip-card" id="tema-rozcestnik"> <div class="flip-card" id="tema-rozcestnik">

25
seminar/templates/seminar/titulnistrana.html

@ -1,3 +1,12 @@
<script>
function preddeadline() {
alert("Řešení, která nám přijdou do tohoto deadlinu, se pokusíme opravit co nejdříve, abyste měli ještě šanci si je ještě opravit před definitivním deadlinem čísla.");
}
function sousdeadline() {
alert("Body za řešení, která nám přijdou do tohoto deadlinu, se ještě započítají pro účast na připravovaném soustředění.");
}
</script>
{% extends 'base.html' %} {% extends 'base.html' %}
{% load humanize %} {% load humanize %}
@ -7,10 +16,22 @@
{% block content %} {% block content %}
{% if nejblizsi_deadline %} {% if nejblizsi_deadline %}
<hr>
<div class="odpocet"> <div class="odpocet">
<p><b><big>Do konce <a href="/zadani/aktualni/">odeslání řešení</a> {% if typ_deadline == 'soustredeni' %}(pro účast na soustředění) {% elif typ_deadline == 'preddeadline' %}(pro otištění došlých řešení) {% endif %}zbývá: <p><b><big>Do
{% if typ_deadline == 'soustredeni' %}
<a href="" onClick="sousdeadline()"
title="Body za řešení, která nám přijdou do tohoto deadlinu, se ještě započítají pro účast na připravovaném soustředění.">
deadlinu</a> odeslání <a href="/zadani/aktualni/">řešení
</a> pro účast na soustředění
{% elif typ_deadline == 'preddeadline' %} <a href="" onClick="preddeadline()"
title="Řešení, která nám přijdou do tohoto deadlinu, se pokusíme opravit co nejdříve, abyste měli ještě šanci si je ještě opravit před definitivním deadlinem čísla.">1. deadlinu</a> aktuálního <a href="/zadani/aktualni/">čísla</a>
{% else %} deadlinu aktuálního <a href="/zadani/aktualni/">čísla</a>
{% endif %}zbývá:
{{nejblizsi_deadline|timeuntil}}</big></b></p> {{nejblizsi_deadline|timeuntil}}</big></b></p>
</div> </div>
<hr>
{% endif %} {% endif %}
<div class=titulnistrana> <div class=titulnistrana>
@ -29,7 +50,7 @@
<div> <div>
M&amp;M je korespondenční seminář. Vydáváme časopis a v něm zajímavé podněty k přemýšlení. Ty na ně můžeš reagovat, M&amp;M je korespondenční seminář. Vydáváme časopis a v něm zajímavé podněty k přemýšlení. Ty na ně můžeš reagovat,
experimentovat a objevovat s námi fascinující zákoutí matiky, fyziky a informatiky. experimentovat a objevovat s námi fascinující zákoutí matiky, fyziky a informatiky.
<a href="auth/registrace"> <div class="button"> Zaregistruj se! </div> </a> {# FIXME odkaz #} <a href="prihlaska/?"> <div class="button"> Zaregistruj se! </div> </a>
M&amp;M je taky soutěž. Za svá řešení dostaneš body a můžeš vyhrát zajímavé ceny, dostat se M&amp;M je taky soutěž. Za svá řešení dostaneš body a můžeš vyhrát zajímavé ceny, dostat se
na Matfyz bez přijímaček a především, můžeš s námi jet na skvělé soustředění. na Matfyz bez přijímaček a především, můžeš s námi jet na skvělé soustředění.
<a href="cojemam/odmeny"> <div class="button"> Co můžeš vyhrát? </div> </a> {# FIXME odkaz #} <a href="cojemam/odmeny"> <div class="button"> Co můžeš vyhrát? </div> </a> {# FIXME odkaz #}

6
seminar/templates/seminar/zadani/AktualniVysledkovka.html

@ -5,11 +5,11 @@
<h1> <h1>
{% block nadpis1a %}{% block nadpis1b %} {% block nadpis1a %}{% block nadpis1b %}
Průběžné výsledky {{ vysledkovka.rocnik }}. ročníku Průběžné výsledky {{ rocnik.rocnik }}. ročníku
{% endblock %}{% endblock %} {% endblock %}{% endblock %}
</h1> </h1>
{% if vysledkovka %} {% if radky_vysledkovky %}
{% include "seminar/vysledkovka_rocnik.html" %} {% include "seminar/vysledkovka_rocnik.html" %}
{% else %} {% else %}
<p>V tomto ročníku zatím žádné výsledky nejsou.</p> <p>V tomto ročníku zatím žádné výsledky nejsou.</p>
@ -23,7 +23,7 @@
{% if user.je_org and vysledkovka_s_neverejnymi %} {% if user.je_org and vysledkovka_s_neverejnymi %}
<div class='mam-org-only'> <div class='mam-org-only'>
<h1>Výsledky včetně neveřejných</h1> <h1>Výsledky včetně neveřejných</h1>
{% with vysledkovka_s_neverejnymi as vysledkovka %} {% with vysledkovka_s_neverejnymi as radky_vysledkovky %}
{% include "seminar/vysledkovka_rocnik.html" %} {% include "seminar/vysledkovka_rocnik.html" %}
{% endwith %} {% endwith %}
</div> </div>

70
seminar/templates/seminar/zadani/AktualniZadani.html

@ -5,7 +5,7 @@
{% endblock %}{% endblock %} {% endblock %}{% endblock %}
{% block content %} {% block content %}
<div> <div class="stranka_aktualni_zadani">
{% with nastaveni.aktualni_cislo as ac %} {% with nastaveni.aktualni_cislo as ac %}
@ -13,69 +13,37 @@
{% if user.je_org or verejne %} {% if user.je_org or verejne %}
{% if user.je_org and not verejne %}<div class="mam-org-only">{% endif %} {% if user.je_org and not verejne %}<div class="mam-org-only">{% endif %}
{% if ac.zadane_problemy.all %} <hr>
<div class="zadani_termin">
Termíny pro odeslání řešení {{ac.poradi}}. série:<br>
{% if ac.datum_deadline_soustredeni %} {% if ac.datum_deadline_soustredeni %}
<div class="zadani_azad_termin"> <span class="datum">{{ac.datum_deadline_soustredeni}}</span> pro účast na soustředění<br>
Termín odeslání {{ac.cislo}}. série pro účast na soustředění:
{{ac.datum_deadline_soustredeni}}
</div>
{% endif %}
{% endif %}
{% if ac.zadane_problemy.all %}
<div class="zadani_azad_termin">
Termín odeslání {{ac.cislo}}. série: {{ac.datum_deadline}}
</div>
{% endif %} {% endif %}
{% if ac.datum_preddeadline %}
<span class="datum">{{ac.datum_preddeadline}}</span> pro otištění v dalším čísle<br>
{% endif %}
{% if ac.pdf %} {% if ac.datum_deadline %}
<h3>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</h3> <span class="datum">{{ac.datum_deadline}}</span> definitivní deadline<br>
{% endif %} {% endif %}
<!--Toto jsem zakomentoval, aby se tam nezobrazovala temata, ale text, že vše najdou pouze v PDF-->
{% if False %}
{% for sada in jednorazove_problemy %}
{# podnadpisy, kdyz neni zakomentuje se nadpis #}
{% if not sada %}<!--{% endif %}
<h2>{% cycle 'Úlohy' 'Seriál' %}</h2>
{% if not sada %}-->{% endif %}
{# publikace jednotlivych zadani #}
{% for problem in sada %}
{% for tag in problem.zamereni.names %}
<a name="zam_{{tag}}"></a>
{% endfor %}
{# TODO použít {{problem.kod_v_rocniku}} ? vrací 4.u1 místo 4.1 #} </div>
<h3>{{problem.cislo_zadani.cislo}}.{{problem.kod}} {{problem.nazev}} {{ problem.body_v_zavorce }}</h3>
{% autoescape off %}{{problem.text_zadani}}{% endautoescape %}
<hr> <hr>
{% endfor %}
{% empty %} {% if ac.titulka_nahled and ac.pdf %}
Aktuálně nejsou zadané žádné úlohy k řešení. <a href="{{ac.pdf.url}}"><img id="azad_obrazek" src="{{ac.titulka_nahled.url}}" alt=Titulní strana {{ac.poradi}}. čísla></a>
{% endfor %}
{% endif %} {% endif %}
{% if user.je_org and not verejne%}</div>{% endif %} {% if ac.pdf %}
{% else %} <h3>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</h3>
<h2>Aktuálně nejsou zveřejněny žádné úlohy</h2>
{% endif %} {% endif %}
{% if user.je_org and not verejne%}</div>{% endif %}
{% if False %}
<h2>Témata</h2>
<ul>
{% for problem in temata %}
{# TODO použít {{problem.kod_v_rocniku}} ? vrací t4 místo 4 #}
<li>
<a href="{{problem.verejne_url}}">Téma {{problem.kod}}: {{problem.nazev}}</a>
</li>
{% empty %}
{% if ac.pdf %}
<p>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</p>
{% else %} {% else %}
<p>Aktuálně nemáme žádná témata.</p> <h2>Aktuálně nejsou zveřejněny žádné úlohy</h2>
{% endif %}
{% endfor %}
</ul>
{% endif %} {% endif %}
{% endwith %} {% endwith %}

12
seminar/testutils.py

@ -581,7 +581,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
rocnik_temata.append(letosni_temata) rocnik_temata.append(letosni_temata)
return rocnik_temata return rocnik_temata
def gen_ulohy_tematu(rnd, organizatori, tema, kod, cislo, cislo_se_vzorakem): def gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod, cislo, cislo_se_vzorakem):
""" Generování úlohy k danému tématu. """ """ Generování úlohy k danému tématu. """
# Proměnné pro náhodné generování názvů a zadání. # Proměnné pro náhodné generování názvů a zadání.
@ -627,10 +627,14 @@ def gen_ulohy_tematu(rnd, organizatori, tema, kod, cislo, cislo_se_vzorakem):
uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root) uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root)
uloha.ulohazadaninode = uloha_zadani uloha.ulohazadaninode = uloha_zadani
# Generování řešení a hodnocení k úloze
gen_reseni_ulohy(rnd, [cislo], uloha, len(resitele)//4, 1,
resitele, resitele)
return uloha, uloha_zadani return uloha, uloha_zadani
def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori): def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele):
logger.info('Generuji úlohy k tématům...') logger.info('Generuji úlohy k tématům...')
# Ke každému ročníku si vezmeme příslušná čísla a témata # Ke každému ročníku si vezmeme příslušná čísla a témata
@ -663,7 +667,7 @@ def gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori)
# Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla. # Generujeme 1 až 4 úložky k tomuto témátku do tohoto čísla.
for kod in range(1, rnd.randint(1, 4)): for kod in range(1, rnd.randint(1, 4)):
u, uz = gen_ulohy_tematu(rnd, organizatori, tema, kod, u, uz = gen_ulohy_tematu(rnd, organizatori, resitele, tema, kod,
cislo, cislo_se_vzorakem) cislo, cislo_se_vzorakem)
insert_last_child(tema_node, uz) insert_last_child(tema_node, uz)
@ -860,7 +864,7 @@ def create_test_data(size = 6, rnd = None):
"MFI", 8) "MFI", 8)
# generování úloh k tématům ve všech číslech # generování úloh k tématům ve všech číslech
gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori) gen_ulohy_k_tematum(rnd, rocniky, rocnik_cisla, rocnik_temata, organizatori, resitele)
#generování soustředění #generování soustředění
soustredeni = gen_soustredeni(rnd, resitele, organizatori) soustredeni = gen_soustredeni(rnd, resitele, organizatori)

11
seminar/treelib.py

@ -217,6 +217,17 @@ def get_prev_node_of_type(node, type):
return current return current
return None 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 # Exception, kterou některé metody při špatném použití mohou házet

67
seminar/urls.py

@ -1,7 +1,7 @@
from django.urls import path, include, re_path from django.urls import path, include, re_path
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from . import views, export from . import views, export
from .utils import org_required, resitel_required from .utils import org_required, resitel_required, viewMethodSwitch
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
urlpatterns = [ urlpatterns = [
@ -9,25 +9,25 @@ urlpatterns = [
# path('<int:rocnik>/t<int:tematko>/', views.TematkoView), # path('<int:rocnik>/t<int:tematko>/', views.TematkoView),
# Organizatori # Organizatori
path('co-je-MaM/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'), path('o-nas/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'),
path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), path('o-nas/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'),
# Archiv # Archiv
path('archiv/rocniky/', views.ArchivView.as_view(), name="seninar_archiv_rocniky"), path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"),
path('archiv/temata/', views.ArchivTemataView.as_view(), name="seninar_archiv_temata"), path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"),
path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'),
path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'),
path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'), path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'),
path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'), #path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'),
path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'), #path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'), #path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'),
path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'), #path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'),
path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'), #path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'),
path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'), #path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'),
path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'), #path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'),
path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'), #path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'),
path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'), #path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'),
#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'), #path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'),
# Soustredeni # Soustredeni
@ -57,13 +57,14 @@ urlpatterns = [
), ),
# Zadani # Zadani
path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), # path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'),
path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'), path('aktualni/zadani/', views.AktualniZadaniView, name='seminar_aktualni_zadani'),
#path('zadani/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'), #path('aktualni/temata/', views.ZadaniTemataView, name='seminar_temata'),
path('aktualni/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_aktualni_vysledky'),
path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'),
# Clanky # Clanky
path('clanky/resitel/', views.ClankyResitelView.as_view(), name='clanky_resitel'), path('archiv/clanky/', views.ClankyResitelView.as_view(), name='clanky_resitel'),
#path('clanky/org/', views.ClankyOrganizatorView.as_view(), name='clanky_organizator'), #path('clanky/org/', views.ClankyOrganizatorView.as_view(), name='clanky_organizator'),
# Aesop # Aesop
@ -123,11 +124,6 @@ urlpatterns = [
org_required(views.soustredeniObalkyView), org_required(views.soustredeniObalkyView),
name='seminar_soustredeni_obalky' name='seminar_soustredeni_obalky'
), ),
path(
'org/vloz_body/<int:tema>/',
org_required(views.VlozBodyView.as_view()),
name='seminar_org_vlozbody'
),
# příprava na nestatický orgorozcestník # příprava na nestatický orgorozcestník
path( path(
'org/rozcestnik/', 'org/rozcestnik/',
@ -136,16 +132,16 @@ urlpatterns = [
), ),
path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'),
path('login/', views.LoginView.as_view(), name='login'), path('prihlasit/', views.LoginView.as_view(), name='login'),
path('logout/', views.LogoutView.as_view(), name='logout'), path('odhlasit/', views.LogoutView.as_view(), name='logout'),
path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'), path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'),
path('reset_password/', views.PasswordResetView.as_view(), name='reset_password'), path('reset-hesla/', views.PasswordResetView.as_view(), name='reset_password'),
path('change_password/', views.PasswordChangeView.as_view(), name='change_password'), path('zmena-hesla/', views.PasswordChangeView.as_view(), name='change_password'),
path('reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'), path('reset-hesla/2/', views.PasswordResetDoneView.as_view(), name='reset_password_done'),
path('reset_password_confirm/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), path('reset-hesla/potvrzeni/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('reset_password_complete/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'), path('reset-hesla/hotovo/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'),
path( path(
'resitel_edit', 'resitel/osobni-udaje/',
login_required(views.resitelEditView, login_url='/login/'), login_required(views.resitelEditView, login_url='/login/'),
name='seminar_resitel_edit' name='seminar_resitel_edit'
), ),
@ -154,9 +150,9 @@ urlpatterns = [
path('profil/', views.profilView, name='profil'), path('profil/', views.profilView, name='profil'),
# Autocomplete # Autocomplete
path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), path('api/autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
path('autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'), path('api/autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'),
path('autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), path('api/autocomplete/problem/odevzdatelny',views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'),
path('temp/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'), path('temp/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'),
path('temp/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'), path('temp/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'),
@ -174,8 +170,7 @@ urlpatterns = [
path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
path('temp/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), path('temp/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
path('temp/reseni/<int:pk>', org_required(views.DetailReseniView.as_view()), name='odevzdavatko_detail_reseni'), path('temp/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'),
path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())), path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())),
path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
] ]

99
seminar/utils.py

@ -5,6 +5,7 @@ import datetime
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from html.parser import HTMLParser from html.parser import HTMLParser
from django import views as DjangoViews
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -16,10 +17,11 @@ import seminar.treelib as t
org_required = permission_required('auth.org', raise_exception=True) org_required = permission_required('auth.org', raise_exception=True)
resitel_required = permission_required('auth.resitel', raise_exception=True) resitel_required = permission_required('auth.resitel', raise_exception=True)
User = get_user_model() User = get_user_model()
User.je_org = lambda self: self.has_perm('auth.org') # Není to úplně hezké, ale budeme doufat, že to je funkční...
User.je_resitel = lambda self: self.has_perm('auth.resitel') User.je_org = property(lambda self: self.has_perm('auth.org'))
AnonymousUser.je_org = lambda self: False User.je_resitel = property(lambda self: self.has_perm('auth.resitel'))
AnonymousUser.je_resitel = lambda self: False AnonymousUser.je_org = False
AnonymousUser.je_resitel = False
class FirstTagParser(HTMLParser): class FirstTagParser(HTMLParser):
@ -191,3 +193,92 @@ def aktivniResitele(cislo, pouze_letosni=False):
else: else:
# spojíme querysety s řešiteli loni a letos do daného čísla # spojíme querysety s řešiteli loni a letos do daného čísla
return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct() return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct()
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í view jakožto funkce, takže u class-based views se 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()
def cisla_rocniku(rocnik, jen_verejne=True):
"""
Vrátí všechna čísla daného ročníku.
Parametry:
rocnik (Rocnik): ročník semináře
jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla
Vrátí:
seznam objektů typu Cislo
"""
if jen_verejne:
return rocnik.verejna_cisla()
else:
return rocnik.cisla.all()
def hlavni_problem(problem):
""" Pro daný problém vrátí jeho nejvyšší nadproblém."""
while not(problem.nadproblem == None):
problem = problem.nadproblem
return problem
def problemy_rocniku(rocnik, jen_verejne=True):
return m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body__in = cisla_rocniku(rocnik, jen_verejne))).distinct().select_related('nadproblem').select_related('nadproblem__nadproblem')
def problemy_cisla(cislo):
""" Vrátí seznam všech problémů s body v daném čísle. """
return m.Problem.objects.filter(hodnoceni__in = m.Hodnoceni.objects.filter(cislo_body = cislo)).distinct().select_related('nadproblem').select_related('nadproblem__nadproblem')
def hlavni_problemy_f(problemy=None):
""" Vrátí seznam všech problémů, které již nemají nadproblém. """
# hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
hlavni_problemy = set()
for p in problemy:
hlavni_problemy.add(hlavni_problem(p))
# zunikátnění
hlavni_problemy = list(hlavni_problemy)
hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku) # setřídit podle t1, t2, c3, ...
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_f(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

1
seminar/views/autocomplete.py

@ -1,5 +1,6 @@
from dal import autocomplete from dal import autocomplete
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db.models import Q
import seminar.models as m import seminar.models as m
from .helpers import LoginRequiredAjaxMixin from .helpers import LoginRequiredAjaxMixin

144
seminar/views/odevzdavatko.py

@ -1,12 +1,22 @@
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView, FormView
from django.views.generic.base import TemplateView from django.views.generic.list import MultipleObjectTemplateResponseMixin,MultipleObjectMixin
from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin
from django.shortcuts import redirect, get_object_or_404
from django.urls import reverse
from django.db import transaction
from dataclasses import dataclass from dataclasses import dataclass
import datetime import datetime
import logging
import seminar.models as m 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 from seminar.utils import aktivniResitele, resi_v_rocniku
logger = logging.getLogger(__name__)
# Co chceme? # Co chceme?
# - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení # - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení
# - TabulkaOdevzdanychReseniView # - TabulkaOdevzdanychReseniView
@ -30,26 +40,60 @@ class TabulkaOdevzdanychReseniView(ListView):
template_name = 'seminar/odevzdavatko/tabulka.html' template_name = 'seminar/odevzdavatko/tabulka.html'
model = m.Hodnoceni model = m.Hodnoceni
def get_queryset(self): def inicializuj_osy_tabulky(self):
# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. """Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů"""
self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... # FIXME: jméno metody není vypovídající...
self.resitele = resi_v_rocniku(self.akt_rocnik) # 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")
# 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. # 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.zadane_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 = super().get_queryset()
qs = qs.filter(problem__in=self.zadane_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 return qs
def get_context_data(self, *args, **kwargs): 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.resitele, self.reseni a self.problemy jsou již 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.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic()
ctx = super().get_context_data(*args, **kwargs) ctx = super().get_context_data(*args, **kwargs)
ctx['problemy'] = self.zadane_problemy ctx['problemy'] = self.problemy
ctx['resitele'] = self.resitele ctx['resitele'] = self.resitele
tabulka = dict() tabulka = dict()
@ -76,7 +120,7 @@ class TabulkaOdevzdanychReseniView(ListView):
hodnoty = [] hodnoty = []
for resitel in self.resitele: for resitel in self.resitele:
resiteluv_radek = [] resiteluv_radek = []
for problem in self.zadane_problemy: for problem in self.problemy:
if problem in tabulka and resitel in tabulka[problem]: if problem in tabulka and resitel in tabulka[problem]:
resiteluv_radek.append(tabulka[problem][resitel]) resiteluv_radek.append(tabulka[problem][resitel])
else: else:
@ -84,9 +128,14 @@ class TabulkaOdevzdanychReseniView(ListView):
hodnoty.append(resiteluv_radek) hodnoty.append(resiteluv_radek)
ctx['radky'] = list(zip(self.resitele, hodnoty)) 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 return ctx
class ReseniProblemuView(ListView): # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?
class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View):
model = m.Reseni model = m.Reseni
template_name = 'seminar/odevzdavatko/seznam.html' template_name = 'seminar/odevzdavatko/seznam.html'
@ -107,12 +156,73 @@ class ReseniProblemuView(ListView):
) )
return qs return qs
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
if self.object_list.count() == 1:
jedine_reseni = self.object_list.first()
return redirect(reverse("odevzdavatko_detail_reseni", kwargs={"pk": jedine_reseni.id}))
context = self.get_context_data()
return self.render_to_response(context)
# Kontext automaticky? # Kontext automaticky?
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView): class DetailReseniView(DetailView):
model = m.Reseni model = m.Reseni
template_name = 'seminar/odevzdavatko/detail.html' template_name = 'seminar/odevzdavatko/detail.html'
# To je všechno? Najde se to podle pk...
def aktualni_hodnoceni(self):
reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
result = [] # Slovníky s klíči problem, body, cislo_body -- initial data pro f.OhodnoceniReseniFormSet
for hodn in m.Hodnoceni.objects.filter(reseni=reseni):
result.append(
{"problem": hodn.problem,
"body": hodn.body,
"cislo_body": hodn.cislo_body,
})
return result
def get_context_data(self, **kw):
ctx = super().get_context_data(**kw)
ctx['form'] = f.OhodnoceniReseniFormSet(
initial = self.aktualni_hodnoceni()
)
return ctx
def hodnoceniReseniView(request, pk, *args, **kwargs):
reseni = get_object_or_404(m.Reseni, pk=pk)
template_name = 'seminar/odevzdavatko/detail.html'
form_class = f.OhodnoceniReseniFormSet
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
# Also: https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#django.forms.ModelForm
formset = f.OhodnoceniReseniFormSet(request.POST)
# TODO: Napsat validaci formuláře a formsetu
# TODO: Implementovat větev, kdy formulář validní není.
if formset.is_valid():
with transaction.atomic():
# Smažeme všechna dosavadní hodnocení tohoto řešení
qs = m.Hodnoceni.objects.filter(reseni=reseni)
logger.info(f"Will delete {qs.count()} objects: {qs}")
qs.delete()
# Vyrobíme nová podle formsetu
for form in formset:
problem = form.cleaned_data['problem']
body = form.cleaned_data['body']
cislo_body = form.cleaned_data['cislo_body']
hodnoceni = m.Hodnoceni(
problem=problem,
body=body,
cislo_body=cislo_body,
reseni=reseni,
)
logger.info(f"Creating Hodnoceni: {hodnoceni}")
hodnoceni.save()
return redirect(success_url)
# Přehled všech řešení kvůli debugování # Přehled všech řešení kvůli debugování

559
seminar/views/views_all.py

@ -29,6 +29,7 @@ from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm
import seminar.forms as f import seminar.forms as f
import seminar.templatetags.treenodes as tnltt import seminar.templatetags.treenodes as tnltt
import seminar.views.views_rest as vr import seminar.views.views_rest as vr
from seminar.views.vysledkovka import vysledkovka_rocniku, vysledkovka_cisla, body_resitelu
from datetime import timedelta, date, datetime, MAXYEAR from datetime import timedelta, date, datetime, MAXYEAR
from django.utils import timezone from django.utils import timezone
@ -48,7 +49,7 @@ import csv
import logging import logging
import time import time
from seminar.utils import aktivniResitele, resi_v_rocniku from seminar.utils import aktivniResitele, resi_v_rocniku, problemy_rocniku, cisla_rocniku, hlavni_problemy_f
# ze starého modelu # ze starého modelu
#def verejna_temata(rocnik): #def verejna_temata(rocnik):
@ -63,20 +64,6 @@ from seminar.utils import aktivniResitele, resi_v_rocniku
def get_problemy_k_tematu(tema): def get_problemy_k_tematu(tema):
return Problem.objects.filter(nadproblem = tema) return Problem.objects.filter(nadproblem = tema)
class VlozBodyView(generic.ListView):
template_name = 'seminar/org/vloz_body.html'
def get_queryset(self):
self.tema = get_object_or_404(Problem,id=self.kwargs['tema'])
print(self.tema)
self.problemy = Problem.objects.filter(nadproblem = self.tema)
print(self.problemy)
self.reseni = Reseni.objects.filter(problem__in=self.problemy)
print(self.reseni)
return self.reseni
class ObalkovaniView(generic.ListView): class ObalkovaniView(generic.ListView):
template_name = 'seminar/org/obalkovani.html' template_name = 'seminar/org/obalkovani.html'
@ -198,7 +185,7 @@ class TNLData(object):
return [cls.from_treenode(treenode)] return [cls.from_treenode(treenode)]
else: else:
found = [] found = []
for tn in all_children(treenode): for tn in treelib.all_children(treenode):
result = cls.filter_treenode(tn, predicate) 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. # 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: for tnl in result:
@ -397,8 +384,8 @@ class ProblemView(generic.DetailView):
return context return context
class AktualniZadaniView(generic.TemplateView): #class AktualniZadaniView(generic.TemplateView):
template_name = 'seminar/treenode.html' # template_name = 'seminar/treenode.html'
# TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného... # TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného...
#class AktualniZadaniView(TreeNodeView): #class AktualniZadaniView(TreeNodeView):
@ -413,21 +400,15 @@ class AktualniZadaniView(generic.TemplateView):
# context['verejne'] = verejne # context['verejne'] = verejne
# return context # return context
#def AktualniZadaniView(request): def AktualniZadaniView(request):
# nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
# verejne = nastaveni.aktualni_cislo.verejne() verejne = nastaveni.aktualni_cislo.verejne()
# problemy = Problem.objects.filter(cislo_zadani=nastaveni.aktualni_cislo).filter(stav = 'zadany') return render(request, 'seminar/zadani/AktualniZadani.html',
# ulohy = problemy.filter(typ = 'uloha').order_by('kod') {'nastaveni': nastaveni,
# serialy = problemy.filter(typ = 'serial').order_by('kod') 'verejne': verejne,
# jednorazove_problemy = [ulohy, serialy] },
# return render(request, 'seminar/zadani/AktualniZadani.html', )
# {'nastaveni': nastaveni,
# 'jednorazove_problemy': jednorazove_problemy,
# 'temata': verejna_temata(nastaveni.aktualni_rocnik),
# 'verejne': verejne,
# },
# )
#
def ZadaniTemataView(request): def ZadaniTemataView(request):
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
verejne = nastaveni.aktualni_cislo.verejne() verejne = nastaveni.aktualni_cislo.verejne()
@ -506,29 +487,32 @@ def ZadaniTemataView(request):
# return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik}) # return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik})
# #
#def ZadaniAktualniVysledkovkaView(request): def ZadaniAktualniVysledkovkaView(request):
# nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
# # Aktualni verejna vysledkovka # Aktualni verejna vysledkovka
# vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik) vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik)
# # kdyz neni verejna vysledkovka, tak zobraz starou cisla = cisla_rocniku(nastaveni.aktualni_rocnik)
# if not vysledkovka: # kdyz neni verejna vysledkovka, tak zobraz starou
# try: if not vysledkovka:
# minuly_rocnik = Rocnik.objects.get( try:
# prvni_rok=(nastaveni.aktualni_rocnik.prvni_rok-1)) minuly_rocnik = Rocnik.objects.get(
# vysledkovka = vysledkovka_rocniku(minuly_rocnik) prvni_rok=(nastaveni.aktualni_rocnik.prvni_rok-1))
# except ObjectDoesNotExist: vysledkovka = vysledkovka_rocniku(minuly_rocnik)
# pass cisla = cisla_rocniku(minuly_rocnik)
# # vysledkovka s neverejnyma vysledkama except ObjectDoesNotExist:
# vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False) pass
# return render( # vysledkovka s neverejnyma vysledkama
# request, vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False)
# 'seminar/zadani/AktualniVysledkovka.html', return render(
# { request,
# 'nastaveni': nastaveni, 'seminar/zadani/AktualniVysledkovka.html',
# 'vysledkovka': vysledkovka, {
# 'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi, 'nastaveni': nastaveni,
# } 'radky_vysledkovky': vysledkovka,
# ) 'cisla': cisla,
'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi,
}
)
### Titulni strana ### Titulni strana
@ -568,6 +552,8 @@ class TitulniStranaView(generic.ListView):
try: try:
nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0] nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0]
if nejblizsi_deadline[0] == deadline_soustredeni[0]:
nejblizsi_deadline = deadline_soustredeni
except IndexError: except IndexError:
nejblizsi_deadline = (None, None) # neni zadna aktualni deadline nejblizsi_deadline = (None, None) # neni zadna aktualni deadline
@ -649,235 +635,8 @@ class ArchivView(generic.ListView):
return context return context
### Výsledky
def sloupec_s_poradim(setrizene_body):
"""
Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník
vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.),
podle toho, jak jdou za sebou ve výsledkovce.
Parametr:
setrizene_body (seznam integerů): sestupně setřízená čísla
Výstup:
sloupec_s_poradim (seznam stringů)
"""
# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím
aktualni_poradi = 1
sloupec_s_poradim = []
# seskupíme seznam všech bodů podle hodnot
for index in range(0, len(setrizene_body)):
# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme
# vypsat už jen prázdné místo, než dojdeme na správný řádek
if (index + 1) < aktualni_poradi:
sloupec_s_poradim.append("")
continue
velikost_skupiny = 0
# zjistíme počet po sobě jdoucích stejných hodnot
while setrizene_body[index] == setrizene_body[index + velikost_skupiny]:
velikost_skupiny = velikost_skupiny + 1
# na konci musíme ošetřit přetečení seznamu
if (index + velikost_skupiny) > len(setrizene_body) - 1:
break
# pokud je velikost skupiny 1, vypíšu pořadí
if velikost_skupiny == 1:
sloupec_s_poradim.append("{}.".format(aktualni_poradi))
# pokud je skupina větší, vypíšu rozsah
else:
sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,
aktualni_poradi+velikost_skupiny-1))
# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno
aktualni_poradi = aktualni_poradi + velikost_skupiny
return sloupec_s_poradim
def cisla_rocniku(rocnik, jen_verejne=True):
"""
Vrátí všechna čísla daného ročníku.
Parametry:
rocnik (Rocnik): ročník semináře
jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla
Vrátí:
seznam objektů typu Cislo
"""
if jen_verejne:
return rocnik.verejna_cisla()
else:
return rocnik.cisla.all()
def hlavni_problem(problem):
""" Pro daný problém vrátí jeho nejvyšší nadproblém."""
while not(problem.nadproblem == None):
problem = problem.nadproblem
return problem
def hlavni_problemy_rocniku(rocnik, jen_verejne=True):
""" Pro zadaný ročník vrátí hlavní problémy ročníku,
tj. ty, které nemají nadproblém."""
hlavni_problemy = []
for cislo in cisla_rocniku(rocnik, jen_verejne):
for problem in hlavni_problemy_cisla(cislo):
hlavni_problemy.append(problem)
hlavni_problemy_set = set(hlavni_problemy)
hlavni_problemy = list(hlavni_problemy_set)
hlavni_problemy.sort(key=lambda k:k.kod_v_rocniku()) # setřídit podle pořadí
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()
# 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čí
# hlavní problémy čísla
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
hlavni_problemy = []
for p in problemy:
hlavni_problemy.append(hlavni_problem(p))
# zunikátnění
hlavni_problemy_set = set(hlavni_problemy)
hlavni_problemy = list(hlavni_problemy_set)
hlavni_problemy.sort(key=lambda k: k.kod_v_rocniku()) # setřídit podle t1, t2, c3, ...
return hlavni_problemy
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.
Parametry:
resitele (seznam obsahující položky typu Resitel): aktivní řešitelé
za (Rocnik/Cislo): za co se mají počítat body
(generování starších výsledkovek)
odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník
zadané v "za"
Výstup:
slovník (Resitel.id):body
"""
resitele_id = [r.id for r in resitele]
# Zjistíme, typ objektu v parametru "za"
if isinstance(za, Rocnik):
cislo = None
rocnik = za
rok = rocnik.prvni_rok
elif isinstance(za, Cislo):
cislo = za
rocnik = None
rok = cislo.rocnik.prvni_rok
else:
assert True, "body_resitelu: za není ani číslo ani ročník."
# Kvůli rychlosti používáme sčítáme body už v databázi, viz
# https://docs.djangoproject.com/en/3.0/topics/db/aggregation/,
# sekce Filtering on annotations (protože potřebujeme filtrovat výsledky
# jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i
# za historická čísla.
# Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení,
# který se použije ve výsledném dotazu.
if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla.
# Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků,
# anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen
# pro čísla s pořadím nejvýše stejným, jako má zadané číslo.
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) |
Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok,
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) ))
elif cislo and not odjakziva: # Body se sčítají za dané číslo.
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 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))
else:
assert True, "body_resitelu: Neplatná kombinace za a odjakživa."
# Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů
resitele_s_body = Resitel.objects.filter(id__in=resitele_id).annotate(
body=body_k_zapocteni)
# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník
# indexovaný řešitelským id obsahující body.
# Pokud jsou body None, nahradíme za 0.
slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body}
return slovnik
class RadekVysledkovkyRocniku(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_cisla_sezn, body_rocnik, body_odjakziva, rok):
self.poradi = poradi
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
self.body_cisla_sezn = body_cisla_sezn
self.titul = resitel.get_titul(body_odjakziva)
def setrid_resitele_a_body(slov_resitel_body):
setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body]
setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id]
setrizene_body = [dvojice[1] for dvojice in slov_resitel_body]
return setrizeni_resitele_id, setrizeni_resitele, setrizene_body
def vysledkovka_rocniku(rocnik, jen_verejne=True):
""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve
formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html"
"""
## 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(resi_v_rocniku(rocnik))
cisla = cisla_rocniku(rocnik, jen_verejne)
body_cisla_slov = {}
for cislo in cisla:
# získáme body za číslo
_, cislobody = secti_body_za_cislo(cislo, aktivni_resitele)
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)
# setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší
setrizeni_resitele_id, setrizeni_resitele, 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)
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
i = 0
for ar_id in setrizeni_resitele_id:
# seznam počtu bodů daného řešitele pro jednotlivá čísla
body_cisla_sezn = []
for cislo in cisla:
body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id])
# vytáhneme informace pro daného řešitele
radek = RadekVysledkovkyRocniku(
poradi[i], # pořadí
Resitel.objects.get(id=ar_id), # řešitel (z id)
body_cisla_sezn, # seznam bodů za čísla
setrizene_body[i], # body za ročník (spočítané výše s pořadím)
resitel_odjakzivabody_slov[ar_id], # body odjakživa
rocnik) # ročník semináře pro získání ročníku řešitele
radky_vysledkovky.append(radek)
i += 1
return radky_vysledkovky
class RocnikView(generic.DetailView): class RocnikView(generic.DetailView):
@ -888,29 +647,25 @@ class RocnikView(generic.DetailView):
def get_object(self, queryset=None): def get_object(self, queryset=None):
if queryset is None: if queryset is None:
queryset = self.get_queryset() queryset = self.get_queryset()
rocnik_arg = self.kwargs.get('rocnik')
queryset = queryset.filter(rocnik=rocnik_arg)
try: return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik'))
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
start = time.time()
context = super(RocnikView, self).get_context_data(**kwargs) context = super(RocnikView, self).get_context_data(**kwargs)
# vysledkovka = True zajistí vykreslení, # vysledkovka = True zajistí vykreslení,
# zkontrolovat, kdy se má a nemá vykreslovat # zkontrolovat, kdy se má a nemá vykreslovat
context['vysledkovka'] = True context['vysledkovka'] = True
if self.request.user.je_org:
context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False) context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False)
context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku(context["rocnik"], jen_verejne=False)
context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"], jen_verejne=False))
context['cisla'] = cisla_rocniku(context["rocnik"]) context['cisla'] = cisla_rocniku(context["rocnik"])
context['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"]) context['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"])
context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku( context['hlavni_problemy_v_rocniku'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"]))
context["rocnik"], jen_verejne=False) end = time.time()
context['hlavni_problemy_v_rocniku'] = hlavni_problemy_rocniku(context["rocnik"]) print("Kontext:", end-start)
context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_rocniku(context["rocnik"], jen_verejne=False)
return context return context
@ -940,179 +695,6 @@ class ProblemView(generic.DetailView):
return context return context
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):
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_cislo = body_cislo
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
self.poradi = poradi
self.body_problemy_sezn = body_problemy_sezn
self.titul = resitel.get_titul(body_odjakziva)
def pricti_body(slovnik, resitel, body):
""" Přiřazuje danému řešiteli body do slovníku. """
# testujeme na None (""), pokud je to první řešení
# daného řešitele, předěláme na 0
# (v dalším kroku přičteme reálný počet bodů),
# rozlišujeme tím mezi 0 a neodevzdaným řešením
if slovnik[resitel.id] == "":
slovnik[resitel.id] = 0
slovnik[resitel.id] += body
def secti_body_za_rocnik(za, aktivni_resitele):
""" Spočítá body za ročník (celý nebo do daného čísla),
setřídí je sestupně a vrátí jako seznam.
Parametry:
za (typu Rocnik nebo Cislo) spočítá za ročník, nebo za ročník do
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)
# 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)
return resitel_rocnikbody_sezn
def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None):
""" Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata)."""
# TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé
# pro každý hlavní problém zavedeme slovník s body za daný hlavní problém
# pro jednotlivé řešitele (slovník slovníků hlavních problémů)
if hlavni_problemy is None:
hlavni_problemy = hlavni_problemy_cisla(cislo)
def ne_clanek_ne_konfera(problem):
return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera))
temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
def cosi(problem):
return problem.id
hlavni_problemy_slovnik = {}
for hp in temata_a_spol:
hlavni_problemy_slovnik[hp.id] = {}
hlavni_problemy_slovnik[-1] = {}
# zakládání prázdných záznamů pro řešitele
cislobody = {}
for ar in aktivni_resitele:
# řešitele převedeme na řetězec pomocí unikátního id
cislobody[ar.id] = ""
for hp in temata_a_spol:
slovnik = hlavni_problemy_slovnik[hp.id]
slovnik[ar.id] = ""
hlavni_problemy_slovnik[-1][ar.id] = ""
# 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 ne_clanek_ne_konfera(nadproblem):
nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id]
else:
nadproblem_slovnik = hlavni_problemy_slovnik[-1]
# 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(cislobody, resitel, body)
pricti_body(nadproblem_slovnik, resitel, body)
return hlavni_problemy_slovnik, cislobody
def vysledkovka_cisla(cislo, context=None):
if context is None:
context = {}
hlavni_problemy = hlavni_problemy_cisla(cislo)
## 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))
# získáme body za číslo
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)
# získáme body odjakživa
resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo)
# řešitelé setřídění podle bodů za číslo sestupně
setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn]
setrizeni_resitele = [Resitel.objects.get(id=i) for i in setrizeni_resitele_id]
# spočítáme pořadí řešitelů
setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn]
poradi = sloupec_s_poradim(setrizeni_resitele_body)
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
i = 0
def ne_clanek_ne_konfera(problem):
return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera))
temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
# def not_empty(value):
# return value != ''
#
# je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0
je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0
for ar_id in setrizeni_resitele_id:
# získáme seznam bodů za problémy pro daného řešitele
problemy = []
for hp in temata_a_spol:
problemy.append(hlavni_problemy_slovnik[hp.id][ar_id])
if je_nejake_ostatni:
problemy.append(hlavni_problemy_slovnik[-1][ar_id])
# 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
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
radky_vysledkovky.append(radek)
i += 1
# vytahané informace předáváme do kontextu
context['cislo'] = cislo
context['radky_vysledkovky'] = radky_vysledkovky
context['problemy'] = temata_a_spol
context['ostatni'] = je_nejake_ostatni
#context['v_cisle_zadane'] = TODO
#context['resene_problemy'] = resene_problemy
return context
class CisloView(generic.DetailView): class CisloView(generic.DetailView):
# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf # FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf
@ -1386,7 +968,7 @@ class ClankyResitelView(generic.ListView):
# FIXME: QuerySet není pole! # FIXME: QuerySet není pole!
def get_queryset(self): 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 = [] queryset = []
skupiny_clanku = group_by_rocnik(clanky) skupiny_clanku = group_by_rocnik(clanky)
for skupina in skupiny_clanku: for skupina in skupiny_clanku:
@ -1476,36 +1058,6 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
def resetPasswordView(request):
pass
def loginView(request):
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
user = authenticate(request,
username=form.cleaned_data['username'],
password=form.cleaned_data['password'])
print(form.cleaned_data)
if user is not None:
login(request,user)
return HttpResponseRedirect('/')
else:
return render(request,
'seminar/profil/login.html',
{'form': form, 'login_error': 'Neplatné jméno nebo heslo'})
else:
form = LoginForm()
return render(request, 'seminar/profil/login.html', {'form': form})
def logoutView(request):
form = LoginForm()
if request.user.is_authenticated:
logout(request)
return render(request, 'seminar/profil/login.html', {'form': form, 'login_error': 'Byli jste úspěšně odhlášeni'})
return render(request, 'seminar/profil/login.html', {'form': form})
def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items))) msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items)))
@ -1518,13 +1070,17 @@ def resitelEditView(request):
## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli ## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
u = request.user u = request.user
osoba_edit = Osoba.objects.get(user=u) osoba_edit = Osoba.objects.get(user=u)
if hasattr(osoba_edit,'resitel'):
resitel_edit = osoba_edit.resitel resitel_edit = osoba_edit.resitel
else:
resitel_edit = None
user_edit = osoba_edit.user user_edit = osoba_edit.user
## Vytvoření slovníku, kterým předvyplním formulář ## Vytvoření slovníku, kterým předvyplním formulář
prefill_1=model_to_dict(user_edit) prefill_1=model_to_dict(user_edit)
if resitel_edit:
prefill_2=model_to_dict(resitel_edit) prefill_2=model_to_dict(resitel_edit)
prefill_3=model_to_dict(osoba_edit)
prefill_1.update(prefill_2) prefill_1.update(prefill_2)
prefill_3=model_to_dict(osoba_edit)
prefill_1.update(prefill_3) prefill_1.update(prefill_3)
form = ProfileEditForm(initial=prefill_1) form = ProfileEditForm(initial=prefill_1)
## Změna údajů a jejich uložení ## Změna údajů a jejich uložení
@ -1550,6 +1106,7 @@ def resitelEditView(request):
## Neznámá země ## Neznámá země
msg = "Unknown country {}".format(fcd['stat_text']) msg = "Unknown country {}".format(fcd['stat_text'])
if resitel_edit:
## Změny v řešiteli ## Změny v řešiteli
resitel_edit.skola = fcd['skola'] resitel_edit.skola = fcd['skola']
resitel_edit.rok_maturity = fcd['rok_maturity'] resitel_edit.rok_maturity = fcd['rok_maturity']
@ -1649,7 +1206,7 @@ class LoginView(auth_views.LoginView):
# Přesměrovací URL má být v kontextu: # Přesměrovací URL má být v kontextu:
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['next'] = reverse('titulni_strana') ctx['next'] = reverse('profil')
return ctx return ctx
class LogoutView(auth_views.LogoutView): class LogoutView(auth_views.LogoutView):

456
seminar/views/vysledkovka.py

@ -0,0 +1,456 @@
import seminar.models as m
from django.db.models import Q, Sum, Count
from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu
import time
### Výsledky
def sloupec_s_poradim(setrizene_body):
"""
Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník
vytvoří seznam s pořadími (včetně 3.-5. a pak 2 volná místa atp.),
podle toho, jak jdou za sebou ve výsledkovce.
Parametr:
setrizene_body (seznam integerů): sestupně setřízená čísla
Výstup:
sloupec_s_poradim (seznam stringů)
"""
# ze seznamu obsahujícího setřízené body spočítáme sloupec s pořadím
aktualni_poradi = 1
sloupec_s_poradim = []
# seskupíme seznam všech bodů podle hodnot
for index in range(0, len(setrizene_body)):
# pokud je pořadí větší než číslo řádku, tak jsme vypsali větší rozsah a chceme
# vypsat už jen prázdné místo, než dojdeme na správný řádek
if (index + 1) < aktualni_poradi:
sloupec_s_poradim.append("")
continue
velikost_skupiny = 0
# zjistíme počet po sobě jdoucích stejných hodnot
while setrizene_body[index] == setrizene_body[index + velikost_skupiny]:
velikost_skupiny = velikost_skupiny + 1
# na konci musíme ošetřit přetečení seznamu
if (index + velikost_skupiny) > len(setrizene_body) - 1:
break
# pokud je velikost skupiny 1, vypíšu pořadí
if velikost_skupiny == 1:
sloupec_s_poradim.append("{}.".format(aktualni_poradi))
# pokud je skupina větší, vypíšu rozsah
else:
sloupec_s_poradim.append("{}.–{}.".format(aktualni_poradi,
aktualni_poradi+velikost_skupiny-1))
# zvětšíme aktuální pořadí o tolik, kolik pozic bylo přeskočeno
aktualni_poradi = aktualni_poradi + velikost_skupiny
return sloupec_s_poradim
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.
Parametry:
resitele (seznam obsahující položky typu Resitel): aktivní řešitelé
za (Rocnik/Cislo): za co se mají počítat body
(generování starších výsledkovek)
odjakziva (bool): zda se mají počítat body odjakživa, nebo jen za číslo/ročník
zadané v "za"
Výstup:
slovník (Resitel.id):body
"""
resitele_id = [r.id for r in resitele]
# Zjistíme, typ objektu v parametru "za"
if isinstance(za, m.Rocnik):
cislo = None
rocnik = za
rok = rocnik.prvni_rok
elif isinstance(za, m.Cislo):
cislo = za
rocnik = None
rok = cislo.rocnik.prvni_rok
else:
assert True, "body_resitelu: za není ani číslo ani ročník."
# Kvůli rychlosti používáme sčítáme body už v databázi, viz
# https://docs.djangoproject.com/en/3.0/topics/db/aggregation/,
# sekce Filtering on annotations (protože potřebujeme filtrovat výsledky
# jen do nějakého data, abychom uměli správně nagenerovat výsledkovky i
# za historická čísla.
# Níže se vytváří dotaz na součet bodů za správně vyfiltrovaná hodnocení,
# který se použije ve výsledném dotazu.
if cislo and odjakziva: # Body se sčítají odjakživa do zadaného čísla.
# Vyfiltrujeme všechna hodnocení, která jsou buď ze starších ročníků,
# anebo ze stejného ročníku, jak je zadané číslo, tam ale sčítáme jen
# pro čísla s pořadím nejvýše stejným, jako má zadané číslo.
body_k_zapocteni = Sum('reseni__hodnoceni__body',
filter=( Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok__lt=rok) |
Q(reseni__hodnoceni__cislo_body__rocnik__prvni_rok=rok,
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi) ))
elif cislo and not odjakziva: # Body se sčítají za dané číslo.
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 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))
else:
assert True, "body_resitelu: Neplatná kombinace za a odjakživa."
# Následující řádek přidá ke každému řešiteli údaj ".body" se součtem jejich bodů
resitele_s_body = m.Resitel.objects.filter(id__in=resitele_id).annotate(
body=body_k_zapocteni)
# Teď jen z QuerySetu řešitelů anotovaných body vygenerujeme slovník
# indexovaný řešitelským id obsahující body.
# Pokud jsou body None, nahradíme za 0.
slovnik = {int(res.id) : (res.body if res.body else 0) for res in resitele_s_body}
return slovnik
class RadekVysledkovkyRocniku(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_cisla_sezn, body_rocnik, body_odjakziva, rok):
self.poradi = poradi
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
self.body_cisla_sezn = body_cisla_sezn
self.titul = resitel.get_titul(body_odjakziva)
def setrid_resitele_a_body(slov_resitel_body):
setrizeni_resitele_id = [dvojice[0] for dvojice in slov_resitel_body]
setrizene_body = [dvojice[1] for dvojice in slov_resitel_body]
return setrizeni_resitele_id, setrizene_body
def vysledkovka_rocniku(rocnik, jen_verejne=True):
""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve
formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html"
"""
start = time.time()
## 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(resi_v_rocniku(rocnik))
cisla = cisla_rocniku(rocnik, jen_verejne)
body_cisla_slov = {}
for cislo in cisla:
# získáme body za číslo
_, cislobody = secti_body_za_cislo(cislo, aktivni_resitele)
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)
# 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)
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
i = 0
setrizeni_resitele_dict = {} # Tento slovnik se vyrab
for r in m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba'):
setrizeni_resitele_dict[r.id] = r
for ar_id in setrizeni_resitele_id:
# seznam počtu bodů daného řešitele pro jednotlivá čísla
body_cisla_sezn = []
for cislo in cisla:
body_cisla_sezn.append(body_cisla_slov[cislo.id][ar_id])
# vytáhneme informace pro daného řešitele
radek = RadekVysledkovkyRocniku(
poradi[i], # pořadí
setrizeni_resitele_dict[ar_id], # řešitel (z id)
body_cisla_sezn, # seznam bodů za čísla
setrizene_body[i], # body za ročník (spočítané výše s pořadím)
resitel_odjakzivabody_slov[ar_id], # body odjakživa
rocnik) # ročník semináře pro získání ročníku řešitele
radky_vysledkovky.append(radek)
i += 1
end = time.time()
print("Vysledkovka rocniku",end-start)
return radky_vysledkovky
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, body_podproblemy, body_podproblemy_iter):
self.resitel = resitel
self.rocnik_resitele = resitel.rocnik(rok)
self.body_cislo = body_cislo
self.body_rocnik = body_rocnik
self.body_celkem_odjakziva = body_odjakziva
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. """
# testujeme na None (""), pokud je to první řešení
# daného řešitele, předěláme na 0
# (v dalším kroku přičteme reálný počet bodů),
# rozlišujeme tím mezi 0 a neodevzdaným řešením
if slovnik[resitel.id] == "":
slovnik[resitel.id] = 0
slovnik[resitel.id] += body
def secti_body_za_rocnik(za, aktivni_resitele):
""" Spočítá body za ročník (celý nebo do daného čísla),
setřídí je sestupně a vrátí jako seznam.
Parametry:
za (typu Rocnik nebo Cislo) spočítá za ročník, nebo za ročník do
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)
# 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)
return resitel_rocnikbody_sezn
def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None):
""" Spočítá u řešitelů body za číslo a za jednotlivé hlavní problémy (témata)."""
# TODO setřídit hlavní problémy čísla podle id, ať jsou ve stejném pořadí pokaždé
# pro každý hlavní problém zavedeme slovník s body za daný hlavní problém
# pro jednotlivé řešitele (slovník slovníků hlavních problémů)
print("Scitam cislo",cislo)
if hlavni_problemy is None:
hlavni_problemy = hlavni_problemy_f(problemy_cisla(cislo))
def ne_clanek_ne_konfera(problem):
inst = problem.get_real_instance()
return not(isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera))
temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
hlavni_problemy_slovnik = {}
for hp in temata_a_spol:
hlavni_problemy_slovnik[hp.id] = {}
hlavni_problemy_slovnik[-1] = {}
# zakládání prázdných záznamů pro řešitele
cislobody = {}
for ar in aktivni_resitele:
# řešitele převedeme na řetězec pomocí unikátního id
cislobody[ar.id] = ""
for hp in temata_a_spol:
slovnik = hlavni_problemy_slovnik[hp.id]
slovnik[ar.id] = ""
hlavni_problemy_slovnik[-1][ar.id] = ""
# vezmeme všechna řešení s body do daného čísla
reseni_do_cisla = m.Reseni.objects.prefetch_related('problem', 'resitele',
'hodnoceni_set').filter(hodnoceni__cislo_body=cislo)
start = time.time()
# 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 ne_clanek_ne_konfera(nadproblem):
nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id]
else:
nadproblem_slovnik = hlavni_problemy_slovnik[-1]
# 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(cislobody, resitel, body)
pricti_body(nadproblem_slovnik, resitel, body)
end = time.time()
print("for cykly:", end-start)
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_f(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 = m.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 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 reseni.hodnoceni_set.all():
body = hodn.body
# a mít více řešitelů
for resitel in 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 = {}
problemy = problemy_cisla(cislo)
hlavni_problemy = hlavni_problemy_f(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
aktivni_resitele = list(aktivniResitele(cislo))
# získáme body za číslo
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)
# získáme body odjakživa
resitel_odjakzivabody_slov = body_resitelu(aktivni_resitele, cislo)
# řešitelé setřídění podle bodů za číslo sestupně
setrizeni_resitele_id = [dvojice[0] for dvojice in resitel_rocnikbody_sezn]
# spočítáme pořadí řešitelů
setrizeni_resitele_body = [dvojice[1] for dvojice in resitel_rocnikbody_sezn]
poradi = sloupec_s_poradim(setrizeni_resitele_body)
# vytvoříme jednotlivé sloupce výsledkovky
radky_vysledkovky = []
i = 0
def ne_clanek_ne_konfera(problem):
return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera))
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 != ''
#
# je_nejake_ostatni = any(filter(not_empty, hlavni_problemy_slovnik[-1].values())) > 0
je_nejake_ostatni = len(hlavni_problemy) - len(temata_a_spol) > 0
setrizeni_resitele_slovnik = {}
setrizeni_resitele = m.Resitel.objects.filter(id__in=setrizeni_resitele_id).select_related('osoba')
for r in setrizeni_resitele:
setrizeni_resitele_slovnik[r.id] = r
for ar_id in setrizeni_resitele_id:
# získáme seznam bodů za problémy pro daného řešitele
body_problemy = []
body_podproblemy = []
for hp in temata_a_spol:
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:
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í
setrizeni_resitele_slovnik[ar_id], # řešitel (z id)
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,
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
# vytahané informace předáváme do kontextu
context['cislo'] = cislo
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
Loading…
Cancel
Save