Browse Source

Merge branch 'data_migrations' into test

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

2
Makefile

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

6
data/flat.json

File diff suppressed because one or more lines are too long

661
data/sitetree_new.json

File diff suppressed because one or more lines are too long

10
mamweb/routers.py

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

24
mamweb/settings_common.py

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

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

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

281
mamweb/static/css/mamweb.css

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

26847
mamweb/static/images/jakresit_1.svg

File diff suppressed because it is too large

After

Width:  |  Height:  |  Size: 1.8 MiB

26847
mamweb/static/images/jakresit_2.svg

File diff suppressed because it is too large

After

Width:  |  Height:  |  Size: 1.8 MiB

26847
mamweb/static/images/jakresit_3.svg

File diff suppressed because it is too large

After

Width:  |  Height:  |  Size: 1.8 MiB

18
mamweb/templates/base.html

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

5
mamweb/urls.py

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

3
requirements.txt

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

13
seminar/admin.py

@ -107,6 +107,8 @@ class TreeNodeAdmin(PolymorphicParentModelAdmin):
m.PohadkaNode,
m.UlohaVzorakNode,
m.TextNode,
m.CastNode,
m.OrgTextNode,
]
actions = ['aktualizuj_nazvy']
@ -160,6 +162,17 @@ class TextNodeAdmin(PolymorphicChildModelAdmin):
base_model = m.TextNode
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.Novinky)

5
seminar/forms.py

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

8
seminar/models.py

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

18
seminar/static/seminar/treenode_editor.js

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

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

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

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

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

12
seminar/templates/seminar/novinky.html

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

23
seminar/templates/seminar/orphanage.html

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

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

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

77
seminar/templates/seminar/titulnistrana.html

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

10
seminar/templates/seminar/treenode.html

@ -1,10 +1,12 @@
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% load comments %}
{% 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 %}

27
seminar/templates/seminar/treenode_add_stub.html

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

18
seminar/templates/seminar/treenode_name.html

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

73
seminar/templates/seminar/treenode_recursive.html

