diff --git a/data/sitetree_new.json b/data/sitetree_new.json index f0eb3a9c..6fb2b7f4 100644 --- a/data/sitetree_new.json +++ b/data/sitetree_new.json @@ -1 +1,816 @@ -[{"model": "sitetree.tree", "pk": 1, "fields": {"title": "Hlavn\u00ed 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 \u0159e\u0161it", "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\u00e1ln\u00ed
ro\u010dn\u00edk", "hint": "", "url": "/zadani/aktualni/", "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": 3, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 4, "fields": {"title": "Soust\u0159ed\u011bn\u00ed", "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": "/archiv/rocniky/", "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": 5, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 6, "fields": {"title": "P\u0159ihl\u00e1sit", "hint": "", "url": "/login/", "urlaspattern": false, "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": "\u00davod", "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\u00e1to\u0159i", "hint": "", "url": "/co-je-MaM/organizatori/", "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": 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\u00e9mata", "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\u00e1t p\u0159\u00edsp\u011bvek", "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\u011bny", "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\u00fdsledkov\u00e1 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": "\u010cl\u00e1nky", "hint": "", "url": "/clanky/resitel/", "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": 34, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 18, "fields": {"title": "\u00davod", "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\u0159ipravujeme", "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\u011bhlo", "hint": "", "url": "/soustredeni/probehlo/", "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": 20, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 21, "fields": {"title": "Profil", "hint": "", "url": "/profil/", "urlaspattern": false, "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\u00ed \u00fadaje", "hint": "", "url": "/profil/osobni-udaje", "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": 21, "sort_order": 23, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 23, "fields": {"title": "Poslat \u0159e\u0161en\u00ed", "hint": "", "url": "/odeslat-reseni/", "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": 21, "sort_order": 36, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 24, "fields": {"title": "T\u00e9mata", "hint": "", "url": "/archiv/temata/", "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": 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\u00e1ln\u00ed", "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": false, "access_perm_type": 1, "parent": 28, "sort_order": 30, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 31, "fields": {"title": "Zastaral\u00e9", "hint": "", "url": "/korektury/zastarale/", "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": 31, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 32, "fields": {"title": "N\u00e1pov\u011bda", "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\u00e1ln\u00ed \u010d\u00edslo", "hint": "", "url": "/zadani/aktualni/", "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": 15, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 34, "fields": {"title": "T\u00e9mata", "hint": "", "url": "/zadani/temata/", "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": 17, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 35, "fields": {"title": "\u010c\u00edsla", "hint": "", "url": "/archiv/rocniky/", "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": 5, "sort_order": 24, "access_permissions": []}}, {"model": "sitetree.treeitem", "pk": 36, "fields": {"title": "\u00davod", "hint": "", "url": "/profil/", "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": 21, "sort_order": 22, "access_permissions": []}}] \ No newline at end of file +[ + { + "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í
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 + ] + } + } +] diff --git a/korektury/urls.py b/korektury/urls.py index 2de95918..452ab1e3 100644 --- a/korektury/urls.py +++ b/korektury/urls.py @@ -3,8 +3,8 @@ from seminar.utils import org_required from . import views urlpatterns = [ - path('korektury/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury-list'), - path('korektury/zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury-list'), + path('korektury/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury_list'), + path('korektury/zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury_stare_list'), path('korektury//', org_required(views.KorekturyView.as_view()), name='korektury'), path('korektury/help/', org_required(views.KorekturyHelpView.as_view()), name='korektury-help'), ] diff --git a/mamweb/routers.py b/mamweb/routers.py index f2a172cb..003dd5a6 100644 --- a/mamweb/routers.py +++ b/mamweb/routers.py @@ -4,7 +4,12 @@ from seminar import viewsets as vs router = routers.DefaultRouter() router.register(r'ulohavzoraknode', vs.UlohaVzorakNodeViewSet,basename='ulohavzoraknode') +router.register(r'reseninode', vs.ReseniNodeViewSet,basename='reseninode') router.register(r'text', vs.TextViewSet) router.register(r'textnode', vs.TextNodeViewSet) router.register(r'castnode', vs.CastNodeViewSet) +router.register(r'problem', vs.ProblemViewSet, basename='problem') +router.register(r'uloha', vs.UlohaViewSet, basename='uloha') +router.register(r'reseni', vs.ReseniViewSet, basename='reseni') +router.register(r'ulohazadaninode', vs.UlohaZadaniNodeViewSet) diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index bec16f7f..7a2bf85b 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -295,6 +295,9 @@ LOGGING = { }, } +# Permissions for uploads +FILE_UPLOAD_PERMISSIONS = 0o0644 + # MaM specific SEMINAR_RESENI_DIR = os.path.join('reseni') diff --git a/mamweb/settings_local.py b/mamweb/settings_local.py index de1f0b9b..0aadd27e 100644 --- a/mamweb/settings_local.py +++ b/mamweb/settings_local.py @@ -28,7 +28,7 @@ INTERNAL_IPS = ['127.0.0.1'] TEMPLATES[0]['OPTIONS']['debug'] = True -ALLOWED_HOSTS = ['127.0.0.1'] +ALLOWED_HOSTS = ['127.0.0.1', '192.168.43.34'] # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases diff --git a/mamweb/static/css/mamweb.css b/mamweb/static/css/mamweb.css index 6cfdf1cf..afb5f7fd 100644 --- a/mamweb/static/css/mamweb.css +++ b/mamweb/static/css/mamweb.css @@ -347,6 +347,10 @@ div.zadani_azad_termin { bottom: 0px; } +#footer p.license a { + color: #333; +} + p.license-mobile { display: none; } diff --git a/seminar/admin.py b/seminar/admin.py index e22ecf35..222cafb4 100644 --- a/seminar/admin.py +++ b/seminar/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin +from django.contrib.auth.models import Permission from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from reversion.admin import VersionAdmin @@ -18,7 +19,7 @@ admin.site.register(m.Soustredeni) @admin.register(m.Osoba) class OsobaAdmin(admin.ModelAdmin): - actions = ['synchronizuj_maily'] + actions = ['synchronizuj_maily', 'udelej_orgem'] def synchronizuj_maily(self, request, queryset): for o in queryset: @@ -29,6 +30,20 @@ class OsobaAdmin(admin.ModelAdmin): self.message_user(request, "E-maily synchronizovány.") 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() + print(queryset) + for o in queryset: + user = o.user + user.user_permissions.add(org_perm) + 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.Problem) class ProblemAdmin(PolymorphicParentModelAdmin): base_model = m.Problem diff --git a/seminar/forms.py b/seminar/forms.py index be722e16..a7e0e404 100644 --- a/seminar/forms.py +++ b/seminar/forms.py @@ -10,6 +10,21 @@ import seminar.models as m from datetime import date import logging +# pro přidání políčka do formuláře je potřeba +# - mít v modelu tu položku, kterou chci upravovat +# - přidat do views (prihlaskaView, resitelEditView) +# - přidat do forms +# - includovat do html + +class DateInput(forms.DateInput): + # aby se datum dalo vybírat z kalendáře + input_type = 'date' + +class TelInput(forms.TextInput): + # tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí + input_type = 'tel' + input_pattern="^[+]?[()/0-9. -]{9,}$" + class LoginForm(forms.Form): username = forms.CharField(label='Přihlašovací jméno', max_length=256, @@ -42,8 +57,8 @@ class PrihlaskaForm(forms.Form): pohlavi_muz = forms.ChoiceField(label='Pohlaví', choices = ((True,'muž'),(False,'žena')), required=True) email = forms.EmailField(label='E-mail',max_length=256, required=True) - telefon = forms.CharField(label='Telefon',max_length=256, required=False) - datum_narozeni = forms.DateField(label='Datum narození', required=False) + telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) + datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) ulice = forms.CharField(label='Ulice', max_length=256, required=False) mesto = forms.CharField(label='Město', max_length=256, required=False) psc = forms.CharField(label='PSČ', max_length=32, required=False) @@ -74,6 +89,8 @@ class PrihlaskaForm(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 emailem upozornění na vydání nového čísla', required=True) + gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True) spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) @@ -135,8 +152,8 @@ class ProfileEditForm(forms.Form): pohlavi_muz = forms.ChoiceField(label='Pohlaví', choices = ((True,'muž'),(False,'žena')), required=True) email = forms.EmailField(label='E-mail',max_length=256, required=True) - telefon = forms.CharField(label='Telefon',max_length=256, required=False) - datum_narozeni = forms.DateField(label='Datum narození', required=False) + telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False) + datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False) ulice = forms.CharField(label='Ulice', max_length=256, required=False) mesto = forms.CharField(label='Město', max_length=256, required=False) psc = forms.CharField(label='PSČ', max_length=32, required=False) @@ -167,6 +184,8 @@ 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) + spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False) # def clean_username(self): # err_logger = logging.getLogger('seminar.prihlaska.problem') @@ -234,7 +253,7 @@ class VlozReseniForm(forms.Form): #resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení', # help_text='Seznam autorů řešení', through='Reseni_Resitele') - cas_doruceni = forms.DateField(label="Čas doručení") + cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení") #cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) diff --git a/seminar/migrations/0090_auto_20201110_1958.py b/seminar/migrations/0090_auto_20201110_1958.py new file mode 100644 index 00000000..97949f58 --- /dev/null +++ b/seminar/migrations/0090_auto_20201110_1958.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.12 on 2020-11-10 18:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0089_cislo_datum_preddeadline'), + ] + + operations = [ + migrations.AlterField( + model_name='textnode', + name='text', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.Text', verbose_name='text'), + ), + ] diff --git a/seminar/migrations/0091_resitel_zasilat_cislo_emailem.py b/seminar/migrations/0091_resitel_zasilat_cislo_emailem.py new file mode 100644 index 00000000..92f8367a --- /dev/null +++ b/seminar/migrations/0091_resitel_zasilat_cislo_emailem.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.17 on 2020-12-01 19:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('seminar', '0090_auto_20201110_1958'), + ] + + operations = [ + migrations.AddField( + model_name='resitel', + name='zasilat_cislo_emailem', + field=models.BooleanField(default=False, help_text='True pokud chce řešitel dostávat číslo emailem', verbose_name='zasílat číslo emailem'), + ), + ] diff --git a/seminar/models.py b/seminar/models.py index 57a44404..f3491089 100644 --- a/seminar/models.py +++ b/seminar/models.py @@ -260,8 +260,10 @@ class Resitel(SeminarModelBase): (ZASILAT_DO_SKOLY, 'Do školy'), (ZASILAT_NIKAM, 'Nikam'), ] + zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU) + zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce řešitel dostávat číslo emailem', default=False) poznamka = models.TextField('neveřejná poznámka', blank=True, help_text='Neveřejná poznámka k řešiteli (plain text)') @@ -309,25 +311,106 @@ class Resitel(SeminarModelBase): return sum(h.body for h in list(vsechna_hodnoceni)) - def get_titul(self, celkove_body=None): - "Vrati titul" - if celkove_body is None: - celkove_body = self.vsechny_body() - - if celkove_body < 10: - return '' - elif celkove_body < 20: - return 'Bc.' - elif celkove_body < 50: - return 'Mgr.' - elif celkove_body < 100: - return 'Dr.' - elif celkove_body < 200: - return 'Doc.' - elif celkove_body < 500: - return 'Prof.' + def get_titul(self, body=None): + "Vrati titul jako řetězec." + + # Nejprve si zadefinujeme titul + from enum import Enum + from functools import total_ordering + @total_ordering + class Titul(Enum): + """ Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """ + nic = (0, '') + bc = (20, 'Bc.') + mgr = (50, 'Mgr.') + dr = (100, 'Dr.') + doc = (200, 'Doc.') + prof = (500, 'Prof.') + akad = (1000, 'Akad.') + + def __lt__(self, other): + return True if self.value[0] < other.value[0] else False + def __eq__(self, other): # Měla by být implicitní, ale klidně explicitně. + return True if self.value[0] == other.value[0] else False + + def __str__(self): + return self.value[1] + + @classmethod + def z_bodu(cls, body): + aktualni = cls.nic + # TODO: ověřit, že to funguje + for titul in cls: # Kdyžtak použít __members__.items() + if titul.value[0] <= body: + aktualni = titul + else: + break + return aktualni + + # Hledáme body v databázi + # V listopadu 2020 jsme se na filosofické schůzce shodli o změně hranic titulů: + # - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími + # - proto se započítávají dvojnásobně a byly posunuté hranice titulů + # - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád. + hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all()) + novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku) + + def body_z_hodnoceni(hh : list): + return sum(h.body for h in hh) + + stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku) + if body is None: + nove_body = body_z_hodnoceni(novejsi_hodnoceni) else: - return 'Akad.' + # Zjistíme, kolik bodů jsou staré, tedy hodnotnější + nove_body = max(0, body - stare_body) # Všechny body nad počet původních hodnotnějších + stare_body = min(stare_body, body) # Skutečný počet hodnotnějších bodů + logicke_body = 2*stare_body + nove_body + + + # Titul se určí následovně: + # - Pokud se řeší body, které jsou starší, než do 26 ročníku (včetně), dáváme tituly postaru. + # - Jinak dáváme tituly po novu... + # - ... ale titul se nesmí odebrat, pokud se zmenšil. + def titul_do_26_rocniku(body): + """ Původní hranice bodů za tituly """ + if body < 10: + return Titul.nic + elif body < 20: + return Titul.bc + elif body < 50: + return Titul.mgr + elif body < 100: + return Titul.dr + elif body < 200: + return Titul.doc + elif body < 500: + return Titul.prof + else: + return Titul.akad + + hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all()) + novejsi_body = body_z_hodnoceni( + Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()) + .difference(hodnoceni_do_26_rocniku) + ) + starsi_body = body_z_hodnoceni(hodnoceni_do_26_rocniku) + if body is not None: + # Ještě z toho vybereme ty správně staré body + novejsi_body = max(0, body - starsi_body) + starsi_body = min(starsi_body, body) + + # Titul pro 26. ročník + stary_titul = titul_do_26_rocniku(starsi_body) + # Titul podle aktuálních pravidel + novy_titul = Titul.z_bodu(logicke_body) + + if novejsi_body == 0: + # Žádné nové body -- titul podle starých pravidel + return str(stary_titul) + return str(max(novy_titul, stary_titul)) + + def __str__(self): return self.osoba.plne_jmeno() @@ -745,15 +828,18 @@ class Problem(SeminarModelBase,PolymorphicModel): def verejne(self): # aktuálně podle stavu problému # FIXME pro některé problémy možná chceme override + # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je. + # Je to tak správně? 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 + #cislo_verejne = False + #if (self.cislo_zadani and self.cislo_zadani.verejne()): + # cislo_verejne = True - return (stav_verejny and cislo_verejne) + #return (stav_verejny and cislo_verejne) verejne.boolean = True def verejne_url(self): @@ -991,7 +1077,7 @@ def aux_generate_filename(self, filename): unidecode(filename.replace('/', '-').replace('\0', '')) ) datedir = timezone.now().strftime('%Y-%m') - fname = "{}_{}".format( + fname = "{}/{}".format( timezone.now().strftime('%Y-%m-%d-%H:%M'), clean) return os.path.join(datedir, fname) @@ -1044,6 +1130,11 @@ class PrilohaReseni(SeminarModelBase): def __str__(self): return str(self.soubor) + def split(self): + "Vrátí cestu rozsekanou po složkách. To se hodí v templatech" + # Věřím, že tohle funguje, případně použít os.path nebo pathlib. + return self.soubor.url.split('/') + class Pohadka(SeminarModelBase): """Kus pohádky před/za úlohou v čísle""" @@ -1480,7 +1571,7 @@ class TextNode(TreeNode): verbose_name = 'Text (Node)' verbose_name_plural = 'Text (Node)' text = models.ForeignKey(Text, - on_delete=models.PROTECT, + on_delete=models.CASCADE, verbose_name = 'text') def aktualizuj_nazev(self): @@ -1513,7 +1604,7 @@ class ReseniNode(TreeNode): verbose_name = 'reseni') def aktualizuj_nazev(self): - self.nazev = "OtisteneReseniNode: "+str(self.reseni) + self.nazev = "ReseniNode: "+str(self.reseni) def getOdkazStr(self): return str(self.reseni) diff --git a/seminar/templates/seminar/archiv/cislo.html b/seminar/templates/seminar/archiv/cislo.html index 698c5d29..d191da22 100644 --- a/seminar/templates/seminar/archiv/cislo.html +++ b/seminar/templates/seminar/archiv/cislo.html @@ -45,6 +45,7 @@
  • Tituly (TeX)
  • Výsledkovka (TeX)
  • Obálkování
  • +
  • Odměny
  • {% endif %} @@ -75,6 +76,7 @@ {% for p in problemy %} {{ p.kod_v_rocniku }} {% endfor %} + {% if ostatni %}Ostatní {% endif %} Za číslo Za ročník Odjakživa diff --git a/seminar/templates/seminar/archiv/odmeny.html b/seminar/templates/seminar/archiv/odmeny.html new file mode 100644 index 00000000..fab1cb7c --- /dev/null +++ b/seminar/templates/seminar/archiv/odmeny.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block content %} +

    + {% block nadpis1a %}{% block nadpis1b %} + Odměny {{ cislo }} + {% endblock %}{% endblock %} +

    + + +{% endblock content %} diff --git a/seminar/templates/seminar/odevzdavatko/detail.html b/seminar/templates/seminar/odevzdavatko/detail.html new file mode 100644 index 00000000..6cee990d --- /dev/null +++ b/seminar/templates/seminar/odevzdavatko/detail.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block content %} + +

    Řešené problémy: {{ object.problem.all | join:", " }}

    + +

    Řešitelé: {{ object.resitele.all | join:", " }}

    + +{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #} +

    Forma: {{ object.get_forma_display }}, doručeno {{ object.cas_doruceni }}

    + +{# Soubory: #} +

    Přílohy:

    +{% if object.prilohy.all %} + + +{% for priloha in object.prilohy.all %} + + + + + {# TODO: Orgo-poznámka, ideálně jako formulář #} +{% endfor %} +
    SouborŘešitelova poznámkaDatum
    {{ priloha.split | last }}{{ priloha.res_poznamka }}{{ priloha.vytvoreno }}
    +{% else %} +

    Žádné přílohy

    +{% endif %} + +{# Hodnocení: #} +{# FIXME: Udělat jako formulář #} +

    Hodnocení:

    +{% if object.hodnoceni_set.all %} + + +{% for h in object.hodnoceni_set.all %} + + + + +{% endfor %} +
    ProblémBodyČíslo pro body
    {{ h.problem }}{{ h.body }}{{ h.cislo_body }}
    +{% else %} +

    Ještě nebylo hodnoceno

    +{% endif %} + + +{% endblock %} diff --git a/seminar/templates/seminar/odevzdavatko/seznam.html b/seminar/templates/seminar/odevzdavatko/seznam.html new file mode 100644 index 00000000..b58dcb54 --- /dev/null +++ b/seminar/templates/seminar/odevzdavatko/seznam.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block content %} + + + +{% endblock %} diff --git a/seminar/templates/seminar/odevzdavatko/tabulka.html b/seminar/templates/seminar/odevzdavatko/tabulka.html new file mode 100644 index 00000000..ff396ce4 --- /dev/null +++ b/seminar/templates/seminar/odevzdavatko/tabulka.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #} + +{% block content %} + + + + {# Prázdná buňka v levém horním rohu #} + {% for p in problemy %} + + {% endfor %} + + {% for resitel,hodnoty in radky%} + + + {% for hodn in hodnoty %} + + {% endfor %} + + {% endfor %} +
    + {# TODO: Přehled řešení k problému, odkázaný odsud? #} + {{ p }} +
    + {# TODO: Chceme mít view i na řešení konkrétního řešitele ke všem problémům? #} + {{ resitel }} + + {% if hodn %} + + {{ hodn.pocet_reseni }} řeš.
    {{ hodn.body }} b
    {{ hodn.posledni_odevzdani|kratke_datum|default_if_none:"Nikdy"|default:"???"}} +
    + {% endif %} +
    + +{% endblock %} diff --git a/seminar/templates/seminar/profil/edit.html b/seminar/templates/seminar/profil/edit.html index 827e26f1..803a64af 100644 --- a/seminar/templates/seminar/profil/edit.html +++ b/seminar/templates/seminar/profil/edit.html @@ -6,6 +6,17 @@ {{form.media}} {% endblock %} + + + {% block content %}

    {% block nadpis1a %}{% block nadpis1b %} @@ -73,6 +84,7 @@

    {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} + {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}

    diff --git a/seminar/templates/seminar/profil/nahraj_reseni.html b/seminar/templates/seminar/profil/nahraj_reseni.html index 9dfda710..0b31aa79 100644 --- a/seminar/templates/seminar/profil/nahraj_reseni.html +++ b/seminar/templates/seminar/profil/nahraj_reseni.html @@ -53,7 +53,7 @@ {{ field }} - {{ field.help_text|safe }}> + {{ field.help_text|safe }} {{ field.errors }} diff --git a/seminar/templates/seminar/profil/prihlaska.html b/seminar/templates/seminar/profil/prihlaska.html index 125a5ffb..22bd3385 100644 --- a/seminar/templates/seminar/profil/prihlaska.html +++ b/seminar/templates/seminar/profil/prihlaska.html @@ -7,6 +7,16 @@ {% endblock %} + + {% block content %}

    {% block nadpis1a %}{% block nadpis1b %} @@ -77,6 +87,7 @@

    {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %} + {% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}

    diff --git a/seminar/templatetags/utils.py b/seminar/templatetags/utils.py new file mode 100644 index 00000000..1cb92d6a --- /dev/null +++ b/seminar/templatetags/utils.py @@ -0,0 +1,27 @@ +from django import template +from datetime import datetime, timedelta +from pytz import timezone +from mamweb.settings import TIME_ZONE +import logging +register = template.Library() + +logger = logging.getLogger(__name__) + +@register.filter(name='kratke_datum', expects_localtime=True) +def kratke_datum(dt): + # None dává None, ne-datum dává False, aby se daly použít filtry typu "default". + if dt is None: + return None + if not isinstance(dt, datetime): + logger.warning(f"Špatné volání filtru {__name__}: {dt}") + return False + naive_now = datetime.now() + tz = timezone(TIME_ZONE) + now = tz.localize(naive_now) + delta = now - dt + if delta <= timedelta(days=1): + return dt.strftime("%k:%M") + if delta <= timedelta(days=365): # Timedelta neumí vyjádřit 1 rok + return dt.strftime("%d. %m.") + return dt.strftime("%d. %m. %Y") + diff --git a/seminar/testutils.py b/seminar/testutils.py index 94fa78b6..913e6abb 100644 --- a/seminar/testutils.py +++ b/seminar/testutils.py @@ -200,6 +200,8 @@ def gen_organizatori(rnd, osoby, last_rocnik): os.user = user os.save() os.user.user_permissions.add(org_perm) + os.user.is_staff = True + os.user.save() organizatori.append(Organizator.objects.create(osoba=os, organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga)) return organizatori diff --git a/seminar/urls.py b/seminar/urls.py index 91e0ce15..7fa7b0a6 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -13,8 +13,8 @@ urlpatterns = [ path('co-je-MaM/organizatori/organizovali/', views.CojemamOrganizatoriStariView.as_view(), name='stari_organizatori'), # Archiv - path('archiv/rocniky/', views.ArchivView.as_view()), - path('archiv/temata/', views.ArchivTemataView.as_view()), + path('archiv/rocniky/', views.ArchivView.as_view(), name="seninar_archiv_rocniky"), + path('archiv/temata/', views.ArchivTemataView.as_view(), name="seninar_archiv_temata"), path('rocnik//', views.RocnikView.as_view(), name='seminar_rocnik'), path('cislo/./', views.CisloView.as_view(), name='seminar_cislo'), @@ -90,17 +90,17 @@ urlpatterns = [ name='seminar_rocnik_vysledkovka' ), path( - 'cislo/./vysledkovka.tex', + 'cislo/./vysledkovka.tex', org_required(views.CisloVysledkovkaView.as_view()), name='seminar_cislo_vysledkovka' ), path( - 'cislo/./obalky.pdf', + 'cislo/./obalky.pdf', org_required(views.cisloObalkyView), name='seminar_cislo_obalky' ), path( - 'cislo/./tituly.tex', + 'cislo/./tituly.tex', org_required(views.TitulyView), name='seminar_cislo_titul' ), @@ -110,10 +110,14 @@ urlpatterns = [ name='stav_databaze' ), path( - 'cislo/./obalkovani', + 'cislo/./obalkovani', org_required(views.ObalkovaniView.as_view()), name='seminar_cislo_resitel_obalkovani' ), + path( + 'cislo/./odmeny/./', + org_required(views.OdmenyView.as_view()), + name="seminar_archiv_odmeny"), path( 'soustredeni//obalky.pdf', org_required(views.soustredeniObalkyView), @@ -168,5 +172,10 @@ urlpatterns = [ # org_member_required(views.OrganizatorAutocomplete.as_view()), # name='seminar_autocomplete_organizator') + path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'), + path('temp/reseni///', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'), + path('temp/reseni/', org_required(views.DetailReseniView.as_view()), name='odevzdavatko_detail_reseni'), + path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())), + path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())), ] diff --git a/seminar/utils.py b/seminar/utils.py index ad9be95e..bcc67013 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -148,16 +148,12 @@ def resi_v_rocniku(rocnik, cislo=None): if cislo is None: # filtrujeme pouze podle ročníku - letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik) + return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), + reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct() else: # filtrujeme podle ročníku i čísla - letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik, - hodnoceni__cislo_body__poradi__lte=cislo.poradi) - - # vygenerujeme queryset řešitelů, co letos něco poslali - letosni_resitele = m.Resitel.objects.none() - for reseni in letosni_reseni: - letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok()) - return letosni_resitele.distinct() + return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(), + reseni__hodnoceni__cislo_body__rocnik=rocnik, + reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct() def aktivniResitele(cislo, pouze_letosni=False): diff --git a/seminar/views/__init__.py b/seminar/views/__init__.py index 222d19bb..a9eb3ea9 100644 --- a/seminar/views/__init__.py +++ b/seminar/views/__init__.py @@ -1,3 +1,4 @@ from .views_all import * from .autocomplete import * from .views_rest import * +from .odevzdavatko import * diff --git a/seminar/views/autocomplete.py b/seminar/views/autocomplete.py index 04ddca83..32c634bc 100644 --- a/seminar/views/autocomplete.py +++ b/seminar/views/autocomplete.py @@ -34,7 +34,9 @@ class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): rocnik = nastaveni.aktualni_rocnik temata = m.Tema.objects.filter(rocnik=rocnik, stav=m.Problem.STAV_ZADANY) ulohy = m.Uloha.objects.filter(cislo_deadline__rocnik = rocnik) + clanky = m.Clanek.objects.filter(cislo__rocnik = rocnik, stav=m.Problem.STAV_ZADANY) # FIXME: Je tohle to, co chceme? ulohy.union(temata) + ulohy.union(clanky) qs = ulohy if self.q: qs = qs.filter( diff --git a/seminar/views/odevzdavatko.py b/seminar/views/odevzdavatko.py new file mode 100644 index 00000000..de8ceec3 --- /dev/null +++ b/seminar/views/odevzdavatko.py @@ -0,0 +1,129 @@ +from django.views.generic import ListView, DetailView +from django.views.generic.base import TemplateView + +from dataclasses import dataclass +import datetime + +import seminar.models as m +from seminar.utils import aktivniResitele, resi_v_rocniku + +# Co chceme? +# - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení +# - TabulkaOdevzdanychReseniView +# - Detail konkrétního problému a řešitele -- přehled všech řešení odevzdaných k tomuto problému +# - ReseniProblemuView +# - Detail konkrétního řešení -- všechny soubory, datum, ... +# - DetailReseniView +# +# Taky se může hodit: +# - Tabulka všech řešitelů x všech problémů? + +@dataclass +class SouhrnReseni: + """Dataclass reprezentující data o odevzdaných řešeních pro zobrazení v tabulce.""" + pocet_reseni : int + posledni_odevzdani : datetime.datetime + body : float + + +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) + # 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() + + qs = super().get_queryset() + qs = qs.filter(problem__in=self.zadane_problemy).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() + + ctx = super().get_context_data(*args, **kwargs) + ctx['problemy'] = self.zadane_problemy + ctx['resitele'] = self.resitele + tabulka = dict() + + def pridej_reseni(problem, resitel, body, cas): + if problem not in tabulka: + tabulka[problem] = dict() + if resitel not in tabulka[problem]: + tabulka[problem][resitel] = SouhrnReseni(pocet_reseni=1, posledni_odevzdani=cas, body=body) + else: + tabulka[problem][resitel].posledni_odevzdani = max(tabulka[problem][resitel].posledni_odevzdani, cas) + tabulka[problem][resitel].body = max(tabulka[problem][resitel].body, body, + key=lambda x: x if x is not None else -1 # None je malé číslo + # FIXME: Možná dává smysl i mít None jako velké číslo -- jakože "TODO: zadat body" + ) + tabulka[problem][resitel].pocet_reseni += 1 + # Pro jednoduchost template si ještě poznamenáme ID problému a řešitele + tabulka[problem][resitel].problem_id = problem.id + tabulka[problem][resitel].resitel_id = resitel.id + + for hodnoceni in self.get_queryset(): + for resitel in hodnoceni.reseni.resitele.all(): + pridej_reseni(hodnoceni.problem, resitel, hodnoceni.body, hodnoceni.reseni.cas_doruceni) + + hodnoty = [] + for resitel in self.resitele: + resiteluv_radek = [] + for problem in self.zadane_problemy: + if problem in tabulka and resitel in tabulka[problem]: + resiteluv_radek.append(tabulka[problem][resitel]) + else: + resiteluv_radek.append(None) + hodnoty.append(resiteluv_radek) + ctx['radky'] = list(zip(self.resitele, hodnoty)) + + return ctx + +class ReseniProblemuView(ListView): + model = m.Reseni + template_name = 'seminar/odevzdavatko/seznam.html' + + def get_queryset(self): + qs = super().get_queryset() + resitel_id = self.kwargs['resitel'] + if resitel_id is None: + raise ValueError("Nemám řešitele!") + problem_id = self.kwargs['problem'] + if problem_id is None: + raise ValueError("Nemám problém! (To je problém!)") + + resitel = m.Resitel.objects.get(id=resitel_id) + problem = m.Problem.objects.get(id=problem_id) + qs = qs.filter( + problem__in=[problem], + resitele__in=[resitel], + ) + return qs + + # Kontext automaticky? + +class DetailReseniView(DetailView): + model = m.Reseni + template_name = 'seminar/odevzdavatko/detail.html' + # To je všechno? Najde se to podle pk... + +# Přehled všech řešení kvůli debugování + +class SeznamReseniView(ListView): + model = m.Reseni + template_name = 'seminar/odevzdavatko/seznam.html' + +class SeznamAktualnichReseniView(SeznamReseniView): + def get_queryset(self): + qs = super().get_queryset() + akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... + resitele = resi_v_rocniku(akt_rocnik) + qs = qs.filter(resitele__in=resitele) # FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel + return qs diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index a550c40a..1aca98f6 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -1,4 +1,4 @@ -# coding:utf-8 + from django.shortcuts import get_object_or_404, render, redirect from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse @@ -17,6 +17,7 @@ from django.contrib.auth.models import User, Permission from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from django.core import serializers +from django.core.exceptions import PermissionDenied from django.forms.models import model_to_dict import seminar.models as s @@ -120,15 +121,57 @@ class TNLData(object): self.appendable_siblings = tnltt.appendableChildren(self.parent) else: self.appendable_siblings = [] - - + @classmethod + def public_above(cls, anode): + """ Returns output of verejne for closest Rocnik, Cislo or Problem above. + (All of them have method verejne.)""" + parent = anode # chceme začít už od konkrétního node včetně + while True: + rocnik = isinstance(parent, s.RocnikNode) + cislo = isinstance(parent, s.CisloNode) + uloha = (isinstance(parent, s.UlohaVzorakNode) or + isinstance(parent, s.UlohaZadaniNode)) + tema = isinstance(parent, s.TemaVCisleNode) + + if (rocnik or cislo or uloha or tema) or parent==None: + break + else: + parent = treelib.get_parent(parent) + if rocnik: + return parent.rocnik.verejne() + elif cislo: + return parent.cislo.verejne() + elif uloha: + return parent.uloha.verejne() + elif tema: + return parent.tema.verejne() + elif None: + print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou" + "ani tématem. {}".format(anode)) + return False + + @classmethod + def all_public_children(cls, anode): + for ch in treelib.all_children(anode): + if TNLData.public_above(ch): + yield ch + else: + continue @classmethod - def from_treenode(cls,anode,parent=None,index=None): - out = cls(anode,parent,index) - for (idx,ch) in enumerate(treelib.all_children(anode)): - # FIXME přidat filtrování na veřejnost - outitem = cls.from_treenode(ch,out,idx) + def from_treenode(cls, anode, user, parent=None, index=None): + if TNLData.public_above(anode) or user.has_perm('auth.org'): + out = cls(anode,parent,index) + else: + raise PermissionDenied() + + if user.has_perm('auth.org'): + enum_children = enumerate(treelib.all_children(anode)) + else: + enum_children = enumerate(TNLData.all_public_children(anode)) + + for (idx,ch) in enum_children: + outitem = cls.from_treenode(ch, user, out, idx) out.children.append(outitem) out.add_edit_options() return out @@ -195,7 +238,7 @@ class TreeNodeView(generic.DetailView): def get_context_data(self,**kwargs): context = super().get_context_data(**kwargs) - context['tnldata'] = TNLData.from_treenode(self.object) + context['tnldata'] = TNLData.from_treenode(self.object,self.request.user) return context class TreeNodeJSONView(generic.DetailView): @@ -203,7 +246,7 @@ class TreeNodeJSONView(generic.DetailView): def get(self,request,*args, **kwargs): self.object = self.get_object() - data = TNLData.from_treenode(self.object).to_json() + data = TNLData.from_treenode(self.object,self.request.user).to_json() return JsonResponse(data) @@ -332,6 +375,7 @@ class ProblemView(generic.DetailView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + user = self.request.user # Teď potřebujeme doplnit tnldata do kontextu. # Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME. if False: @@ -339,11 +383,11 @@ class ProblemView(generic.DetailView): pass elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera): # Tyhle Problémy mají ŘešeníNode - context['tnldata'] = TNLData.from_treenode(self.object.reseninode) + context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user) elif isinstance(self.object, s.Uloha): # FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever - tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode) - tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode) + tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user) + tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user) context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak]) elif isinstance(self.object, s.Tema): rocniknode = self.object.rocnik.rocniknode @@ -385,16 +429,16 @@ class AktualniZadaniView(generic.TemplateView): # ) # def ZadaniTemataView(request): - nastaveni = get_object_or_404(Nastaveni) - verejne = nastaveni.aktualni_cislo.verejne() - akt_rocnik = nastaveni.aktualni_cislo.rocnik - temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') - return render(request, 'seminar/tematka/rozcestnik.html', - { - 'tematka': temata, - 'verejne': verejne, - }, - ) + nastaveni = get_object_or_404(Nastaveni) + verejne = nastaveni.aktualni_cislo.verejne() + akt_rocnik = nastaveni.aktualni_cislo.rocnik + temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') + return render(request, 'seminar/tematka/rozcestnik.html', + { + 'tematka': temata, + 'verejne': verejne, + }, + ) # nastaveni = get_object_or_404(Nastaveni) @@ -945,18 +989,30 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): 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 hlavni_problemy: + 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 hlavni_problemy: + 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', @@ -969,7 +1025,10 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None): # řešení může řešit více problémů for prob in list(reseni.problem.all()): nadproblem = hlavni_problem(prob) - nadproblem_slovnik = hlavni_problemy_slovnik[nadproblem.id] + 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()): @@ -1014,11 +1073,26 @@ def vysledkovka_cisla(cislo, context=None): # 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 hlavni_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í @@ -1034,7 +1108,8 @@ def vysledkovka_cisla(cislo, context=None): # vytahané informace předáváme do kontextu context['cislo'] = cislo context['radky_vysledkovky'] = radky_vysledkovky - context['problemy'] = hlavni_problemy + context['problemy'] = temata_a_spol + context['ostatni'] = je_nejake_ostatni #context['v_cisle_zadane'] = TODO #context['resene_problemy'] = resene_problemy return context @@ -1063,6 +1138,7 @@ class CisloView(generic.DetailView): context = super(CisloView, self).get_context_data(**kwargs) cislo = context['cislo'] + context['prevcislo'] = Cislo.objects.filter((Q(rocnik__lt=self.object.rocnik) | Q(poradi__lt=self.object.poradi))&Q(rocnik__lte=self.object.rocnik)).first() # vrátíme context (aktuálně obsahuje jen věci ohledně výsledkovky return vysledkovka_cisla(cislo, context) @@ -1079,6 +1155,30 @@ class ArchivTemataView(generic.ListView): ctx['rocniky'][rocnik] = list(temata) return ctx +class OdmenyView(generic.TemplateView): + template_name = 'seminar/archiv/odmeny.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + fromcislo = Cislo.objects.get(rocnik=self.kwargs.get('frocnik'), poradi=self.kwargs.get('fcislo')) + tocislo = Cislo.objects.get(rocnik=self.kwargs.get('trocnik'), poradi=self.kwargs.get('tcislo')) + resitele = aktivniResitele(tocislo) + frombody = body_resitelu(resitele, fromcislo) + tobody = body_resitelu(resitele, tocislo) + outlist = [] + for (aid, tbody) in tobody.items(): + fbody = frombody.get(aid,0) + resitel = Resitel.objects.get(pk=aid) + ftitul = resitel.get_titul(fbody) + ttitul = resitel.get_titul(tbody) + if ftitul != ttitul: + outlist.append({'jmeno': resitel.osoba.plne_jmeno(), 'ftitul': ftitul, 'ttitul': ttitul}) + context['zmeny'] = outlist + return context + + + + ### Generovani vysledkovky class CisloVysledkovkaView(CisloView): @@ -1332,6 +1432,12 @@ class ResitelView(LoginRequiredMixin,generic.DetailView): ### Formulare +# pro přidání políčka do formuláře je potřeba +# - mít v modelu tu položku, kterou chci upravovat +# - přidat do views (prihlaskaView, resitelEditView) +# - přidat do forms +# - includovat do html + class AddSolutionView(LoginRequiredMixin, FormView): template_name = 'seminar/org/vloz_reseni.html' form_class = f.VlozReseniForm @@ -1409,7 +1515,7 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data): from django.forms.models import model_to_dict def resitelEditView(request): err_logger = logging.getLogger('seminar.prihlaska.problem') - ## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately + ## 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 @@ -1448,6 +1554,7 @@ def resitelEditView(request): 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'): resitel_edit.skola = fcd['skola'] else: @@ -1511,7 +1618,8 @@ def prihlaskaView(request): r = Resitel( rok_maturity = fcd['rok_maturity'], - zasilat = fcd['zasilat'] + zasilat = fcd['zasilat'], + zasilat_cislo_emailem = fcd['zasilat_cislo_emailem'] ) r.save() diff --git a/seminar/views/views_rest.py b/seminar/views/views_rest.py index aa3fca55..ef49b3cc 100644 --- a/seminar/views/views_rest.py +++ b/seminar/views/views_rest.py @@ -6,25 +6,27 @@ from seminar import treelib DEFAULT_NODE_DEPTH = 2 + class TextSerializer(serializers.ModelSerializer): class Meta: model = m.Text fields = '__all__' - - -class UlohaVzorakNodeSerializer(serializers.ModelSerializer): +class ProblemSerializer(serializers.ModelSerializer): class Meta: - model = m.UlohaVzorakNode + model = m.Problem fields = '__all__' - depth = DEFAULT_NODE_DEPTH - -class UlohaZadaniNodeSerializer(serializers.ModelSerializer): + +class UlohaSerializer(serializers.ModelSerializer): class Meta: - model = m.UlohaZadaniNode + model = m.Uloha fields = '__all__' - depth = DEFAULT_NODE_DEPTH +class ReseniSerializer(serializers.ModelSerializer): + class Meta: + model = m.Reseni + fields = '__all__' + class RocnikNodeSerializer(serializers.ModelSerializer): class Meta: model = m.RocnikNode @@ -138,12 +140,161 @@ class CastNodeCreateSerializer(serializers.ModelSerializer): fields = ('nadpis','where','refnode') depth = DEFAULT_NODE_DEPTH +class UlohaZadaniNodeSerializer(serializers.ModelSerializer): + class Meta: + model = m.UlohaZadaniNode + fields = '__all__' + depth = DEFAULT_NODE_DEPTH + +class UlohaZadaniNodeWriteSerializer(serializers.ModelSerializer): + uloha = UlohaSerializer() + + def update(self,node,validated_data): + node.uloha.max_body = validated_data.get('uloha').get('max_body') + node.uloha.kod = validated_data.get('uloha').get('kod') + node.uloha.nazev = validated_data.get('uloha').get('nazev') + node.uloha.save() + return node + + class Meta: + model = m.TextNode + fields = ('id','uloha') + depth = DEFAULT_NODE_DEPTH + +class UlohaZadaniNodeCreateSerializer(serializers.ModelSerializer): + uloha = UlohaSerializer() + refnode = serializers.IntegerField() + where = serializers.CharField() + + def create(self,validated_data): + # text_zadani = validated_data.pop('text_zadani') + temp_uloha = validated_data.pop('uloha') + where = validated_data.pop('where') + refnode_id = validated_data.pop('refnode') + refnode = m.TreeNode.objects.get(pk=refnode_id) + + # Z cesty ke koreni stromu zjistime, v jakem jsme tematu a v jakem cisle + cislo = None + tema = None + travelnode = refnode + while travelnode is not None: + if isinstance(travelnode, m.TemaVCisleNode): + tema = travelnode.tema + if isinstance(travelnode, m.CisloNode): + cislo = travelnode.cislo + travelnode = treelib.get_parent(travelnode) + # Vyrobime ulohu + uloha = m.Uloha.objects.create(cislo_zadani=cislo, nadproblem = tema, **temp_uloha) + + # A vyrobime UlohaZadaniNode + if where == 'syn': + node = treelib.create_child(refnode,m.UlohaZadaniNode,uloha = uloha) + elif where == 'za': + node = treelib.create_node_after(refnode,m.UlohaZadaniNode,uloha = uloha) + elif where == 'pred': + node = treelib.create_node_before(refnode,m.UlohaZadaniNode,uloha = uloha) + node.where = None + node.refnode = None + node.max_body = None + node.kod = None + return node + + class Meta: + model = m.UlohaZadaniNode + fields = ('uloha','where','refnode') + depth = DEFAULT_NODE_DEPTH + +class UlohaVzorakNodeSerializer(serializers.ModelSerializer): + class Meta: + model = m.UlohaVzorakNode + fields = '__all__' + depth = DEFAULT_NODE_DEPTH + +class UlohaVzorakNodeWriteSerializer(serializers.ModelSerializer): + uloha = serializers.PrimaryKeyRelatedField(queryset=m.Uloha.objects.all(), many=False, read_only=False) + + class Meta: + model = m.UlohaVzorakNode + fields = ('id','uloha') + depth = DEFAULT_NODE_DEPTH + + +class UlohaVzorakNodeCreateSerializer(serializers.ModelSerializer): + uloha_id = serializers.IntegerField() + refnode = serializers.IntegerField() + where = serializers.CharField() + + def create(self, validated_data): + uloha_id = validated_data.pop('uloha_id') + uloha = m.Uloha.objects.get(pk=uloha_id) + where = validated_data.pop('where') + refnode_id = validated_data.pop('refnode') + refnode = m.TreeNode.objects.get(pk=refnode_id) + + if where == 'syn': + node = treelib.create_child(refnode,m.UlohaVzorakNode,uloha = uloha) + elif where == 'za': + node = treelib.create_node_after(refnode,m.UlohaVzorakNode,uloha = uloha) + elif where == 'pred': + node = treelib.create_node_before(refnode,m.UlohaVzorakNode,uloha = uloha) + node.refnode = None + node.where = None + node.uloha_id = None + + return node + + class Meta: + model = m.UlohaVzorakNode + fields = ('refnode', 'uloha_id', 'where') + depth = DEFAULT_NODE_DEPTH + + + + class ReseniNodeSerializer(serializers.ModelSerializer): class Meta: model = m.ReseniNode fields = '__all__' depth = DEFAULT_NODE_DEPTH +class ReseniNodeWriteSerializer(serializers.ModelSerializer): + reseni = serializers.PrimaryKeyRelatedField(queryset=m.Reseni.objects.all(), many=False, read_only=False) + + class Meta: + model = m.ReseniNode + fields = ('id','reseni') + depth = DEFAULT_NODE_DEPTH + +class ReseniNodeCreateSerializer(serializers.ModelSerializer): + reseni_id = serializers.IntegerField() + refnode = serializers.IntegerField() + where = serializers.CharField() + + def create(self,validated_data): + # text_zadani = validated_data.pop('text_zadani') + reseni_id = validated_data.pop('reseni_id') + reseni = m.Reseni.objects.get(pk=reseni_id) + where = validated_data.pop('where') + refnode_id = validated_data.pop('refnode') + refnode = m.TreeNode.objects.get(pk=refnode_id) + + # A vyrobime UlohaZadaniNode + if where == 'syn': + node = treelib.create_child(refnode,m.ReseniNode,reseni = reseni) + elif where == 'za': + node = treelib.create_node_after(refnode,m.ReseniNode,reseni = reseni) + elif where == 'pred': + node = treelib.create_node_before(refnode,m.ReseniNode,reseni = reseni) + node.where = None + node.refnode = None + node.reseni_id = None + return node + + class Meta: + model = m.ReseniNode + fields = ('reseni_id','where','refnode') + depth = DEFAULT_NODE_DEPTH + class TreeNodeSerializer(PolymorphicSerializer): model_serializer_mapping = { diff --git a/seminar/viewsets.py b/seminar/viewsets.py index cc59f7d7..7e2ea63a 100644 --- a/seminar/viewsets.py +++ b/seminar/viewsets.py @@ -1,4 +1,7 @@ from rest_framework import viewsets,filters +from rest_framework import status +from rest_framework.response import Response +from django.core.exceptions import PermissionDenied from rest_framework.permissions import BasePermission, AllowAny from . import models as m from . import views @@ -18,31 +21,6 @@ class PermissionMixin(object): # návštěvník nemusí být zalogován, aby si prohlížel obsah return [permission() for permission in permission_classes] - def verejne_nad(self, node): - """ Returns output of verejne for closest Rocnik, Cislo or Problem above. - (All of them have method verejne.)""" - parent = get_parent(node) - while True: - rocnik = isinstance(parent, RocnikNode) - cislo = isinstance(parent, CisloNode) - problem = isinstance(parent, ProblemNode) - - if (rocnik or cislo or problem): - break - else: - parent = get_parent(parent) - if rocnik: - return parent.rocnik.verejne() - elif cislo: - return parent.cislo.verejne() - elif problem: - return parent.problem.verjne() - - def has_object_permission(self, request, view, obj): - # test that obj is Node - assert isinstance(obj, Node) - return verejne_nad(node) - class ReadWriteSerializerMixin(object): """ Overrides get_serializer_class to choose the read serializer @@ -87,10 +65,6 @@ class ReadWriteSerializerMixin(object): ) return self.create_serializer_class -class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet): - queryset = m.UlohaVzorakNode.objects.all() - serializer_class = views.UlohaVzorakNodeSerializer - class TextViewSet(PermissionMixin, viewsets.ModelViewSet): queryset = m.Text.objects.all() serializer_class = views.TextSerializer @@ -107,12 +81,91 @@ class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelVi write_serializer_class = views.CastNodeSerializer create_serializer_class = views.CastNodeCreateSerializer -class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet): - serializer_class = views.UlohaVzorakNodeSerializer + def destroy(self, request, *args, **kwargs): + obj = self.get_object() + print(obj) + if obj.first_child is None: + return super().destroy(request,*args,**kwargs) + raise PermissionDenied('Nelze smazat CastNode, který má děti!') + + +class UlohaVzorakNodeViewSet(PermissionMixin, ReadWriteSerializerMixin, viewsets.ModelViewSet): + read_serializer_class = views.UlohaVzorakNodeSerializer + write_serializer_class = views.UlohaVzorakNodeWriteSerializer + create_serializer_class = views.UlohaVzorakNodeCreateSerializer def get_queryset(self): queryset = m.UlohaVzorakNode.objects.all() nazev = self.request.query_params.get('nazev',None) if nazev is not None: queryset = queryset.filter(nazev__contains=nazev) + + if self.request.user.has_perm('auth.org'): + return queryset + else: # pro neorgy jen zveřejněné vzoráky + return queryset.filter(uloha__cislo_reseni__verejne_db=True) + + nadproblem = self.request.query_params.get('nadproblem',None) + if nadproblem is not None: + queryset = queryset.filter(nadproblem__pk = nadproblem) + return queryset + +class ReseniViewSet(viewsets.ModelViewSet): + serializer_class = views.ReseniSerializer + + def get_queryset(self): + queryset = m.Reseni.objects.all() + #FIXME upravit nazvy dle skutecnych polozek reseni + nazev = self.request.query_params.get('nazev',None) + if nazev is not None: + queryset = queryset.filter(nazev__contains=nazev) + nadproblem = self.request.query_params.get('nadproblem',None) + if nadproblem is not None: + queryset = queryset.filter(nadproblem__pk = nadproblem) + return queryset + +class UlohaViewSet(viewsets.ModelViewSet): + serializer_class = views.UlohaSerializer + + def get_queryset(self): + queryset = m.Uloha.objects.all() + nazev = self.request.query_params.get('nazev',None) + if nazev is not None: + queryset = queryset.filter(nazev__contains=nazev) + nadproblem = self.request.query_params.get('nadproblem',None) + if nadproblem is not None: + queryset = queryset.filter(nadproblem__pk = nadproblem) return queryset + +class UlohaZadaniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet): + queryset = m.UlohaZadaniNode.objects.all() + read_serializer_class = views.UlohaZadaniNodeSerializer + write_serializer_class = views.UlohaZadaniNodeWriteSerializer + create_serializer_class = views.UlohaZadaniNodeCreateSerializer + +class ReseniNodeViewSet(ReadWriteSerializerMixin, viewsets.ModelViewSet): + queryset = m.ReseniNode.objects.all() + read_serializer_class = views.ReseniNodeSerializer + write_serializer_class = views.ReseniNodeWriteSerializer + create_serializer_class = views.ReseniNodeCreateSerializer + + + +class ProblemViewSet(viewsets.ModelViewSet): + serializer_class = views.ProblemSerializer + + def get_queryset(self): + queryset = m.Problem.objects.all() + ucel = self.request.query_params.get('ucel',None) + rocnik = self.request.query_params.get('rocnik',None) + tema = self.request.query_params.get('tema',None) + + if rocnik is not None: + queryset = queryset.filter(rocnik=rocnik) + + #if tema is not None: + + + + return queryset + diff --git a/vue_frontend/src/components/AddNewNode.vue b/vue_frontend/src/components/AddNewNode.vue index 2d837fb3..e2b4885a 100644 --- a/vue_frontend/src/components/AddNewNode.vue +++ b/vue_frontend/src/components/AddNewNode.vue @@ -1,12 +1,12 @@ @@ -24,6 +24,7 @@ export default { types: Array, where: String, refnode: Object, + tema: Object, }, data: () => ({ selected: null, @@ -47,4 +48,4 @@ export default { .addnewnode { display: inline; } - \ No newline at end of file + diff --git a/vue_frontend/src/components/CastNode.vue b/vue_frontend/src/components/CastNode.vue index 146bf5de..35390136 100644 --- a/vue_frontend/src/components/CastNode.vue +++ b/vue_frontend/src/components/CastNode.vue @@ -7,7 +7,9 @@
    -

    {{ currentText }}

    +

    {{ currentText }}

    + +
    @@ -25,6 +27,7 @@ export default { props: { item: Object, editorShow: Boolean, + editorMode: Boolean, create: Boolean, where: String, refnode: Object @@ -76,11 +79,27 @@ export default { } this.editorShow = false; + }, + deleteCast: function() { + console.log("Deleting cast"); + axios.delete('/api/castnode/'+this.item.node.id+'/' + ).then( () => { + this.loading = false; + this.$root.$emit('updateData',"castNode delete update"); + }).catch(e => { + this.errors.push(e); + }); } - } + } } diff --git a/vue_frontend/src/components/TextNode.vue b/vue_frontend/src/components/TextNode.vue index aaa02fdb..b565d229 100644 --- a/vue_frontend/src/components/TextNode.vue +++ b/vue_frontend/src/components/TextNode.vue @@ -23,7 +23,8 @@ @@ -162,6 +163,16 @@ export default { } this.editorShow = false; }, + deleteText: function() { + console.log("Deleting text"); + axios.delete('/api/textnode/'+this.item.node.id+'/' + ).then( () => { + this.loading = false; + this.$root.$emit('updateData',"textNode delete update"); + }).catch(e => { + this.errors.push(e); + }); + }, } } @@ -169,6 +180,9 @@ export default { diff --git a/vue_frontend/src/components/TreeNode.vue b/vue_frontend/src/components/TreeNode.vue index 0d82b1a6..4c8e6b2a 100644 --- a/vue_frontend/src/components/TreeNode.vue +++ b/vue_frontend/src/components/TreeNode.vue @@ -1,16 +1,19 @@ diff --git a/vue_frontend/src/components/UlohaVzorakNode.vue b/vue_frontend/src/components/UlohaVzorakNode.vue index 753a5cef..9d388a4e 100644 --- a/vue_frontend/src/components/UlohaVzorakNode.vue +++ b/vue_frontend/src/components/UlohaVzorakNode.vue @@ -1,18 +1,23 @@ @@ -26,31 +31,58 @@ export default { isResult: false, searchQuery: '', searchResults: [], - showSelect: false, selected: null, - selected_id: null + selected_id: null, + editorShow: false, } }, props: { item: Object, create: Boolean, - showSelect: Boolean, editorMode: Boolean, + editorShow: Boolean, + tema: Object, + refnode: Object, + where: String, }, mounted: function(){ - if (this.item.node.uloha === null){ + axios.defaults.headers.common['X-CSRFToken'] = this.getCookie('csrftoken'); + if (this.create){ + this.editorShow = true; + this.searchQuery = ""; + this.selected_id = null; + } else { + this.searchQuery = this.item.node.uloha.nazev; + this.selected_id = this.item.node.uloha.id; + this.selected = this.item.node.uloha; + } + if (this.item !== null && this.item.node.uloha === null){ console.log("Uloha je null!"); console.log(this.item); } - if (this.create){ - this.showSelect = true; - console.log('Creating'); - } }, methods: { + getCookie: function (name){ + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i].trim(); + // Does this cookie string begin with the name we want? + + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + }, submitSearch: function(){ if (this.searchQuery.length < 3) { return;} - var reqURL = "/api/ulohavzoraknode/?nazev="+this.searchQuery; + let nazev = "nazev="+this.searchQuery+"&"; + let nadproblem = "nadproblem="+this.tema.id+"&"; + var reqURL = "/api/uloha/?"+nazev+nadproblem; axios.get(reqURL).then( (response) => { this.searchResults = response.data.results; this.isResult = true; @@ -63,6 +95,38 @@ export default { setSelected: function(res){ this.searchQuery = res.nazev this.selected_id = res.id + this.selected = res + }, + createNode: function(){ + console.log('Creating UlohaVzorakNode'); + this.loading = true + axios.post('/api/ulohavzoraknode/',{ + refnode: this.refnode.id, + where: this.where, + uloha_id: this.selected_id, + }).then(response => { + console.log(response.data); + this.loading=false; + this.$root.$emit('updateData',"ulohaZadaniNode create update"); + }).catch( e => { + console.log(e); + }); + }, + saveNode: function(){ + console.log('Saving UlohaVzorakNode'); + this.loading = true + axios.put('/api/ulohavzoraknode/'+this.item.node.id+'/',{ + id: this.item.node.id, + uloha: this.selected_id + }).then(response => { + console.log(response.data); + this.loading=false; + this.item.node = response.data; + this.$root.$emit('updateData',"ulohaZadaniNode save update"); + }).catch( e => { + console.log(e); + }); + this.editorShow = false; } } } diff --git a/vue_frontend/src/components/UlohaZadaniNode.vue b/vue_frontend/src/components/UlohaZadaniNode.vue index 7c5f6346..32f0d93b 100644 --- a/vue_frontend/src/components/UlohaZadaniNode.vue +++ b/vue_frontend/src/components/UlohaZadaniNode.vue @@ -1,23 +1,113 @@ diff --git a/vue_frontend/src/router/index.js b/vue_frontend/src/router/index.js index aead18ba..ccb6c8c0 100644 --- a/vue_frontend/src/router/index.js +++ b/vue_frontend/src/router/index.js @@ -20,7 +20,7 @@ export default new Router({ }, { path: '/zadani/aktualni', name: 'treenode_zadani', - props: {'tnid': 23}, + props: {'tnid': 1655}, component: TreeNodeRoot }, { path: '/cislo/:cislo', diff --git a/vue_frontend/vue.config.js b/vue_frontend/vue.config.js index 8d61b6ce..e4fc61ce 100644 --- a/vue_frontend/vue.config.js +++ b/vue_frontend/vue.config.js @@ -15,7 +15,7 @@ const pages = { module.exports = { pages: pages, filenameHashing: false, - productionSourceMap: false, + productionSourceMap: true, publicPath: process.env.NODE_ENV === 'production' ? '/static/seminar/vue/' : 'http://localhost:8080/', @@ -23,6 +23,7 @@ module.exports = { chainWebpack: config => { + config.optimization.minimize(false) config.optimization .splitChunks({ cacheGroups: { @@ -33,7 +34,7 @@ module.exports = { priority: 1 }, }, - }); + }).minimize(false); Object.keys(pages).forEach(page => { config.plugins.delete(`html-${page}`);