Compare commits

..

1 commit

Author SHA1 Message Date
Pavel "LEdoian" Turinsky
bee0083dac wtf, je to podle dokumentace… a bugreportu 2024-06-03 02:52:48 +02:00
548 changed files with 20621 additions and 29933 deletions
.gitignore
_git_hooks
aesop
api
data
deploy_v2
docs
galerie
header_fotky
ilustrace_odmeny
korektury

12
.gitignore vendored
View file

@ -31,15 +31,9 @@ TODO
# reversion kvůli historii objektů v reversion # reversion kvůli historii objektů v reversion
**/reversion **/reversion
# dokumentace
docs/_build
docs/modules
# logy týracího skriptu (./checklinks.sh)
/wget.log.*
# pro lidi, co programují v nástrojích od JetBrains # pro lidi, co programují v nástrojích od JetBrains
.idea .idea
# Mac users # dokumentace
.DS_Store docs/_build
docs/modules

16
_git_hooks/README.md Normal file
View file

@ -0,0 +1,16 @@
git hooks
=========
Kontrola stylu pythoních zdrojáků pomocí flake8. Kontrolujeme jen změny,
abychom nenutili lidi dělat nesouvisející úpravy, které by rozbíjely historii
(git blame).
pre-commit
----------
* kontrola změn před commitnutím
* instalace: lokálně zkopírovat do .git/hooks (musí být spustitelný)
update
------
* kontrola změn přicházejících s pushem
* instalace: na atreyi zkopírovat do /akce/MaM/MaMweb/mamweb.git/hooks

30
_git_hooks/pre-commit Executable file
View file

@ -0,0 +1,30 @@
#!/bin/sh
#
# Git hook script to verify what is about to be committed.
# Checks that the changes don't introduce new flake8 errors.
TMPDIFF=`tempfile`
FLAKE8="`git rev-parse --show-toplevel`/bin/flake8"
status=0
# select only changed python files which are not migrations
changed=`git diff --cached --name-only | grep 'py$' | grep -v 'migrations/[0-9]'`
if [ -z $changed ] ; then
# Nothing to check. Note the exit is necessary -- we would not pass any
# paths to git diff below and it would output the diff unfiltered.
exit 0
fi
git diff --unified=1 --cached HEAD -- $changed > $TMPDIFF
# only do the check when there are some changes to be commited
# otherwise flake8 would hang waiting for input
if [ -s $TMPDIFF ] ; then
cat $TMPDIFF | $FLAKE8 --diff
status=$?
fi
rm -f $TMPDIFF
exit $status

61
_git_hooks/update Executable file
View file

@ -0,0 +1,61 @@
#!/bin/sh
# git update hook to check that pushed changes don't introduce new flake8
# errors
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
TMPDIR=`mktemp -d`
TMPDIFF=`tempfile`
[ $refname != "refs/heads/master" -a $refname != "refs/heads/stable" ] && exit 0
# select only changed python files which are not migrations
changed=`git diff --name-only $oldrev $newrev | grep 'py$' | grep -v 'migrations/[0-9]'`
if [ -z $changed ] ; then
# Nothing to check. Note the exit is necessary -- we would not pass any
# paths to git diff below and it would output the diff unfiltered.
exit 0
fi
git diff --unified=1 $oldrev $newrev -- $changed >${TMPDIFF}
# there is no working tree in bare git repository, so we recreate it for flake8
git archive $newrev | tar -x -C ${TMPDIR}
cd ${TMPDIR}
# report only errors on lines in diff
# (if threre was flake8 installed on atrey, we could just call flake8)
/akce/MaM/WWW/mamweb-test/bin/flake8 --diff <${TMPDIFF}
status=$?
if [ $status != 0 ] ; then
echo
echo -n "Změny, které se snažíte pushnout, obsahují kód v pythonu "
echo -n "nevyhovující flake8 (viz výše). Opravte je a zkuste to znovu. "
echo -n "Nezapomeňte, že můžete editovat historii (git commit --amend, "
echo -n "git rebase -i). Pokud byste chybu příště raději odhalili už při "
echo "commitu, zkopírujte si pre-commit hook z _git_hooks do .git/hooks."
echo
fi
rm -rf ${TMPDIR}
rm -f ${TMPDIFF}
exit $status

View file

@ -1,3 +1,6 @@
"""
Soubor sloužící k pojmenování a jiným nastavením djangovské aplikace.
"""
from django.apps import AppConfig from django.apps import AppConfig
class AesopConfig(AppConfig): class AesopConfig(AppConfig):

View file

@ -1,3 +1,10 @@
"""
Soubor sloužící jako router, tj. zde se definují url adresy a na co ukazují:
- ``aesop-export/mam-rocnik-<int:prvni_rok>.csv`` (seminar_export_rocnik) :class:`~aesop.views.ExportRocnikView`
- ``aesop-export/mam-sous-<str:datum_zacatku>.csv`` (seminar_export_sous) :class:`~aesop.views.ExportSousView`
- ``aesop-export/index.csv`` (seminar_export_index) :class:`~aesop.views.ExportIndexView`
"""
from django.urls import path from django.urls import path
from aesop import views from aesop import views
@ -5,16 +12,16 @@ urlpatterns = [
path( path(
'aesop-export/mam-rocnik-<int:prvni_rok>.csv', 'aesop-export/mam-rocnik-<int:prvni_rok>.csv',
views.ExportRocnikView.as_view(), views.ExportRocnikView.as_view(),
name='aesop_export_rocnik' name='seminar_export_rocnik'
), ),
path( path(
'aesop-export/mam-sous-<str:datum_zacatku>.csv', 'aesop-export/mam-sous-<str:datum_zacatku>.csv',
views.ExportSousView.as_view(), views.ExportSousView.as_view(),
name='aesop_export_sous' name='seminar_export_sous'
), ),
path( path(
'aesop-export/index.csv', 'aesop-export/index.csv',
views.ExportIndexView.as_view(), views.ExportIndexView.as_view(),
name='aesop_export_index' name='seminar_export_index'
), ),
] ]

View file

@ -1,3 +1,8 @@
"""
Soubor sloužící k deklaraci jednotlivých views (nejčastěji funkce beroucí request
a vracející :func:`django.shortcuts.render` respektive nějakou response, nebo
třídy většinou rozšiřující nějakou třídu z :mod:`django.views.generic`)
"""
import django import django
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.http import HttpResponse from django.http import HttpResponse
@ -6,19 +11,18 @@ from django.views import generic
from django.utils.encoding import force_str from django.utils.encoding import force_str
from .utils import default_ovvpfile from .utils import default_ovvpfile
from soustredeni.models import Soustredeni from seminar.models import Rocnik, Soustredeni
from tvorba.models import Rocnik
from vysledkovky import utils from vysledkovky import utils
from tvorba.utils import aktivniResitele from seminar.utils import aktivniResitele
class ExportIndexView(generic.View): class ExportIndexView(generic.View):
def get(self, request): def get(self, request):
ls = [] ls = []
for r in Rocnik.objects.filter(exportovat = True): for r in Rocnik.objects.filter(exportovat = True):
url = reverse('aesop_export_rocnik', kwargs={'prvni_rok': r.prvni_rok}) url = reverse('seminar_export_rocnik', kwargs={'prvni_rok': r.prvni_rok})
ls.append(url.split('/')[-1]) ls.append(url.split('/')[-1])
for s in Soustredeni.objects.filter(exportovat = True): for s in Soustredeni.objects.filter(exportovat = True):
url = reverse('aesop_export_sous', kwargs={'datum_zacatku': s.datum_zacatku.isoformat()}) url = reverse('seminar_export_sous', kwargs={'datum_zacatku': s.datum_zacatku.isoformat()})
ls.append(url.split('/')[-1]) ls.append(url.split('/')[-1])
return HttpResponse('\n'.join(ls) + '\n', content_type='text/plain; charset=utf-8') return HttpResponse('\n'.join(ls) + '\n', content_type='text/plain; charset=utf-8')

View file

@ -1,3 +1,6 @@
"""
Soubor sloužící k pojmenování a jiným nastavením djangovské aplikace.
"""
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -1,7 +1,8 @@
from django.test import TestCase, tag from django.test import TestCase, tag
from django.urls import reverse from django.urls import reverse
from personalni.models import Skola import seminar.models as m
from personalni.utils import sync_skoly import seminar.views as v
from seminar.utils import sync_skoly
@tag('stejny-model-na-produkci') @tag('stejny-model-na-produkci')
class OrgSkolyAutocompleteTestCase(TestCase): class OrgSkolyAutocompleteTestCase(TestCase):
@ -48,7 +49,7 @@ class OrgSkolyAutocompleteTestCase(TestCase):
"""Testuje, že pro každého orga je jeho škola ve výsledném QuerySetu""" """Testuje, že pro každého orga je jeho škola ve výsledném QuerySetu"""
for pfx, id in self.spravna_data: for pfx, id in self.spravna_data:
with self.subTest(prefix=pfx, spravne_id=id): with self.subTest(prefix=pfx, spravne_id=id):
spravna_skola = Skola.objects.get(id=id) spravna_skola = m.Skola.objects.get(id=id)
# Zeptáme se view, co si myslí # Zeptáme se view, co si myslí
resp = self.client.get(reverse('autocomplete_skola')+'?q='+pfx).json() resp = self.client.get(reverse('autocomplete_skola')+'?q='+pfx).json()
ids = [int(x['id']) for x in resp['results']] ids = [int(x['id']) for x in resp['results']]

View file

@ -1,6 +1,18 @@
"""
Soubor sloužící jako router, tj. zde se definují url adresy a na co ukazují:
- ``api/expor/skoly/`` (export_skoly) :func:`~api.views.exports.exportSkolView`
- ``api/autocomplete/skola/`` (autocomplete_skola) :class:`~api.views.autocomplete.SkolaAutocomplete`
- ``api/autocomplete/resitel/`` (autocomplete_resitel) :class:`~api.views.autocomplete.ResitelAutocomplete`
- ``api/autocomplete/problem/odevzdatelny`` (autocomplete_problem_odevzdatelny) :class:`~api.views.autocomplete.OdevzdatelnyProblemAutocomplete`
Na autocomplete v3 čeká:
- ``autocomplete/organizatori/`` (seminar_autocomplete_organizator) :class:`~api.views.autocomplete.OrganizatorAutocomplete`
"""
from django.urls import path from django.urls import path
from . import views from . import views
from personalni.utils import org_required from seminar.utils import org_required
urlpatterns = [ urlpatterns = [
# Export škol # Export škol
@ -17,5 +29,5 @@ urlpatterns = [
# Ceka na autocomplete v3 # Ceka na autocomplete v3
# path('autocomplete/organizatori/', # path('autocomplete/organizatori/',
# org_member_required(views.OrganizatorAutocomplete.as_view()), # org_member_required(views.OrganizatorAutocomplete.as_view()),
# name='autocomplete_organizator') # name='seminar_autocomplete_organizator')
] ]

View file

@ -1,2 +1,7 @@
"""
Soubory sloužící k deklaraci jednotlivých views (nejčastěji funkce beroucí request
a vracející :func:`django.shortcuts.render` respektive nějakou response, nebo
třídy většinou rozšiřující nějakou třídu z :mod:`django.views.generic`)
"""
from .autocomplete import * from .autocomplete import *
from .exports import * from .exports import *

View file

@ -5,9 +5,7 @@ from dal import autocomplete
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.db.models import Q from django.db.models import Q
from personalni.models import Skola, Resitel import seminar.models as m
from tvorba.models import Problem
from various.models import Nastaveni
from .helpers import LoginRequiredAjaxMixin from .helpers import LoginRequiredAjaxMixin
# TODO filosofie - zkratky, jak v databázi, tak ve vyhledávání (SPŠE, GASOŠ, Kpt., soukr) # TODO filosofie - zkratky, jak v databázi, tak ve vyhledávání (SPŠE, GASOŠ, Kpt., soukr)
@ -15,7 +13,7 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání škol hlavně při registraci. """ """ View k :mod:`dal.autocomplete` pro vyhledávání škol hlavně při registraci. """
def get_queryset(self): def get_queryset(self):
# Don't forget to filter out results depending on the visitor ! # Don't forget to filter out results depending on the visitor !
qs = Skola.objects.all() qs = m.Skola.objects.all()
if self.q: if self.q:
words = self.q.split(' ') #TODO re split podle bileho znaku words = self.q.split(' ') #TODO re split podle bileho znaku
partq = Q() partq = Q()
@ -33,7 +31,7 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView): class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání řešitelů především v odevzdávátku. """ """ View k :mod:`dal.autocomplete` pro vyhledávání řešitelů především v odevzdávátku. """
def get_queryset(self): def get_queryset(self):
qs = Resitel.objects.all() qs = m.Resitel.objects.all()
if self.q: if self.q:
parts = self.q.split() parts = self.q.split()
query = Q() query = Q()
@ -53,8 +51,8 @@ class PublicResitelAutocomplete(LoginRequiredAjaxMixin, autocomplete.Select2Quer
především v odevzdávátku. především v odevzdávátku.
""" """
def get_queryset(self): def get_queryset(self):
letos = Nastaveni.get_solo().aktualni_rocnik letos = m.Nastaveni.get_solo().aktualni_rocnik
qs = Resitel.objects.filter( qs = m.Resitel.objects.filter(
rok_maturity__gte=letos.druhy_rok() rok_maturity__gte=letos.druhy_rok()
).filter( ).filter(
prezdivka_resitele__isnull=False prezdivka_resitele__isnull=False
@ -72,7 +70,7 @@ class PublicResitelAutocomplete(LoginRequiredAjaxMixin, autocomplete.Select2Quer
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView): class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """ """ View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
def get_queryset(self): def get_queryset(self):
qs = Problem.objects.filter(stav=Problem.STAV_ZADANY) qs = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY)
if self.q: if self.q:
qs = qs.filter( qs = qs.filter(
Q(nazev__icontains=self.q)) Q(nazev__icontains=self.q))
@ -89,12 +87,12 @@ class ProblemAutocomplete(autocomplete.Select2QuerySetView):
""" View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """ """ View k :mod:`dal.autocomplete` pro vyhledávání problémů především v odevzdávátku. """
def get_queryset(self): def get_queryset(self):
# FIXME i starší úlohy # FIXME i starší úlohy
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(m.Nastaveni)
rocnik = nastaveni.aktualni_rocnik rocnik = nastaveni.aktualni_rocnik
temaQ = Q(Tema___rocnik = rocnik) temaQ = Q(Tema___rocnik = rocnik)
ulohaQ = Q(Uloha___cislo_zadani__rocnik=rocnik) ulohaQ = Q(Uloha___cislo_zadani__rocnik=rocnik)
clanekQ = Q(Clanek___cislo__rocnik=rocnik) clanekQ = Q(Clanek___cislo__rocnik=rocnik)
qs = Problem.objects.filter(temaQ | ulohaQ | clanekQ).order_by("-stav", "nazev") qs = m.Problem.objects.filter(temaQ | ulohaQ | clanekQ).order_by("-stav", "nazev")
if self.q: if self.q:
qs = qs.filter( qs = qs.filter(
Q(nazev__icontains=self.q)) Q(nazev__icontains=self.q))

View file

@ -1,4 +1,4 @@
import personalni.models as m import seminar.models as m
from django.core import serializers as ser from django.core import serializers as ser
from django.http import HttpResponse from django.http import HttpResponse
def exportSkolView(request): def exportSkolView(request):

View file

@ -1,655 +0,0 @@
[
{
"fields": {
"name": "org",
"permissions": [
[
"org",
"auth",
"user"
],
[
"add_flatpage",
"flatpages",
"flatpage"
],
[
"change_flatpage",
"flatpages",
"flatpage"
],
[
"delete_flatpage",
"flatpages",
"flatpage"
],
[
"view_flatpage",
"flatpages",
"flatpage"
],
[
"add_galerie",
"galerie",
"galerie"
],
[
"change_galerie",
"galerie",
"galerie"
],
[
"delete_galerie",
"galerie",
"galerie"
],
[
"view_galerie",
"galerie",
"galerie"
],
[
"add_obrazek",
"galerie",
"obrazek"
],
[
"change_obrazek",
"galerie",
"obrazek"
],
[
"delete_obrazek",
"galerie",
"obrazek"
],
[
"view_obrazek",
"galerie",
"obrazek"
],
[
"add_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"change_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"view_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"add_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"change_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"view_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"add_komentar",
"korektury",
"komentar"
],
[
"change_komentar",
"korektury",
"komentar"
],
[
"delete_komentar",
"korektury",
"komentar"
],
[
"view_komentar",
"korektury",
"komentar"
],
[
"add_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"change_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"delete_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"view_korekturovanepdf",
"korektury",
"korekturovanepdf"
],
[
"add_oprava",
"korektury",
"oprava"
],
[
"change_oprava",
"korektury",
"oprava"
],
[
"delete_oprava",
"korektury",
"oprava"
],
[
"view_oprava",
"korektury",
"oprava"
],
[
"add_novinky",
"novinky",
"novinky"
],
[
"change_novinky",
"novinky",
"novinky"
],
[
"delete_novinky",
"novinky",
"novinky"
],
[
"view_novinky",
"novinky",
"novinky"
],
[
"change_organizator",
"personalni",
"organizator"
],
[
"view_organizator",
"personalni",
"organizator"
],
[
"change_osoba",
"personalni",
"osoba"
],
[
"view_osoba",
"personalni",
"osoba"
],
[
"add_prijemce",
"personalni",
"prijemce"
],
[
"change_prijemce",
"personalni",
"prijemce"
],
[
"delete_prijemce",
"personalni",
"prijemce"
],
[
"view_prijemce",
"personalni",
"prijemce"
],
[
"change_resitel",
"personalni",
"resitel"
],
[
"view_resitel",
"personalni",
"resitel"
],
[
"add_skola",
"personalni",
"skola"
],
[
"change_skola",
"personalni",
"skola"
],
[
"delete_skola",
"personalni",
"skola"
],
[
"view_skola",
"personalni",
"skola"
],
[
"view_hlasovani",
"prednasky",
"hlasovani"
],
[
"view_hlasovanioznalostech",
"prednasky",
"hlasovanioznalostech"
],
[
"add_prednaska",
"prednasky",
"prednaska"
],
[
"change_prednaska",
"prednasky",
"prednaska"
],
[
"delete_prednaska",
"prednasky",
"prednaska"
],
[
"view_prednaska",
"prednasky",
"prednaska"
],
[
"add_seznam",
"prednasky",
"seznam"
],
[
"change_seznam",
"prednasky",
"seznam"
],
[
"delete_seznam",
"prednasky",
"seznam"
],
[
"view_seznam",
"prednasky",
"seznam"
],
[
"add_znalost",
"prednasky",
"znalost"
],
[
"change_znalost",
"prednasky",
"znalost"
],
[
"delete_znalost",
"prednasky",
"znalost"
],
[
"view_znalost",
"prednasky",
"znalost"
],
[
"add_konfera",
"soustredeni",
"konfera"
],
[
"change_konfera",
"soustredeni",
"konfera"
],
[
"delete_konfera",
"soustredeni",
"konfera"
],
[
"view_konfera",
"soustredeni",
"konfera"
],
[
"add_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"change_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"delete_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"view_konfery_ucastnici",
"soustredeni",
"konfery_ucastnici"
],
[
"add_soustredeni",
"soustredeni",
"soustredeni"
],
[
"change_soustredeni",
"soustredeni",
"soustredeni"
],
[
"delete_soustredeni",
"soustredeni",
"soustredeni"
],
[
"view_soustredeni",
"soustredeni",
"soustredeni"
],
[
"add_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"change_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"delete_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"view_soustredeni_organizatori",
"soustredeni",
"soustredeni_organizatori"
],
[
"add_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"change_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"delete_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"view_soustredeni_ucastnici",
"soustredeni",
"soustredeni_ucastnici"
],
[
"add_tag",
"taggit",
"tag"
],
[
"change_tag",
"taggit",
"tag"
],
[
"delete_tag",
"taggit",
"tag"
],
[
"view_tag",
"taggit",
"tag"
],
[
"add_taggeditem",
"taggit",
"taggeditem"
],
[
"change_taggeditem",
"taggit",
"taggeditem"
],
[
"delete_taggeditem",
"taggit",
"taggeditem"
],
[
"view_taggeditem",
"taggit",
"taggeditem"
],
[
"add_cislo",
"tvorba",
"cislo"
],
[
"change_cislo",
"tvorba",
"cislo"
],
[
"delete_cislo",
"tvorba",
"cislo"
],
[
"view_cislo",
"tvorba",
"cislo"
],
[
"add_clanek",
"tvorba",
"clanek"
],
[
"change_clanek",
"tvorba",
"clanek"
],
[
"delete_clanek",
"tvorba",
"clanek"
],
[
"view_clanek",
"tvorba",
"clanek"
],
[
"add_deadline",
"tvorba",
"deadline"
],
[
"change_deadline",
"tvorba",
"deadline"
],
[
"view_deadline",
"tvorba",
"deadline"
],
[
"add_pohadka",
"tvorba",
"pohadka"
],
[
"change_pohadka",
"tvorba",
"pohadka"
],
[
"delete_pohadka",
"tvorba",
"pohadka"
],
[
"view_pohadka",
"tvorba",
"pohadka"
],
[
"add_problem",
"tvorba",
"problem"
],
[
"change_problem",
"tvorba",
"problem"
],
[
"delete_problem",
"tvorba",
"problem"
],
[
"view_problem",
"tvorba",
"problem"
],
[
"add_rocnik",
"tvorba",
"rocnik"
],
[
"change_rocnik",
"tvorba",
"rocnik"
],
[
"delete_rocnik",
"tvorba",
"rocnik"
],
[
"view_rocnik",
"tvorba",
"rocnik"
],
[
"add_tema",
"tvorba",
"tema"
],
[
"change_tema",
"tvorba",
"tema"
],
[
"delete_tema",
"tvorba",
"tema"
],
[
"view_tema",
"tvorba",
"tema"
],
[
"add_uloha",
"tvorba",
"uloha"
],
[
"change_uloha",
"tvorba",
"uloha"
],
[
"delete_uloha",
"tvorba",
"uloha"
],
[
"view_uloha",
"tvorba",
"uloha"
],
[
"add_nastaveni",
"various",
"nastaveni"
],
[
"change_nastaveni",
"various",
"nastaveni"
],
[
"delete_nastaveni",
"various",
"nastaveni"
],
[
"view_nastaveni",
"various",
"nastaveni"
]
]
},
"model": "auth.group",
"pk": 1
},
{
"fields": {
"name": "resitel",
"permissions": [
[
"resitel",
"auth",
"user"
]
]
},
"model": "auth.group",
"pk": 2
}
]

View file

@ -73,7 +73,7 @@
"sort_order": 3, "sort_order": 3,
"title": "Aktuální<br/> ročník", "title": "Aktuální<br/> ročník",
"tree": 1, "tree": 1,
"url": "tvorba_aktualni_zadani", "url": "seminar_aktualni_zadani",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -121,7 +121,7 @@
"sort_order": 5, "sort_order": 5,
"title": "Archiv", "title": "Archiv",
"tree": 1, "tree": 1,
"url": "tvorba_archiv_rocniky", "url": "seminar_archiv_rocniky",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -289,7 +289,7 @@
"sort_order": 43, "sort_order": 43,
"title": "Výsledková listina", "title": "Výsledková listina",
"tree": 1, "tree": 1,
"url": "tvorba_aktualni_vysledky", "url": "seminar_aktualni_vysledky",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -361,7 +361,7 @@
"sort_order": 20, "sort_order": 20,
"title": "Proběhlo", "title": "Proběhlo",
"tree": 1, "tree": 1,
"url": "soustredeni_seznam", "url": "seminar_seznam_soustredeni",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -409,7 +409,7 @@
"sort_order": 23, "sort_order": 23,
"title": "Osobní údaje", "title": "Osobní údaje",
"tree": 1, "tree": 1,
"url": "personalni_resitel_edit", "url": "seminar_resitel_edit",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -439,7 +439,7 @@
"sort_order": 36, "sort_order": 36,
"title": "Nahrát řešení", "title": "Nahrát řešení",
"tree": 1, "tree": 1,
"url": "odevzdavatko_nahraj_reseni", "url": "seminar_nahraj_reseni",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -463,7 +463,7 @@
"sort_order": 35, "sort_order": 35,
"title": "Témata", "title": "Témata",
"tree": 1, "tree": 1,
"url": "tvorba_archiv_temata", "url": "seminar_archiv_temata",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -589,7 +589,7 @@
"sort_order": 15, "sort_order": 15,
"title": "Aktuální číslo", "title": "Aktuální číslo",
"tree": 1, "tree": 1,
"url": "tvorba_aktualni_zadani", "url": "seminar_aktualni_zadani",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -613,7 +613,7 @@
"sort_order": 24, "sort_order": 24,
"title": "Čísla", "title": "Čísla",
"tree": 1, "tree": 1,
"url": "tvorba_archiv_rocniky", "url": "seminar_archiv_rocniky",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -721,7 +721,7 @@
"sort_order": 36, "sort_order": 36,
"title": "Vložit řešení", "title": "Vložit řešení",
"tree": 1, "tree": 1,
"url": "odevzdavatko_vloz_reseni", "url": "seminar_vloz_reseni",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -804,7 +804,7 @@
"sort_order": 37, "sort_order": 37,
"title": "Moje řešení", "title": "Moje řešení",
"tree": 1, "tree": 1,
"url": "odevzdavatko_resitel_odevzdana_reseni", "url": "seminar_resitel_odevzdana_reseni",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -828,7 +828,7 @@
"sort_order": 33, "sort_order": 33,
"title": "Aktuální ročník", "title": "Aktuální ročník",
"tree": 1, "tree": 1,
"url": "tvorba_aktualni_rocnik", "url": "seminar_aktualni_rocnik",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -900,7 +900,7 @@
"sort_order": 46, "sort_order": 46,
"title": "Ročník {{rocnik.rocnik}}", "title": "Ročník {{rocnik.rocnik}}",
"tree": 1, "tree": 1,
"url": "tvorba_rocnik rocnik.rocnik", "url": "seminar_rocnik rocnik.rocnik",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -924,7 +924,7 @@
"sort_order": 47, "sort_order": 47,
"title": "Číslo {{ cislo.rocnik.rocnik }}.{{ cislo.poradi }}", "title": "Číslo {{ cislo.rocnik.rocnik }}.{{ cislo.poradi }}",
"tree": 1, "tree": 1,
"url": "tvorba_cislo cislo.rocnik.rocnik cislo.poradi", "url": "seminar_cislo cislo.rocnik.rocnik cislo.poradi",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -1007,18 +1007,7 @@
"access_guest": false, "access_guest": false,
"access_loggedin": false, "access_loggedin": false,
"access_perm_type": 1, "access_perm_type": 1,
"access_permissions": [ "access_permissions": [],
[
"org",
"auth",
"user"
],
[
"resitel",
"auth",
"user"
]
],
"access_restricted": true, "access_restricted": true,
"alias": null, "alias": null,
"description": "", "description": "",
@ -1061,7 +1050,7 @@
"sort_order": 52, "sort_order": 52,
"title": "Nahrát řešení k nadproblému {{nadproblem_id}}", "title": "Nahrát řešení k nadproblému {{nadproblem_id}}",
"tree": 1, "tree": 1,
"url": "odevzdavatko_nahraj_reseni nadproblem_id", "url": "seminar_nahraj_reseni nadproblem_id",
"urlaspattern": true "urlaspattern": true
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
@ -1090,29 +1079,5 @@
}, },
"model": "sitetree.treeitem", "model": "sitetree.treeitem",
"pk": 53 "pk": 53
},
{
"fields": {
"access_guest": false,
"access_loggedin": false,
"access_perm_type": 1,
"access_permissions": [],
"access_restricted": false,
"alias": null,
"description": "",
"hidden": false,
"hint": "",
"inbreadcrumbs": true,
"inmenu": true,
"insitetree": true,
"parent": 20,
"sort_order": 54,
"title": "Export do abstraktů sousu {{ soustredeni.id }}",
"tree": 1,
"url": "soustredeni_abstrakty soustredeni.id",
"urlaspattern": true
},
"model": "sitetree.treeitem",
"pk": 54
} }
] ]