@ -1,28 +1,55 @@
{% load treenodes %}
{# <b>{{depth}}</b> #}
<div>
{% 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|isText %}
{{obj.node.text.na_web}}
{% else %}
Objekt jiného typu {{obj.node}}
<div class="borderized parent">
<div class="node_type">
{{obj.node}}
{{obj.node.id}}
{% if obj.node|deletable %}
<button type="submit" formaction="{%url 'treenode_smazat' obj.node.id%}">Smazat</button>
{% endif %}
{% if obj.parent and obj.parent|editableSiblings %}
<button type="submit" formaction="{%url 'treenode_odvesitpryc' obj.node.id%}">Odvěsit pryč ze stromu {{obj.parent.node}}</button>
{% endif %}
{% if obj|canPodvesitPred %}
<button type="submit" formaction="{%url 'treenode_podvesit' obj.node.id 'pred'%}">Podvěsit pod předchozí</button> - nejsou testovací data
{% endif %}
{% if obj|canPodvesitZa %}
<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 %}
{%if obj.children %}
<div>
{%for ch in obj.children %}
{%with obj=ch depth=depth|add:"1" template_name="seminar/treenode_recursive.html" %}
{%include template_name%}
{%endwith%}
{%endfor%}
{% include "seminar/treenode_name.html" %}
{%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 %}
{# ----------- Vypisujeme podstrom ----------#}
{%with obj=ch depth=depth|add:"1" %} {%include "seminar/treenode_recursive.html" %} {%endwith%}
{# ----------- Přidáváme mezi syny / za posledního -------- #}
{% if forloop.last %}
{% with kam="za" kam_slug="za" obj=ch %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% else %}
{% with kam="mezi" obj=ch kam_slug="za" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{% endif %}
{# ----------- Prohazujeme sousedy ----------#}
<div class="pink">
{% if not forloop.last and ch|editableSiblings %}
<button type="submit" formaction="{%url 'treenode_prohodit' ch.node.id%}">Prohodit ^ a v</button>
{% endif %}
</div>
{% endfor %}
</div>
{%endif%}
{% else %}
{# ----------- Přidáváme prvního syna ----------#}
{% with kam="jako syna" kam_slug="syn" %} {% include "seminar/treenode_add_stub.html" %} {% endwith %}
{%endif%}
</div>

7
seminar/templates/seminar/vuetest.html

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

218
seminar/templatetags/treenodes.py

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

46
seminar/testutils.py

@ -244,8 +244,8 @@ def gen_zadani_ulohy(rnd, cisla, organizatori, pocet_oboru, poradi_cisla, poradi
na_web = text,
do_cisla = text,
)
zad = TextNode.objects.create(text = text_zadani)
uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad)
zad = TextNode.objects.create(text = text_zadani, root = p.cislo_zadani.rocnik.rocniknode)
uloha_zadani = UlohaZadaniNode.objects.create(uloha = p, first_child = zad, root = p.cislo_zadani.rocnik.rocniknode)
p.ulohazadaninode = 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,
do_cisla = obsah
)
vzorak = TextNode.objects.create(text = text_vzoraku)
uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak)
vzorak = TextNode.objects.create(text = text_vzoraku, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha_vzorak = UlohaVzorakNode.objects.create(uloha = uloha, first_child = vzorak, root = uloha.cislo_zadani.rocnik.rocniknode)
uloha.ulohavzoraknode = uloha_vzorak
uloha.opravovatele.set(rnd.sample(organizatori, pocet_opravovatelu))
@ -434,7 +434,7 @@ def gen_cisla(rnd, rocniky):
datum_deadline=deadline,
verejne_db=True
)
node2 = CisloNode.objects.create(cislo = cislo, succ = node)
node2 = CisloNode.objects.create(cislo = cislo, succ = node, root=rocnik.rocniknode)
cislo.save()
node = node2
if otec:
@ -473,54 +473,54 @@ def gen_dlouhe_tema(rnd, organizatori, rocnik, nazev, obor, kod):
for cislo in cisla:
# Přidáme TemaVCisleNode do daného čísla
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)
# 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)
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)
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)
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)
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)
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)
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)
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)
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.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)
# Občas přidáme mezičíslo
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
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)
odstavec = lorem.paragraph()
text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec)
text_node_mezicislo = TextNode.objects.create(text = text_mezicislo)
text_mezicislo = Text.objects.create(na_web = odstavec, do_cisla = odstavec)
text_node_mezicislo = TextNode.objects.create(text = text_mezicislo, root=cislo_node.root)
add_first_child(cast_node_mezicislo, text_node_mezicislo)
return tema
@ -564,7 +564,7 @@ def gen_temata(rnd, rocniky, rocnik_cisla, organizatori):
# Vyrobíme TemaVCisleNody pro obsah
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?
otec = cisla[i-1].cislonode
otec_syn(otec, node)
@ -621,8 +621,8 @@ def gen_ulohy_tematu(rnd, organizatori, tema, kod, cislo, cislo_se_vzorakem):
na_web = obsah,
do_cisla = obsah,
)
zad = TextNode.objects.create(text = text_zadani)
uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad)
zad = TextNode.objects.create(text = text_zadani, root=tema.temavcislenode_set.first().root)
uloha_zadani = UlohaZadaniNode.objects.create(uloha=uloha, first_child = zad, root=tema.temavcislenode_set.first().root)
uloha.ulohazadaninode = uloha_zadani
return uloha, uloha_zadani

254
seminar/treelib.py

