Merge branch 'data_migrations' into odevzdavatko
This commit is contained in:
commit
b48d400543
54 changed files with 10996 additions and 182 deletions
8
Makefile
8
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}
|
||||
|
@ -139,3 +141,9 @@ sync_local_db:
|
|||
|
||||
# Sync database and media. See above lines
|
||||
sync_local: sync_local_media sync_local_db
|
||||
|
||||
# Push local compiled Vue to gimli test site
|
||||
push_compiled_vue_to_test:
|
||||
scp vue_frontend/webpack-stats.json mam-web@gimli:/akce/mam/www/mamweb-test/vue_frontend/
|
||||
rsync -ave ssh seminar/static/seminar/vue mam-web@gimli:/akce/mam/www/mamweb-test/seminar/static/seminar/
|
||||
ssh mam-web@gimli.ms.mff.cuni.cz 'cd /akce/mam/www/mamweb-test/ && . env/bin/activate && ./manage.py collectstatic --noinput'
|
||||
|
|
File diff suppressed because one or more lines are too long
10
mamweb/routers.py
Normal file
10
mamweb/routers.py
Normal file
|
@ -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)
|
||||
|
|
@ -120,6 +120,10 @@ INSTALLED_APPS = (
|
|||
'imagekit',
|
||||
|
||||
'polymorphic',
|
||||
|
||||
'webpack_loader',
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
|
||||
# MaMweb
|
||||
'mamweb',
|
||||
|
@ -184,6 +188,27 @@ CKEDITOR_CONFIGS = {
|
|||
},
|
||||
}
|
||||
|
||||
# Webpack loader
|
||||
VUE_FRONTEND_DIR = os.path.join(BASE_DIR, 'vue_frontend')
|
||||
|
||||
WEBPACK_LOADER = {
|
||||
'DEFAULT': {
|
||||
'CACHE': False,
|
||||
'BUNDLE_DIR_NAME': 'vue/', # must end with slash
|
||||
'STATS_FILE': os.path.join(VUE_FRONTEND_DIR, 'webpack-stats.json'),
|
||||
'POLL_INTERVAL': 0.1,
|
||||
'TIMEOUT': None,
|
||||
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Dajngo REST Framework
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
'PAGE_SIZE': 100
|
||||
}
|
||||
|
||||
# Comments
|
||||
|
||||
|
@ -270,6 +295,9 @@ LOGGING = {
|
|||
},
|
||||
}
|
||||
|
||||
# Permissions for uploads
|
||||
FILE_UPLOAD_PERMISSIONS = 0o0644
|
||||
|
||||
# MaM specific
|
||||
|
||||
SEMINAR_RESENI_DIR = os.path.join('reseni')
|
||||
|
|
38
mamweb/static/css/mamweb-dev.css
Normal file
38
mamweb/static/css/mamweb-dev.css
Normal file
|
@ -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;
|
||||
}
|
|
@ -759,17 +759,26 @@ div.odpocet {
|
|||
|
||||
/*stránka organizátorů*/
|
||||
|
||||
div.seznam_orgu {
|
||||
div.seznam_orgu, div.rozcestnik_temat {
|
||||
text-align: center;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
div.org_pole, div.rocnik_pole {
|
||||
div.org_pole, div.rocnik_pole, div.tema_pole {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
min-width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.tema_pole {
|
||||
display: inline-block;
|
||||
width: 40%;
|
||||
min-width: 350px;
|
||||
padding-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.cislo_pole {
|
||||
display: inline-block;
|
||||
width: 15%;
|
||||
|
@ -812,6 +821,11 @@ div.org_email {
|
|||
height: 205px;
|
||||
}
|
||||
|
||||
#tema-rozcestnik.flip-card {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* This container is needed to position the front and back side */
|
||||
.flip-card-inner {
|
||||
position: relative;
|
||||
|
@ -835,7 +849,8 @@ div.org_email {
|
|||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
div.flip-card-foto img {
|
||||
|
||||
div.flip-card-foto, div.flip-card-foto img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
<title>{% block title %}{% block nadpis1a %}{% endblock %} – Korespondenční seminář M&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">
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
1
seminar/.~lock.profile_vysledkovka.txt#
Normal file
1
seminar/.~lock.profile_vysledkovka.txt#
Normal file
|
@ -0,0 +1 @@
|
|||
,anet,erebus,25.03.2020 22:21,file:///home/anet/.config/libreoffice/4;
|
|
@ -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)
|
||||
|
|
|
@ -277,3 +277,8 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
|
|||
|
||||
)
|
||||
|
||||
class NahrajObrazekKTreeNoduForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Obrazek
|
||||
fields = ('na_web',)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -744,11 +743,20 @@ class Problem(SeminarModelBase,PolymorphicModel):
|
|||
return '<Není zadaný>'
|
||||
|
||||
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é.
|
||||
return False
|
||||
# FIXME: Tohle je blbost
|
||||
return (self.cislo_zadani and self.cislo_zadani.verejne())
|
||||
# aktuálně podle stavu problému
|
||||
# FIXME pro některé problémy možná chceme override
|
||||
# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
|
||||
# Je to tak správně?
|
||||
stav_verejny = False
|
||||
if self.stav == 'zadany' or self.stav == 'vyreseny':
|
||||
stav_verejny = True
|
||||
return stav_verejny
|
||||
|
||||
#cislo_verejne = False
|
||||
#if (self.cislo_zadani and self.cislo_zadani.verejne()):
|
||||
# cislo_verejne = True
|
||||
|
||||
#return (stav_verejny and cislo_verejne)
|
||||
verejne.boolean = True
|
||||
|
||||
def verejne_url(self):
|
||||
|
@ -844,9 +852,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:
|
||||
|
@ -1367,6 +1373,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):
|
||||
|
|
7
seminar/permissions.py
Normal file
7
seminar/permissions.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from rest_framework.permissions import BasePermission
|
||||
|
||||
class AllowWrite(BasePermission):
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return request.user.has_perm('auth.org')
|
||||
|
BIN
seminar/static/images/tema-bez-obrazku.png
Normal file
BIN
seminar/static/images/tema-bez-obrazku.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 300 KiB |
18
seminar/static/seminar/treenode_editor.js
Normal file
18
seminar/static/seminar/treenode_editor.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
function showSelectedItemForm(sel,id){
|
||||
var option;
|
||||
var name;
|
||||
var div;
|
||||
Array.from(sel.options).forEach(function(option){
|
||||
console.log(option);
|
||||
name = 'pridat-'+option.value+'-'+id;
|
||||
div = document.getElementById(name);
|
||||
console.log(div);
|
||||
div.style.display = 'none';
|
||||
});
|
||||
name = sel.options[sel.selectedIndex].value;
|
||||
name = 'pridat-'+name+'-'+id;
|
||||
div = document.getElementById(name);
|
||||
console.log(div);
|
||||
div.style.display = 'block';
|
||||
|
||||
}
|
97
seminar/templates/seminar/archiv/cislo-normal.html
Normal file
97
seminar/templates/seminar/archiv/cislo-normal.html
Normal file
|
@ -0,0 +1,97 @@
|
|||
{% extends "seminar/archiv/base_cisla.html" %}
|
||||
|
||||
{# {% block content %}
|
||||
<div>
|
||||
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
Číslo {{ cislo }}
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
|
||||
{% if cislo.pdf %}
|
||||
<p><a href='{{ cislo.pdf.url }}'>Číslo v pdf</a>
|
||||
{% endif %}
|
||||
<p><a href='{{ cislo.rocnik.verejne_url }}'>Ročník {{ cislo.rocnik }}</a>
|
||||
|
||||
{% if v_cisle_zadane %}
|
||||
<h2>Zadané problémy</h2>
|
||||
<ul>
|
||||
{% for p in v_cisle_zadane %}
|
||||
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}>
|
||||
{% if user.is_staff or cislo.verejne %}
|
||||
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if resene_problemy %}
|
||||
<h2>Řešené problémy</h2>
|
||||
<ul>
|
||||
{% for p in resene_problemy %}
|
||||
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}>
|
||||
{% if user.is_staff or cislo.verejne %}
|
||||
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
<div class="mam-org-only">
|
||||
<h2> Orgovské odkazy </h2>
|
||||
<ul>
|
||||
<li><a href="obalky.pdf">Obálky (PDF)</a></li>
|
||||
<li><a href="tituly.tex">Tituly (TeX)</a></li>
|
||||
<li><a href="vysledkovka.tex">Výsledkovka (TeX)</a></li>
|
||||
<li><a href="obalkovani">Obálkování</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if cislo.verejna_vysledkovka %}
|
||||
<h2>Výsledkovka</h2>
|
||||
{% else %}
|
||||
{% if user.is_staff %}
|
||||
<div class='mam-org-only'>
|
||||
<h2>Výsledkovka (neveřejná)</h2>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if cislo.verejna_vysledkovka or user.is_staff %}
|
||||
<table class='vysledkovka'>
|
||||
<tr class='border-b'>
|
||||
<th class='border-r'>#
|
||||
<th class='border-r'>Jméno #}
|
||||
{# problémy by měly být veřejné, když je veřejná výsledkovka #}
|
||||
{# {% for p in problemy %}
|
||||
<th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a>
|
||||
{% endfor %}
|
||||
<th class='border-r'>Za číslo</sup>
|
||||
<th class='border-r'>Za ročník
|
||||
<th class='border-r'>Odjakživa
|
||||
{% for rv in radky_vysledkovky %}
|
||||
<tr>
|
||||
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
|
||||
<th class='border-r'>
|
||||
{% if rv.resitel.titul != "" %}
|
||||
{{ rv.resitel.titul }}<sup>MM</sup>
|
||||
{% endif %}
|
||||
{{ rv.resitel.osoba.plne_jmeno }}
|
||||
{% for b in rv.hlavni_problemy_body %}
|
||||
<td class='border-r'>{{ b }}
|
||||
{% endfor %}
|
||||
<td class='border-r'>{{ rv.body_cislo }}
|
||||
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
|
||||
<td class='border-r'>{{ rv.body_celkem_odjakziva }}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if not cislo.verejna_vysledkovka and user.is_staff %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock content %} #}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
|
@ -48,6 +49,14 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script id="vuedata" type="application/json">{"treenode":{{cislo.cislonode.id}}}</script>
|
||||
<div id="app">
|
||||
<app></app>
|
||||
</div>
|
||||
{% render_bundle 'chunk-vendors' %}
|
||||
{% render_bundle 'vue_app_01' %}
|
||||
|
||||
|
||||
{% if cislo.verejna_vysledkovka %}
|
||||
<h2>Výsledkovka</h2>
|
||||
|
||||
|
|
19
seminar/templates/seminar/archiv/problem_tema.html
Normal file
19
seminar/templates/seminar/archiv/problem_tema.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "seminar/archiv/problem.html" %}
|
||||
|
||||
{% block problem %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
{{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }}
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
|
||||
<h2>Zadání</h2>
|
||||
{{ problem.text_zadani |safe }}
|
||||
{% if problem.text_reseni %}
|
||||
<h2>Řešení</h2>
|
||||
{{ problem.text_reseni |safe }}
|
||||
{% endif %}
|
||||
|
||||
{# TODO vysledkovka tematu #}
|
||||
|
||||
{% endblock %}
|
23
seminar/templates/seminar/archiv/problem_uloha.html
Normal file
23
seminar/templates/seminar/archiv/problem_uloha.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{% extends "seminar/archiv/problem.html" %}
|
||||
|
||||
{% block problem %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
{{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }} {{ problem.body_v_zavorce }}
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
{% if problem.cislo_zadani %}
|
||||
<p>Zadáno v čísle <a href='{{ problem.cislo_zadani.verejne_url }}'>{{ problem.cislo_zadani.kod }}</a>.
|
||||
{% endif %}
|
||||
{% if problem.cislo_reseni %}
|
||||
<p>Řešeno v čísle <a href='{{ problem.cislo_reseni.verejne_url }}'>{{ problem.cislo_reseni.kod }}</a>.
|
||||
{% endif %}
|
||||
|
||||
<h2>Zadání</h2>
|
||||
{{ problem.text_zadani |safe }}
|
||||
{% if problem.text_reseni %}
|
||||
<h2>Řešení</h2>
|
||||
{{ problem.text_reseni |safe }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
23
seminar/templates/seminar/orphanage.html
Normal file
23
seminar/templates/seminar/orphanage.html
Normal file
|
@ -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 %}
|
|
@ -1,14 +1,54 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
Aktuální témata
|
||||
{% endblock %}{% endblock %}
|
||||
</h2>
|
||||
|
||||
<p>Témata jsou texty nejen z oblasti matematiky, fyziky a informatiky, které popisují nějaký
|
||||
problém a jsou doprovázeny návodnými úlohami. Vaším úkolem je zamyslet se nad daným
|
||||
problémem a sepsat vaše úvahy ve formě krátkého textu.</p>
|
||||
|
||||
<p><a href="/co-je-MaM/jak-resit/">Jak řešit téma?</a></p>
|
||||
|
||||
|
||||
<div class="rozcestnik_temat">
|
||||
|
||||
{% for tematko in tematka %}
|
||||
<h1>{{tematko.nazev}}</h1>
|
||||
<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>
|
||||
|
||||
{# karta témátka - zepředu ilustrační, zezadu abstrakt #}
|
||||
<div class="tema_pole">
|
||||
|
||||
<h3>
|
||||
<a href='{{ rocnik.verejne_url }}'>Téma {{ tematko.nazev }}</a>
|
||||
</h3>
|
||||
|
||||
<div class="flip-card" id="tema-rozcestnik">
|
||||
|
||||
<div class="flip-card-inner">
|
||||
<div class="flip-card-front">
|
||||
<div class="flip-card-foto">
|
||||
{% if tematko.obrazek %}
|
||||
<img src="{{ tematko.obrazek.url }}" alt="{{ tematko.nazev }}">
|
||||
{% else %} {# pokud témátko nemá fotku, zobrazuje se defaultní obrázek #}
|
||||
{% load static %} <img src="{% static 'images/tema-bez-obrazku.png' %}" alt="{{ tematko.nazev }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flip-card-back">
|
||||
<p>{{ tematko.abstrakt }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{# konec karty témátka #}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -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
Normal file
27
seminar/templates/seminar/treenode_add_stub.html
Normal file
|
@ -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
Normal file
18
seminar/templates/seminar/treenode_name.html
Normal file
|
@ -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 %}
|
|
@ -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}}
|
||||
{% 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%}
|
||||
</div>
|
||||
{%endif%}
|
||||
<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 %}
|
||||
{% 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>
|
||||
{% 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
Normal file
7
seminar/templates/seminar/vuetest.html
Normal file
|
@ -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' %}
|
|
@ -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 isOtisteneReseniNode(value):
|
||||
# return isinstance(value, m.OtisteneReseniNode)
|
||||
#def podvesitelneNody(value):
|
||||
# if isText()
|
||||
|
||||
@register.filter
|
||||
def deletable(value):
|
||||
if isTemaVCisle(value):
|
||||
return True
|
||||
if isOrgText(value):
|
||||
return True
|
||||
if isReseni(value):
|
||||
return True
|
||||
if isUlohaZadani(value):
|
||||
return True
|
||||
if isUlohaVzorak(value):
|
||||
return True
|
||||
if isCast(value):
|
||||
return True
|
||||
if isText(value):
|
||||
return True
|
||||
return False
|
||||
|
||||
@register.filter
|
||||
def editableSiblings(value):
|
||||
if isCast(value.node):
|
||||
return True
|
||||
if isText(value.node):
|
||||
return True
|
||||
if isReseni(value.node) and value.tema_in_path:
|
||||
return True
|
||||
if isUlohaZadani(value.node) and value.tema_in_path:
|
||||
return True
|
||||
if isUlohaVzorak(value.node) and value.tema_in_path:
|
||||
return True
|
||||
return False
|
||||
|
||||
@register.filter
|
||||
def editableChildren(value):
|
||||
if isRocnik(value.node):
|
||||
return False
|
||||
if isCislo(value.node):
|
||||
return False
|
||||
if isText(value.node):
|
||||
return False
|
||||
return True
|
||||
|
||||
@register.filter
|
||||
def textOnlySubtree(value):
|
||||
text_only = True
|
||||
if isText(value.node):
|
||||
return True
|
||||
if not isCast(value.node):
|
||||
return False
|
||||
for ch in value.children:
|
||||
if not textOnlySubtree(ch):
|
||||
return False
|
||||
return True
|
||||
|
||||
def canPodvesit(obj,new_parent):
|
||||
if isCast(new_parent.node):
|
||||
# print("Lze",obj,new_parent)
|
||||
return True
|
||||
if textOnlySubtree(obj):
|
||||
# print("Lze",obj,new_parent)
|
||||
return True
|
||||
return False
|
||||
|
||||
@register.filter
|
||||
def canPodvesitZa(value):
|
||||
if not value.index or value.index+1 >= len(value.parent.children):
|
||||
return False
|
||||
new_parent = value.parent.children[value.index+1]
|
||||
return canPodvesit(value,new_parent)
|
||||
|
||||
@register.filter
|
||||
def canPodvesitPred(value):
|
||||
if not value.index or value.index <= 0:
|
||||
return False
|
||||
new_parent = value.parent.children[value.index-1]
|
||||
return canPodvesit(value,new_parent)
|
||||
|
||||
|
||||
class NodeTypes(Enum):
|
||||
ROCNIK = ('rocnikNode','Ročník')
|
||||
CISLO = ('cisloNode', 'Číslo')
|
||||
MEZICISLO = ('mezicisloNode', 'Mezičíslo')
|
||||
CAST = ('castNode', 'Část')
|
||||
TEXT = ('textNode', 'Text')
|
||||
TEMAVCISLE = ('temaVCisleNode', 'Téma v čísle')
|
||||
RESENI = ('reseniNode','Řešení')
|
||||
ULOHAZADANI = ('ulohaZadaniNode','Zadání')
|
||||
ULOHAVZORAK = ('ulohaVzorakNode','Vzorák')
|
||||
POHADKA = ('pohadkaNode','Pohádka')
|
||||
ORGTEXT = ('orgText','Orgtext')
|
||||
|
||||
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def appendableChildren(value):
|
||||
print(value)
|
||||
print(value.node)
|
||||
print(isUlohaZadani(value.node))
|
||||
if isTemaVCisle(value.node):
|
||||
return (NodeTypes.RESENI.value[0],
|
||||
NodeTypes.ULOHAZADANI.value[0],
|
||||
NodeTypes.ULOHAVZORAK.value[0],
|
||||
NodeTypes.CAST.value[0],
|
||||
NodeTypes.TEXT.value[0],
|
||||
)
|
||||
if isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node):
|
||||
print("Text/Cast")
|
||||
return (NodeTypes.CAST.value[0],
|
||||
NodeTypes.TEXT.value[0],
|
||||
)
|
||||
if isCast(value.node):
|
||||
return appendableChildren(value.parent)
|
||||
return []
|
||||
|
||||
@register.simple_tag
|
||||
def canAppendReseni(value):
|
||||
if isTemaVCisle(value.node):
|
||||
return True
|
||||
if isCast(value.node):
|
||||
return canAppendReseni(value.parent)
|
||||
return False
|
||||
|
||||
@register.simple_tag
|
||||
def canAppendUlohaZadani(value):
|
||||
if isTemaVCisle(value.node):
|
||||
return True
|
||||
if isCast(value.node):
|
||||
return canAppendUlohaZadani(value.parent)
|
||||
return False
|
||||
|
||||
@register.simple_tag
|
||||
def canAppendUlohaVzorak(value):
|
||||
if isTemaVCisle(value.node):
|
||||
return True
|
||||
if isCast(value.node):
|
||||
return canAppendUlohaVzorak(value.parent)
|
||||
return False
|
||||
|
||||
@register.simple_tag
|
||||
def canAppendCast(value):
|
||||
if isTemaVCisle(value.node) or isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node):
|
||||
return True
|
||||
if isCast(value.node):
|
||||
return canAppendCast(value.parent)
|
||||
return False
|
||||
|
||||
@register.simple_tag
|
||||
def canAppendText(value):
|
||||
if isTemaVCisle(value.node) or isOrgText(value.node) or isReseni(value.node) or isUlohaZadani(value.node) or isUlohaVzorak(value.node):
|
||||
return True
|
||||
if isCast(value.node):
|
||||
return canAppendText(value.parent)
|
||||
return False
|
||||
|
||||
#@register.filter
|
||||
#def is(value):
|
||||
# return
|
||||
#
|
||||
# NodeTypes..value,
|
||||
#@register.filter
|
||||
#def is(value):
|
||||
# return
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ def gen_resitele(rnd, osoby, skoly):
|
|||
x += 1
|
||||
os.user = user
|
||||
os.save()
|
||||
os.user.user_permissions.add(resitel_perm)
|
||||
os.user.user_permissions.add(resitel_perm)
|
||||
resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly),
|
||||
rok_maturity=rnd.randint(2019, 2029),
|
||||
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]))
|
||||
|
@ -199,7 +199,7 @@ def gen_organizatori(rnd, osoby, last_rocnik):
|
|||
x += 1
|
||||
os.user = user
|
||||
os.save()
|
||||
os.user.user_permissions.add(org_perm)
|
||||
os.user.user_permissions.add(org_perm)
|
||||
organizatori.append(Organizator.objects.create(osoba=os,
|
||||
organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga))
|
||||
return organizatori
|
||||
|
@ -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
|
||||
|
|
|
@ -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,20 +14,31 @@ 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
|
||||
|
||||
# FIXME: Proč?????
|
||||
def safe_succ(node):
|
||||
try:
|
||||
return node.succ
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
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
|
||||
|
@ -34,15 +47,8 @@ def first_brother(node):
|
|||
brother = safe_pred(brother)
|
||||
return brother
|
||||
|
||||
# A to samé pro .father_of_first
|
||||
def safe_father_of_first(node):
|
||||
first = first_brother(node)
|
||||
try:
|
||||
return first.father_of_first
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
## 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)
|
||||
|
|
|
@ -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
|
||||
|
@ -20,6 +20,14 @@ urlpatterns = [
|
|||
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,6 +157,9 @@ 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'),
|
||||
|
||||
|
|
|
@ -148,16 +148,12 @@ def resi_v_rocniku(rocnik, cislo=None):
|
|||
|
||||
if cislo is None:
|
||||
# filtrujeme pouze podle ročníku
|
||||
letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik)
|
||||
return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
|
||||
reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct()
|
||||
else: # filtrujeme podle ročníku i čísla
|
||||
letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik,
|
||||
hodnoceni__cislo_body__poradi__lte=cislo.poradi)
|
||||
|
||||
# vygenerujeme queryset řešitelů, co letos něco poslali
|
||||
letosni_resitele = m.Resitel.objects.none()
|
||||
for reseni in letosni_reseni:
|
||||
letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok())
|
||||
return letosni_resitele.distinct()
|
||||
return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
|
||||
reseni__hodnoceni__cislo_body__rocnik=rocnik,
|
||||
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct()
|
||||
|
||||
|
||||
def aktivniResitele(cislo, pouze_letosni=False):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from .views_all import *
|
||||
from .autocomplete import *
|
||||
from .views_rest import *
|
||||
from .odevzdavatko import *
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# coding:utf-8
|
||||
|
||||
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse
|
||||
|
@ -16,13 +16,19 @@ from django.contrib.auth import views as auth_views
|
|||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import transaction
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
import seminar.models as s
|
||||
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, MAXYEAR
|
||||
from django.utils import timezone
|
||||
|
@ -89,17 +95,142 @@ 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 = []
|
||||
@classmethod
|
||||
def public_above(cls, anode):
|
||||
""" Returns output of verejne for closest Rocnik, Cislo or Problem above.
|
||||
(All of them have method verejne.)"""
|
||||
parent = anode # chceme začít už od konkrétního node včetně
|
||||
while True:
|
||||
rocnik = isinstance(parent, s.RocnikNode)
|
||||
cislo = isinstance(parent, s.CisloNode)
|
||||
uloha = (isinstance(parent, s.UlohaVzorakNode) or
|
||||
isinstance(parent, s.UlohaZadaniNode))
|
||||
tema = isinstance(parent, s.TemaVCisleNode)
|
||||
|
||||
if (rocnik or cislo or uloha or tema) or parent==None:
|
||||
break
|
||||
else:
|
||||
parent = treelib.get_parent(parent)
|
||||
if rocnik:
|
||||
return parent.rocnik.verejne()
|
||||
elif cislo:
|
||||
return parent.cislo.verejne()
|
||||
elif uloha:
|
||||
return parent.uloha.verejne()
|
||||
elif tema:
|
||||
return parent.tema.verejne()
|
||||
elif None:
|
||||
print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou"
|
||||
"ani tématem. {}".format(anode))
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def all_public_children(cls, anode):
|
||||
for ch in treelib.all_children(anode):
|
||||
if TNLData.public_above(ch):
|
||||
yield ch
|
||||
else:
|
||||
continue
|
||||
|
||||
@classmethod
|
||||
def from_treenode(cls, anode, user, parent=None, index=None):
|
||||
if TNLData.public_above(anode) or user.has_perm('auth.org'):
|
||||
out = cls(anode,parent,index)
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
|
||||
if user.has_perm('auth.org'):
|
||||
enum_children = enumerate(treelib.all_children(anode))
|
||||
else:
|
||||
enum_children = enumerate(TNLData.all_public_children(anode))
|
||||
|
||||
for (idx,ch) in enum_children:
|
||||
outitem = cls.from_treenode(ch, user, out, idx)
|
||||
out.children.append(outitem)
|
||||
out.add_edit_options()
|
||||
return out
|
||||
|
||||
@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 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
|
||||
|
||||
def __repr__(self):
|
||||
return("TNL({})".format(self.node))
|
||||
|
||||
class TreeNodeView(generic.DetailView):
|
||||
model = s.TreeNode
|
||||
|
@ -107,22 +238,181 @@ 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,self.request.user)
|
||||
return context
|
||||
|
||||
# TODO Co chceme vlastně zobrazovat na této stránce? Zatím je zde aktuální číslo, ale může tu být cokoli jiného...
|
||||
class 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)
|
||||
class TreeNodeJSONView(generic.DetailView):
|
||||
model = s.TreeNode
|
||||
|
||||
def get(self,request,*args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
data = TNLData.from_treenode(self.object,self.request.user).to_json()
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
|
||||
class TreeNodePridatView(generic.View):
|
||||
type_from_str = {
|
||||
'rocnikNode': m.RocnikNode,
|
||||
'cisloNode': m.CisloNode,
|
||||
'castNode': m.CastNode,
|
||||
'textNode': m.TextNode,
|
||||
'temaVCisleNode': m.TemaVCisleNode,
|
||||
'reseniNode': m.ReseniNode,
|
||||
'ulohaZadaniNode': m.UlohaZadaniNode,
|
||||
'ulohaVzorakNode': m.UlohaVzorakNode,
|
||||
'pohadkaNode': m.PohadkaNode,
|
||||
'orgText': m.OrgTextNode,
|
||||
}
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ###########
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
kam = self.kwargs['kam']
|
||||
co = self.kwargs['co']
|
||||
typ = self.type_from_str[co]
|
||||
|
||||
raise NotImplementedError('Neni to dopsane, dopis to!')
|
||||
|
||||
if kam not in ('pred','syn','za'):
|
||||
raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna')
|
||||
|
||||
if co == m.TextNode:
|
||||
new_obj = m.Text()
|
||||
new_obj.save()
|
||||
elif co == m.CastNode:
|
||||
new_obj = m.CastNode()
|
||||
new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam))
|
||||
new_obj.save()
|
||||
elif co == m.ReseniNode:
|
||||
new_obj = m
|
||||
pass
|
||||
elif co == m.UlohaZadaniNode:
|
||||
pass
|
||||
elif co == m.UlohaReseniNode:
|
||||
pass
|
||||
else:
|
||||
new_obj = None
|
||||
|
||||
|
||||
if kam == 'pred':
|
||||
pass
|
||||
|
||||
|
||||
if kam == 'syn':
|
||||
if typ == m.TextNode:
|
||||
text_obj = m.Text()
|
||||
text_obj.save()
|
||||
node = treelib.create_child(node,typ,text=text_obj)
|
||||
else:
|
||||
node = treelib.create_child(node,typ)
|
||||
if kam == 'za':
|
||||
if typ == m.TextNode:
|
||||
text_obj = m.Text()
|
||||
text_obj.save()
|
||||
node = treelib.create_node_after(node,typ,text=text_obj)
|
||||
else:
|
||||
node = treelib.create_node_after(node,typ)
|
||||
|
||||
return redirect(node.get_admin_url())
|
||||
|
||||
|
||||
class TreeNodeSmazatView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
if node.first_child:
|
||||
raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!')
|
||||
treelib.disconnect_node(node)
|
||||
node.delete()
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
class TreeNodeOdvesitPrycView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
treelib.disconnect_node(node)
|
||||
node.root = None
|
||||
node.save()
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
|
||||
class TreeNodePodvesitView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
kam = self.kwargs['kam']
|
||||
if kam == 'pred':
|
||||
treelib.lower_node(node)
|
||||
elif kam == 'za':
|
||||
raise NotImplementedError('Podvěsit za není zatím podporováno')
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
class TreeNodeProhoditView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
treelib.swap_succ(node)
|
||||
return redirect(request.headers.get('referer'))
|
||||
#FIXME ve formulari predat puvodni url a vratit redirect na ni
|
||||
|
||||
class SirotcinecView(generic.ListView):
|
||||
model = s.TreeNode
|
||||
template_name = 'seminar/orphanage.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None)
|
||||
|
||||
# FIXME pouzit Django REST Framework
|
||||
class TextWebView(generic.DetailView):
|
||||
model = s.Text
|
||||
|
||||
def get(self,request,*args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return JsonResponse(model_to_dict(self.object,exclude='do_cisla'))
|
||||
|
||||
|
||||
class ProblemView(generic.DetailView):
|
||||
model = s.Problem
|
||||
# Zkopírujeme template_name od TreeNodeView, protože jsme prakticky jen trošku upravený TreeNodeView
|
||||
template_name = TreeNodeView.template_name
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
verejne = nastaveni.aktualni_cislo.verejne()
|
||||
context['verejne'] = verejne
|
||||
user = self.request.user
|
||||
# Teď potřebujeme doplnit tnldata do kontextu.
|
||||
# Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
|
||||
if False:
|
||||
# Hezčí formátování zbytku :-P
|
||||
pass
|
||||
elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera):
|
||||
# Tyhle Problémy mají ŘešeníNode
|
||||
context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
|
||||
elif isinstance(self.object, s.Uloha):
|
||||
# FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever
|
||||
tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
|
||||
tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
|
||||
context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
|
||||
elif isinstance(self.object, s.Tema):
|
||||
rocniknode = self.object.rocnik.rocniknode
|
||||
context['tnldata'] = TNLData.filter_treenode(rocniknode, lambda x: isinstance(x, s.TemaVCisleNode))
|
||||
else:
|
||||
raise ValueError("Obecný problém nejde zobrazit.")
|
||||
return context
|
||||
|
||||
|
||||
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()
|
||||
|
@ -138,7 +428,19 @@ class AktualniZadaniView(TreeNodeView):
|
|||
# },
|
||||
# )
|
||||
#
|
||||
#def ZadaniTemataView(request):
|
||||
def ZadaniTemataView(request):
|
||||
nastaveni = get_object_or_404(Nastaveni)
|
||||
verejne = nastaveni.aktualni_cislo.verejne()
|
||||
akt_rocnik = nastaveni.aktualni_cislo.rocnik
|
||||
temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
|
||||
return render(request, 'seminar/tematka/rozcestnik.html',
|
||||
{
|
||||
'tematka': temata,
|
||||
'verejne': verejne,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# nastaveni = get_object_or_404(Nastaveni)
|
||||
# temata = verejna_temata(nastaveni.aktualni_rocnik)
|
||||
# for t in temata:
|
||||
|
@ -1318,6 +1620,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):
|
||||
|
|
162
seminar/views/views_rest.py
Normal file
162
seminar/views/views_rest.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
from rest_framework import serializers
|
||||
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||
|
||||
import seminar.models as m
|
||||
from seminar import treelib
|
||||
|
||||
DEFAULT_NODE_DEPTH = 2
|
||||
|
||||
class TextSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.Text
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
|
||||
class UlohaVzorakNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.UlohaVzorakNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class UlohaZadaniNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.UlohaZadaniNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class RocnikNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.RocnikNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class CisloNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.CisloNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class MezicisloNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.MezicisloNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class TemaVCisleNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.TemaVCisleNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class OrgTextNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.OrgTextNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class PohadkaNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.PohadkaNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class TextNodeSerializer(serializers.ModelSerializer):
|
||||
text = TextSerializer()
|
||||
|
||||
class Meta:
|
||||
model = m.TextNode
|
||||
fields = ('id','text','polymorphic_ctype')
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class TextNodeWriteSerializer(serializers.ModelSerializer):
|
||||
text = TextSerializer()
|
||||
|
||||
def update(self,node,validated_data):
|
||||
node.text.na_web = validated_data.get('text').get('na_web')
|
||||
node.text.save()
|
||||
return node
|
||||
|
||||
class Meta:
|
||||
model = m.TextNode
|
||||
fields = ('id','text')
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class TextNodeCreateSerializer(serializers.ModelSerializer):
|
||||
text = TextSerializer()
|
||||
refnode = serializers.CharField()
|
||||
where = serializers.CharField()
|
||||
|
||||
def create(self,validated_data):
|
||||
temp_text = validated_data.pop('text')
|
||||
where = validated_data.pop('where')
|
||||
refnode_id = validated_data.pop('refnode')
|
||||
refnode = m.TreeNode.objects.get(pk=refnode_id)
|
||||
text = m.Text.objects.create(**temp_text)
|
||||
if where == 'syn':
|
||||
node = treelib.create_child(refnode,m.TextNode,text=text)
|
||||
elif where == 'za':
|
||||
node = treelib.create_node_after(refnode,m.TextNode,text=text)
|
||||
elif where == 'pred':
|
||||
node = treelib.create_node_before(refnode,m.TextNode,text=text)
|
||||
node.where = None
|
||||
node.refnode = None
|
||||
return node
|
||||
|
||||
class Meta:
|
||||
model = m.TextNode
|
||||
fields = ('text','where','refnode')
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class CastNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.CastNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class CastNodeCreateSerializer(serializers.ModelSerializer):
|
||||
refnode = serializers.CharField()
|
||||
where = serializers.CharField()
|
||||
|
||||
def create(self,validated_data):
|
||||
temp_nadpis = validated_data.pop('nadpis')
|
||||
where = validated_data.pop('where')
|
||||
refnode_id = validated_data.pop('refnode')
|
||||
refnode = m.TreeNode.objects.get(pk=refnode_id)
|
||||
if where == 'syn':
|
||||
node = treelib.create_child(refnode,m.CastNode,nadpis=temp_nadpis)
|
||||
elif where == 'za':
|
||||
node = treelib.create_node_after(refnode,m.CastNode,nadpis=temp_nadpis)
|
||||
elif where == 'pred':
|
||||
node = treelib.create_node_before(refnode,m.CastNode,nadpis=temp_nadpis)
|
||||
node.where = None
|
||||
node.refnode = None
|
||||
return node
|
||||
|
||||
class Meta:
|
||||
model = m.CastNode
|
||||
fields = ('nadpis','where','refnode')
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
class ReseniNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = m.ReseniNode
|
||||
fields = '__all__'
|
||||
depth = DEFAULT_NODE_DEPTH
|
||||
|
||||
|
||||
class TreeNodeSerializer(PolymorphicSerializer):
|
||||
model_serializer_mapping = {
|
||||
m.RocnikNode: RocnikNodeSerializer,
|
||||
m.CisloNode: CisloNodeSerializer,
|
||||
m.MezicisloNode: MezicisloNodeSerializer,
|
||||
m.TemaVCisleNode: TemaVCisleNodeSerializer,
|
||||
m.OrgTextNode: OrgTextNodeSerializer,
|
||||
m.UlohaZadaniNode: UlohaZadaniNodeSerializer,
|
||||
m.UlohaVzorakNode: UlohaVzorakNodeSerializer,
|
||||
m.PohadkaNode: PohadkaNodeSerializer,
|
||||
m.TextNode: TextNodeSerializer,
|
||||
m.CastNode: CastNodeSerializer,
|
||||
m.ReseniNode: ReseniNodeSerializer,
|
||||
}
|
||||
|
96
seminar/viewsets.py
Normal file
96
seminar/viewsets.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
from rest_framework import viewsets,filters
|
||||
from rest_framework.permissions import BasePermission, AllowAny
|
||||
from . import models as m
|
||||
from . import views
|
||||
|
||||
from seminar.permissions import AllowWrite
|
||||
|
||||
class PermissionMixin(object):
|
||||
""" Redefines get_permissions so that only organizers can make changes. """
|
||||
|
||||
def get_permissions(self):
|
||||
permission_classes = []
|
||||
print("get_permissions have been called.")
|
||||
if self.action in ["create", "update", "partial_update", "destroy"]:
|
||||
permission_classes = [AllowWrite] # speciální permission na zápis - orgové
|
||||
else:
|
||||
permission_classes = [AllowAny]
|
||||
# návštěvník nemusí být zalogován, aby si prohlížel obsah
|
||||
return [permission() for permission in permission_classes]
|
||||
|
||||
class ReadWriteSerializerMixin(object):
|
||||
"""
|
||||
Overrides get_serializer_class to choose the read serializer
|
||||
for GET requests and the write serializer for POST requests.
|
||||
|
||||
Set read_serializer_class and write_serializer_class attributes on a
|
||||
viewset.
|
||||
"""
|
||||
|
||||
read_serializer_class = None
|
||||
create_serializer_class = None
|
||||
write_serializer_class = None
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "create":
|
||||
return self.get_create_serializer_class()
|
||||
if self.action in ["update", "partial_update", "destroy"]:
|
||||
return self.get_write_serializer_class()
|
||||
return self.get_read_serializer_class()
|
||||
|
||||
def get_read_serializer_class(self):
|
||||
assert self.read_serializer_class is not None, (
|
||||
"'%s' should either include a `read_serializer_class` attribute,"
|
||||
"or override the `get_read_serializer_class()` method."
|
||||
% self.__class__.__name__
|
||||
)
|
||||
return self.read_serializer_class
|
||||
|
||||
def get_write_serializer_class(self):
|
||||
assert self.write_serializer_class is not None, (
|
||||
"'%s' should either include a `write_serializer_class` attribute,"
|
||||
"or override the `get_write_serializer_class()` method."
|
||||
% self.__class__.__name__
|
||||
)
|
||||
return self.write_serializer_class
|
||||
|
||||
def get_create_serializer_class(self):
|
||||
assert self.create_serializer_class is not None, (
|
||||
"'%s' should either include a `create_serializer_class` attribute,"
|
||||
"or override the `get_create_serializer_class()` method."
|
||||
% self.__class__.__name__
|
||||
)
|
||||
return self.create_serializer_class
|
||||
|
||||
class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
|
||||
queryset = m.UlohaVzorakNode.objects.all()
|
||||
serializer_class = views.UlohaVzorakNodeSerializer
|
||||
|
||||
class TextViewSet(PermissionMixin, viewsets.ModelViewSet):
|
||||
queryset = m.Text.objects.all()
|
||||
serializer_class = views.TextSerializer
|
||||
|
||||
class TextNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
|
||||
queryset = m.TextNode.objects.all()
|
||||
read_serializer_class = views.TextNodeSerializer
|
||||
write_serializer_class = views.TextNodeWriteSerializer
|
||||
create_serializer_class = views.TextNodeCreateSerializer
|
||||
|
||||
class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
|
||||
queryset = m.CastNode.objects.all()
|
||||
read_serializer_class = views.CastNodeSerializer
|
||||
write_serializer_class = views.CastNodeSerializer
|
||||
create_serializer_class = views.CastNodeCreateSerializer
|
||||
|
||||
class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
|
||||
serializer_class = views.UlohaVzorakNodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = m.UlohaVzorakNode.objects.all()
|
||||
nazev = self.request.query_params.get('nazev',None)
|
||||
if nazev is not None:
|
||||
queryset = queryset.filter(nazev__contains=nazev)
|
||||
if self.request.user.has_perm('auth.org'):
|
||||
return queryset
|
||||
else: # pro neorgy jen zveřejněné vzoráky
|
||||
return queryset.filter(uloha__cislo_reseni__verejne_db=True)
|
22
vue_frontend/.gitignore
vendored
Normal file
22
vue_frontend/.gitignore
vendored
Normal file
|
@ -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
Normal file
5
vue_frontend/babel.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
48
vue_frontend/package.json
Normal file
48
vue_frontend/package.json
Normal file
|
@ -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
Normal file
7
vue_frontend/src/App.vue
Normal file
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<router-view id="app"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default { name: 'app' }
|
||||
</script>
|
50
vue_frontend/src/components/AddNewNode.vue
Normal file
50
vue_frontend/src/components/AddNewNode.vue
Normal file
|
@ -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
Normal file
86
vue_frontend/src/components/CastNode.vue
Normal file
|
@ -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
Normal file
19
vue_frontend/src/components/CisloNode.vue
Normal file
|
@ -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
Normal file
21
vue_frontend/src/components/RocnikNode.vue
Normal file
|
@ -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
Normal file
19
vue_frontend/src/components/TemaVCisleNode.vue
Normal file
|
@ -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
Normal file
174
vue_frontend/src/components/TextNode.vue
Normal file
|
@ -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
Normal file
161
vue_frontend/src/components/TreeNode.vue
Normal file
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
|
||||
<div :class="editorMode ? 'treenode-org' : 'treenode'">
|
||||
<!--b v-if="v_tematu">v tematu</b>
|
||||
<b v-if="visible">visible</b>
|
||||
Force visible: {{String(force_visible)}}-->
|
||||
<component :is='item.node.polymorphic_ctype.model' :item='item' :key='item.node.id'
|
||||
:editorMode="editorMode"
|
||||
:debugMode="debugMode"></component>
|
||||
|
||||
|
||||
<button v-if="debugMode" v-on:click="debugShow = !debugShow" class="nodebug">Ladící data</button> <!-- bude tu nějaký if na class="nodebug", v debug módu bude tlačítko vidět, jinak ne -->
|
||||
<div v-if="debugShow">
|
||||
<pre>{{ item.node.polymorphic_ctype.model }}</pre>
|
||||
<pre>{{ item }}</pre>
|
||||
</div>
|
||||
|
||||
<div v-if="item.children.length === 0">
|
||||
<div v-if="item.appendable_children.length > 0 && editorMode">
|
||||
<b>Vložit jako syna: </b>
|
||||
<addnewnode :types="item.appendable_children" :refnode="item.node" where="syn" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else :class="editorMode ? 'children-org' : 'children'"> <!-- bude tu nějaký if na class="children" -->
|
||||
<div v-if="item.children.length > 0 && item.children[0].appendable_siblings.length > 0 && editorMode">
|
||||
<b>Vložit před: </b>
|
||||
<addnewnode :types="item.children[0].appendable_siblings" :refnode="item.children[0].node" where="pred" />
|
||||
</div>
|
||||
<div v-if="item.node.polymorphic_ctype.model==='temavcislenode'">
|
||||
<!--Children: {{String(showChildren)}}-->
|
||||
<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" >
|
||||
<!--Hide: {{hideNode(chld)}}, v tematu: {{v_tematu}}, force_visible: {{force_visible}}-->
|
||||
<div v-if="!hideNode(chld)">
|
||||
<div v-if="chld.node.polymorphic_ctype.model==='ulohazadaninode'">
|
||||
<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button>
|
||||
<button v-else v-on:click="showChildren=!showChildren"> Rozbalit </button>
|
||||
<TreeNode :item="chld" :v_tematu="true"
|
||||
:force_visible="showChildren"
|
||||
:editorMode="editorMode"
|
||||
:debugMode="debugMode">
|
||||
</TreeNode>
|
||||
</div>
|
||||
<div v-else>
|
||||
<TreeNode :item="chld" :v_tematu="true"
|
||||
:force_visible="showChildren"
|
||||
:editorMode="editorMode"
|
||||
:debugMode="debugMode">
|
||||
</TreeNode>
|
||||
</div>
|
||||
<div v-if="chld.appendable_siblings.length > 0 && editorMode" >
|
||||
<b v-if="index < (item.children.length - 1)">Vložit mezi: </b>
|
||||
<b v-else>Vložit za: </b>
|
||||
<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" where="za" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="showChildren" v-on:click="showChildren=!showChildren"> Schovat </button>
|
||||
<button v-else v-on:click="showChildren=!showChildren"> Rozbalit </button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="(chld, index) in item.children" v-bind:key="chld.nazev" >
|
||||
<div v-if="v_tematu && chld.node.polymorphic_ctype.model==='ulohazadaninode'">
|
||||
<div> Tady možná něco je </div>
|
||||
<TreeNode :item="chld" :v_tematu="v_tematu"
|
||||
:force_visible="force_visible"
|
||||
:editorMode="editorMode"
|
||||
:debugMode="debugMode">
|
||||
</TreeNode>
|
||||
</div>
|
||||
<div v-else>
|
||||
<TreeNode :item="chld" :v_tematu="v_tematu"
|
||||
:force_visible="force_visible"
|
||||
:editorMode="editorMode"
|
||||
:debugMode="debugMode">
|
||||
</TreeNode>
|
||||
</div>
|
||||
<div v-if="chld.appendable_siblings.length > 0 && editorMode" >
|
||||
<b v-if="index < (item.children.length - 1)">Vložit mezi: </b>
|
||||
<b v-else>Vložit za: </b>
|
||||
<addnewnode :types="chld.appendable_siblings" :refnode="chld.node" where="za" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import rocniknode from './RocnikNode.vue'
|
||||
import cislonode from './CisloNode.vue'
|
||||
import temavcislenode from './TemaVCisleNode.vue'
|
||||
import castnode from './CastNode.vue'
|
||||
import textnode from './TextNode.vue'
|
||||
import ulohazadaninode from './UlohaZadaniNode.vue'
|
||||
import ulohavzoraknode from './UlohaVzorakNode.vue'
|
||||
import addnewnode from './AddNewNode.vue'
|
||||
|
||||
export default {
|
||||
name: 'TreeNode',
|
||||
components: {
|
||||
rocniknode,
|
||||
cislonode,
|
||||
temavcislenode,
|
||||
castnode,
|
||||
textnode,
|
||||
ulohazadaninode,
|
||||
ulohavzoraknode,
|
||||
addnewnode
|
||||
},
|
||||
data: () => ({
|
||||
debugShow: false,
|
||||
showChildren: false
|
||||
}),
|
||||
computed: {
|
||||
},
|
||||
props: {
|
||||
item: Object,
|
||||
force_visible: Boolean,
|
||||
v_tematu: Boolean,
|
||||
editorMode: Boolean,
|
||||
debugMode: Boolean,
|
||||
},
|
||||
methods: {
|
||||
hideNode: function(chld){
|
||||
if (this.showChildren || this.force_visible){
|
||||
return false;
|
||||
}
|
||||
if (chld.node.polymorphic_ctype.model === 'ulohazadaninode'){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
|
||||
|
||||
.treenode-org {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: #6a0043 2px solid;
|
||||
}
|
||||
|
||||
|
||||
.children-org {
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
border: #6a0043 2px dashed;
|
||||
}
|
||||
|
||||
.nodebug {
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
</style>
|
75
vue_frontend/src/components/TreeNodeRoot.vue
Normal file
75
vue_frontend/src/components/TreeNodeRoot.vue
Normal file
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<div id="loading" v-if="loading">
|
||||
Loading...
|
||||
</div>
|
||||
<!--pre>
|
||||
{{item}}
|
||||
</pre-->
|
||||
<button v-show="editorMode" v-on:click="editorMode = false">Vypnout editační mód</button>
|
||||
<button v-show="!editorMode" v-on:click="editorMode = true">Zapnout editační mód</button>
|
||||
<button v-show="debugMode" v-on:click="debugMode = false">Vypnout ladicí mód</button>
|
||||
<button v-show="!debugMode" v-on:click="debugMode = true">Zapnout ladicí mód</button>
|
||||
<TreeNode :item="item" :editorMode="editorMode" :debugMode="debugMode"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TreeNode from './TreeNode.vue'
|
||||
import axios from 'axios'
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
TreeNode,
|
||||
},
|
||||
data: () => ({
|
||||
loading: true,
|
||||
item: null,
|
||||
tnid: 1,
|
||||
editorMode: false,
|
||||
debugMode: false,
|
||||
}),
|
||||
props:{
|
||||
tnid: Number,
|
||||
tnsource: String,
|
||||
editorMode: Boolean,
|
||||
debugMode: Boolean,
|
||||
},
|
||||
mounted: function() {
|
||||
if (this.tnsource && this.tnsource=='inline'){
|
||||
let data = JSON.parse(document.getElementById('vuedata').textContent);
|
||||
this.tnid = data.treenode;
|
||||
}
|
||||
this.getArticles();
|
||||
this.$root.$on('updateData',(arg) => {
|
||||
console.log(arg);
|
||||
this.getArticles();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
getArticles: function() {
|
||||
this.loading = true;
|
||||
axios.get('/treenode/'+this.tnid+'/json/')
|
||||
.then((response) => {
|
||||
this.item = response.data;
|
||||
this.loading = false;
|
||||
console.log('Data updated');
|
||||
this.$root.$emit('dataUpdated',"dataUpdated");
|
||||
})
|
||||
.catch((err) => {
|
||||
this.loading = false;
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
},
|
||||
events: {
|
||||
updateData: function(){
|
||||
this.getArticles()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '../../../mamweb/static/css/mamweb.css';
|
||||
</style>
|
75
vue_frontend/src/components/UlohaVzorakNode.vue
Normal file
75
vue_frontend/src/components/UlohaVzorakNode.vue
Normal file
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div class="ulohavzoraknode">
|
||||
<!--pre>UlohaVzorakNode {{item}} {{typeof(item)}}</pre-->
|
||||
<h5>Řešení {{item.node.uloha.cislo_zadani.poradi}}.{{ item.node.uloha.kod }}: {{ item.node.uloha.nazev }}</h5>
|
||||
<button v-if="editorMode" v-on:click="showSelect=!showSelect" class="upravit">Upravit</button>
|
||||
<div v-if="showSelect">
|
||||
<form class="searchForm" v-on:submit.prevent="submitSearch">
|
||||
<input type="text" v-model="searchQuery" placeholder="Napište název" @keyup="submitSearch">
|
||||
</form>
|
||||
<div class="searchResult" v-show="isResult">
|
||||
<ul>
|
||||
<li v-for="res in searchResults" :key="res.id" v-on:click="setSelected(res)">{{res.nazev}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'UlohaVzorakNode',
|
||||
data: () => {
|
||||
return {
|
||||
isResult: false,
|
||||
searchQuery: '',
|
||||
searchResults: [],
|
||||
showSelect: false,
|
||||
selected: null,
|
||||
selected_id: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
item: Object,
|
||||
create: Boolean,
|
||||
showSelect: Boolean,
|
||||
editorMode: Boolean,
|
||||
},
|
||||
mounted: function(){
|
||||
if (this.item.node.uloha === null){
|
||||
console.log("Uloha je null!");
|
||||
console.log(this.item);
|
||||
}
|
||||
if (this.create){
|
||||
this.showSelect = true;
|
||||
console.log('Creating');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitSearch: function(){
|
||||
if (this.searchQuery.length < 3) { return;}
|
||||
var reqURL = "/api/ulohavzoraknode/?nazev="+this.searchQuery;
|
||||
axios.get(reqURL).then( (response) => {
|
||||
this.searchResults = response.data.results;
|
||||
this.isResult = true;
|
||||
console.log("Got:");
|
||||
console.log(this.searchResults);
|
||||
}).catch( (err) => { /* fail response msg */
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
setSelected: function(res){
|
||||
this.searchQuery = res.nazev
|
||||
this.selected_id = res.id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.upravit {
|
||||
margin-top:-40px;
|
||||
}
|
||||
</style>
|
23
vue_frontend/src/components/UlohaZadaniNode.vue
Normal file
23
vue_frontend/src/components/UlohaZadaniNode.vue
Normal file
|
@ -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
Normal file
12
vue_frontend/src/main.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(CKEditor);
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
33
vue_frontend/src/router/index.js
Normal file
33
vue_frontend/src/router/index.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import TreeNodeRoot from '../components/TreeNodeRoot.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
linkActiveClass: 'active',
|
||||
routes: [{
|
||||
path: '/temp/vue',
|
||||
name: 'treenodedefault',
|
||||
props: {'tnid': 23},
|
||||
component: TreeNodeRoot
|
||||
}, {
|
||||
path: '/temp/vue/:tnid',
|
||||
name: 'treenode',
|
||||
props: true,
|
||||
component: TreeNodeRoot
|
||||
}, {
|
||||
path: '/zadani/aktualni',
|
||||
name: 'treenode_zadani',
|
||||
props: {'tnid': 23},
|
||||
component: TreeNodeRoot
|
||||
}, {
|
||||
path: '/cislo/:cislo',
|
||||
name: 'treenode_cislo',
|
||||
props: {'tnsource':'inline'},
|
||||
component: TreeNodeRoot
|
||||
}
|
||||
]
|
||||
})
|
||||
|
61
vue_frontend/vue.config.js
Normal file
61
vue_frontend/vue.config.js
Normal file
|
@ -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
Normal file
8544
vue_frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue