Browse Source

Merge branch 'data_migrations' into test

middleware_test
Pavel "LEdoian" Turinsky 3 years ago
parent
commit
e15425d60a
  1. 4
      Makefile
  2. 249
      data/auth_groups.json
  3. 2441
      data/auth_permissions.json
  4. 512
      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. 35
      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. 94
      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. 31
      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. 88
      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. 587
      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
# Instalace závislostí webu
pip install -r requirements.txt --upgrade
# Po vygenerování testdat spusť ./manage.py loaddata sitetree_new.json, ať máš menu
# Pro synchronizaci flatpages spusť make sync_prod_flatpages
# Po vygenerování testdat spusť ./manage.py loaddata data/*, ať máš menu a další modely
:x
install_venv:
${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

512
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:
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))
return

2
mamweb/settings_common.py

@ -308,4 +308,4 @@ CISLO_IMG_DIR = os.path.join('cislo', 'img')
# 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
SEND_EMAIL_NOTIFICATIONS = True
POSLI_MAILOVOU_NOTIFIKACI = True

55
mamweb/static/css/mamweb.css

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

35
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">
</script>
{# Případné skripty widgetů formulářů #}
{% if form %}
{{form.media}}
{% endif %}
{# script specifický pro stránku #}
{% block script %}{% endblock %}
@ -52,9 +56,8 @@
{% endif %}
<div class="container">
<div class="content-wrapper">
<div class='row'>
<div class='col-md-12'>
<a href='/'>
<div id="title" >M&M - korespondenční seminář a časopis MFF&nbspUK</div>
<div id="header">
@ -65,11 +68,7 @@
<img class="logo-mobile" src="{% static 'images/logo-mobile.svg' %}" />
</div>
</a>
</div>
</div>
<div class='row'>
<div class='col-md-12'>
{# ========= MENU ========== #}
@ -77,8 +76,6 @@
{# ========= MENU MOBILE ========== #}
</div>
</div>
<!--Navbar-->
<nav class="nav-button">
@ -102,25 +99,23 @@
{# ========= END MENU ========== #}
<div class='row'>
<div class='row content'>
<div class='col-md-12'>
<div class='content'>
{% block content %}
{% endblock content %}
</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">
<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>
<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>
<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>
<script src="{% static 'js/bootstrap.js' %}"></script>
<script src="{% static 'js/jquery.jcarousel-core.js' %}" type="text/javascript"></script>

4
mamweb/templates/graph.svg

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

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

2
mamweb/templates/menu.html

@ -3,7 +3,7 @@
{% autoescape off %}
<ul class="menu">
{% 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 %}"
>
<a href="{% sitetree_url for item %}" >

84
seminar/admin.py

@ -1,11 +1,14 @@
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 reversion.admin import VersionAdmin
from django_reverse_admin import ReverseModelAdmin
from solo.admin import SingletonModelAdmin
# Todo: reversion
import seminar.models as m
@ -14,8 +17,6 @@ admin.site.register(m.Skola)
admin.site.register(m.Prijemce)
admin.site.register(m.Rocnik)
admin.site.register(m.Cislo)
admin.site.register(m.Organizator)
admin.site.register(m.Soustredeni)
@admin.register(m.Osoba)
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ů"
def udelej_orgem(self,request,queryset):
org_perm = Permission.objects.filter(codename__exact='org').first()
org_group = Group.objects.get(name='org')
print(queryset)
for o in queryset:
user = o.user
user.user_permissions.add(org_perm)
print(user)
user.groups.add(org_group)
user.is_staff = True
user.save()
org = m.Organizator.objects.create(osoba=o)
org.save()
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)
class ProblemAdmin(PolymorphicParentModelAdmin):
@ -53,36 +62,81 @@ class ProblemAdmin(PolymorphicParentModelAdmin):
m.Uloha,
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)
class TemaAdmin(PolymorphicChildModelAdmin):
class TemaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Tema
show_in_index = True
@admin.register(m.Clanek)
class ClanekAdmin(PolymorphicChildModelAdmin):
class ClanekAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Clanek
show_in_index = True
@admin.register(m.Uloha)
class UlohaAdmin(PolymorphicChildModelAdmin):
class UlohaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Uloha
show_in_index = True
@admin.register(m.Konfera)
class KonferaAdmin(PolymorphicChildModelAdmin):
class KonferaAdmin(ProblemAdminMixin,PolymorphicChildModelAdmin):
base_model = m.Konfera
show_in_index = True
class TextAdminInline(admin.TabularInline):
model = m.Text
formfield_overrides = {
models.TextField: {'widget': widgets.TextInput}
}
exclude = ['text_zkraceny_set','text_zkraceny']
admin.site.register(m.Text)
class ResitelInline(admin.TabularInline):
model = m.Resitel
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):
model = m.PrilohaReseni
@ -92,6 +146,7 @@ admin.site.register(m.PrilohaReseni)
class Reseni_ResiteleInline(admin.TabularInline):
model = m.Reseni_Resitele
@admin.register(m.Reseni)
class ReseniAdmin(ReverseModelAdmin):
base_model = m.Reseni
@ -106,7 +161,6 @@ admin.site.register(m.Hodnoceni)
admin.site.register(m.Pohadka)
admin.site.register(m.Obrazek)
# Polymorfismus pro stromy
# 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 django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
from django.forms import formset_factory
from django.forms.models import inlineformset_factory
from .models import Skola, Resitel, Osoba, Problem
@ -184,7 +185,7 @@ class ProfileEditForm(forms.Form):
max_value=date.today().year+8,
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)
# def clean_username(self):
@ -301,3 +302,91 @@ class NahrajObrazekKTreeNoduForm(forms.ModelForm):
model = m.Obrazek
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])