View file

@ -0,0 +1,622 @@
[
{
"codename": "org",
"ct_app_label": "auth",
"ct_model": "user"
},
{
"codename": "add_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "change_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "delete_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "view_flatpage",
"ct_app_label": "flatpages",
"ct_model": "flatpage"
},
{
"codename": "add_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "change_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "delete_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "view_galerie",
"ct_app_label": "galerie",
"ct_model": "galerie"
},
{
"codename": "add_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "change_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "delete_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "view_obrazek",
"ct_app_label": "galerie",
"ct_model": "obrazek"
},
{
"codename": "add_fotkaheader",
"ct_app_label": "header_fotky",
"ct_model": "fotkaheader"
},
{
"codename": "change_fotkaheader",
"ct_app_label": "header_fotky",
"ct_model": "fotkaheader"
},
{
"codename": "view_fotkaheader",
"ct_app_label": "header_fotky",
"ct_model": "fotkaheader"
},
{
"codename": "add_fotkaurlvazba",
"ct_app_label": "header_fotky",
"ct_model": "fotkaurlvazba"
},
{
"codename": "change_fotkaurlvazba",
"ct_app_label": "header_fotky",
"ct_model": "fotkaurlvazba"
},
{
"codename": "view_fotkaurlvazba",
"ct_app_label": "header_fotky",
"ct_model": "fotkaurlvazba"
},
{
"codename": "add_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "change_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "delete_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "view_komentar",
"ct_app_label": "korektury",
"ct_model": "komentar"
},
{
"codename": "add_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "change_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "delete_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "view_korekturovanepdf",
"ct_app_label": "korektury",
"ct_model": "korekturovanepdf"
},
{
"codename": "add_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "change_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "delete_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "view_oprava",
"ct_app_label": "korektury",
"ct_model": "oprava"
},
{
"codename": "add_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "change_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "delete_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "view_hlasovani",
"ct_app_label": "prednasky",
"ct_model": "hlasovani"
},
{
"codename": "add_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "change_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "delete_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "view_prednaska",
"ct_app_label": "prednasky",
"ct_model": "prednaska"
},
{
"codename": "add_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "change_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "delete_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "view_seznam",
"ct_app_label": "prednasky",
"ct_model": "seznam"
},
{
"codename": "add_cislo",
"ct_app_label": "seminar",
"ct_model": "cislo"
},
{
"codename": "change_cislo",
"ct_app_label": "seminar",
"ct_model": "cislo"
},
{
"codename": "delete_cislo",
"ct_app_label": "seminar",
"ct_model": "cislo"
},
{
"codename": "view_cislo",
"ct_app_label": "seminar",
"ct_model": "cislo"
},
{
"codename": "add_clanek",
"ct_app_label": "seminar",
"ct_model": "clanek"
},
{
"codename": "change_clanek",
"ct_app_label": "seminar",
"ct_model": "clanek"
},
{
"codename": "delete_clanek",
"ct_app_label": "seminar",
"ct_model": "clanek"
},
{
"codename": "view_clanek",
"ct_app_label": "seminar",
"ct_model": "clanek"
},
{
"codename": "add_deadline",
"ct_app_label": "seminar",
"ct_model": "deadline"
},
{
"codename": "change_deadline",
"ct_app_label": "seminar",
"ct_model": "deadline"
},
{
"codename": "view_deadline",
"ct_app_label": "seminar",
"ct_model": "deadline"
},
{
"codename": "add_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "change_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "delete_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "view_konfera",
"ct_app_label": "soustredeni",
"ct_model": "konfera"
},
{
"codename": "add_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "change_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "delete_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "view_konfery_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "konfery_ucastnici"
},
{
"codename": "add_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "change_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "delete_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "view_nastaveni",
"ct_app_label": "various",
"ct_model": "nastaveni"
},
{
"codename": "add_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "change_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "delete_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "view_novinky",
"ct_app_label": "novinky",
"ct_model": "novinky"
},
{
"codename": "change_organizator",
"ct_app_label": "personalni",
"ct_model": "organizator"
},
{
"codename": "view_organizator",
"ct_app_label": "personalni",
"ct_model": "organizator"
},
{
"codename": "change_osoba",
"ct_app_label": "personalni",
"ct_model": "osoba"
},
{
"codename": "view_osoba",
"ct_app_label": "personalni",
"ct_model": "osoba"
},
{
"codename": "add_pohadka",
"ct_app_label": "seminar",
"ct_model": "pohadka"
},
{
"codename": "change_pohadka",
"ct_app_label": "seminar",
"ct_model": "pohadka"
},
{
"codename": "delete_pohadka",
"ct_app_label": "seminar",
"ct_model": "pohadka"
},
{
"codename": "view_pohadka",
"ct_app_label": "seminar",
"ct_model": "pohadka"
},
{
"codename": "add_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "change_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "delete_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "view_prijemce",
"ct_app_label": "personalni",
"ct_model": "prijemce"
},
{
"codename": "add_problem",
"ct_app_label": "seminar",
"ct_model": "problem"
},
{
"codename": "change_problem",
"ct_app_label": "seminar",
"ct_model": "problem"
},
{
"codename": "delete_problem",
"ct_app_label": "seminar",
"ct_model": "problem"
},
{
"codename": "view_problem",
"ct_app_label": "seminar",
"ct_model": "problem"
},
{
"codename": "change_resitel",
"ct_app_label": "personalni",
"ct_model": "resitel"
},
{
"codename": "view_resitel",
"ct_app_label": "personalni",
"ct_model": "resitel"
},
{
"codename": "add_rocnik",
"ct_app_label": "seminar",
"ct_model": "rocnik"
},
{
"codename": "change_rocnik",
"ct_app_label": "seminar",
"ct_model": "rocnik"
},
{
"codename": "delete_rocnik",
"ct_app_label": "seminar",
"ct_model": "rocnik"
},
{
"codename": "view_rocnik",
"ct_app_label": "seminar",
"ct_model": "rocnik"
},
{
"codename": "add_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "change_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "delete_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "view_skola",
"ct_app_label": "personalni",
"ct_model": "skola"
},
{
"codename": "add_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "change_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "delete_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "view_soustredeni",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni"
},
{
"codename": "add_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "change_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "delete_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "view_soustredeni_organizatori",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "add_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "change_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "delete_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "view_soustredeni_ucastnici",
"ct_app_label": "soustredeni",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "add_tema",
"ct_app_label": "seminar",
"ct_model": "tema"
},
{
"codename": "change_tema",
"ct_app_label": "seminar",
"ct_model": "tema"
},
{
"codename": "delete_tema",
"ct_app_label": "seminar",
"ct_model": "tema"
},
{
"codename": "view_tema",
"ct_app_label": "seminar",
"ct_model": "tema"
},
{
"codename": "add_uloha",
"ct_app_label": "seminar",
"ct_model": "uloha"
},
{
"codename": "change_uloha",
"ct_app_label": "seminar",
"ct_model": "uloha"
},
{
"codename": "delete_uloha",
"ct_app_label": "seminar",
"ct_model": "uloha"
},
{
"codename": "view_uloha",
"ct_app_label": "seminar",
"ct_model": "uloha"
},
{
"codename": "add_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "change_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "delete_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "view_tag",
"ct_app_label": "taggit",
"ct_model": "tag"
},
{
"codename": "add_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
},
{
"codename": "change_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
},
{
"codename": "delete_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
},
{
"codename": "view_taggeditem",
"ct_app_label": "taggit",
"ct_model": "taggeditem"
}
]

View file

@ -13,6 +13,7 @@ make install_venv
make install make install
deploy_v2/pre_migration.py deploy_v2/pre_migration.py
make deploy_test make deploy_test
./manage.py load_org_permissions admin_org_prava.json
./manage.py loaddata data/* ./manage.py loaddata data/*
systemctl --user start mamweb-test.service systemctl --user start mamweb-test.service
./manage.py generate_thumbnails ./manage.py generate_thumbnails

View file

@ -13,7 +13,6 @@
import os import os
import sys import sys
import django import django
from django.utils.version import get_docs_version
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'mamweb.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'mamweb.settings'
django.setup() django.setup()
@ -37,7 +36,6 @@ extensions = [
'sphinx.ext.intersphinx', 'sphinx.ext.intersphinx',
'sphinx.ext.autosectionlabel', 'sphinx.ext.autosectionlabel',
'myst_parser', 'myst_parser',
'sphinxcontrib_django',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -74,8 +72,8 @@ html_static_path = ['_static']
# Provázání s jinými dokumentacemi # Provázání s jinými dokumentacemi
intersphinx_mapping = {'python': ('https://docs.python.org/3', None), intersphinx_mapping = {'python': ('https://docs.python.org/3', None),
'django': (f'http://docs.djangoproject.com/en/{get_docs_version()}/', 'django': ('http://docs.djangoproject.com/en/3.2/',
f'http://docs.djangoproject.com/en/{get_docs_version()}/_objects/'),} 'http://docs.djangoproject.com/en/3.2/_objects/'),}
# Generování tříd/funkcí/atributů v pořádí jak jsou naprogramované # Generování tříd/funkcí/atributů v pořádí jak jsou naprogramované
autodoc_member_order = "bysource" autodoc_member_order = "bysource"

View file

@ -1,27 +0,0 @@
CSS (a další styly na webu)
===========================
Inspirován `css-trick článkem <https://css-tricks.com/methods-organize-css/>`_ jsem se rozhodl rozdělit
CSSka do
- Konstant (``constants.css``), které jsou využívány na mnoha místech CSSek
- Nastylování html tagů (``base.css``)
- Layoutu (``layout.css``), což je to, co určuje celkové rozložení stránky
- Jednotlivých prvků (``modules.css``)
Dále jsem separoval CSSka pro **galerii** (potřebuje hodně specifických stylů). Stejně tak **korekturovátko** má styly separátně.
Dále web (asi) používá externí frameworky (v separátních složkách mají k sobě i JS a podobné věci):
- bootstrap: dělá nějaké basic stylování, *web je na něm hodně závislý* (například jsem zjistil, že bootstrap kdysi přidával ``font-size:14px``, bez čehož se web úplně rozpadnul) (také na něm běží mobilní meníčko, které navíc vyžaduje Popper, tedy bootstrap.bundle.js místo bootstrap.js)
Pak jsou tu ``mamweb-dev.css`` a ``printtable.css``, co jsem si ještě nerozmyslel, co s tím.
Pár myšlenek
------------
- Až na pár výjimek (galerii a korekturovátko) bych styly držel v jedné složce a málo souborech,
protože CSS šíleně dědí všechno možné
- Chce to dobře pojmenovávat třídy (speciálně aby bylo vidět, co ta třída dělá nebo kde se používá)
- Chce to hodně komentovat kód (speciálně tam, kde není splněn předchozí bod)

View file

@ -9,6 +9,12 @@ static
------ ------
Složka, kam django nakopíruje všechno ze složek static a pak na to z templatů / kódu jde ukazovat pomocí ``static``. Složka, kam django nakopíruje všechno ze složek static a pak na to z templatů / kódu jde ukazovat pomocí ``static``.
_git_hooks
----------
Hooky do gitu pro kontrolu Pythoního stylu. Především ``flake8``.
Zbylo tu z minulosti mamwebu.
data data
---- ----
Obsahuje data, která patří do databáze, ale jsou přímo součástí webu jako Obsahuje data, která patří do databáze, ale jsou přímo součástí webu jako

View file

@ -30,7 +30,6 @@ Dokumentace (jak v ``docs/``, tak přímo v kódu) je psaná ve
zavislosti zavislosti
sphinx sphinx
skripty skripty
zkratky
modules/modules modules/modules
dalsi_soubory dalsi_soubory
zapisy/zapisy zapisy/zapisy

View file

@ -1,15 +1,13 @@
Sphinx na našem webu Sphinx na našem webu
==================== ====================
Dokumentace se zkompiluje příkazem ``make html`` ve složce ``docs``. (Musíte mít zapnutý virtualenv) Dokumentace se zkompiluje příkazem ``make html`` ve složce ``doc``.
Složka ``modules`` je automaticiky generována a přegenerovávána. (**Nic v ní neupravovat!**) Složka ``modules`` je automaticiky generována a přegenerovávána. (**Nic v ní neupravovat!**)
Jinak všechny rst, co jsou ve složce ``docs`` a jejích podsložkách nezačínajících podtržítkem, budou v dokumentaci a to je přesně to, co editovat pro změnu dokumentace (kromě dokumentace přímo v Pythonu). Jinak všechny rst, co jsou ve složce ``doc`` a jejích podsložkách nezačínajících podtržítkem, budou v dokumentaci a to je přesně to, co editovat pro změnu dokumentace (kromě dokumentace přímo v Pythonu).
Sphinx se píše v rst: `Návod na syntaxi rst`_ `Cheat sheet`_ Sphinx se píše v rst: `Návod na syntaxi rst`_ `Cheat sheet`_
Pokud něco chcete protlačit do bočního meníčka, je potřeba to připsat do souboru ``docs/index.rst`` (Zatím není úplně konsensus nad tím, co tam má a nemá být, takže pokud si nejste jistí, cpěte tam *všechno* ☺)
To je snad vše, co je potřeba vědět k dokumentaci mamwebu. Následující sekce jsou o tom, co jsem provedl Sphinxu, aby to fungovalo: To je snad vše, co je potřeba vědět k dokumentaci mamwebu. Následující sekce jsou o tom, co jsem provedl Sphinxu, aby to fungovalo:
.. _Návod na syntaxi rst: https://sphinx-tutorial.readthedocs.io/step-1/#sections .. _Návod na syntaxi rst: https://sphinx-tutorial.readthedocs.io/step-1/#sections

View file

@ -10,9 +10,11 @@ věci jako chybové hlášky a vzhled M&M stránek (menu, patička, atd.). Aktu
i veškeré csv. i veškeré csv.
Další jsou pak jednotlivé aplikace (pokud něco hledáte, tak zřejmě chcete najít Další jsou pak jednotlivé aplikace (pokud něco hledáte, tak zřejmě chcete najít
tu aplikaci, která tomu odpovídá, respektive se k ní dostat přes url). tu aplikaci, která tomu odpovídá, respektive se k ní dostat přes url), za
zmínku stojí seminar, kde jsou takové ty věci, co zbyly. Plus jsou tam aktuálně
téměř všechny modely, protože je těžké je přesunout jinam.
**TLDR: Nevšímejte si složek data/ seminar/ a souborů přímo v kořenové složce.** **TLDR: Nevšímejte si složky data/ a souborů přímo v kořenové složce.**
Kromě věcí potřebných ke gitu, :doc:`ke spuštění <vyvoj>` a fukci djanga, Kromě věcí potřebných ke gitu, :doc:`ke spuštění <vyvoj>` a fukci djanga,
dalších drobností, lokální databáze a již zmíněných aplikací jsou tu ``data``, dalších drobností, lokální databáze a již zmíněných aplikací jsou tu ``data``,
kde je takový ten obsah webu, co by se měl dát snadno měnit (tudíž musí být v kde je takový ten obsah webu, co by se měl dát snadno měnit (tudíž musí být v
@ -20,9 +22,6 @@ databázi), tj. statické stránky, menu a obrázky v pozadí menu. Ten je třeb
měnit hlavně na produkci a sekundárně tady (může to dělat i newebař a nechcete měnit hlavně na produkci a sekundárně tady (může to dělat i newebař a nechcete
přepsat jeho práci). Vše, co nejsou aplikace je popsáno :doc:`tady <dalsi_soubory>`. přepsat jeho práci). Vše, co nejsou aplikace je popsáno :doc:`tady <dalsi_soubory>`.
Ještě je tu aplikace ``seminar/``, kde bylo původně skoro všechno, a tak nám
tam zbývá spoustu historických migrací (čehož se jen tak nezbavíme).
Základy djanga Základy djanga
-------------- --------------

View file

@ -116,7 +116,7 @@ Aktuálně: Jakýsi coding style zhruba existuje, není popsaný, šíří se li
- Nesmí být striktně vynucovaný - Nesmí být striktně vynucovaný
- Musel by být hodně nastavitelný - Musel by být hodně nastavitelný
- Nechceme mít kód plný `#NOQA: WTF42` - Nechceme mít kód plný `#NOQA: WTF42`
- Nejspíš vždycky bude mít false positives (`tvorba.utils.roman_numerals`) i false negatives (`tvorba.models.Cislo.posli_cislo_mailem`) - Nejspíš vždycky bude mít false positives (`seminar.utils.roman_numerals`) i false negatives (`seminar.models.tvorba.Cislo.posli_cislo_mailem`)
- Možná dobrý sluha, ale určitě špatný pán (also: špatná zkušenost ☺) - Možná dobrý sluha, ale určitě špatný pán (also: špatná zkušenost ☺)
- __Důsledek:__ Hrozí, že těch falešných varování bude moc, čímž to ztratí smysl úplně - __Důsledek:__ Hrozí, že těch falešných varování bude moc, čímž to ztratí smysl úplně
- Potenciálně by šlo aplikovat jen lokálně na změny? - Potenciálně by šlo aplikovat jen lokálně na změny?

View file

@ -1,86 +0,0 @@
Zkratky aplikací ve zdrojácích
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Ve zdrojácích (zejména různé ``models.py``, ``views.py`` ap.) používáme spoustu
modelů. Někdy je praktičtější / někdo preferuje importovat celou aplikaci jako
jedno jméno a používat modely bez explicitních importů, tj::
# „hromadné“ importy:
import personalni.models as p
...
p.Organizator.objects.all()
# „explicitní“ importy:
from personalni.models import Organizator
...
Organizator.objects.all()
Na webschůzce 2024-11-05 jsme na toto téma otevřeli diskusi, tady je její závěr.
.. admonition:: Historické okénko
:class: note
Kdysi jsme měli (prakticky) všechny modely v jedné aplikaci, ``seminar``. Na
různých místech se pak ``seminar.models`` importovalo typicky jako ``s``
nebo jako ``m``.
Přirozeně, toto už nejde tak snadno, protože už neexistuje jedno místo, ze
kterého chceme tahat modely v kódu.
Konvence
========
Shodli jsme se, že nám rozhodně nevadí explicitní importy a z pohledu
čitelnosti je preferujeme. Nicméně při psaní kódu to některým webařům přijde
nepohodlné, takže očekáváme, že bude existovat spousta kódu, která bude chtít
importovat hromadně. Usnesli jsme se proto na následujících kanonických
zkratkách, aby se aplikace alespoň zkracovaly konzistentně.
V závorkách je uvedené případné jméno, ale nepředpokládáme, že někdo bude danou
aplikaci chtít importovat hromadně. Některé aplikace zkratku nemají, ty se
importují vždy pod plným jménem nebo explicitně.
.. list-table::
:header-rows: 1
* - Model
- Zkratka
* - ``aesop``
- ---
* - ``api``
- --- (``api``)
* - ``galerie``
- ``g``
* - ``header_fotky``
- --- (``hdr``)
* - ``korektury``
- ``kor``
* - ``novinky``
- ``nov``
* - ``odevzdavatko``
- ``odev``
* - ``personální``
- ``pers``/``p``
* - ``sifrovacka``
- (``sifr``)
* - ``soustredeni``
- ``sou``
* - ``treenode``
- ``tn``
* - ``tvorba``
- ``tv``
* - ``various``
- ``v``/``var``
* - ``vyroci``
- ---
* - ``vysledkovky``
- ``vysl``
.. admonition:: O všech modelech pod jedním jménem
:class: warning
Historické okénko výš zatajuje jeden detail: Při práci v shellu se hodí mít
modely k dispozici a nemuset přemýšlet nad dělením do aplikací, takže ve
skutečnosti existuje ``mamweb.vsechno``, jenž všechny modely obsahuje.
Z čitelnostních důvodů je ale *zakázáno* tento modul používat v kódu.

25
galerie/TODO Normal file
View file

@ -0,0 +1,25 @@
========
| TODO |
|======|
Aktualni
* co s titulni fotkou
* do CSS
* nahledy
* nastylovat tabulku s nahledy
* komentare uz na nahledy?
* detail
* nahledy pred a po
* opravit prechodove sipky
* vyrobit prechodove sipky ve M&M-stylu
Dlouhodobe
* sipky na prechazeni mezi fotkami
* hromadne PRIDANI fotek do jiz existujici galerie
Fylozoficke
* zvolit velikosti velke a male fotky
* je potreba i jine razeni nez automaticky podle casu nebo staci podgalerie?
* napr. dve hry na dvou ruznych mistech ve stejny cas
* fotky od ucastniku ze hry (skupinky se pohybuji ve stejny cas, ale maji sled fotek) -- nestaci to pripadne vrazit do podgalerii?

View file

@ -1,4 +1,6 @@
from galerie.models import Obrazek, Galerie, VZDY, ORG, NIKDY, UCASTNIK #coding: utf-8
from galerie.models import Obrazek, Galerie
from django.contrib import admin from django.contrib import admin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django import forms from django import forms
@ -8,9 +10,8 @@ from django.db import models
def zverejnit_fotogalerii(modeladmin, request, queryset): def zverejnit_fotogalerii(modeladmin, request, queryset):
'''zverejni vybranou fotogalerii i jeji vsechny podgalerie''' '''zverejni vybranou fotogalerii i jeji vsechny podgalerie'''
queryset = queryset.filter(zobrazit=ORG)
for galerie in queryset: for galerie in queryset:
galerie.zobrazit = VZDY galerie.zobrazit = 0
galerie.save() galerie.save()
zverejnit_fotogalerii(modeladmin, request, zverejnit_fotogalerii(modeladmin, request,
Galerie.objects.filter(galerie_up = galerie)) Galerie.objects.filter(galerie_up = galerie))
@ -19,9 +20,8 @@ def zverejnit_fotogalerii(modeladmin, request, queryset):
def prepnout_fotogalerii_do_org_rezimu(modeladmin, request, queryset): def prepnout_fotogalerii_do_org_rezimu(modeladmin, request, queryset):
'''zneverjni vybranou fotogalerii i jeji vsechny podgalerie''' '''zneverjni vybranou fotogalerii i jeji vsechny podgalerie'''
queryset = queryset.filter(zobrazit=VZDY)
for galerie in queryset: for galerie in queryset:
galerie.zobrazit = ORG galerie.zobrazit = 1
galerie.save() galerie.save()
prepnout_fotogalerii_do_org_rezimu(modeladmin, request, prepnout_fotogalerii_do_org_rezimu(modeladmin, request,
Galerie.objects.filter(galerie_up = galerie)) Galerie.objects.filter(galerie_up = galerie))

View file

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from autocomplete_light import shortcuts as autocomplete_light
from .models import Obrazek, Galerie
from .views import cesta_od_korene
class ObrazekAutocomplete(autocomplete_light.AutocompleteModelBase):
model = Obrazek
search_fields = ['nazev', 'popis']
split_words = True
limit_choices = 15
attrs = {
# This will set the input placeholder attribute:
'placeholder': u'Obrázek',
# This will set the yourlabs.Autocomplete.minimumCharacters
# options, the naming conversion is handled by jQuery
'data-autocomplete-minimum-characters': 1,
}
choice_html_format = '''
<span class="block" data-value="{}">
<span class="block">
{}
<span class="block">{}</span>
</span>
</span>
'''
def choice_label(self, obrazek):
cesta = "/".join(g.nazev for g in cesta_od_korene(obrazek.galerie))
popis = "{}<br>".format(obrazek.popis) if obrazek.popis else ""
return '{}<br>{}{}'.format(obrazek.nazev, popis, cesta)
def choice_html(self, obrazek):
"""Vrátí kus html i s obrázkem, které se pak ukazuje v nabídce"""
return self.choice_html_format.format(self.choice_value(obrazek),
obrazek.obrazek_maly_tag(), self.choice_label(obrazek))
widget_attrs={
'data-widget-maximum-values': 15,
'class': 'modern-style',
}
autocomplete_light.register(ObrazekAutocomplete)

View file

@ -1,4 +1,7 @@
#coding: utf-8
from django import forms from django import forms
from seminar.models import Soustredeni
class KomentarForm(forms.Form): class KomentarForm(forms.Form):
komentar = forms.CharField(label = "Komentář:", max_length = 300, required=False) komentar = forms.CharField(label = "Komentář:", max_length = 300, required=False)

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-30 21:40 # Generated by Django 1.11.20 on 2019-04-30 21:40
from __future__ import unicode_literals from __future__ import unicode_literals

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.21 on 2019-06-10 21:58 # Generated by Django 1.11.21 on 2019-06-10 21:58
from __future__ import unicode_literals from __future__ import unicode_literals

View file

@ -1,18 +0,0 @@
# Generated by Django 4.2.20 on 2025-04-23 18:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('galerie', '0013_post_split_soustredeni'),
]
operations = [
migrations.AlterField(
model_name='galerie',
name='zobrazit',
field=models.IntegerField(choices=[(0, 'Vždy'), (1, 'Organizátorům'), (3, 'Účastníkům a orgům'), (2, 'Nikdy')], default=1, verbose_name='Zobrazit?'),
),
]

View file

@ -1,19 +0,0 @@
# Generated by Django 4.2.16 on 2025-04-30 18:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('galerie', '0014_alter_galerie_zobrazit'),
]
operations = [
migrations.AlterField(
model_name='galerie',
name='galerie_up',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='galerie.galerie'),
),
]

View file

@ -1,19 +0,0 @@
# Generated by Django 4.2.16 on 2025-04-30 19:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('galerie', '0015_alter_galerie_galerie_up'),
]
operations = [
migrations.AlterField(
model_name='obrazek',
name='galerie',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='galerie.galerie'),
),
]

View file

@ -1,3 +1,5 @@
# coding: utf-8
from django.db import models from django.db import models
#from django.db.models import Q #from django.db.models import Q
from imagekit.models import ImageSpecField from imagekit.models import ImageSpecField
@ -10,11 +12,9 @@ from soustredeni.models import Soustredeni
VZDY=0 VZDY=0
ORG=1 ORG=1
NIKDY=2 NIKDY=2
UCASTNIK=3
VIDITELNOST = ( VIDITELNOST = (
(VZDY, 'Vždy'), (VZDY, 'Vždy'),
(ORG, 'Organizátorům'), (ORG, 'Organizátorům'),
(UCASTNIK, 'Účastníkům a orgům'),
(NIKDY, 'Nikdy'), (NIKDY, 'Nikdy'),
) )
@ -56,7 +56,7 @@ class Obrazek(models.Model):
nazev = models.CharField('Název', max_length=50, blank=True, null=True) nazev = models.CharField('Název', max_length=50, blank=True, null=True)
popis = models.TextField('Popis', blank=True, null=True) popis = models.TextField('Popis', blank=True, null=True)
datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add=True) datum_vlozeni = models.DateTimeField('Datum vložení', auto_now_add=True)
galerie = models.ForeignKey('Galerie', blank=True, null=True, on_delete=models.CASCADE) galerie = models.ForeignKey('Galerie', blank=True, null=True, on_delete=models.SET_NULL)
poradi = models.IntegerField('Pořadí', blank=True, null=True) poradi = models.IntegerField('Pořadí', blank=True, null=True)
def __str__(self): def __str__(self):
@ -90,7 +90,7 @@ class Galerie(models.Model):
titulni_obrazek = models.ForeignKey(Obrazek, blank = True, null = True, related_name = "+", on_delete = models.SET_NULL) titulni_obrazek = models.ForeignKey(Obrazek, blank = True, null = True, related_name = "+", on_delete = models.SET_NULL)
zobrazit = models.IntegerField('Zobrazit?', default = ORG, choices = VIDITELNOST) zobrazit = models.IntegerField('Zobrazit?', default = ORG, choices = VIDITELNOST)
galerie_up = models.ForeignKey('Galerie', blank = True, null = True, galerie_up = models.ForeignKey('Galerie', blank = True, null = True,
on_delete=models.PROTECT) on_delete=models.SET_NULL)
soustredeni = models.ForeignKey(Soustredeni, blank = True, null = True, soustredeni = models.ForeignKey(Soustredeni, blank = True, null = True,
on_delete=models.PROTECT) on_delete=models.PROTECT)
poradi = models.IntegerField('Pořadí', blank = True, null = False, default = 0) poradi = models.IntegerField('Pořadí', blank = True, null = False, default = 0)
@ -100,3 +100,25 @@ class Galerie(models.Model):
class Meta: class Meta:
verbose_name = 'Galerie' verbose_name = 'Galerie'
verbose_name_plural = 'Galerie' verbose_name_plural = 'Galerie'
#def link_na_preview(self):
#"""Odkaz na galerii, používá se v admin rozhranní. """
#return '<a href="/fotogalerie/galerie/%s/">Preview</a>' % self.id
#link_na_preview.allow_tags = True
#link_na_preview.short_description = 'Zobrazit galerii'
#
#def je_publikovano(self):
#"""Vraci True, pokud je tato galerie publikovana. """
#if self.zobrazit == VZDY:
#return True
#if self.zobrazit == PODLE_CLANKU:
#for clanek in self.clanek_set.all():
#if clanek.je_publikovano():
#return True
#return False
#
#@staticmethod
#def publikovane_galerie():
#"""Vraci galerie, ktere uz maji byt publikovane."""
#clanky = Blog.models.Clanek.publikovane_clanky()
#return Galerie.objects.filter(Q(zobrazit=VZDY) | (Q(clanek__in=clanky) & Q(zobrazit=PODLE_CLANKU))).distinct()

View file

@ -1,191 +0,0 @@
@charset "utf-8"; /* vynuť utf-8 */
/* Galerie */
/* velká fotka */
/* zmenšování spolu s oknem prohlížeče */
.galerie .obrazek, .titulni_obrazek {
max-width: 100%;
height: auto;
width: auto\9; /* ie8 */
}
.predchozi_obrazek{
position: absolute;
z-index: 1;
width: 33%;
height: 100%;
left: 0;
top: 0;
}
.predchozi_obrazek:hover{
background-image: url("/static/galerie/prvky/predchozi.svg");
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
background-position: left center;
background-repeat: no-repeat;
}
.dalsi_obrazek{
position: absolute;
z-index: 1;
width: 33%;
height: 100%;
left: 67%;
top: 0;
}
.dalsi_obrazek:hover{
background-image: url("/static/galerie/prvky/dalsi.svg");
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
background-position: right center;
background-repeat: no-repeat;
}
.galerie {
position: relative;
text-align: center;
margin: 20px auto 0 auto;
}
.galerie h1 {
text-align: center;
}
.galerie_hlavicka {
margin: 30px auto 30px auto;
}
.popis {
margin: 10px 10px 30px 0px;
text-align: center;
}
#nahoru {
text-align: center;
}
/* titulní obrázek hlavní galerie soustředění */
.galerie_nahledy{
/*margin: 1em 0;*/
margin: auto;
padding: 10px;
text-align: center;
overflow: auto;
}
.galerie_nahledy img {
margin: 10px;
}
.galerie_nahledy div.navigace {
display: inline-block;
}
.galerie_nahled, .podgalerie_nahled { /* frame */
display: block;
position: relative;
float: left;
width: 200px;
height: 200px;
text-align: center;
border: solid;
border-width: 1px;
border-radius: 4px;
border-color: var(--svetla-oranzova);
background-color: var(--barva-pozadi);
white-space: nowrap;
margin: 10px;
font-weight: bold;
}
.galerie_nahled:hover, .podgalerie_nahled:hover {
background-color: var(--svetla-oranzova);
filter: drop-shadow(0px 5px 5px rgba(0, 0, 0, 0.4));
color: var(--tmava-oranzova);
}
.vystredeno{ /* helper */
display: inline-block;
height: 100%;
vertical-align: middle;
}
.galerie_nahled img {
vertical-align: middle;
max-height: 180px;
max-width: 180px;
}
.galerie_nahled div {
position: absolute;
bottom: 0px;
width: 100%;
text-align: center;
}
.podgalerie_nahled img {
margin-top: 20px;
margin-bottom: 15px;
max-height: 125px;
max-width: 167px;
}
.podgalerie_nahled .nazev_galerie {
position: absolute;
width: 100%;
top: 160px;
}
.podgalerie_nahled.mam-org-only, .podgalerie_nahled.mam-resitel-only {
margin: 10px;
padding: 0;
}
/* Odkazy na předchozí a následující podgalerii */
.galerie_predchozi_nasledujici {
overflow: auto;
margin: 10px auto 10px auto;
}
.galerie_predchozi_nasledujici .predchozi {
float: left;
}
.galerie_predchozi_nasledujici .nasledujici {
float: right;
}
/* posune kotvu obrázku v galerii o oranžový pruh dolu, aby se pod ním obrázek neschovával */
/* https://stackoverflow.com/questions/10732690/offsetting-an-html-anchor-to-adjust-for-fixed-header */
.kotva_obrazku {
position: absolute;
width: 0;
height: 55px; /* viz #title */
margin-top: -55px; /* viz #title */
}
@media(max-width: 860px) {
.kotva_obrazku {
height: 3em; /* #FIXME nemám páru, jak zjistit výšku toho elementu */
margin-top: -3em; /* #FIXME */
}
}
/* plus a minus tlacitka */
.mam-org-only-galerie {
background: var(--orgovska-svetla-fialova);
padding: 10px;
margin: 10px 10px 10px -20px;
border: #333 2px dashed;
float: left;
}
.mam-org-only-galerie a{
padding: 3px 5px;
margin: 5px;
border-radius: 20px;
background-color: var(--tmava-oranzova);;
color: var(--barva-pozadi);
float: left;
}

