Compare commits

...

61 commits

Author SHA1 Message Date
cb924f846f Vytáhnuty konstanty do settings_common.py
(Takže teď lze jednoduše měnit e-maily a také testovací data (settings_local.py) nemusí mít 30 ročníků pro otestování zlomů)
2023-08-11 11:25:53 +02:00
206d37e21c Fix c00f60b38f 2023-08-11 11:25:52 +02:00
f707cc02f7 Uhlazena konfigurace (settings_*.py) 2023-08-11 11:25:47 +02:00
e85c333bf8 Smazáno staré testování e-mailů 2023-08-10 23:41:15 +02:00
5cccd40800 Na některých serverech už produkce nepoběží… A testweb rozhodně nesmí běhat na místě produkce. 2023-08-10 23:30:48 +02:00
ff04978a95 More hierarchy to URL 2023-08-10 22:58:42 +02:00
c2613af9a6 Doteď nevadilo, že to tu není, tak mažu 2023-08-10 22:43:42 +02:00
bbaf109bbe Vhled je dělaný přes context processor, mažu middleware 2023-08-10 22:40:32 +02:00
56ac6f74b6 Smazány 2 malinkaté automaticky vygenerované věci 2023-08-10 22:27:50 +02:00
2319087bcb TODO přesunuto do Kanboardu 2023-08-10 22:25:44 +02:00
06d0a8cce3 Odstraněny další generické dokumentace 2023-08-10 22:25:21 +02:00
afc238ba74 Tohle vypadá staře a navíc ve staré verzi, mažu 2023-08-10 22:08:22 +02:00
c6805ad7b5 Další zastaralé coding: utf-8 2023-08-10 22:05:26 +02:00
6ff0438874 TODO přesunuto do kanboardu 2023-08-10 21:59:27 +02:00
1f96234a0f Update dokumentace (deploy v2 a práva skupin) 2023-08-10 21:20:44 +02:00
cdd1759976 Práva skupin (hlavně orgů) 2023-08-10 21:01:50 +02:00
1790c0faf7 Usměrnění importů import * 2023-08-10 19:32:48 +02:00
0c45d8051f Rozházeny seminar.testutils (stále je potřeba je upravit do příčetného stavu a opravit import *) 2023-08-10 17:27:18 +02:00
8939a26ec8 Rozházeny seminar.templatetags 2023-08-10 15:53:19 +02:00
d40265ba74 Commandy ze semináře do tvorby 2023-08-10 15:44:28 +02:00
8301cbdb4d Už nebude potřeba (pak to udělám pomocí loaddata) 2023-08-10 15:41:29 +02:00
c00f60b38f FormularOKView a pracuje_se přesunuto ze semináře do various 2023-08-10 15:34:32 +02:00
5878d1bf7e Zapomenutý template (stav_databaze) v seminari 2023-08-10 15:30:57 +02:00
c96ada8b37 Vypreparování views, templates a urls tvorby ze semináře 2023-08-10 15:21:22 +02:00
f3c38f1f02 Nepoužitý logger v seminar.views 2023-08-10 14:31:19 +02:00
1f9966c51f Nepoužitý ResitelInline v seminar.admin 2023-08-10 14:15:50 +02:00
a346c4d49a Přesun adminu tvorby ze semináře 2023-08-10 14:14:41 +02:00
9412a52567 Přesun stavu databáze do various (dokončeno rozebírání seminar.utils)
Důvodem je, že mi přijde, že stav databáze je dosti složitá a neviditelná věc, na to, aby byla přímo v semináři
2023-08-10 14:04:31 +02:00
7a02a826cd Nepoužitý logger? (v seminar.utils) 2023-08-10 13:47:56 +02:00
9c68eac050 Přesunutí "switche view podle GET/POST" ze seminar.utils do various.utils 2023-08-10 13:46:15 +02:00
15b17023de Přesunutí římských čísel ze seminar.utils do various.utils 2023-08-10 13:43:49 +02:00
c89a982440 Odstraněn nepoužívaný FirstTagParser (z seminar.utils) 2023-08-10 13:40:38 +02:00
39afe79da7 Vypreparování odevzdávátka ze seminar.utils 2023-08-10 13:40:01 +02:00
5fcf9bac15 Vypreparování personálního ze seminar.utils 2023-08-10 13:20:18 +02:00
7dc0e1d71b Přejmenování tabulek modelů zbylých v aplikaci seminar 2023-08-10 12:45:28 +02:00
9ac0d06e1e Další drobné úpravy: admin_url potřebuje app_label 2023-08-10 12:39:11 +02:00
6bb39fc0a0 Ještě jedna drobná chybička (testdata ale stále nefungují, protože import * přepisují importy) 2023-08-10 12:27:50 +02:00
5c9ed5e5ed Ruční přepsání migrací, aby se nedalo namigrovat do rozbitého stavu. POZOR: odmigrovat vše od tagu v3_uklizeno
Teď už je nejhorší stav, že dvě aplikace ukazují na tu samou tabulku.
2023-08-10 12:14:18 +02:00
9a20fc7c79 Oprava drobných chybiček při migracích 2023-08-10 09:46:46 +02:00
e569346274 Migrace a přejmenování tabulek modelů treenode 2023-08-10 09:42:26 +02:00
78923f5237 Přesun kódu modelů treenode 2023-08-10 09:12:23 +02:00
0fd3526a87 Migrace a přejmenování tabulek modelů soustředění 2023-08-09 21:49:54 +02:00
d240774022 Přesun kódu modelů soustředění 2023-08-09 21:29:14 +02:00
acea74bc6e Migrace a přejmenování tabulek modelů odevzdávátka 2023-08-09 21:06:14 +02:00
9b3cbb512c Přesun kódu modelů odevzdávátka 2023-08-09 19:32:55 +02:00
1af4a13a62 Migrace a přejmenování tabulek modelů tvorby 2023-08-09 18:58:28 +02:00
115589e770 Přesun kódu modelů tvorby 2023-08-09 17:11:20 +02:00
2b52ec028e Přesun Natavení z tvorby 2023-08-09 16:40:23 +02:00
5346da5107 Init tvorby 2023-08-09 16:12:57 +02:00
75a7e607d5 Migrace a přejmenování tabulek modelů personálního 2023-07-31 20:57:28 +02:00
ebf8165c53 Přesun kódu modelů personálního 2023-07-31 19:53:12 +02:00
8c881621b0 Přesun ReseniNode do treenode 2023-07-31 19:31:23 +02:00
666b455bbd Vyřešení cyklických importů po předchozím commitu 2023-07-31 19:21:36 +02:00
e45c819424 import seminar.models as m na from seminar.models.neco import neco 2023-07-31 19:13:56 +02:00
8319b28272 Návod na ilustrace odměn nepatří do kořenové složky 2023-07-31 17:45:49 +02:00
6d3a70165f Deploy v2 už nebude aktuální 2023-07-31 17:41:18 +02:00
59d9589162 Generické dokumentace djangovských souborů to chce řešit jinak… 2023-07-31 17:33:39 +02:00
44b10449af `# -*- coding: utf-8 -*- už přestalo být relevantní 2023-07-31 17:26:39 +02:00
3631ec3c5b AESOP přestal býti aktuální 2023-07-31 17:23:06 +02:00
1868f96594 Měnit mezery na tabulátory už bychom také neměli potřebovat 2023-07-31 16:21:58 +02:00
f8379b8b67 Kontroly Pythonu už nevedeme 2023-07-31 16:20:26 +02:00
288 changed files with 5163 additions and 4915 deletions

3
.gitignore vendored
View file

@ -25,9 +25,6 @@ TODO
# .htpasswd kvůli přihlášení # .htpasswd kvůli přihlášení
.htpasswd .htpasswd
# .htpasswd pro AESOPa
/.htpasswd-aesop
# reversion kvůli historii objektů v reversion # reversion kvůli historii objektů v reversion
**/reversion **/reversion

View file

@ -1,16 +0,0 @@
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

View file

@ -1,30 +0,0 @@
#!/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

View file

@ -1,61 +0,0 @@
#!/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 +0,0 @@
"""
Obsahuje vše, co se týká aesopu (exportu, který po nás vyžaduje OPMK).
"""

View file

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

View file

@ -1,30 +0,0 @@
from django.http import HttpResponse
from django.utils.encoding import force_text
class OvvpFile:
def __init__(self):
# { header: value, ... }
self.headers = {}
# [ 'column-name', ... ]
self.columns = []
# [ { column: value, ...}, ...]
self.rows = []
def to_lines(self):
# header
for hk in sorted(self.headers.keys()):
yield f'{hk}\t{self.headers[hk]}\n'
yield '\n'
# columns
yield '\t'.join(self.columns) + '\n'
# rows
for r in self.rows:
yield '\t'.join([force_text(r[c]) for c in self.columns]) + '\n'
def to_string(self):
return ''.join(self.to_lines())
# Pozn: tohle je ta jediná funkce, která se reálně používá…
def to_HttpResponse(self):
return HttpResponse(self.to_string(), content_type='text/plain; charset=utf-8')

View file

@ -1,27 +0,0 @@
"""
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 aesop import views
urlpatterns = [
path(
'aesop-export/mam-rocnik-<int:prvni_rok>.csv',
views.ExportRocnikView.as_view(),
name='seminar_export_rocnik'
),
path(
'aesop-export/mam-sous-<str:datum_zacatku>.csv',
views.ExportSousView.as_view(),
name='seminar_export_sous'
),
path(
'aesop-export/index.csv',
views.ExportIndexView.as_view(),
name='seminar_export_index'
),
]

View file

@ -1,16 +0,0 @@
import datetime
from django.utils.encoding import force_text
from aesop.ovvpfile import OvvpFile
def default_ovvpfile(event, rocnik):
of = OvvpFile()
of.headers['version'] = '1'
of.headers['event'] = event
of.headers['year'] = force_text(rocnik.prvni_rok)
of.headers['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
of.headers['id-scope'] = 'mam'
of.headers['id-generation'] = '1'
return of

View file

@ -1,101 +0,0 @@
"""
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
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.urls import reverse
from django.views import generic
from django.utils.encoding import force_text
from .utils import default_ovvpfile
from seminar.models import Rocnik, Soustredeni
from vysledkovky import utils
from seminar.utils import aktivniResitele
class ExportIndexView(generic.View):
def get(self, request):
ls = []
for r in Rocnik.objects.filter(exportovat = True):
url = reverse('seminar_export_rocnik', kwargs={'prvni_rok': r.prvni_rok})
ls.append(url.split('/')[-1])
for s in Soustredeni.objects.filter(exportovat = True):
url = reverse('seminar_export_sous', kwargs={'datum_zacatku': s.datum_zacatku.isoformat()})
ls.append(url.split('/')[-1])
return HttpResponse('\n'.join(ls) + '\n', content_type='text/plain; charset=utf-8')
class ExportSousView(generic.View):
def get(self, request, datum_zacatku=None):
try:
dz = django.utils.dateparse.parse_date(datum_zacatku)
except:
dz = None
if dz is None:
raise django.http.Http404()
s = get_object_or_404(Soustredeni, datum_zacatku=dz, exportovat=True)
akce = {Soustredeni.TYP_JARNI: 'MaM.sous.jaro',
Soustredeni.TYP_PODZIMNI: 'MaM.sous.podzim',
Soustredeni.TYP_VIKEND: 'MaM.vikend',
}[s.typ]
of = default_ovvpfile(akce, s.rocnik)
of.headers['x-event-begin'] = s.datum_zacatku.isoformat()
of.headers['x-event-end'] = s.datum_konce.isoformat()
of.headers['x-event-location'] = s.misto
of.headers['comment'] = u'MaM-Web export ucastniku soustredeni v {x-event-location} od {x-event-begin} do {x-event-end}'.format(**of.headers)
of.columns = ['id', 'name', 'surname', 'gender', 'email', 'end-year', 'school', 'school-name']
for u in s.ucastnici.all():
of.rows.append(u.export_row())
return of.to_HttpResponse()
# POZOR! Předělání na nový model neotestováno v reálu (ale zase jen drobné změny)
class ExportRocnikView(generic.View):
def get(self, request, prvni_rok=None):
try:
pr = int(prvni_rok)
except:
pr = None
if pr is None:
raise django.http.Http404()
rocnik = get_object_or_404(Rocnik, prvni_rok=pr, exportovat=True)
cislo = rocnik.posledni_zverejnena_vysledkovka_cislo()
resitele = aktivniResitele(cislo, True)
slovnik_body = utils.secti_body_za_rocnik(cislo, resitele, False)
setrizeni_resitele, body = utils.setrid_resitele_a_body(slovnik_body)
of = default_ovvpfile('MaM.rocnik', rocnik)
of.headers['comment'] = u'MaM-Web export aktivnich resitelu rocniku {rocnik} do cisla {cislo}'.format(rocnik=rocnik, cislo=cislo)
of.columns = ['id', 'name', 'surname', 'gender', 'born', 'email', 'end-year',
'street', 'town', 'postcode', 'country', 'spam-flag', 'spam-date',
'school', 'school-name', 'points', 'rank',]
resitele_slovnik = {}
for r in resitele:
resitele_slovnik[r.id] = r
# počítání pořadí řešitelů
posledni_body = 100000
posledni_poradi = 0
for i in range(len(setrizeni_resitele)):
rd = resitele_slovnik[setrizeni_resitele[i]].export_row()
if posledni_body > body[i]:
posledni_body = body[i]
posledni_poradi = i + 1
rd['rank'] = posledni_poradi
rd['points'] = body[i]
of.rows.append(rd)
return of.to_HttpResponse()

View file

@ -1,6 +1,3 @@
"""
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,8 +1,8 @@
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
import seminar.models as m from personalni.models import Skola
import seminar.views as v import seminar.views as v
from seminar.utils import sync_skoly from personalni.utils import sync_skoly
class OrgSkolyAutocompleteTestCase(TestCase): class OrgSkolyAutocompleteTestCase(TestCase):
@classmethod @classmethod
@ -48,7 +48,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 = m.Skola.objects.get(id=id) spravna_skola = 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,30 +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 seminar.utils import org_required from personalni.utils import org_required
urlpatterns = [ urlpatterns = [
# Export škol # Export škol
path('api/export/skoly/', views.exportSkolView, name='export_skoly'), path('export/skoly/', views.exportSkolView, name='export_skoly'),
# Autocomplete # Autocomplete
path('api/autocomplete/skola/', views.SkolaAutocomplete.as_view(), name='autocomplete_skola'), path('autocomplete/skola/', views.SkolaAutocomplete.as_view(), name='autocomplete_skola'),
path('api/autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'), path('autocomplete/resitel/', org_required(views.ResitelAutocomplete.as_view()), name='autocomplete_resitel'),
path('api/autocomplete/resitel_public/', views.PublicResitelAutocomplete.as_view(), name='autocomplete_resitel_public'), path('autocomplete/resitel_public/', views.PublicResitelAutocomplete.as_view(), name='autocomplete_resitel_public'),
path('api/autocomplete/problem/odevzdatelny', views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'), path('autocomplete/problem/odevzdatelny', views.OdevzdatelnyProblemAutocomplete.as_view(), name='autocomplete_problem_odevzdatelny'),
path('api/autocomplete/problem/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'), path('autocomplete/problem/vsechny', views.ProblemAutocomplete.as_view(), name='autocomplete_problem'),
# Ceka na autocomplete v3 # Ceka na autocomplete v3
# path('autocomplete/organizatori/', # path('autocomplete/organizatori/',

View file

@ -1,7 +1,2 @@
"""
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,7 +5,9 @@ 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
import seminar.models as m from personalni.models import Skola, Resitel
from tvorba.models import Problem
from seminar.models.nastaveni 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)
@ -13,7 +15,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 = m.Skola.objects.all() qs = 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()
@ -31,7 +33,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 = m.Resitel.objects.all() qs = Resitel.objects.all()
if self.q: if self.q:
parts = self.q.split() parts = self.q.split()
query = Q() query = Q()
@ -51,8 +53,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 = m.Nastaveni.get_solo().aktualni_rocnik letos = Nastaveni.get_solo().aktualni_rocnik
qs = m.Resitel.objects.filter( qs = 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
@ -70,7 +72,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 = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY) qs = Problem.objects.filter(stav=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))
@ -87,12 +89,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(m.Nastaveni) nastaveni = get_object_or_404(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 = m.Problem.objects.filter(temaQ | ulohaQ | clanekQ).order_by("-stav", "nazev") qs = 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 seminar.models as m from personalni.models import Skola
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):
@ -8,7 +8,7 @@ def exportSkolView(request):
# Některé fieldy nechceme: Kontaktní osoby, AESOP ID, org poznámky. # Některé fieldy nechceme: Kontaktní osoby, AESOP ID, org poznámky.
fields = ('id', 'izo', 'nazev', 'kratky_nazev', 'ulice', 'mesto', 'psc', 'stat', 'je_zs', 'je_ss') fields = ('id', 'izo', 'nazev', 'kratky_nazev', 'ulice', 'mesto', 'psc', 'stat', 'je_zs', 'je_ss')
# TODO: Použít JSONL, aby protistrana mohla číst po řádkách a nesežralo to tunu paměti úplně hned # TODO: Použít JSONL, aby protistrana mohla číst po řádkách a nesežralo to tunu paměti úplně hned
skoly_json = ser.serialize("json", m.Skola.objects.all(), fields=fields) skoly_json = ser.serialize("json", Skola.objects.all(), fields=fields)
response = HttpResponse( response = HttpResponse(
content = skoly_json, content = skoly_json,
content_type = 'text/json', content_type = 'text/json',

View file

@ -1,17 +0,0 @@
#!/bin/sh
if test "$#" -lt 1
then
echo "Usage: $0 file ..."
exit 2
fi
for file in "$@"
do
# Do the sed magic: keep replacing 4 spaces at the begining of line
sed -i -re '
: loop
s/^( *) /\1 /
t loop
' "$file"
done

600
data/prava_skupin.json Normal file
View file

@ -0,0 +1,600 @@
[
{
"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"
],
[
"delete_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"view_fotkaheader",
"header_fotky",
"fotkaheader"
],
[
"add_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"change_fotkaurlvazba",
"header_fotky",
"fotkaurlvazba"
],
[
"delete_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"
],
[
"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"
],
[
"change_skola",
"personalni",
"skola"
],
[
"view_skola",
"personalni",
"skola"
],
[
"add_hlasovani",
"prednasky",
"hlasovani"
],
[
"change_hlasovani",
"prednasky",
"hlasovani"
],
[
"delete_hlasovani",
"prednasky",
"hlasovani"
],
[
"view_hlasovani",
"prednasky",
"hlasovani"
],
[
"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"
],
[
"change_nastaveni",
"seminar",
"nastaveni"
],
[
"view_nastaveni",
"seminar",
"nastaveni"
],
[
"add_novinky",
"seminar",
"novinky"
],
[
"change_novinky",
"seminar",
"novinky"
],
[
"delete_novinky",
"seminar",
"novinky"
],
[
"view_novinky",
"seminar",
"novinky"
],
[
"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_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"
],
[
"delete_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"
]
]
},
"model": "auth.group",
"pk": 1
},
{
"fields": {
"name": "resitel",
"permissions": [
[
"resitel",
"auth",
"user"
]
]
},
"model": "auth.group",
"pk": 2
}
]

View file

@ -1,3 +0,0 @@
Tahle slozka obsahuje vsechny detaily a popisy, jak nasadit "druhou verzi" M&M webu.
TODO: chybi tu popis na zprovozneni flatpages, na loaddata &c.

Binary file not shown.

View file

@ -1,652 +0,0 @@
[
{
"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_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_konfera",
"ct_app_label": "seminar",
"ct_model": "konfera"
},
{
"codename": "change_konfera",
"ct_app_label": "seminar",
"ct_model": "konfera"
},
{
"codename": "delete_konfera",
"ct_app_label": "seminar",
"ct_model": "konfera"
},
{
"codename": "view_konfera",
"ct_app_label": "seminar",
"ct_model": "konfera"
},
{
"codename": "add_konfery_ucastnici",
"ct_app_label": "seminar",
"ct_model": "konfery_ucastnici"
},
{
"codename": "change_konfery_ucastnici",
"ct_app_label": "seminar",
"ct_model": "konfery_ucastnici"
},
{
"codename": "delete_konfery_ucastnici",
"ct_app_label": "seminar",
"ct_model": "konfery_ucastnici"
},
{
"codename": "view_konfery_ucastnici",
"ct_app_label": "seminar",
"ct_model": "konfery_ucastnici"
},
{
"codename": "add_nastaveni",
"ct_app_label": "seminar",
"ct_model": "nastaveni"
},
{
"codename": "change_nastaveni",
"ct_app_label": "seminar",
"ct_model": "nastaveni"
},
{
"codename": "delete_nastaveni",
"ct_app_label": "seminar",
"ct_model": "nastaveni"
},
{
"codename": "view_nastaveni",
"ct_app_label": "seminar",
"ct_model": "nastaveni"
},
{
"codename": "add_novinky",
"ct_app_label": "seminar",
"ct_model": "novinky"
},
{
"codename": "change_novinky",
"ct_app_label": "seminar",
"ct_model": "novinky"
},
{
"codename": "delete_novinky",
"ct_app_label": "seminar",
"ct_model": "novinky"
},
{
"codename": "view_novinky",
"ct_app_label": "seminar",
"ct_model": "novinky"
},
{
"codename": "add_organizator",
"ct_app_label": "seminar",
"ct_model": "organizator"
},
{
"codename": "change_organizator",
"ct_app_label": "seminar",
"ct_model": "organizator"
},
{
"codename": "delete_organizator",
"ct_app_label": "seminar",
"ct_model": "organizator"
},
{
"codename": "view_organizator",
"ct_app_label": "seminar",
"ct_model": "organizator"
},
{
"codename": "add_osoba",
"ct_app_label": "seminar",
"ct_model": "osoba"
},
{
"codename": "change_osoba",
"ct_app_label": "seminar",
"ct_model": "osoba"
},
{
"codename": "delete_osoba",
"ct_app_label": "seminar",
"ct_model": "osoba"
},
{
"codename": "view_osoba",
"ct_app_label": "seminar",
"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": "seminar",
"ct_model": "prijemce"
},
{
"codename": "change_prijemce",
"ct_app_label": "seminar",
"ct_model": "prijemce"
},
{
"codename": "delete_prijemce",
"ct_app_label": "seminar",
"ct_model": "prijemce"
},
{
"codename": "view_prijemce",
"ct_app_label": "seminar",
"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": "add_resitel",
"ct_app_label": "seminar",
"ct_model": "resitel"
},
{
"codename": "change_resitel",
"ct_app_label": "seminar",
"ct_model": "resitel"
},
{
"codename": "delete_resitel",
"ct_app_label": "seminar",
"ct_model": "resitel"
},
{
"codename": "view_resitel",
"ct_app_label": "seminar",
"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": "seminar",
"ct_model": "skola"
},
{
"codename": "change_skola",
"ct_app_label": "seminar",
"ct_model": "skola"
},
{
"codename": "delete_skola",
"ct_app_label": "seminar",
"ct_model": "skola"
},
{
"codename": "view_skola",
"ct_app_label": "seminar",
"ct_model": "skola"
},
{
"codename": "add_soustredeni",
"ct_app_label": "seminar",
"ct_model": "soustredeni"
},
{
"codename": "change_soustredeni",
"ct_app_label": "seminar",
"ct_model": "soustredeni"
},
{
"codename": "delete_soustredeni",
"ct_app_label": "seminar",
"ct_model": "soustredeni"
},
{
"codename": "view_soustredeni",
"ct_app_label": "seminar",
"ct_model": "soustredeni"
},
{
"codename": "add_soustredeni_organizatori",
"ct_app_label": "seminar",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "change_soustredeni_organizatori",
"ct_app_label": "seminar",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "delete_soustredeni_organizatori",
"ct_app_label": "seminar",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "view_soustredeni_organizatori",
"ct_app_label": "seminar",
"ct_model": "soustredeni_organizatori"
},
{
"codename": "add_soustredeni_ucastnici",
"ct_app_label": "seminar",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "change_soustredeni_ucastnici",
"ct_app_label": "seminar",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "delete_soustredeni_ucastnici",
"ct_app_label": "seminar",
"ct_model": "soustredeni_ucastnici"
},
{
"codename": "view_soustredeni_ucastnici",
"ct_app_label": "seminar",
"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"
},
{
"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_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"
}
]

View file

@ -1,513 +0,0 @@
#!/usr/bin/env python3
import psycopg2
import psycopg2.extras
OLD_DB = "mam_old"
NEW_DB = "mamweb"
oldconn = psycopg2.connect(f"dbname={OLD_DB}")
newconn = psycopg2.connect(f"dbname={NEW_DB}")
oldcur = oldconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
newcur = newconn.cursor(cursor_factory=psycopg2.extras.DictCursor)
# Uses global variables oldcur, newcur!
def execute_simple(old_query, new_query=None):
if new_query is None:
new_query = old_query
oldcur.execute(old_query)
newcur.execute(new_query)
if oldcur.rowcount != newcur.rowcount:
raise ValueError(f"Queries '{old_query}' and '{new_query}' returned different number of rows ({oldcur.rowcount} and {newcur.rowcount})")
return(oldcur.fetchall(), newcur.fetchall())
def check_same(old_row, new_row, old_fields, new_fields=None):
if type(old_fields) != list:
old_fields = [old_fields]
if new_fields is None:
new_fields = old_fields
fields = zip(old_fields, new_fields)
for old_field, new_field in fields:
if old_row[old_field] == new_row[new_field]:
continue
raise ValueError(f"Fields '{old_field}'({old_row[old_field]}) and '{new_field}'({new_row[new_field]}) differs for rows \n'{old_row}' and \n'{new_row}'")
return True
def get_user_id_for_org_id(org_id):
query = """SELECT auth_user.id FROM auth_user
INNER JOIN seminar_osoby ON seminar_osoby.user_id = auth_user.id
INNER JOIN seminar_organizator ON seminar_organizator.osoba_id = seminar_osoby.id
WHERE seminar_organizator.id = %s """
newcur.execute(query,(org_id,))
return newcur.fetchone()['id']
def check_skola():
old_query = "SELECT * FROM seminar_skoly ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','aesop_id','izo','nazev','kratky_nazev','ulice','mesto','psc','stat','je_zs','je_ss','poznamka'])
def check_resitel():
old_query = 'SELECT * FROM seminar_resitele ORDER BY id'
new_query = '''SELECT seminar_resitele.id, skola_id, rok_maturity, zasilat, seminar_resitele.poznamka,
o.jmeno AS jmeno, o.prijmeni AS prijmeni, o.user_id AS user_id, o.pohlavi_muz AS pohlavi_muz, o.email AS email, o.telefon AS telefon, o.datum_narozeni AS datum_narozeni,
o.datum_souhlasu_udaje AS datum_souhlasu_udaje, o.datum_souhlasu_zasilani AS datum_souhlasu_zasilani, o.datum_registrace AS datum_prihlaseni, o.ulice AS ulice, o.mesto AS mesto, o.psc AS psc, o.stat AS stat
FROM seminar_resitele JOIN seminar_osoby AS o ON seminar_resitele.osoba_id = o.id ORDER BY seminar_resitele.id'''
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res,new_res)
fields_osoba = [
'jmeno',
'prijmeni',
'user_id',
'pohlavi_muz',
#'email', #vyreseno separatne
'telefon',
'datum_narozeni',
'datum_souhlasu_udaje',
'datum_souhlasu_zasilani',
'datum_prihlaseni',
'ulice',
'mesto',
'psc',
'stat',
]
fields_keep = [
'id',
'skola_id',
'rok_maturity',
'zasilat',
'poznamka',
]
fields = fields_keep+fields_osoba
for o,n in res:
check_same(o,n,fields)
if o['email'] != n['email'] and o['email'] != '':
print(f"WARNING: Emails differ: old: {o['email']}, new: {n['email']}")
def check_reseni():
# Migrace 0058 zamerne meni (zmensuje) pocet reseni, aby kazdy clanek mel
# jen jedno reseni (s vice resiteli, coz postaru neslo)
# Kvuli tomu je potreba kontrolovat dve veci:
# 1) Ze kazdy resitel dostal za kazdy problem spravne bodu
# 2) Ze detaily reseni zustaly zachovany
# Cast 1)
old_query = 'SELECT * FROM seminar_reseni ORDER BY problem_id, resitel_id, body, timestamp'
new_query = '''SELECT seminar_reseni.id, forma, seminar_reseni.poznamka, cas_doruceni, hodnoceni.problem_id AS problem_id, hodnoceni.body AS body, hodnoceni.cislo_body_id AS cislo_body_id, res.id AS resitel_id
FROM seminar_reseni
JOIN seminar_hodnoceni AS hodnoceni ON seminar_reseni.id = hodnoceni.reseni_id
JOIN seminar_reseni_resitele AS rr ON seminar_reseni.id = rr.reseni_id
JOIN seminar_resitele AS res ON res.id = rr.resitele_id
ORDER BY problem_id, resitel_id, body, cas_doruceni'''
# Po spojeni nekterych problemu se lisi casy doruceni a poznamky, proto je nebudeme kontrolovat (jde v podstate o triviality, tak je to snad jedno)
same_fields = ['forma', 'problem_id', 'body', 'cislo_body_id', 'resitel_id']
renamed_fields = [
#('timestamp', 'cas_doruceni'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,old_fields, new_fields)
# Cast 2)
# Query se lisi tim, ze uz nejoinujeme resitele.
old_query = 'SELECT * FROM seminar_reseni ORDER BY id'
new_query = '''SELECT seminar_reseni.id, forma, poznamka, cas_doruceni AS timestamp, h.problem_id AS problem_id, h.body AS body, h.cislo_body_id AS cislo_body_id
FROM seminar_reseni
JOIN seminar_hodnoceni AS h ON h.reseni_id = seminar_reseni.id
ORDER BY id'''
# execute_simple kontroluje stejnost poctu radku, to nechceme.
oldcur.execute(old_query)
newcur.execute(new_query)
old_res, new_res = oldcur.fetchall(), newcur.fetchall()
# Zkontrolujeme, ze pro kazde nove reseni ma stare reseni spravna data.
new_ids = [n['id'] for n in new_res]
spravna_old = list(filter(lambda o: o['id'] in new_ids, old_res))
res = zip(spravna_old,new_res)
for o,n in res:
# Tady by se poznamky i timestampy mely zachovat
# Z nejakeho duvodu se ale poznamky lisi ve whitespace, tak je zkontrolujeme separatne
check_same(o,n,['id', 'forma', 'timestamp', 'problem_id', 'body', 'cislo_body_id'])
old_pozn = o['poznamka'].strip()
new_pozn = n['poznamka'].strip()
if old_pozn != new_pozn:
raise ValueError('Poznamky se lisi pro radky {dict(o)} a {dict(n)}')
def check_organizator():
old_query = 'SELECT * FROM seminar_organizator ORDER BY id'
new_query = '''SELECT seminar_organizator.id AS id, studuje, strucny_popis_organizatora, users.id AS uid, osoba.prezdivka AS o_prezdivka, osoba.foto AS o_foto, organizuje_od, organizuje_do
FROM seminar_organizator
JOIN seminar_osoby AS osoba ON osoba_id = osoba.id
JOIN auth_user AS users ON osoba.user_id = users.id
ORDER BY seminar_organizator.id'''
same_fields = ['studuje', 'strucny_popis_organizatora']
renamed_fields = [
('user_id', 'uid'),
#('prezdivka', 'o_prezdivka'),
('foto', 'o_foto'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res, new_res)
for o,n in res:
check_same(o,n,old_fields, new_fields)
# organizuje od, do:
# Migrace prirazuje aktualni casovou zonu, takze chceme tady rucne vynutit CET.
from datetime import timedelta, timezone
cet = timezone(timedelta(hours=1))
if o['organizuje_od_roku'] is None and n['organizuje_od'] is None:
pass
elif o['organizuje_od_roku'] != n['organizuje_od'].astimezone(cet).year:
raise ValueError(f'Not matching organizuje_od for org id={o["id"]}: old {o["organizuje_od_roku"]}, new {n["organizuje_od"]}')
if o['organizuje_do_roku'] is None and n['organizuje_do'] is None:
pass
elif o['organizuje_do_roku'] != n['organizuje_do'].astimezone(cet).year:
raise ValueError(f'Not matching organizuje_do for org id={o["id"]}: old {o["organizuje_do_roku"]}, new {n["organizuje_do"]}')
if o['prezdivka'] == n['o_prezdivka']:
continue
if o['prezdivka'] is None and n['o_prezdivka'] == '':
continue
raise ValueError(f'Not matching prezdivka for org id={o["id"]}: old {o["prezdivka"]}, new {n["o_prezdivka"]}')
def check_rocnik():
old_query = "SELECT * FROM seminar_rocniky ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','prvni_rok', 'rocnik', 'exportovat'])
def check_cislo():
old_query = "SELECT * FROM seminar_cisla ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, ['id','rocnik_id','cislo', 'datum_vydani','datum_deadline','verejne','poznamka','pdf'],
['id','rocnik_id','poradi','datum_vydani','datum_deadline','verejne','poznamka','pdf'])
def check_priloha_reseni():
old_query = "SELECT * FROM seminar_priloha_reseni"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, ['id','reseni_id', 'timestamp', 'soubor', 'poznamka'],
['id','reseni_id', 'vytvoreno', 'soubor', 'poznamka'])
def check_soustredeni():
old_query = "SELECT * FROM seminar_soustredeni ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','rocnik_id','datum_zacatku','datum_konce','verejne','misto','text','typ','exportovat'])
#Kontrola ucasnici, organizatori v samostatnych funkcich
def check_soustredeni_ucastnici():
old_query = "SELECT * FROM seminar_soustredeni_ucastnici ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','resitel_id','soustredeni_id','poznamka'])
def check_soustredeni_organizatori():
old_query = "SELECT * FROM seminar_soustredeni_organizatori ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','organizator_id','soustredeni_id','poznamka'])
def check_nastaveni():
old_query = "SELECT * FROM seminar_nastaveni ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','aktualni_cislo_id'])
def check_novinky():
old_query = "SELECT * FROM seminar_novinky ORDER BY id"
old_res, new_res = execute_simple(old_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','datum','text','obrazek','zverejneno'])
if get_user_id_for_org_id(n['autor_id']) != o['autor_id']:
raise ValueError("Nesedi autori u novinek")
def check_pohadka():
old_query = "SELECT * FROM seminar_pohadky ORDER BY id"
new_query = """SELECT sp.id AS id, sp.autor_id AS autor_id, sp.vytvoreno AS vytvoreno, snp.treenode_ptr_id AS treenode_ptr_id, st.na_web AS text,
zn_pred.uloha_id AS uloha_pred, zn_po.uloha_id AS uloha_po
FROM seminar_pohadky AS sp
-- Text pohádky
INNER JOIN seminar_nodes_pohadka AS snp ON sp.id = snp.pohadka_id
INNER JOIN seminar_nodes_treenode AS snt ON snt.id = snp.treenode_ptr_id
INNER JOIN seminar_nodes_obsah AS sno ON sno.treenode_ptr_id = snt.first_child_id
INNER JOIN seminar_texty AS st ON sno.text_id = st.id
-- Predchozí úloha
LEFT OUTER JOIN seminar_nodes_treenode AS ztn_pred ON ztn_pred.succ_id = snt.id
LEFT OUTER JOIN seminar_nodes_uloha_zadani AS zn_pred ON zn_pred.treenode_ptr_id = ztn_pred.id
-- Následující úloha
LEFT OUTER JOIN seminar_nodes_uloha_zadani AS zn_po ON zn_po.treenode_ptr_id = snt.succ_id
ORDER BY sp.id"""
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n,['id','timestamp','text'],['id','vytvoreno','text'])
if o['autor_id'] is not None:
if get_user_id_for_org_id(n['autor_id']) != o['autor_id']:
raise ValueError("Nesedi autori u pohadky")
# Správné úlohy
# NOTE: o['pred'] rika, zda je pohadka pred ulohou, nikoliv zda je relevantni uloha pred pohadkou!
spravny_klic = 'uloha_po' if o['pred'] else 'uloha_pred'
if o['uloha_id'] != n[spravny_klic]:
raise ValueError(f"Pohádka přidružená ke špatné úloze! old: {o['uloha_id']}, new: {n[spravny_klic]}, pozice: {spravny_klic}")
# Problémy jsou rozdělené podle typů:
def check_problem_common():
old_query = "SELECT id, nazev, stav, kod, autor_id, text_org, timestamp, typ FROM seminar_problemy ORDER BY id"
new_query = """SELECT sp.id AS id, sp.nazev AS nazev, sp.stav AS stav, sp.kod AS kod, au.id AS autor_id, sp.poznamka AS poznamka, sp.vytvoreno AS vytvoreno
FROM seminar_problemy AS sp
LEFT OUTER JOIN seminar_organizator AS so ON sp.autor_id = so.id
LEFT OUTER JOIN seminar_osoby AS sos ON so.osoba_id = sos.id
LEFT OUTER JOIN auth_user AS au ON sos.user_id = au.id
ORDER BY sp.id"""
same_fields = ['id', 'nazev', 'stav', 'autor_id', 'kod']
renamed_fields = [
('text_org', 'poznamka'),
('timestamp', 'vytvoreno'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query,new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, old_fields, new_fields)
# Opravovatelé
# Po staru byli opravovatele organizatori, takze je potreba je dohledat.
old_query = """SELECT seminar_problemy.id, org.id AS opravovatel_id FROM seminar_problemy
JOIN seminar_organizator AS org ON seminar_problemy.opravovatel_id = org.user_id;"""
new_query = "SELECT problem_id, organizator_id FROM seminar_problemy_opravovatele"
# Simple cursors
#oldcur = oldconn.cursor()
oldcur.execute(old_query)
old_results = oldcur.fetchall()
#newcur = newconn.cursor()
newcur.execute(new_query)
new_results = newcur.fetchall()
for oldr in old_results:
if oldr not in new_results:
raise ValueError(f'Opravovatel pair {oldr} not found in new db.')
# Zaměření se vyřeší okometricky (#1186)
def check_uloha():
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'uloha' ORDER BY id"
new_query = """SELECT cislo_zadani_id, cislo_reseni_id, problem_ptr_id, max_body, COALESCE(uzt.na_web, '') AS text_zadani, COALESCE(uvt.na_web, '') AS text_reseni, cislo_deadline_id
FROM seminar_ulohy
-- Problém:
JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id
-- Text zadání:
-- ZadaniNode a VzorakNode maji existovat vzdy, ale obsah nemusi (pokud ho nemaji)
INNER JOIN seminar_nodes_uloha_zadani AS uzn ON problem.id = uzn.uloha_id
INNER JOIN seminar_nodes_treenode AS uztn ON uztn.id = uzn.treenode_ptr_id
LEFT OUTER JOIN seminar_nodes_obsah AS uzo ON uzo.treenode_ptr_id = uztn.first_child_id
LEFT OUTER JOIN seminar_texty AS uzt ON uzo.text_id = uzt.id
-- Text vzoráku:
INNER JOIN seminar_nodes_uloha_vzorak AS uvn ON problem.id = uvn.uloha_id
INNER JOIN seminar_nodes_treenode AS uvtn ON uvtn.id = uvn.treenode_ptr_id
LEFT OUTER JOIN seminar_nodes_obsah AS uvo ON uvo.treenode_ptr_id = uvtn.first_child_id
LEFT OUTER JOIN seminar_texty AS uvt ON uvo.text_id = uvt.id
ORDER BY problem_ptr_id"""
same_fields = ['cislo_zadani_id', 'cislo_reseni_id', 'text_zadani', 'text_reseni']
renamed_fields = [
('id', 'problem_ptr_id'),
('body', 'max_body'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, old_fields, new_fields)
# Datum deadline vypadá prázdně, tak to budeme předpokládat.
if n['cislo_deadline_id'] is not None:
raise ValueError("Úloha má deadline.")
def check_tema():
old_query = """SELECT text_zadani, text_reseni, typ, c.rocnik_id AS rocnik_id
FROM seminar_problemy
LEFT OUTER JOIN seminar_cisla AS c ON c.id = cislo_zadani_id
WHERE typ IN ('tema', 'serial')
ORDER BY seminar_problemy.id"""
new_query = """SELECT tema_typ, COALESCE(zad_text.na_web, '') AS text_zadani, COALESCE(res_text.na_web, '') AS text_reseni, rn.rocnik_id AS rocnik_id
FROM seminar_temata
-- Problém:
JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id
-- Text:
-- TvCNode dva potomky, oba TextNode. První drží původní text zadání, druhý řešení.
INNER JOIN seminar_nodes_temavcisle AS tvcn ON tvcn.tema_id = id
INNER JOIN seminar_nodes_treenode AS ttn ON tvcn.treenode_ptr_id = ttn.id
LEFT OUTER JOIN seminar_nodes_treenode AS zad_tn ON ttn.first_child_id = zad_tn.id -- jen 33 z nich ma zadani
LEFT OUTER JOIN seminar_nodes_treenode AS res_tn ON zad_tn.succ_id = res_tn.id -- jen 4 z nich ma reseni
LEFT OUTER JOIN seminar_nodes_obsah AS zad_on ON zad_on.treenode_ptr_id = zad_tn.id
LEFT OUTER JOIN seminar_nodes_obsah AS res_on ON res_on.treenode_ptr_id = res_tn.id
LEFT OUTER JOIN seminar_texty AS zad_text ON zad_on.text_id = zad_text.id
LEFT OUTER JOIN seminar_texty AS res_text ON res_on.text_id = res_text.id -- vsechny 4
-- Ročník tématu:
-- Podle rootu TvCN
LEFT OUTER JOIN seminar_nodes_rocnik AS rn ON ttn.root_id = rn.treenode_ptr_id
ORDER BY problem_ptr_id"""
same_fields = ['text_zadani', 'text_reseni', 'rocnik_id']
renamed_fields = [
('typ', 'tema_typ'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
check_same(o,n, old_fields, new_fields)
def check_konfera():
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'konfera'"
new_query = "SELECT * FROM seminar_konfera"
oldcur.execute(old_query)
newcur.execute(new_query)
if oldcur.rowcount != 0 or newcur.rowcount != 0:
raise ValueError('There exists a Konfera!')
def check_org_clanek():
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'org-clanek'"
oldcur.execute(old_query)
if oldcur.rowcount != 0:
raise ValueError('There exists a Org-clanek!')
def check_res_clanek():
# Dva(!) články mají text (zadání), který se má zachovat.
old_query = "SELECT * FROM seminar_problemy WHERE typ = 'res-clanek' ORDER BY id"
new_query = """SELECT cislo_id, text.na_web AS text_zadani
FROM seminar_clanky
JOIN seminar_problemy AS problem ON problem_ptr_id = problem.id
INNER JOIN seminar_hodnoceni AS hodn ON problem.id = hodn.problem_id
INNER JOIN seminar_reseni AS rese ON rese.id = hodn.reseni_id
INNER JOIN seminar_nodes_otistene_reseni AS rn ON rese.text_cely_id = rn.treenode_ptr_id -- Tenhle radek neni potreba, ale ujistuje se mj. o spravnem typu TreeNode.
INNER JOIN seminar_nodes_treenode AS tn ON rn.treenode_ptr_id = tn.id
-- Nektere clanky vubec nemely text, tak jim migr 0058 nevyrobila dalsi treenody
LEFT OUTER JOIN seminar_nodes_obsah AS son ON son.treenode_ptr_id = tn.first_child_id
LEFT OUTER JOIN seminar_texty AS text ON text.id = son.text_id
ORDER BY problem_ptr_id"""
same_fields = ['text_zadani']
renamed_fields = [
('cislo_zadani_id', 'cislo_id'),
]
old_fields = same_fields + [f[0] for f in renamed_fields]
new_fields = same_fields + [f[1] for f in renamed_fields]
old_res, new_res = execute_simple(old_query, new_query)
res = zip(old_res,new_res)
for o,n in res:
# text_zadani po novu mohl byt None
if n['text_zadani'] is None:
n['text_zadani'] = ''
check_same(o,n, old_fields, new_fields)
assert(o['text_reseni'] == '')
def check_untyped_problem():
old_query = "SELECT * FROM seminar_problemy WHERE typ NOT IN ('uloha', 'tema', 'serial', 'konfera', 'org-clanek', 'res-clanek')"
oldcur.execute(old_query)
if oldcur.rowcount != 0:
raise ValueError('There exists a Problem without type!')
check_skola()
check_resitel()
check_reseni()
check_organizator()
check_rocnik()
check_cislo()
check_priloha_reseni()
check_soustredeni()
check_soustredeni_ucastnici()
check_soustredeni_organizatori()
check_nastaveni()
check_novinky()
check_pohadka()
check_problem_common()
check_uloha()
check_tema()
check_konfera()
check_org_clanek()
check_res_clanek()
check_untyped_problem()

View file

@ -1,22 +0,0 @@
#!/bin/bash
set -u
deactivate || true
cd /akce/mam/www/mamweb-test/
make sync_test
systemctl --user stop mamweb-test.service
rm -rvf env
make install_venv
. env/bin/activate
make install
deploy_v2/pre_migration.py
make deploy_test
./manage.py load_org_permissions admin_org_prava.json
./manage.py loaddata data/*
systemctl --user start mamweb-test.service
./manage.py generate_thumbnails
echo 'Et voilá!'
echo 'Nezapomeň opravit práva pro sitetree!'

View file

@ -1,14 +0,0 @@
Milí řešitelé M&M,
web M&Mka dostal nový kabátek a zároveň se v něm objevilo odevzdávátko. Navíc,
pokud se zaregistrujete pod e-mailem, na který jsme vám poslali tuhle zprávu,
uvidíte rovnou i body za svá dosud odevzdaná řešení.
Web budeme i nadále vylepšovat, a zajímá nás i jak to vidíte vy. Pokud byste
na webu našli nějaký nedostatek, nebo nám prostě jen chtěli napsat, nebojte se
k tomu použít adresu mam@matfyz.cz.
Těšíme se na vaše řešení! (Připomínáme termín pro účast na soustředění 21. září.)
Za časopis M&M,
Pavel Turinský

View file

@ -1,3 +0,0 @@
Jsou špatně práva k sitetree (protože se používají primární klíče, které jsou jiné :-()
Špatné položky se dají najít pomocí následujícího příkazu:
grep -E 'access_permissions": \[$' data/sitetree.json -A17 | grep -E 'acc|tit' -A2

View file

@ -1,42 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import django
#### Inicializace Djanga
sys.path.append(os.path.dirname(os.path.realpath(__file__))+'/..')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mamweb.settings')
django.setup()
## Pozor, nejde pouzit ORM, protoze kod je na jine verzi nez databaze a nejde namigrovat.
from django.db import connection
def smaz_zle_clanky():
# Tyhle clanky vubec nejsou clanky, bude potreba je udelat cele jinak a spravne.
#m.Problem.objects.filter(id__in=[1981, 1970, 2222]).delete()
## with connection.cursor() as cursor:
## # Nejdriv musime smazat reseni:
## cursor.execute('DELETE FROM seminar_reseni WHERE problem_id IN (1981, 1970, 2222);')
## # Nakonec i ty clanky samotne
## cursor.execute('DELETE FROM seminar_problemy WHERE id IN (1981, 1970, 2222);')
# Update: stejně je v DB bordel, tak z nich prostě jen udělám témata a všechno zhruba přežije…
with connection.cursor() as cursor:
cursor.execute("UPDATE seminar_problemy SET typ = 'tema' WHERE id IN (1981, 1970, 2222);")
def smaz_divne_uzivatele():
# U techto uzivatelu neexistuje Organizator s nimi spojeny
# Takze pak delaji akorat neporadek
with connection.cursor() as cursor:
# Jeste je potreba zrusit vazby
cursor.execute('UPDATE django_comments SET user_id = NULL WHERE user_id = 34;')
cursor.execute('UPDATE seminar_problemy SET autor_id = NULL WHERE autor_id = 34;')
cursor.execute('DELETE FROM django_admin_log WHERE user_id = 34;')
cursor.execute('DELETE FROM auth_user_groups WHERE user_id = 34;')
cursor.execute('DELETE FROM auth_user WHERE id IN (34, 40, 30, 50, 54, 58, 43);')
smaz_zle_clanky()
smaz_divne_uzivatele()

View file

@ -31,9 +31,10 @@ nebo (v případě meníčka)::
./manage.py dumpdata sitetree --natural-foreign > data/sitetree_new.json ./manage.py dumpdata sitetree --natural-foreign > data/sitetree_new.json
./fix_json.py data/sitetree_new.json data/sitetree.json ./fix_json.py data/sitetree_new.json data/sitetree.json
deploy_v2 nebo (v případě práv)::
---------
Věci, které byly potřeba při nasazování nového (2021) webu. ./manage.py dumpdata auth.group --natural-foreign > data/prava_skupin_new.json
./fix_json.py data/prava_skupin_new.json data/prava_skupin.json
docs docs
---- ----

View file

@ -1,3 +1,4 @@
TODO přepsat do rst 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

View file

@ -1,25 +0,0 @@
========
| 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,3 @@
#coding: utf-8
from galerie.models import Obrazek, Galerie from galerie.models import Obrazek, Galerie
from django.contrib import admin from django.contrib import admin

View file

@ -1,47 +0,0 @@
# -*- 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,7 +1,6 @@
#coding: utf-8
from django import forms from django import forms
from seminar.models import Soustredeni from soustredeni.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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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

@ -0,0 +1,29 @@
# Generated by Django 2.2.28 on 2023-08-09 19:30
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('galerie', '0010_auto_20200819_0947'),
('soustredeni', '0001_initial'),
]
run_before = [
('seminar', '0121_smazani_soustredeni'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.AlterField(
model_name='galerie',
name='soustredeni',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='soustredeni.Soustredeni'),
),
],
database_operations=[],
),
]

View file

@ -1,4 +1,3 @@
# 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
@ -8,7 +7,7 @@ from imagekit.processors import ResizeToFit, Transpose
import os import os
from seminar.models import Soustredeni from soustredeni.models import Soustredeni
VZDY=0 VZDY=0
ORG=1 ORG=1

View file

@ -1,14 +1,5 @@
"""
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 seminar.utils import org_required from personalni.utils import org_required
from . import views from . import views
urlpatterns = [ urlpatterns = [

View file

@ -1,4 +1,3 @@
# coding: utf-8
import random import random
@ -8,7 +7,7 @@ from django.template import RequestContext
from datetime import datetime from datetime import datetime
from galerie.models import Obrazek, Galerie from galerie.models import Obrazek, Galerie
from seminar.models import Soustredeni from soustredeni.models import Soustredeni
from galerie.forms import KomentarForm, NewGalerieForm from galerie.forms import KomentarForm, NewGalerieForm
def zobrazit(galerie, request): def zobrazit(galerie, request):

View file

@ -1,14 +1,3 @@
"""
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,6 +1,3 @@
"""
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,8 +1,3 @@
"""
Context processory lze přidat do djanga v :mod:`~mamweb.settings` a dělají to,
že do contextu (tj. to, z čeho se např. berou proměnné v templatech) libovolné
stránky přidají další věci.
"""
from datetime import datetime, date from datetime import datetime, date
import random import random

View file

@ -1,17 +1,3 @@
"""
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,11 +0,0 @@
- 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,13 +1,4 @@
""" from django.conf import settings
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 from korektury.models import KorekturovanePDF
@ -15,7 +6,7 @@ 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
@ -50,8 +41,8 @@ class KorekturovanePDFAdmin(VersionAdmin):
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
if not change and obj.poslat_mail: # Je nový a má se poslat mail if not change and obj.poslat_mail: # Je nový a má se poslat mail
odkaz = request.build_absolute_uri(reverse('korektury', kwargs={'pdf': obj.id})) odkaz = request.build_absolute_uri(reverse('korektury', kwargs={'pdf': obj.id}))
odesilatel = 'korekturovatko-nove-pdf@mam.mff.cuni.cz' odesilatel = settings.KOREKTURY_NOVE_PDF_EMAIL
prijemce = 'org@mam.mff.cuni.cz' prijemce = settings.KONFERA_ORGOVE_EMAIL
predmet = f'Nové korektury: {obj.nazev}' predmet = f'Nové korektury: {obj.nazev}'
text = f'''\ text = f'''\
V korekturovátku se objevil nový soubor: {obj.nazev} V korekturovátku se objevil nový soubor: {obj.nazev}

View file

@ -1,13 +1,3 @@
"""
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 from django import forms
class OpravaForm(forms.Form): class OpravaForm(forms.Form):

View file

@ -1,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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,4 +1,3 @@
# -*- 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

@ -0,0 +1,39 @@
# Generated by Django 2.2.28 on 2023-07-31 17:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('korektury', '0020_lepsi_popis_nazvu_PDF_v_adminu'),
('personalni', '0002_initial'),
]
run_before = [
('seminar', '0116_smazani_personalniho'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.AlterField(
model_name='komentar',
name='autor',
field=models.ForeignKey(blank=True, help_text='Autor komentáře', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Organizator'),
),
migrations.AlterField(
model_name='korekturovanepdf',
name='org',
field=models.ForeignKey(blank=True, default=None, help_text='Zodpovědný organizátor za obsah', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Organizator'),
),
migrations.AlterField(
model_name='oprava',
name='autor',
field=models.ForeignKey(blank=True, help_text='Autor opravy', null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Organizator'),
),
],
database_operations=[],
),
]

View file

@ -1,27 +1,12 @@
"""
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 django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from django.utils.encoding import force_text
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.text import get_valid_filename from django.utils.text import get_valid_filename
from seminar.models import Organizator from personalni.models import Organizator
import subprocess import subprocess
from reversion import revisions as reversion from reversion import revisions as reversion

View file

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,6 +1,3 @@
"""
Soubor sloužící ke generování testdat.
"""
import logging import logging
import os import os
from shutil import copyfile, rmtree from shutil import copyfile, rmtree

View file

@ -1,18 +1,10 @@
"""
Soubor sloužící jako router, tj. zde se definují url adresy a na co ukazují:
- ``korektury/`` (korektury_list) :class:`~korektury.views.KorekturySeskupeneListView`
- ``korektury/neseskupene/`` (korektury_neseskupene_list) :class:`~korektury.views.KorekturyAktualniListView`
- ``korektury/zastarale/`` (korektury_stare_list) :class:`~korektury.views.KorekturyZastaraleListView`
- ``korektury/<int:pdf>/`` (korektury) :class:`~korektury.views.KorekturyView`
"""
from django.urls import path from django.urls import path
from seminar.utils import org_required from personalni.utils import org_required
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('korektury/', org_required(views.KorekturySeskupeneListView.as_view()), name='korektury_list'), path('', org_required(views.KorekturySeskupeneListView.as_view()), name='korektury_list'),
path('korektury/neseskupene/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury_neseskupene_list'), path('neseskupene/', org_required(views.KorekturyAktualniListView.as_view()), name='korektury_neseskupene_list'),
path('korektury/zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury_stare_list'), path('zastarale/', org_required(views.KorekturyZastaraleListView.as_view()), name='korektury_stare_list'),
path('korektury/<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'), path('<int:pdf>/', org_required(views.KorekturyView.as_view()), name='korektury'),
] ]

View file

@ -1,8 +1,3 @@
"""
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`)
"""
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.views import generic from django.views import generic
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -166,7 +161,7 @@ class KorekturyView(generic.TemplateView):
from django.urls import reverse from django.urls import reverse
odkaz = self.request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk})) odkaz = self.request.build_absolute_uri(reverse('korektury', kwargs={'pdf': oprava.pdf.pk}))
odkaz = f"{odkaz}#op{oprava.id}-pointer" odkaz = f"{odkaz}#op{oprava.id}-pointer"
from_email = 'korekturovatko@mam.mff.cuni.cz' from_email = settings.KOREKTURY_EMAIL
subject = 'Nová korektura od {} v {}'.format(autor, oprava.pdf.nazev) subject = 'Nová korektura od {} v {}'.format(autor, oprava.pdf.nazev)
texty = [(oprava.autor.osoba.plne_jmeno(),oprava.text)] texty = [(oprava.autor.osoba.plne_jmeno(),oprava.text)]
for kom in Komentar.objects.filter(oprava=oprava): for kom in Komentar.objects.filter(oprava=oprava):
@ -200,13 +195,6 @@ class KorekturyView(generic.TemplateView):
if email: if email:
emails.discard(email) emails.discard(email)
if not settings.POSLI_MAILOVOU_NOTIFIKACI:
print("Poslal bych upozornění na tyto adresy: ", " ".join(emails))
print("---- Upozornění:")
print(text)
print("---- Konec upozornění")
return
EmailMessage( EmailMessage(
subject=subject, subject=subject,
body=text, body=text,

View file

@ -1,88 +0,0 @@
from datetime import datetime, date
from django.conf import settings
from django.http import HttpResponse, HttpResponseRedirect
class LoggedInHintCookieMiddleware(object):
"""Middleware to securely help with 'logged-in' detection for dual HTTP/HTTPS sites.
On insecure requests: Checks for a (non-secure) cookie settings.LOGGED_IN_HINT_COOKIE_NAME
and if present, redirects to HTTPS (same adress).
Note this usually breaks non-GET (POST) requests.
On secure requests: Updates cookie settings.LOGGED_IN_HINT_COOKIE_NAME to reflect
whether an user is logged in in the current session (cookie set to 'True' or cleared).
The cookie is set to expire at the same time as the sessionid cookie.
By default, LOGGED_IN_HINT_COOKIE_NAME = 'logged_in_hint'.
"""
def __init__(self):
if hasattr(settings, 'LOGGED_IN_HINT_COOKIE_NAME'):
self.cookie_name = settings.LOGGED_IN_HINT_COOKIE_NAME
else: self.cookie_name = 'logged_in_hint'
self.cookie_value = 'True'
def cookie_correct(self, request):
return self.cookie_name in request.COOKIES and request.COOKIES[self.cookie_name] == self.cookie_value
def process_request(self, request):
if not request.is_secure():
if self.cookie_correct(request):
# redirect insecure (assuming http) requests with hint cookie to https
url = request.build_absolute_uri()
assert url[:5] == 'http:'
return HttpResponseRedirect('https:' + url[5:])
return None
def process_response(self, request, response):
if request.is_secure():
# assuming full session info (as the conn. is secure)
try:
user = request.user
except AttributeError: # no user - ajax or other special request
return response
if user.is_authenticated():
if not self.cookie_correct(request):
expiry = None if request.session.get_expire_at_browser_close() else request.session.get_expiry_date()
response.set_cookie(self.cookie_name, value=self.cookie_value, expires=expiry, secure=False)
else:
if self.cookie_name in request.COOKIES:
response.delete_cookie(self.cookie_name)
return response
class vzhled:
def process_request(self, request):
return None
def process_view(self, request, view_func, view_args, view_kwargs):
#print "====== process_request ======"
#print view_func
#print view_args
#print view_kwargs
#print "============================="
return None
def process_template_response(self, request, response):
hodin = datetime.now().hour
if (hodin <= 6) or (hodin >= 14): # TODO 20
response.context_data['noc'] = True
else:
response.context_data['noc'] = False
return response
def process_response(self, request, response):
#hodin = datetime.now().hour
#if (hodin <= 6) or (hodin >= 14): # TODO 20
#response.context_data['noc'] = True
#else:
#response.context_data['noc'] = False
return response
##def process_exception(request, exception):
#pass

View file

@ -1,80 +1,64 @@
"""
Django settings for mamweb project.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
import traceback import traceback
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Application definition # Application definition
SITE_ID = 1 SITE_ID = 1
ROOT_URLCONF = 'mamweb.urls' ROOT_URLCONF = 'mamweb.urls'
WSGI_APPLICATION = 'mamweb.wsgi.application' WSGI_APPLICATION = 'mamweb.wsgi.application'
APPEND_SLASH = True # Lokalizace
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = 'cs' LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague' TIME_ZONE = 'Europe/Prague'
USE_I18N = True USE_L10N = True # S přechodem k djangu>=4 lze smazat (localized formatting)
USE_L10N = True USE_TZ = True # S přechodem k djangu>=5 lze smazat (timezone aware datetimes)
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
# Statické soubory (CSS, JavaScript, obrázky) a další média
STATIC_URL = '/static/' STATIC_URL = '/static/'
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
) )
# Where redirect for login required services # URL pro přihlášení (default je account/login)
LOGIN_URL = 'login' LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'profil' LOGIN_REDIRECT_URL = 'profil'
# Odhlášení po zavření prohlížeče # Odhlášení po zavření prohlížeče
# (pozor nefunguje na firefox se znovuotevíráním oken po startu firefoxu) # (pozor nefunguje na firefox se znovuotevíráním oken po startu firefoxu)
# default je False a SESSION_COOKIE_AGE = 3600*24*14 = 2 týdny # default je False a SESSION_COOKIE_AGE = 3600*24*14 = 2 týdny
SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_EXPIRE_AT_BROWSER_CLOSE = True
DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok
# Modules configuration
# SECURITY WARNING: keep the secret key used in production secret!
# Create file 'django.secret' in every install (it is not kept in git)
try:
with open(os.path.join(os.path.dirname(__file__), '..', 'django.secret')) as f:
SECRET_KEY = f.readline().strip()
except:
SECRET_KEY = '12345zmr_k53a*@f4q_+ji^o@!pgpef*5&8c7zzdqwkdlkj'
# Přidávání dalších součástí (do) djangovské mašinérie
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', 'django.contrib.auth.backends.ModelBackend',
) )
MIDDLEWARE = ( MIDDLEWARE = (
# 'reversion.middleware.RevisionMiddleware', # 'reversion.middleware.RevisionMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
# FIXME: rozbilo se při přechodu na Django 2.0, nevím, jestli
# se to dá zahodit bez náhrady
# 'mamweb.middleware.LoggedInHintCookieMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
) )
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -93,12 +77,7 @@ TEMPLATES = [
}, },
}, },
] ]
INSTALLED_APPS = ( INSTALLED_APPS = (
# Basic # Basic
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
@ -144,11 +123,11 @@ INSTALLED_APPS = (
'various', 'various',
'various.autentizace', 'various.autentizace',
'api', 'api',
'aesop',
'odevzdavatko', 'odevzdavatko',
'vysledkovky', 'vysledkovky',
'personalni', 'personalni',
'soustredeni', 'soustredeni',
'tvorba',
'treenode', 'treenode',
'vyroci', 'vyroci',
@ -166,28 +145,31 @@ INSTALLED_APPS = (
'django_cleanup.apps.CleanupConfig', # Uklízí media/ 'django_cleanup.apps.CleanupConfig', # Uklízí media/
) )
DEBUG_TOOLBAR_CONFIG = {
'SHOW_COLLAPSED': True,
}
SUMMERNOTE_CONFIG = { # MaM-specifické složky
'iframe': False, SEMINAR_RESENI_DIR = os.path.join('reseni')
'airMode': False, SEMINAR_KONFERY_DIR = os.path.join('konfery')
'attachment_require_authentication': True, KOREKTURY_PDF_DIR = os.path.join('korektury', 'pdf')
'width': '80%', KOREKTURY_IMG_DIR = os.path.join('korektury', 'img')
# 'height': '30em', CISLO_IMG_DIR = os.path.join('cislo', 'img')
'toolbar': [
['style', ['style']],
['font', ['bold', 'italic', 'superscript', 'subscript', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture', 'hr']],
['view', ['fullscreen', 'codeview']],
['help', ['help']],
]
}
# MaM-specifické konstanty
ROCNIK_ZRUSENI_TEMAT = 25
ROCNIK_INFLACE_BODU = 25
ROCNIK_INFLACE_TITULU = 26
#
NOVE_CISLO_EMAIL = 'zadani@mam.mff.cuni.cz'
NOVE_RESENI_EMAIL = 'submitovatko@mam.mff.cuni.cz'
KOREKTURY_NOVE_PDF_EMAIL = 'korekturovatko-nove-pdf@mam.mff.cuni.cz'
KOREKTURY_EMAIL = 'korekturovatko@mam.mff.cuni.cz'
REGISTRACE_EMAIL = 'registrace@mam.mff.cuni.cz'
PASSWD_RESET_EMAIL = 'login@mam.mff.cuni.cz'
#
KONFERA_ORGOVE_EMAIL = 'org@mam.mff.cuni.cz'
# CKEditor = WYSIWYG html editor
CKEDITOR_UPLOAD_PATH = "uploads/" CKEDITOR_UPLOAD_PATH = "uploads/"
CKEDITOR_IMAGE_BACKEND = 'pillow' CKEDITOR_IMAGE_BACKEND = 'pillow'
# CKEDITOR_JQUERY_URL = '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js' # CKEDITOR_JQUERY_URL = '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js'
@ -200,7 +182,6 @@ CKEDITOR_CONFIGS = {
['NumberedList', 'BulletedList', '-', 'Blockquote', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'], ['NumberedList', 'BulletedList', '-', 'Blockquote', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
['Link', 'Unlink', 'Anchor', '-', 'Image', 'Table', 'HorizontalRule'], ['Link', 'Unlink', 'Anchor', '-', 'Image', 'Table', 'HorizontalRule'],
['Format'], ['Format'],
], ],
# 'toolbar': 'full', # 'toolbar': 'full',
'height': '40em', 'height': '40em',
@ -226,31 +207,21 @@ WEBPACK_LOADER = {
# Dajngo REST Framework # Dajngo REST Framework
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100 'PAGE_SIZE': 100
} }
# SECURITY WARNING: keep the secret key used in production secret! # Logování
# Create file 'django.secret' in every install (it is not kept in git)
try:
with open(os.path.join(os.path.dirname(__file__), '..', 'django.secret')) as f:
SECRET_KEY = f.readline().strip()
except:
SECRET_KEY = '12345zmr_k53a*@f4q_+ji^o@!pgpef*5&8c7zzdqwkdlkj'
# Logging
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'formatters': { 'formatters': {
'verbose': { 'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s (logger %(name)s): %(message)s' 'format':
'%(levelname)s %(asctime)s %(module)s (logger %(name)s): %(message)s'
}, },
}, },
@ -292,7 +263,7 @@ LOGGING = {
# Catch-all logger # Catch-all logger
'': { '': {
'handlers': ['console'], # Add 'mail_admins' in prod and test 'handlers': ['console'],
'level': 'DEBUG', 'level': 'DEBUG',
'filters': ['StripSensitiveFormData'], 'filters': ['StripSensitiveFormData'],
}, },
@ -302,7 +273,7 @@ LOGGING = {
'handlers': { 'handlers': {
'console': { 'console': {
'level': 'WARNING', ## Set to 'DEBUG' in local 'level': 'WARNING',
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
'formatter': 'verbose', 'formatter': 'verbose',
}, },
@ -334,24 +305,8 @@ LOGGING = {
}, },
} }
# Permissions for uploads
FILE_UPLOAD_PERMISSIONS = 0o0644
# MaM specific # Logování neexistujících proměnných v templatech
SEMINAR_RESENI_DIR = os.path.join('reseni')
SEMINAR_KONFERY_DIR = os.path.join('konfery')
KOREKTURY_PDF_DIR = os.path.join('korektury', 'pdf')
KOREKTURY_IMG_DIR = os.path.join('korektury', 'img')
CISLO_IMG_DIR = os.path.join('cislo', 'img')
# E-MAIL NOTIFICATIONS
POSLI_MAILOVOU_NOTIFIKACI = False
# Logování chyb
class InvalidTemplateVariable(str): class InvalidTemplateVariable(str):
def __mod__(self, variable): def __mod__(self, variable):
import logging import logging
@ -363,5 +318,38 @@ class InvalidTemplateVariable(str):
return '' return ''
TEMPLATES[0]['OPTIONS']['string_if_invalid'] = InvalidTemplateVariable('%s') TEMPLATES[0]['OPTIONS']['string_if_invalid'] = InvalidTemplateVariable('%s')
# Django 3.2 vyžaduje explicitní nastavení autoklíče, zatím nechápu proč # Django 3.2 vyžaduje explicitní nastavení autoklíče, zatím nechápu proč
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# TODO odstranit? (Je to default.)
# Whether to append trailing slashes to URLs.
APPEND_SLASH = True
# TODO odstranit? (Je to default.)
# Permissions for uploads
FILE_UPLOAD_PERMISSIONS = 0o0644
# TODO odstranit? (Je to default.)
# Automatická lokalizace
USE_I18N = True
# TODO odstranit? (Nevím o tom, že bychom ho někde používali.)
# Summernote = WYSIWYG editor (pro admin?)
SUMMERNOTE_CONFIG = {
'iframe': False,
'airMode': False,
'attachment_require_authentication': True,
'width': '80%',
# 'height': '30em',
'toolbar': [
['style', ['style']],
['font', ['bold', 'italic', 'superscript', 'subscript', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture', 'hr']],
['view', ['fullscreen', 'codeview']],
['help', ['help']],
]
}

View file

@ -0,0 +1,22 @@
from .settings_common import *
DEBUG = True
TEMPLATES[0]['OPTIONS']['debug'] = True
MIDDLEWARE += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
INSTALLED_APPS += (
'debug_toolbar', # Takovéto užitečné (pro debug) na html stránce vpravo
'django_extensions', # Kolekce zajímavých ./manage.py commandů
)
DEBUG_TOOLBAR_CONFIG = {
'SHOW_COLLAPSED': True,
}
# Nechceme nikomu omylem poslat e-mail
# Když někdo spustí omylem tohle nastavení, prostě to při poslání mailu spadne
# V settings_local a settings_test přepíšeme...
EMAIL_BACKEND = None

View file

@ -1,5 +1,3 @@
import os.path
# #
# Lokalni / vyvojove nastaveni settings.py # Lokalni / vyvojove nastaveni settings.py
# #
@ -7,35 +5,20 @@ import os.path
# DJANGO_SETTINGS_MODULE=mamweb.settings_local ./manage.py ... # DJANGO_SETTINGS_MODULE=mamweb.settings_local ./manage.py ...
# #
# Import common settings import os.path
from .settings_common import *
MIDDLEWARE += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
INSTALLED_APPS += (
'debug_toolbar',
'django_extensions',
)
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
INTERNAL_IPS = ['127.0.0.1']
TEMPLATES[0]['OPTIONS']['debug'] = True
from ipaddress import ip_network from ipaddress import ip_network
from .settings_common_debug import *
LOCAL_TEST_PROD = "local"
INTERNAL_IPS = ['127.0.0.1']
ALLOWED_HOSTS = [str(ip) for ip in ip_network('192.168.0.0/16')] ALLOWED_HOSTS = [str(ip) for ip in ip_network('192.168.0.0/16')]
ALLOWED_HOSTS.append('127.0.0.1') ALLOWED_HOSTS += ['127.0.0.1', 'localhost']
ALLOWED_HOSTS.append('localhost')
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
# Databáze
# SQLite
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
@ -45,6 +28,7 @@ DATABASES = {
}, },
}, },
} }
# # PostgreSQL
# DATABASES = { # DATABASES = {
# 'default': { # 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2', # 'ENGINE': 'django.db.backends.postgresql_psycopg2',
@ -53,7 +37,12 @@ DATABASES = {
# }, # },
# } # }
# LOGGING
# E-maily posílat chceme, ale do terminálu :-)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Logování
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': True, 'disable_existing_loggers': True,
@ -78,12 +67,6 @@ LOGGING = {
}, },
}, },
'loggers': { 'loggers': {
# Vypisovani databazovych dotazu do konzole
#'django.db.backends': {
# 'level': 'DEBUG',
# 'handlers': ['console'],
# 'propagate': False,
#},
'werkzeug': { 'werkzeug': {
'handlers': ['console'], 'handlers': ['console'],
'level': 'DEBUG', 'level': 'DEBUG',
@ -96,11 +79,3 @@ LOGGING = {
}, },
}, },
} }
# set to 'DEBUG' for EXTRA verbose output
# LOGGING['handlers']['console']['level'] = 'INFO'
# E-maily posílat chceme, ale do terminálu :-)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SEND_EMAIL_NOTIFICATIONS = True
LOCAL_TEST_PROD = "local"

