Browse Source

Merge branch 'data_migrations' into test

middleware_test
parent
commit
99d7bd50a1
  1. 2
      Makefile
  2. 6
      data/flat.json
  3. 661
      data/sitetree_new.json
  4. 10
      mamweb/routers.py
  5. 24
      mamweb/settings_common.py
  6. 38
      mamweb/static/css/mamweb-dev.css
  7. 281
      mamweb/static/css/mamweb.css
  8. 26847
      mamweb/static/images/jakresit_1.svg
  9. 26847
      mamweb/static/images/jakresit_2.svg
  10. 26847
      mamweb/static/images/jakresit_3.svg
  11. 18
      mamweb/templates/base.html
  12. 5
      mamweb/urls.py
  13. 3
      requirements.txt
  14. 13
      seminar/admin.py
  15. 5
      seminar/forms.py
  16. 8
      seminar/models.py
  17. 18
      seminar/static/seminar/treenode_editor.js
  18. 6
      seminar/templates/seminar/archiv/cisla.html
  19. 16
      seminar/templates/seminar/jak-resit.html
  20. 12
      seminar/templates/seminar/novinky.html
  21. 23
      seminar/templates/seminar/orphanage.html
  22. 33
      seminar/templates/seminar/tematka/rozcestnik.html
  23. 77
      seminar/templates/seminar/titulnistrana.html
  24. 10
      seminar/templates/seminar/treenode.html
  25. 27
      seminar/templates/seminar/treenode_add_stub.html
  26. 18
      seminar/templates/seminar/treenode_name.html
  27. 73
      seminar/templates/seminar/treenode_recursive.html
  28. 7
      seminar/templates/seminar/vuetest.html
  29. 218
      seminar/templatetags/treenodes.py
  30. 46
      seminar/testutils.py
  31. 254
      seminar/treelib.py
  32. 18
      seminar/urls.py
  33. 1
      seminar/views/__init__.py
  34. 416
      seminar/views/views_all.py
  35. 162
      seminar/views/views_rest.py
  36. 77
      seminar/viewsets.py
  37. 22
      vue_frontend/.gitignore
  38. 5
      vue_frontend/babel.config.js
  39. 48
      vue_frontend/package.json
  40. 7
      vue_frontend/src/App.vue
  41. 50
      vue_frontend/src/components/AddNewNode.vue
  42. 86
      vue_frontend/src/components/CastNode.vue
  43. 19
      vue_frontend/src/components/CisloNode.vue
  44. 21
      vue_frontend/src/components/RocnikNode.vue
  45. 19
      vue_frontend/src/components/TemaVCisleNode.vue
  46. 174
      vue_frontend/src/components/TextNode.vue
  47. 161
      vue_frontend/src/components/TreeNode.vue
  48. 70
      vue_frontend/src/components/TreeNodeRoot.vue
  49. 74
      vue_frontend/src/components/UlohaVzorakNode.vue
  50. 23
      vue_frontend/src/components/UlohaZadaniNode.vue
  51. 12
      vue_frontend/src/main.js
  52. 28
      vue_frontend/src/router/index.js
  53. 61
      vue_frontend/vue.config.js
  54. 8544
      vue_frontend/yarn.lock

2
Makefile

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

6
data/flat.json

File diff suppressed because one or more lines are too long

661
data/sitetree_new.json

File diff suppressed because one or more lines are too long

10
mamweb/routers.py

@ -0,0 +1,10 @@
from rest_framework import routers
from seminar import viewsets as vs
router = routers.DefaultRouter()
router.register(r'ulohavzoraknode', vs.UlohaVzorakNodeViewSet,basename='ulohavzoraknode')
router.register(r'text', vs.TextViewSet)
router.register(r'textnode', vs.TextNodeViewSet)
router.register(r'castnode', vs.CastNodeViewSet)

24
mamweb/settings_common.py

@ -120,6 +120,9 @@ INSTALLED_APPS = (
'imagekit', 'imagekit',
'polymorphic', 'polymorphic',
'webpack_loader',
'rest_framework',
# MaMweb # MaMweb
'mamweb', 'mamweb',
@ -184,6 +187,27 @@ CKEDITOR_CONFIGS = {
}, },
} }
# Webpack loader
VUE_FRONTEND_DIR = os.path.join(BASE_DIR, 'vue_frontend')
WEBPACK_LOADER = {
'DEFAULT': {
'CACHE': False,
'BUNDLE_DIR_NAME': 'vue/', # must end with slash
'STATS_FILE': os.path.join(VUE_FRONTEND_DIR, 'webpack-stats.json'),
'POLL_INTERVAL': 0.1,
'TIMEOUT': None,
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
}
}
# Dajngo REST Framework
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100
}
# Comments # Comments

38
mamweb/static/css/mamweb-dev.css

@ -0,0 +1,38 @@
/*
.pink {
background-color: #ffc0cb;
}
div.borderized {
border-style: solid;
border-radius: 5px;
padding: 5px;
padding-right: 20px;
}
div.tnmenu {
float: right;
margin-right: 0px;
}
div.parent {
border-width: 2px;
}
div.children {
border-width: 1px;
}
div.node_type {
background-color: #d4d4d4;
}
.hidden-tn {
display: none;
}
/*test*/
h1 {
color: chartreuse;
}

281
mamweb/static/css/mamweb.css

@ -101,6 +101,26 @@ h6 {
color: black; color: black;
} }
.button {
margin: 10px 0px 10px 0px;
padding: 4px 0; /*vertikální centování textu*/
text-align: center;
background-color: #e84e10;
color: #fffbf6;
font-size: 150%;
font-weight: bold;
font-variant: small-caps;
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
}
.button:hover {
position: relative;
top: 2px;
left:2px;
background-color: #df490e;
;
}
.org-logged-in .mam-text-plugin { .org-logged-in .mam-text-plugin {
border: dashed 1px #6a0043; border: dashed 1px #6a0043;
@ -286,7 +306,11 @@ ul.submenu li>a:hover {
color: black; color: black;
} }
ul.menu li.active>a, ul.submenu li.active>a { ul.menu li.active>a {
color: #f9d59e;
}
ul.submenu li.active>a {
color: black; color: black;
} }
@ -367,13 +391,82 @@ input[type="file"] {
} }
.field-with-comment:hover span.field-comment{ .field-with-comment:hover span.field-comment{
display:block; display: block;
} }
input { input {
margin: 5px; margin: 5px;
} }
/* titulni stranka */
.titulnistrana {
display: flex;
text-align: justify;
}
.titulnistrana h1 {
text-align: center;
}
.zjistit_vic{
text-align: center;
margin-bottom: 30px;
}
.zjistit_vic hr {
display: none;
}
.graf-svg {
display: flex;
}
#svg-graf {
width: 100%;
height: auto;
margin: 30px;
}
.titulnistrana_obsah {
width: 66%;
}
.vitej_titulka, .temata_titulka {
width: 49%;
padding: 10px;
display: table-cell;
}
.titulnistrana_novinky {
width: 33%;
padding: 10px;
}
.novinka_obrazek {
margin: 10px 0px 10px 0px;
width: 100%;
}
.novinka_datum {
font-weight: bold;
}
.novinka_autor {
text-align: right;
font-style: italic;
}
div.org-text {
font-style: italic;
}
div.odpocet {
margin: 20px;
text-align: center;
}
/********************** /**********************
* Zmenšování displeje * Zmenšování displeje
***********************/ ***********************/
@ -403,42 +496,22 @@ input {
width: 100%; width: 100%;
} }
div.novinky{ ul.menu {
max-width: 100%; font-size: 90%;
margin-left: auto; margin-top: -7px;
margin-right: auto;
float: none;
}
div.graf{
width: 70%;
float: none;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
}
#svg-graf{
width: 100%;
height: auto;;
} }
ul.menu { ul.menu li {
font-size: 90%; margin-top: 10px; /* posunutí textu hlavního menu níže */
margin-top: -7px; }
}
ul.menu li {
margin-top: 10px; /* posunutí textu hlavního menu níže */
}
ul.submenu li {
margin-top: 0px; /* aby se spolu s textem hlavního menu neposunoval níže i text submenu */
}
ul.submenu { ul.submenu li {
margin-top: 8px; /* mezera mezi hlavním menu a submenu */ margin-top: 0px; /* aby se spolu s textem hlavního menu neposunoval níže i text submenu */
} }
ul.submenu {
margin-top: 8px; /* mezera mezi hlavním menu a submenu */
}
} }
@ -470,11 +543,53 @@ ul.submenu {
display: inline-grid; display: inline-grid;
max-width: 300px; max-width: 300px;
} }
/* titulni stranka */
.titulnistrana {
display: block;
}
.graf {
padding-top: 40px;
}
.titulnistrana_obsah {
width: 100%;
}
.vitej_titulka, .temata_titulka {
width: 49%;
padding: 10px;
display: table-cell;
}
.titulnistrana_novinky {
width: 100%;
max-width: 500px;
padding: 10px;
margin: auto;
}
#svg-graf {
width: 100%;
max-width: 500px;
padding: 10px;
margin: auto;
}
.zjistit_vic hr {
display: flex;
}
} }
/* malý tablet, mobil */ /* malý tablet, mobil */
@media (max-width: 650px) { @media (max-width: 650px) {
.no-mobile{
display: none;
}
#hide-if-small.login-bar-flatpage { #hide-if-small.login-bar-flatpage {
display: none; display: none;
} }
@ -611,18 +726,34 @@ ul.submenu {
text-align: justify; text-align: justify;
} }
div.novinky { table.form td, table.form tr {
max-width: 100%; display: inherit;
float: none; }
}
/* titulni stranka */
.titulnistrana {
display: block;
}
div.graf { .graf {
padding-top: 40px;
}
.titulnistrana_obsah {
width: 100%; width: 100%;
} }
table.form td, table.form tr { .vitej_titulka, .temata_titulka {
display: inherit; width: 100%;
} padding: 10px;
display: block;
}
.titulnistrana_novinky {
width: 100%;
padding: 10px;
}
} }
@ -704,11 +835,6 @@ div.org_email {
backface-visibility: hidden; backface-visibility: hidden;
} }
/* Style the front side (fallback if image is missing) */
.flip-card-front {
background-color: #bbb;
}
div.flip-card-foto img { div.flip-card-foto img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -724,6 +850,10 @@ div.flip-card-foto img {
padding-top: 20px; padding-top: 20px;
} }
#archiv.flip-card-back {
background-color: white;
}
/* karty archiv */ /* karty archiv */
div.popis_rocniku { div.popis_rocniku {
@ -920,46 +1050,8 @@ div.cislo_odkazy ul {
} }
/* titulni stranka */ /**/
.zjistit_vic{
text-align: center;
}
.graf{
float: left;
}
.novinky{
float: right;
max-width: 42%;
}
.novinka_obrazek img {
margin-bottom: 15px;
}
div.novinka_obrazek {
width: 100%;
max-width: 400px; /*FIXME*/
}
div.org-text {
font-style: italic;
}
div.nahledy_cisel {
float: right;
height: 297px;
width: 420px;
position: relative;
margin-right: 10%;
margin-bottom: 50px;
}
div.nahledy_cisel div, div.nahledy_cisel img {
position: absolute;
}
ul.form { ul.form {
list-style-type: none; list-style-type: none;
padding-left: 0px; padding-left: 0px;
@ -984,3 +1076,20 @@ p.gdpr {
div.gdpr { div.gdpr {
font-size: 6pt; font-size: 6pt;
} }
/* Jak řešit */
.jakresit img {
width: 33%;
padding: 10px;
filter: none;
}
@media(max-width: 860px) {
.jakresit img {
margin: auto;
display: grid;
width: 100%;
max-width: 360px;
}
}

26847
mamweb/static/images/jakresit_1.svg

File diff suppressed because it is too large

After

Width:  |  Height:  |  Size: 1.8 MiB

26847
mamweb/static/images/jakresit_2.svg

File diff suppressed because it is too large

After

Width:  |  Height:  |  Size: 1.8 MiB

26847
mamweb/static/images/jakresit_3.svg

File diff suppressed because it is too large

After

Width:  |  Height:  |  Size: 1.8 MiB

18
mamweb/templates/base.html

@ -6,7 +6,8 @@
<title>{% block title %}{% block nadpis1a %}{% endblock %} – Korespondenční seminář M&amp;M{% endblock title %}</title> <title>{% block title %}{% block nadpis1a %}{% endblock %} – Korespondenční seminář M&amp;M{% endblock title %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{% static 'images/MATFYZ_MM_barevne.svg' %}" type="image/x-icon"> <link rel="shortcut icon" href="{% static 'images/MATFYZ_MM_barevne.svg' %}" type="image/x-icon">
{% render_block "css" %} {% render_block css %}
{% block custom_css %}{% endblock %}
<link href="{% static 'css/bootstrap-theme.css' %}" rel="stylesheet"> <link href="{% static 'css/bootstrap-theme.css' %}" rel="stylesheet">
<link href="{% static 'css/bootstrap.css' %}" rel="stylesheet"> <link href="{% static 'css/bootstrap.css' %}" rel="stylesheet">
<link href="{% static 'css/mamweb.css' %}" rel="stylesheet"> <link href="{% static 'css/mamweb.css' %}" rel="stylesheet">
@ -56,14 +57,17 @@
<div class='col-md-12'> <div class='col-md-12'>
<a href='/'> <a href='/'>
<div id="title" >M&M - korespondenční seminář a časopis MFF&nbspUK</div> <div id="title" >M&M - korespondenční seminář a časopis MFF&nbspUK</div>
<div id="header" style="background-image: url('{{ fotka }}')"> <div id="header">
<div class="no-mobile" style="background-image: url('{{ fotka }}')">
{# TODO style=… není fancy řešení, ale u <img> se bojím, že mi to rozbije vzhled #} {# TODO style=… není fancy řešení, ale u <img> se bojím, že mi to rozbije vzhled #}
{% sitetree_menu from "main_menu" include "trunk" template "logo.html" %} {% sitetree_menu from "main_menu" include "trunk" template "logo.html" %}
</div>
<img class="logo-mobile" src="{% static 'images/logo-mobile.svg' %}" /> <img class="logo-mobile" src="{% static 'images/logo-mobile.svg' %}" />
</div> </div>
</a> </a>
</div> </div>
</div> </div>
<div class='row'> <div class='row'>
<div class='col-md-12'> <div class='col-md-12'>
@ -73,6 +77,9 @@
{# ========= MENU MOBILE ========== #} {# ========= MENU MOBILE ========== #}
</div>
</div>
<!--Navbar--> <!--Navbar-->
<nav class="nav-button"> <nav class="nav-button">
@ -95,13 +102,15 @@
{# ========= END MENU ========== #} {# ========= END MENU ========== #}
<div class='row'>
<div class='row content'> <div class='row content'>
<div class='col-md-12'> <div class='col-md-12'>
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
</div> </div>
</div>
</div> </div>
<div class='row'> <div class='row'>
<div class='col-md-12'> <div class='col-md-12'>
<div id="footer"> <div id="footer">
@ -110,6 +119,7 @@
<p class="license-mobile">Korespondenční seminář M&M organizují převážně studenti <a href="https://www.mff.cuni.cz/">MFF UK</a>. Organizaci semináře a vydávání časopisu podporuje <a href="https://jcmf.cz/">Jednota českých matematiků a fyziků</a>. S obsahem webu M&amp;M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p> <p class="license-mobile">Korespondenční seminář M&M organizují převážně studenti <a href="https://www.mff.cuni.cz/">MFF UK</a>. Organizaci semináře a vydávání časopisu podporuje <a href="https://jcmf.cz/">Jednota českých matematiků a fyziků</a>. S obsahem webu M&amp;M je možné nakládat dle licence <a href="https://creativecommons.org/licenses/by/3.0/cz/">Creative Commons Attribution 3.0</a>.</p>
</div> </div>
</div> </div>
</div> </div>
<script src="{% static 'js/bootstrap.js' %}"></script> <script src="{% static 'js/bootstrap.js' %}"></script>

5
mamweb/urls.py

@ -6,6 +6,8 @@ from django.views.generic.base import TemplateView
from django import views from django import views
from django.urls import path # As per docs. from django.urls import path # As per docs.
from .routers import router
urlpatterns = [ urlpatterns = [
# Admin a nastroje # Admin a nastroje
@ -25,6 +27,9 @@ urlpatterns = [
path('comments_dj/', include('django_comments.urls')), path('comments_dj/', include('django_comments.urls')),
path('comments_fl/', include('fluent_comments.urls')), path('comments_fl/', include('fluent_comments.urls')),
# REST API
path('api/', include(router.urls)),
] ]
# This is only needed when using runserver. # This is only needed when using runserver.

3
requirements.txt

@ -28,6 +28,9 @@ django-imagekit
django-polymorphic django-polymorphic
django-sitetree django-sitetree
django_reverse_admin django_reverse_admin
django-rest-framework
django-webpack-loader
django-rest-polymorphic
# Comments # Comments
akismet==1.0.1 akismet==1.0.1

13
seminar/admin.py

@ -107,6 +107,8 @@ class TreeNodeAdmin(PolymorphicParentModelAdmin):
m.PohadkaNode, m.PohadkaNode,
m.UlohaVzorakNode, m.UlohaVzorakNode,
m.TextNode, m.TextNode,
m.CastNode,
m.OrgTextNode,
] ]
actions = ['aktualizuj_nazvy'] actions = ['aktualizuj_nazvy']
@ -160,6 +162,17 @@ class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.TextNode base_model = m.TextNode
show_in_index = True show_in_index = True
@admin.register(m.CastNode)
class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.CastNode
show_in_index = True
fields = ('nadpis',)
@admin.register(m.OrgTextNode)
class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.OrgTextNode
show_in_index = True
admin.site.register(m.Nastaveni, SingletonModelAdmin) admin.site.register(m.Nastaveni, SingletonModelAdmin)
admin.site.register(m.Novinky) admin.site.register(m.Novinky)

5
seminar/forms.py

@ -277,3 +277,8 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
) )
class NahrajObrazekKTreeNoduForm(forms.ModelForm):
class Meta:
model = m.Obrazek
fields = ('na_web',)

8
seminar/models.py

@ -28,7 +28,6 @@ from reversion import revisions as reversion
from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode from seminar.utils import roman, FirstTagParser # Pro získání úryvku z TextNode
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované) from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
from seminar.treelib import safe_pred
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
@ -706,6 +705,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
(STAV_SMAZANY, 'Smazaný'), (STAV_SMAZANY, 'Smazaný'),
] ]
stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH) stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH)
# Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek)
zamereni = TaggableManager(verbose_name='zaměření', zamereni = TaggableManager(verbose_name='zaměření',
help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True) help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True)
@ -745,6 +745,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
def verejne(self): def verejne(self):
# FIXME: Tohle se liší podle typu problému, má se udělat polymorfně. # FIXME: Tohle se liší podle typu problému, má se udělat polymorfně.
# Zatím je tu jen dummy fail-safe default: nic není veřejné. # Zatím je tu jen dummy fail-safe default: nic není veřejné.
# Doporučené řešení: dělat tohle podle stavu problému a veřejnosti čísla, ve kterém je
return False return False
# FIXME: Tohle je blbost # FIXME: Tohle je blbost
return (self.cislo_zadani and self.cislo_zadani.verejne()) return (self.cislo_zadani and self.cislo_zadani.verejne())
@ -843,9 +844,7 @@ class Text(SeminarModelBase):
tn.save() tn.save()
def __str__(self): def __str__(self):
parser = FirstTagParser() return str(self.na_web)[:20]
parser.feed(str(self.na_web))
return parser.firstTag
class Uloha(Problem): class Uloha(Problem):
class Meta: class Meta:
@ -1361,6 +1360,7 @@ class MezicisloNode(TreeNode):
# TODO: Využít TreeLib # TODO: Využít TreeLib
def aktualizuj_nazev(self): def aktualizuj_nazev(self):
from seminar.treelib import safe_pred
if safe_pred(self) is not None: if safe_pred(self) is not None:
if (self.prev.get_real_instance_class() != CisloNode and if (self.prev.get_real_instance_class() != CisloNode and
self.prev.get_real_instance_class() != MezicisloNode): self.prev.get_real_instance_class() != MezicisloNode):