View file

@ -1,4 +1,4 @@
{% extends "galerie/base.html" %} {% extends "base.html" %}
{% block nadpis1a %} {% block nadpis1a %}
@ -46,7 +46,6 @@
{% block content %} {% block content %}
<div class="{% if obrazek.galerie.zobrazit == 1 or obrazek.galerie.zobrazit == 2 %}mam-org-only{% endif %}{% if obrazek.galerie.zobrazit == 3 %}mam-resitel-only{% endif %}">
<h2> <h2>
{% for g in cesta %} {% for g in cesta %}
@ -84,7 +83,7 @@
{# Popisek fotky #} {# Popisek fotky #}
<div class="popis"> <div class="popis">
{% if upravy_popisku %} {% if preview %}
<form action=".#nahoru" method="post" id="komentarform"> <form action=".#nahoru" method="post" id="komentarform">
{% csrf_token %} {% csrf_token %}
<table> <table>
@ -134,6 +133,4 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "galerie/base.html" %} {% extends "base.html" %}
{% block nadpis1a %} {% block nadpis1a %}
Galerie {{galerie.nazev}} Galerie {{galerie.nazev}}
@ -6,11 +6,8 @@ Galerie {{galerie.nazev}}
{% block content %} {% block content %}
{# FIXME: použít konstanty… #} {% if galerie.zobrazit > 0 %}
{% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %}
<div class="mam-org-only"> <div class="mam-org-only">
{% elif galerie.zobrazit == 3 %}
<div class="mam-resitel-only">
{% endif %} {% endif %}
<h2> <h2>
@ -48,43 +45,37 @@ Galerie {{galerie.nazev}}
{% if podgalerie %} {% if podgalerie %}
{% with 22 as max_delka_nazvu %} {% with 22 as max_delka_nazvu %}
<div class="galerie_nahledy"> <div class="galerie_nahledy">
{% for pgalerie in podgalerie %} {% for galerie in podgalerie %}
<a href="../{{pgalerie.pk}}" <a href="../{{galerie.pk}}"
{% if pgalerie.nazev|length > max_delka_nazvu %} {% if galerie.nazev|length > max_delka_nazvu %}
title="{{ pgalerie.nazev }}" title="{{ galerie.nazev }}"
{% endif %}
class="podgalerie_nahled">
{% if galerie.titulni_obrazek %}
{% with galerie.titulni_obrazek.obrazek_maly as obrazek %}
<img src="{{ obrazek.url }}"
/>
{% endwith %}
{% endif %} {% endif %}
class="podgalerie_nahled {% if pgalerie.zobrazit == 1 or pgalerie.zobrazit == 2 %}mam-org-only{% endif %}{% if pgalerie.zobrazit == 3 %}mam-resitel-only{% endif %}"> <div class="nazev_galerie">
{% if pgalerie.titulni_obrazek %} {{ galerie|truncatechars:max_delka_nazvu }}
{% with pgalerie.titulni_obrazek.obrazek_maly as obrazek %}
<img src="{{ obrazek.url }}"
/>
{% endwith %}
{% endif %}
<div class="nazev_galerie">
{{ pgalerie|truncatechars:max_delka_nazvu }}
</div>
</a>
{% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %}
{% if user.je_org %}
<div class="mam-org-only-galerie">
({{pgalerie.poradi}})
<span class="plus"><a href="plus/{{pgalerie.pk}}/">+</a></span>
<span class="minus"><a href="minus/{{pgalerie.pk}}/">-</a></span>
</div> </div>
{% endif %} </a>
{% endif %} {% if user.je_org and galerie.zobrazit > 0 %}
<div class="mam-org-only-galerie">
({{galerie.poradi}})
<span class="plus"><a href="plus/{{galerie.pk}}/">+</a></span>
<span class="minus"><a href="minus/{{galerie.pk}}/">-</a></span>
</div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endwith %} {% endwith %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if user.je_org %} {% if user.je_org and galerie.zobrazit > 0 %}
<div class="mam-org-only"> <div class="mam-org-only">
{% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %} <a href="./new">Vytvořit novou podgalerii </a>
<a href="./new">Vytvořit novou podgalerii</a>, <a href="{% url 'admin:galerie_galerie_change' galerie.pk %}">upravit galerii v adminu</a>
{% else %}
Jestli chceš změnit pořadí podgalerií nebo přidat novou, nastav zobrazení jen pro orgy v <a href="{% url 'admin:galerie_galerie_change' galerie.pk %}">adminu</a>.
{% endif %}
</div> </div>
{% endif %} {% endif %}
@ -129,10 +120,8 @@ Galerie {{galerie.nazev}}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if galerie.zobrazit == 1 or galerie.zobrazit == 2 %} {% if galerie.zobrazit > 0 %}
</div> {# mam-org-only #} </div> {# mam-org-only #}
{% elif galerie.zobrazit == 2 %}
</div> {# mam-resitel-only #}
{% endif %} {% endif %}
{% endblock content %} {% endblock content %}

View file

@ -1,4 +1,4 @@
{% extends "galerie/base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}

View file

@ -1,6 +0,0 @@
{% extends "base.html" %}
{% load static %}
{% block custom_css %}
<link href="{% static 'css/galerie.css' %}?version=2" rel="stylesheet">
{% endblock %}

View file

@ -1,5 +1,14 @@
"""
Soubor sloužící jako router, tj. zde se definují url adresy a na co ukazují:
- ``<int:pk>/`` :func:`~galerie.views.nahled`
- ``<int:pk>/<int:fotka>/`` :func:`~galerie.views.detail`
- ``<int:galerie>/new/`` :func:`~galerie.views.new_galerie`
- ``<int:galerie>/plus/<int:subgalerie>/`` :func:`~galerie.views.plus_galerie`
- ``<int:galerie>/minus/<int:subgalerie>/`` :func:`~galerie.views.minus_galerie`
"""
from django.urls import path from django.urls import path
from personalni.utils import org_required from seminar.utils import org_required
from . import views from . import views
urlpatterns = [ urlpatterns = [

View file

@ -1,6 +0,0 @@
from galerie.models import Galerie
def top_galerie(g: Galerie) -> Galerie:
while g.galerie_up is not None:
g = g.galerie_up
return g

View file

@ -1,41 +1,24 @@
# coding: utf-8
import random import random
from django.http import HttpResponse, Http404, HttpRequest from django.http import HttpResponse, Http404
from django.shortcuts import render, HttpResponseRedirect, get_object_or_404 from django.shortcuts import render, HttpResponseRedirect, get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from datetime import datetime from datetime import datetime
from galerie.utils import top_galerie from galerie.models import Obrazek, Galerie
from personalni.utils import resitel_uzivatele from seminar.models import Soustredeni
from galerie.models import Obrazek, Galerie, VZDY, ORG, UCASTNIK, NIKDY
from soustredeni.models import Soustredeni
from galerie.forms import KomentarForm, NewGalerieForm from galerie.forms import KomentarForm, NewGalerieForm
import logging def zobrazit(galerie, request):
logger = logging.getLogger(__name__) preview = False
if galerie.zobrazit >= 1:
def galerie_ke_zobrazeni(soustredeni: Soustredeni | None, request: HttpRequest) -> tuple[int]: if request.user.je_org:
if request.user.is_superuser: return (VZDY, ORG, UCASTNIK, NIKDY) preview = True;
if request.user.je_org: return (VZDY, ORG, UCASTNIK)
if request.user.is_anonymous: return (VZDY,)
if soustredeni is None: return (VZDY,)
if (resitel := resitel_uzivatele(request.user)) is not None:
if resitel.soustredeni_set.contains(soustredeni):
return (VZDY, UCASTNIK)
else: else:
return (VZDY,) raise Http404
logger.warning("Nepodařilo se zjistit, jaké galerie se mají zobrazit!") return preview
return (VZDY,)
def zobrazit(galerie: Galerie, request: HttpRequest) -> bool:
soustredeni = top_galerie(galerie).soustredeni
return galerie.zobrazit in galerie_ke_zobrazeni(soustredeni, request)
def dovolit_upravy_popisku(galerie: Galerie, request: HttpRequest) -> bool:
# FIXME: Dočasné: úpravy jen když je to v org-only stavu. (Odpovídá předchozímu chování)
return request.user.je_org and galerie.zobrazit in (ORG, NIKDY)
def cesta_od_korene(g): def cesta_od_korene(g):
@ -50,19 +33,19 @@ def cesta_od_korene(g):
def nahled(request, pk, soustredeni): def nahled(request, pk, soustredeni):
"""Zobrazeni nahledu vsech fotek ve skupine.""" """Zobrazeni nahledu vsech fotek ve skupine."""
galerie = get_object_or_404(Galerie, pk=pk) galerie = get_object_or_404(Galerie, pk=pk)
soustredeni = top_galerie(galerie).soustredeni
# FIXME: přepsat model a použít přímo dolů…
podgalerie = Galerie.objects.filter(galerie_up = galerie).order_by('poradi') podgalerie = Galerie.objects.filter(galerie_up = galerie).order_by('poradi')
podgalerie = podgalerie.filter(zobrazit__in=galerie_ke_zobrazeni(soustredeni, request)) if not request.user.je_org:
podgalerie = podgalerie.filter(zobrazit__lt=1)
obrazky = galerie.obrazek_set.all().order_by('poradi', 'nazev') obrazky = Obrazek.objects.filter(galerie = galerie).order_by('poradi', 'nazev')
ma_se_zobrazit = zobrazit(galerie, request) preview = zobrazit(galerie, request)
if not ma_se_zobrazit: raise Http404("Galerie sice existuje, ale my se tváříme, že ne :-D")
sourozenci = [] sourozenci = []
if galerie.galerie_up: if galerie.galerie_up:
sourozenci = galerie.galerie_up.galerie_set.filter(zobrazit__in=galerie_ke_zobrazeni(soustredeni, request)).order_by('poradi') sourozenci = galerie.galerie_up.galerie_set.all().order_by('poradi')
if not request.user.je_org:
sourozenci = sourozenci.filter(zobrazit__lt=1)
predchozi = None predchozi = None
nasledujici = None nasledujici = None
@ -81,6 +64,7 @@ def nahled(request, pk, soustredeni):
{'galerie' : galerie, {'galerie' : galerie,
'podgalerie' : podgalerie, 'podgalerie' : podgalerie,
'obrazky' : obrazky, 'obrazky' : obrazky,
'preview' : preview,
'cesta': cesta, 'cesta': cesta,
'sourozenci': sourozenci, 'sourozenci': sourozenci,
'predchozi': predchozi, 'predchozi': predchozi,
@ -96,41 +80,9 @@ def detail(request, pk, fotka, soustredeni):
NAHLEDU = 1 NAHLEDU = 1
galerie = get_object_or_404(Galerie, pk=pk) galerie = get_object_or_404(Galerie, pk=pk)
soustredeni = top_galerie(galerie).soustredeni preview = zobrazit(galerie, request)
ma_se_zobrazit = zobrazit(galerie, request)
if not ma_se_zobrazit: raise Http404("Obrázek neukážu!")
obrazek = get_object_or_404(Obrazek, pk=fotka) obrazek = get_object_or_404(Obrazek, pk=fotka)
# Pořadí není povinné. FIXME: `nazev` je zavádějící… Ale tohle je kanonické pořadí obrázků v galerii…
obrazky = galerie.obrazek_set.all().order_by('poradi', 'nazev') obrazky = galerie.obrazek_set.all().order_by('poradi', 'nazev')
obrazky = list(obrazky)
index_obrazku = obrazky.index(obrazek)
# Podle mě se nemůže stát, že by volání výš selhalo, kdyžtak shodí web. (původně to byl explicitně ošetřený stav dávající 404)
predchozi_obrazky = list(reversed(obrazky[:index_obrazku]))
nasledujici_obrazky = obrazky[index_obrazku+1:]
# Může jich být hodně…
predchozi_obrazky = predchozi_obrazky[:NAHLEDU]
nasledujici_obrazky = nasledujici_obrazky[:NAHLEDU]
# Předchozí obrázky chceme v normálním pořadí
predchozi_obrazky = list(reversed(predchozi_obrazky))
if galerie.galerie_up is not None:
sousedni_galerie = Galerie.objects.filter(galerie_up=galerie.galerie_up, zobrazit__in=galerie_ke_zobrazeni(soustredeni, request)).order_by('poradi')
sousedni_galerie = list(sousedni_galerie)
# Teoreticky se můžeme dívat na galerie.poradi, ale jednak už tenhle pattern stejně je výš a druhak je galerií málo, takže pomalost nevadí.
index_galerie = sousedni_galerie.index(galerie)
predchozi_galerie = sousedni_galerie[index_galerie-1] if index_galerie > 0 else None
nasledujici_galerie = sousedni_galerie[index_galerie+1] if index_galerie < len(sousedni_galerie) - 1 else None
else:
predchozi_galerie = None
nasledujici_galerie = None
# Pokud je obrázků dost, tak další galerii nepotřebujeme
# (jo, mohli jsme si ušetřit práci, ale takhle je kód imho přehlednější a za pár ušetřených dotazů do DB to nestojí)
if len(predchozi_obrazky) >= NAHLEDU:
predchozi_galerie = None
if len(nasledujici_obrazky) >= NAHLEDU:
nasledujici_galerie = None
# vytvoreni a obslouzeni formulare # vytvoreni a obslouzeni formulare
if request.method == 'POST': if request.method == 'POST':
@ -141,6 +93,49 @@ def detail(request, pk, fotka, soustredeni):
else: else:
form = KomentarForm({'komentar': obrazek.popis}) form = KomentarForm({'komentar': obrazek.popis})
# Poradi aktualniho obrazku v galerii/stitku.
for i in range(len(obrazky)):
if obrazky[i] == obrazek:
poradi = i
break
else:
# Obrazek neni v galerii/stitku.
raise Http404
# Nacteni okolnich obrazku a galerii
# TODO vyjmout zjisteni predchozich a nasledujicich galerii
# a udelat z toho funkci, ktera se pouzije u nahledu
predchozi_galerie = None
nasledujici_galerie = None
obrazky_dalsi = obrazky[poradi+1:poradi+NAHLEDU+1]
if (poradi+1) > NAHLEDU:
obrazky_predchozi = obrazky[poradi-NAHLEDU:poradi]
else:
obrazky_predchozi = obrazky[0:poradi]
if galerie.poradi > 1:
predchozi_galerie = Galerie.objects.\
filter(galerie_up=galerie.galerie_up).\
filter(poradi=(galerie.poradi-1))
if predchozi_galerie:
predchozi_galerie = predchozi_galerie[0]
else:
predchozi_galerie = None
if (poradi+1) == len(obrazky): # Tohle je poslední obrázek
if (galerie.poradi is not None
and galerie.galerie_up is not None):
nasledujici_galerie = Galerie.objects.\
filter(galerie_up=galerie.galerie_up).\
filter(poradi=(galerie.poradi+1))
else:
nasledujici_galerie = None
if nasledujici_galerie:
nasledujici_galerie = nasledujici_galerie[0]
else:
nasledujici_galerie = None
# Preskalovani obrazku do vybraneho prostoru. # Preskalovani obrazku do vybraneho prostoru.
vyska = obrazek.obrazek_stredni.height vyska = obrazek.obrazek_stredni.height
sirka = obrazek.obrazek_stredni.width sirka = obrazek.obrazek_stredni.width
@ -158,9 +153,9 @@ def detail(request, pk, fotka, soustredeni):
'obrazek' : obrazek, 'obrazek' : obrazek,
'vyska' : vyska, 'vyska' : vyska,
'sirka' : sirka, 'sirka' : sirka,
'obrazky_predchozi' : predchozi_obrazky, 'obrazky_predchozi' : obrazky_predchozi,
'obrazky_dalsi' : nasledujici_obrazky, 'obrazky_dalsi' : obrazky_dalsi,
'upravy_popisku' : dovolit_upravy_popisku(galerie, request), 'preview' : preview,
'form' : form, 'form' : form,
'cesta': cesta_od_korene(galerie), 'cesta': cesta_od_korene(galerie),
}) })
@ -186,7 +181,7 @@ def new_galerie(request, galerie, soustredeni):
gal = Galerie() gal = Galerie()
gal.nazev = form.cleaned_data['nazev'] gal.nazev = form.cleaned_data['nazev']
#gal.popis = form.cleaned_data['popis'] # popis nepouzivame #gal.popis = form.cleaned_data['popis'] # popis nepouzivame
gal.zobrazit = ORG # galerie je v procesu vytvareni gal.zobrazit = 1 # galerie je v procesu vytvareni
''' pokud je to podgalerie pridej nadrazenou galerii ''' pokud je to podgalerie pridej nadrazenou galerii
a nadrazene soustredeni nechej volne, a nadrazene soustredeni nechej volne,
pokud je to hlavni galerie, tak nadrazena galerie neexistuje, pokud je to hlavni galerie, tak nadrazena galerie neexistuje,

View file

@ -1,3 +1,14 @@
"""
Soubor sloužící k definici toho, co bude v adminu. Většinou pouhým zavoláním
funkce :func:`django.contrib.admin.site.register`, v případě, že chceme něco
upravit, tak jako třída rozšiřující :class:`django.contrib.admin.ModelAdmin`
s dekorátorem :func:`django.contrib.admin.register`.
Zde se definuje admin pro:
- :class:`~header_fotky.models.FotkaHeader`
- :class:`~header_fotky.models.FotkaUrlVazba`
"""
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import ModelAdmin from django.contrib.admin import ModelAdmin
import header_fotky.models as m import header_fotky.models as m

View file

@ -1,3 +1,6 @@
"""
Soubor sloužící k pojmenování a jiným nastavením djangovské aplikace.
"""
from django.apps import AppConfig from django.apps import AppConfig

View file

@ -1,3 +1,17 @@
"""
Tento soubor slouží k definici databázového modelu.
Třídy rozšiřují většinou :class:`django.db.models.Model` a jejich atributy jsou
většinou sloupce v databázi (tj. nastaví se na hodnotu něčeho z :mod:`django.db.models`).
Na výběr jsou:
- :class:`django.db.models.TextField`
- :class:`django.db.models.ForeignKey`
- :class:`django.db.models.DateField`
- :class:`django.db.models.DateTimeField`
- :class:`django.db.models.ImageField`
- :class:`django.db.models.CharField`
"""
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone

View file

@ -1,4 +1,3 @@
FIXME přepsat do rst, přidat i další věci a případně přesunout na wiki
Přidání obrázků do odměn: Přidání obrázků do odměn:
admin -> flatpage odměn -> ikona přidat obrázek admin -> flatpage odměn -> ikona přidat obrázek
záložka odeslat, vybrat obrázek, odeslat záložka odeslat, vybrat obrázek, odeslat

11
korektury/TODO Normal file
View file

@ -0,0 +1,11 @@
- korektura potrebuje reakci
+ komentáře fixně na username
- používat skutečné jméno?
- vyžádat pozornost autora obsahu
- zvednout upload limit na 5MB
- sbalit a rozbalit korekturu
- nahrávání jiných věcí než PDF - kontrolovat?
- stylování
- seznam PDF - co zobrazovat?

View file

@ -1,10 +1,21 @@
"""
Soubor sloužící k definici toho, co bude v adminu. Většinou pouhým zavoláním
funkce :func:`django.contrib.admin.site.register`, v případě, že chceme něco
upravit, tak jako třída rozšiřující :class:`django.contrib.admin.ModelAdmin`
s dekorátorem :func:`django.contrib.admin.register`.
Zde se definuje admin pro:
- :class:`korektury.models.KorekturovanePDF`
"""
from django.contrib import admin from django.contrib import admin
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
from korektury.models import KorekturovanePDF, Oprava, KorekturaTag from korektury.models import KorekturovanePDF
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.urls import reverse from django.urls import reverse
# Register your models here.
class KorekturovanePDFAdmin(VersionAdmin): class KorekturovanePDFAdmin(VersionAdmin):
""" """
nastaví čas vložení (:attr:`~koretkury.models.KorekturovanePDF.cas`) a počet nastaví čas vložení (:attr:`~koretkury.models.KorekturovanePDF.cas`) a počet
@ -25,13 +36,12 @@ class KorekturovanePDFAdmin(VersionAdmin):
fieldsets = [ fieldsets = [
(None, (None,
{'fields': {'fields':
['pdf', 'cas', 'stran', 'nazev', 'orgove', 'komentar', 'poslat_mail']}), ['pdf', 'cas', 'org', 'stran', 'nazev', 'komentar', 'poslat_mail']}),
# (u'PDF', {'fields': ['pdf']}), # (u'PDF', {'fields': ['pdf']}),
] ]
list_display = ['nazev', 'cas', 'stran'] list_display = ['nazev', 'cas', 'stran', 'org']
list_filter = [] list_filter = []
search_fields = [] search_fields = []
autocomplete_fields = ['orgove']
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" """
@ -62,11 +72,3 @@ Korekturovátko
).send() ).send()
admin.site.register(KorekturovanePDF, KorekturovanePDFAdmin) admin.site.register(KorekturovanePDF, KorekturovanePDFAdmin)
class OpravaAdmin(admin.ModelAdmin):
model = Oprava
filter_horizontal = ("informovani_orgove", "tagy",)
admin.site.register(Oprava, OpravaAdmin)
admin.site.register(KorekturaTag)

View file

@ -1,7 +0,0 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'korektury.api'
label = 'korektury_api' # Protože jedno api už máme.

View file

@ -1,11 +0,0 @@
from django.urls import path
from personalni.utils import org_required
from . import views
urlpatterns = [
path('<int:pdf_id>/stav', org_required(views.korektury_stav_view), name='korektury_api_pdf_stav'),
path('oprava/stav', org_required(views.oprava_stav_view), name='korektury_api_oprava_stav'),
path('<int:pdf_id>/opravy_a_komentare', org_required(views.opravy_a_komentare_view), name='korektury_api_opravy_a_komentare'),
path('oprava/smaz', org_required(views.oprava_smaz_view), name='korektury_api_oprava_smaz'),
path('komentar/smaz', org_required(views.komentar_smaz_view), name='korektury_api_komentar_smaz'),
]

View file

@ -1,134 +0,0 @@
from http import HTTPStatus
from django.http import JsonResponse, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.html import linebreaks
from rest_framework import serializers
from korektury.utils import send_email_notification_komentar
from korektury.models import Oprava, KorekturovanePDF, Komentar, KorekturaTag
from personalni.models import Organizator
def korektury_stav_view(request, pdf_id: int, **kwargs):
q = request.POST
pdf = get_object_or_404(KorekturovanePDF, id=pdf_id)
status = q.get('state')
if status is not None:
assert status in KorekturovanePDF.STATUS.values
pdf.status = status
pdf.save()
return JsonResponse({'status': pdf.status})
def oprava_stav_view(request, **kwargs):
q = request.POST
op_id_str = q.get('id')
assert op_id_str is not None
op_id = int(op_id_str)
op = get_object_or_404(Oprava, id=op_id)
status = q.get('action')
if status is not None:
assert status in Oprava.STATUS.values
op.status = status
op.save()
return JsonResponse({'status': op.status})
def oprava_smaz_view(request, **kwargs):
q = request.POST
op_id_str = q.get('oprava_id')
assert op_id_str is not None
op_id = int(op_id_str)
oprava = get_object_or_404(Oprava, id=op_id)
oprava.delete()
return HttpResponse(status=HTTPStatus.NO_CONTENT)
def komentar_smaz_view(request, **kwargs):
q = request.POST
kom_id_str = q.get('komentar_id')
assert kom_id_str is not None
kom_id = int(kom_id_str)
komentar = get_object_or_404(Komentar, id=kom_id)
komentar.delete()
return HttpResponse(status=HTTPStatus.NO_CONTENT)
class KomentarSerializer(serializers.ModelSerializer):
class Meta:
model = Komentar
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["autor"] = str(instance.autor)
ret["text"] = linebreaks(ret["text"], autoescape=True) # Autora není třeba escapovat, ten se vkládá jako text.
return ret
class KorekturaTagSerializer(serializers.ModelSerializer):
class Meta:
model = KorekturaTag
fields = '__all__'
class OpravaSerializer(serializers.ModelSerializer):
class Meta:
model = Oprava
fields = '__all__'
def to_representation(self, instance):
ret = super().to_representation(instance)
ret["komentare"] = [KomentarSerializer(komentar).data for komentar in instance.komentar_set.all()]
ret["tagy"] = [KorekturaTagSerializer(tag).data for tag in instance.tagy.all()]
return ret
# komentar_set = serializers.ListField(child=KomentarSerializer())
def opravy_a_komentare_view(request, pdf_id: int, **kwargs):
if request.method == 'POST':
q = request.POST
x = int(q.get('x'))
y = int(q.get('y'))
img_id = int(q.get('img_id'))
oprava_id = int(q.get('oprava_id'))
komentar_id = int(q.get('komentar_id'))
text = q.get('text')
# prirazeni autora podle prihlaseni
autor_user = request.user
# pokud existuje ucet (user), ale neni to organizator = 403
autor = Organizator.objects.filter(osoba__user=autor_user).first()
if komentar_id != -1:
komentar = get_object_or_404(Komentar, id=komentar_id)
if komentar.text != text:
komentar.text = text
komentar.autor = autor
komentar.save()
oprava = komentar.oprava
else:
if oprava_id != -1:
oprava = get_object_or_404(Oprava, id=oprava_id)
else:
pdf = get_object_or_404(KorekturovanePDF, id=pdf_id)
oprava = Oprava.objects.create(
pdf=pdf,
strana=img_id,
x=x,
y=y,
)
Komentar.objects.create(oprava=oprava, autor=autor, text=text)
send_email_notification_komentar(oprava, autor, request)
tagy_raw = q.get('tagy')
if tagy_raw is not None:
oprava.tagy.clear()
if tagy_raw != "":
tagy = list(map(int, tagy_raw.split(",")))
oprava.tagy.add(*KorekturaTag.objects.filter(id__in=tagy))
opravy = Oprava.objects.filter(pdf=pdf_id).all()
# Serializovat list je prý security vulnerability, tedy je přidán slovník pro bezpečnost
return JsonResponse({"context": [OpravaSerializer(oprava).data for oprava in opravy]})

24
korektury/forms.py Normal file
View file

@ -0,0 +1,24 @@
"""
Formuláře (:class:`django.forms.Form`) umožňují jednoduchou tvorbu formulářů,
které lze pak jednoduše dát do frontendu i zpracovat na backendu.
Pro přidání políčka do formuláře je potřeba
- mít v modelu tu položku, kterou chci upravovat
- přidat do views (prihlaskaView, resitelEditView)
- přidat do forms
- includovat do html
"""
from django import forms
class OpravaForm(forms.Form):
""" formulář k přidání opravy (:class:`korektury.models.Oprava`) """
text = forms.CharField(max_length=256)
autor = forms.CharField(max_length=20)
x = forms.IntegerField()
y = forms.IntegerField()
scroll = forms.CharField(max_length=256)
pdf = forms.CharField(max_length=256)
img_id = forms.CharField(max_length=256)
id = forms.CharField(max_length=256)
action = forms.CharField(max_length=256)

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-30 21:40 # Generated by Django 1.11.20 on 2019-04-30 21:40
from __future__ import unicode_literals from __future__ import unicode_literals

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.21 on 2019-06-10 21:58 # Generated by Django 1.11.21 on 2019-06-10 21:58
from __future__ import unicode_literals from __future__ import unicode_literals

View file

@ -1,41 +0,0 @@
# Generated by Django 4.2.13 on 2024-06-11 23:53
from django.db import migrations, models
def pridej_orgy(apps, schema_editor):
PDF = apps.get_model('korektury', 'KorekturovanePDF')
for pdf in PDF.objects.all(): # Tohle by asi mělo jít udělat pomocí update, ale moc práce a rychlé hledání taky nepomohlo.
if pdf.org is not None: pdf.orgove.add(pdf.org)
pdf.save() # ig?
def vyber_orga(apps, schema_editor):
PDF = apps.get_model('korektury', 'KorekturovanePDF')
for pdf in PDF.objects.all():
orgove = pdf.orgove.all()
if len(orgove) > 1:
raise migrations.exceptions.IrreversibleError(f'PDF {pdf.id} má víc než jednoho zodpovědného orga, nejde odmigrovat na verzi, která umí jen jednoho.')
if len(orgove) == 0:
pdf.org = None
else:
pdf.org = orgove[0]
pdf.save()
class Migration(migrations.Migration):
dependencies = [
('personalni', '0011_osloveni_vsechny_choices'),
('korektury', '0023_personalni_post_migrate'),
]
operations = [
migrations.AddField(
model_name='korekturovanepdf',
name='orgove',
field=models.ManyToManyField(blank=True, default=None, help_text='Zodpovědní organizátoři za obsah (chodí jim maily o nových korekturách)', to='personalni.organizator'),
),
migrations.RunPython(pridej_orgy, vyber_orga),
migrations.RemoveField(
model_name='korekturovanepdf',
name='org',
),
]

View file

@ -1,45 +0,0 @@
# Generated by Django 4.2.16 on 2024-12-12 10:25
from django.db import migrations
import datetime
from django.utils import timezone
def oprava2komentar(apps, schema_editor):
Oprava = apps.get_model('korektury', 'Oprava')
Komentar = apps.get_model('korektury', 'Komentar')
for o in Oprava.objects.all():
Komentar.objects.create(oprava=o, text=o.text, autor=o.autor, cas=timezone.make_aware(datetime.datetime.fromtimestamp(0)))
def komentar2oprava(apps, schema_editor):
Oprava = apps.get_model('korektury', 'Oprava')
Komentar = apps.get_model('korektury', 'Komentar')
for o in Oprava.objects.all():
k = Komentar.objects.filter(oprava=o).first()
o.text = k.text
o.autor = k.autor
o.save()
k.delete()
class Migration(migrations.Migration):
dependencies = [
('korektury', '0024_vic_orgu_k_pdf'),
]
operations = [
migrations.RunPython(oprava2komentar, komentar2oprava),
migrations.RemoveField(
model_name='oprava',
name='autor',
),
migrations.RemoveField(
model_name='oprava',
name='text',
),
]

View file

@ -1,29 +0,0 @@
# Generated by Django 4.2.16 on 2025-02-11 14:28
from django.db import migrations, models
def pridani_orgu(apps, _schema_editor):
Komentar = apps.get_model('korektury','Komentar')
for komentar in Komentar.objects.all():
org = komentar.autor
if org is not None:
# Tohle jde asi udělat lépe než .all(…). Ale nejhorší na tom je, že .add(…) funguje jinak tady v migracích.
if org not in komentar.oprava.informovani_orgove.all():
komentar.oprava.informovani_orgove.add(org)
class Migration(migrations.Migration):
dependencies = [
('personalni', '0019_rename_upozorneni_resitel_upozornovat_na_opravy_reseni'),
('korektury', '0025_remove_oprava_autor_remove_oprava_text'),
]
operations = [
migrations.AddField(
model_name='oprava',
name='informovani_orgove',
field=models.ManyToManyField(blank=True, default=None, help_text='Orgové informovaní při přidání komentáře ke korektuře', related_name='informovan_o_opravach', to='personalni.organizator', verbose_name='Informovaní organizátoři'),
),
migrations.RunPython(pridani_orgu, migrations.RunPython.noop),
]

View file

@ -1,27 +0,0 @@
# Generated by Django 4.2.16 on 2025-02-11 16:07
import colorfield.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('korektury', '0026_oprava_informovani_orgove'),
]
operations = [
migrations.CreateModel(
name='KorekturaTag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nazev', models.CharField(help_text='Název daného tagu, <20 znaků', max_length=20, verbose_name='název tagu')),
('barva', colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=None, verbose_name='barva daného tagu')),
],
),
migrations.AddField(
model_name='oprava',
name='tagy',
field=models.ManyToManyField(blank=True, default=None, to='korektury.korekturatag'),
),
]