View file

@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
import os.path
# #
# Produkcni nastaveni settings.py # Produkcni nastaveni settings.py
# #
@ -11,28 +7,27 @@ import os.path
# Import common settings # Import common settings
from .settings_common import * from .settings_common import *
LOCAL_TEST_PROD = "prod"
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
INSTALLED_APPS += (
'django_extensions',
)
# SECURITY WARNING: keep the secret key used in production secret!
assert not SECRET_KEY.startswith('12345') assert not SECRET_KEY.startswith('12345')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = False
TEMPLATE_DEBUG = False TEMPLATE_DEBUG = False
# SECURITY: only send sensitive cookies via HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
ALLOWED_HOSTS = ['mam.mff.cuni.cz', 'www.mam.mff.cuni.cz', 'atrey.karlin.mff.cuni.cz',
'mamweb.bezva.org','gimli.ms.mff.cuni.cz']
# Database ALLOWED_HOSTS = ['mam.mff.cuni.cz', 'www.mam.mff.cuni.cz', 'gimli.ms.mff.cuni.cz']
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
# Přidání aplikací
INSTALLED_APPS += (
'django_extensions', # Kolekce zajímavých ./manage.py commandů
)
# Databáze
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', 'ENGINE': 'django.db.backends.postgresql_psycopg2',
@ -44,28 +39,14 @@ DATABASES = {
}, },
} }
import os # Nastavení e-mailů (odkud a kam mají chodit)
SERVER_EMAIL = 'mamweb-prod-errors@mam.mff.cuni.cz' SERVER_EMAIL = 'mamweb-prod-errors@mam.mff.cuni.cz'
ADMINS = [('M&M ERRORs', 'mam-errors@mam.mff.cuni.cz')] ADMINS = [('M&M ERRORs', 'mam-errors@mam.mff.cuni.cz')]
# SECURITY: only send sensitive cookies via HTTPS # Logování
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# LOGGING
LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins'] LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins'] LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django.security.csrf']['level'] = 'ERROR' LOGGING['loggers']['django.security.csrf']['level'] = 'ERROR'
LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/prod/registration.log' LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/prod/registration.log'
LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/prod/registration_errors.log' LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/prod/registration_errors.log'
# E-MAIL NOTIFICATIONS
POSLI_MAILOVOU_NOTIFIKACI = True
LOCAL_TEST_PROD = "prod"