18
seminar/static/seminar/treenode_editor.js

@ -0,0 +1,18 @@
function showSelectedItemForm(sel,id){
var option;
var name;
var div;
Array.from(sel.options).forEach(function(option){
console.log(option);
name = 'pridat-'+option.value+'-'+id;
div = document.getElementById(name);
console.log(div);
div.style.display = 'none';
});
name = sel.options[sel.selectedIndex].value;
name = 'pridat-'+name+'-'+id;
div = document.getElementById(name);
console.log(div);
div.style.display = 'block';
}

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

@ -8,10 +8,6 @@
{% endblock %}{% endblock %} {% endblock %}{% endblock %}
</h2> </h2>
<!-- <div class='nahledy_cisel'>
{% autoescape off %}{{ nahledy }}{% endautoescape %}
</div>-->
{% for rocnik, url_png in object_list.items %} {% for rocnik, url_png in object_list.items %}
<div class="rocnik_pole"> <div class="rocnik_pole">
@ -33,7 +29,7 @@
</div> </div>
</div> </div>
<div class="flip-card-back"> <div class="flip-card-back" id="archiv">
<div class="popis_rocniku"> <div class="popis_rocniku">
Jednotlivá čísla: Jednotlivá čísla:
<ul> <ul>

16
seminar/templates/seminar/jak-resit.html

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% load humanize %}
{% load staticfiles %}
{% block content %}
<div class=jakresit>
<img class="jakresit_obrazek" alt="" src="{% static 'images/jakresit_1.svg' %}" />
<img class="jakresit_obrazek" alt="" src="{% static 'images/jakresit_2.svg' %}" />
<img class="jakresit_obrazek" alt="" src="{% static 'images/jakresit_3.svg' %}" />
</div>
{% endblock %}

12
seminar/templates/seminar/novinky.html