View file

@ -1,22 +0,0 @@
# Generated by Django 4.2.16 on 2025-03-19 18:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('korektury', '0027_korekturatag_oprava_tagy'),
]
operations = [
migrations.AlterModelOptions(
name='korekturovanepdf',
options={'ordering': ['-cas'], 'verbose_name': 'PDF ke korekturování', 'verbose_name_plural': 'PDFka ke korekturování'},
),
migrations.AlterField(
model_name='korekturovanepdf',
name='nazev',
field=models.CharField(help_text='Název (např. „42.6 | analýza v4“ nebo „propagace | letáček v0“) korekturovaného PDF, v seznamu se seskupují podle textu do první mezery, tedy zde „42.6“ respektive „propagace“.', max_length=50, verbose_name='název PDF'),
),
]

View file

@ -1,9 +1,19 @@
"""
Tento soubor slouží k definici databázového modelu.
Třídy rozšiřují většinou :class:`django.db.models.Model` a jejich atributy jsou
většinou sloupce v databázi (tj. nastaví se na hodnotu něčeho z :mod:`django.db.models`).
Na výběr jsou:
- :class:`django.db.models.TextField`
- :class:`django.db.models.ForeignKey`
- :class:`django.db.models.DateField`
- :class:`django.db.models.DateTimeField`
- :class:`django.db.models.ImageField`
- :class:`django.db.models.CharField`
"""
import os import os
from colorfield.fields import ColorField
from django.db import models from django.db import models
from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -30,38 +40,42 @@ def generate_filename(self, filename):
clean) clean)
return os.path.join(settings.KOREKTURY_PDF_DIR, fname) return os.path.join(settings.KOREKTURY_PDF_DIR, fname)
#@reversion.register(ignore_duplicates=True) #@reversion.register(ignore_duplicates=True)
class KorekturovanePDF(models.Model): class KorekturovanePDF(models.Model):
class Meta: class Meta:
ordering = ['-cas'] ordering = ['-cas']
db_table = 'korekturovane_cislo' db_table = 'korekturovane_cislo'
verbose_name = 'PDF ke korekturování' verbose_name = u'PDF k opravám'
verbose_name_plural = 'PDFka ke korekturování' verbose_name_plural = u'PDF k opravám'
#Interní ID #Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
cas = models.DateTimeField(u'čas vložení PDF',default=timezone.now,help_text = 'Čas vložení PDF') cas = models.DateTimeField(u'čas vložení PDF',default=timezone.now,help_text = 'Čas vložení PDF')
nazev = models.CharField(u'název PDF',blank = False,max_length=50, help_text='Název (např. „42.6 | analýza v4“ nebo „propagace | letáček v0“) korekturovaného PDF, v seznamu se seskupují podle textu do první mezery, tedy zde „42.6“ respektive „propagace“.') nazev = models.CharField(u'název PDF',blank = False,max_length=50, help_text='Název (např. `22.1 | analyza v4` nebo `propagace | letacek v0`) korekturovaného PDF')
komentar = models.TextField(u'komentář k PDF',blank = True, help_text='Komentář ke korekturovanému PDF (např. na co se zaměřit)') komentar = models.TextField(u'komentář k PDF',blank = True, help_text='Komentář ke korekturovanému PDF (např. na co se zaměřit)')
pdf = models.FileField(u'PDF', upload_to = generate_filename) pdf = models.FileField(u'PDF', upload_to = generate_filename)
orgove = models.ManyToManyField(Organizator, blank=True, org = models.ForeignKey(Organizator, blank=True,
help_text='Zodpovědní organizátoři za obsah (chodí jim maily o nových korekturách)', help_text='Zodpovědný organizátor za obsah',
default=None) null=True, default=None, on_delete=models.SET_NULL)
stran = models.IntegerField(u'počet stran', help_text='Počet stran PDF', stran = models.IntegerField(u'počet stran', help_text='Počet stran PDF',
default=0) default=0)
STATUS_PRIDAVANI = 'pridavani'
class STATUS(models.TextChoices): STATUS_ZANASENI = 'zanaseni'
PRIDAVANI = 'pridavani', 'Přidávání korektur' STATUS_ZASTARALE = 'zastarale'
ZANASENI = 'zanaseni', 'Korektury jsou zanášeny' STATUS_CHOICES = (
ZASTARALE = 'zastarale', 'Stará verze, nekorigovat' (STATUS_PRIDAVANI, u'Přidávání korektur'),
(STATUS_ZANASENI, u'Korektury jsou zanášeny'),
status = models.CharField(u'stav PDF',max_length=16, choices=STATUS.choices, blank=False, default = STATUS.PRIDAVANI) (STATUS_ZASTARALE, u'Stará verze, nekorigovat'),
)
status = models.CharField(u'stav PDF',max_length=16, choices=STATUS_CHOICES, blank=False,
default = STATUS_PRIDAVANI)
poslat_mail = models.BooleanField('Poslat mail o novém PDF', default=True, poslat_mail = models.BooleanField('Poslat mail o novém PDF', default=True,
help_text='Určuje, zda se má o nově nahraném PDF poslat e-mail do mam-org. Při upravování existujícího souboru už nemá žádný vliv.', help_text='Určuje, zda se má o nově nahraném PDF poslat e-mail do mam-org. Při upravování existujícího souboru už nemá žádný vliv.',
@ -130,17 +144,6 @@ class KorekturovanePDF(models.Model):
except IndexError: except IndexError:
return self.nazev return self.nazev
def get_absolute_url(self):
return reverse('korektury', kwargs={'pdf': self.id})
class KorekturaTag(models.Model):
nazev = models.CharField("název tagu", blank = False, max_length=20, help_text="Název daného tagu, <20 znaků")
barva = ColorField("barva daného tagu", default="#FFFFFF")
def __str__(self):
return self.nazev
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Oprava(models.Model): class Oprava(models.Model):
@ -160,22 +163,32 @@ class Oprava(models.Model):
x = models.IntegerField(u'x-ová souřadnice bugu') x = models.IntegerField(u'x-ová souřadnice bugu')
y = models.IntegerField(u'y-ová souřadnice bugu') y = models.IntegerField(u'y-ová souřadnice bugu')
class STATUS(models.TextChoices): STATUS_K_OPRAVE = 'k_oprave'
K_OPRAVE = 'k_oprave', 'K opravě' STATUS_OPRAVENO = 'opraveno'
OPRAVENO = 'opraveno', 'Opraveno' STATUS_NENI_CHYBA = 'neni_chyba'
NENI_CHYBA = 'neni_chyba', 'Není chyba' STATUS_K_ZANESENI = 'k_zaneseni'
K_ZANESENI = 'k_zaneseni', 'K zanesení do TeXu' STATUS_CHOICES = (
(STATUS_K_OPRAVE, u'K opravě'),
(STATUS_OPRAVENO, u'Opraveno'),
(STATUS_NENI_CHYBA, u'Není chyba'),
(STATUS_K_ZANESENI, u'K zanesení do TeXu'),
)
status = models.CharField(u'stav opravy',max_length=16, choices=STATUS_CHOICES, blank=False,
default = STATUS_K_OPRAVE)
status = models.CharField(u'stav opravy',max_length=16, choices=STATUS.choices, blank=False, default = STATUS.K_OPRAVE) autor = models.ForeignKey(Organizator, blank = True,
help_text='Autor opravy',
null = True, on_delete=models.SET_NULL)
informovani_orgove = models.ManyToManyField( text = models.TextField(u'text opravy',blank = True, help_text='Text opravy')
Organizator, blank=True, default=None,
verbose_name='Informovaní organizátoři', # def __init__(self,dictionary):
help_text="Orgové informovaní při přidání komentáře ke korektuře", # for k,v in dictionary.items():
related_name='informovan_o_opravach', # setattr(self,k,v)
)
def __str__(self):
return '{} od {}: {}'.format(self.status,self.autor,self.text)
tagy = models.ManyToManyField(KorekturaTag, blank=True, default=None,)
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
@ -201,7 +214,5 @@ class Komentar(models.Model):
def __str__(self): def __str__(self):
return '{} od {}: {}'.format(self.cas,self.autor,self.text) return '{} od {}: {}'.format(self.cas,self.autor,self.text)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.autor is not None:
self.oprava.informovani_orgove.add(self.autor)

Binary file not shown.

Before

(image error) Size: 1.1 KiB

After

(image error) Size: 697 B

Binary file not shown.

Before

(image error) Size: 1.4 KiB

After

(image error) Size: 717 B

Binary file not shown.

Before

(image error) Size: 307 B

After

(image error) Size: 270 B

View file

@ -1,10 +0,0 @@
<svg id="icon-reload" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 120 120" xml:space="preserve">
<g>
<path d="M60,95.5c-19.575,0-35.5-15.926-35.5-35.5c0-19.575,15.925-35.5,35.5-35.5c13.62,0,25.467,7.714,31.418,19h22.627
C106.984,20.347,85.462,3.5,60,3.5C28.796,3.5,3.5,28.796,3.5,60c0,31.203,25.296,56.5,56.5,56.5
c16.264,0,30.911-6.882,41.221-17.88L85.889,84.255C79.406,91.168,70.201,95.5,60,95.5z"/>
</g>
<line fill="none" x1="120" y1="0" x2="120" y2="45.336"/>
<line fill="none" x1="91.418" y1="43.5" x2="114.045" y2="43.5"/>
<polygon points="120,21.832 119.992,68.842 74.827,55.811 "/>
</svg>

Before

(image error) Size: 712 B

Binary file not shown.

Before

(image error) Size: 1.1 KiB

After

(image error) Size: 702 B

Binary file not shown.

Before

(image error) Size: 1.1 KiB

After

(image error) Size: 617 B

View file

@ -1,220 +1,136 @@
.textzanaseni { display:none; } body,
.textzastarale { display:none; } .adding{
#prekomentar, #prekorektura, #prepointer { display: none; } background: #f3f3f3;
color: black;
body {
&[data-stav_pdf="pridavani"] {
background: #f3f3f3;
}
&[data-stav_pdf="zanaseni"] {
background: yellow;
.textzanaseni { display: unset; }
}
&[data-stav_pdf="zastarale"] {
background: red;
.textzastarale { display: unset; }
}
} }
.comitting
#sbal-korektury, #rozbal-korektury { {
float: right; background: yellow;
margin-left: 4pt; }
.deprecated {
background: red;
} }
img{background:white;} img{background:white;}
/* Barvy korektur */ /* Barvy korektur */
[data-stav_korektury="k_oprave"] { .k_oprave {
--rgb: 255, 0, 0; --rgb: 255, 0, 0;
[value="k_oprave"] { display: none }
.komentovat_disabled { display: none }
} }
[data-stav_korektury="opraveno"] { .opraveno {
--rgb: 0, 0, 255; --rgb: 0, 0, 255;
[value="opraveno"] { display: none }
.komentovat { display: none }
} }
[data-stav_korektury="neni_chyba"] { .neni_chyba {
--rgb: 128, 128, 128; --rgb: 128, 128, 128;
[value="neni_chyba"] { display: none }
.komentovat { display: none }
} }
[data-stav_korektury="k_zaneseni"] { .k_zaneseni {
--rgb: 0, 255, 0; --rgb: 0, 255, 0;
[value="k_zaneseni"] { display: none }
.komentovat { display: none }
} }
/* Skrývání korektur */ .pointer-hi,
[data-korektura_sbalena="true"] {
.korektura-telo { display: none; }
.korektura-tlacitka { display: none; }
.sbal-rozbal-img { transform: rotate(180deg); }
}
/* Skrývání komentářů */
[data-komentar_sbalen="true"] {
.sbal-rozbal-img { transform: rotate(180deg); }
.uprav-komentar { display: none; }
.komtext { display: none; }
}
/* Čára od textu k místu korektury */
.pointer{ .pointer{
position:absolute; position:absolute;
border-bottom-left-radius: 10px; /*border-bottom-left-radius: 10px; */
border-left: 1px solid rgb(var(--rgb),var(--alpha)); border-left: 2px solid yellow;
border-bottom: 1px solid rgb(var(--rgb),var(--alpha)); border-bottom: 2px solid yellow;
pointer-events: none; border-color: rgb(var(--rgb),var(--alpha));
--alpha: 0.35;
/* Zvýraznění čáry při najetí na korekturu */
&[data-hover="true"] {
border-width: 3px;
--alpha: 1;
}
} }
/* Korektura samotná */ .pointer {
.korektura { border-width: 1px;
--alpha: 0.35;
}
.pointer-hi {
border-width: 3px;
--alpha: 1;
}
.box:hover{
border-width:3px;
margin: 0px;
}
.box {
margin: 1px; margin: 1px;
background-color: white; background-color: white;
width: 300px; width:300px;
/*position:absolute;*/
padding: 3px; padding: 3px;
border: 2px solid rgb(var(--rgb)); border: 2px solid black;
border-radius: 10px; border-radius: 10px;
position: absolute; border-color: rgb(var(--rgb));
&:hover {
border-width:3px;
margin: 0;
}
button, img {
border: 1px solid white;
background-color:transparent;
margin:0;
padding: 1px;
&:hover {
border: 1px solid black;
}
}
button img { pointer-events: none; }
.hlavicka-komentare {
overflow: auto;
}
.autor {
font-weight: bold;
float: left;
margin-top: 3px;
}
.float-right{
float:right;
}
} }
form { form {
display:inline; display:inline;
} }
/* Zobrazované PDF */ .float-right{
.imgdiv { float:right;
position:relative;
left:0;
top:0;
} }
/* Přidávání korektury / úprava komentáře */ .imgdiv {
#korekturovaci-formular-div { position:relative;
left:0px;
top:0px;
}
#commform-div {
display: none;
position: absolute; position: absolute;
background-color: white; background-color: white;
border: 1px solid;
padding: 3px; padding: 3px;
/*
width: 310;
height: 220;
*/
z-index: 10; z-index: 10;
border: 4px solid red; border: 4px solid red;
border-radius: 10px; border-radius: 10px;
background-color: white;
opacity: 80%; opacity: 80%;
} }
.close-button{
.korektury-tag { background-color: yellow;
border-radius: 5px;
margin: 2px;
padding: 2px;
&[data-vybran="false"] { background: unset !important; }
/*&[data-vybran="true"] { border-color: unset !important; }*/
}
/* Šipky na posouvání korektur */
#korektury-sipky {
position: fixed;
bottom: 5px;
left: 5px;
opacity: 50%;
button, img {
border: 1px solid white;
background-color:transparent;
margin:0;
padding: 1px;
border-radius: 5px;
&:hover {
border: 1px solid black;
}
}
button img { pointer-events: none; }
#predchozi-korektura, #dalsi-korektura {
background-color: #EEEEEE;
}
#predchozi-korektura-k-oprave, #dalsi-korektura-k-oprave {
background-color: #FF0000;
}
#predchozi-korektura-k-zaneseni, #dalsi-korektura-k-zaneseni {
background-color: #00FF00;
}
/* Tlačítko na aktualizaci */
#korektury-aktualizace {
background-color: #e84e10;
}
} }
/**** ROZLIŠENÍ MEZI LOKÁLNÍM, TESTOVACÍM A PRODUKČNÍM WEBEM ****/ .box button,
body.localweb, body.testweb, body.suprodweb { .box img,
&:before, &:after { .box-done button,
content: ""; .box-done img,
position: fixed; .box-ready button,
width: 20px; .box-ready img,
height: 100%; .box-wontfix button,
top: 0; .box-wontfix img{
z-index: -1000; border: 1px solid white;
} background-color:transparent;
margin:0;
&:before { left: 0; } padding: 1px;
&:after { right: 0; } }
.box button:hover,
.box img:hover,
.box-done img:hover,
.box-done button:hover,
.box-ready img:hover,
.box-ready button:hover,
.box-wontfix img:hover,
.box-wontfix button:hover{
border: 1px solid black;
}
.comment hr {
height: 0px;
}
.corr-header {
overflow: auto;
}
.author {
font-weight: bold;
float: left;
margin-top: 3px;
} }
body.localweb { &:before, &:after { background: greenyellow; } }
body.testweb { &:before, &:after { background: darkorange; } }
body.suprodweb { &:before, &:after { background: red; } }
/****************************************************************/