View file

@ -1,42 +1,24 @@
# -*- coding: utf-8 -*-
import os.path
# #
# Testovaci nastaveni settings.py (testovani na atreyi) # Testovaci nastaveni settings.py (testovani na mam-test.ks.marfyz.cz)
# #
# Pro vyber tohoto nastaveni muzete pouzit tez: # Pro vyber tohoto nastaveni muzete pouzit tez:
# DJANGO_SETTINGS_MODULE=mamweb.settings_test ./manage.py ... # DJANGO_SETTINGS_MODULE=mamweb.settings_test ./manage.py ...
# #
# Import common settings from .settings_common_debug import *
from .settings_common import * # zatim nutne, casem snad vyresime # noqa LOCAL_TEST_PROD = "test"
MIDDLEWARE += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
INSTALLED_APPS += (
'debug_toolbar',
'django_extensions',
)
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ')^u=i65*zmr_k53a*@f4q_+ji^o@!pgpef*5&8c7zzv9l+zo)n' SECRET_KEY = ')^u=i65*zmr_k53a*@f4q_+ji^o@!pgpef*5&8c7zzv9l+zo)n'
# SECURITY: only send sensitive cookies via HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATES[0]['OPTIONS']['debug'] = True ALLOWED_HOSTS = ['mam-test.kam.mff.cuni.cz', 'gimli.ms.mff.cuni.cz', 'mam-test.ks.matfyz.cz']
ALLOWED_HOSTS = ['*.mam.mff.cuni.cz', 'atrey.karlin.mff.cuni.cz', 'mam.mff.cuni.cz', 'mam-test.kam.mff.cuni.cz', 'gimli.ms.mff.cuni.cz', 'mam-test.ks.matfyz.cz']
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
# Databáze
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', 'ENGINE': 'django.db.backends.postgresql_psycopg2',
@ -48,32 +30,27 @@ DATABASES = {
}, },
} }
import os
# Nastavení e-mailů (odkud a kam mají chodit)
SERVER_EMAIL = 'mamweb-test-errors@mam.mff.cuni.cz' SERVER_EMAIL = 'mamweb-test-errors@mam.mff.cuni.cz'
ADMINS = [ ADMINS = [
('M&M ERRORs', 'mam-errors@mam.mff.cuni.cz'), ('M&M ERRORs', 'mam-errors@mam.mff.cuni.cz'),
] ]
# SECURITY: only send sensitive cookies via HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# LOGGING
LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins']
LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/test/registration.log'
LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/test/registration_errors.log'
FILE_UPLOAD_PERMISSIONS = 0o440
# Testování e-mailů # Testování e-mailů
POSLI_MAILOVOU_NOTIFIKACI = True
EMAIL_BACKEND = 'various.mail_prefixer.PrefixingMailBackend' EMAIL_BACKEND = 'various.mail_prefixer.PrefixingMailBackend'
# TODO Pouze na otestování testu… Zvolit konferu! # TODO Pouze na otestování testu… Zvolit konferu!
# XXX: Je to pole, protože implementační detail backendu. # XXX: Je to pole, protože implementační detail backendu.
TESTOVACI_EMAILOVA_KONFERENCE = ['betatest@mam.mff.cuni.cz'] TESTOVACI_EMAILOVA_KONFERENCE = ['betatest@mam.mff.cuni.cz']
LOCAL_TEST_PROD = "test"
# Netuším proč (Jidáš)...
FILE_UPLOAD_PERMISSIONS = 0o440
# Logování
LOGGING['loggers']['']['handlers'] = ['console', 'mail_admins']
LOGGING['loggers']['django']['handlers'] = ['console', 'mail_admins']
LOGGING['handlers']['registration_logfile']['filename'] = '/home/mam-web/logs/test/registration.log'
LOGGING['handlers']['registration_error_log']['filename'] = '/home/mam-web/logs/test/registration_errors.log'