@ -9,7 +9,7 @@
{% endif %} {% endif %}
{% if novinka.zverejneno or user.je_org %} {% if novinka.zverejneno or user.je_org %}
{# datum #} {# datum #}
<div><b>{{novinka.datum}}</b></div> <div class=novinka_datum>{{novinka.datum}}</div>
{# text #} {# text #}
{{ novinka.text | safe }} {{ novinka.text | safe }}
{# obrazek #} {# obrazek #}
@ -25,12 +25,12 @@
</div> </div>
{% endif %} {% endif %}
{# autor #} {# autor #}
<div class=novinky_name><p>{{novinka.autor.first_name}} <div class=novinka_autor>
{% if novinka.autor.organizator.prezdivka%} {{novinka.autor.osoba.jmeno}}
&bdquo;{{novinka.autor.organizator.prezdivka}}&ldquo; {% if novinka.autor.osoba.prezdivka%}
&bdquo;{{novinka.autor.osoba.prezdivka}}&ldquo;
{% endif %} {% endif %}
{{novinka.autor.last_name}} {{novinka.autor.osoba.prijmeni}}
</p>
</div> </div>
{% endif %} {% endif %}
{% if not novinka.zverejneno and user.je_org %} {% if not novinka.zverejneno and user.je_org %}

23
seminar/templates/seminar/orphanage.html

@ -0,0 +1,23 @@
{% extends "seminar/archiv/base.html" %}
{% load staticfiles %}
{% load sekizai_tags %}
{# toto z nejakeho duvodu nefunguje #}
{% addtoblock css %}
dfsdfs
<link rel="stylesheet" type="text/css" href="{% static 'css/mamweb-dev.css' %}" />
{% endaddtoblock "css" %}
{% block custom_css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/mamweb-dev.css' %}" />
{% endblock custom_css %}
{% load comments %}
{% block content %}
<ul>
{% for obj in object_list %}
<li>{{obj}} (id {{obj.id}})</li>
{% endfor %}
</ul>
{% endblock content %}

33
seminar/templates/seminar/tematka/rozcestnik.html

@ -1,14 +1,25 @@
{% extends "base.html" %}
{% block content %}
<p style="text-align:justify">Témata jsou texty nejen z oblasti matematiky, fyziky a informatiky, které popisují nějaký problém a jsou doprovázeny návodnými úlohami. Vaším úkolem je&nbsp;zamyslet se nad daným problémem a sepsat vaše úvahy ve formě krátkého textu.</p>
<p style="text-align:justify"><a href="/co-je-MaM/jak-resit/">Jak řešit téma?</a></p>
<p style="text-align:justify">&nbsp;</p>
<h1>Aktuální témata</h1>
{% for tematko in tematka %} {% for tematko in tematka %}
<h1>{{tematko.nazev}}</h1> <h2>{{tematko.nazev}}</h2>
<div class="tematko-obrazek">
{% if tematko.obrazek %}
<a href="{{tematko.obrazek.url}}" class="ref-tema-obr"><img src="{{tematko.obrazek.url}}" height="{{tematko.obrazek.height}}" alt="{{tematko.nazev}}"></a>
{% else %} {# pokud témátko nemá fotku, zobrazuje se defaultní obrázek #}
{% load static %} <img src="{% static 'images/no-photo.png' %}" height=200px alt="{{tematko.nazev}}">
{% endif %}
</div>
<p>{{tematko.abstrakt}}</p> <p>{{tematko.abstrakt}}</p>
<ul>
{% for cislo in tematko.cisla %}
<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{cislo.0.1}}">{{cislo.0.0}}</a></li>
<ul>
{% for odkaz in cislo.1 %}
<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{odkaz.1}}">{{odkaz.0}}</a></li>
{% endfor %}
</ul>
{% endfor %}
</ul>
{% endfor %} {% endfor %}
{% endblock %}

77
seminar/templates/seminar/titulnistrana.html

@ -5,32 +5,53 @@
{% block content %} {% block content %}
{# Uvitaci text #}
{% if nejblizsi_deadline %}
<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á:
{{nejblizsi_deadline|timeuntil}}</big></b></p>
</div>
{% endif %}
<div class=titulnistrana>
<div class="titulnistrana_obsah">
<div class="vitej_titulka">
<h1> <h1>
{% block nadpis1a %}{% block nadpis1b %} {% block nadpis1a %}
Vítej! Vítej
{% endblock %}{% endblock %} {% endblock %}
mezi námi
</h1> </h1>
<p>
M&amp;M je korespondenční seminář. Několikrát do roka zdarma vydáváme časopis a v něm zajímavé podněty k přemýšlení. Ty na ně můžeš reagovat.<br>
M&amp;M je taky soutěž. Můžeš vyhrát knížky, deskovky nebo dokonce dort. Můžeš se dostat na matfyz bez přijímaček. A především s námi můžeš jet na skvělé soustředění. <div>
</p> 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.
<div class="novinky"> <a href="auth/registrace"> <div class="button"> Zaregistruj se! </div> </a> {# FIXME odkaz #}
{% if dead %} M&amp;M je taky soutěž. Za svá řešení dostaneš body a můžeš vyhrát zajímavé ceny, dostat se
<div class="odpocet"> na Matfyz bez přijímaček a především, můžeš s námi jet na skvělé soustředění.
<p><b>Do konce <a href="/zadani/aktualni/">odeslání řešení</a> {% if deadline_soustredeni %}(pro účast na soustředění) {% endif %}zbývá:<br> <a href="cojemam/odmeny"> <div class="button"> Co můžeš vyhrát? </div> </a> {# FIXME odkaz #}
<big>{{ted|timesince:dead}}</big></b></p>
</div> </div>
{% endif %}
</div>
{# Novinky #} <div class="temata_titulka">
<h1>Novinky</h1>
{% include 'seminar/novinky.html' %}
<a href='/stare-novinky/'>Archiv novinek</a> <h1>
Řeš témata!
</h1>
<div>
Přidej se k nám! Pusť se do řešení témát.
{% for tema in aktualni_temata %}
<a href="{{ tema.url }}"> <div class="button"> {{ tema.nazev }} </div> </a>
{% endfor %}
</div>
</div> </div>
<div class="graf"> <div class="graf">
<div class="graf-svg"> <div class="graf-svg">
@ -38,8 +59,24 @@ M&amp;M je korespondenční seminář. Několikrát do roka zdarma vydáváme č
</div> </div>
<span class="zjistit_vic"> <span class="zjistit_vic">
<h2><a href="/co-je-MaM/uvod/">Zjistit víc!</a></h2> <h2><a href="/co-je-MaM/uvod/">Zjisti víc!</a></h2>
<hr>
</span> </span>
</div> </div>
</div>
<div class="titulnistrana_novinky">
{# Novinky #}
<h1>Co je nového?</h1>
{% include 'seminar/novinky.html' %}
<a href='/stare-novinky/'>Archiv novinek</a>
</div>
</div>
{% endblock %} {% endblock %}

10
seminar/templates/seminar/treenode.html

@ -1,10 +1,12 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% load comments %}
{% block content %} {% block content %}
<div id="app">
<app></app>
</div>
{% render_bundle 'chunk-vendors' %}
{% render_bundle 'vue_app_01' %}
{%with obj=tnldata depth=1 template_name="seminar/treenode_recursive.html" %}
{%include template_name%}
{%endwith%}
{% endblock content %} {% endblock content %}

27
seminar/templates/seminar/treenode_add_stub.html

@ -0,0 +1,27 @@
{% load treenodes %}
{% if kam_slug == "syn" %}
{% appendableChildren obj as dostupne_typy %}
{% else %}
{% appendableChildren obj.parent as dostupne_typy %}
{% endif %}
{# ulohaZadani ulohaVzorak Reseni Cast Text #}
{% if dostupne_typy %}
<div class="pink">Přidat {{kam}}
<select name="pridat-typ-{{obj.node.id}}-{{kam_slug}}" onchange="showSelectedItemForm(this,'{{obj.node.id}}-{{kam_slug}}')">
{% for typ in dostupne_typy %}
<option value="{{typ.0}}">{{typ.1}}</option>
{% endfor %}
</select>
<div class="hidden-tn" id="pridat-castNode-{{obj.node.id}}-{{kam_slug}}">
Nadpis: <input name="pridat-cast-{{obj.node.id}}-{{kam_slug}}" type="text">
<button action="submit" formaction="{%url 'treenode_pridat' obj.node.id kam_slug%}">Přidat</button>
</div>
<div class="hidden-tn" id="pridat-textNode-{{obj.node.id}}-{{kam_slug}}"> Vytvořit</div>
<div class="hidden-tn" id="pridat-reseniNode-{{obj.node.id}}-{{kam_slug}}">Vytvořit, Tady bude autocomplete na reseniNode</div>
<div class="hidden-tn" id="pridat-ulohaZadaniNode-{{obj.node.id}}-{{kam_slug}}">Vytvořit zadání</div>
<div class="hidden-tn" id="pridat-ulohaVzorakNode-{{obj.node.id}}-{{kam_slug}}">Vytvořit vzorák k: Tady bude autocomplete na problémy k aktuálnímu kontextu</div>
</div>
{% endif %}{# appendablebleChildren #}

18
seminar/templates/seminar/treenode_name.html

@ -0,0 +1,18 @@
{% load treenodes %}
{% if obj.node|isRocnik %}
<h{{depth}}> Ročník {{obj.node.rocnik}} </h{{depth}}>
{% elif obj.node|isCislo %}
<h{{depth}}> Číslo {{obj.node.cislo}} </h{{depth}}>
{% elif obj.node|isTemaVCisle %}
<h{{depth}}> Téma {{obj.node.tema.nazev}} </h{{depth}}>
{% elif obj.node|isUlohaZadani %}
<h{{depth}}>Úloha {{obj.node.uloha.kod_v_rocniku}} ({{obj.node.uloha.max_body}} b)</h{{depth}}>
{% elif obj.node|isUlohaVzorak %}
<h{{depth}}>Řešení: {{obj.node.uloha.kod_v_rocniku}}</h{{depth}}>
{% elif obj.node|isCast %}
<h{{depth}}> {{obj.node.nadpis}} </h{{depth}}>
{% elif obj.node|isText %}
{{obj.node.text.na_web}}
{% else %}
Objekt jiného typu {{obj.node}}
{% endif %}

73
seminar/templates/seminar/treenode_recursive.html

@ -1,28 +1,55 @@
{% load treenodes %} {% load treenodes %}
{# <b>{{depth}}</b> #} {# <b>{{depth}}</b> #}
<div> <div class="borderized parent">
{% if obj.node|isRocnik %} <div class="node_type">
<h{{depth}}> Ročník {{obj.node.rocnik}} </h{{depth}}> {{obj.node}}
{% elif obj.node|isCislo %} {{obj.node.id}}
<h{{depth}}> Číslo {{obj.node.cislo}} </h{{depth}}> {% if obj.node|deletable %}
{% elif obj.node|isTemaVCisle %} <button type="submit" formaction="{%url 'treenode_smazat' obj.node.id%}">Smazat</button>
<h{{depth}}> Téma {{obj.node.tema.nazev}} </h{{depth}}> {% endif %}
{% elif obj.node|isUlohaZadani %} {% if obj.parent and obj.parent|editableSiblings %}
<h{{depth}}>Úloha {{obj.node.uloha.kod_v_rocniku}} ({{obj.node.uloha.max_body}} b)</h{{depth}}> <button type="submit" formaction="{%url 'treenode_odvesitpryc' obj.node.id%}">Odvěsit pryč ze stromu {{obj.parent.node}}</button>
{% elif obj.node|isUlohaVzorak %} {% endif %}
<h{{depth}}>Řešení: {{obj.node.uloha.kod_v_rocniku}}</h{{depth}}> {% if obj|canPodvesitPred %}
{% elif obj.node|isText %} <button type="submit" formaction="{%url 'treenode_podvesit' obj.node.id 'pred'%}">Podvěsit pod předchozí</button> - nejsou testovací data
{{obj.node.text.na_web}} {% endif %}
{% else %} {% if obj|canPodvesitZa %}
Objekt jiného typu {{obj.node}} <button type="submit" formaction="{%url 'treenode_podvesit' obj.node.id 'za'%}">Podvěsit pod následující</button> - nejsou testovací data
{% endif %}
</div>
{% if False %}
<div class="node_move">
FIXME: není zatím implementováno
<button>Zvyš úroveň nadpisu</button> - nejsou testovací data
</div>
{% endif %} {% endif %}
{%if obj.children %} {% include "seminar/treenode_name.html" %}
<div> {%if obj.children %}
{%for ch in obj.children %} <div class="borderized children">
{%with obj=ch depth=depth|add:"1" template_name="seminar/treenode_recursive.html" %}
{%include template_name%} {% with kam="před" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{%endwith%} {%for ch in obj.children %}
{%endfor%}
{# ----------- Vypisujeme podstrom ----------#}
{%with obj=ch depth=depth|add:"1" %} {%include "seminar/treenode_recursive.html" %} {%endwith%}
{# ----------- Přidáváme mezi syny / za posledního -------- #}
{% if forloop.last %}
{% with kam="za" kam_slug="za" obj=ch %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% else %}
{% with kam="mezi" obj=ch kam_slug="za" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% endif %}
{# ----------- Prohazujeme sousedy ----------#}
<div class="pink">
{% if not forloop.last and ch|editableSiblings %}
<button type="submit" formaction="{%url 'treenode_prohodit' ch.node.id%}">Prohodit ^ a v</button>
{% endif %}
</div>
{% endfor %}
</div> </div>
{%endif%} {% else %}
{# ----------- Přidáváme prvního syna ----------#}
{% with kam="jako syna" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{%endif%}
</div> </div>

7
seminar/templates/seminar/vuetest.html

@ -0,0 +1,7 @@
{% load render_bundle from webpack_loader %}
<div id="app">
<app></app>
</div>
{% render_bundle 'chunk-vendors' %}
{% render_bundle 'vue_app_01' %}

218
seminar/templatetags/treenodes.py

@ -1,49 +1,243 @@
from django import template from django import template
from enum import Enum
import seminar.models as m import seminar.models as m
register = template.Library() register = template.Library()
@register.filter
def nodeType(value):
if isinstance(value,RocnikNode): return "Ročník"
if isinstance(value,CisloNode): return "Číslo"
if isinstance(value,CastNode): return "Část"
if isinstance(value,TextNode): return "Text"
if isinstance(value,TemaVCisleNode): return "Téma v čísle"
if isinstance(value,KonferaNode): return "Konfera"
if isinstance(value,ClanekNode): return "Článek"
if isinstance(value,UlohaVzorakNode): return "Vzorák"
if isinstance(value,UlohaZadaniNode): return "Zadání úlohy"
if isinstance(value,PohadkaNode): return "Pohádka"
### NASLEDUJICI FUNKCE SE POUZIVAJI VE views_all.py V SEKCI PRIPRAVJICI TNLData
### NEMAZAT, PRESUNOUT S TNLDaty NEKAM BOKEM
@register.filter @register.filter
def isRocnik(value): def isRocnik(value):
return isinstance(value, m.RocnikNode) return isinstance(value, m.RocnikNode)
@register.filter @register.filter
def isCislo(value): def isCislo(value):
return isinstance(value, m.CisloNode) return isinstance(value, m.CisloNode)
@register.filter @register.filter
def isCast(value): def isCast(value):
return isinstance(value, m.CastNode) return isinstance(value, m.CastNode)
@register.filter @register.filter
def isText(value): def isText(value):
return isinstance(value, m.TextNode) return isinstance(value, m.TextNode)
@register.filter @register.filter
def isTemaVCisle(value): def isTemaVCisle(value):
return isinstance(value, m.TemaVCisleNode) return isinstance(value, m.TemaVCisleNode)
@register.filter @register.filter
def isKonfera(value): def isKonfera(value):
return isinstance(value, m.KonferaNode) return isinstance(value, m.KonferaNode)
@register.filter @register.filter
def isClanek(value): def isClanek(value):
return isinstance(value, m.ClanekNode) return isinstance(value, m.ClanekNode)
@register.filter @register.filter
def isUlohaVzorak(value): def isUlohaVzorak(value):
return isinstance(value, m.UlohaVzorakNode) return isinstance(value, m.UlohaVzorakNode)
@register.filter @register.filter
def isUlohaZadani(value): def isUlohaZadani(value):
return isinstance(value, m.UlohaZadaniNode) return isinstance(value, m.UlohaZadaniNode)
@register.filter @register.filter
def isPohadka(value): def isPohadka(value):
return isinstance(value, m.PohadkaNode) return isinstance(value, m.PohadkaNode)
@register.filter
def isReseni(value):
return False
# return isinstance(value, m.OtisteneReseniNode)
@register.filter
def isOrgText(value):
return False
# return isinstance(value, m.OrgTextNode)
###
#@register.filter
#def podvesitelneNody(value):
# if isText()
@register.filter
def deletable(value):
if isTemaVCisle(value):
return True
if isOrgText(value):
return True
if isReseni(value):
return True
if isUlohaZadani(value):
return True
if isUlohaVzorak(value):
return True
if isCast(value):
return True
if isText(value):
return True
return False
@register.filter
def editableSiblings(value):
if isCast(value.node):
return True
if isText(value.node):
return True
if isReseni(value.node) and value.tema_in_path:
return True
if isUlohaZadani(value.node) and value.tema_in_path:
return True
if isUlohaVzorak(value.node) and value.tema_in_path:
return True
return False
@register.filter
def editableChildren(value):
if isRocnik(value.node):
return False
if isCislo(value.node):
return False
if isText(value.node):
return False
return True
@register.filter
def textOnlySubtree(value):
text_only = True
if isText(value.node):
return True
if not isCast(value.node):
return False
for ch in value.children:
if not textOnlySubtree(ch):
return False
return True
def canPodvesit(obj,new_parent):
if isCast(new_parent.node):
# print("Lze",obj,new_parent)
return True
if textOnlySubtree(obj):
# print("Lze",obj,new_parent)
return True
return False
@register.filter
def canPodvesitZa(value):
if not value.index or value.index+1 >= len(value.parent.children):
return False
new_parent = value.parent.children[value.index+1]
return canPodvesit(value,new_parent)
@register.filter
def canPodvesitPred(value):
if not value.index or value.index <= 0:
return False
new_parent = value.parent.children[value.index-1]
return canPodvesit(value,new_parent)
class NodeTypes(Enum):
ROCNIK = ('rocnikNode','Ročník')
CISLO = ('cisloNode', 'Číslo')
MEZICISLO = ('mezicisloNode', 'Mezičíslo')
CAST = ('castNode', 'Část')
TEXT = ('textNode', 'Text')
TEMAVCISLE = ('temaVCisleNode', 'Téma v čísle')
RESENI = ('reseniNode','Řešení')
ULOHAZADANI = ('ulohaZadaniNode','Zadání')
ULOHAVZORAK = ('ulohaVzorakNode','Vzorák')
POHADKA = ('pohadkaNode','Pohádka')
ORGTEXT = ('orgText','Orgtext')
@register.simple_tag
def appendableChildren(value):
print(value)
print(value.node)
print(isUlohaZadani(value.node))
if isTemaVCisle(value.node):
return (NodeTypes.RESENI.value[0],
NodeTypes.ULOHAZADANI.value[0],
NodeTypes.ULOHAVZORAK.value[0],
NodeTypes.CAST.value[0],
NodeTypes.TEXT.value[0],
)
if isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node):
print("Text/Cast")
return (NodeTypes.CAST.value[0],
NodeTypes.TEXT.value[0],
)
if isCast(value.node):
return appendableChildren(value.parent)
return []
@register.simple_tag
def canAppendReseni(value):
if isTemaVCisle(value.node):
return True
if isCast(value.node):
return canAppendReseni(value.parent)
return False
@register.simple_tag
def canAppendUlohaZadani(value):
if isTemaVCisle(value.node):
return True
if isCast(value.node):
return canAppendUlohaZadani(value.parent)
return False
@register.simple_tag
def canAppendUlohaVzorak(value):
if isTemaVCisle(value.node):
return True
if isCast(value.node):
return canAppendUlohaVzorak(value.parent)
return False
@register.simple_tag
def canAppendCast(value):
if isTemaVCisle(value.node) or isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node):
return True
if isCast(value.node):
return canAppendCast(value.parent)
return False
@register.simple_tag
def canAppendText(value):
if isTemaVCisle(value.node) or isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node):
return True
if isCast(value.node):
return canAppendText(value.parent)
return False
#@register.filter
#def is(value):
# return
#
# NodeTypes..value,
#@register.filter #@register.filter
#def isOtisteneReseniNode(value): #def is(value):
# return isinstance(value, m.OtisteneReseniNode) # return

46
seminar/testutils.py

@ -244,8 +244,8 @@ def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi
na_web = text, na_web = text,
do_cisla = text, do_cisla = text,
) )
zad = TextNode.objects.create(text = text_zadani) zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode)
uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad) uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode)
p.ulohazadaninode = uloha_zadani p.ulohazadaninode = uloha_zadani
otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani) otec_syn(cisla[poradi_cisla-2-1].cislonode, uloha_zadani)
@ -266,8 +266,8 @@ def gen_vzoroveho_reseni_ulohy(rnd, organizatori, uloha, pocet_opravovatelu):
na_web = obsah, na_web = obsah,
do_cisla = obsah do_cisla = obsah
) )
vzorak = TextNode.objects.create(text = text_vzoraku) vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak) uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha.ulohavzoraknode = uloha_vzorak uloha.ulohavzoraknode = uloha_vzorak
uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu)) uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu))
@ -434,7 +434,7 @@ def gen_cisla(rnd, rocniky):
datum_deadline=deadline, datum_deadline=deadline,
verejne_db=True verejne_db=True
) )
node2 = CisloNode.objects.create(cislo = cislo, succ = node) node2 = CisloNode.objects.create(cislo = cislo, succ = node, root=rocnik.rocniknode)
cislo.save() cislo.save()
node = node2 node = node2
if otec: if otec:
@ -473,54 +473,54 @@ def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
for cislo in cisla: for cislo in cisla:
# Přidáme TemaVCisleNode do daného čísla # Přidáme TemaVCisleNode do daného čísla
cislo_node = cislo.cislonode cislo_node = cislo.cislonode
tema_cislo_node = TemaVCisleNode.objects.create(tema = tema) tema_cislo_node = TemaVCisleNode.objects.create(tema = tema, root = cislo_node.root)
insert_last_child(cislo_node, tema_cislo_node) insert_last_child(cislo_node, tema_cislo_node)
# Přidávání obsahu do čísla # Přidávání obsahu do čísla
cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod)) cast_node = m.CastNode.objects.create(nadpis = "Příspěvek k číslu {}".format(cislo.kod), root=cislo_node.root)
add_first_child(tema_cislo_node, cast_node) add_first_child(tema_cislo_node, cast_node)
text_node = TextNode.objects.create(text = get_text()) text_node = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node, text_node) add_first_child(cast_node, text_node)
cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém") cast_node2 = m.CastNode.objects.create(nadpis = "První podproblém", root=cislo_node.root)
add_first_child(text_node, cast_node2) add_first_child(text_node, cast_node2)
text_node2 = TextNode.objects.create(text = get_text()) text_node2 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node2, text_node2) add_first_child(cast_node2, text_node2)
cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém") cast_node3 = m.CastNode.objects.create(nadpis = "Druhý podproblém", root=cislo_node.root)
add_first_child(text_node2, cast_node3) add_first_child(text_node2, cast_node3)
text_node3 = TextNode.objects.create(text = get_text()) text_node3 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node3) add_first_child(cast_node3, text_node3)
cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém") cast_node4 = m.CastNode.objects.create(nadpis = "Třetí podproblém", root=cislo_node.root)
add_first_child(text_node3, cast_node4) add_first_child(text_node3, cast_node4)
text_node4 = TextNode.objects.create(text = get_text()) text_node4 = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3, text_node4) add_first_child(cast_node3, text_node4)
cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s " cast_node3a = m.CastNode.objects.create(nadpis = "Podproblém paralelní s "
"druhým podproblémem") "druhým podproblémem", root=cislo_node.root)
cast_node3.succ = cast_node3a cast_node3.succ = cast_node3a
cast_node3.save() cast_node3.save()
text_node3a = TextNode.objects.create(text = get_text()) text_node3a = TextNode.objects.create(text = get_text(), root=cislo_node.root)
add_first_child(cast_node3a, text_node3a) add_first_child(cast_node3a, text_node3a)
# Občas přidáme mezičíslo # Občas přidáme mezičíslo
if rnd.randint(1, 3) == 1: if rnd.randint(1, 3) == 1:
create_node_after(cislo_node, m.MezicisloNode) create_node_after(cislo_node, m.MezicisloNode, root=cislo_node.root)
mezicislo_node = cislo_node.succ mezicislo_node = cislo_node.succ
cast_node_mezicislo = m.CastNode.objects.create( cast_node_mezicislo = m.CastNode.objects.create(
nadpis = "Příspěvek k mezičíslu".format(cislo.kod)) nadpis = "Příspěvek k mezičíslu".format(cislo.kod), root=cislo_node.root)
add_first_child(mezicislo_node, cast_node_mezicislo) add_first_child(mezicislo_node, cast_node_mezicislo)
odstavec = lorem.paragraph() odstavec = lorem.paragraph()
text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec) text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec)
text_node_mezicislo = TextNode.objects.create(text = text_mezicislo) text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root)
add_first_child(cast_node_mezicislo, text_node_mezicislo) add_first_child(cast_node_mezicislo, text_node_mezicislo)
return tema return tema
@ -564,7 +564,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
# Vyrobíme TemaVCisleNody pro obsah # Vyrobíme TemaVCisleNody pro obsah
for i in range(zacatek_tematu, konec_tematu+1): for i in range(zacatek_tematu, konec_tematu+1):
node = TemaVCisleNode.objects.create(tema = t) node = TemaVCisleNode.objects.create(tema = t,root=rocnik.rocniknode)
# FIXME: Není to off-by-one? # FIXME: Není to off-by-one?
otec = cisla[i-1].cislonode otec = cisla[i-1].cislonode
otec_syn(otec, node) otec_syn(otec, node)
@ -621,8 +621,8 @@ def gen_ulohy_tematu(rnd, organizatori, tema, kod, cislo, cislo_se_vzorakem):
na_web = obsah, na_web = obsah,
do_cisla = obsah, do_cisla = obsah,
) )
zad = TextNode.objects.create(text = text_zadani) zad = TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root)
uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad) uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root)
uloha.ulohazadaninode = uloha_zadani uloha.ulohazadaninode = uloha_zadani
return uloha, uloha_zadani return uloha, uloha_zadani