View file

@ -0,0 +1,283 @@
function place_comments_one_div(img_id, comments)
{
var img = document.getElementById(img_id);
if( img == null ) {
return;
}
var par = img.parentNode;
var w = img.clientWidth;
var h = img.clientHeight;
var w_skip = 10;
var h_skip = 5;
var pointer_min_h = 30;
var bott_max = 0;
var comments_sorted = comments.sort(function (a,b) {
return a[2] - b[2];
//pokus o hezci kladeni poiteru, ale nic moc
if( a[3] < b[3] ) {
return (a[2] + pointer_min_h)- b[2];
} else {
return (a[2] - pointer_min_h)- b[2];
}
});
//console.log("w:" + w);
for (c in comments_sorted) {
var id = comments_sorted[c][0];
var x = comments_sorted[c][1];
var y = comments_sorted[c][2];
var el = document.getElementById(id);
var elp = document.getElementById(id + "-pointer");
if( el == null || elp == null ) {
continue;
}
par.appendChild(elp);
par.appendChild(el);
var delta_y = (y > bott_max) ? 0: bott_max - y + h_skip;
elp.style.left = x;
elp.style.top = y ;
elp.style.width = w - x + w_skip;
elp.style.height = pointer_min_h + delta_y;
elp.img_id = img_id;
el.img_id = img_id;
el.style.position = 'absolute';
el.style.left = w + w_skip;
el.style.top = y + delta_y;
var bott = el.offsetTop + el.offsetHeight;
bott_max = ( bott_max > bott ) ? bott_max : bott;
//console.log( "par.w:" + par.style.width);
}
if( par.offsetHeight < bott_max ) {
//par.style.height = bott_max;
//alert("preteklo to:"+ par.offsetHeight +",mx:" + bott_max );
par.style.height = bott_max;
}
}
function place_comments() {
for (var i=0; i < comments.length-1; i++) {
place_comments_one_div(comments[i][0], comments[i][1])
}
}
// ctrl-enter submits form
function textarea_onkey(ev)
{
//console.log("ev:" + ev.keyCode + "," + ev.ctrlKey);
if( (ev.keyCode == 13 || ev.keyCode == 10 ) && ev.ctrlKey ) {
var form = document.getElementById('commform');
if( form ) {
save_scroll(form);
//form.action ='';
form.submit();
}
return true;
}
return false;
}
//hide comment form
function close_commform() {
var formdiv = document.getElementById('commform-div');
if( formdiv == null ) {
alert("form null");
return true;
}
formdiv.style.display = 'none';
return false;
}
// show comment form, when clicked to image
function img_click(element, ev) {
var body_class = document.body.className;
switch(body_class){
case "comitting":
if (!confirm("Právě jsou zanášeny korektury, opravdu chcete přidat novou?"))
return;
break;
case "deprecated":
if (!confirm("Toto PDF je již zastaralé, opravdu chcete vytvořit korekturu?"))
return;
break;
}
var dx, dy;
var par = element.parentNode;
if( ev.pageX != null ) {
dx = ev.pageX - par.offsetLeft;
dy = ev.pageY - par.offsetTop;
} else { //IE
dx = ev.offsetX;
dy = ev.offsetY;
}
var img_id = element.id;
if( element.img_id != null ) {
// click was to '-pointer'
img_id = element.img_id;
}
return show_form(img_id, dx, dy, '', '', '', '');
}
// hide or show text of correction
function toggle_visibility(oid){
var buttondiv = document.getElementById(oid+'-buttons')
var text = document.getElementById(oid+'-body');
if (text.style.display == 'none'){
text.style.display = 'block';
buttondiv.style.display = 'inline-block';
}else {
text.style.display = 'none';
buttondiv.style.display = 'none';
}
for (var i=0;i<comments.length-1;i++){
place_comments_one_div(comments[i][0], comments[i][1])
}
}
// show comment form, when 'edit' or 'comment' button pressed
function box_edit(oid, action)
{
var divpointer = document.getElementById(oid + '-pointer');
var text;
if (action == 'update') {
var text_el = document.getElementById(oid + '-text');
text = text_el.textContent; // FIXME původně tu bylo innerHTML.unescapeHTML()
} else {
text = '';
}
var dx = parseInt(divpointer.style.left);
var dy = parseInt(divpointer.style.top);
var divbox = document.getElementById(oid);
//alert('not yet 2:' + text + text_el); // + divpointer.style.top "x" + divpo );
id = oid.substring(2);
return show_form(divbox.img_id, dx, dy, id, text, action);
}
// show comment form when 'update-comment' button pressed
function update_comment(oid,ktid)
{
var divpointer = document.getElementById(oid + '-pointer');
var dx = parseInt(divpointer.style.left);
var dy = parseInt(divpointer.style.top);
var divbox = document.getElementById(oid);
var text = document.getElementById(ktid).textContent; // FIXME původně tu bylo innerHTML.unescapeHTML()
return show_form(divbox.img_id, dx, dy, ktid.substring(2), text, 'update-comment');
}
//fill up comment form and show him
function show_form(img_id, dx, dy, id, text, action) {
var form = document.getElementById('commform');
var formdiv = document.getElementById('commform-div');
var textarea = document.getElementById('commform-text');
var inputX = document.getElementById('commform-x');
var inputY = document.getElementById('commform-y');
var inputImgId = document.getElementById('commform-img-id');
var inputId = document.getElementById('commform-id');
var inputAction = document.getElementById('commform-action');
var img = document.getElementById(img_id);
if( formdiv == null || textarea == null ) {
alert("form null");
return 1;
}
//form.action = "#" + img_id;
// set hidden values
inputX.value = dx;
inputY.value = dy;
inputImgId.value = img_id;
inputId.value = id;
inputAction.value = action;
textarea.value = text;
//textarea.value = "dxy:"+ dx + "x" + dy + "\n" + 'id:' + img_id;
// show form
formdiv.style.display = 'block';
formdiv.style.left = dx;
formdiv.style.top = dy;
img.parentNode.appendChild(formdiv);
textarea.focus();
return true;
}
function box_onmouseover(box)
{
var id = box.id;
var pointer = document.getElementById(box.id + '-pointer');
pointer.classList.remove('pointer');
pointer.classList.add('pointer-hi');
}
function box_onmouseout(box)
{
var id = box.id;
var pointer = document.getElementById(box.id + '-pointer');
pointer.classList.remove('pointer-hi');
pointer.classList.add('pointer');
}
function save_scroll(form)
{
//alert('save_scroll:' + document.body.scrollTop);
form.scroll.value = document.body.scrollTop;
//alert('save_scroll:' + form.scroll.value);
return true;
}
function toggle_corrections(aclass)
{
var stylesheets = document.styleSheets;
var ssheet = null;
for (var i=0;i<stylesheets.length; i++){
if (stylesheets[i].title === "opraf-css"){
ssheet = stylesheets[i];
break;
}
}
if (! ssheet){
return;
}
for (var i=0;i<ssheet.cssRules.length;i++){
var rule = ssheet.cssRules[i];
if (rule.selectorText === '.'+aclass){
if (rule.style.display === ""){
rule.style.display = "none";
} else {
rule.style.display = "";
}
}
}
place_comments();
}
String.prototype.unescapeHTML = function () {
return(
this.replace(/&amp;/g,'&').
replace(/&gt;/g,'>').
replace(/&lt;/g,'<').
replace(/&quot;/g,'"')
);
};

View file