@ -1,7 +1,9 @@
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
# 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: 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.
def print_tree(node,indent=0):
@ -12,37 +14,41 @@ def print_tree(node,indent=0):
if node.succ:
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
def safe_pred(node):
if node is None:
return None
try:
return node.prev
except ObjectDoesNotExist:
return None
def first_brother(node):
if node is None:
# FIXME: Proč?????
def safe_succ(node):
try:
return node.succ
except ObjectDoesNotExist:
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):
if node is None:
return None
first = first_brother(node)
try:
return first.father_of_first
except ObjectDoesNotExist:
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
# Tohle se teď zrovna k None chová správně, ale je potřeba na to dávat pozor
def get_parent(node):
# Nejdřív získáme prvního potomka...
while safe_pred(node) is not None:
@ -51,14 +57,33 @@ def get_parent(node):
return safe_father_of_first(node)
def get_last_child(node):
if node is None:
return None
first = node.first_child
if first is None:
return None
else:
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é)
def general_next(node):
if node is None:
return None
# Máme potomka?
if node.first_child is not None:
return node.first_child
@ -71,6 +96,8 @@ def general_next(node):
return node.succ
def last_brother(node):
if node is None:
return None
while node.succ is not None:
node = node.succ
return node
@ -78,6 +105,7 @@ def last_brother(node):
def general_prev(node):
# Předchůdce je buď rekurzivně poslední potomek předchůdce, nebo náš otec.
# Otce vyřešíme nejdřív:
# Tady se ošetří node=None samo
if safe_pred(node) is None:
return safe_father_of_first(node)
pred = safe_pred(node)
@ -95,12 +123,16 @@ def me_and_right_brothers(node):
current = current.succ
def right_brothers(node):
if node is None:
return
generator = me_and_right_brothers(node.succ)
for item in generator:
yield item
# Generátor všech sourozenců (vč. sám sebe)
def all_brothers(node):
if node is None:
return
# Najdeme prvního bratra
fb = first_brother(node)
marb = me_and_right_brothers(fb)
@ -108,6 +140,8 @@ def all_brothers(node):
yield cur
def all_proper_brothers(node):
if node is None:
return
all = all_brothers(node)
for br in all:
if br is node:
@ -116,12 +150,16 @@ def all_proper_brothers(node):
def all_children(node):
""" Generátor všech potomků zadaného Node. """
if node is None:
return
brothers = all_brothers(node.first_child)
for br in brothers:
yield br
def all_children_of_type(node, type):
""" Generuje všechny potomky daného Node a daného typu. """
if node is None:
return
brothers = all_brothers(node.first_child)
for br in brothers:
if isinstance(br, type):
@ -130,6 +168,8 @@ def all_children_of_type(node, type):
# Generátor následníků v "the-right-order"
# Bez tohoto vrcholu
def all_following(node):
if node is None:
return
current = general_next(node)
while current is not None:
yield current
@ -139,12 +179,16 @@ def all_following(node):
# 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.
def get_next_brother_of_type(node, type):
if node is None:
return
for current in right_brothers(node):
if isinstance(current, type):
return current
return None
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.
current = node
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í
def get_next_node_of_type(node, type):
if node is None:
return
for cur in all_folowing(node):
if isinstance(cur, type):
return cur
@ -162,6 +208,8 @@ def get_next_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.
if node is None:
return
current = node
while general_prev(current) is not None:
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:
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.root = predecessor.root
new_node.save()
succ = predecessor.succ
predecessor.succ = new_node
predecessor.save()
new_node.succ = succ
new_node.save()
return new_node
# Vyrábí prvního syna, ostatní nalepí za (existují-li)
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.root = parent.root
new_node.save()
orig_child = parent.first_child
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
new_node.succ = orig_child
new_node.save()
return new_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. """
last = get_last_child(parent)
if not is_orphan(node):
@ -213,6 +282,11 @@ def insert_last_child(parent, node):
last.save()
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:
# Easy: přidáme za předchůdce
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)
# 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.root = successor.root
new.succ = successor
new.save()
return new
# ValueError, pokud je (aspoň) jeden parametr None
def swap(node, other):
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):
if node is None:
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
if pred is not None:
pred.succ = succ
pred.succ = None
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.save()
node.succ = post_succ
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
# Dokumentace viz wiki:
# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku)
def raise_node(node):
if node is None:
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)
# FIXME: Velmi naivní, chybí error checky
# FIXME: Trochu méně naivní, nevěřím tomu, prosím otestovat
D = node
C = get_parent(D)
E = C.succ
subtree4_head = D.first_child
subtree4_tail = last_brother(subtree4_head)
subtree3P_head = D.succ
subtree3L_head = C.first_child
subtree3L_tail = safe_pred(D)
if C is None:
raise TreeLibError("Nelze povýšit vrchol, jenž nemá otce.")
E = C.succ # Může být None a ničemu to nevadí
subtree4_head = D.first_child # Může být None, ale pak se musí z 3P udělat přímo potomek D
subtree4_tail = last_brother(subtree4_head) # Měl by být None právě když je sub4_head=None
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...
pass
# 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()
D.succ = E
D.succ = E # Nespadne
D.save()
subtree3L_tail.succ = None
subtree3L_tail.save()
subtree4_tail.succ = subtree3P.head
subtree4_tail.save()
if subtree3L_tail is not None:
subtree3L_tail.succ = None
subtree3L_tail.save()
else:
assert C.first_child is D
C.first_child = None
C.save()
if subtree4_tail is not None:
subtree4_tail.succ = subtree3P_head
subtree4_tail.save()
else:
D.first_child = subtree3P_head
D.save()
# To by mělo být všechno...
# (lower bude jednoduchá rotace, ne mega, existence jednoduché rotace mi došla až po nakreslení obrázku)
def lower_node(node):
if node is None:
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)
# FIXME: Velmi naivní, chybí error checky
# FIXME: Trochu naivní, prosím otestovat
C = node
D = C.succ
D = C.succ # Může být None a ničemu to nevadí
B = safe_pred(C)
subtree2_head = B.first_child
subtree2_tail = last_brother(subtree2_head)
if B is None:
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...
pass
# 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()
subtree2_tail.succ = C
subtree2_tail.save()
if subtree2_tail is not None:
subtree2_tail.succ = C
subtree2_tail.save()
else:
assert subtree2_head is None
B.first_child = C
B.save()
# To by mělo být všechno...
def disconnect_node(node):
#FIXME: dodělat odstranění roota všem potomkům
if node is None:
raise TreeLibError("Nelze odpojit None. Tohle by se nemělo stát.")
print(f'My:{node}, predchudce:{safe_pred(node)}, naslednik:{safe_succ(node)}, otec:{safe_father_of_first(node)}')
# Jsme prvnim synem
if safe_pred(node) is None:
if safe_succ(node) is None: # Jsme jedinym synem - upravime otce (pokud mame) a odpojime se
father = safe_father_of_first(node)
if father is not None:
father.first_child = None
father.save()
return
else: # mame bratra
swap_succ(node) # Staneme se neprvním synem, pokracujeme mimo if
# Jsme neprvním synem
prev = node.prev
prev.succ = node.succ
node.succ = None
node.save()
clear_root(node)
prev.save()
def clear_root(node):
node.root = None
node.save()
if node.first_child:
clear_root(node.first_child)
if node.succ:
clear_root(node.succ)
def set_root(node,root):
node.root = root
node.save()
if node.first_child:
clear_root(node.first_child)
if node.succ:
clear_root(node.succ)

18
seminar/urls.py

@ -1,4 +1,4 @@
from django.urls import path, include
from django.urls import path, include, re_path
from django.contrib.auth.decorators import login_required
from . import views, export
from .utils import org_required, resitel_required
@ -17,9 +17,17 @@ urlpatterns = [
path('archiv/temata/', views.ArchivTemataView.as_view()),
path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'),
path('cislo/<int:rocnik>.<int:cislo>/', views.CisloView.as_view(), name='seminar_cislo'), # odkomentované jenom kvůli testování archivu
path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'),
path('problem/<int:pk>/', views.ProblemView.as_view(), name='seminar_problem'),
path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'),
path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'),
path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'),
path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'),
path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'),
path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'),
path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'),
path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'),
#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'),
# Soustredeni
@ -50,7 +58,7 @@ urlpatterns = [
# 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('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'),
@ -149,7 +157,11 @@ urlpatterns = [
path('temp/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'),
path('temp/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('jak-resit/', views.JakResitView.as_view(), name='jak-resit'),
# Ceka na autocomplete v3
# path('autocomplete/organizatori/',

1
seminar/views/__init__.py

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

416
seminar/views/views_all.py

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

162
seminar/views/views_rest.py

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

77
seminar/viewsets.py

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

22
vue_frontend/.gitignore

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

5
vue_frontend/babel.config.js

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

48
vue_frontend/package.json

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

7
vue_frontend/src/App.vue

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

50
vue_frontend/src/components/AddNewNode.vue

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

86
vue_frontend/src/components/CastNode.vue

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

19
vue_frontend/src/components/CisloNode.vue

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

21
vue_frontend/src/components/RocnikNode.vue

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

19
vue_frontend/src/components/TemaVCisleNode.vue

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

174
vue_frontend/src/components/TextNode.vue

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

161
vue_frontend/src/components/TreeNode.vue

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

70
vue_frontend/src/components/TreeNodeRoot.vue

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

74
vue_frontend/src/components/UlohaVzorakNode.vue

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

23
vue_frontend/src/components/UlohaZadaniNode.vue

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

12
vue_frontend/src/main.js

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

28
vue_frontend/src/router/index.js

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

61
vue_frontend/vue.config.js

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

8544
vue_frontend/yarn.lock

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