254
seminar/treelib.py

@ -1,7 +1,9 @@
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
# NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode # NOTE: node.prev a node.succ jsou implementovány přímo v models.TreeNode
# TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None) # TODO: Všechny tyto funkce se naivně spoléhají na to, že jako parametr dostanou nějaký TreeNode (některé možná zvládnou i None)
# TODO: Chceme, aby všechno nějak zvládlo None jako parametr. # TODO: Chceme, aby všechno nějak zvládlo None jako parametr.
# TODO: Do nějakých consistency-checků přidat hledání polo-sirotků (kteří nesplňují invarianty, třeba nejsou dosažitelní a mají root, vyrábějí DAG, ...)
# Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode. # Slouží k debugování pro rychlé získání představy o podobě podstromu pod tímto TreeNode.
def print_tree(node,indent=0): def print_tree(node,indent=0):
@ -12,37 +14,41 @@ def print_tree(node,indent=0):
if node.succ: if node.succ:
print_tree(node.succ, indent=indent) print_tree(node.succ, indent=indent)
def is_orphan(node):
""" Zjišťuje, jestli už je daný Node někde pověšený či nikoli. """
if safe_father_of_first(node) is None and safe_pred(node) is None:
return True
else:
return False
# Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist # Django je trošku hloupé, takže node.prev nevrací None, ale hází django.core.exceptions.ObjectDoesNotExist
def safe_pred(node): def safe_pred(node):
if node is None:
return None
try: try:
return node.prev return node.prev
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
def first_brother(node): # FIXME: Proč?????
if node is None: def safe_succ(node):
try:
return node.succ
except ObjectDoesNotExist:
return None return None
brother = node
while safe_pred(brother) is not None:
brother = safe_pred(brother)
return brother
# A to samé pro .father_of_first
def safe_father_of_first(node): def safe_father_of_first(node):
if node is None:
return None
first = first_brother(node) first = first_brother(node)
try: try:
return first.father_of_first return first.father_of_first
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None return None
def first_brother(node):
if node is None:
return None
brother = node
while safe_pred(brother) is not None:
brother = safe_pred(brother)
return brother
## Rodinné vztahy ## Rodinné vztahy
# Tohle se teď zrovna k None chová správně, ale je potřeba na to dávat pozor
def get_parent(node): def get_parent(node):
# Nejdřív získáme prvního potomka... # Nejdřív získáme prvního potomka...
while safe_pred(node) is not None: while safe_pred(node) is not None:
@ -51,14 +57,33 @@ def get_parent(node):
return safe_father_of_first(node) return safe_father_of_first(node)
def get_last_child(node): def get_last_child(node):
if node is None:
return None
first = node.first_child first = node.first_child
if first is None: if first is None:
return None return None
else: else:
return last_brother(first) return last_brother(first)
def is_orphan(node):
""" Zjišťuje, jestli už je daný Node někde pověšený či nikoli. """
# None jsem se rozhodl, že sirotek není
if node is None:
return False
if get_parent(node) is None:
if node.succ is not None or safe_pred(node) is not None or safe_father_of_first(node) is not None or node.root is not None:
import logging
logger = logging.getLogger(__name__)
# Error = pošle mail :-)
logger.error(f"Node-sirotek s id {node.id} má rodinné vztahy (Node: {node})")
return True
else:
return False
# Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé) # Obecný next: další Node v "the-right-order" pořadí (já, pak potomci, pak sousedé)
def general_next(node): def general_next(node):
if node is None:
return None
# Máme potomka? # Máme potomka?
if node.first_child is not None: if node.first_child is not None:
return node.first_child return node.first_child
@ -71,6 +96,8 @@ def general_next(node):
return node.succ return node.succ
def last_brother(node): def last_brother(node):
if node is None:
return None
while node.succ is not None: while node.succ is not None:
node = node.succ node = node.succ
return node return node
@ -78,6 +105,7 @@ def last_brother(node):
def general_prev(node): def general_prev(node):
# Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec. # Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec.
# Otce vyřešíme nejdřív: # Otce vyřešíme nejdřív:
# Tady se ošetří node=None samo
if safe_pred(node) is None: if safe_pred(node) is None:
return safe_father_of_first(node) return safe_father_of_first(node)
pred = safe_pred(node) pred = safe_pred(node)
@ -95,12 +123,16 @@ def me_and_right_brothers(node):
current = current.succ current = current.succ
def right_brothers(node): def right_brothers(node):
if node is None:
return
generator = me_and_right_brothers(node.succ) generator = me_and_right_brothers(node.succ)
for item in generator: for item in generator:
yield item yield item
# Generátor všech sourozenců (vč. sám sebe) # Generátor všech sourozenců (vč. sám sebe)
def all_brothers(node): def all_brothers(node):
if node is None:
return
# Najdeme prvního bratra # Najdeme prvního bratra
fb = first_brother(node) fb = first_brother(node)
marb = me_and_right_brothers(fb) marb = me_and_right_brothers(fb)
@ -108,6 +140,8 @@ def all_brothers(node):
yield cur yield cur
def all_proper_brothers(node): def all_proper_brothers(node):
if node is None:
return
all = all_brothers(node) all = all_brothers(node)
for br in all: for br in all:
if br is node: if br is node:
@ -116,12 +150,16 @@ def all_proper_brothers(node):
def all_children(node): def all_children(node):
""" Generátor všech potomků zadaného Node. """ """ Generátor všech potomků zadaného Node. """
if node is None:
return
brothers = all_brothers(node.first_child) brothers = all_brothers(node.first_child)
for br in brothers: for br in brothers:
yield br yield br
def all_children_of_type(node, type): def all_children_of_type(node, type):
""" Generuje všechny potomky daného Node a daného typu. """ """ Generuje všechny potomky daného Node a daného typu. """
if node is None:
return
brothers = all_brothers(node.first_child) brothers = all_brothers(node.first_child)
for br in brothers: for br in brothers:
if isinstance(br, type): if isinstance(br, type):
@ -130,6 +168,8 @@ def all_children_of_type(node, type):
# Generátor následníků v "the-right-order" # Generátor následníků v "the-right-order"
# Bez tohoto vrcholu # Bez tohoto vrcholu
def all_following(node): def all_following(node):
if node is None:
return
current = general_next(node) current = general_next(node)
while current is not None: while current is not None:
yield current yield current
@ -139,12 +179,16 @@ def all_following(node):
# Najdi dalšího bratra nějakého typu, nebo None. # Najdi dalšího bratra nějakého typu, nebo None.
# hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ. # hledá i podtřídy, i.e. get_next_brother_of_type(neco, TreeNode) je prostě succ.
def get_next_brother_of_type(node, type): def get_next_brother_of_type(node, type):
if node is None:
return
for current in right_brothers(node): for current in right_brothers(node):
if isinstance(current, type): if isinstance(current, type):
return current return current
return None return None
def get_prev_brother_of_type(node, type): def get_prev_brother_of_type(node, type):
if node is None:
return
# Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. # Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem.
current = node current = node
while safe_pred(current) is not None: while safe_pred(current) is not None:
@ -155,6 +199,8 @@ def get_prev_brother_of_type(node, type):
# Totéž pro "the-right-order" pořadí # Totéž pro "the-right-order" pořadí
def get_next_node_of_type(node, type): def get_next_node_of_type(node, type):
if node is None:
return
for cur in all_folowing(node): for cur in all_folowing(node):
if isinstance(cur, type): if isinstance(cur, type):
return cur return cur
@ -162,6 +208,8 @@ def get_next_node_of_type(node, type):
def get_prev_node_of_type(node, type): def get_prev_node_of_type(node, type):
# Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem. # Na tohle není rozumný generátor, ani ho asi nechceme, prostě to implementujeme cyklem.
if node is None:
return
current = node current = node
while general_prev(current) is not None: while general_prev(current) is not None:
current = general_prev(current) current = general_prev(current)
@ -171,20 +219,38 @@ def get_prev_node_of_type(node, type):
# Exception, kterou některé metody při špatném použití mohou házet
# Hlavní důvod je možnost informovat o selhání, aby se příslušný problém dal zobrazit na frontendu,
class TreeLibError(RuntimeError):
pass
# Editace stromu: # Editace stromu:
def create_node_after(predecessor, type, **kwargs): def create_node_after(predecessor, type, **kwargs):
from seminar.models import TreeNode
if predecessor is None:
raise TreeLibError("Nelze vyrábět sirotky! (predecessor=None)")
if not issubclass(type, TreeNode):
raise TreeLibError("Nový node není node!")
new_node = type.objects.create(**kwargs) new_node = type.objects.create(**kwargs)
new_node.root = predecessor.root
new_node.save() new_node.save()
succ = predecessor.succ succ = predecessor.succ
predecessor.succ = new_node predecessor.succ = new_node
predecessor.save() predecessor.save()
new_node.succ = succ new_node.succ = succ
new_node.save() new_node.save()
return new_node
# Vyrábí prvního syna, ostatní nalepí za (existují-li) # Vyrábí prvního syna, ostatní nalepí za (existují-li)
def create_child(parent, type, **kwargs): def create_child(parent, type, **kwargs):
from seminar.models import TreeNode
if parent is None:
raise TreeLibError("Nelze vyrábět sirotky! (parent=None)")
if not issubclass(type, TreeNode):
raise TreeLibError("Nový node není node!")
new_node = type.objects.create(**kwargs) new_node = type.objects.create(**kwargs)
new_node.root = parent.root
new_node.save() new_node.save()
orig_child = parent.first_child orig_child = parent.first_child
parent.first_child = new_node parent.first_child = new_node
@ -193,8 +259,11 @@ def create_child(parent, type, **kwargs):
# Přidáme původního prvního syna jako potomka nového vrcholu # Přidáme původního prvního syna jako potomka nového vrcholu
new_node.succ = orig_child new_node.succ = orig_child
new_node.save() new_node.save()
return new_node
def insert_last_child(parent, node): def insert_last_child(parent, node):
if parent is None:
raise TreeLibError("Nelze vyrábět sirotky! (parent=None)")
""" Zadaný Node přidá jako posledního potomka otce. """ """ Zadaný Node přidá jako posledního potomka otce. """
last = get_last_child(parent) last = get_last_child(parent)
if not is_orphan(node): if not is_orphan(node):
@ -213,6 +282,11 @@ def insert_last_child(parent, node):
last.save() last.save()
def create_node_before(successor, type, **kwargs): def create_node_before(successor, type, **kwargs):
from seminar.models import TreeNode
if successor is None:
raise TreeLibError("Nelze vyrábět sirotky! (successor=None)")
if not issubclass(type, TreeNode):
raise TreeLibError("Nový node není node!")
if safe_pred(successor) is not None: if safe_pred(successor) is not None:
# Easy: přidáme za předchůdce # Easy: přidáme za předchůdce
create_node_after(successor.prev, type, **kwargs) create_node_after(successor.prev, type, **kwargs)
@ -223,36 +297,18 @@ def create_node_before(successor, type, **kwargs):
create_child(successor.father_of_first, type, **kwargs) create_child(successor.father_of_first, type, **kwargs)
# Teď už easy: Jsme sirotci, takže se vyrobíme a našeho následníka si přidáme jako succ # Teď už easy: Jsme sirotci, takže se vyrobíme a našeho následníka si přidáme jako succ
new = type.objects.create(**kwargs) new = type.objects.create(**kwargs)
new.root = successor.root
new.succ = successor new.succ = successor
new.save() new.save()
return new
# ValueError, pokud je (aspoň) jeden parametr None # ValueError, pokud je (aspoň) jeden parametr None
def swap(node, other): def swap(node, other):
raise NotImplementedError("YAGNI (You aren't gonna need it).") raise NotImplementedError("YAGNI (You aren't gonna need it).")
# Exception, kterou některé metody při špatném použití mohou házet
# Hlavní důvod je možnost informovat o selhání, aby se příslušný problém dal zobrazit na frontendu,
class TreeLibError(RuntimeError):
pass
def swap_pred(node):
if node is None:
raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.")
pred = safe_pred(node)
if pred is None:
raise TreeLibError("Nelze posunout vlevo, není tam žádný další uzel.")
pre_pred = safe_pred(pred)
succ = node.succ
if pre_pred is not None:
pre_pred.succ = node
pre_pred.save()
node.succ = pred
node.save()
pred.succ = succ
pred.save()
@transaction.atomic
def swap_succ(node): def swap_succ(node):
if node is None: if node is None:
raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.")
@ -263,63 +319,147 @@ def swap_succ(node):
post_succ = succ.succ post_succ = succ.succ
if pred is not None: if pred is not None:
pred.succ = succ pred.succ = None
pred.save() pred.save()
# Nemame predchudce -> je potreba upravit otce
father = safe_father_of_first(node)
if pred is None and father is not None: # Mame otce
father.first_child = succ
father.save()
succ.succ = node succ.succ = node
succ.save() succ.save()
node.succ = post_succ node.succ = post_succ
node.save() node.save()
if pred is not None:
pred.succ = succ
pred.save()
@transaction.atomic
def swap_pred(node):
if node is None:
raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.")
pred = safe_pred(node)
if pred is None:
raise TreeLibError("Nelze posunout vlevo, není tam žádný další uzel.")
return swap_succ(pred)
# Rotace stromu # Rotace stromu
# Dokumentace viz wiki: # Dokumentace viz wiki:
# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku)
def raise_node(node): def raise_node(node):
if node is None: if node is None:
raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.")
# Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) # Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1)
# FIXME: Velmi naivní, chybí error checky # FIXME: Trochu méně naivní, nevěřím tomu, prosím otestovat
D = node D = node
C = get_parent(D) C = get_parent(D)
E = C.succ if C is None:
subtree4_head = D.first_child raise TreeLibError("Nelze povýšit vrchol, jenž nemá otce.")
subtree4_tail = last_brother(subtree4_head) E = C.succ # Může být None a ničemu to nevadí
subtree3P_head = D.succ subtree4_head = D.first_child # Může být None, ale pak se musí z 3P udělat přímo potomek D
subtree3L_head = C.first_child subtree4_tail = last_brother(subtree4_head) # Měl by být None právě když je sub4_head=None
subtree3L_tail = safe_pred(D) subtree3P_head = D.succ # Může být None a ničemu to nevadí
subtree3L_tail = safe_pred(D) # Pokud je None, D je první syn C a C má tedy skončit bezdětný
# Prostor pro motlitbu... # Prostor pro motlitbu...
pass pass
# Amen. # Amen.
C.succ = D # Teď už nesmíme spadnout, protože jinak skončíme se stromem v nekonzistentním stavu
C.succ = D # Nespadne
C.save() C.save()
D.succ = E D.succ = E # Nespadne
D.save() D.save()
subtree3L_tail.succ = None
subtree3L_tail.save() if subtree3L_tail is not None:
subtree4_tail.succ = subtree3P.head subtree3L_tail.succ = None
subtree4_tail.save() subtree3L_tail.save()
else:
assert C.first_child is D
C.first_child = None
C.save()
if subtree4_tail is not None:
subtree4_tail.succ = subtree3P_head
subtree4_tail.save()
else:
D.first_child = subtree3P_head
D.save()
# To by mělo být všechno... # To by mělo být všechno...
# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku)
def lower_node(node): def lower_node(node):
if node is None: if node is None:
raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.") raise TreeLibError("Nelze přesunout None. Tohle by se nemělo stát.")
# Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1) # Pojmenování viz WIKI (as of 2020-03-19 01:33:44 GMT+1)
# FIXME: Velmi naivní, chybí error checky # FIXME: Trochu naivní, prosím otestovat
C = node C = node
D = C.succ D = C.succ # Může být None a ničemu to nevadí
B = safe_pred(C) B = safe_pred(C)
subtree2_head = B.first_child if B is None:
subtree2_tail = last_brother(subtree2_head) raise TreeLibError("Nelze ponížit prvního syna (není pod co)")
subtree2_head = B.first_child # Je-li None, pak se z C má stát první syn
subtree2_tail = last_brother(subtree2_head) # None iff head=None, doufám
# Prostor pro motlitbu... # Prostor pro motlitbu...
pass pass
# Amen. # Amen.
B.succ = D # Teď už nesmíme spadnout, protože jinak skončíme se stromem v nekonzistentním stavu
B.succ = D # Nespadne
B.save() B.save()
subtree2_tail.succ = C if subtree2_tail is not None:
subtree2_tail.save() subtree2_tail.succ = C
subtree2_tail.save()
else:
assert subtree2_head is None
B.first_child = C
B.save()
# To by mělo být všechno... # To by mělo být všechno...
def disconnect_node(node):
#FIXME: dodělat odstranění roota všem potomkům
if node is None:
raise TreeLibError("Nelze odpojit None. Tohle by se nemělo stát.")
print(f'My:{node}, predchudce:{safe_pred(node)}, naslednik:{safe_succ(node)}, otec:{safe_father_of_first(node)}')
# Jsme prvnim synem
if safe_pred(node) is None:
if safe_succ(node) is None: # Jsme jedinym synem - upravime otce (pokud mame) a odpojime se
father = safe_father_of_first(node)
if father is not None:
father.first_child = None
father.save()
return
else: # mame bratra
swap_succ(node) # Staneme se neprvním synem, pokracujeme mimo if
# Jsme neprvním synem
prev = node.prev
prev.succ = node.succ
node.succ = None
node.save()
clear_root(node)
prev.save()
def clear_root(node):
node.root = None
node.save()
if node.first_child:
clear_root(node.first_child)
if node.succ:
clear_root(node.succ)
def set_root(node,root):
node.root = root
node.save()
if node.first_child:
clear_root(node.first_child)
if node.succ:
clear_root(node.succ)