94
seminar/models.py

@ -18,6 +18,7 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.text import get_valid_filename
from imagekit.models import ImageSpecField, ProcessedImageField
from imagekit.processors import ResizeToFit, Transpose
from django.utils.functional import cached_property
from django_countries.fields import CountryField
from solo.models import SingletonModel
@ -26,11 +27,14 @@ from taggit.managers import TaggableManager
from reversion import revisions as reversion
from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode
from seminar import treelib
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
from polymorphic.models import PolymorphicModel
from django.core.mail import EmailMessage
from seminar.utils import aktivniResitele
class SeminarModelBase(models.Model):
@ -624,9 +628,43 @@ class Cislo(SeminarModelBase):
return None
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):
super().save(*args, **kwargs)
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.
try:
self.cislonode.save()
@ -818,10 +856,11 @@ class Problem(SeminarModelBase,PolymorphicModel):
return self.nazev
# Implicitini implementace, jednotlivé dědící třídy si přepíšou
@cached_property
def kod_v_rocniku(self):
if self.stav == 'zadany':
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 '<Není zadaný>'
@ -829,17 +868,26 @@ class Problem(SeminarModelBase,PolymorphicModel):
# aktuálně podle stavu problému
# FIXME pro některé problémy možná chceme override
# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
# Je to tak správně?
# Je to tak správně? Podle aktuální představy ano.
stav_verejny = False
if self.stav == 'zadany' or self.stav == 'vyreseny':
stav_verejny = True
return stav_verejny
#cislo_verejne = False
#if (self.cislo_zadani and self.cislo_zadani.verejne()):
# cislo_verejne = True
#return (stav_verejny and cislo_verejne)
print("stav_verejny: {}".format(stav_verejny))
cislo_verejne = False
cislonode = self.cislo_node()
if cislonode is None:
# problém nemá vlastní node, veřejnost posuzujeme jen podle stavu
print("empty node")
return stav_verejny
else:
cislo_zadani = cislonode.cislo
if (cislo_zadani and cislo_zadani.verejne()):
print("cislo: {}".format(cislo_zadani))
cislo_verejne = True
print("stav_verejny: {}".format(stav_verejny))
print("cislo_verejne: {}".format(cislo_verejne))
return (stav_verejny and cislo_verejne)
verejne.boolean = True
def verejne_url(self):
@ -875,16 +923,17 @@ class Tema(Problem):
tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,
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)
abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
obrazek = models.ImageField('Obrázek na rozcestník', null=True)
@cached_property
def kod_v_rocniku(self):
if self.stav == 'zadany':
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 '<Není zadaný>'
@ -894,6 +943,16 @@ class Tema(Problem):
for tvcn in self.temavcislenode_set.all():
tvcn.save()
def cislo_node(self):
tema_node_set = self.temavcislenode_set.all()
tema_cisla_vyskyt = []
for tn in tema_node_set:
tema_cisla_vyskyt.append(
treelib.get_upper_node_of_type(tn, CisloNode).cislo)
tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani)
prvni_zadani = tema_cisla_vyskyt[0]
return prvni_zadani.cislonode
class Clanek(Problem):
class Meta:
db_table = 'seminar_clanky'
@ -907,9 +966,12 @@ class Clanek(Problem):
if self.stav == 'zadany':
# Nemělo by být potřeba
# 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 '<Není zadaný>'
def node(self):
return None
class Text(SeminarModelBase):
class Meta:
@ -961,11 +1023,12 @@ class Uloha(Problem):
# UlohaZadaniNode
# UlohaVzorakNode
@cached_property
def kod_v_rocniku(self):
if self.stav == 'zadany':
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
if self.nadproblem:
return self.nadproblem.kod_v_rocniku()+name
return self.nadproblem.kod_v_rocniku+name
return name
return '<Není zadaný>'
@ -983,6 +1046,9 @@ class Uloha(Problem):
# Neexistující *Node nemá smysl aktualizovat.
pass
def cislo_node(self):
zadani_node = self.ulohazadaninode
return treelib.get_upper_node_of_type(zadani_node, CisloNode)
@reversion.register(ignore_duplicates=True)
class Reseni(SeminarModelBase):
@ -1273,6 +1339,8 @@ class Konfera(Problem):
def __str__(self):
return "{}: ({})".format(self.nazev, self.soustredeni)
def cislo_node(self):
return None
# Vazebna tabulka. Mozna se generuje automaticky.
@reversion.register(ignore_duplicates=True)

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

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