@ -1,66 +0,0 @@
{# Část korekturovátka, která obsahuje všechno okolo korektur #}
{% include "korektury/korekturovatko/moduly/schovani_korektur.html" %}
{% include "korektury/korekturovatko/moduly/edit_komentar.html" %}
{% include "korektury/korekturovatko/moduly/stranky_pdfka.html" %}
{# {% for k in korektury %} {% include "korektury/korekturovatko/korektura.html" %} {% endfor %} #}
{% include "korektury/korekturovatko/moduly/korektura.html" %}
{% include "korektury/korekturovatko/moduly/komentar.html" %}
{% include "korektury/korekturovatko/moduly/dalsi_korektura.html" %}
<script>
/**
* Fetchne korektury a komentáře a na základě toho aktualizuje všechno
* (korektury, komentáře, zásluhy, počty korektur v daných stavech, umístění korektur)
* @param {RequestInit} data FormData a jiné náležitosti (method: POST) posílané při přidání/úpravě korektury/komentáře
* @param {Boolean} catchError jestli padat hlasitě (pokud se aktualizuje automaticky a spadne to např. na nepřítomnost sítě, pak není třeba informovat uživatele)
* @param {(() => *)?} pri_uspechu akce, která se má provést při úspěchu (speciálně zavřít formulář)
*/
function aktualizuj_vse(data={}, catchError=true, pri_uspechu=null) { // FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
fetch('{% url "korektury_api_opravy_a_komentare" korekturovanepdf.id %}', data)
.then(response => {
if (!response.ok && catchError) {alert('Něco se nepovedlo:' + response.statusText);}
else response.json().then(data => {
for (const korektura_data of data["context"]) {
const korektura = Korektura.aktualizuj_nebo_vytvor(korektura_data);
for (const komentar_data of korektura_data["komentare"]) {
Komentar.aktualizuj_nebo_vytvor(komentar_data, korektura);
}
}
aktualizuj_pocty_stavu();
aktualizuj_pocty_zasluh();
umisti_korektury();
if (pri_uspechu) pri_uspechu();
});
})
.catch(error => {if (catchError) alert('Něco se nepovedlo:' + error);});
}
window.addEventListener("load", _ => {
aktualizuj_vse({}, true, () => {
if (location.hash !== "") { // Po rozházení korektur sescrollujeme na kotvu v URL
const h = location.hash.substring(1);
location.hash = "HACK";
location.hash = h;
}
});
});
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
setInterval(() => aktualizuj_vse({}, false), 120000); // Každý dvě minuty fetchni korektury
</script>
{# Formulář, který mouhou použít tlačítka bez svého formuláře k vytvoření POST requestu, viz CSRF_FORM níže #}
<form id='CSRF_form' style='display: none'>{% csrf_token %}</form>
<script>
/**
* Formulář, který mouhou použít tlačítka bez svého formuláře k vytvoření POST requestu
* @type {HTMLFormElement}
*/
const CSRF_FORM = document.getElementById('CSRF_form');
</script>

View file

@ -1,64 +0,0 @@
{# Okolí samotného hlavni_cast_korekturovatka.html, tedy „povinné HTML věci“, informace o korekturovaném PDF a starání se o stav PDF #}
{% load static %}
<html lang='cs'>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" title="opraf-css" type="text/css" media="screen, projection" href="{% static "korektury/opraf.css"%}?version=3" />
<link href="{% static 'css/rozliseni.css' %}?version=3" rel="stylesheet">
<title>Korektury {{korekturovanepdf.nazev}}</title>
</head>
<body class="{{ LOCAL_TEST_PROD }}web" data-stav_pdf="{{ korekturovanepdf.status }}">
<h1>Korektury {{korekturovanepdf.nazev}}</h1>
<h2 class="textzanaseni"> Probíhá zanášení korektur, zvažte, zda chcete přidávat nové </h2>
<h2 class="textzastarale"> Toto PDF je již zastaralé, nepřidávejte nové korektury </h2>
<i>{{korekturovanepdf.komentar}}</i>
<br>
<i>Klikni na chybu, napiš komentář</i> |
<a href="{{korekturovanepdf.pdf.url}}">stáhnout PDF (bez korektur)</a> |
<a href="../">seznam souborů</a> |
<a href="/admin/korektury/korekturovanepdf/">Spravovat PDF</a> |
<a href="../help">nápověda</a> |
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|
<a href="/">hlavní stránka</a> |
<a href="https://mam.mff.cuni.cz/wiki">wiki</a> |
<hr />
{% include "korektury/korekturovatko/hlavni_cast_korekturovatka.html" %}
{% include "korektury/korekturovatko/zmena_stavu_pdf.html" %}
<hr/>
<p>
Děkujeme opravovatelům: <span id="pocty_autoru"></span></p>
<hr>
<script>
/**
* HTML prvek, kam se zapíší (pomocí .innerHTML) počty korektur jednotlivých autorů
* @type {HTMLElement}
*/
const span_s_pocty_autoru = document.getElementById("pocty_autoru")
/** Aktualizuje, kolik který autor má komentářů u daného korekturovaného PDF. */
function aktualizuj_pocty_zasluh() {
const pocty_autoru = {};
for (let komentar of Object.values(komentare)) {
if (!(komentar.autor in pocty_autoru)) pocty_autoru[komentar.autor] = 0;
pocty_autoru[komentar.autor] += 1;
}
const setrizene = [];
for (const keyval of Object.entries(pocty_autoru)) setrizene.push(keyval);
setrizene.sort(function(a, b) {return a[1] - b[1];});
let ans = "";
for (let [autor, pocet] of setrizene) ans += `, ${autor} (${pocet})`;
span_s_pocty_autoru.innerHTML = ans.substring(2);
}
</script>
</body>
</html>

View file

@ -1,77 +0,0 @@
{# Template starající se o tlačítka v levém dolním rohu, především skákající na další/předchozí korekturu. #}
{% load static %}
<div id="korektury-sipky">
<button type='button' id="predchozi-korektura" title='Předchozí korektura'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<button type='button' id="predchozi-korektura-k-oprave" title='Předchozí korektura k opravě'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<button type='button' id="predchozi-korektura-k-zaneseni" title='Předchozí korektura k zaneseni'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
<br>
<button type='button' id="dalsi-korektura" title='Další korektura'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id="dalsi-korektura-k-oprave" title='Další korektura k opravě'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id="dalsi-korektura-k-zaneseni" title='Další korektura k zaneseni'>
<img src='{% static "korektury/imgs/hide.png" %}' alt='⬇' style="transform: rotate(180deg);"/>
</button>
<button type='button' id='korektury-aktualizace'
title='Aktualizuj korektury
Nemusíš mačkat, pokud ti stačí, že se korektury aktualizují samy každé 2 minuty a při každém přidání korektury/komentáře.'
>
<img src='{% static "korektury/imgs/reload.svg" %}' alt='↻' style="width: 15px"/>
</button>
</div>
<script>
document.getElementById('predchozi-korektura').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false) });
document.getElementById('dalsi-korektura').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true) });
document.getElementById('predchozi-korektura-k-oprave').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false, "k_oprave") });
document.getElementById('dalsi-korektura-k-oprave').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true, "k_oprave") });
document.getElementById('predchozi-korektura-k-zaneseni').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(false, "k_zaneseni") });
document.getElementById('dalsi-korektura-k-zaneseni').addEventListener('click', _ => { dalsi_nebo_predchozi_korektura(true, "k_zaneseni") });
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval GET.
document.getElementById("korektury-aktualizace").addEventListener("click", _ => aktualizuj_vse({}, false));
/**
* Sescrolluje na další nebo předchozí (vůči hornímu okraji okna) korekturu (v daném stavu).
* V případě neexistence takové korektury vyhodí alert.
* @param {boolean} dalsi reprezentuje, zda chceme další nebo předchozí korekturu
* @param {?string} stav pokud je nenullový, tak ignoruje korektury v jiném stavu
*/
function dalsi_nebo_predchozi_korektura(dalsi=true, stav=null) {
let predchozi = null;
for (const strana of setrizene_strany) {
// strana.setrid_korektury(); // Nemělo by být potřeba, protože se volá vždy, když se renderují korektury.
for (const korektura of strana.korektury) {
if (stav == null || korektura.stav === stav) {
const y = korektura.htmlElement.getBoundingClientRect().y;
if (y >= -1) {
if (dalsi) {
if (y > 1) {
korektura.htmlElement.scrollIntoView();
return;
}
} else {
if (predchozi !== null) predchozi.htmlElement.scrollIntoView(); else alert("Výše už není žádná taková korektura.");
return;
}
}
predchozi = korektura;
}
}
}
if (!dalsi && predchozi !== null) {
predchozi.htmlElement.scrollIntoView();
return;
}
alert("Žádná další korektura.");
}
</script>

View file