18
seminar/urls.py

@ -1,4 +1,4 @@
from django.urls import path, include from django.urls import path, include, re_path
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from . import views, export from . import views, export
from .utils import org_required, resitel_required from .utils import org_required, resitel_required
@ -17,9 +17,17 @@ urlpatterns = [
path('archiv/temata/', views.ArchivTemataView.as_view()), path('archiv/temata/', views.ArchivTemataView.as_view()),
path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'), path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'),
path('cislo/<int:rocnik>.<int:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), # odkomentované jenom kvůli testování archivu path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'),
path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'), path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'),
path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'), path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'),
path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
path('treenode/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'), #path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'),
# Soustredeni # Soustredeni
@ -50,7 +58,7 @@ urlpatterns = [
# Zadani # Zadani
path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), path('zadani/aktualni/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'),
# path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'), path('zadani/temata/', views.ZadaniTemataView, name='seminar_temata'),
#path('zadani/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'), #path('zadani/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_vysledky'),
path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'),
@ -149,7 +157,11 @@ urlpatterns = [
path('temp/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'), path('temp/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'),
path('temp/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'), path('temp/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'),
re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'),
path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()),
path('', views.TitulniStranaView.as_view(), name='titulni_strana'), path('', views.TitulniStranaView.as_view(), name='titulni_strana'),
path('jak-resit/', views.JakResitView.as_view(), name='jak-resit'),
# Ceka na autocomplete v3 # Ceka na autocomplete v3
# path('autocomplete/organizatori/', # path('autocomplete/organizatori/',

1
seminar/views/__init__.py

@ -1,2 +1,3 @@
from .views_all import * from .views_all import *
from .autocomplete import * from .autocomplete import *
from .views_rest import *

416
seminar/views/views_all.py

