Browse Source

Merge branch 'data_migrations' into odevzdavatko

export_seznamu_prednasek
Pavel "LEdoian" Turinsky 4 years ago
parent
commit
b48d400543
  1. 8
      Makefile
  2. 4
      data/flat.json
  3. 10
      mamweb/routers.py
  4. 28
      mamweb/settings_common.py
  5. 38
      mamweb/static/css/mamweb-dev.css
  6. 21
      mamweb/static/css/mamweb.css
  7. 3
      mamweb/templates/base.html
  8. 5
      mamweb/urls.py
  9. 3
      requirements.txt
  10. 1
      seminar/.~lock.profile_vysledkovka.txt#
  11. 13
      seminar/admin.py
  12. 5
      seminar/forms.py
  13. 25
      seminar/models.py
  14. 7
      seminar/permissions.py
  15. BIN
      seminar/static/images/tema-bez-obrazku.png
  16. 18
      seminar/static/seminar/treenode_editor.js
  17. 97
      seminar/templates/seminar/archiv/cislo-normal.html
  18. 9
      seminar/templates/seminar/archiv/cislo.html
  19. 19
      seminar/templates/seminar/archiv/problem_tema.html
  20. 23
      seminar/templates/seminar/archiv/problem_uloha.html
  21. 23
      seminar/templates/seminar/orphanage.html
  22. 64
      seminar/templates/seminar/tematka/rozcestnik.html
  23. 10
      seminar/templates/seminar/treenode.html
  24. 27
      seminar/templates/seminar/treenode_add_stub.html
  25. 18
      seminar/templates/seminar/treenode_name.html
  26. 71
      seminar/templates/seminar/treenode_recursive.html
  27. 7
      seminar/templates/seminar/vuetest.html
  28. 198
      seminar/templatetags/treenodes.py
  29. 44
      seminar/testutils.py
  30. 244
      seminar/treelib.py
  31. 15
      seminar/urls.py
  32. 14
      seminar/utils.py
  33. 1
      seminar/views/__init__.py
  34. 362
      seminar/views/views_all.py
  35. 162
      seminar/views/views_rest.py
  36. 96
      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. 75
      vue_frontend/src/components/TreeNodeRoot.vue
  49. 75
      vue_frontend/src/components/UlohaVzorakNode.vue
  50. 23
      vue_frontend/src/components/UlohaZadaniNode.vue
  51. 12
      vue_frontend/src/main.js
  52. 33
      vue_frontend/src/router/index.js
  53. 61
      vue_frontend/vue.config.js
  54. 8544
      vue_frontend/yarn.lock

8
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}
@ -139,3 +141,9 @@ sync_local_db:
# Sync database and media. See above lines # Sync database and media. See above lines
sync_local: sync_local_media sync_local_db sync_local: sync_local_media sync_local_db
# Push local compiled Vue to gimli test site
push_compiled_vue_to_test:
scp vue_frontend/webpack-stats.json mam-web@gimli:/akce/mam/www/mamweb-test/vue_frontend/
rsync -ave ssh seminar/static/seminar/vue mam-web@gimli:/akce/mam/www/mamweb-test/seminar/static/seminar/
ssh mam-web@gimli.ms.mff.cuni.cz 'cd /akce/mam/www/mamweb-test/ && . env/bin/activate && ./manage.py collectstatic --noinput'

4
data/flat.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)

28
mamweb/settings_common.py

@ -121,6 +121,10 @@ INSTALLED_APPS = (
'polymorphic', 'polymorphic',
'webpack_loader',
'rest_framework',
'rest_framework.authtoken',
# MaMweb # MaMweb
'mamweb', 'mamweb',
'seminar', 'seminar',
@ -184,6 +188,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
@ -270,6 +295,9 @@ LOGGING = {
}, },
} }
# Permissions for uploads
FILE_UPLOAD_PERMISSIONS = 0o0644
# MaM specific # MaM specific
SEMINAR_RESENI_DIR = os.path.join('reseni') SEMINAR_RESENI_DIR = os.path.join('reseni')

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;
}

21
mamweb/static/css/mamweb.css