View file

@ -1,20 +1,3 @@
"""
Soubor sloužící jako základní router, tj. zde se includují veškeré ostatní urls:
- ``admin/`` :mod:`django.contrib.admin.site.urls`
- ``ckeditor/`` :mod:`ckeditor_uploader.urls`
- :mod:`seminar.urls`
- :mod:`odevzdavatko.urls`
- :mod:`korektury.urls`
- :mod:`prednasky.urls`
- :mod:`soustredeni.urls`
- :mod:`personalni.urls`
- :mod:`various.autentizace.urls`
- :mod:`api.urls`
- :mod:`treenode.urls`
- :mod:`aesop.urls`
- ``comments_dj/`` :mod:`django_comments.urls`
"""
from django.urls import path, include from django.urls import path, include
from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib import admin from django.contrib import admin
@ -34,17 +17,20 @@ urlpatterns = [
# Seminarova aplikace (ma vlastni podadresare) # Seminarova aplikace (ma vlastni podadresare)
path('', include('seminar.urls')), path('', include('seminar.urls')),
# Tvorba, Čísla, Ročníky, atd. (ma vlastni podadresare)
path('', include('tvorba.urls')),
# Odevzdavatko (ma vlastni podadresare) # Odevzdavatko (ma vlastni podadresare)
path('', include('odevzdavatko.urls')), path('', include('odevzdavatko.urls')),
# Korekturovaci aplikace (ma vlastni podadresare) # Korekturovaci aplikace (ma vlastni podadresare)
path('', include('korektury.urls')), path('korektury/', include('korektury.urls')),
# Prednaskova aplikace (ma vlastni podadresare) # Prednaskova aplikace (ma vlastni podadresare)
path('', include('prednasky.urls')), path('prednasky/', include('prednasky.urls')),
# Soustredkova aplikace (ma vlastni podadresare) # Soustredkova aplikace (ma vlastni podadresare)
path('', include('soustredeni.urls')), path('soustredeni/', include('soustredeni.urls')),
# Personalni aplikace (ma vlastni podadresare) # Personalni aplikace (ma vlastni podadresare)
# (profil, osobní údaje, ..., ne autentizace, viz dále) # (profil, osobní údaje, ..., ne autentizace, viz dále)
@ -54,14 +40,11 @@ urlpatterns = [
path('', include('various.autentizace.urls')), path('', include('various.autentizace.urls')),
# Api (ma vlastni podadresare) (autocomplete apod.) # Api (ma vlastni podadresare) (autocomplete apod.)
path('', include('api.urls')), path('api/', include('api.urls')),
# treenode (ma vlastni podadresare) # treenode (ma vlastni podadresare)
path('', include('treenode.urls')), path('', include('treenode.urls')),
# Aesop (ma vlastni podadresare)
path('', include('aesop.urls')),
# Comments (interni i verejne) # Comments (interni i verejne)
path('comments_dj/', include('django_comments.urls')), path('comments_dj/', include('django_comments.urls')),

View file

@ -1,26 +1,20 @@
"""
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`.
"""
from django.contrib import admin from django.contrib import admin
from django_reverse_admin import ReverseModelAdmin from django_reverse_admin import ReverseModelAdmin
import seminar.models as m from .models import *
class PrilohaReseniInline(admin.TabularInline): class PrilohaReseniInline(admin.TabularInline):
model = m.PrilohaReseni model = PrilohaReseni
extra = 1 extra = 1
class Reseni_ResiteleInline(admin.TabularInline): class Reseni_ResiteleInline(admin.TabularInline):
model = m.Reseni_Resitele model = Reseni_Resitele
@admin.register(m.Reseni) @admin.register(Reseni)
class ReseniAdmin(ReverseModelAdmin): class ReseniAdmin(ReverseModelAdmin):
base_model = m.Reseni base_model = Reseni
inline_type = 'tabular' inline_type = 'tabular'
# inline_reverse = ['text_cely','resitele'] TODO vrátit zpět a zrychlit dotaz # inline_reverse = ['text_cely','resitele'] TODO vrátit zpět a zrychlit dotaz
inline_reverse = ['resitele'] inline_reverse = ['resitele']
@ -30,5 +24,5 @@ class ReseniAdmin(ReverseModelAdmin):
# inlines = [PrilohaReseniInline,Reseni_ResiteleInline] # inlines = [PrilohaReseniInline,Reseni_ResiteleInline]
admin.site.register(m.PrilohaReseni) admin.site.register(PrilohaReseni)
admin.site.register(m.Hodnoceni) admin.site.register(Hodnoceni)

View file

@ -1,6 +1,3 @@
"""
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

@ -4,8 +4,10 @@ from django.forms import formset_factory
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django.utils import timezone from django.utils import timezone
from seminar.models import Resitel from personalni.models import Resitel
import seminar.models as m from tvorba.models import Problem, Deadline
from seminar.models.nastaveni import Nastaveni
from .models import *
import logging import logging
@ -22,7 +24,7 @@ class DateInput(forms.DateInput):
class PosliReseniForm(forms.Form): class PosliReseniForm(forms.Form):
problem = forms.ModelMultipleChoiceField( problem = forms.ModelMultipleChoiceField(
queryset=m.Problem.objects.all(), queryset=Problem.objects.all(),
label="Problémy", label="Problémy",
widget=autocomplete.ModelSelect2Multiple( widget=autocomplete.ModelSelect2Multiple(
url='autocomplete_problem', url='autocomplete_problem',
@ -58,7 +60,7 @@ class PosliReseniForm(forms.Form):
#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True) #cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True)
forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES) forma = forms.ChoiceField(label="Forma řešení",choices = Reseni.FORMA_CHOICES)
#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, #forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
# default=FORMA_EMAIL) # default=FORMA_EMAIL)
@ -69,7 +71,7 @@ class PosliReseniForm(forms.Form):
class NahrajReseniForm(forms.ModelForm): class NahrajReseniForm(forms.ModelForm):
class Meta: class Meta:
model = m.Reseni model = Reseni
fields = ('problem', 'resitele') fields = ('problem', 'resitele')
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
@ -109,11 +111,11 @@ class NahrajReseniForm(forms.ModelForm):
def clean_problem(self): def clean_problem(self):
problem = self.cleaned_data.get('problem') problem = self.cleaned_data.get('problem')
for p in problem: for p in problem:
if p.stav != m.Problem.STAV_ZADANY: if p.stav != Problem.STAV_ZADANY:
raise forms.ValidationError("Problém " + str(p) + " již nelze řešit!") raise forms.ValidationError("Problém " + str(p) + " již nelze řešit!")
return problem return problem
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni, ReseniSPrilohamiFormSet = inlineformset_factory(Reseni,PrilohaReseni,
form = NahrajReseniForm, form = NahrajReseniForm,
fields = ('soubor','res_poznamka'), fields = ('soubor','res_poznamka'),
widgets = {'res_poznamka':forms.TextInput()}, widgets = {'res_poznamka':forms.TextInput()},
@ -125,7 +127,7 @@ ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
class JednoHodnoceniForm(forms.ModelForm): class JednoHodnoceniForm(forms.ModelForm):
class Meta: class Meta:
model = m.Hodnoceni model = Hodnoceni
fields = ('problem', 'body', 'deadline_body', 'feedback',) fields = ('problem', 'body', 'deadline_body', 'feedback',)
widgets = { widgets = {
'problem': autocomplete.ModelSelect2( 'problem': autocomplete.ModelSelect2(
@ -158,7 +160,7 @@ OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
class PoznamkaReseniForm(forms.ModelForm): class PoznamkaReseniForm(forms.ModelForm):
class Meta: class Meta:
model = m.Reseni model = Reseni
fields = ('poznamka',) fields = ('poznamka',)
# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat # FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat
@ -198,7 +200,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
from django.db.utils import OperationalError from django.db.utils import OperationalError
try: try:
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik
except OperationalError: except OperationalError:
# django.db.utils.OperationalError: no such table: seminar_nastaveni # django.db.utils.OperationalError: no such table: seminar_nastaveni
# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál # Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál
@ -214,7 +216,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
result.append(("0001-01-01", f"Odjakživa")) result.append(("0001-01-01", f"Odjakživa"))
for deadline in m.Deadline.objects.filter( for deadline in Deadline.objects.filter(
deadline__lte=timezone.now(), deadline__lte=timezone.now(),
cislo__rocnik=aktualni_rocnik cislo__rocnik=aktualni_rocnik
).order_by("deadline"): ).order_by("deadline"):

View file

@ -0,0 +1,108 @@
# Generated by Django 2.2.28 on 2023-08-09 17:37
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import odevzdavatko.models
class Migration(migrations.Migration):
initial = True
dependencies = [
('tvorba', '0001_initial'),
('personalni', '0002_initial'),
('seminar', '0117_separace_tvorby'),
]
run_before = [
('seminar', '0120_smazani_odevzdavatka'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.CreateModel(
name='Hodnoceni',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('body', models.DecimalField(blank=True, decimal_places=1, max_digits=8, null=True, verbose_name='body')),
('feedback', models.TextField(blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)', verbose_name='zpětná vazba')),
('cislo_body', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.Cislo', verbose_name='číslo pro body')),
('deadline_body', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.Deadline', verbose_name='deadline pro body')),
('problem', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='hodnoceni', to='tvorba.Problem', verbose_name='problém')),
],
options={
'verbose_name': 'Hodnocení',
'verbose_name_plural': 'Hodnocení',
'db_table': 'mam_hodnoceni',
},
),
migrations.CreateModel(
name='Reseni',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('cas_doruceni', models.DateTimeField(blank=True, default=django.utils.timezone.now, verbose_name='čas_doručení')),
('forma', models.CharField(choices=[('papir', 'Papírové řešení'), ('email', 'Emailem'), ('upload', 'Upload přes web')], default='email', max_length=16, verbose_name='forma řešení')),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k řešení (plain text)', verbose_name='neveřejná poznámka')),
('zverejneno', models.BooleanField(default=False, help_text='Udává, zda je řešení zveřejněno', verbose_name='řešení zveřejněno')),
('problem', models.ManyToManyField(help_text='Problém', through='odevzdavatko.Hodnoceni', to='tvorba.Problem', verbose_name='problém')),
],
options={
'verbose_name': 'Řešení',
'verbose_name_plural': 'Řešení',
'db_table': 'mam_reseni',
'ordering': ['-cas_doruceni'],
},
),
migrations.CreateModel(
name='Reseni_Resitele',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='odevzdavatko.Reseni', verbose_name='řešení')),
('resitele', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='personalni.Resitel', verbose_name='řešitel')),
],
options={
'verbose_name': 'Řešení řešitelů',
'verbose_name_plural': 'Řešení řešitelů',
'db_table': 'mam_reseni_resitele',
'ordering': ['reseni', 'resitele'],
},
),
migrations.AddField(
model_name='reseni',
name='resitele',
field=models.ManyToManyField(help_text='Seznam autorů řešení', through='odevzdavatko.Reseni_Resitele', to='personalni.Resitel', verbose_name='autoři řešení'),
),
migrations.AddField(
model_name='reseni',
name='text_cely',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reseni_cely_set', to='seminar.ReseniNode', verbose_name='Plná verze textu řešení'),
),
migrations.CreateModel(
name='PrilohaReseni',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='vytvořeno')),
('soubor', models.FileField(upload_to=odevzdavatko.models.generate_filename, verbose_name='soubor')),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu', verbose_name='neveřejná poznámka')),
('res_poznamka', models.TextField(blank=True, help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje', verbose_name='poznámka řešitele')),
('reseni', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prilohy', to='odevzdavatko.Reseni', verbose_name='řešení')),
],
options={
'verbose_name': 'Příloha řešení',
'verbose_name_plural': 'Přílohy řešení',
'db_table': 'mam_priloha_reseni',
'ordering': ['reseni', 'vytvoreno'],
},
),
migrations.AddField(
model_name='hodnoceni',
name='reseni',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='odevzdavatko.Reseni', verbose_name='řešení'),
),
],
database_operations=[],
),
]

View file

@ -0,0 +1,25 @@
# Generated by Django 2.2.28 on 2023-08-10 07:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('odevzdavatko', '0001_initial'),
('treenode', '0001_initial'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.AlterField(
model_name='reseni',
name='text_cely',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reseni_cely_set', to='treenode.ReseniNode', verbose_name='Plná verze textu řešení'),
),
],
database_operations=[],
),
]

View file

@ -9,19 +9,18 @@ from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from seminar.models import tvorba as am from tvorba.models import Cislo, Deadline, Problem, Uloha, aux_generate_filename
from seminar.models import personalni as pm from personalni.models import Resitel
from seminar.models import treenode as tm from seminar.models.base import SeminarModelBase
from seminar.models import base as bm
from seminar.utils import vzorecek_na_prepocet, inverze_vzorecku_na_prepocet __all__ = ["Reseni", "Hodnoceni", "PrilohaReseni", "Reseni_Resitele"]
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Reseni(bm.SeminarModelBase): class Reseni(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_reseni' db_table = 'mam_reseni'
verbose_name = 'Řešení' verbose_name = 'Řešení'
verbose_name_plural = 'Řešení' verbose_name_plural = 'Řešení'
#ordering = ['-problem', 'resitele'] # FIXME: Takhle to chceme, ale nefunguje to. #ordering = ['-problem', 'resitele'] # FIXME: Takhle to chceme, ale nefunguje to.
@ -31,10 +30,10 @@ class Reseni(bm.SeminarModelBase):
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
# Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby. # Ke každé dvojici řešní a problém existuje nanejvýš jedno hodnocení, doplnění vazby.
problem = models.ManyToManyField(am.Problem, verbose_name='problém', help_text='Problém', problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
through='Hodnoceni') through='Hodnoceni')
resitele = models.ManyToManyField(pm.Resitel, verbose_name='autoři řešení', resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení',
help_text='Seznam autorů řešení', through='Reseni_Resitele') help_text='Seznam autorů řešení', through='Reseni_Resitele')
@ -51,7 +50,7 @@ class Reseni(bm.SeminarModelBase):
forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False, forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
default=FORMA_EMAIL) default=FORMA_EMAIL)
text_cely = models.OneToOneField('ReseniNode', verbose_name='Plná verze textu řešení', text_cely = models.OneToOneField("treenode.ReseniNode", verbose_name='Plná verze textu řešení',
blank=True, null=True, related_name="reseni_cely_set", blank=True, null=True, related_name="reseni_cely_set",
on_delete=models.PROTECT) on_delete=models.PROTECT)
@ -81,7 +80,7 @@ class Reseni(bm.SeminarModelBase):
# NOTE: Potenciální DB HOG (bez select_related) # NOTE: Potenciální DB HOG (bez select_related)
def deadline_reseni(self): def deadline_reseni(self):
return am.Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first() return Deadline.objects.filter(deadline__gte=self.cas_doruceni).order_by("deadline").first()
## Pravdepodobne uz nebude potreba: ## Pravdepodobne uz nebude potreba:
# def save(self, *args, **kwargs): # def save(self, *args, **kwargs):
@ -90,9 +89,9 @@ class Reseni(bm.SeminarModelBase):
# self.cislo_body = self.problem.cislo_reseni # self.cislo_body = self.problem.cislo_reseni
# super(Reseni, self).save(*args, **kwargs) # super(Reseni, self).save(*args, **kwargs)
class Hodnoceni(bm.SeminarModelBase): class Hodnoceni(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_hodnoceni' db_table = 'mam_hodnoceni'
verbose_name = 'Hodnocení' verbose_name = 'Hodnocení'
verbose_name_plural = 'Hodnocení' verbose_name_plural = 'Hodnocení'
@ -103,16 +102,16 @@ class Hodnoceni(bm.SeminarModelBase):
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body', body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body',
blank=True, null=True) blank=True, null=True)
cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body', cislo_body = models.ForeignKey(Cislo, verbose_name='číslo pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
# V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body # V ročníku < 26 nastaveno na deadline vygenerovaný pro původní cislo_body
deadline_body = models.ForeignKey(am.Deadline, verbose_name='deadline pro body', deadline_body = models.ForeignKey(Deadline, verbose_name='deadline pro body',
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT) related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
problem = models.ForeignKey(am.Problem, verbose_name='problém', problem = models.ForeignKey(Problem, verbose_name='problém',
related_name='hodnoceni', on_delete=models.PROTECT) related_name='hodnoceni', on_delete=models.PROTECT)
feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)') feedback = models.TextField('zpětná vazba', blank=True, default='', help_text='Zpětná vazba řešiteli (plain text)')
@ -138,6 +137,7 @@ class Hodnoceni(bm.SeminarModelBase):
def body_neprepocitane(self): def body_neprepocitane(self):
if self.body is None: if self.body is None:
return None return None
from odevzdavatko.utils import inverze_vzorecku_na_prepocet
return inverze_vzorecku_na_prepocet(self.body, self.reseni.resitele.count()) return inverze_vzorecku_na_prepocet(self.body, self.reseni.resitele.count())
@body_neprepocitane.setter @body_neprepocitane.setter
@ -145,12 +145,14 @@ class Hodnoceni(bm.SeminarModelBase):
if value is None: if value is None:
self.body = None self.body = None
else: else:
from odevzdavatko.utils import vzorecek_na_prepocet
self.body = vzorecek_na_prepocet(value, self.reseni.resitele.count()) self.body = vzorecek_na_prepocet(value, self.reseni.resitele.count())
@property @property
def body_neprepocitane_celkem(self): def body_neprepocitane_celkem(self):
if self.body_celkem is None: if self.body_celkem is None:
return None return None
from odevzdavatko.utils import inverze_vzorecku_na_prepocet
return inverze_vzorecku_na_prepocet(self.body_celkem, self.reseni.resitele.count()) return inverze_vzorecku_na_prepocet(self.body_celkem, self.reseni.resitele.count())
@body_neprepocitane_celkem.setter @body_neprepocitane_celkem.setter
@ -158,17 +160,19 @@ class Hodnoceni(bm.SeminarModelBase):
if value is None: if value is None:
self.body = None self.body = None
else: else:
from odevzdavatko.utils import vzorecek_na_prepocet
self.body_celkem = vzorecek_na_prepocet(value, self.reseni.resitele.count()) self.body_celkem = vzorecek_na_prepocet(value, self.reseni.resitele.count())
@property @property
def body_max(self): def body_max(self):
if self.body_neprepocitane_max is None: if self.body_neprepocitane_max is None:
return None return None
from odevzdavatko.utils import vzorecek_na_prepocet
return vzorecek_na_prepocet(self.body_neprepocitane_max, self.reseni.resitele.count()) return vzorecek_na_prepocet(self.body_neprepocitane_max, self.reseni.resitele.count())
@property @property
def body_neprepocitane_max(self): def body_neprepocitane_max(self):
if not isinstance(self.problem.get_real_instance(), am.Uloha): if not isinstance(self.problem.get_real_instance(), Uloha):
return None return None
return self.problem.uloha.max_body return self.problem.uloha.max_body
@ -178,15 +182,15 @@ class Hodnoceni(bm.SeminarModelBase):
def generate_filename(self, filename): def generate_filename(self, filename):
return os.path.join( return os.path.join(
settings.SEMINAR_RESENI_DIR, settings.SEMINAR_RESENI_DIR,
am.aux_generate_filename(self, filename) aux_generate_filename(self, filename)
) )
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class PrilohaReseni(bm.SeminarModelBase): class PrilohaReseni(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_priloha_reseni' db_table = 'mam_priloha_reseni'
verbose_name = 'Příloha řešení' verbose_name = 'Příloha řešení'
verbose_name_plural = 'Přílohy řešení' verbose_name_plural = 'Přílohy řešení'
ordering = ['reseni', 'vytvoreno'] ordering = ['reseni', 'vytvoreno']
@ -221,7 +225,7 @@ class PrilohaReseni(bm.SeminarModelBase):
class Reseni_Resitele(models.Model): class Reseni_Resitele(models.Model):
class Meta: class Meta:
db_table = 'seminar_reseni_resitele' db_table = 'mam_reseni_resitele'
verbose_name = 'Řešení řešitelů' verbose_name = 'Řešení řešitelů'
verbose_name_plural = 'Řešení řešitelů' verbose_name_plural = 'Řešení řešitelů'
ordering = ['reseni', 'resitele'] ordering = ['reseni', 'resitele']
@ -229,7 +233,7 @@ class Reseni_Resitele(models.Model):
# Interní ID # Interní ID
id = models.AutoField(primary_key = True) id = models.AutoField(primary_key = True)
resitele = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT) resitele = models.ForeignKey(Resitel, verbose_name='řešitel', on_delete=models.PROTECT)
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE) reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
@ -239,19 +243,3 @@ class Reseni_Resitele(models.Model):
def __str__(self): def __str__(self):
return '{} od {}'.format(self.reseni, self.resitel) return '{} od {}'.format(self.reseni, self.resitel)
# NOTE: Poteciální DB HOG bez select_related # NOTE: Poteciální DB HOG bez select_related
class ReseniNode(tm.TreeNode):
class Meta:
db_table = 'seminar_nodes_otistene_reseni'
verbose_name = 'Otištěné řešení (Node)'
verbose_name_plural = 'Otištěná řešení (Node)'
reseni = models.ForeignKey(Reseni,
on_delete=models.PROTECT,
verbose_name = 'reseni')
def aktualizuj_nazev(self):
self.nazev = "ReseniNode: "+str(self.reseni)
def getOdkazStr(self):
return str(self.reseni)

View file

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #} {% load datumy %} {# Možná by mohlo být někde výš v hierarchii templatů... #}
{% block content %} {% block content %}

View file

@ -2,8 +2,8 @@ from django import template
register = template.Library() register = template.Library()
from personalni.utils import normalizuj_jmeno from personalni.utils import normalizuj_jmeno
import seminar.models as m # jen kvůli typové anotaci… from personalni.models import Osoba # jen kvůli typové anotaci…
@register.filter @register.filter
def jmeno_jako_prefix(o: m.Osoba): def jmeno_jako_prefix(o: Osoba):
return normalizuj_jmeno(o).replace(' ', '_') return normalizuj_jmeno(o).replace(' ', '_')

49
odevzdavatko/testutils.py Normal file
View file

@ -0,0 +1,49 @@
import datetime
import random
from .models import *
def gen_reseni_ulohy(rnd, cisla, uloha, pocet_resitelu, poradi_cisla, resitele_cisla, resitele):
pocet_reseni = rnd.randint(pocet_resitelu//4, pocet_resitelu * 4)
# generujeme náhodný počet řešení vzhledem k počtu řešitelů čísla
for _ in range(pocet_reseni):
# print("Generuji {}-té řešení".format(reseni))
if rnd.randint(1, 10) == 1:
# cca desetina řešení od více řešitelů
res_vyber = rnd.sample(resitele_cisla, rnd.randint(2, 5))
else:
res_vyber = rnd.sample(resitele_cisla, 1)
if resitele[0] in res_vyber: # speciální řešitel, který nemá žádné body
res_vyber.remove(resitele[0])
# Vytvoření řešení.
if uloha.cislo_zadani.zlomovy_deadline_pro_papirove_cislo() is not None:
# combine, abychom dostali plný čas a ne jen datum
cas_doruceni = (
uloha.cislo_zadani.deadline_v_cisle.first().deadline -
datetime.timedelta(days=random.randint(0, 40)) -
datetime.timedelta(minutes=random.randint(0, 60*24))
)
# astimezone, protože jinak vyhazuje warning o nenastavené TZ
res = Reseni.objects.create(
forma=rnd.choice(Reseni.FORMA_CHOICES)[0],
cas_doruceni=cas_doruceni.astimezone(datetime.timezone.utc),
)
else:
res = Reseni.objects.create(
forma=rnd.choice(Reseni.FORMA_CHOICES)[0],
)
# Problém a řešitele přiřadíme později, ManyToManyField
# se nedá vyplnit v create().
res.resitele.set(res_vyber)
res.save()
# Vytvoření hodnocení.
hod = Hodnoceni.objects.create(
body=rnd.randint(0, uloha.max_body),
cislo_body=cisla[poradi_cisla - 1],
reseni=res,
problem=uloha
)
return

View file

@ -1,21 +1,8 @@
"""
Soubor sloužící jako router, tj. zde se definují url adresy a na co ukazují:
- ``org/add_solution`` (seminar_vloz_reseni) :class:`~odevzdavatko.views.PosliReseniView`
- ``resitel/nahraj_reseni`` (seminar_nahraj_reseni) :class:`~odevzdavatko.views.NahrajReseniView`
- ``resitel/odevzdana_reseni/`` (seminar_resitel_odevzdana_reseni) :class:`~odevzdavatko.views.PrehledOdevzdanychReseni`
- ``org/reseni/`` (odevzdavatko_tabulka) :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
- ``org/reseni/rocnik/<int:rocnik>/`` (odevzdavatko_tabulka) :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
- ``org/reseni/<int:problem>/<int:resitel>/`` (odevzdavatko_reseni_resitele_k_problemu) :class:`~odevzdavatko.views.ReseniProblemuView`
- ``org/reseni/<int:pk>/`` (odevzdavatko_detail_reseni) :func:`~seminar.utils.viewMethodSwitch` + :class:`~odevzdavatko.views.DetailReseniView` + :func:`~odevzdavatko.views.hodnoceniReseniView`
- ``org/reseni/all`` :class:`~odevzdavatko.views.SeznamReseniView`
- ``org/reseni/akt`` :class:`~odevzdavatko.views.TabulkaOdevzdanychReseniView`
- ``resitel/reseni/<int:pk>`` (odevzdavatko_resitel_reseni) :class:`~odevzdavatko.views.ResitelReseniView`
"""
from django.urls import path from django.urls import path
from seminar.utils import org_required, resitel_required, viewMethodSwitch, \ from personalni.utils import org_required, resitel_required, \
resitel_or_org_required resitel_or_org_required
from various.utils import viewMethodSwitch
from . import views from . import views
urlpatterns = [ urlpatterns = [

11
odevzdavatko/utils.py Normal file
View file

@ -0,0 +1,11 @@
import decimal
def vzorecek_na_prepocet(body, resitelu):
""" Vzoreček na přepočet plných bodů na parciálni, když má řešení více řešitelů. """
return body * 3 / (resitelu + 2)
def inverze_vzorecku_na_prepocet(body: decimal.Decimal, resitelu) -> decimal.Decimal:
""" Vzoreček na přepočet parciálních bodů na plné, když má řešení více řešitelů. """
return round(body * (resitelu + 2) / 3, 1)

View file

@ -10,17 +10,21 @@ from django.shortcuts import redirect, get_object_or_404, render
from django.urls import reverse from django.urls import reverse
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.conf import settings
from dataclasses import dataclass from dataclasses import dataclass
import datetime import datetime
from itertools import groupby from itertools import groupby
import logging import logging
import seminar.models as m from .models import *
from tvorba.models import Problem, Rocnik, Deadline
from seminar.models.nastaveni import Nastaveni
from personalni.models import Resitel, Organizator, Osoba
from . import forms as f from . import forms as f
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
from seminar.utils import resi_v_rocniku from personalni.utils import resi_v_rocniku
from seminar.views import formularOKView from various.views import formularOKView
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -47,20 +51,20 @@ class SouhrnReseni:
class TabulkaOdevzdanychReseniView(ListView): class TabulkaOdevzdanychReseniView(ListView):
template_name = 'odevzdavatko/tabulka.html' template_name = 'odevzdavatko/tabulka.html'
model = m.Hodnoceni model = Hodnoceni
def inicializuj_osy_tabulky(self): def inicializuj_osy_tabulky(self):
"""Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů""" """Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů"""
# FIXME: jméno metody není vypovídající... # FIXME: jméno metody není vypovídající...
# NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat # NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat
# TODO: Prefetches, Select related, ... # TODO: Prefetches, Select related, ...
self.resitele = m.Resitel.objects.all() self.resitele = Resitel.objects.all()
self.problemy = m.Problem.objects.all() self.problemy = Problem.objects.all()
self.reseni = m.Reseni.objects.all() self.reseni = Reseni.objects.all()
self.aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci self.aktualni_rocnik = Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci
if 'rocnik' in self.kwargs: if 'rocnik' in self.kwargs:
self.aktualni_rocnik = get_object_or_404(m.Rocnik, rocnik=self.kwargs['rocnik']) self.aktualni_rocnik = get_object_or_404(Rocnik, rocnik=self.kwargs['rocnik'])
form = FiltrForm(self.request.GET, rocnik=self.aktualni_rocnik) form = FiltrForm(self.request.GET, rocnik=self.aktualni_rocnik)
if form.is_valid(): if form.is_valid():
@ -91,14 +95,14 @@ class TabulkaOdevzdanychReseniView(ListView):
self.resitele = self.resitele.filter(rok_maturity__gt=self.aktualni_rocnik.prvni_rok) self.resitele = self.resitele.filter(rok_maturity__gt=self.aktualni_rocnik.prvni_rok)
if problemy == FiltrForm.PROBLEMY_MOJE: if problemy == FiltrForm.PROBLEMY_MOJE:
org = m.Organizator.objects.get(osoba__user=self.request.user) org = Organizator.objects.get(osoba__user=self.request.user)
self.problemy = self.problemy.filter( self.problemy = self.problemy.filter(
Q(autor=org)|Q(garant=org)|Q(opravovatele=org), Q(autor=org)|Q(garant=org)|Q(opravovatele=org),
Q(stav=m.Problem.STAV_ZADANY)|Q(stav=m.Problem.STAV_VYRESENY), Q(stav=Problem.STAV_ZADANY)|Q(stav=Problem.STAV_VYRESENY),
) )
elif problemy == FiltrForm.PROBLEMY_LETOSNI: elif problemy == FiltrForm.PROBLEMY_LETOSNI:
self.problemy = self.problemy.filter( self.problemy = self.problemy.filter(
Q(stav=m.Problem.STAV_ZADANY)|Q(stav=m.Problem.STAV_VYRESENY), Q(stav=Problem.STAV_ZADANY)|Q(stav=Problem.STAV_VYRESENY),
) )
#self.problemy = list(filter(lambda problem: problem.rocnik() == self.aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník.... #self.problemy = list(filter(lambda problem: problem.rocnik() == self.aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník....
# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. # NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy.
@ -164,7 +168,7 @@ class TabulkaOdevzdanychReseniView(ListView):
# Pro použití hacku na automatické {{form.media}} v template: # Pro použití hacku na automatické {{form.media}} v template:
ctx['form'] = ctx['filtr'] ctx['form'] = ctx['filtr']
# Pro maximum v přesměrovátku ročníků # Pro maximum v přesměrovátku ročníků
ctx['aktualni_rocnik'] = m.Nastaveni.get_solo().aktualni_rocnik ctx['aktualni_rocnik'] = Nastaveni.get_solo().aktualni_rocnik
if 'rocnik' in self.kwargs: if 'rocnik' in self.kwargs:
ctx['rocnik'] = self.kwargs['rocnik'] ctx['rocnik'] = self.kwargs['rocnik']
else: else:
@ -174,7 +178,7 @@ class TabulkaOdevzdanychReseniView(ListView):
# Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?
class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View): class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View):
model = m.Reseni model = Reseni
template_name = 'odevzdavatko/seznam.html' template_name = 'odevzdavatko/seznam.html'
def get_queryset(self): def get_queryset(self):
@ -186,8 +190,8 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
if problem_id is None: if problem_id is None:
raise ValueError("Nemám problém! (To je problém!)") raise ValueError("Nemám problém! (To je problém!)")
resitel = m.Resitel.objects.get(id=resitel_id) resitel = Resitel.objects.get(id=resitel_id)
problem = m.Problem.objects.get(id=problem_id) problem = Problem.objects.get(id=problem_id)
qs = qs.filter( qs = qs.filter(
problem__in=[problem], problem__in=[problem],
resitele__in=[resitel], resitele__in=[resitel],
@ -217,13 +221,13 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex ## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
class DetailReseniView(DetailView): class DetailReseniView(DetailView):
""" Náhled na řešení. Editace je v :py:class:`EditReseniView`. """ """ Náhled na řešení. Editace je v :py:class:`EditReseniView`. """
model = m.Reseni model = Reseni
template_name = 'odevzdavatko/detail.html' template_name = 'odevzdavatko/detail.html'
def aktualni_hodnoceni(self): def aktualni_hodnoceni(self):
self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk']) self.reseni = get_object_or_404(Reseni, id=self.kwargs['pk'])
result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet result = [] # Slovníky s klíči problem, body, deadline_body -- initial data pro f.OhodnoceniReseniFormSet
for hodn in m.Hodnoceni.objects.filter(reseni=self.reseni): for hodn in Hodnoceni.objects.filter(reseni=self.reseni):
seznam_atributu = [ seznam_atributu = [
"problem", "problem",
"body", "body",
@ -280,7 +284,7 @@ class EditReseniView(DetailReseniView):
def hodnoceniReseniView(request, pk, *args, **kwargs): def hodnoceniReseniView(request, pk, *args, **kwargs):
reseni = get_object_or_404(m.Reseni, pk=pk) reseni = get_object_or_404(Reseni, pk=pk)
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk}) success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
# FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově # FIXME: Použit initial i tady a nebastlit hodnocení tak nízkoúrovňově
@ -296,7 +300,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
poznamka_form.save() poznamka_form.save()
# Smažeme všechna dosavadní hodnocení tohoto řešení # Smažeme všechna dosavadní hodnocení tohoto řešení
qs = m.Hodnoceni.objects.filter(reseni=reseni) qs = Hodnoceni.objects.filter(reseni=reseni)
logger.info(f"Will delete {qs.count()} objects: {qs}") logger.info(f"Will delete {qs.count()} objects: {qs}")
qs.delete() qs.delete()
@ -307,7 +311,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
del(data_for_hodnoceni["body_celkem"]) del(data_for_hodnoceni["body_celkem"])
del(data_for_hodnoceni["body_neprepocitane"]) del(data_for_hodnoceni["body_neprepocitane"])
del(data_for_hodnoceni["body_neprepocitane_celkem"]) del(data_for_hodnoceni["body_neprepocitane_celkem"])
hodnoceni = m.Hodnoceni( hodnoceni = Hodnoceni(
reseni=reseni, reseni=reseni,
**form.cleaned_data, **form.cleaned_data,
) )
@ -327,14 +331,14 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
class PrehledOdevzdanychReseni(ListView): class PrehledOdevzdanychReseni(ListView):
model = m.Hodnoceni model = Hodnoceni
template_name = 'odevzdavatko/prehled_reseni.html' template_name = 'odevzdavatko/prehled_reseni.html'
def get_queryset(self): def get_queryset(self):
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
raise RuntimeError("Uživatel měl být přihlášený!") raise RuntimeError("Uživatel měl být přihlášený!")
# get_or_none, aby neexistence řešitele (např. u orgů) neházela chybu # get_or_none, aby neexistence řešitele (např. u orgů) neházela chybu
resitel = m.Resitel.objects.filter(osoba__user=self.request.user).first() resitel = Resitel.objects.filter(osoba__user=self.request.user).first()
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.filter(reseni__resitele__in=[resitel]) qs = qs.filter(reseni__resitele__in=[resitel])
# Setřídíme podle času doručení řešení, aby se netřídily podle okamžiku vyrobení Hodnocení # Setřídíme podle času doručení řešení, aby se netřídily podle okamžiku vyrobení Hodnocení
@ -355,13 +359,13 @@ class PrehledOdevzdanychReseni(ListView):
# Přehled všech řešení kvůli debugování # Přehled všech řešení kvůli debugování
class SeznamReseniView(ListView): class SeznamReseniView(ListView):
model = m.Reseni model = Reseni
template_name = 'odevzdavatko/seznam.html' template_name = 'odevzdavatko/seznam.html'
class SeznamAktualnichReseniView(SeznamReseniView): class SeznamAktualnichReseniView(SeznamReseniView):
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... akt_rocnik = Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi...
resitele = resi_v_rocniku(akt_rocnik) resitele = resi_v_rocniku(akt_rocnik)
qs = qs.filter(resitele__in=resitele) # FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel qs = qs.filter(resitele__in=resitele) # FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel
return qs return qs
@ -373,7 +377,7 @@ class VlozReseniView(LoginRequiredMixin, FormView):
def form_valid(self, form): def form_valid(self, form):
data = form.cleaned_data data = form.cleaned_data
nove_reseni = m.Reseni.objects.create( nove_reseni = Reseni.objects.create(
cas_doruceni=data['cas_doruceni'], cas_doruceni=data['cas_doruceni'],
forma=data['forma'], forma=data['forma'],
poznamka=data['poznamka'], poznamka=data['poznamka'],
@ -400,35 +404,35 @@ class VlozReseniView(LoginRequiredMixin, FormView):
class NahrajReseniRozcestnikTematekView(LoginRequiredMixin, ListView): class NahrajReseniRozcestnikTematekView(LoginRequiredMixin, ListView):
model = m.Problem model = Problem
template_name = 'odevzdavatko/nahraj_reseni_nadproblem.html' template_name = 'odevzdavatko/nahraj_reseni_nadproblem.html'
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(stav=m.Problem.STAV_ZADANY, nadproblem__isnull=True) return super().get_queryset().filter(stav=Problem.STAV_ZADANY, nadproblem__isnull=True)
class NahrajReseniView(LoginRequiredMixin, CreateView): class NahrajReseniView(LoginRequiredMixin, CreateView):
model = m.Reseni model = Reseni
template_name = 'odevzdavatko/nahraj_reseni.html' template_name = 'odevzdavatko/nahraj_reseni.html'
form_class = f.NahrajReseniForm form_class = f.NahrajReseniForm
nadproblem: m.Problem nadproblem: Problem
def setup(self, request, *args, **kwargs): def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs) super().setup(request, *args, **kwargs)
nadproblem_id = self.kwargs["nadproblem_id"] nadproblem_id = self.kwargs["nadproblem_id"]
self.nadproblem = get_object_or_404(m.Problem, id=nadproblem_id) self.nadproblem = get_object_or_404(Problem, id=nadproblem_id)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# Zaříznutí nezadaných problémů # Zaříznutí nezadaných problémů
if self.nadproblem.stav != m.Problem.STAV_ZADANY: if self.nadproblem.stav != Problem.STAV_ZADANY:
raise PermissionDenied() raise PermissionDenied()
# Zaříznutí starých řešitelů: # Zaříznutí starých řešitelů:
# FIXME: Je to tady dost naprasené, mělo by to asi být jinde… # FIXME: Je to tady dost naprasené, mělo by to asi být jinde…
osoba = m.Osoba.objects.get(user=self.request.user) osoba = Osoba.objects.get(user=self.request.user)
resitel = osoba.resitel resitel = osoba.resitel
if resitel.rok_maturity <= m.Nastaveni.get_solo().aktualni_rocnik.prvni_rok: if resitel.rok_maturity <= Nastaveni.get_solo().aktualni_rocnik.prvni_rok:
return render(request, 'universal.html', { return render(request, 'universal.html', {
'title': 'Nelze odevzdat', 'title': 'Nelze odevzdat',
'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.', 'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.',
@ -440,7 +444,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
nadproblem_id = self.nadproblem.id nadproblem_id = self.nadproblem.id
return { return {
"nadproblem_id": nadproblem_id, "nadproblem_id": nadproblem_id,
"problem": [] if self.nadproblem.podproblem.filter(stav=m.Problem.STAV_ZADANY).exists() else nadproblem_id "problem": [] if self.nadproblem.podproblem.filter(stav=Problem.STAV_ZADANY).exists() else nadproblem_id
} }
@ -452,7 +456,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
data['prilohy'] = f.ReseniSPrilohamiFormSet() data['prilohy'] = f.ReseniSPrilohamiFormSet()
data["nadproblem_id"] = self.nadproblem.id data["nadproblem_id"] = self.nadproblem.id
data["nadproblem"] = get_object_or_404(m.Problem, id=self.nadproblem.id) data["nadproblem"] = get_object_or_404(Problem, id=self.nadproblem.id)
return data return data
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni # FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni
@ -464,17 +468,17 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
return super().form_invalid(form) return super().form_invalid(form)
with transaction.atomic(): with transaction.atomic():
self.object = form.save() self.object = form.save()
self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user)) self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user))
self.object.resitele.add(*form.cleaned_data["resitele"]) self.object.resitele.add(*form.cleaned_data["resitele"])
self.object.cas_doruceni = timezone.now() self.object.cas_doruceni = timezone.now()
self.object.forma = m.Reseni.FORMA_UPLOAD self.object.forma = Reseni.FORMA_UPLOAD
self.object.save() self.object.save()
prilohy.instance = self.object prilohy.instance = self.object
prilohy.save() prilohy.save()
for hodnoceni in self.object.hodnoceni_set.all(): for hodnoceni in self.object.hodnoceni_set.all():
hodnoceni.deadline_body = m.Deadline.objects.filter(deadline__gte=self.object.cas_doruceni).first() hodnoceni.deadline_body = Deadline.objects.filter(deadline__gte=self.object.cas_doruceni).first()
hodnoceni.save() hodnoceni.save()
# Pošleme mail opravovatelům a garantovi # Pošleme mail opravovatelům a garantovi
@ -492,7 +496,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
# FIXME: Víc informativní obsah mailů, možná vč. příloh? # FIXME: Víc informativní obsah mailů, možná vč. příloh?
prijemci = map(lambda it: it.osoba.email, prijemci) prijemci = map(lambda it: it.osoba.email, prijemci)
resitel = m.Osoba.objects.get(user = self.request.user) resitel = Osoba.objects.get(user = self.request.user)
seznam = "problému " + str(problemy[0]) if len(problemy) == 1 else 'následujícím problémům:\n' + ', \n'.join(map(str, problemy)) seznam = "problému " + str(problemy[0]) if len(problemy) == 1 else 'následujícím problémům:\n' + ', \n'.join(map(str, problemy))
seznam_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })") seznam_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })")
@ -500,7 +504,7 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
EmailMessage( EmailMessage(
subject="Nové řešení k " + seznam_do_subjectu, subject="Nové řešení k " + seznam_do_subjectu,
body=f"Řešitel{ '' if resitel.pohlavi_muz else 'ka' } { resitel } právě nahrál{'' if resitel.pohlavi_muz else 'a' } nové řešení k { seznam }.\n\nHurá do opravování: { self.object.absolute_url() }", body=f"Řešitel{ '' if resitel.pohlavi_muz else 'ka' } { resitel } právě nahrál{'' if resitel.pohlavi_muz else 'a' } nové řešení k { seznam }.\n\nHurá do opravování: { self.object.absolute_url() }",
from_email="submitovatko@mam.mff.cuni.cz", # FIXME: Chceme to mít radši tady, nebo v nastavení? from_email=settings.NOVE_RESENI_EMAIL,
to=list(prijemci), to=list(prijemci),
).send() ).send()

View file

@ -1,10 +1,10 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django_reverse_admin import ReverseModelAdmin from django_reverse_admin import ReverseModelAdmin
import seminar.models as m from .models import *
@admin.register(m.Osoba) @admin.register(Osoba)
class OsobaAdmin(admin.ModelAdmin): class OsobaAdmin(admin.ModelAdmin):
actions = ['synchronizuj_maily', 'udelej_orgem'] actions = ['synchronizuj_maily', 'udelej_orgem']
search_fields = ['jmeno', 'prijmeni', 'prezdivka'] search_fields = ['jmeno', 'prijmeni', 'prezdivka']
@ -27,25 +27,25 @@ class OsobaAdmin(admin.ModelAdmin):
user.groups.add(org_group) user.groups.add(org_group)
user.is_staff = True user.is_staff = True
user.save() user.save()
org = m.Organizator.objects.create(osoba=o) org = Organizator.objects.create(osoba=o)
org.save() org.save()
udelej_orgem.short_description = "Udělej vybraných osob organizátory" udelej_orgem.short_description = "Udělej vybraných osob organizátory"
class OsobaInline(admin.TabularInline): class OsobaInline(admin.TabularInline):
model = m.Osoba model = Osoba
@admin.register(m.Organizator) @admin.register(Organizator)
class OrganizatorAdmin(ReverseModelAdmin): class OrganizatorAdmin(ReverseModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
inline_type = 'stacked' inline_type = 'stacked'
inline_reverse = ['osoba'] inline_reverse = ['osoba']
@admin.register(m.Resitel) @admin.register(Resitel)
class ResitelAdmin(ReverseModelAdmin): class ResitelAdmin(ReverseModelAdmin):
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka'] search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
ordering = ('osoba__prijmeni', 'osoba__jmeno') ordering = ('osoba__prijmeni', 'osoba__jmeno')
inline_type = 'stacked' inline_type = 'stacked'
inline_reverse = ['osoba'] inline_reverse = ['osoba']
admin.site.register(m.Skola) admin.site.register(Skola)
admin.site.register(m.Prijemce) admin.site.register(Prijemce)

View file

@ -1,6 +1,3 @@
"""
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

@ -4,7 +4,7 @@ from django.contrib.auth.forms import PasswordResetForm
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User from django.contrib.auth.models import User
from seminar.models import Skola, Resitel, Osoba from .models import *
from datetime import date from datetime import date
import logging import logging

View file

@ -0,0 +1,134 @@
# Generated by Django 2.2.28 on 2023-07-31 17:54
from django.conf import settings
from django.db import migrations, models
import django.utils.timezone
import django_countries.fields
import imagekit.models.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('personalni', '0001_skupiny'),
('seminar', '0114_prejmenovani_tabulek'),
]
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.CreateModel(
name='Osoba',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('jmeno', models.CharField(max_length=256, verbose_name='jméno')),
('prijmeni', models.CharField(max_length=256, verbose_name='příjmení')),
('prezdivka', models.CharField(blank=True, max_length=256, null=True, verbose_name='přezdívka')),
('pohlavi_muz', models.BooleanField(default=False, verbose_name='pohlaví (muž)')),
('email', models.EmailField(blank=True, default='', max_length=256, verbose_name='e-mail')),
('telefon', models.CharField(blank=True, default='', max_length=256, verbose_name='telefon')),
('datum_narozeni', models.DateField(blank=True, null=True, verbose_name='datum narození')),
('datum_souhlasu_udaje', models.DateField(blank=True, help_text='Datum souhlasu se zpracováním osobních údajů', null=True, verbose_name='datum souhlasu (údaje)')),
('datum_souhlasu_zasilani', models.DateField(blank=True, help_text='Datum souhlasu se zasíláním MFF materiálů', null=True, verbose_name='datum souhlasu (spam)')),
('datum_registrace', models.DateField(default=django.utils.timezone.now, verbose_name='datum registrace do semináře')),
('ulice', models.CharField(blank=True, default='', max_length=256, verbose_name='ulice')),
('mesto', models.CharField(blank=True, default='', max_length=256, verbose_name='město')),
('psc', models.CharField(blank=True, default='', max_length=32, verbose_name='PSČ')),
('stat', django_countries.fields.CountryField(default='CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát')),
('jak_se_dozvedeli', models.TextField(blank=True, verbose_name='Jak se dozvěděli')),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k osobě (plain text)', verbose_name='neveřejná poznámka')),
('foto', imagekit.models.fields.ProcessedImageField(blank=True, help_text='Vlož fotografii osoby o libovolné velikosti', null=True, upload_to='image_osoby/velke/%Y/', verbose_name='Fotografie osoby')),
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL, verbose_name='uživatel')),
],
options={
'verbose_name': 'Osoba',
'verbose_name_plural': 'Osoby',
'db_table': 'mam_osoby',
'ordering': ['prijmeni', 'jmeno'],
},
),
migrations.CreateModel(
name='Skola',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('aesop_id', models.CharField(blank=True, default='', help_text='Aesopi ID typu "izo:..." nebo "aesop:..."', max_length=32, verbose_name='Aesop ID')),
('izo', models.CharField(blank=True, help_text='IZO školy (jen české školy)', max_length=32, verbose_name='IZO')),
('nazev', models.CharField(help_text='Celý název školy', max_length=256, verbose_name='název')),
('kratky_nazev', models.CharField(blank=True, help_text='Zkrácený název pro zobrazení ve výsledkovce', max_length=256, verbose_name='zkrácený název')),
('ulice', models.CharField(max_length=256, verbose_name='ulice')),
('mesto', models.CharField(max_length=256, verbose_name='město')),
('psc', models.CharField(max_length=32, verbose_name='PSČ')),
('stat', django_countries.fields.CountryField(default='CZ', help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)', max_length=2, verbose_name='stát')),
('je_zs', models.BooleanField(default=True, verbose_name='základní stupeň')),
('je_ss', models.BooleanField(default=True, verbose_name='střední stupeň')),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka ke škole (plain text)', verbose_name='neveřejná poznámka')),
('kontaktni_osoba', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Osoba', verbose_name='Kontaktní osoba')),
],
options={
'verbose_name': 'Škola',
'verbose_name_plural': 'Školy',
'db_table': 'mam_skoly',
'ordering': ['mesto', 'nazev'],
},
),
migrations.CreateModel(
name='Resitel',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('prezdivka_resitele', models.CharField(blank=True, max_length=256, null=True, unique=True, verbose_name='přezdívka řešitele')),
('rok_maturity', models.IntegerField(blank=True, null=True, verbose_name='rok maturity')),
('zasilat', models.CharField(choices=[('domu', 'Domů'), ('do_skoly', 'Do školy'), ('nikam', 'Nezasílat papírově')], default='domu', max_length=32, verbose_name='kam zasílat')),
('zasilat_cislo_emailem', models.BooleanField(default=False, help_text='True pokud chce řešitel dostávat číslo emailem', verbose_name='zasílat číslo emailem')),
('zasilat_cislo_papirove', models.BooleanField(default=True, help_text='True pokud chce řešitel dostávat číslo papírově', verbose_name='zasílat číslo papírově')),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k řešiteli (plain text)', verbose_name='neveřejná poznámka')),
('osoba', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='personalni.Osoba', verbose_name='osoba')),
('skola', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='personalni.Skola', verbose_name='škola')),
],
options={
'verbose_name': 'Řešitel',
'verbose_name_plural': 'Řešitelé',
'db_table': 'mam_resitele',
'ordering': ['osoba'],
},
),
migrations.CreateModel(
name='Prijemce',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('poznamka', models.TextField(blank=True, help_text='Neveřejná poznámka k příemci čísel (plain text)', verbose_name='neveřejná poznámka')),
('zasilat_cislo_emailem', models.BooleanField(default=False, help_text='True pokud chce příjemce dostávat číslo emailem', verbose_name='zasílat číslo emailem')),
('osoba', models.OneToOneField(help_text='Které osobě či na jakou adresu se mají zasílat čísla', on_delete=django.db.models.deletion.CASCADE, to='personalni.Osoba', verbose_name='komu')),
],
options={
'verbose_name': 'příjemce',
'verbose_name_plural': 'příjemce',
'db_table': 'mam_prijemce',
},
),
migrations.CreateModel(
name='Organizator',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vytvoreno', models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False, verbose_name='Vytvořeno')),
('organizuje_od', models.DateTimeField(blank=True, null=True, verbose_name='Organizuje od')),
('organizuje_do', models.DateTimeField(blank=True, null=True, verbose_name='Organizuje do')),
('studuje', models.CharField(blank=True, help_text="Např. 'Studuje Obecnou fyziku (Bc.), 3. ročník', 'Vystudovala Diskrétní modely a algoritmy (Mgr.)' nebo 'Přednáší na MFF'", max_length=256, null=True, verbose_name='Studium aj.')),
('strucny_popis_organizatora', models.TextField(blank=True, null=True, verbose_name='Stručný popis organizátora')),
('skola', models.CharField(blank=True, help_text='Škola, např. MFF, VŠCHT, VUT, ... prostě aby se nemuselo psát do studuješkolu, ale jen obor, možnost zobrazit zvlášť', max_length=256, null=True, verbose_name='Škola, kterou studuje')),
('osoba', models.OneToOneField(help_text='osobní údaje organizátora', on_delete=django.db.models.deletion.PROTECT, related_name='org', to='personalni.Osoba', verbose_name='osoba')),
],
options={
'verbose_name': 'Organizátor',
'verbose_name_plural': 'Organizátoři',
'db_table': 'mam_organizatori',
'ordering': ['-organizuje_do', 'osoba__jmeno', 'osoba__prijmeni'],
},
),
],
database_operations=[],
),
]

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import logging import logging
from django.db import models from django.db import models
@ -12,7 +11,9 @@ from django_countries.fields import CountryField
from reversion import revisions as reversion from reversion import revisions as reversion
from .base import SeminarModelBase from seminar.models.base import SeminarModelBase
__all__ = ["Osoba", "Skola", "Prijemce", "Organizator", "Resitel"]
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -21,7 +22,7 @@ logger = logging.getLogger(__name__)
class Osoba(SeminarModelBase): class Osoba(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_osoby' db_table = 'mam_osoby'
verbose_name = 'Osoba' verbose_name = 'Osoba'
verbose_name_plural = 'Osoby' verbose_name_plural = 'Osoby'
ordering = ['prijmeni','jmeno'] ordering = ['prijmeni','jmeno']
@ -115,16 +116,12 @@ class Osoba(SeminarModelBase):
u.save() u.save()
super().save() super().save()
#
# Mělo by být částečně vytaženo z Aesopa
# viz https://ovvp.mff.cuni.cz/wiki/aesop/export-skol.
#
@reversion.register(ignore_duplicates=True) @reversion.register(ignore_duplicates=True)
class Skola(SeminarModelBase): class Skola(SeminarModelBase):
# Mělo by být částečně vytaženo z Aesopa
class Meta: class Meta:
db_table = 'seminar_skoly' db_table = 'mam_skoly'
verbose_name = 'Škola' verbose_name = 'Škola'
verbose_name_plural = 'Školy' verbose_name_plural = 'Školy'
ordering = ['mesto', 'nazev'] ordering = ['mesto', 'nazev']
@ -177,7 +174,7 @@ class Skola(SeminarModelBase):
class Prijemce(SeminarModelBase): class Prijemce(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_prijemce' db_table = 'mam_prijemce'
verbose_name = 'příjemce' verbose_name = 'příjemce'
verbose_name_plural = 'příjemce' verbose_name_plural = 'příjemce'
@ -205,7 +202,7 @@ class Prijemce(SeminarModelBase):
class Resitel(SeminarModelBase): class Resitel(SeminarModelBase):
class Meta: class Meta:
db_table = 'seminar_resitele' db_table = 'mam_resitele'
verbose_name = 'Řešitel' verbose_name = 'Řešitel'
verbose_name_plural = 'Řešitelé' verbose_name_plural = 'Řešitelé'
ordering = ['osoba'] ordering = ['osoba']
@ -243,30 +240,6 @@ class Resitel(SeminarModelBase):
poznamka = models.TextField('neveřejná poznámka', blank=True, poznamka = models.TextField('neveřejná poznámka', blank=True,
help_text='Neveřejná poznámka k řešiteli (plain text)') help_text='Neveřejná poznámka k řešiteli (plain text)')
def export_row(self):
"Slovnik pro pouziti v AESOP exportu"
return {
'id': self.id,
'name': self.osoba.jmeno,
'surname': self.osoba.prijmeni,
'gender': 'M' if self.osoba.pohlavi_muz else 'F',
'born': self.osoba.datum_narozeni.isoformat() if self.osoba.datum_narozeni else '',
'email': self.osoba.email,
'end-year': self.rok_maturity or '',
'street': self.osoba.ulice,
'town': self.osoba.mesto,
'postcode': self.osoba.psc,
'country': self.osoba.stat,
'spam-flag': 'Y' if self.osoba.datum_souhlasu_zasilani else '',
'spam-date': self.osoba.datum_souhlasu_zasilani.isoformat() if self.osoba.datum_souhlasu_zasilani else '',
'school': self.skola.aesop_id if self.skola else '',
'school-name': str(self.skola) if self.skola else 'Skola neni znama',
}
def rocnik(self, rocnik): def rocnik(self, rocnik):
"""Vrati skolni rocnik resitele pro zadany Rocnik. """Vrati skolni rocnik resitele pro zadany Rocnik.
Vraci '' pro neznamy rok maturity resitele, Z* pro ekvivalent .""" Vraci '' pro neznamy rok maturity resitele, Z* pro ekvivalent ."""
@ -281,7 +254,7 @@ class Resitel(SeminarModelBase):
def vsechny_body(self): def vsechny_body(self):
"Spočítá body odjakživa." "Spočítá body odjakživa."
vsechna_reseni = self.reseni_set.all() vsechna_reseni = self.reseni_set.all()
from .odevzdavatko import Hodnoceni from odevzdavatko.models import Hodnoceni
vsechna_hodnoceni = Hodnoceni.objects.filter( vsechna_hodnoceni = Hodnoceni.objects.filter(
reseni__in=vsechna_reseni) reseni__in=vsechna_reseni)
return sum(h.body for h in list(vsechna_hodnoceni) if h.body is not None) return sum(h.body for h in list(vsechna_hodnoceni) if h.body is not None)
@ -328,8 +301,8 @@ class Resitel(SeminarModelBase):
# - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími # - body z 25. ročníku a dříve byly shledány dvakrát hodnotnějšími
# - proto se započítávají dvojnásobně a byly posunuté hranice titulů # - proto se započítávají dvojnásobně a byly posunuté hranice titulů
# - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád. # - staré tituly se ale nemají odebrat, pokud řešitel v t.č. minulém (26.) ročníku měl titul, má ho mít pořád.
from .odevzdavatko import Hodnoceni from odevzdavatko.models import Hodnoceni
hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all()) hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=settings.ROCNIK_INFLACE_BODU,reseni__in=self.reseni_set.all())
novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku) novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku)
def body_z_hodnoceni(hh : list): def body_z_hodnoceni(hh : list):
@ -366,8 +339,8 @@ class Resitel(SeminarModelBase):
else: else:
return Titul.akad return Titul.akad
from .odevzdavatko import Hodnoceni from odevzdavatko.models import Hodnoceni
hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all()) hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(deadline_body__cislo__rocnik__rocnik__lte=settings.ROCNIK_INFLACE_TITULU,reseni__in=self.reseni_set.all())
novejsi_body = body_z_hodnoceni( novejsi_body = body_z_hodnoceni(
Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()) Hodnoceni.objects.filter(reseni__in=self.reseni_set.all())
.difference(hodnoceni_do_26_rocniku) .difference(hodnoceni_do_26_rocniku)
@ -438,6 +411,7 @@ class Organizator(SeminarModelBase):
return "{} {}".format(self.osoba.jmeno, self.osoba.prijmeni) return "{} {}".format(self.osoba.jmeno, self.osoba.prijmeni)
class Meta: class Meta:
db_table = 'mam_organizatori'
verbose_name = 'Organizátor' verbose_name = 'Organizátor'
verbose_name_plural = 'Organizátoři' verbose_name_plural = 'Organizátoři'
# Řadí aktivní orgy na začátek, pod tím v pořadí od nejstarších neaktivní orgy. # Řadí aktivní orgy na začátek, pod tím v pořadí od nejstarších neaktivní orgy.

223
personalni/testutils.py Normal file
View file

@ -0,0 +1,223 @@
import unidecode
import logging
import datetime
from pytz import timezone
from django.contrib.auth.models import Permission, Group
import django.contrib.auth
from .models import *
User = django.contrib.auth.get_user_model()
logger = logging.getLogger(__name__)
# testuje unikátnost vygenerovaného jména
def __unikatni_jmeno(osoby, jmeno, prijmeni):
for os in osoby:
if os.jmeno == jmeno and os.prijmeni == prijmeni:
return 0
return 1
def gen_osoby(rnd, size):
logger.info('Generuji osoby (size={})...'.format(size))
jmena_m = ['Aleš', 'Tomáš', 'Martin', 'Jakub', 'Petr', 'Lukáš', 'Cyril', 'Pavel Karel']
jmena_f = ['Eva', 'Karolína', 'Zuzana', 'Sylvie', 'Iva', 'Jana', 'Marie', 'Marta Iva', 'Shu Shan']
prijmeni_m = ['Novotný', 'Svoboda', 'Pecha', 'Kořen', 'Holan', 'Uhlíř', 'Chytráček', 'Pokora', 'Koch', 'Szegedy', 'Rudý', "von Neumann", "d'Este"]
prijmeni_f = ['Novotná', 'Svobodová', 'Machová', 'Zelená', 'Yu-Xin', 'Mlsná', 'Dubná', 'Mrkvová', 'Suchá', 'Lovelace', 'Holcová', 'Rui', "Nováčková Tydlitátová"]
prezdivky = ['Kaki', 'Hurdur', 'Maracuja', 'Bobbo', "", "", "", "", "", "", "", 'Riki', 'Sapa', "", '', '---', 'Koko']
domain = ['example.com', 'dolujeme.eu', 'mff.cuni.cz', 'strcprstskrzkrk.cz', 'british.co.uk', 'splachni.to', 'haha.org']
seznam_ulic = ['Krátká', 'Vlhká', 'Jungmanova', '17. listopadu', '4. října', 'Roztocká', 'Forstova', 'Generála Františka Janouška', 'Náměstí Války', 'Svratecké náměstí', 'Zelená lhota', 'Z Plynu', 'K Jezeru', 'U Kocourkova', 'Uštěpačná', 'Ostrorepská', 'Zubří']
seznam_mest = ['Praha', 'Brno', 'Ostrava', 'Horní Jelení', 'Dolní Zábrdovice', 'Prdelkov', 'Stará myslivna', 'Kocourkov', 'Šalingrad', 'Medvědí hora', 'Basilej', 'Unterschiedlich', 'Old York', 'Lancastershire', 'Vóloďháza']
osoby = []
# 30 je náhodná konstanta, size je použité na víc místech a
# říká, jak velká asi chceme testovací data
for i in range(30 * size):
pohlavi = rnd.randint(0, 1)
jmeno = rnd.choice([jmena_m, jmena_f][pohlavi])
prijmeni = rnd.choice([prijmeni_m, prijmeni_f][pohlavi])
pokusy = 0
max_pokusy = 120*size
while not __unikatni_jmeno and pokusy < max_pokusy:
# pokud jméno a příjmení není unikátní, zkoušíme generovat nová
# do daného limitu (abychom se nezacyklili do nekonečna při málo jménech a příjmeních
# ze kterých se generuje)
jmeno = rnd.choice([jmena_m, jmena_f][pohlavi])
prijmeni = rnd.choice([prijmeni_m, prijmeni_f][pohlavi])
pokusy += 1
if pokusy >= max_pokusy:
print("Chyba, na danou velikost testovacích dat příliš málo možných jmen a příjmení")
exit()
prezdivka = rnd.choice(prezdivky)
email = "@".join([unidecode.unidecode(jmeno), rnd.choice(domain)])
telefon = "".join([str(rnd.choice([k for k in range(10)])) for _ in range(9)])
narozeni = datetime.date(
rnd.randint(1980, datetime.datetime.now().year),
rnd.randint(1, 12), rnd.randint(1, 28),
)
ulic = rnd.choice(seznam_ulic)
cp = rnd.randint(1, 99)
ulice = " ".join([ulic, str(cp)])
mesto = rnd.choice(seznam_mest)
psc = "".join([str(rnd.choice([k for k in range(10)])) for _ in range(5)])
osoby.append(Osoba.objects.create(
jmeno=jmeno, prijmeni=prijmeni,
prezdivka=prezdivka, pohlavi_muz=pohlavi, email=email,
telefon=telefon, datum_narozeni=narozeni, ulice=ulice,
mesto=mesto, psc=psc,
datum_registrace=datetime.date(
rnd.randint(2019, 2029),
rnd.randint(1, 12), rnd.randint(1, 28)
),
))
# TODO pridat foto male a velke. Jak?
# Pavel tvrdí, že to necháme a přidáme až do adminu
return osoby
def gen_skoly(): # TODO někdy to přepsat, aby jich bylo více
logger.info('Generuji školy...')
skoly = []
prvnizs = Skola.objects.create(
mesto='Praha', stat='CZ', psc='101 00',
ulice='Krátká 5', nazev='První ZŠ', je_zs=True, je_ss=False,
)
skoly.append(prvnizs)
skoly.append(Skola.objects.create(
mesto='Praha', stat='CZ', psc='101 00',
ulice='Krátká 5', nazev='První SŠ', je_zs=False, je_ss=True,
))
skoly.append(Skola.objects.create(
mesto='Praha', stat='CZ', psc='102 00',
ulice='Dlouhá 5', nazev='Druhá SŠ', je_zs=False, je_ss=True,
))
skoly.append(Skola.objects.create(
mesto='Praha', stat='CZ', psc='103 00',
ulice='Široká 3', nazev='Třetí SŠ a ZŠ', je_zs=True, je_ss=True,
))
skoly.append(Skola.objects.create(
mesto='Ostrava', stat='CZ', psc='700 00',
ulice='Hluboká 42', nazev='Hutní gympl', je_zs=False, je_ss=True,
))
skoly.append(Skola.objects.create(
mesto='Humenné', stat='SK', psc='012 34',
ulice='Pltká 1', nazev='Sredná škuola', je_zs=False, je_ss=True,
))
# tohle bude speciální škola, které později dodáme kontaktní osobu
zlinska = None
zlinska = Skola.objects.create(
mesto='Zlín', stat='CZ', psc='76001',
ulice='náměstí T.G. Masaryka 2734-9',
nazev='Gymnázium a Střední jazyková škola s právem SJZ',
kratky_nazev="GaSJŠspSJZ", je_zs=True, je_ss=True,
)
skoly.append(zlinska)
return skoly, zlinska
def gen_resitele(rnd, osoby, skoly):
logger.info('Generuji řešitele...')
resitele = []
x = 0
resitel_perm = Permission.objects.filter(codename__exact='resitel').first()
resitel_group = Group.objects.filter(name__exact='resitel').first()
for os in osoby:
rand = rnd.randint(0, 8)
if not (rand % 8 == 0):
if not os.user:
if x:
user = User.objects.create_user(
username='r'+str(x), email=os.email, password='r',
)
else:
user = User.objects.create_user(
username='r', email=os.email, password='r',
)
x += 1
os.user = user
os.save()
os.user.user_permissions.add(resitel_perm)
os.user.groups.add(resitel_group)
resitele.append(Resitel.objects.create(
osoba=os, skola=rnd.choice(skoly),
rok_maturity=os.datum_narozeni.year + rnd.randint(18, 21),
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]
))
return resitele
def gen_prijemci(rnd, osoby, kolik=10):
logger.info('Generuji příjemce (kolik={})...'.format(kolik))
prijemci = []
for i in rnd.sample(osoby, kolik):
prijemci.append(Prijemce.objects.create(osoba=i))
return prijemci
def gen_organizatori(rnd, osoby, last_rocnik):
logger.info('Generuji organizátory...')
organizatori = []
seznam_konicku = ["vařím", "jezdím na kole", "řeším diferenciální rovnice", "koukám z okna", "tancuji", "programuji", "jezdím vlakem", "nedělám nic"]
seznam_oboru = ["matematiku", "matematiku", "matematiku", "fyziku", "literaturu", "informatiku", "informatiku", "běhání dokolečka"]
x = 0
org_perm = Permission.objects.filter(codename__exact='org').first()
org_group = Group.objects.filter(name__exact='org').first()
for os in osoby:
rand = rnd.randint(0, 8)
if rand % 8 == 0:
pusobnost = rnd.randint(1, last_rocnik)
od = datetime.datetime(
year=1993 + pusobnost,
month=rnd.randint(1, 12),
day=rnd.randint(1, 28),
tzinfo=timezone('CET'),
)
do = datetime.datetime(
year=od.year + rnd.randint(1, 6),
month=rnd.randint(1, 12),
day=rnd.randint(1, 28),
tzinfo=timezone('CET'),
)
# aktualni organizatori jeste nemaji vyplnene organizuje_do
# popis orga
konicek1 = rnd.choice(seznam_konicku)
konicek2 = rnd.choice(seznam_konicku)
obor = rnd.choice(seznam_oboru)
popis_orga = "Ve volném čase " + konicek1 + " a také " + konicek2 + ". Studuji " + obor + " a moc mě to baví."
if do.year > datetime.datetime.now().year:
do = None
if not os.user:
if x:
user = User.objects.create_user(
username='o'+str(x), email=os.email, password='o',
)
else:
user = User.objects.create_user(
username='o', email=os.email, password='o',
)
x += 1
os.user = user
os.save()
os.user.user_permissions.add(org_perm)
os.user.groups.add(org_group)
os.user.is_staff = True
os.user.save()
organizatori.append(Organizator.objects.create(
osoba=os,
organizuje_od=od, organizuje_do=do,
strucny_popis_organizatora=popis_orga
))
return organizatori

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