@ -16,15 +16,20 @@ from django.contrib.auth import views as auth_views
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction from django.db import transaction
from django.core import serializers
from django.forms.models import model_to_dict
import seminar.models as s import seminar.models as s
import seminar.models as m
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
from seminar import utils, treelib from seminar import utils, treelib
from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm
import seminar.forms as f import seminar.forms as f
import seminar.templatetags.treenodes as tnltt
import seminar.views.views_rest as vr
from datetime import timedelta, date, datetime from datetime import timedelta, date, datetime, MAXYEAR
from django.utils import timezone from django.utils import timezone
from itertools import groupby from itertools import groupby
from collections import OrderedDict from collections import OrderedDict
@ -44,14 +49,15 @@ import time
from seminar.utils import aktivniResitele, resi_v_rocniku from seminar.utils import aktivniResitele, resi_v_rocniku
# ze starého modelu
def verejna_temata(rocnik): #def verejna_temata(rocnik):
"""Vrací queryset zveřejněných témat v daném ročníku. # """
""" # Vrací queryset zveřejněných témat v daném ročníku.
return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod') # """
# return Problem.objects.filter(typ=Problem.TYP_TEMA, cislo_zadani__rocnik=rocnik, cislo_zadani__verejne_db=True).order_by('kod')
def temata_v_rocniku(rocnik): #
return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik) #def temata_v_rocniku(rocnik):
# return Problem.objects.filter(typ=Problem.TYP_TEMA, rocnik=rocnik)
def get_problemy_k_tematu(tema): def get_problemy_k_tematu(tema):
return Problem.objects.filter(nadproblem = tema) return Problem.objects.filter(nadproblem = tema)
@ -88,17 +94,99 @@ class ObalkovaniView(generic.ListView):
return context return context
class TNLData(object): class TNLData(object):
def __init__(self,anode): def __init__(self,anode,parent=None, index=None):
self.node = anode self.node = anode
self.sernode = vr.TreeNodeSerializer(anode)
self.children = [] self.children = []
self.parent = parent
self.tema_in_path = False
self.index = index
if parent:
self.tema_in_path = parent.tema_in_path
if isinstance(anode, m.TemaVCisleNode):
self.tema_in_path = True
def add_edit_options(self):
self.deletable = tnltt.deletable(self)
self.editable_siblings = tnltt.editableSiblings(self)
self.editable_children = tnltt.editableChildren(self)
self.text_only_subtree = tnltt.textOnlySubtree(self)
self.can_podvesit_za = tnltt.canPodvesitZa(self)
self.can_podvesit_pred = tnltt.canPodvesitPred(self)
self.appendable_children = tnltt.appendableChildren(self)
print("appChld",self.appendable_children)
if self.parent:
self.appendable_siblings = tnltt.appendableChildren(self.parent)
else:
self.appendable_siblings = []
def treenode_strom_na_seznamy(node): @classmethod
out = TNLData(node) def from_treenode(cls,anode,parent=None,index=None):
for ch in treelib.all_children(node): out = cls(anode,parent,index)
outitem = treenode_strom_na_seznamy(ch) for (idx,ch) in enumerate(treelib.all_children(anode)):
out.children.append(outitem) outitem = cls.from_treenode(ch,out,idx)
return out out.children.append(outitem)
out.add_edit_options()
return out
@classmethod
def from_tnldata_list(cls, tnllist):
"""Vyrobíme virtuální TNL, který nemá obsah, ale má za potomky všechna zadaná TNLData"""
result = cls(None)
for idx, tnl in enumerate(tnllist):
result.children.append(tnl)
tnl.parent = result
tnl.index = idx
result.add_edit_options()
return result
@classmethod
def filter_treenode(cls, treenode, predicate):
tnll = cls._filter_treenode_recursive(treenode, predicate) # TreeNodeList List :-)
return TNLData.from_tnldata_list(tnll)
@classmethod
def _filter_treenode_recursive(cls, treenode, predicate):
if predicate(treenode):
return [cls.from_treenode(treenode)]
else:
found = []
for tn in 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:
found.append(tnl)
return found
def to_json(self):
#self.node = anode
#self.children = []
#self.parent = parent
#self.tema_in_path = False
#self.index = index
out = {}
out['node'] = self.sernode.data
out['children'] = [n.to_json() for n in self.children]
out['tema_in_path'] = self.tema_in_path
out['index'] = self.index
out['deletable'] = self.deletable
out['editable_siblings'] = self.editable_siblings
out['editable_children'] = self.editable_children
out['text_only_subtree'] = self.text_only_subtree
out['can_podvesit_za'] = self.can_podvesit_za
out['can_podvesit_pod'] = self.can_podvesit_pred
out['appendable_children'] = self.appendable_children
out['appendable_siblings'] = self.appendable_siblings
return out
def __repr__(self):
return("TNL({})".format(self.node))
class TreeNodeView(generic.DetailView): class TreeNodeView(generic.DetailView):
model = s.TreeNode model = s.TreeNode
@ -106,22 +194,180 @@ class TreeNodeView(generic.DetailView):
def get_context_data(self,**kwargs): def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['tnldata'] = treenode_strom_na_seznamy(self.object) context['tnldata'] = TNLData.from_treenode(self.object)
return context return context
class TreeNodeJSONView(generic.DetailView):
model = s.TreeNode
def get(self,request,*args, **kwargs):
self.object = self.get_object()
data = TNLData.from_treenode(self.object).to_json()
return JsonResponse(data)
class TreeNodePridatView(generic.View):
type_from_str = {
'rocnikNode': m.RocnikNode,
'cisloNode': m.CisloNode,
'castNode': m.CastNode,
'textNode': m.TextNode,
'temaVCisleNode': m.TemaVCisleNode,
'reseniNode': m.ReseniNode,
'ulohaZadaniNode': m.UlohaZadaniNode,
'ulohaVzorakNode': m.UlohaVzorakNode,
'pohadkaNode': m.PohadkaNode,
'orgText': m.OrgTextNode,
}
def post(self, request, *args, **kwargs):
######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ###########
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
kam = self.kwargs['kam']
co = self.kwargs['co']
typ = self.type_from_str[co]
raise NotImplementedError('Neni to dopsane, dopis to!')
if kam not in ('pred','syn','za'):
raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna')
if co == m.TextNode:
new_obj = m.Text()
new_obj.save()
elif co == m.CastNode:
new_obj = m.CastNode()
new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam))
new_obj.save()
elif co == m.ReseniNode:
new_obj = m
pass
elif co == m.UlohaZadaniNode:
pass
elif co == m.UlohaReseniNode:
pass
else:
new_obj = None
if kam == 'pred':
pass
if kam == 'syn':
if typ == m.TextNode:
text_obj = m.Text()
text_obj.save()
node = treelib.create_child(node,typ,text=text_obj)
else:
node = treelib.create_child(node,typ)
if kam == 'za':
if typ == m.TextNode:
text_obj = m.Text()
text_obj.save()
node = treelib.create_node_after(node,typ,text=text_obj)
else:
node = treelib.create_node_after(node,typ)
return redirect(node.get_admin_url())
class TreeNodeSmazatView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
if node.first_child:
raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!')
treelib.disconnect_node(node)
node.delete()
return redirect(request.headers.get('referer'))
class TreeNodeOdvesitPrycView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
treelib.disconnect_node(node)
node.root = None
node.save()
return redirect(request.headers.get('referer'))
class TreeNodePodvesitView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
kam = self.kwargs['kam']
if kam == 'pred':
treelib.lower_node(node)
elif kam == 'za':
raise NotImplementedError('Podvěsit za není zatím podporováno')
return redirect(request.headers.get('referer'))
class TreeNodeProhoditView(generic.base.View):
def post(self, request, *args, **kwargs):
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
treelib.swap_succ(node)
return redirect(request.headers.get('referer'))
#FIXME ve formulari predat puvodni url a vratit redirect na ni
class SirotcinecView(generic.ListView):
model = s.TreeNode
template_name = 'seminar/orphanage.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... def get_queryset(self):
class AktualniZadaniView(TreeNodeView): return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None)
def get_object(self):
nastaveni = get_object_or_404(Nastaveni)
return nastaveni.aktualni_cislo.cislonode
def get_context_data(self,**kwargs): # FIXME pouzit Django REST Framework
nastaveni = get_object_or_404(Nastaveni) class TextWebView(generic.DetailView):
model = s.Text
def get(self,request,*args, **kwargs):
self.object = self.get_object()
return JsonResponse(model_to_dict(self.object,exclude='do_cisla'))
class ProblemView(generic.DetailView):
model = s.Problem
# Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView
template_name = TreeNodeView.template_name
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
verejne = nastaveni.aktualni_cislo.verejne() # Teď potřebujeme doplnit tnldata do kontextu.
context['verejne'] = verejne # Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
if False:
# Hezčí formátování zbytku :-P
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)
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)
context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
elif isinstance(self.object, s.Tema):
rocniknode = self.object.rocnik.rocniknode
context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode))
else:
raise ValueError("Obecný problém nejde zobrazit.")
return context return context
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):
# def get_object(self):
# nastaveni = get_object_or_404(Nastaveni)
# return nastaveni.aktualni_cislo.cislonode
#
# def get_context_data(self,**kwargs):
# nastaveni = get_object_or_404(Nastaveni)
# context = super().get_context_data(**kwargs)
# verejne = nastaveni.aktualni_cislo.verejne()
# context['verejne'] = verejne
# return context
#def AktualniZadaniView(request): #def AktualniZadaniView(request):
# nastaveni = get_object_or_404(Nastaveni) # nastaveni = get_object_or_404(Nastaveni)
# verejne = nastaveni.aktualni_cislo.verejne() # verejne = nastaveni.aktualni_cislo.verejne()
@ -137,7 +383,19 @@ class AktualniZadaniView(TreeNodeView):
# }, # },
# ) # )
# #
#def ZadaniTemataView(request): 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) # nastaveni = get_object_or_404(Nastaveni)
# temata = verejna_temata(nastaveni.aktualni_rocnik) # temata = verejna_temata(nastaveni.aktualni_rocnik)
# for t in temata: # for t in temata:
@ -242,45 +500,51 @@ def spravne_novinky(request):
qs = qs.filter(zverejneno=True) qs = qs.filter(zverejneno=True)
return qs.order_by('-datum') return qs.order_by('-datum')
def aktualni_temata(rocnik):
"""
Vrací PolymorphicQuerySet témat v daném ročníku, ke kterým se aktuálně něco odevzdat.
"""
return Tema.objects.filter(rocnik=rocnik, stav='zadany').order_by('kod')
class TitulniStranaView(generic.ListView): class TitulniStranaView(generic.ListView):
template_name='seminar/titulnistrana.html' template_name='seminar/titulnistrana.html'
def get_queryset(self): def get_queryset(self):
return spravne_novinky(self.request)[:5] return spravne_novinky(self.request)[:3]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(TitulniStranaView, self).get_context_data(**kwargs) context = super(TitulniStranaView, self).get_context_data(**kwargs)
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
deadline_soustredeni = (nastaveni.aktualni_cislo.datum_deadline_soustredeni, "soustredeni")
preddeadline = (nastaveni.aktualni_cislo.datum_preddeadline, "preddeadline")
deadline = (nastaveni.aktualni_cislo.datum_deadline, "deadline")
# zjisteni spravneho terminu try:
if nastaveni.aktualni_cislo.datum_deadline_soustredeni: nejblizsi_deadline = sorted(filter(lambda dl: dl[0] is not None and dl[0] >= date.today(), [deadline_soustredeni, preddeadline, deadline]))[0]
cas_deadline_soustredeni = nastaveni.aktualni_cislo.\ except IndexError:
datum_deadline_soustredeni nejblizsi_deadline = (None, None) # neni zadna aktualni deadline
if (datetime.now().date() <= cas_deadline_soustredeni):
cas_deadline = cas_deadline_soustredeni if nejblizsi_deadline[0] is not None:
deadline_soustredeni = True context['nejblizsi_deadline'] = datetime.combine(nejblizsi_deadline[0], datetime.max.time())
else:
cas_deadline = nastaveni.aktualni_cislo.datum_deadline
deadline_soustredeni = False
else:
cas_deadline = nastaveni.aktualni_cislo.datum_deadline
deadline_soustredeni = False
# Pokud neni zverejnene cislo nezverejnuj odpocet
if nastaveni.aktualni_cislo.verejne():
# pokus se zjistit termin odeslani a pokud neni zadany,
# nezverejnuj odpocet
context['deadline_soustredeni'] = deadline_soustredeni
try:
context['dead'] = datetime.combine(cas_deadline,
datetime.max.time())
context['ted'] = datetime.now()
except:
context['dead'] = None
else: else:
context['dead'] = None context['nejblizsi_deadline'] = None
context['deadline_soustredeni'] = deadline_soustredeni
context['typ_deadline'] = nejblizsi_deadline[1]
# Aktuální témata
nazvy_a_odkazy_na_aktualni_temata = []
akt_temata = aktualni_temata(nastaveni.aktualni_rocnik)
for tema in akt_temata:
# FIXME: netuším, jestli funguje tema.verejne_url(), nemáme testdata na témátka - je to asi url vzhledem k ročníku
nazvy_a_odkazy_na_aktualni_temata.append({'nazev':tema.nazev,'url':tema.verejne_url()})
context['aktualni_temata'] = nazvy_a_odkazy_na_aktualni_temata
print(context)
return context return context
class StareNovinkyView(generic.ListView): class StareNovinkyView(generic.ListView):
@ -343,7 +607,8 @@ class ArchivView(generic.ListView):
### Výsledky ### Výsledky
def sloupec_s_poradim(setrizene_body): def sloupec_s_poradim(setrizene_body):
""" Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník """
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.), 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. podle toho, jak jdou za sebou ve výsledkovce.
Parametr: Parametr:
@ -383,7 +648,8 @@ def sloupec_s_poradim(setrizene_body):
return sloupec_s_poradim return sloupec_s_poradim
def cisla_rocniku(rocnik, jen_verejne=True): def cisla_rocniku(rocnik, jen_verejne=True):
""" Vrátí všechna čísla daného ročníku. """
Vrátí všechna čísla daného ročníku.
Parametry: Parametry:
rocnik (Rocnik): ročník semináře rocnik (Rocnik): ročník semináře
jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla jen_verejne (bool): zda se mají vrátit jen veřejná, nebo všechna čísla
@ -538,7 +804,7 @@ def vysledkovka_rocniku(rocnik, jen_verejne=True):
# získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně # získáme body za ročník, seznam obsahuje dvojice (řešitel_id, body) setřízené sestupně
resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele) resitel_rocnikbody_sezn = secti_body_za_rocnik(rocnik, aktivni_resitele)
# setřídíme řešitele podle počtu bodů a získáme seznam s body od nejvyšších po nenižší # 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) setrizeni_resitele_id, setrizeni_resitele, setrizene_body = setrid_resitele_a_body(resitel_rocnikbody_sezn)
poradi = sloupec_s_poradim(setrizene_body) poradi = sloupec_s_poradim(setrizene_body)
@ -773,6 +1039,7 @@ def vysledkovka_cisla(cislo, context=None):
return context return context
class CisloView(generic.DetailView): class CisloView(generic.DetailView):
# FIXME zobrazování témátek a vůbec, teď je tam jen odkaz na číslo v pdf
model = Cislo model = Cislo
template_name = 'seminar/archiv/cislo.html' template_name = 'seminar/archiv/cislo.html'
@ -1308,6 +1575,32 @@ class PasswordChangeView(auth_views.PasswordChangeView):
#template_name = 'seminar/password_change.html' #template_name = 'seminar/password_change.html'
success_url = reverse_lazy('titulni_strana') success_url = reverse_lazy('titulni_strana')
class VueTestView(generic.TemplateView):
template_name = 'seminar/vuetest.html'
class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView):
model = s.Obrazek
form_class = f.NahrajObrazekKTreeNoduForm
def get_initial(self):
initial = super().get_initial()
initial['na_web'] = self.request.FILES['upload']
return initial
def form_valid(self,form):
print(self.request.headers)
print(self.request.headers['Textid'])
print(form.instance)
print(form)
self.object = form.save(commit=False)
print(self.object.na_web)
self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid']))
self.object.save()
return JsonResponse({"url":self.object.na_web.url})
# Jen hloupé rozhazovátko # Jen hloupé rozhazovátko
def profilView(request): def profilView(request):
@ -1332,3 +1625,10 @@ def formularOKView(request):
} }
return render(request, template_name, context) return render(request, template_name, context)
#------------------ Jak řešit - možná má být udělané úplně jinak
class JakResitView(generic.ListView):
template_name = 'seminar/jak-resit.html'
def get_queryset(self):
return None