@ -759,17 +759,26 @@ div.odpocet {
/*stránka organizátorů*/ /*stránka organizátorů*/
div.seznam_orgu { div.seznam_orgu, div.rozcestnik_temat {
text-align: center; text-align: center;
padding-bottom: 10px;
} }
div.org_pole, div.rocnik_pole { div.org_pole, div.rocnik_pole, div.tema_pole {
display: inline-block; display: inline-block;
width: 30%; width: 30%;
min-width: 300px; min-width: 300px;
text-align: center; text-align: center;
} }
div.tema_pole {
display: inline-block;
width: 40%;
min-width: 350px;
padding-bottom: 20px;
text-align: center;
}
div.cislo_pole { div.cislo_pole {
display: inline-block; display: inline-block;
width: 15%; width: 15%;
@ -812,6 +821,11 @@ div.org_email {
height: 205px; height: 205px;
} }
#tema-rozcestnik.flip-card {
width: 300px;
height: 300px;
}
/* This container is needed to position the front and back side */ /* This container is needed to position the front and back side */
.flip-card-inner { .flip-card-inner {
position: relative; position: relative;
@ -835,7 +849,8 @@ div.org_email {
backface-visibility: hidden; backface-visibility: hidden;
} }
div.flip-card-foto img {
div.flip-card-foto, div.flip-card-foto img {
width: 100%; width: 100%;
height: 100%; height: 100%;

3
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">

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

1
seminar/.~lock.profile_vysledkovka.txt#

@ -0,0 +1 @@
,anet,erebus,25.03.2020 22:21,file:///home/anet/.config/libreoffice/4;

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',)

25
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
@ -744,11 +743,20 @@ class Problem(SeminarModelBase,PolymorphicModel):
return '<Není zadaný>' return '<Není zadaný>'
def verejne(self): def verejne(self):
# FIXME: Tohle se liší podle typu problému, má se udělat polymorfně. # aktuálně podle stavu problému
# Zatím je tu jen dummy fail-safe default: nic není veřejné. # FIXME pro některé problémy možná chceme override
return False # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
# FIXME: Tohle je blbost # Je to tak správně?
return (self.cislo_zadani and self.cislo_zadani.verejne()) stav_verejny = False
if self.stav == 'zadany' or self.stav == 'vyreseny':
stav_verejny = True
return stav_verejny
#cislo_verejne = False
#if (self.cislo_zadani and self.cislo_zadani.verejne()):
# cislo_verejne = True
#return (stav_verejny and cislo_verejne)
verejne.boolean = True verejne.boolean = True
def verejne_url(self): def verejne_url(self):
@ -844,9 +852,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:
@ -1367,6 +1373,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):

7
seminar/permissions.py

@ -0,0 +1,7 @@
from rest_framework.permissions import BasePermission
class AllowWrite(BasePermission):
def has_permission(self, request, view):
return request.user.has_perm('auth.org')

BIN
seminar/static/images/tema-bez-obrazku.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

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';
}

97
seminar/templates/seminar/archiv/cislo-normal.html

@ -0,0 +1,97 @@
{% extends "seminar/archiv/base_cisla.html" %}
{# {% block content %}
<div>
<h1>
{% block nadpis1a %}{% block nadpis1b %}
Číslo {{ cislo }}
{% endblock %}{% endblock %}
</h1>
{% if cislo.pdf %}
<p><a href='{{ cislo.pdf.url }}'>Číslo v pdf</a>
{% endif %}
<p><a href='{{ cislo.rocnik.verejne_url }}'>Ročník {{ cislo.rocnik }}</a>
{% if v_cisle_zadane %}
<h2>Zadané problémy</h2>
<ul>
{% for p in v_cisle_zadane %}
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}>
{% if user.is_staff or cislo.verejne %}
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %}
{% endfor %}
</ul>
{% endif %}
{% if resene_problemy %}
<h2>Řešené problémy</h2>
<ul>
{% for p in resene_problemy %}
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}>
{% if user.is_staff or cislo.verejne %}
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %}
{% endfor %}
</ul>
{% endif %}
{% if user.is_staff %}
<div class="mam-org-only">
<h2> Orgovské odkazy </h2>
<ul>
<li><a href="obalky.pdf">Obálky (PDF)</a></li>
<li><a href="tituly.tex">Tituly (TeX)</a></li>
<li><a href="vysledkovka.tex">Výsledkovka (TeX)</a></li>
<li><a href="obalkovani">Obálkování</a></li>
</ul>
</div>
{% endif %}
{% if cislo.verejna_vysledkovka %}
<h2>Výsledkovka</h2>
{% else %}
{% if user.is_staff %}
<div class='mam-org-only'>
<h2>Výsledkovka (neveřejná)</h2>
{% endif %}
{% endif %}
{% if cislo.verejna_vysledkovka or user.is_staff %}
<table class='vysledkovka'>
<tr class='border-b'>
<th class='border-r'>#
<th class='border-r'>Jméno #}
{# problémy by měly být veřejné, když je veřejná výsledkovka #}
{# {% for p in problemy %}
<th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a>
{% endfor %}
<th class='border-r'>Za číslo</sup>
<th class='border-r'>Za ročník
<th class='border-r'>Odjakživa
{% for rv in radky_vysledkovky %}
<tr>
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
<th class='border-r'>
{% if rv.resitel.titul != "" %}
{{ rv.resitel.titul }}<sup>MM</sup>
{% endif %}
{{ rv.resitel.osoba.plne_jmeno }}
{% for b in rv.hlavni_problemy_body %}
<td class='border-r'>{{ b }}
{% endfor %}
<td class='border-r'>{{ rv.body_cislo }}
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
<td class='border-r'>{{ rv.body_celkem_odjakziva }}
</tr>
{% endfor %}
</table>
{% endif %}
{% if not cislo.verejna_vysledkovka and user.is_staff %}
</div>
{% endif %}
</div>
{% endblock content %} #}

9
seminar/templates/seminar/archiv/cislo.html

@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% block content %} {% block content %}
<div> <div>
@ -48,6 +49,14 @@
</div> </div>
{% endif %} {% endif %}
<script id="vuedata" type="application/json">{"treenode":{{cislo.cislonode.id}}}</script>
<div id="app">
<app></app>
</div>
{% render_bundle 'chunk-vendors' %}
{% render_bundle 'vue_app_01' %}
{% if cislo.verejna_vysledkovka %} {% if cislo.verejna_vysledkovka %}
<h2>Výsledkovka</h2> <h2>Výsledkovka</h2>

19
seminar/templates/seminar/archiv/problem_tema.html

@ -0,0 +1,19 @@
{% extends "seminar/archiv/problem.html" %}
{% block problem %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
{{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }}
{% endblock %}{% endblock %}
</h1>
<h2>Zadání</h2>
{{ problem.text_zadani |safe }}
{% if problem.text_reseni %}
<h2>Řešení</h2>
{{ problem.text_reseni |safe }}
{% endif %}
{# TODO vysledkovka tematu #}
{% endblock %}

23
seminar/templates/seminar/archiv/problem_uloha.html

@ -0,0 +1,23 @@
{% extends "seminar/archiv/problem.html" %}
{% block problem %}
<h1>
{% block nadpis1a %}{% block nadpis1b %}
{{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }} {{ problem.body_v_zavorce }}
{% endblock %}{% endblock %}
</h1>
{% if problem.cislo_zadani %}
<p>Zadáno v čísle <a href='{{ problem.cislo_zadani.verejne_url }}'>{{ problem.cislo_zadani.kod }}</a>.
{% endif %}
{% if problem.cislo_reseni %}
<p>Řešeno v čísle <a href='{{ problem.cislo_reseni.verejne_url }}'>{{ problem.cislo_reseni.kod }}</a>.
{% endif %}
<h2>Zadání</h2>
{{ problem.text_zadani |safe }}
{% if problem.text_reseni %}
<h2>Řešení</h2>
{{ problem.text_reseni |safe }}
{% endif %}
{% endblock %}

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 %}

64
seminar/templates/seminar/tematka/rozcestnik.html

@ -1,14 +1,54 @@
{% extends "base.html" %}
{% block content %}
<h2>
{% block nadpis1a %}{% block nadpis1b %}
Aktuální témata
{% endblock %}{% endblock %}
</h2>
<p>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><a href="/co-je-MaM/jak-resit/">Jak řešit téma?</a></p>
<div class="rozcestnik_temat">
{% for tematko in tematka %} {% for tematko in tematka %}
<h1>{{tematko.nazev}}</h1>
<p>{{tematko.abstrakt}}</p> {# karta témátka - zepředu ilustrační, zezadu abstrakt #}
<ul> <div class="tema_pole">
{% for cislo in tematko.cisla %}
<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{cislo.0.1}}">{{cislo.0.0}}</a></li> <h3>
<ul> <a href='{{ rocnik.verejne_url }}'>Téma {{ tematko.nazev }}</a>
{% for odkaz in cislo.1 %} </h3>
<li><a href="/{{rocnik}}/t{{tematko.kod}}/#{{odkaz.1}}">{{odkaz.0}}</a></li>
{% endfor %} <div class="flip-card" id="tema-rozcestnik">
</ul>
{% endfor %} <div class="flip-card-inner">
</ul> <div class="flip-card-front">
<div class="flip-card-foto">
{% if tematko.obrazek %}
<img src="{{ tematko.obrazek.url }}" alt="{{ tematko.nazev }}">
{% else %} {# pokud témátko nemá fotku, zobrazuje se defaultní obrázek #}
{% load static %} <img src="{% static 'images/tema-bez-obrazku.png' %}" alt="{{ tematko.nazev }}">
{% endif %}
</div>
</div>
<div class="flip-card-back">
<p>{{ tematko.abstrakt }}</p>
</div>
</div>
</div>
</div>
{# konec karty témátka #}
{% endfor %} {% endfor %}
</div>
{% 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 %}

71
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 %}
<div class="borderized children">
{% with kam="před" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{%for ch in obj.children %} {%for ch in obj.children %}
{%with obj=ch depth=depth|add:"1" template_name="seminar/treenode_recursive.html" %}
{%include template_name%} {# ----------- Vypisujeme podstrom ----------#}
{%endwith%} {%with obj=ch depth=depth|add:"1" %} {%include "seminar/treenode_recursive.html" %} {%endwith%}
{%endfor%} {# ----------- 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> </div>
{%endif%} {% endfor %}
</div>
{% 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' %}

198
seminar/templatetags/treenodes.py

@ -1,8 +1,25 @@
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)
@ -43,7 +60,184 @@ def isUlohaZadani(value):
def isPohadka(value): def isPohadka(value):
return isinstance(value, m.PohadkaNode) return isinstance(value, m.PohadkaNode)
#@register.filter @register.filter
#def isOtisteneReseniNode(value): def isReseni(value):
return False
# return isinstance(value, m.OtisteneReseniNode) # 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
#def is(value):
# return

44
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

244
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()
if subtree3L_tail is not None:
subtree3L_tail.succ = None subtree3L_tail.succ = None
subtree3L_tail.save() subtree3L_tail.save()
subtree4_tail.succ = subtree3P.head 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() 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()
if subtree2_tail is not None:
subtree2_tail.succ = C subtree2_tail.succ = C
subtree2_tail.save() 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)

15
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
@ -20,6 +20,14 @@ urlpatterns = [
path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'),
path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'), path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'),
path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'), path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'),
path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
path('treenode/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,6 +157,9 @@ 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'), path('jak-resit/', views.JakResitView.as_view(), name='jak-resit'),

14
seminar/utils.py

@ -148,16 +148,12 @@ def resi_v_rocniku(rocnik, cislo=None):
if cislo is None: if cislo is None:
# filtrujeme pouze podle ročníku # filtrujeme pouze podle ročníku
letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik) return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct()
else: # filtrujeme podle ročníku i čísla else: # filtrujeme podle ročníku i čísla
letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik, return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
hodnoceni__cislo_body__poradi__lte=cislo.poradi) reseni__hodnoceni__cislo_body__rocnik=rocnik,
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct()
# vygenerujeme queryset řešitelů, co letos něco poslali
letosni_resitele = m.Resitel.objects.none()
for reseni in letosni_reseni:
letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok())
return letosni_resitele.distinct()
def aktivniResitele(cislo, pouze_letosni=False): def aktivniResitele(cislo, pouze_letosni=False):

1
seminar/views/__init__.py

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

362
seminar/views/views_all.py

@ -1,4 +1,4 @@
# coding:utf-8
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse
@ -16,13 +16,19 @@ 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.core.exceptions import PermissionDenied
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, MAXYEAR from datetime import timedelta, date, datetime, MAXYEAR
from django.utils import timezone from django.utils import timezone
@ -89,40 +95,324 @@ 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 = []
@classmethod
def public_above(cls, anode):
""" Returns output of verejne for closest Rocnik, Cislo or Problem above.
(All of them have method verejne.)"""
parent = anode # chceme začít už od konkrétního node včetně
while True:
rocnik = isinstance(parent, s.RocnikNode)
cislo = isinstance(parent, s.CisloNode)
uloha = (isinstance(parent, s.UlohaVzorakNode) or
isinstance(parent, s.UlohaZadaniNode))
tema = isinstance(parent, s.TemaVCisleNode)
if (rocnik or cislo or uloha or tema) or parent==None:
break
else:
parent = treelib.get_parent(parent)
if rocnik:
return parent.rocnik.verejne()
elif cislo:
return parent.cislo.verejne()
elif uloha:
return parent.uloha.verejne()
elif tema:
return parent.tema.verejne()
elif None:
print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou"
"ani tématem. {}".format(anode))
return False
@classmethod
def all_public_children(cls, anode):
for ch in treelib.all_children(anode):
if TNLData.public_above(ch):
yield ch
else:
continue
@classmethod
def from_treenode(cls, anode, user, parent=None, index=None):
if TNLData.public_above(anode) or user.has_perm('auth.org'):
out = cls(anode,parent,index)
else:
raise PermissionDenied()
def treenode_strom_na_seznamy(node): if user.has_perm('auth.org'):
out = TNLData(node) enum_children = enumerate(treelib.all_children(anode))
for ch in treelib.all_children(node): else:
outitem = treenode_strom_na_seznamy(ch) enum_children = enumerate(TNLData.all_public_children(anode))
for (idx,ch) in enum_children:
outitem = cls.from_treenode(ch, user, out, idx)
out.children.append(outitem) out.children.append(outitem)
out.add_edit_options()
return out 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
template_name = 'seminar/treenode.html' template_name = 'seminar/treenode.html'
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,self.request.user)
return context return context
# 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 TreeNodeJSONView(generic.DetailView):
class AktualniZadaniView(TreeNodeView): model = s.TreeNode
def get_object(self):
nastaveni = get_object_or_404(Nastaveni)
return nastaveni.aktualni_cislo.cislonode
def get_context_data(self,**kwargs): def get(self,request,*args, **kwargs):
nastaveni = get_object_or_404(Nastaveni) self.object = self.get_object()
data = TNLData.from_treenode(self.object,self.request.user).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'
def get_queryset(self):
return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None)
# FIXME pouzit Django REST Framework
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() user = self.request.user
context['verejne'] = verejne # Teď potřebujeme doplnit tnldata do kontextu.
# 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,user)
elif isinstance(self.object, s.Uloha):
# FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever
tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
elif isinstance(self.object, s.Tema):
rocniknode = self.object.rocnik.rocniknode
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()
@ -138,7 +428,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:
@ -1318,6 +1620,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):

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,
}