@ -1,166 +0,0 @@
{# Template starající se o editační/přidávací formulář. #}
<div id="korekturovaci-formular-div" style="display: none">
<input size="24" name="au" value="{{user.osoba}}" readonly/>
<button type="button" id="korekturovaci-formular-odesli">Oprav!</button>
<button type="button" id="korekturovaci-formular-zavri">Zavřít</button>
<br/>
<textarea id="korekturovaci-formular-text" cols=40 rows=10 name="txt"></textarea>
<br/>
<div id="korekturovaci-formular-tagy-info">Úprava tagů celé korektury:</div>
<div id="korekturovaci-formular-tagy">
{% for tag in tagy %}
<button type="button" class="korektury-tag" value="{{tag.id}}" data-vybran="false" style="background: {{ tag.barva }}; border-color: {{ tag.barva }};">{{tag.nazev}}</button>
{% endfor %}
</div>
</div>
<script>
/** V podstatě singleton (viz korekturovaci_formular) starající se o editační/přidávací formulář. */
class _KorekturovaciFormular {
/**
* <div> obsahující celý formulář.
* @type {HTMLElement}
*/
div;
/**
* Políčko, kam uživatel vyplňuje text.
* @type {HTMLElement}
*/
text;
/**
* Tlačítko odeslat. Často ho chceme disablenout.
* @type {HTMLElement}
*/
odesilaci_button;
/**
* <div> obsahující všechny tagy, pomocí tagy.getElementsByTagName("button") umíme dělat operace nad všemi tagy.
* @type {HTMLElement}
*/
tagy;
/**
* Text upozorňující na to, že tagy nepřidáváme, ale editujeme. (Tj. chceme ho schovat, když vytváříme novou korekturu.)
* @type {HTMLElement}
*/
tagy_info;
/**
* zda při přidávání nové korektury mají být všechny tagy odvybrané, nebo mají kopírovat předchozí nastavení
* @type {boolean}
*/
pri_otevreni_odvyber_tagy;
constructor() {
this.div = document.getElementById('korekturovaci-formular-div');
this.text = document.getElementById('korekturovaci-formular-text');
this.odesilaci_button = document.getElementById('korekturovaci-formular-odesli');
const zaviraci_button = document.getElementById('korekturovaci-formular-zavri');
this.tagy = document.getElementById('korekturovaci-formular-tagy');
this.tagy_info = document.getElementById('korekturovaci-formular-tagy-info');
// ctrl-enter odešle formulář
this.text.addEventListener("keydown", ev => {
if (ev.code === "Enter" && ev.ctrlKey) this.odesli_formular();
});
zaviraci_button.addEventListener("click", _ => { this.schovej(); });
this.odesilaci_button.addEventListener("click", _ => { this.odesli_formular(); });
for (const tag of this.tagy.getElementsByTagName("button")) tag.addEventListener("click", event => { this.vyber_nebo_odvyber_tag(event); });
this.pri_otevreni_odvyber_tagy = true;
}
/**
* Přepne tag na vybraný/nevybraný (v závislosti na tom, zda byl nevybrán/vybrán)
* @param {MouseEvent} event vyvolaný kliknutím na daný tag (musí mít za event.target daný tag)
*/
vyber_nebo_odvyber_tag(event) {
const button = event.target;
button.dataset.vybran = String(button.dataset.vybran === "false");
}
/** Nastaví všechny tagy na nevybrané. */
odvyber_tagy() { for (const tag of this.tagy.getElementsByTagName("button")) tag.dataset.vybran = "false"; }
/** Schová (zavře) korekturovací formulář */
schovej() { this.div.style.display = 'none'; }
/**
* Zobrazí/otevře korekturovací formulář (bez toho, aby v něm cokoliv měnil).
* @param {Strana} strana (na které straně se má zobrazit)
* @param {number} x
* @param {number} y
*/
_zobraz(strana, x, y) {
this.odesilaci_button.disabled = false;
this.div.style.display = 'block';
this.div.style.left = x;
this.div.style.top = y;
strana.htmlElement_div.appendChild(korekturovaci_formular.div);
this.text.focus();
}
/**
* Předvyplní správně korekturovací formulář a zobrazí/otevře ho
* @param {Strana} strana (na které straně se má zobrazit)
* @param {Number} x
* @param {Number} y
* @param {string} text (text k předvyplněný, místo null chceš psáť "")
* @param {Number} komentar_id (!= -1 znamená úprava komentáře, -1 znamená přidávání korektury/komentáře)
* @param {Number} korektura_id (v případě komentar_id != -1 znamená: -1 je nová korektura, ne-1 je nový komentář)
*/
zobraz(strana, x, y, text, korektura_id=-1, komentar_id=-1) {
if (this.div.style.display !== 'none' && this.text.value !== "" && !confirm("Zavřít předchozí okénko přidávání korektury / editace komentáře?")) return;
// set hidden values
this.x = x;
this.y = y;
this.strana = strana;
this.korektura_id = korektura_id;
this.komentar_id = komentar_id;
this.text.value = text;
// show form
if (korektura_id === -1 && komentar_id === -1) {
if (this.pri_otevreni_odvyber_tagy) this.odvyber_tagy();
this.tagy_info.style.display = 'none';
} else {
const korektura = korektury[korektura_id];
this.tagy_info.style.display = 'unset';
for (const tag of this.tagy.getElementsByTagName("button"))
tag.dataset.vybran = String(korektura.tagy.has(parseInt(tag.value)));
}
this._zobraz(strana, x, y);
}
/** Shrábne data a pošle daný požadavek, čímž kromě vyřízení dané věci aktualizuje korektury+komentáře. */
odesli_formular() {
this.odesilaci_button.disabled = true;
const data = new FormData(CSRF_FORM);
data.append('x', this.x);
data.append('y', this.y);
data.append('img_id', this.strana.id);
data.append('oprava_id', this.korektura_id);
data.append('komentar_id', this.komentar_id);
const tagy = [];
for (const tag of this.tagy.getElementsByTagName("button")) {
if (tag.dataset.vybran !== "false") tagy.push(tag.value);
}
data.append('tagy', String(tagy));
data.append('text', this.text.value);
aktualizuj_vse({method: 'POST', body: data}, true, () => {this.schovej(); this.odesilaci_button.disabled = false;});
}
}
/**
* Objekt starající se o editační/přidávací formulář (jeho předvyplňování, zobrazování a posílání).
* @type {_KorekturovaciFormular}
*/
const korekturovaci_formular = new _KorekturovaciFormular();
</script>

View file

@ -1,168 +0,0 @@
{# Template starající se o jeden každý komentář u korektury. #}
{% load static %}
<div class='comment' id='prekomentar' {# id='k{{k.id}}' #}>
<div class='hlavicka-komentare'>
<div class='autor'>{# {{k.autor}} #}</div>
<div class='float-right'>
<button type='button' style='display: none' class="smaz-komentar" title='Smaž komentář'>
<img src='{% static "korektury/imgs/delete.png" %}' alt='del'/>
</button>
<button type='button' class="uprav-komentar" title='Uprav komentář'>
<img src='{% static "korektury/imgs/edit.png" %}' alt='edit'/>
</button>
<button type='button' class='sbal-rozbal' title='Skrýt/Zobrazit'>
<img class='sbal-rozbal-img' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
</div>
</div>
<div class='komtext'>{# {{k.text|linebreaks}} #}</div>
<hr>
</div>
<script>
/**
* Prototyp komentáře, ze kterého se vygeneruje každý komentář (resp. jeho HTML reprezentace) v dokumentu
* @type {HTMLElement}
*/
const prekomentar = document.getElementById('prekomentar');
/**
* Mapování ID |-> komentář
* @type {Object.<Number, Komentar>}
*/
const komentare = {};
/** Třída reprezentující jeden komentář (a starající se o vytvoření a updatování jeho HTML reprezentace) */
class Komentar {
/**
* Z dat aktualizuje (v případě, že korektura s daným ID existuje) nebo vytvoří Komentar
* @param {Object.<string, ?>} komentar_data „Slovník“ obsahující data daného komentáře
* @param {Korektura} korektura ke které se komentář má připojit
*/
static aktualizuj_nebo_vytvor(komentar_data, korektura) {
const id = komentar_data['id'];
if (id in komentare) komentare[id].aktualizuj(komentar_data);
else new Komentar(komentar_data, korektura);
}
/**
* <div> se jménem autora komentáře
* @type {HTMLElement}
*/
#autor;
/**
* <div> obsahující text komentáře
* @type {HTMLElement}
*/
#text;
/**
* <div> reprezentující celý komentář
* @type {HTMLElement}
*/
htmlElement;
/** @type {Number} */
id;
/** @type{Korektura} */
korektura;
/** @type{string} */
autor;
/** @type {boolean} */
sbalen = false;
/**
* Vytvoří HTML reprezentaci, připojí komentář pod korekturu, nastaví event-listenery, uloží si data
* @param {Object.<string, ?>} komentar_data „Slovník“ obsahující data daného komentáře
* @param {Korektura} korektura korektura ke které se komentář má připojit
*/
constructor(komentar_data, korektura) {
this.htmlElement = prekomentar.cloneNode(true);
this.#autor = this.htmlElement.getElementsByClassName('autor')[0];
this.#text = this.htmlElement.getElementsByClassName('komtext')[0];
this.id = komentar_data['id'];
this.htmlElement.id = 'k' + this.id;
this.korektura = korektura;
this.korektura.pridej_htmlElement_komentare(this.htmlElement);
this.aktualizuj(komentar_data);
this.htmlElement.getElementsByClassName('sbal-rozbal')[0].addEventListener('click', _ => this.#sbal_nebo_rozbal());
this.htmlElement.getElementsByClassName('uprav-komentar')[0].addEventListener('click', _ => this.#uprav_komentar());
this.htmlElement.getElementsByClassName('smaz-komentar')[0].addEventListener('click', _ => this.#smaz_komentar());
komentare[this.id] = this;
}
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci komentáře
* @param {Object.<string, ?>} komentar_data „Slovník“ obsahující data daného komentáře
*/
aktualizuj(komentar_data) {
this.set_autor(komentar_data['autor']);
this.set_text(komentar_data['text']);
};
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci autora komentáře
* @param {String} autor
*/
set_autor(autor) {
this.#autor.textContent=autor;
this.autor = autor;
};
/**
* @param {String} text
*/
set_text(text) {
this.#text.innerHTML=text;
};
/** Sbalí/rozbalí (podle toho, zda byl rozbalený/sbalený) komentář, ale nezmění pozice korektur (je třeba později zavolat umisti_korektury()) */
sbal_nebo_rozbal() {
this.sbalen = !this.sbalen;
this.htmlElement.dataset.komentar_sbalen = String(this.sbalen);
}
/** Doplněk sbal_nebo_rozbal, který i přeskládá korektury. */
#sbal_nebo_rozbal(){
this.sbal_nebo_rozbal();
umisti_korektury();
}
/** Ukáže formulář na editaci komentáře (když je zmáčknuto „uprav-komentar“) */
#uprav_komentar() {
return korekturovaci_formular.zobraz(this.korektura.strana, this.korektura.x, this.korektura.y, this.#text.textContent, this.korektura.id, this.id);
}
/** Smaže komentář (když je zmáčknuto „smaz-komentar“) */
#smaz_komentar() {
if (confirm('Opravdu smazat komentář?')) {
const data = new FormData(CSRF_FORM);
data.append('komentar_id', this.id);
fetch('{% url "korektury_api_komentar_smaz" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
this.smaz_pouze_na_strance();
umisti_korektury();
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
}
/** Smaže div komentáře (ne databázový záznam!), používá se, když je smazán komentář nebo jeho nadřazená korektura */
smaz_pouze_na_strance() {
delete komentare[this.id];
this.htmlElement.remove();
}
}
</script>

View file

@ -1,271 +0,0 @@
{% load static %}
<div id='prepointer' {# id='kor{{k.id}}-pointer' #}
class='pointer'
data-hover='false'
{# data-stav_korektury='{{k.status}}' #}
></div>
<div id='prekorektura' {# name='kor{{k.id}}' id='kor{{k.id}}' #}
class='korektura'
{# data-stav_korektury='{{k.status}}' #}
data-korektura_sbalena='false'
>
<div class="korektura-tagy">
{# {% for tag in k.tagy %} <span style="background:{{ tag.barva }}>{{ tag.text }}<span/> #}
</div>
<div class='korektura-telo'>
{# {% for k in k.komentare %} {% include "korektury/korekturovatko/komentar.html" %} {% endfor %} #}
</div>
<div class='hlavicka-komentare'>
<span class='float-right'>
<span class='korektura-tlacitka'>
<button type='button' style='display: none' class="smaz-korekturu" title='Smaž korekturu'>
<img src='{% static "korektury/imgs/delete.png" %}' alt='🗑️'/>
</button>
<button type='button' class='action' value='k_oprave' title='Označ jako neopravené'>
<img src='{% static "korektury/imgs/undo.png" %}' alt='↪'/>
</button>
<button type='button' class='action' value='opraveno' title='Označ jako opravené'>
<img src='{% static "korektury/imgs/check.png" %}' alt='✔️'/>
</button>
<button type='button' class='action' value='neni_chyba' title='Označ, že se nebude měnit'>
<img src='{% static "korektury/imgs/cross.png" %}' alt='❌'/>
</button>
<button type='button' class='action' value='k_zaneseni' title='Označ jako připraveno k zanesení'>
<img src='{% static "korektury/imgs/tex.png" %}' alt='TeX'/>
</button>
<a href='{% url "admin:korektury_oprava_change" -1 %}' class='edit' title='Uprav korekturu jako takovou.' style="text-decoration: none;"> {# FIXME Udělat z toho tlačítko? #}
<img src='{% static "korektury/imgs/edit.png" %}' alt='✏️' style="opacity: 0.5;"/> {# FIXME Odlišit jinak než pomocí opacity? #}
</a>
<button type='button' class='komentovat_disabled' title='Korekturu nelze komentovat, protože už je uzavřená' disabled=''>
<img src='{% static "korektury/imgs/comment-gr.png" %}' alt='💭'/>
</button>
<button type='button' class='komentovat' title='Komentovat'>
<img src='{% static "korektury/imgs/comment.png" %}' alt='💭'/>
</button>
</span>
<button type='button' class='sbal-rozbal' title='Skrýt/Zobrazit'>
<img class='sbal-rozbal-img' src='{% static "korektury/imgs/hide.png" %}' alt='⬆'/>
</button>
</span>
</div>
</div>
<script>
/**
* Prototyp korektury, ze kterého se vygeneruje každý komentář (resp. jeho HTML reprezentace) v dokumentu
* @type {HTMLElement}
*/
const prekorektura = document.getElementById('prekorektura');
/**
* Prototyp pointeru (té lomené čáry od korektury)
* @type {HTMLElement}
*/
const prepointer = document.getElementById('prepointer');
/**
* Mapování ID |-> korektura
* @type {Object.<Number, Korektura>}
*/
const korektury = {};
/** Třída reprezentující jednu korekturu (a starající se o vytvoření a updatování její HTML reprezentace) */
class Korektura {
/**
* Z dat aktualizuje (v případě, že korektura s daným ID existuje) nebo vytvoří Korekturu
* @param {Object.<string, ?>} korektura_data „Slovník“ obsahující data dané korektury
* @returns {Korektura} vytvořená/aktualizovaná Korektura (pro použití při vytváření/aktualizaci komentářů)
*/
static aktualizuj_nebo_vytvor(korektura_data) {
const id = korektura_data['id'];
if (id in korektury) return korektury[id].aktualizuj(korektura_data);
else return new Korektura(korektura_data);
}
/**
* <div> obsahující <div>y komentářů
* @type {HTMLElement}
*/
#komentare;
/**
* <div> obsahující tagy
* @type {HTMLElement}
*/
#tagy;
/**
* <div> reprezentující celý korekturu
* @type {HTMLElement}
*/
htmlElement;
/**
* <div> reprezentující pointer (tu lomenou čáru od korektury)
* @type {HTMLElement}
*/
pointer;
/** @type {Number} */
id;
/** @type {Number} */
x;
/** @type {Number} */
y;
/** @type {Strana} */
strana;
/** @type {string} */
stav;
/** @type {boolean} */
sbalena = false;
/** @type Set<Number> */
tagy;
/**
* Vytvoří HTML reprezentaci, připojí korekturu pod stranu (ale neumístí ji), nastaví event-listenery, uloží si data
* @param {Object.<string, ?>} korektura_data „Slovník“ obsahující data dané korektury
*/
constructor(korektura_data) {
this.htmlElement = prekorektura.cloneNode(true);
this.pointer = prepointer.cloneNode(true);
this.#komentare = this.htmlElement.getElementsByClassName('korektura-telo')[0];
this.#tagy = this.htmlElement.getElementsByClassName('korektura-tagy')[0];
this.id = korektura_data['id'];
this.htmlElement.id = 'kor' + this.id;
this.pointer.id = 'kor' + this.id + '-pointer';
this.x = korektura_data['x'];
this.y = korektura_data['y'];
this.aktualizuj(korektura_data);
this.htmlElement.getElementsByClassName('sbal-rozbal')[0].addEventListener('click', _ => this.#sbal_nebo_rozbal());
for (const button of this.htmlElement.getElementsByClassName('action'))
button.addEventListener('click', async event => this.#zmen_stav_korektury(event));
this.htmlElement.getElementsByClassName('komentovat')[0].addEventListener('click', _ => this.#komentuj())
this.htmlElement.getElementsByClassName('smaz-korekturu')[0].addEventListener('click', _ => this.#smaz_korekturu());
const odkaz_editace = this.htmlElement.getElementsByClassName('edit')[0];
odkaz_editace.href = odkaz_editace.href.replace("-1", this.id);
odkaz_editace.onclick = ev => { if (!confirm("Editace korektury je velmi pokročilá featura umožňující přesouvat korekturu nebo přidávat informované orgy, opravdu chceš pokračovat do adminu?")) ev.preventDefault(); };
this.htmlElement.addEventListener('mouseover', _ => this.pointer.dataset.hover = 'true');
this.htmlElement.addEventListener('mouseout', _ => this.pointer.dataset.hover = 'false');
const cislo_strany = korektura_data['strana'];
if (cislo_strany in strany) {
this.strana = strany[cislo_strany];
this.strana.korektury.push(this);
} else alert("Někdo korekturoval stranu, která neexistuje. Dejte vědět webařům :)");
korektury[this.id] = this;
}
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci korektury
* @param {Object.<string, ?>} korektura_data „Slovník“ obsahující data dané korektury
* @returns {Korektura} pro jednodušší implementaci aktualizuj_nebo_vytvor vracíme this
*/
aktualizuj(korektura_data) {
this.set_stav(korektura_data['status']);
this.set_tagy(korektura_data["tagy"]);
return this;
};
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci tagů korektury
* @param {Object.<string, ?>[]} tagy
*/
set_tagy(tagy) {
this.#tagy.innerHTML = "";
this.tagy = new Set();
for (const tag of tagy) {
this.tagy.add(tag["id"]);
const span = document.createElement("span");
span.innerHTML = tag["nazev"];
span.classList.add("korektury-tag");
span.style.backgroundColor = tag["barva"];
this.#tagy.appendChild(span);
}
}
/**
* Aktualizuje/nastaví JS data i HTML reprezentaci stavu korektury
* @param {String} stav
*/
set_stav(stav) {
this.stav = stav;
this.htmlElement.dataset.stav_korektury=stav;
this.pointer.dataset.stav_korektury=stav;
};
/**
* Přidá HTML reprezentaci komentáře pod tuto korekturu
* @param {HTMLElement} htmlElement přidávaný komentář (jako HTML prvek)
*/
pridej_htmlElement_komentare(htmlElement) { this.#komentare.appendChild(htmlElement); }
/** Sbalí/rozbalí (podle toho, zda byla rozbalená/sbalená) korekturu, ale nezmění pozice korektur (je třeba později zavolat umisti_korektury()) */
sbal_nebo_rozbal() {
this.sbalena = !this.sbalena;
this.htmlElement.dataset.korektura_sbalena = String(this.sbalena);
}
/** Doplněk sbal_nebo_rozbal, který i přeskládá korektury. */
#sbal_nebo_rozbal(){
this.sbal_nebo_rozbal();
umisti_korektury();
}
/** Ukaž komentovací formulář (když je zmáčknuto komentovat) */
#komentuj() { korekturovaci_formular.zobraz(this.strana, this.x, this.y, "", this.id); }
/**
* Změní stav (když je zmáčknuto tlačítko daného stavu)
* @param {MouseEvent} event který vyvolal danou změnu (event.target.value musí být chtěný stav)
*/
#zmen_stav_korektury(event) {
const data = new FormData(CSRF_FORM);
data.append('id', this.id);
data.append('action', event.target.value);
fetch('{% url "korektury_api_oprava_stav" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
else response.json().then(data => {
this.set_stav(data['status']);
aktualizuj_pocty_stavu();
});
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
/** Smaže korekturu (když je zmáčknuto „smaz-korekturu“) */
#smaz_korekturu() {
if (confirm('Opravdu smazat korekturu?')) {
const data = new FormData(CSRF_FORM);
data.append('oprava_id', this.id);
fetch('{% url "korektury_api_oprava_smaz" %}', {method: 'POST', body: data})
.then(response => {
if (!response.ok) {alert('Něco se nepovedlo:' + response.statusText);}
this.#smaz_pouze_na_strance()
aktualizuj_pocty_stavu();
aktualizuj_pocty_zasluh();
umisti_korektury();
})
.catch(error => {alert('Něco se nepovedlo:' + error);});
}
}
/** Smaže div korektury (včetně všech komentářů; ne databázový záznam!) */
#smaz_pouze_na_strance() {
this.strana.korektury.splice(this.strana.korektury.indexOf(this), 1);
delete korektury[this.id];
for (const komentar of Object.values(komentare)) if (komentar.korektura === this) komentar.smaz_pouze_na_strance();
this.htmlElement.remove();
this.pointer.remove();
}
}
</script>

View file

@ -1,86 +0,0 @@
{# Template starající se o tlačítkovou lištu nahoře, tj. hlavně o hromadné schovávání korektur. #}
Zobrazit:
<input type="checkbox" id="k_oprave_checkbox" checked>
<label for="k_oprave_checkbox">K opravě (<span id="k_oprave_pocet"></span>)</label>
<input type="checkbox" id="opraveno_checkbox" checked>
<label for="opraveno_checkbox">Opraveno (<span id="opraveno_pocet"></span>)</label>
<input type="checkbox" id="neni_chyba_checkbox" checked>
<label for="neni_chyba_checkbox">Není chyba (<span id="neni_chyba_pocet"></span>)</label>
<input type="checkbox" id="k_zaneseni_checkbox" checked>
<label for="k_zaneseni_checkbox">K zanesení (<span id="k_zaneseni_pocet"></span>)</label>
<button type="button" id="sbal-korektury">Sbal korektury</button>
<button type="button" id="rozbal-korektury">Rozbal korektury</button>
<hr/>
<script>
document.getElementById('k_oprave_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('k_oprave'));
document.getElementById('opraveno_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('opraveno'));
document.getElementById('neni_chyba_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('neni_chyba'));
document.getElementById('k_zaneseni_checkbox').addEventListener('change', () => skryj_nebo_zobraz_korektury('k_zaneseni'));
document.getElementById("sbal-korektury").addEventListener("click", () => {
for (const korektura of Object.values(korektury))
if (!korektura.sbalena) korektura.sbal_nebo_rozbal();
umisti_korektury();
})
document.getElementById("rozbal-korektury").addEventListener("click", () => {
for (const korektura of Object.values(korektury))
if (korektura.sbalena) korektura.sbal_nebo_rozbal();
umisti_korektury();
})
/**
* Změní CSS tak, aby se korektury příslušného stavu nezobrazovali/zobrazovali (v závislosti na tom, jestli byly zobrazené/nezobrazené)
* @param {string} aclass stav korektur, které mají být skryty/zobrazeny
*/
function skryj_nebo_zobraz_korektury(aclass)
{
const stylesheets = document.styleSheets;
let ssheet = null;
for (let i=0; i<stylesheets.length; i++){
if (stylesheets[i].title === "opraf-css"){
ssheet = stylesheets[i];
break;
}
}
if (! ssheet){
return;
}
for (let i=0; i<ssheet.cssRules.length; i++){
const rule = ssheet.cssRules[i];
if (rule.selectorText === '[data-stav_korektury="'+aclass+'"]'){
if (rule.style.display === ""){
rule.style.display = "none";
} else {
rule.style.display = "";
}
}
}
umisti_korektury();
}
/**
* Mapování stav korektur |-> span, kde se píše, kolik je korektur toho stavu.
* Používané v následující funcki
* @type {Object.<string, HTMLElement>}
*/
const spany_s_pocty_stavu_korektur = {
'k_oprave': document.getElementById('k_oprave_pocet'),
'opraveno': document.getElementById('opraveno_pocet'),
'neni_chyba': document.getElementById('neni_chyba_pocet'),
'k_zaneseni': document.getElementById('k_zaneseni_pocet'),
}
/** Aktualizuje počty korektur jednotlivých stavů */
function aktualizuj_pocty_stavu() {
const pocty_stavu_korektur = {};
for (const stav_korektury of Object.keys(spany_s_pocty_stavu_korektur)) pocty_stavu_korektur[stav_korektury] = 0;
for (const korektura of Object.values(korektury)) {
if (!(korektura.stav in pocty_stavu_korektur)) pocty_stavu_korektur[korektura.stav] = 0;
pocty_stavu_korektur[korektura.stav] += 1;
}
for (let [stav, pocet] of Object.entries(pocty_stavu_korektur)) spany_s_pocty_stavu_korektur[stav].innerText = pocet;
}
</script>

View file

@ -1,148 +0,0 @@
{# Template starající se o zobrazení PDF stran a o umístění korektur na ně. (O samotné korektury se stará `./korektura.html`.) #}
{% for i in indexy_stran %}
<div class='imgdiv'>
<img
id='img-{{i}}'
width='1021' height='1448'
src='/media/korektury/img/{{korekturovanepdf.get_prefix}}-{{i}}.png'
alt='Strana {{ i|add:1 }}'
class="strana"
/>
</div>
<hr/>
{% endfor %}
<script>
// Pro umisťování korektur
const HORIZONTALNI_MEZERA = 10;
const VERTIKALNI_MEZERA = 5;
const MINIMALNI_VYSKA_POINTERU = 30;
/**
* Mapování index_strany |-> strana
* @type {Object.<int, Strana>}
*/
const strany = {};
/** Třída spravující jednu stranu PDF a umisťující na ni příslušné korektury. */
class Strana {
/**
* <img> příslušící straně
* @type {HTMLElement}
*/
htmlElement_img;
/**
* <div> obalující stranu, do něj se umisťují korektury
* @type {HTMLElement}
*/
htmlElement_div;
/**
* Index strany (používá se při ukládání korektury (a načítání <img>))
* @type {Number}
*/
id;
/**
* Korektury na příslušné straně (BÚNO setříděné podle vertikálního umístění)
* @type {Korektura[]}
*/
korektury;
/**
* Uloží si data (včetně pointrů na správné části HTML DOMu) a nastaví event-listener
* @param {HTMLElement} htmlElement_img
*/
constructor(htmlElement_img) {
this.htmlElement_img = htmlElement_img;
this.htmlElement_div = this.htmlElement_img.parentNode;
this.id = parseInt(this.htmlElement_img.id.substring(4));
this.korektury = []
this.htmlElement_img.addEventListener('click', event => this.#korekturuj(event));
strany[this.id] = this;
}
/**
* Otevře korekturovací formulář pro přidání korektury v daném místě
* @param {MouseEvent} event
*/
#korekturuj(event) {
switch (document.body.dataset.stav_pdf) {
case 'zanaseni':
if (!confirm('Právě jsou zanášeny korektury, opravdu chcete přidat novou?')) return;
break;
case 'zastarale':
if (!confirm('Toto PDF je již zastaralé, opravdu chcete vytvořit korekturu?')) return;
break;
}
let dx, dy;
if (event.pageX != null) {
dx = event.pageX - this.htmlElement_div.offsetLeft;
dy = event.pageY - this.htmlElement_div.offsetTop;
} else { //IE a další
dx = event.offsetX;
dy = event.offsetY;
}
korekturovaci_formular.zobraz(this, dx, dy, '');
console.log("Pro přesun korektur: strana = " + this.id + ", x = " + dx + ", y = " + dy);
}
/** Setřídí seznam korektur příslušný dané straně */
setrid_korektury() { this.korektury.sort((a, b) => a.y - b.y); }
/** Zobrazí korektury a jejich pointry (a umístí je správně pod sebe) na dané straně */
umisti_korektury() {
this.setrid_korektury()
const w = this.htmlElement_img.clientWidth;
let spodek_posledni_korektury = 0;
for (const korektura of this.korektury) {
const x = korektura.x;
const y = korektura.y;
const pointer = korektura.pointer;
this.htmlElement_div.appendChild(pointer);
this.htmlElement_div.appendChild(korektura.htmlElement);
const delta_y = (y > spodek_posledni_korektury) ? 0: spodek_posledni_korektury - y + VERTIKALNI_MEZERA;
pointer.style.left = x;
pointer.style.top = y;
pointer.style.width = w - x + HORIZONTALNI_MEZERA;
pointer.style.height = MINIMALNI_VYSKA_POINTERU + delta_y;
korektura.htmlElement.style.left = w + HORIZONTALNI_MEZERA;
korektura.htmlElement.style.top = y + delta_y;
spodek_posledni_korektury = Math.max(
spodek_posledni_korektury,
korektura.htmlElement.offsetTop + korektura.htmlElement.offsetHeight + VERTIKALNI_MEZERA
); // FIXME nemám páru, proč +VERTIKALNI_MEZERA funguje, ale opravuje to bug, že nově vytvořené korektury za sebou neměly mezeru
}
this.htmlElement_div.style.height = "unset";
if (this.htmlElement_div.offsetHeight < spodek_posledni_korektury)
this.htmlElement_div.style.height = spodek_posledni_korektury;
}
}
// Vytvoření objektu Strana pro každou stranu
for (const strana_img of document.getElementsByClassName('strana'))
new Strana(strana_img);
/**
* Seznam stran setřízený podle toho, jak jdou po sobě (aby se dali korektury prohledávat od první na HTML stránce po poslední)
* @type {Strana[]}
*/
const setrizene_strany = Object.values(strany);
setrizene_strany.sort((a, b) => a.htmlElement_img.offsetTop - b.htmlElement_img.offsetTop);
/** Zobrazí korektury a jejich pointry (a umístí je správně pod sebe) na všech stranách */
function umisti_korektury() { for (const strana of Object.values(strany)) strana.umisti_korektury(); }
</script>

View file

@ -1,49 +0,0 @@
{# Template starající se o formulář na změnu stavu PDF (včetně jeho odeslání) #}
<b>Změnit stav PDF:</b>
<br>
<form method="post" id="PDFSTAV_FORM">
{% csrf_token %}
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.PRIDAVANI }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.PRIDAVANI %} checked {% endif %}>Přidávání korektur
<br>
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.ZANASENI }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.ZANASENI %} checked {% endif %}>Zanášení korektur
<br>
<input type="radio" name="state" value="{{ korekturovanepdf.STATUS.ZASTARALE }}" {% if korekturovanepdf.status == korekturovanepdf.STATUS.ZASTARALE %} checked {% endif %}>Zastaralé, nekorigovat
<br>
<input type='submit' value='Změnit stav PDF'/>
</form>
<script>
/**
* Formulář měnící stav korekturovaného PDF
* @type {HTMLFormElement}
*/
const pdfstav_form = document.getElementById('PDFSTAV_FORM');
/**
* Fetchne stav korekturovaného PDF a změní ho na dané stránce.
* FIXME: nemění, který radio-button je vybrán.
* @param {RequestInit} data FormData a jiné náležitosti (method: POST) posílané při změně stavu korekturovaného PDF
* @param {Boolean} catchError jestli padat hlasitě (pokud se aktualizuje automaticky a spadne to např. na nepřítomnost sítě, pak není třeba informovat uživatele)
*/
function fetchStav(data, catchError=true) {
fetch("{% url 'korektury_api_pdf_stav' korekturovanepdf.id %}", data
)
.then(response => {
if (!response.ok) { if (catchError) alert("Něco se nepovedlo:" + response.statusText);}
else response.json().then(data => document.body.dataset.stav_pdf = data["status"]);
})
.catch(error => {if (catchError) alert("Něco se nepovedlo:" + error);});
}
pdfstav_form.addEventListener('submit', async event => {
event.preventDefault();
const data = new FormData(pdfstav_form);
fetchStav({method: "POST", body: data});
});
// FIXME není mi jasné, zda v {} nemá být `cache: "no-store"`, aby prohlížeč necachoval get.
setInterval(() => fetchStav({}, false), 120000); // Každý dvě minuty fetchni stav
</script>

Some files were not shown because too many files have changed in this diff Show more