162
seminar/views/views_rest.py

@ -0,0 +1,162 @@
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
import seminar.models as m
from seminar import treelib
DEFAULT_NODE_DEPTH = 2
class TextSerializer(serializers.ModelSerializer):
class Meta:
model = m.Text
fields = '__all__'
class UlohaVzorakNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.UlohaVzorakNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class UlohaZadaniNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.UlohaZadaniNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class RocnikNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.RocnikNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class CisloNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.CisloNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class MezicisloNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.MezicisloNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class TemaVCisleNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.TemaVCisleNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class OrgTextNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.OrgTextNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class PohadkaNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.PohadkaNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class TextNodeSerializer(serializers.ModelSerializer):
text = TextSerializer()
class Meta:
model = m.TextNode
fields = ('id','text','polymorphic_ctype')
depth = DEFAULT_NODE_DEPTH
class TextNodeWriteSerializer(serializers.ModelSerializer):
text = TextSerializer()
def update(self,node,validated_data):
node.text.na_web = validated_data.get('text').get('na_web')
node.text.save()
return node
class Meta:
model = m.TextNode
fields = ('id','text')
depth = DEFAULT_NODE_DEPTH
class TextNodeCreateSerializer(serializers.ModelSerializer):
text = TextSerializer()
refnode = serializers.CharField()
where = serializers.CharField()
def create(self,validated_data):
temp_text = validated_data.pop('text')
where = validated_data.pop('where')
refnode_id = validated_data.pop('refnode')
refnode = m.TreeNode.objects.get(pk=refnode_id)
text = m.Text.objects.create(**temp_text)
if where == 'syn':
node = treelib.create_child(refnode,m.TextNode,text=text)
elif where == 'za':
node = treelib.create_node_after(refnode,m.TextNode,text=text)
elif where == 'pred':
node = treelib.create_node_before(refnode,m.TextNode,text=text)
node.where = None
node.refnode = None
return node
class Meta:
model = m.TextNode
fields = ('text','where','refnode')
depth = DEFAULT_NODE_DEPTH
class CastNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.CastNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class CastNodeCreateSerializer(serializers.ModelSerializer):
refnode = serializers.CharField()
where = serializers.CharField()
def create(self,validated_data):
temp_nadpis = validated_data.pop('nadpis')
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.CastNode,nadpis=temp_nadpis)
elif where == 'za':
node = treelib.create_node_after(refnode,m.CastNode,nadpis=temp_nadpis)
elif where == 'pred':
node = treelib.create_node_before(refnode,m.CastNode,nadpis=temp_nadpis)
node.where = None
node.refnode = None
return node
class Meta:
model = m.CastNode
fields = ('nadpis','where','refnode')
depth = DEFAULT_NODE_DEPTH
class ReseniNodeSerializer(serializers.ModelSerializer):
class Meta:
model = m.ReseniNode
fields = '__all__'
depth = DEFAULT_NODE_DEPTH
class TreeNodeSerializer(PolymorphicSerializer):
model_serializer_mapping = {
m.RocnikNode: RocnikNodeSerializer,
m.CisloNode: CisloNodeSerializer,
m.MezicisloNode: MezicisloNodeSerializer,
m.TemaVCisleNode: TemaVCisleNodeSerializer,
m.OrgTextNode: OrgTextNodeSerializer,
m.UlohaZadaniNode: UlohaZadaniNodeSerializer,
m.UlohaVzorakNode: UlohaVzorakNodeSerializer,
m.PohadkaNode: PohadkaNodeSerializer,
m.TextNode: TextNodeSerializer,
m.CastNode: CastNodeSerializer,
m.ReseniNode: ReseniNodeSerializer,
}

77
seminar/viewsets.py

@ -0,0 +1,77 @@
from rest_framework import viewsets,filters
from . import models as m
from . import views
class ReadWriteSerializerMixin(object):
"""
Overrides get_serializer_class to choose the read serializer
for GET requests and the write serializer for POST requests.
Set read_serializer_class and write_serializer_class attributes on a
viewset.
"""
read_serializer_class = None
create_serializer_class = None
write_serializer_class = None
def get_serializer_class(self):
if self.action == "create":
return self.get_create_serializer_class()
if self.action in ["update", "partial_update", "destroy"]:
return self.get_write_serializer_class()
return self.get_read_serializer_class()
def get_read_serializer_class(self):
assert self.read_serializer_class is not None, (
"'%s' should either include a `read_serializer_class` attribute,"
"or override the `get_read_serializer_class()` method."
% self.__class__.__name__
)
return self.read_serializer_class
def get_write_serializer_class(self):
assert self.write_serializer_class is not None, (
"'%s' should either include a `write_serializer_class` attribute,"
"or override the `get_write_serializer_class()` method."
% self.__class__.__name__
)
return self.write_serializer_class
def get_create_serializer_class(self):
assert self.create_serializer_class is not None, (
"'%s' should either include a `create_serializer_class` attribute,"
"or override the `get_create_serializer_class()` method."
% self.__class__.__name__
)
return self.create_serializer_class
class UlohaVzorakNodeViewSet(viewsets.ModelViewSet):
queryset = m.UlohaVzorakNode.objects.all()
serializer_class = views.UlohaVzorakNodeSerializer
class TextViewSet(viewsets.ModelViewSet):
queryset = m.Text.objects.all()
serializer_class = views.TextSerializer
class TextNodeViewSet(ReadWriteSerializerMixin,viewsets.ModelViewSet):
queryset = m.TextNode.objects.all()
read_serializer_class = views.TextNodeSerializer
write_serializer_class = views.TextNodeWriteSerializer
create_serializer_class = views.TextNodeCreateSerializer
class CastNodeViewSet(ReadWriteSerializerMixin,viewsets.ModelViewSet):
queryset = m.CastNode.objects.all()
read_serializer_class = views.CastNodeSerializer
write_serializer_class = views.CastNodeSerializer
create_serializer_class = views.CastNodeCreateSerializer
class UlohaVzorakNodeViewSet(viewsets.ModelViewSet):
serializer_class = views.UlohaVzorakNodeSerializer
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)
return queryset

22
vue_frontend/.gitignore

@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

5
vue_frontend/babel.config.js

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

48
vue_frontend/package.json