@ -9,11 +9,7 @@
Číslo {{ cislo }}
{% endblock %}{% endblock %}
</h1>
{% 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>
<a href='{{ cislo.rocnik.verejne_url }}'>Zpět na ročník {{ cislo.rocnik }}</a>
{% if v_cisle_zadane %}
<h2>Zadané problémy</h2>
@ -50,12 +46,26 @@
</div>
{% 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>
<div id="app">
<app></app>
</div>
{% render_bundle 'chunk-vendors' %}
{% render_bundle 'vue_app_01' %}
{% endcomment %}
{% if cislo.verejna_vysledkovka %}
@ -74,9 +84,24 @@
<th class='border-r'>#
<th class='border-r'>Jméno
{% for p in problemy %}
<th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a>
<th class='border-r' id="problem{{ 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 %}
{% if ostatni %}<th class='border-r'>Ostatní {% endif %}
{# TODELETE #}
{% for podproblemy in podproblemy_iter.next %}
<th class='border-r podproblem{{ problemy.len }}'><a href="{{ podproblemy.verejne_url }}">{{ podproblemy.kod_v_rocniku }}</a>
{% endfor %}
{# TODELETE #}
<th class='border-r'>Za číslo
<th class='border-r'>Za ročník
<th class='border-r'>Odjakživa
@ -90,6 +115,13 @@
{{ rv.resitel.osoba.plne_jmeno }}
{% for b in rv.body_problemy_sezn %}
<td class='border-r'>{{ b }}
{# TODELETE #}
{% for body_podproblemu in rv.body_podproblemy_iter.next %}
<td class='border-r podproblem{{ forloop.parentloop.counter }}'>{{ body_podproblemu }}
{% endfor %}
{# TODELETE #}
{% endfor %}
<td class='border-r'>{{ rv.body_cislo }}
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
@ -97,6 +129,23 @@
</tr>
{% endfor %}
</table>
{# TODELETE #}
<script>
{% for p in problemy %}
$(".podproblem{{ forloop.counter }}").css("display", "none")
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }})
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end)
function podproblem{{ forloop.counter }}(event) {
$(".podproblem{{ forloop.counter }}").css("display", "")
}
function podproblem{{ forloop.counter }}end(event) {
$(".podproblem{{ forloop.counter }}").css("display", "none")
}
{% endfor %}
</script>
{# TODELETE #}
{% endif %}
{% if not cislo.verejna_vysledkovka and user.je_org %}

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

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

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

@ -8,10 +8,10 @@
</h1>
{% 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>
{% 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 %}
</ul>
{% endfor %}

31
seminar/templates/seminar/clanky/resitelske_clanky.html

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

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

@ -2,6 +2,59 @@
{% 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šitelé: {{ object.resitele.all | join:", " }}</p>
@ -27,21 +80,33 @@
{% endif %}
{# Hodnocení: #}
{# FIXME: Udělat jako formulář #}
<h3>Hodnocení:</h3>
{% if object.hodnoceni_set.all %}
<table>
<form method=post><table>
{% csrf_token %}
{{ form.management_form }}
<table id="form_set">
<tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr>
{% for h in object.hodnoceni_set.all %}
<tr>
<td>{{ h.problem }}</a></td>
<td>{{ h.body }}</td>
<td>{{ h.cislo_body }}</td></tr>
{% for subform in form %}
<tr class="hodnoceni">
<td>{{ subform.problem }}</td>
<td>{{ subform.body }}</td>
<td>{{ subform.cislo_body }}</td>
<td><input type=button class="smazat_hodnoceni" value="Smazat" id="id_{{subform.prefix}}-jsremove"></td>
</tr>
{% endfor %}
</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 %}

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

@ -4,6 +4,14 @@
{% block content %}
<form method=get action=.>
{{ filtr.resitele }}
{{ filtr.problemy }}
Od: {{ filtr.reseni_od }}
Do: {{ filtr.reseni_do }}
<input type=submit value="→">
</form>
<table>
<tr>
<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 %}
{% block 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>
<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>
<ul>
<li><a href="/korektury/">korekturování</a></li>
<li><a href="/admin/korektury/korekturovanepdf/add/">přidat pdf k opravám</a></li>
</ul>
</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>
</ul>
<hr />
@ -58,6 +59,7 @@
<h2><strong>Soustředění</strong></h2>
<ul>
<li><a href="/admin/seminar/soustredeni/add/">přidat soustředění</a></li>
<li><strong>přednášky</strong>
<ul>

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

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

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

@ -1,8 +1,6 @@
{% extends "base.html" %}
{% load staticfiles %}
{% 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>
{% endblock %}

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

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

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

@ -24,7 +24,7 @@
<div class="tema_pole">
<h3>
<a href='{{ rocnik.verejne_url }}'>Téma {{ tematko.nazev }}</a>
<a href='{{ tematko.verejne_url }}'>Téma {{ tematko.nazev }}</a>
</h3>
<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' %}
{% load humanize %}
@ -7,10 +16,22 @@
{% block content %}
{% if nejblizsi_deadline %}
<hr>
<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>
</div>
<hr>
{% endif %}
<div class=titulnistrana>
@ -29,7 +50,7 @@
<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,
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
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 #}

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

@ -5,11 +5,11 @@
<h1>
{% 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 %}
</h1>
{% if vysledkovka %}
{% if radky_vysledkovky %}
{% include "seminar/vysledkovka_rocnik.html" %}
{% else %}
<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 %}
<div class='mam-org-only'>
<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" %}
{% endwith %}
</div>

88
seminar/templates/seminar/zadani/AktualniZadani.html

@ -5,78 +5,46 @@
{% endblock %}{% endblock %}
{% block content %}
<div>
<div class="stranka_aktualni_zadani">
{% with nastaveni.aktualni_cislo as ac %}
{# Zobrazovani neverejnych zadani jen organizatorum #}
{% 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 %}
<hr>
<div class="zadani_termin">
Termíny pro odeslání řešení {{ac.poradi}}. série:<br>
{% if ac.datum_deadline_soustredeni %}
<span class="datum">{{ac.datum_deadline_soustredeni}}</span> pro účast na soustředění<br>
{% endif %}
{% if ac.zadane_problemy.all %}
{% if ac.datum_deadline_soustredeni %}
<div class="zadani_azad_termin">
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 %}
{% if ac.datum_preddeadline %}
<span class="datum">{{ac.datum_preddeadline}}</span> pro otištění v dalším čísle<br>
{% endif %}
{% if ac.datum_deadline %}
<span class="datum">{{ac.datum_deadline}}</span> definitivní deadline<br>
{% endif %}
{% if ac.pdf %}
<h3>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</h3>
{% 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 %}
</div>
<hr>
{# TODO použít {{problem.kod_v_rocniku}} ? vrací 4.u1 místo 4.1 #}
<h3>{{problem.cislo_zadani.cislo}}.{{problem.kod}} {{problem.nazev}} {{ problem.body_v_zavorce }}</h3>
{% autoescape off %}{{problem.text_zadani}}{% endautoescape %}
<hr>
{% endfor %}
{% empty %}
Aktuálně nejsou zadané žádné úlohy k řešení.
{% endfor %}
{% endif %}
{% if ac.titulka_nahled and ac.pdf %}
<a href="{{ac.pdf.url}}"><img id="azad_obrazek" src="{{ac.titulka_nahled.url}}" alt=Titulní strana {{ac.poradi}}. čísla></a>
{% endif %}
{% if user.je_org and not verejne%}</div>{% endif %}
{% else %}
<h2>Aktuálně nejsou zveřejněny žádné úlohy</h2>
{% endif %}
{% if ac.pdf %}
<h3>Aktuální témata najdete v <a href="{{ac.pdf.url}}">aktuálním čísle v PDF</a>.</h3>
{% 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 %}
<p>Aktuálně nemáme žádná témata.</p>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% else %}
<h2>Aktuálně nejsou zveřejněny žádné úlohy</h2>
{% endif %}
{% endwith %}

12
seminar/testutils.py

@ -581,7 +581,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
rocnik_temata.append(letosni_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. """
# 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.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
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...')
# 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.
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)
insert_last_child(tema_node, uz)
@ -860,7 +864,7 @@ def create_test_data(size = 6, rnd = None):
"MFI", 8)
# 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í
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 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

67
seminar/urls.py

@ -1,7 +1,7 @@
from django.urls import path, include, re_path
from django.contrib.auth.decorators import login_required
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
urlpatterns = [
@ -9,25 +9,25 @@ urlpatterns = [
# path('<int:rocnik>/t<int:tematko>/', views.TematkoView),
# Organizatori
path('co-je-MaM/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'),
path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'),
path('o-nas/organizatori/', views.CojemamOrganizatoriView.as_view(), name='organizatori'),
path('o-nas/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'),
# Archiv
path('archiv/rocniky/', views.ArchivView.as_view(), name="seninar_archiv_rocniky"),
path('archiv/temata/', views.ArchivTemataView.as_view(), name="seninar_archiv_temata"),
path('archiv/rocniky/', views.ArchivView.as_view(), name="seminar_archiv_rocniky"),
path('archiv/temata/', views.ArchivTemataView.as_view(), name="seminar_archiv_temata"),
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('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>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
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/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/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/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'),
#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/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/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/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/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'),
# Soustredeni
@ -57,13 +57,14 @@ urlpatterns = [
),
# Zadani
path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'),
path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'),
#path('zadani/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'),
# path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'),
path('aktualni/zadani/', views.AktualniZadaniView, name='seminar_aktualni_zadani'),
#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'),
# 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'),
# Aesop
@ -123,11 +124,6 @@ urlpatterns = [
org_required(views.soustredeniObalkyView),
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
path(
'org/rozcestnik/',
@ -136,16 +132,16 @@ urlpatterns = [
),
path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'),
path('login/', views.LoginView.as_view(), name='login'),
path('logout/', views.LogoutView.as_view(), name='logout'),
path('prihlasit/', views.LoginView.as_view(), name='login'),
path('odhlasit/', views.LogoutView.as_view(), name='logout'),
path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'),
path('reset_password/', views.PasswordResetView.as_view(), name='reset_password'),
path('change_password/', views.PasswordChangeView.as_view(), name='change_password'),
path('reset_password_done/', views.PasswordResetDoneView.as_view(), name='reset_password_done'),
path('reset_password_confirm/<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/', views.PasswordResetView.as_view(), name='reset_password'),
path('zmena-hesla/', views.PasswordChangeView.as_view(), name='change_password'),
path('reset-hesla/2/', views.PasswordResetDoneView.as_view(), name='reset_password_done'),
path('reset-hesla/potvrzeni/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('reset-hesla/hotovo/', views.PasswordResetCompleteView.as_view(), name='reset_password_complete'),
path(
'resitel_edit',
'resitel/osobni-udaje/',
login_required(views.resitelEditView, login_url='/login/'),
name='seminar_resitel_edit'
),
@ -154,9 +150,9 @@ urlpatterns = [
path('profil/', views.profilView, name='profil'),
# Autocomplete
path('autocomplete/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
path('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/skola/',views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
path('api/autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'),
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/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/<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/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.decorators import permission_required
from html.parser import HTMLParser
from django import views as DjangoViews
from django.contrib.auth.models import AnonymousUser
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)
resitel_required = permission_required('auth.resitel', raise_exception=True)
User = get_user_model()
User.je_org = lambda self: self.has_perm('auth.org')
User.je_resitel = lambda self: self.has_perm('auth.resitel')
AnonymousUser.je_org = lambda self: False
AnonymousUser.je_resitel = lambda self: False
# Není to úplně hezké, ale budeme doufat, že to je funkční...
User.je_org = property(lambda self: self.has_perm('auth.org'))
User.je_resitel = property(lambda self: self.has_perm('auth.resitel'))
AnonymousUser.je_org = False
AnonymousUser.je_resitel = False
class FirstTagParser(HTMLParser):
@ -191,3 +193,92 @@ def aktivniResitele(cislo, pouze_letosni=False):
else:
# spojíme querysety s řešiteli loni a letos do daného čísla
return (resi_v_rocniku(loni) | resi_v_rocniku(letos, cislo)).distinct()
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 django.shortcuts import get_object_or_404
from django.db.models import Q
import seminar.models as m
from .helpers import LoginRequiredAjaxMixin

144
seminar/views/odevzdavatko.py

@ -1,12 +1,22 @@
from django.views.generic import ListView, DetailView
from django.views.generic.base import TemplateView
from django.views.generic import ListView, DetailView, FormView
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
import datetime
import logging
import seminar.models as m
import seminar.forms as f
from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from seminar.utils import aktivniResitele, resi_v_rocniku
logger = logging.getLogger(__name__)
# Co chceme?
# - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení
# - TabulkaOdevzdanychReseniView
@ -30,26 +40,60 @@ class TabulkaOdevzdanychReseniView(ListView):
template_name = 'seminar/odevzdavatko/tabulka.html'
model = m.Hodnoceni
def get_queryset(self):
# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení.
self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
self.resitele = resi_v_rocniku(self.akt_rocnik)
def inicializuj_osy_tabulky(self):
"""Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů"""
# FIXME: jméno metody není vypovídající...
# NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat
# TODO: Prefetches, Select related, ...
self.resitele = m.Resitel.objects.all()
self.problemy = m.Problem.objects.all()
self.reseni = m.Reseni.objects.all()
form = FiltrForm(self.request.GET)
if form.is_valid():
fcd = form.cleaned_data
resitele = fcd["resitele"]
problemy = fcd["problemy"]
reseni_od = fcd["reseni_od"]
reseni_do = fcd["reseni_do"]
else:
resitele = FiltrForm.get_initial_for_field(FormFiltr.resitele, "resitele")
problemy = FiltrForm.get_initial_for_field(FormFiltr.problemy, "problemy")
resitele_od = FiltrForm.get_initial_for_field(FormFiltr.resitele_od, "resitele_od")
resitele_do = FiltrForm.get_initial_for_field(FormFiltr.resitele_do, "resitele_do")
# Filtrujeme!
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci
if resitele == FiltrForm.RESITELE_RELEVANTNI:
logger.warning("Někdo chtěl v tabulce jen relevantní řešitele a měl smůlu :-(")
resitele = FiltrForm.RESITELE_LETOSNI # Fall-through
elif resitele == FiltrForm.RESITELE_LETOSNI:
self.resitele = resi_v_rocniku(aktualni_rocnik)
if problemy == FiltrForm.PROBLEMY_MOJE:
org = m.Organizator.objects.get(osoba__user=self.request.user)
from django.db.models import Q
self.problemy = self.problemy.filter(Q(autor=org)|Q(garant=org)|Q(opravovatele=org), stav=m.Problem.STAV_ZADANY)
elif problemy == FiltrForm.PROBLEMY_LETOSNI:
self.problemy = self.problemy.filter(stav=m.Problem.STAV_ZADANY)
#self.problemy = list(filter(lambda problem: problem.rocnik() == aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník....
# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy.
self.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 = 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
def get_context_data(self, *args, **kwargs):
# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení.
self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
self.resitele = resi_v_rocniku(self.akt_rocnik)
# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy.
self.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic()
# self.resitele, self.reseni a self.problemy jsou již nastavené
ctx = super().get_context_data(*args, **kwargs)
ctx['problemy'] = self.zadane_problemy
ctx['problemy'] = self.problemy
ctx['resitele'] = self.resitele
tabulka = dict()
@ -76,7 +120,7 @@ class TabulkaOdevzdanychReseniView(ListView):
hodnoty = []
for resitel in self.resitele:
resiteluv_radek = []
for problem in self.zadane_problemy:
for problem in self.problemy:
if problem in tabulka and resitel in tabulka[problem]:
resiteluv_radek.append(tabulka[problem][resitel])
else:
@ -84,9 +128,14 @@ class TabulkaOdevzdanychReseniView(ListView):
hodnoty.append(resiteluv_radek)
ctx['radky'] = list(zip(self.resitele, hodnoty))
ctx['filtr'] = FiltrForm(initial=self.request.GET)
# Pro použití hacku na automatické {{form.media}} v template:
ctx['form'] = ctx['filtr']
return ctx
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
template_name = 'seminar/odevzdavatko/seznam.html'
@ -107,12 +156,73 @@ class ReseniProblemuView(ListView):
)
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?
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView):
model = m.Reseni
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í

587
seminar/views/views_all.py

@ -29,6 +29,7 @@ from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm
import seminar.forms as f
import seminar.templatetags.treenodes as tnltt
import seminar.views.views_rest as vr
from seminar.views.vysledkovka import vysledkovka_rocniku, vysledkovka_cisla, body_resitelu
from datetime import timedelta, date, datetime, MAXYEAR
from django.utils import timezone
@ -48,7 +49,7 @@ import csv
import logging
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
#def verejna_temata(rocnik):
@ -63,20 +64,6 @@ from seminar.utils import aktivniResitele, resi_v_rocniku
def get_problemy_k_tematu(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):
template_name = 'seminar/org/obalkovani.html'
@ -198,7 +185,7 @@ class TNLData(object):
return [cls.from_treenode(treenode)]
else:
found = []
for tn in all_children(treenode):
for tn in treelib.all_children(treenode):
result = cls.filter_treenode(tn, predicate)
# Result by v tuhle chvíli měl být seznam TNLDat odpovídající treenodům, jež matchnuly predikát.
for tnl in result:
@ -397,8 +384,8 @@ class ProblemView(generic.DetailView):
return context
class AktualniZadaniView(generic.TemplateView):
template_name = 'seminar/treenode.html'
#class AktualniZadaniView(generic.TemplateView):
# 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...
#class AktualniZadaniView(TreeNodeView):
@ -413,21 +400,15 @@ class AktualniZadaniView(generic.TemplateView):
# context['verejne'] = verejne
# return context
#def AktualniZadaniView(request):
# nastaveni = get_object_or_404(Nastaveni)
# verejne = nastaveni.aktualni_cislo.verejne()
# problemy = Problem.objects.filter(cislo_zadani=nastaveni.aktualni_cislo).filter(stav = 'zadany')
# ulohy = problemy.filter(typ = 'uloha').order_by('kod')
# serialy = problemy.filter(typ = 'serial').order_by('kod')
# 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 AktualniZadaniView(request):
nastaveni = get_object_or_404(Nastaveni)
verejne = nastaveni.aktualni_cislo.verejne()
return render(request, 'seminar/zadani/AktualniZadani.html',
{'nastaveni': nastaveni,
'verejne': verejne,
},
)
def ZadaniTemataView(request):
nastaveni = get_object_or_404(Nastaveni)
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})
#
#def ZadaniAktualniVysledkovkaView(request):
# nastaveni = get_object_or_404(Nastaveni)
# # Aktualni verejna vysledkovka
# vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik)
# # kdyz neni verejna vysledkovka, tak zobraz starou
# if not vysledkovka:
# try:
# minuly_rocnik = Rocnik.objects.get(
# prvni_rok=(nastaveni.aktualni_rocnik.prvni_rok-1))
# vysledkovka = vysledkovka_rocniku(minuly_rocnik)
# except ObjectDoesNotExist:
# pass
# # vysledkovka s neverejnyma vysledkama
# vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False)
# return render(
# request,
# 'seminar/zadani/AktualniVysledkovka.html',
# {
# 'nastaveni': nastaveni,
# 'vysledkovka': vysledkovka,
# 'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi,
# }
# )
def ZadaniAktualniVysledkovkaView(request):
nastaveni = get_object_or_404(Nastaveni)
# Aktualni verejna vysledkovka
vysledkovka = vysledkovka_rocniku(nastaveni.aktualni_rocnik)
cisla = cisla_rocniku(nastaveni.aktualni_rocnik)
# kdyz neni verejna vysledkovka, tak zobraz starou
if not vysledkovka:
try:
minuly_rocnik = Rocnik.objects.get(
prvni_rok=(nastaveni.aktualni_rocnik.prvni_rok-1))
vysledkovka = vysledkovka_rocniku(minuly_rocnik)
cisla = cisla_rocniku(minuly_rocnik)
except ObjectDoesNotExist:
pass
# vysledkovka s neverejnyma vysledkama
vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False)
return render(
request,
'seminar/zadani/AktualniVysledkovka.html',
{
'nastaveni': nastaveni,
'radky_vysledkovky': vysledkovka,
'cisla': cisla,
'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi,
}
)
### Titulni strana
@ -568,6 +552,8 @@ class TitulniStranaView(generic.ListView):
try:
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:
nejblizsi_deadline = (None, None) # neni zadna aktualni deadline
@ -649,236 +635,9 @@ class ArchivView(generic.ListView):
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):
model = Rocnik
@ -888,29 +647,25 @@ class RocnikView(generic.DetailView):
def get_object(self, queryset=None):
if queryset is None:
queryset = self.get_queryset()
rocnik_arg = self.kwargs.get('rocnik')
queryset = queryset.filter(rocnik=rocnik_arg)
try:
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
return get_object_or_404(queryset,rocnik=self.kwargs.get('rocnik'))
def get_context_data(self, **kwargs):
start = time.time()
context = super(RocnikView, self).get_context_data(**kwargs)
# vysledkovka = True zajistí vykreslení,
# zkontrolovat, kdy se má a nemá vykreslovat
context['vysledkovka'] = True
context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False)
if self.request.user.je_org:
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['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"])
context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku(
context["rocnik"], jen_verejne=False)
context['hlavni_problemy_v_rocniku'] = hlavni_problemy_rocniku(context["rocnik"])
context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_rocniku(context["rocnik"], jen_verejne=False)
context['hlavni_problemy_v_rocniku'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"]))
end = time.time()
print("Kontext:", end-start)
return context
@ -940,179 +695,6 @@ class ProblemView(generic.DetailView):
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):
# 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!
def get_queryset(self):
clanky = Clanek.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
clanky = Clanek.objects.filter(stav=Problem.STAV_VYRESENY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
queryset = []
skupiny_clanku = group_by_rocnik(clanky)
for skupina in skupiny_clanku:
@ -1476,36 +1058,6 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
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):
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
u = request.user
osoba_edit = Osoba.objects.get(user=u)
resitel_edit = osoba_edit.resitel
if hasattr(osoba_edit,'resitel'):
resitel_edit = osoba_edit.resitel
else:
resitel_edit = None
user_edit = osoba_edit.user
## Vytvoření slovníku, kterým předvyplním formulář
prefill_1=model_to_dict(user_edit)
prefill_2=model_to_dict(resitel_edit)
if resitel_edit:
prefill_2=model_to_dict(resitel_edit)
prefill_1.update(prefill_2)
prefill_3=model_to_dict(osoba_edit)
prefill_1.update(prefill_2)
prefill_1.update(prefill_3)
form = ProfileEditForm(initial=prefill_1)
## Změna údajů a jejich uložení
@ -1550,17 +1106,18 @@ def resitelEditView(request):
## Neznámá země
msg = "Unknown country {}".format(fcd['stat_text'])
## Změny v řešiteli
resitel_edit.skola = fcd['skola']
resitel_edit.rok_maturity = fcd['rok_maturity']
resitel_edit.zasilat = fcd['zasilat']
resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
if fcd.get('skola'):
if resitel_edit:
## Změny v řešiteli
resitel_edit.skola = fcd['skola']
else:
# Unknown school - log it
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
resitel_edit.save()
resitel_edit.rok_maturity = fcd['rok_maturity']
resitel_edit.zasilat = fcd['zasilat']
resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
if fcd.get('skola'):
resitel_edit.skola = fcd['skola']
else:
# Unknown school - log it
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
resitel_edit.save()
osoba_edit.save()
return formularOKView(request)
else:
@ -1649,7 +1206,7 @@ class LoginView(auth_views.LoginView):
# Přesměrovací URL má být v kontextu:
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['next'] = reverse('titulni_strana')
ctx['next'] = reverse('profil')
return ctx
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