96
seminar/viewsets.py

@ -0,0 +1,96 @@
from rest_framework import viewsets,filters
from rest_framework.permissions import BasePermission, AllowAny
from . import models as m
from . import views
from seminar.permissions import AllowWrite
class PermissionMixin(object):
""" Redefines get_permissions so that only organizers can make changes. """
def get_permissions(self):
permission_classes = []
print("get_permissions have been called.")
if self.action in ["create", "update", "partial_update", "destroy"]:
permission_classes = [AllowWrite] # speciální permission na zápis - orgové
else:
permission_classes = [AllowAny]
# návštěvník nemusí být zalogován, aby si prohlížel obsah
return [permission() for permission in permission_classes]
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(PermissionMixin, viewsets.ModelViewSet):
queryset = m.UlohaVzorakNode.objects.all()
serializer_class = views.UlohaVzorakNodeSerializer
class TextViewSet(PermissionMixin, viewsets.ModelViewSet):
queryset = m.Text.objects.all()
serializer_class = views.TextSerializer
class TextNodeViewSet(PermissionMixin, 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(PermissionMixin, 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(PermissionMixin, 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)
if self.request.user.has_perm('auth.org'):
return queryset
else: # pro neorgy jen zveřejněné vzoráky
return queryset.filter(uloha__cislo_reseni__verejne_db=True)

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>

75
vue_frontend/src/components/TreeNodeRoot.vue

@ -0,0 +1,75 @@
<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,
tnsource: String,
editorMode: Boolean,
debugMode: Boolean,
},
mounted: function() {
if (this.tnsource && this.tnsource=='inline'){
let data = JSON.parse(document.getElementById('vuedata').textContent);
this.tnid = data.treenode;
}
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>

75
vue_frontend/src/components/UlohaVzorakNode.vue

@ -0,0 +1,75 @@
<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,
editorMode: 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')

33
vue_frontend/src/router/index.js

@ -0,0 +1,33 @@
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
}, {
path: '/cislo/:cislo',
name: 'treenode_cislo',
props: {'tnsource':'inline'},
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