@ -0,0 +1,48 @@
{
"name": "vue_frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@ckeditor/ckeditor5-upload": "^23.0.0",
"@ckeditor/ckeditor5-vue": "^1.0.1",
"axios": "^0.19.2",
"ckeditor5-build-classic-simple-upload-adapter-image-resize": "^1.0.4",
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.4.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.4.0",
"@vue/cli-plugin-eslint": "~4.4.0",
"@vue/cli-service": "^4.5.6",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11",
"webpack-bundle-tracker": "0.4.3"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

7
vue_frontend/src/App.vue

@ -0,0 +1,7 @@
<template>
<router-view id="app"/>
</template>
<script>
export default { name: 'app' }
</script>

50
vue_frontend/src/components/AddNewNode.vue

@ -0,0 +1,50 @@
<template>
<div class="addnewnode">
<button v-if="types.includes('castNode')" v-on:click="selected='castNode'" :disabled="selected && selected !== 'castNode'">Část</button>
<button v-if="types.includes('textNode')" v-on:click="selected='textNode'" :disabled="selected && selected !== 'textNode'">Text</button>
<button v-if="types.includes('reseniNode')" v-on:click="selected='reseniNode'" :disabled="selected && selected !== 'reseniNode'">Řešení</button>
<button v-if="types.includes('ulohaZadaniNode')" v-on:click="selected='ulohaZadaniNode'" :disabled="selected && selected !== 'ulohaZadaniNode'">Zadání úlohy</button>
<button v-if="types.includes('ulohaVzorakNode')" v-on:click="selected='ulohaVzorakNode'" :disabled="selected && selected !== 'ulohaVzorakNode'">Vzorák</button>
<div v-if="selected">
<component :is='selected' :item='null' :where="where" :refnode="refnode" create></component>
</div>
</div>
</template>
<script>
import castNode from './CastNode.vue'
import textNode from './TextNode.vue'
import ulohaZadaniNode from './UlohaZadaniNode.vue'
import ulohaVzorakNode from './UlohaVzorakNode.vue'
//import reseniNode from './UlohaVzorakNode.vue'
export default {
name: 'AddNewNode',
props: {
types: Array,
where: String,
refnode: Object,
},
data: () => ({
selected: null,
}),
components: {
castNode,
textNode,
ulohaZadaniNode,
ulohaVzorakNode,
},
mounted: function() {
this.$root.$on('dataUpdated',(arg) => {
this.selected = null;
console.log('dataUpdated'+arg);
});
}
}
</script>
<style>
.addnewnode {
display: inline;
}
</style>

86
vue_frontend/src/components/CastNode.vue

@ -0,0 +1,86 @@
<template>
<div class="castnode">
<!--pre>CastNode {{item}} {{typeof(item)}}</pre-->
<div v-if="editorShow">
<input type="text" v-model="currentText" />
<button v-on:click="saveText">Uložit</button>
<button v-on:click="currentText = originalText;editorShow=!editorShow;">Zahodit úpravy</button>
</div>
<div v-else>
<h4>{{ currentText }} </h4> <button v-if="editorMode" v-on:click="editorShow=!editorShow">Upravit</button>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'CastNode',
data: () => ({
editorShow: false,
currentText: "",
originalText: "",
}),
props: {
item: Object,
editorShow: Boolean,
create: Boolean,
where: String,
refnode: Object
},
mounted: function() {
if (this.create){
this.currentText = "";
this.originalText = "";
this.editorShow = true;
}
else {
this.currentText = this.item.node.nadpis;
this.originalText = this.item.node.nadpis;
}
//this.getText();
},
methods: {
saveText: function() {
console.log("Saving cast");
console.log(this.currentText);
if (this.create){
console.log(this.refnode);
console.log(this.where);
axios.post('/api/castnode/',{
'nadpis': this.currentText,
'refnode': this.refnode.id,
'where': this.where
}).then(response => {
this.originalText = response.data.nadpis;
this.$root.$emit('updateData',"castNode create update");
})
.catch(e => {
console.log(e);
this.errors.push(e);
});
} else {
axios.put('/api/castnode/'+this.item.node.id+'/',{
'nadpis': this.currentText,
'id': this.item.node.id
}).then(response => {
this.originalText = response.data.nadpis;
})
.catch(e => {
console.log(e);
this.errors.push(e)
});
}
this.editorShow = false;
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

19
vue_frontend/src/components/CisloNode.vue

@ -0,0 +1,19 @@
<template>
<div class="cislonode">
<!--pre>CisloNode {{item}} {{typeof(item)}}</pre-->
<h1>{{ item.node.cislo.poradi }}. číslo</h1>
</div>
</template>
<script>
export default {
name: 'CisloNode',
props: {
item: Object
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

21
vue_frontend/src/components/RocnikNode.vue

@ -0,0 +1,21 @@
<template>
<div class="rocniknode">
<!--pre>RocnikNode {{item}} {{typeof(item)}}</pre-->
<h1>Ročník {{ item.node.rocnik.rocnik }} ({{ item.node.rocnik.prvni_rok }}/{{item.node.rocnik.prvni_rok+1 }})</h1>
</div>
</template>
<script>
export default {
name: 'RocnikNode',
props: {
item: Object
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

19
vue_frontend/src/components/TemaVCisleNode.vue

@ -0,0 +1,19 @@
<template>
<div class="temavcislenode">
<!--pre>TemaVCisleNode {{item}} {{typeof(item)}}</pre-->
<h2>Téma {{ item.node.tema.kod }}: {{ item.node.tema.nazev }}</h2>
</div>
</template>
<script>
export default {
name: 'TemaVCisleNode',
props: {
item: Object
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

174
vue_frontend/src/components/TextNode.vue

@ -0,0 +1,174 @@
<template>
<div v-if="loading" class="loading">
<p>Loading...</p>
</div>
<div v-else class="textnode">
<!--pre>TextNode {{item}} {{typeof(item)}}</pre-->
<template v-if="editorShow">
<div v-if="plainEditShow">
<textarea id="textarea" v-model="currentText" rows="8" cols="50"></textarea>
<br>
<button v-on:click="plainEditShow=!plainEditShow">Editovat v editoru</button>
</div>
<div v-else>
<component v-bind:is="editorComponent" :editor="editor" v-model="currentText" :config="editorConfig"></component>
<button v-on:click="plainEditShow=!plainEditShow">Editovat HTML</button>
</div>
<button v-on:click="saveText">Uložit</button>
<button v-on:click="currentText = originalText;editorShow=!editorShow;">Zahodit úpravy</button>
</template>
<template v-else v-bind:class="changedObject">
<p v-html="currentText"></p>
<button v-if="editorMode" v-on:click="editorShow=!editorShow">Upravit</button>
</template>
</div>
</template>
<script>
import axios from 'axios'
//import ClassicEditor from '@ckeditor/ckeditor5-build-classic'
import ClassicEditor from 'ckeditor5-build-classic-simple-upload-adapter-image-resize';
import CKEditor from '@ckeditor/ckeditor5-vue';
export default {
name: 'TextNode',
data: () => ({
loading: false,
editor: ClassicEditor,
editorData: '<p>Content of the editor.</p>',
editorConfig: {
extraPlugins: ['SimpleUploadAdapter'],
simpleUpload: {
uploadUrl: "/temp/image_upload/",
headers: {},
withCredentials: true
}
// The configuration of the editor.
},
editorShow: false,
plainEditShow: false,
editorComponent: CKEditor.component,
currentText: "",// this.item.node.text.na_web,
originalText: "",// this.item.node.text.na_web,
}),
computed: {
changedObject: function () {
//console.log(this.currentText);
//console.log(this.originalText);
return {
changed: this.currentText !== this.originalText,
}
},
textId: function () {
console.log(this.create);
console.log(this.node.text.id);
if (this.create){
return null;
}
return this.node.text.id;
}
},
props: {
item: Object,
editorShow: Boolean,
editorMode: Boolean,
create: Boolean,
where: String,
refnode: Object
},
mounted: function() {
//console.log("mounted");
this.editorConfig.simpleUpload.headers['X-CSRFToken'] = this.getCookie('csrftoken');
axios.defaults.headers.common['X-CSRFToken'] = this.getCookie('csrftoken');
if (this.create){
this.currentText = "";
this.originalText = "";
this.editorShow = true;
} else {
this.currentText = this.item.node.text.na_web;
this.originalText = this.item.node.text.na_web;
this.editorConfig.simpleUpload.headers.textId = this.item.node.text.id;
}
//this.getText();
},
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;
},
getText: function() {
this.loading = true;
console.log(this.item);
console.log(this.item.node.text);
axios.get('/treenode/text/'+this.item.node.text)
.then((response) => {
this.text = response.data.na_web;
this.loading = false;
})
.catch((err) => {
this.loading = false;
console.log(err);
})
},
saveText: function() {
console.log("Saving text");
console.log(this.currentText);
if (this.create){
console.log(this.refnode);
console.log(this.where);
axios.post('/api/textnode/',{
'text': { 'na_web': this.currentText},
'refnode': this.refnode.id,
'where': this.where
}).then(response => {
this.originalText = response.data.text.na_web;
this.loading = false;
this.$root.$emit('updateData',"textNode create update");
})
.catch(e => {
this.errors.push(e)
});
} else {
axios.put('/api/textnode/'+this.item.node.id+'/',{
'text': { 'na_web': this.currentText},
'id': this.item.node.id
}).then(response => {
this.originalText = response.data.text.na_web;
})
.catch(e => {
this.errors.push(e)
});
}
this.editorShow = false;
},
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.changed {
background-color: 'yellow';
}
</style>

161
vue_frontend/src/components/TreeNode.vue

@ -0,0 +1,161 @@
<template>
<div :class="editorMode ? 'treenode-org' : 'treenode'">
<!--b v-if="v_tematu">v tematu</b>
<b v-if="visible">visible</b>
Force visible: {{String(force_visible)}}-->
<component :is='item.node.polymorphic_ctype.model' :item='item' :key='item.node.id'
:editorMode="editorMode"
:debugMode="debugMode"></component>
<button v-if="debugMode" v-on:click="debugShow = !debugShow" class="nodebug">Ladící data</button> <!-- bude tu nějaký if na class="nodebug", v debug módu bude tlačítko vidět, jinak ne -->
<div v-if="debugShow">
<pre>{{ item.node.polymorphic_ctype.model }}</pre>
<pre>{{ item }}</pre>
</div>
<div v-if="item.children.length === 0">
<div v-if="item.appendable_children.length > 0 && editorMode">
<b>Vložit jako syna: </b>
<addnewnode :types="item.appendable_children" :refnode="item.node" where="syn" />
</div>
</div>
<div v-else :class="editorMode ? 'children-org' : 'children'"> <!-- bude tu nějaký if na class="children" -->
<div v-if="item.children.length > 0 && item.children[0].appendable_siblings.length > 0 && editorMode">
<b>Vložit před: </b>
<addnewnode :types="item.children[0].appendable_siblings" :refnode="item.children[0].node" where="pred" />
</div>
<div v-if="item.node.polymorphic_ctype.model==='temavcislenode'">
<!--Children: {{String(showChildren)}}-->
<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" >
<!--Hide: {{hideNode(chld)}}, v tematu: {{v_tematu}}, force_visible: {{force_visible}}-->
<div v-if="!hideNode(chld)">
<div v-if="chld.node.polymorphic_ctype.model==='ulohazadaninode'">
<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button>
<button v-else v-on:click="showChildren=!showChildren"> Rozbalit </button>
<TreeNode :item="chld" :v_tematu="true"
:force_visible="showChildren"
:editorMode="editorMode"
:debugMode="debugMode">
</TreeNode>
</div>
<div v-else>
<TreeNode :item="chld" :v_tematu="true"
:force_visible="showChildren"
:editorMode="editorMode"
:debugMode="debugMode">
</TreeNode>
</div>
<div v-if="chld.appendable_siblings.length > 0 && editorMode" >
<b v-if="index < (item.children.length - 1)">Vložit mezi: </b>
<b v-else>Vložit za: </b>
<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" where="za" />
</div>
</div>
</div>
<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button>
<button v-else v-on:click="showChildren=!showChildren"> Rozbalit </button>
</div>
<div v-else>
<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" >
<div v-if="v_tematu && chld.node.polymorphic_ctype.model==='ulohazadaninode'">
<div> Tady možná něco je </div>
<TreeNode :item="chld" :v_tematu="v_tematu"
:force_visible="force_visible"
:editorMode="editorMode"
:debugMode="debugMode">
</TreeNode>
</div>
<div v-else>
<TreeNode :item="chld" :v_tematu="v_tematu"
:force_visible="force_visible"
:editorMode="editorMode"
:debugMode="debugMode">
</TreeNode>
</div>
<div v-if="chld.appendable_siblings.length > 0 && editorMode" >
<b v-if="index < (item.children.length - 1)">Vložit mezi: </b>
<b v-else>Vložit za: </b>
<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" where="za" />
</div>
</div>
</div>
</div>
</template>
<script>
import rocniknode from './RocnikNode.vue'
import cislonode from './CisloNode.vue'
import temavcislenode from './TemaVCisleNode.vue'
import castnode from './CastNode.vue'
import textnode from './TextNode.vue'
import ulohazadaninode from './UlohaZadaniNode.vue'
import ulohavzoraknode from './UlohaVzorakNode.vue'
import addnewnode from './AddNewNode.vue'
export default {
name: 'TreeNode',
components: {
rocniknode,
cislonode,
temavcislenode,
castnode,
textnode,
ulohazadaninode,
ulohavzoraknode,
addnewnode
},
data: () => ({
debugShow: false,
showChildren: false
}),
computed: {
},
props: {
item: Object,
force_visible: Boolean,
v_tematu: Boolean,
editorMode: Boolean,
debugMode: Boolean,
},
methods: {
hideNode: function(chld){
if (this.showChildren || this.force_visible){
return false;
}
if (chld.node.polymorphic_ctype.model === 'ulohazadaninode'){
return false;
}
return true;
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.treenode-org {
padding: 5px;
margin: 5px;
border: #6a0043 2px solid;
}
.children-org {
padding: 10px;
margin: 5px;
border: #6a0043 2px dashed;
}
.nodebug {
/* display: none; */
}
</style>

70
vue_frontend/src/components/TreeNodeRoot.vue

@ -0,0 +1,70 @@
<template>
<div id="app">
<div id="loading" v-if="loading">
Loading...
</div>
<!--pre>
{{item}}
</pre-->
<button v-show="editorMode" v-on:click="editorMode = false">Vypnout editační mód</button>
<button v-show="!editorMode" v-on:click="editorMode = true">Zapnout editační mód</button>
<button v-show="debugMode" v-on:click="debugMode = false">Vypnout ladicí mód</button>
<button v-show="!debugMode" v-on:click="debugMode = true">Zapnout ladicí mód</button>
<TreeNode :item="item" :editorMode="editorMode" :debugMode="debugMode"/>
</div>
</template>
<script>
import TreeNode from './TreeNode.vue'
import axios from 'axios'
export default {
name: 'App',
components: {
TreeNode,
},
data: () => ({
loading: true,
item: null,
tnid: 1,
editorMode: false,
debugMode: false,
}),
props:{
tnid: Number,
editorMode: Boolean,
debugMode: Boolean,
},
mounted: function() {
this.getArticles();
this.$root.$on('updateData',(arg) => {
console.log(arg);
this.getArticles();
});
},
methods: {
getArticles: function() {
this.loading = true;
axios.get('/treenode/'+this.tnid+'/json/')
.then((response) => {
this.item = response.data;
this.loading = false;
console.log('Data updated');
this.$root.$emit('dataUpdated',"dataUpdated");
})
.catch((err) => {
this.loading = false;
console.log(err);
})
}
},
events: {
updateData: function(){
this.getArticles()
}
}
}
</script>
<style>
@import '../../../mamweb/static/css/mamweb.css';
</style>

74
vue_frontend/src/components/UlohaVzorakNode.vue

@ -0,0 +1,74 @@
<template>
<div class="ulohavzoraknode">
<!--pre>UlohaVzorakNode {{item}} {{typeof(item)}}</pre-->
<h5>Řešení {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5>
<button v-if="editorMode" v-on:click="showSelect=!showSelect" class="upravit">Upravit</button>
<div v-if="showSelect">
<form class="searchForm" v-on:submit.prevent="submitSearch">
<input type="text" v-model="searchQuery" placeholder="Napište název" @keyup="submitSearch">
</form>
<div class="searchResult" v-show="isResult">
<ul>
<li v-for="res in searchResults" :key="res.id" v-on:click="setSelected(res)">{{res.nazev}}</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'UlohaVzorakNode',
data: () => {
return {
isResult: false,
searchQuery: '',
searchResults: [],
showSelect: false,
selected: null,
selected_id: null
}
},
props: {
item: Object,
create: Boolean,
showSelect: Boolean,
},
mounted: function(){
if (this.item.node.uloha === null){
console.log("Uloha je null!");
console.log(this.item);
}
if (this.create){
this.showSelect = true;
console.log('Creating');
}
},
methods: {
submitSearch: function(){
if (this.searchQuery.length < 3) { return;}
var reqURL = "/api/ulohavzoraknode/?nazev="+this.searchQuery;
axios.get(reqURL).then( (response) => {
this.searchResults = response.data.results;
this.isResult = true;
console.log("Got:");
console.log(this.searchResults);
}).catch( (err) => { /* fail response msg */
console.log(err);
});
},
setSelected: function(res){
this.searchQuery = res.nazev
this.selected_id = res.id
}
}
}
</script>
<style scoped>
.upravit {
margin-top:-40px;
}
</style>

23
vue_frontend/src/components/UlohaZadaniNode.vue

@ -0,0 +1,23 @@
<template>
<div class="ulohazadaninode">
<!--pre>UlohaZadaniNode {{item.node.uloha}} {{typeof(item)}}</pre-->
<h5>Zadání {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5>
</div>
</template>
<script>
export default {
name: 'UlohaZadaniNode',
props: {
item: Object,
created: Boolean
,
mounted: function(){
if (this.item.node.uloha === null){
console.log("Uloha je null!");
console.log(this.item);
}
}
}
}
</script>

12
vue_frontend/src/main.js

@ -0,0 +1,12 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import CKEditor from '@ckeditor/ckeditor5-vue'
Vue.config.productionTip = false
Vue.use(CKEditor);
new Vue({
router,
render: h => h(App),
}).$mount('#app')

28
vue_frontend/src/router/index.js

@ -0,0 +1,28 @@
import Vue from 'vue'
import Router from 'vue-router'
import TreeNodeRoot from '../components/TreeNodeRoot.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
linkActiveClass: 'active',
routes: [{
path: '/temp/vue',
name: 'treenodedefault',
props: {'tnid': 23},
component: TreeNodeRoot
}, {
path: '/temp/vue/:tnid',
name: 'treenode',
props: true,
component: TreeNodeRoot
}, {
path: '/zadani/aktualni',
name: 'treenode_zadani',
props: {'tnid': 23},
component: TreeNodeRoot
},
]
})

61
vue_frontend/vue.config.js

@ -0,0 +1,61 @@
const BundleTracker = require("webpack-bundle-tracker");
const pages = {
'vue_app_01': {
entry: './src/main.js',
chunks: ['chunk-vendors']
},
/* 'vue_app_02': {
entry: './src/newhampshir.js',
chunks: ['chunk-vendors']
},
*/
}
module.exports = {
pages: pages,
filenameHashing: false,
productionSourceMap: false,
publicPath: process.env.NODE_ENV === 'production'
? '/static/seminar/vue/'
: 'http://localhost:8080/',
outputDir: '../seminar/static/seminar/vue/',
chainWebpack: config => {
config.optimization
.splitChunks({
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
chunks: "all",
priority: 1
},
},
});
Object.keys(pages).forEach(page => {
config.plugins.delete(`html-${page}`);
config.plugins.delete(`preload-${page}`);
config.plugins.delete(`prefetch-${page}`);
})
config
.plugin('BundleTracker')
.use(BundleTracker, [{filename: '../vue_frontend/webpack-stats.json'}]);
config.resolve.alias
.set('__STATIC__', 'static')
config.devServer
.public('http://localhost:8080')
.host('localhost')
.port(8080)
.hotOnly(true)
.watchOptions({poll: 1000})
.https(false)
.headers({"Access-Control-Allow-Origin": ["*"]})
}
};

8544
vue_frontend/yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save