Merge branch 'unsafe_develop' into develop
# Conflicts: # odevzdavatko/views.py # seminar/admin.py # seminar/models.py # seminar/templates/seminar/archiv/cislo-normal.html # seminar/urls.py
This commit is contained in:
commit
f5570bdf6b
97 changed files with 3700 additions and 3418 deletions
|
@ -7,7 +7,7 @@ from django.utils.encoding import force_text
|
|||
|
||||
from .utils import default_ovvpfile
|
||||
from seminar.models import Rocnik, Soustredeni
|
||||
from seminar.views import vysledkovka
|
||||
from vysledkovky import utils
|
||||
from seminar.utils import aktivniResitele
|
||||
|
||||
class ExportIndexView(generic.View):
|
||||
|
@ -66,8 +66,8 @@ class ExportRocnikView(generic.View):
|
|||
rocnik = get_object_or_404(Rocnik, prvni_rok=pr, exportovat=True)
|
||||
cislo = rocnik.posledni_zverejnena_vysledkovka_cislo()
|
||||
resitele = aktivniResitele(cislo, True)
|
||||
slovnik_body = vysledkovka.secti_body_za_rocnik(cislo, resitele, False)
|
||||
setrizeni_resitele, body = vysledkovka.setrid_resitele_a_body(slovnik_body)
|
||||
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)
|
||||
|
|
|
@ -138,6 +138,11 @@ INSTALLED_APPS = (
|
|||
'various.autentizace',
|
||||
'api',
|
||||
'aesop',
|
||||
'odevzdavatko',
|
||||
'vysledkovky',
|
||||
'personalni',
|
||||
'soustredeni',
|
||||
'treenode',
|
||||
|
||||
# Admin upravy:
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.views.generic.base import TemplateView
|
|||
from django import views
|
||||
from django.urls import path # As per docs.
|
||||
|
||||
from .routers import router
|
||||
from treenode.routers import router
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
|
@ -16,19 +16,32 @@ urlpatterns = [
|
|||
|
||||
# Seminarova aplikace (ma vlastni podadresare)
|
||||
path('', include('seminar.urls')),
|
||||
|
||||
# Odevzdavatko (ma vlastni podadresare)
|
||||
path('', include('odevzdavatko.urls')),
|
||||
|
||||
# Korekturovaci aplikace (ma vlastni podadresare)
|
||||
path('', include('korektury.urls')),
|
||||
|
||||
|
||||
# Prednaskova aplikace (ma vlastni podadresare)
|
||||
path('', include('prednasky.urls')),
|
||||
|
||||
# Soustredkova aplikace (ma vlastni podadresare)
|
||||
path('', include('soustredeni.urls')),
|
||||
|
||||
# Personalni aplikace (ma vlastni podadresare)
|
||||
# (profil, osobní údaje, ..., ne autentizace, viz dále)
|
||||
path('', include('personalni.urls')),
|
||||
|
||||
# Autentizační aplikace (ma vlastni podadresare)
|
||||
path('', include('various.autentizace.urls')),
|
||||
|
||||
# Api (ma vlastni podadresare) (autocomplete apod.)
|
||||
path('', include('api.urls')),
|
||||
|
||||
# treenode (ma vlastni podadresare)
|
||||
path('', include('treenode.urls')),
|
||||
|
||||
# Aesop (ma vlastni podadresare)
|
||||
path('', include('aesop.urls')),
|
||||
|
||||
|
|
11
odevzdavatko/__init__.py
Normal file
11
odevzdavatko/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
"""
|
||||
Obsahuje vše, co se týká odevzdávání (+ nahrávání) a opravování řešení řešitelů.
|
||||
|
||||
Slovníček:
|
||||
Moje řešení = Přehled řešení = Řešení, která odevzdal aktuálního uživatel sám.
|
||||
Došlá řešení = Tabulka + seznam + detail + ... = Řešení, která poslal někdo jiný.
|
||||
Poslat řešení = Odevdat mé řešení. (Tj. řešení se vztahem k aktuálnímu uživateli.)
|
||||
Nahrát řešení = Nahrání řešení bez vztahu k aktuálnímu uživateli.
|
||||
|
||||
TODO: Místo vložit řešení v nahrávání a posílání řešení dát něco jiného?
|
||||
"""
|
28
odevzdavatko/admin.py
Normal file
28
odevzdavatko/admin.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from django.contrib import admin
|
||||
from django_reverse_admin import ReverseModelAdmin
|
||||
import seminar.models as m
|
||||
|
||||
|
||||
class PrilohaReseniInline(admin.TabularInline):
|
||||
model = m.PrilohaReseni
|
||||
extra = 1
|
||||
|
||||
|
||||
class Reseni_ResiteleInline(admin.TabularInline):
|
||||
model = m.Reseni_Resitele
|
||||
|
||||
|
||||
@admin.register(m.Reseni)
|
||||
class ReseniAdmin(ReverseModelAdmin):
|
||||
base_model = m.Reseni
|
||||
inline_type = 'tabular'
|
||||
# inline_reverse = ['text_cely','resitele'] TODO vrátit zpět a zrychlit dotaz
|
||||
inline_reverse = ['resitele']
|
||||
exclude = ['text_zkraceny', 'text_zkraceny_set']
|
||||
inlines = [PrilohaReseniInline]
|
||||
# FAIL in template
|
||||
# inlines = [PrilohaReseniInline,Reseni_ResiteleInline]
|
||||
|
||||
|
||||
admin.site.register(m.PrilohaReseni)
|
||||
admin.site.register(m.Hodnoceni)
|
5
odevzdavatko/apps.py
Normal file
5
odevzdavatko/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OdevzdavatkoConfig(AppConfig):
|
||||
name = 'odevzdavatko'
|
218
odevzdavatko/forms.py
Normal file
218
odevzdavatko/forms.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
from django import forms
|
||||
from dal import autocomplete
|
||||
from django.forms import formset_factory
|
||||
from django.forms.models import inlineformset_factory
|
||||
|
||||
from seminar.models import Resitel
|
||||
import seminar.models as m
|
||||
|
||||
import logging
|
||||
|
||||
# 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
|
||||
|
||||
class DateInput(forms.DateInput):
|
||||
# aby se datum dalo vybírat z kalendáře
|
||||
input_type = 'date'
|
||||
|
||||
|
||||
class PosliReseniForm(forms.Form):
|
||||
#FIXME jen podproblémy daného problému
|
||||
problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all())
|
||||
# to_field_name
|
||||
#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
|
||||
# through='Hodnoceni')
|
||||
|
||||
# FIXME pridat vice resitelu
|
||||
resitel = forms.ModelChoiceField(label="Řešitel",
|
||||
queryset=Resitel.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url='autocomplete_resitel',
|
||||
attrs = {'data-placeholder--id': '-1',
|
||||
'data-placeholder--text' : '---',
|
||||
'data-allow-clear': 'true'})
|
||||
)
|
||||
|
||||
|
||||
#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení',
|
||||
# help_text='Seznam autorů řešení', through='Reseni_Resitele')
|
||||
|
||||
cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení")
|
||||
|
||||
#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 = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
|
||||
# default=FORMA_EMAIL)
|
||||
|
||||
poznamka = forms.CharField(label='Neveřejná poznámka', required=False)
|
||||
#poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
# help_text='Neveřejná poznámka k řešení (plain text)')
|
||||
|
||||
#TODO body do cisla
|
||||
#TODO prilohy
|
||||
|
||||
##def __init__(self, *args, **kwargs):
|
||||
## super().__init__(*args, **kwargs)
|
||||
## #self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()])
|
||||
|
||||
class NahrajReseniForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Reseni
|
||||
fields = ('problem',)
|
||||
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
|
||||
|
||||
widgets = {'problem':
|
||||
autocomplete.ModelSelect2Multiple(
|
||||
url='autocomplete_problem_odevzdatelny',
|
||||
attrs = {'data-placeholder--id': '-1',
|
||||
'data-placeholder--text' : '---',
|
||||
'data-allow-clear': 'true'},
|
||||
)
|
||||
}
|
||||
|
||||
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
|
||||
form = NahrajReseniForm,
|
||||
fields = ('soubor','res_poznamka'),
|
||||
widgets = {'res_poznamka':forms.TextInput()},
|
||||
extra = 1,
|
||||
can_delete = False,
|
||||
|
||||
)
|
||||
|
||||
|
||||
class JednoHodnoceniForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Hodnoceni
|
||||
fields = ('problem', 'body', 'cislo_body')
|
||||
widgets = {
|
||||
'problem': autocomplete.ModelSelect2(
|
||||
url='autocomplete_problem_odevzdatelny', # FIXME: Dovolit i starší?
|
||||
)
|
||||
}
|
||||
|
||||
OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
|
||||
extra = 0,
|
||||
)
|
||||
|
||||
class PoznamkaReseniForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Reseni
|
||||
fields = ('poznamka',)
|
||||
|
||||
# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat
|
||||
DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
class OdevzdavatkoTabulkaFiltrForm(forms.Form):
|
||||
"""Form pro filtrování přehledové odevzdávátkové tabulky
|
||||
|
||||
Inspirováno https://kam.mff.cuni.cz/mffzoom/"""
|
||||
|
||||
# Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices)
|
||||
|
||||
RESITELE_RELEVANTNI = 'relevantni'
|
||||
RESITELE_NEODMATUROVAVSI = 'neodmaturovavsi'
|
||||
RESITELE_CHOICES = [
|
||||
(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky
|
||||
(RESITELE_NEODMATUROVAVSI, 'Všichni bez maturity'),
|
||||
# Možná: všechny vč. historických?
|
||||
]
|
||||
|
||||
PROBLEMY_MOJE = 'moje'
|
||||
PROBLEMY_LETOSNI = 'letosni'
|
||||
PROBLEMY_CHOICES = [
|
||||
(PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga
|
||||
(PROBLEMY_LETOSNI, 'Všechny letošní'),
|
||||
# TODO: *hlavní problémy, možná všechny...
|
||||
# XXX: Chtělo by to i "aktuálně zadané...
|
||||
]
|
||||
|
||||
# TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)?
|
||||
|
||||
|
||||
@classmethod
|
||||
def gen_terminy(cls, rocnik=None):
|
||||
import datetime
|
||||
from time import strftime
|
||||
|
||||
from django.db.utils import OperationalError
|
||||
try:
|
||||
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik
|
||||
aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo
|
||||
except OperationalError:
|
||||
# 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
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error("Rozbitá databáze (před počátečními migracemi?)")
|
||||
return [('broken', 'Je to rozbitý'), ('fubar', 'Nefunguje to')]
|
||||
|
||||
# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš.
|
||||
if rocnik is not None:
|
||||
aktualni_rocnik = rocnik
|
||||
aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last()
|
||||
|
||||
result = []
|
||||
|
||||
for cislo in m.Cislo.objects.filter(
|
||||
rocnik=aktualni_rocnik,
|
||||
poradi__lte=aktualni_cislo.poradi,
|
||||
).reverse(): # Standardně se řadí od nejnovějšího čísla
|
||||
# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst...
|
||||
if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()),
|
||||
f"Vydání {cislo.poradi}. čísla"))
|
||||
if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()),
|
||||
f"Předdeadline {cislo.poradi}. čísla"))
|
||||
if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()),
|
||||
f"Sous. deadline {cislo.poradi}. čísla"))
|
||||
if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()),
|
||||
f"Finální deadline {cislo.poradi}. čísla"))
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes"))
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def gen_initial(cls, rocnik=None):
|
||||
terminy = cls.gen_terminy(rocnik)
|
||||
initial = {
|
||||
'resitele': cls.RESITELE_RELEVANTNI,
|
||||
'problemy': cls.PROBLEMY_MOJE,
|
||||
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení…
|
||||
'reseni_od': terminy[-2] if rocnik is None else terminy[0],
|
||||
'reseni_do': terminy[-1],
|
||||
'neobodovane': False,
|
||||
}
|
||||
return initial
|
||||
|
||||
def __init__(self, *args, rocnik=None, **kwargs):
|
||||
if 'initial' not in kwargs:
|
||||
super().__init__(initial=self.gen_initial(rocnik), *args, **kwargs)
|
||||
else:
|
||||
super().__init__(*args, **kwargs)
|
||||
# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem...
|
||||
# A "sem" znamená do libovolné metody, protože jinak se jedná o kód, který django spustí při inicializaci a protože potřebujeme databázi, tak by spadnul při vyrábění testdat...
|
||||
self.terminy = self.gen_terminy(rocnik)
|
||||
self.fields['reseni_od'].widget = forms.Select(choices=self.gen_terminy(rocnik))
|
||||
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení…
|
||||
self.fields['reseni_od'].initial = self.terminy[-2] if rocnik is None else self.terminy[0]
|
||||
self.fields['reseni_do'].widget = forms.Select(choices=self.gen_terminy(rocnik))
|
||||
self.fields['reseni_do'].initial = self.terminy[-1]
|
||||
|
||||
# NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views...
|
||||
resitele = forms.ChoiceField(choices=RESITELE_CHOICES)
|
||||
problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES)
|
||||
|
||||
reseni_od = forms.DateField(input_formats=[DATE_FORMAT])
|
||||
reseni_do = forms.DateField(input_formats=[DATE_FORMAT])
|
||||
neobodovane = forms.BooleanField(required=False)
|
0
odevzdavatko/migrations/__init__.py
Normal file
0
odevzdavatko/migrations/__init__.py
Normal file
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 717 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
@ -4,7 +4,7 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
{# FIXME: Necopypastovat! Tohle je zkopírované ze static/seminar/dynamic_formsets.js #}
|
||||
{# FIXME: Necopypastovat! Tohle je zkopírované ze static/odevzdavatko/dynamic_formsets.js #}
|
||||
<script type='text/javascript'>
|
||||
// Credit https://medium.com/all-about-django/adding-forms-dynamically-to-a-django-formset-375f1090c2b0
|
||||
function updateElementIndex(el, prefix, ndx) {
|
||||
|
@ -104,14 +104,14 @@ $(document).ready(function(){
|
|||
<td>{{ subform.problem }}</td>
|
||||
<td>{{ subform.body }}</td>
|
||||
<td>{{ subform.cislo_body }}</td>
|
||||
<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "seminar/cross.png" %}" alt="Smazat"></a></td>
|
||||
<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<a href="#"> <img src="{% static "seminar/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> </br>
|
||||
<a href="#"> <img src="{% static "odevzdavatko/plus.png" %}" id="pridat_hodnoceni" alt="Přidat hodnocení"></a> </br>
|
||||
<input type=submit value="Uložit"></form>
|
||||
|
||||
<table id="empty_form" style="display: none;">
|
||||
|
@ -119,7 +119,7 @@ $(document).ready(function(){
|
|||
<td>{{ form.empty_form.problem }}</td>
|
||||
<td>{{ form.empty_form.body }}</td>
|
||||
<td>{{ form.empty_form.cislo_body }}</td>
|
||||
<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "seminar/cross.png" %}" alt="Smazat"></a></td>
|
||||
<td><a href="#" class="smazat_hodnoceni" id="id_{{subform.prefix}}-jsremove"><img src="{% static "odevzdavatko/cross.png" %}" alt="Smazat"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% block script %}
|
||||
<script src="{% static 'seminar/dynamic_formsets.js' %}"></script>
|
||||
<script src="{% static 'odevzdavatko/dynamic_formsets.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
|
@ -3,7 +3,6 @@
|
|||
{% block script %}
|
||||
<!--script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script!-->
|
||||
{{form.media}}
|
||||
<script src="{% static 'seminar/prihlaska.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<form method=get action=.>
|
||||
<form method=get action=../odevzdavatko>
|
||||
{{ filtr.resitele }}
|
||||
{{ filtr.problemy }}
|
||||
Od: {{ filtr.reseni_od }}
|
18
odevzdavatko/urls.py
Normal file
18
odevzdavatko/urls.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from django.urls import path
|
||||
|
||||
from seminar.utils import org_required, resitel_required, viewMethodSwitch, \
|
||||
resitel_or_org_required
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('org/add_solution', org_required(views.PosliReseniView.as_view()), name='seminar_vloz_reseni'),
|
||||
path('resitel/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'),
|
||||
path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'),
|
||||
|
||||
path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
||||
path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
||||
path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
|
||||
path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'),
|
||||
path('org/reseni/all', org_required(views.SeznamReseniView.as_view())),
|
||||
path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
|
||||
]
|
|
@ -1,9 +1,12 @@
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.views.generic import ListView, DetailView, FormView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.mail import send_mail
|
||||
from django.utils import timezone
|
||||
from django.views.generic import ListView, DetailView, FormView, CreateView
|
||||
from django.views.generic.list import MultipleObjectTemplateResponseMixin,MultipleObjectMixin
|
||||
from django.views.generic.base import View
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.shortcuts import redirect, get_object_or_404, render
|
||||
from django.urls import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
|
@ -14,9 +17,10 @@ from itertools import groupby
|
|||
import logging
|
||||
|
||||
import seminar.models as m
|
||||
import seminar.forms as f
|
||||
from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
|
||||
from seminar.utils import aktivniResitele, resi_v_rocniku, deadline
|
||||
from . import forms as f
|
||||
from .forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm
|
||||
from seminar.utils import resi_v_rocniku, deadline
|
||||
from seminar.views import formularOKView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -42,7 +46,7 @@ class SouhrnReseni:
|
|||
|
||||
|
||||
class TabulkaOdevzdanychReseniView(ListView):
|
||||
template_name = 'seminar/odevzdavatko/tabulka.html'
|
||||
template_name = 'odevzdavatko/tabulka.html'
|
||||
model = m.Hodnoceni
|
||||
|
||||
def inicializuj_osy_tabulky(self):
|
||||
|
@ -166,7 +170,7 @@ class TabulkaOdevzdanychReseniView(ListView):
|
|||
# Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?
|
||||
class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View):
|
||||
model = m.Reseni
|
||||
template_name = 'seminar/odevzdavatko/seznam.html'
|
||||
template_name = 'odevzdavatko/seznam.html'
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
|
@ -208,7 +212,7 @@ class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixi
|
|||
## XXX: https://docs.djangoproject.com/en/3.1/topics/class-based-views/mixins/#avoid-anything-more-complex
|
||||
class DetailReseniView(DetailView):
|
||||
model = m.Reseni
|
||||
template_name = 'seminar/odevzdavatko/detail.html'
|
||||
template_name = 'odevzdavatko/detail.html'
|
||||
|
||||
def aktualni_hodnoceni(self):
|
||||
self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
|
||||
|
@ -232,7 +236,7 @@ class DetailReseniView(DetailView):
|
|||
|
||||
def hodnoceniReseniView(request, pk, *args, **kwargs):
|
||||
reseni = get_object_or_404(m.Reseni, pk=pk)
|
||||
template_name = 'seminar/odevzdavatko/detail.html'
|
||||
template_name = 'odevzdavatko/detail.html'
|
||||
form_class = f.OhodnoceniReseniFormSet
|
||||
success_url = reverse('odevzdavatko_detail_reseni', kwargs={'pk': pk})
|
||||
|
||||
|
@ -271,7 +275,7 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
|
|||
|
||||
class ResitelReseniView(DetailView):
|
||||
model = m.Reseni
|
||||
template_name = 'seminar/odevzdavatko/detail_resitele.html'
|
||||
template_name = 'odevzdavatko/detail_resitele.html'
|
||||
|
||||
def aktualni_hodnoceni(self):
|
||||
self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
|
||||
|
@ -299,7 +303,7 @@ class ResitelReseniView(DetailView):
|
|||
|
||||
class PrehledOdevzdanychReseni(ListView):
|
||||
model = m.Hodnoceni
|
||||
template_name = 'seminar/odevzdavatko/resitel_prehled.html'
|
||||
template_name = 'odevzdavatko/prehled_reseni.html'
|
||||
|
||||
def get_queryset(self):
|
||||
if not self.request.user.is_authenticated:
|
||||
|
@ -325,7 +329,7 @@ class PrehledOdevzdanychReseni(ListView):
|
|||
|
||||
class SeznamReseniView(ListView):
|
||||
model = m.Reseni
|
||||
template_name = 'seminar/odevzdavatko/seznam.html'
|
||||
template_name = 'odevzdavatko/seznam.html'
|
||||
|
||||
class SeznamAktualnichReseniView(SeznamReseniView):
|
||||
def get_queryset(self):
|
||||
|
@ -334,3 +338,94 @@ class SeznamAktualnichReseniView(SeznamReseniView):
|
|||
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
|
||||
return qs
|
||||
|
||||
|
||||
class PosliReseniView(LoginRequiredMixin, FormView):
|
||||
template_name = 'odevzdavatko/posli_reseni.html'
|
||||
form_class = f.PosliReseniForm
|
||||
|
||||
def form_valid(self, form):
|
||||
data = form.cleaned_data
|
||||
nove_reseni = m.Reseni.objects.create(
|
||||
cas_doruceni=data['cas_doruceni'],
|
||||
forma=data['forma'],
|
||||
poznamka=data['poznamka'],
|
||||
)
|
||||
nove_reseni.resitele.add(data['resitel'])
|
||||
nove_reseni.problem.add(data['problem'])
|
||||
nove_reseni.save()
|
||||
# Chtěl jsem, aby bylo vidět, že se to uložilo, tak přesměrovávám na profil.
|
||||
return redirect(reverse('profil'))
|
||||
|
||||
|
||||
class NahrajReseniView(LoginRequiredMixin, CreateView):
|
||||
model = m.Reseni
|
||||
template_name = 'odevzdavatko/nahraj_reseni.html'
|
||||
form_class = f.NahrajReseniForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Zaříznutí starých řešitelů:
|
||||
# FIXME: Je to tady dost naprasené, mělo by to asi být jinde…
|
||||
osoba = m.Osoba.objects.get(user=self.request.user)
|
||||
resitel = osoba.resitel
|
||||
if resitel.rok_maturity <= m.Nastaveni.get_solo().aktualni_rocnik.prvni_rok:
|
||||
return render(request, 'universal.html', {
|
||||
'title': 'Nelze odevzdat',
|
||||
'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.',
|
||||
'text': 'Pokud se ti zdá, že to je chyba, napiš nám prosím e-mail. Díky.',
|
||||
})
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self,**kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
if self.request.POST:
|
||||
data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES)
|
||||
else:
|
||||
data['prilohy'] = f.ReseniSPrilohamiFormSet()
|
||||
return data
|
||||
|
||||
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni
|
||||
# Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset
|
||||
def form_valid(self,form):
|
||||
context = self.get_context_data()
|
||||
prilohy = context['prilohy']
|
||||
if not prilohy.is_valid():
|
||||
return super().form_invalid(form)
|
||||
with transaction.atomic():
|
||||
self.object = form.save()
|
||||
self.object.resitele.add(m.Resitel.objects.get(osoba__user = self.request.user))
|
||||
self.object.cas_doruceni = timezone.now()
|
||||
self.object.forma = m.Reseni.FORMA_UPLOAD
|
||||
self.object.save()
|
||||
|
||||
prilohy.instance = self.object
|
||||
prilohy.save()
|
||||
|
||||
# Pošleme mail opravovatelům a garantovi
|
||||
# FIXME: Nechat spočítat databázi? Je to pár dotazů (pravděpodobně), takže to za to možná nestojí
|
||||
prijemci = set()
|
||||
problemy = []
|
||||
for prob in form.cleaned_data['problem']:
|
||||
prijemci.update(prob.opravovatele.all())
|
||||
if prob.garant is not None:
|
||||
prijemci.add(prob.garant)
|
||||
problemy.append(prob)
|
||||
# FIXME: Možná poslat mail i relevantním orgům nadproblémů?
|
||||
if len(prijemci) < 1:
|
||||
logger.warning(f"Pozor, neposílám e-mail nikomu. Problémy: {problemy}")
|
||||
# FIXME: Víc informativní obsah mailů, možná vč. příloh?
|
||||
prijemci = map(lambda it: it.osoba.email, prijemci)
|
||||
|
||||
resitel = m.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_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })")
|
||||
|
||||
send_mail(
|
||||
subject="Nové řešení k " + seznam_do_subjectu,
|
||||
message=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í?
|
||||
recipient_list=list(prijemci),
|
||||
)
|
||||
|
||||
return formularOKView(self.request, text='Řešení úspěšně odevzdáno')
|
4
personalni/__init__.py
Normal file
4
personalni/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
Obsahuje vše okolo registrace a osobních údajů (ne přihlášení a změnu hesla).
|
||||
Také obsahuje rozcestníky a Řešitele s Organizátorem.
|
||||
"""
|
48
personalni/admin.py
Normal file
48
personalni/admin.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group
|
||||
from django_reverse_admin import ReverseModelAdmin
|
||||
import seminar.models as m
|
||||
|
||||
|
||||
@admin.register(m.Osoba)
|
||||
class OsobaAdmin(admin.ModelAdmin):
|
||||
actions = ['synchronizuj_maily', 'udelej_orgem']
|
||||
|
||||
def synchronizuj_maily(self, request, queryset):
|
||||
for o in queryset:
|
||||
if o.user is not None:
|
||||
u = o.user
|
||||
u.email = o.email
|
||||
u.save()
|
||||
self.message_user(request, "E-maily synchronizovány.")
|
||||
synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů"
|
||||
|
||||
def udelej_orgem(self,request,queryset):
|
||||
org_group = Group.objects.get(name='org')
|
||||
print(queryset)
|
||||
for o in queryset:
|
||||
user = o.user
|
||||
print(user)
|
||||
user.groups.add(org_group)
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
org = m.Organizator.objects.create(osoba=o)
|
||||
org.save()
|
||||
udelej_orgem.short_description = "Udělej vybraných osob organizátory"
|
||||
|
||||
@admin.register(m.Organizator)
|
||||
class OrganizatorAdmin(admin.ModelAdmin):
|
||||
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
|
||||
|
||||
class OsobaInline(admin.TabularInline):
|
||||
model = m.Osoba
|
||||
|
||||
@admin.register(m.Resitel)
|
||||
class ResitelAdmin(ReverseModelAdmin):
|
||||
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
|
||||
ordering = ('osoba__jmeno','osoba__prijmeni')
|
||||
inline_type = 'stacked'
|
||||
inline_reverse = ['osoba']
|
||||
|
||||
admin.site.register(m.Skola)
|
||||
admin.site.register(m.Prijemce)
|
5
personalni/apps.py
Normal file
5
personalni/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PersonalniConfig(AppConfig):
|
||||
name = 'personalni'
|
|
@ -3,11 +3,8 @@ from dal import autocomplete
|
|||
from django.contrib.auth.forms import PasswordResetForm
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.auth.models import User
|
||||
from django.forms import formset_factory
|
||||
from django.forms.models import inlineformset_factory
|
||||
|
||||
from .models import Skola, Resitel, Osoba, Problem
|
||||
import seminar.models as m
|
||||
from seminar.models import Skola, Resitel, Osoba
|
||||
|
||||
from datetime import date
|
||||
import logging
|
||||
|
@ -19,13 +16,13 @@ import logging
|
|||
# - includovat do html
|
||||
|
||||
class DateInput(forms.DateInput):
|
||||
# aby se datum dalo vybírat z kalendáře
|
||||
input_type = 'date'
|
||||
|
||||
# aby se datum dalo vybírat z kalendáře
|
||||
input_type = 'date'
|
||||
|
||||
class TelInput(forms.TextInput):
|
||||
# tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí
|
||||
input_type = 'tel'
|
||||
input_pattern="^[+]?[()/0-9. -]{9,}$"
|
||||
# tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí
|
||||
input_type = 'tel'
|
||||
input_pattern="^[+]?[()/0-9. -]{9,}$"
|
||||
|
||||
|
||||
class PrihlaskaForm(PasswordResetForm):
|
||||
|
@ -58,7 +55,7 @@ class PrihlaskaForm(PasswordResetForm):
|
|||
attrs = {'data-placeholder--id': '-1',
|
||||
'data-placeholder--text' : '---',
|
||||
'data-allow-clear': 'true'})
|
||||
,required=False)
|
||||
,required=False)
|
||||
|
||||
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
|
||||
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
|
||||
|
@ -156,7 +153,7 @@ class ProfileEditForm(forms.Form):
|
|||
attrs = {'data-placeholder--id': '-1',
|
||||
'data-placeholder--text' : '---',
|
||||
'data-allow-clear': 'true'})
|
||||
,required=False)
|
||||
,required=False)
|
||||
|
||||
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
|
||||
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
|
||||
|
@ -217,210 +214,8 @@ class ProfileEditForm(forms.Form):
|
|||
# elif data.get('skola_adresa')=='':
|
||||
# self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
|
||||
|
||||
|
||||
class PoMaturiteProfileEditForm(ProfileEditForm):
|
||||
rok_maturity = forms.IntegerField(
|
||||
label='Rok maturity',
|
||||
required=True)
|
||||
|
||||
class VlozReseniForm(forms.Form):
|
||||
#FIXME jen podproblémy daného problému
|
||||
problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all())
|
||||
# to_field_name
|
||||
#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
|
||||
# through='Hodnoceni')
|
||||
|
||||
# FIXME pridat vice resitelu
|
||||
resitel = forms.ModelChoiceField(label="Řešitel",
|
||||
queryset=Resitel.objects.all(),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url='autocomplete_resitel',
|
||||
attrs = {'data-placeholder--id': '-1',
|
||||
'data-placeholder--text' : '---',
|
||||
'data-allow-clear': 'true'})
|
||||
)
|
||||
|
||||
|
||||
#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení',
|
||||
# help_text='Seznam autorů řešení', through='Reseni_Resitele')
|
||||
|
||||
cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení")
|
||||
|
||||
#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 = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
|
||||
# default=FORMA_EMAIL)
|
||||
|
||||
poznamka = forms.CharField(label='Neveřejná poznámka', required=False)
|
||||
#poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
# help_text='Neveřejná poznámka k řešení (plain text)')
|
||||
|
||||
#TODO body do cisla
|
||||
#TODO prilohy
|
||||
|
||||
##def __init__(self, *args, **kwargs):
|
||||
## super().__init__(*args, **kwargs)
|
||||
## #self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()])
|
||||
|
||||
class NahrajReseniForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Reseni
|
||||
fields = ('problem',)
|
||||
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
|
||||
|
||||
widgets = {'problem':
|
||||
autocomplete.ModelSelect2Multiple(
|
||||
url='autocomplete_problem_odevzdatelny',
|
||||
attrs = {'data-placeholder--id': '-1',
|
||||
'data-placeholder--text' : '---',
|
||||
'data-allow-clear': 'true'},
|
||||
)
|
||||
}
|
||||
|
||||
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
|
||||
form = NahrajReseniForm,
|
||||
fields = ('soubor','res_poznamka'),
|
||||
widgets = {'res_poznamka':forms.TextInput()},
|
||||
extra = 1,
|
||||
can_delete = False,
|
||||
|
||||
)
|
||||
|
||||
class NahrajObrazekKTreeNoduForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Obrazek
|
||||
fields = ('na_web',)
|
||||
|
||||
|
||||
class JednoHodnoceniForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Hodnoceni
|
||||
fields = ('problem', 'body', 'cislo_body')
|
||||
widgets = {
|
||||
'problem': autocomplete.ModelSelect2(
|
||||
url='autocomplete_problem_odevzdatelny', # FIXME: Dovolit i starší?
|
||||
)
|
||||
}
|
||||
|
||||
OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
|
||||
extra = 0,
|
||||
)
|
||||
|
||||
class PoznamkaReseniForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Reseni
|
||||
fields = ('poznamka',)
|
||||
|
||||
# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat
|
||||
DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
class OdevzdavatkoTabulkaFiltrForm(forms.Form):
|
||||
"""Form pro filtrování přehledové odevzdávátkové tabulky
|
||||
|
||||
Inspirováno https://kam.mff.cuni.cz/mffzoom/"""
|
||||
|
||||
# Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices)
|
||||
|
||||
RESITELE_RELEVANTNI = 'relevantni'
|
||||
RESITELE_NEODMATUROVAVSI = 'neodmaturovavsi'
|
||||
RESITELE_CHOICES = [
|
||||
(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky
|
||||
(RESITELE_NEODMATUROVAVSI, 'Všichni bez maturity'),
|
||||
# Možná: všechny vč. historických?
|
||||
]
|
||||
|
||||
PROBLEMY_MOJE = 'moje'
|
||||
PROBLEMY_LETOSNI = 'letosni'
|
||||
PROBLEMY_CHOICES = [
|
||||
(PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga
|
||||
(PROBLEMY_LETOSNI, 'Všechny letošní'),
|
||||
# TODO: *hlavní problémy, možná všechny...
|
||||
# XXX: Chtělo by to i "aktuálně zadané...
|
||||
]
|
||||
|
||||
# TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)?
|
||||
|
||||
|
||||
@classmethod
|
||||
def gen_terminy(cls, rocnik=None):
|
||||
import datetime
|
||||
from time import strftime
|
||||
|
||||
from django.db.utils import OperationalError
|
||||
try:
|
||||
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik
|
||||
aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo
|
||||
except OperationalError:
|
||||
# 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
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error("Rozbitá databáze (před počátečními migracemi?)")
|
||||
return [('broken', 'Je to rozbitý'), ('fubar', 'Nefunguje to')]
|
||||
|
||||
# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš.
|
||||
if rocnik is not None:
|
||||
aktualni_rocnik = rocnik
|
||||
aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last()
|
||||
|
||||
result = []
|
||||
|
||||
for cislo in m.Cislo.objects.filter(
|
||||
rocnik=aktualni_rocnik,
|
||||
poradi__lte=aktualni_cislo.poradi,
|
||||
).reverse(): # Standardně se řadí od nejnovějšího čísla
|
||||
# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst...
|
||||
if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()),
|
||||
f"Vydání {cislo.poradi}. čísla"))
|
||||
if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()),
|
||||
f"Předdeadline {cislo.poradi}. čísla"))
|
||||
if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()),
|
||||
f"Sous. deadline {cislo.poradi}. čísla"))
|
||||
if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today():
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()),
|
||||
f"Finální deadline {cislo.poradi}. čísla"))
|
||||
result.append((
|
||||
strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes"))
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def gen_initial(cls, rocnik=None):
|
||||
terminy = cls.gen_terminy(rocnik)
|
||||
initial = {
|
||||
'resitele': cls.RESITELE_RELEVANTNI,
|
||||
'problemy': cls.PROBLEMY_MOJE,
|
||||
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení…
|
||||
'reseni_od': terminy[-2] if rocnik is None else terminy[0],
|
||||
'reseni_do': terminy[-1],
|
||||
'neobodovane': False,
|
||||
}
|
||||
return initial
|
||||
|
||||
def __init__(self, *args, rocnik=None, **kwargs):
|
||||
if 'initial' not in kwargs:
|
||||
super().__init__(initial=self.gen_initial(rocnik), *args, **kwargs)
|
||||
else:
|
||||
super().__init__(*args, **kwargs)
|
||||
# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem...
|
||||
# A "sem" znamená do libovolné metody, protože jinak se jedná o kód, který django spustí při inicializaci a protože potřebujeme databázi, tak by spadnul při vyrábění testdat...
|
||||
self.terminy = self.gen_terminy(rocnik)
|
||||
self.fields['reseni_od'].widget = forms.Select(choices=self.gen_terminy(rocnik))
|
||||
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení…
|
||||
self.fields['reseni_od'].initial = self.terminy[-2] if rocnik is None else self.terminy[0]
|
||||
self.fields['reseni_do'].widget = forms.Select(choices=self.gen_terminy(rocnik))
|
||||
self.fields['reseni_do'].initial = self.terminy[-1]
|
||||
|
||||
# NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views...
|
||||
resitele = forms.ChoiceField(choices=RESITELE_CHOICES)
|
||||
problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES)
|
||||
|
||||
reseni_od = forms.DateField(input_formats=[DATE_FORMAT])
|
||||
reseni_do = forms.DateField(input_formats=[DATE_FORMAT])
|
||||
neobodovane = forms.BooleanField(required=False)
|
0
personalni/migrations/__init__.py
Normal file
0
personalni/migrations/__init__.py
Normal file
105
personalni/templates/personalni/udaje/edit.html
Normal file
105
personalni/templates/personalni/udaje/edit.html
Normal file
|
@ -0,0 +1,105 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static 'personalni/prihlaska.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
<!--
|
||||
|
||||
# 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
|
||||
|
||||
-->
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
Změna osobních údajů
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
<form action="{% url 'seminar_resitel_edit' %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{form.non_field_errors}}
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Přihlašovací údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.username %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Osobní údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.telefon %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.datum_narozeni %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Bydliště
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.ulice %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.mesto %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.psc %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Škola
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola %}
|
||||
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
|
||||
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.rok_maturity %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Pošta
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Zasílání propagačních materiálů
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.spam %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<input type="submit" value="Změnit">
|
||||
</form>
|
||||
<script>
|
||||
$("#id_stat").on("change",addrCountryChanged);
|
||||
$("#id_skola_text_button").on("click",schoolNotInList);
|
||||
</script>
|
||||
{% endblock %}
|
123
personalni/templates/personalni/udaje/prihlaska.html
Normal file
123
personalni/templates/personalni/udaje/prihlaska.html
Normal file
|
@ -0,0 +1,123 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static 'personalni/prihlaska.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
<!--
|
||||
|
||||
# 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
|
||||
|
||||
-->
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
Přihláška do semináře
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
|
||||
<p><b>Tučně</b> popsaná pole jsou povinná.</p>
|
||||
|
||||
<form action="{% url 'seminar_prihlaska' %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{form.non_field_errors}}
|
||||
|
||||
|
||||
<hr>
|
||||
<h4>
|
||||
Přihlašovací údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.username %}
|
||||
{# {% include "personalni/udaje/prihlaska_field.html" with field=form.password %}#}
|
||||
{# {% include "personalni/udaje/prihlaska_field.html" with field=form.password_check %}#}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Osobní údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.jmeno %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.prijmeni %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.pohlavi_muz%}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.email %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.telefon %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.datum_narozeni %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Bydliště
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.ulice %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.mesto %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.psc %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Škola
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola %}
|
||||
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
|
||||
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.rok_maturity %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Pošta
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat %}
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
|
||||
</table>
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
GDPR
|
||||
</h4>
|
||||
{% include "personalni/udaje/gdpr.html" %}
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.gdpr %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Zasílání propagačních materiálů
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "personalni/udaje/prihlaska_field.html" with field=form.spam %}
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<input type="submit" value="Odeslat">
|
||||
</form>
|
||||
<script>
|
||||
$("#id_stat").on("change",addrCountryChanged);
|
||||
$("#id_skola_text_button").on("click",schoolNotInList);
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
24
personalni/urls.py
Normal file
24
personalni/urls.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from django.urls import path
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from . import views
|
||||
from seminar.utils import org_required
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
'org/rozcestnik/',
|
||||
org_required(views.OrgoRozcestnikView.as_view()),
|
||||
name='seminar_org_rozcestnik'
|
||||
),
|
||||
|
||||
path('prihlaska/', views.prihlaskaView, name='seminar_prihlaska'),
|
||||
|
||||
path(
|
||||
'resitel/osobni-udaje/',
|
||||
login_required(views.resitelEditView),
|
||||
name='seminar_resitel_edit'
|
||||
),
|
||||
|
||||
# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku
|
||||
path('profil/', views.profilView, name='profil'),
|
||||
|
||||
]
|
306
personalni/views.py
Normal file
306
personalni/views.py
Normal file
|
@ -0,0 +1,306 @@
|
|||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.views import generic
|
||||
from django.db.models import Q
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.contrib.auth.models import User, Permission, Group
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import transaction
|
||||
|
||||
import seminar.models as s
|
||||
import seminar.models as m
|
||||
from .forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
|
||||
|
||||
from datetime import date
|
||||
import logging
|
||||
|
||||
from seminar.views import formularOKView
|
||||
from various.autentizace.views import LoginView
|
||||
from various.autentizace.utils import posli_reset_hesla
|
||||
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
|
||||
class OrgoRozcestnikView(TemplateView):
|
||||
""" Zobrazí organizátorský rozcestník."""
|
||||
|
||||
template_name = 'personalni/profil/orgorozcestnik.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['posledni_soustredeni'] = s.Soustredeni.objects.order_by('-datum_konce').first()
|
||||
nastaveni = s.Nastaveni.objects.first()
|
||||
aktualni_rocnik = nastaveni.aktualni_rocnik
|
||||
context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
|
||||
# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
|
||||
# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít
|
||||
# přes treenody (a dát si přitom pozor na MezicisloNode)
|
||||
|
||||
neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True)
|
||||
reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True)
|
||||
context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count()
|
||||
context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count()
|
||||
|
||||
u = self.request.user
|
||||
os = s.Osoba.objects.get(user=u)
|
||||
organizator = s.Organizator.objects.get(osoba=os)
|
||||
|
||||
context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count()
|
||||
context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count()
|
||||
|
||||
#FIXME: přidat stav='STAV_ZADANY'
|
||||
temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
||||
rocnik=aktualni_rocnik).distinct()
|
||||
ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
||||
cislo_zadani__rocnik=aktualni_rocnik).distinct()
|
||||
clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
||||
cislo__rocnik=aktualni_rocnik).distinct()
|
||||
|
||||
context['temata'] = temata
|
||||
context['ulohy'] = ulohy
|
||||
context['clanky'] = clanky
|
||||
context['organizator'] = organizator
|
||||
return context
|
||||
|
||||
#content_type = 'text/plain; charset=UTF8'
|
||||
#XXX
|
||||
|
||||
|
||||
class ResitelView(LoginRequiredMixin,generic.DetailView):
|
||||
model = s.Resitel
|
||||
template_name = 'personalni/profil/resitel.html'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
print(self.request.user)
|
||||
return s.Resitel.objects.get(osoba__user=self.request.user)
|
||||
|
||||
### Formulare
|
||||
|
||||
# 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
|
||||
|
||||
def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
|
||||
msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items)))
|
||||
logger.warn(msg)
|
||||
gdpr_logger.warn(msg+", form:{}".format(form_data))
|
||||
|
||||
|
||||
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
|
||||
def resitelEditView(request):
|
||||
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
||||
## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
|
||||
u = request.user
|
||||
osoba_edit = s.Osoba.objects.get(user=u)
|
||||
if hasattr(osoba_edit,'resitel'):
|
||||
resitel_edit = osoba_edit.resitel
|
||||
else:
|
||||
resitel_edit = None
|
||||
user_edit = osoba_edit.user
|
||||
## Vytvoření slovníku, kterým předvyplním formulář
|
||||
prefill_1=model_to_dict(user_edit)
|
||||
if resitel_edit:
|
||||
prefill_2=model_to_dict(resitel_edit)
|
||||
prefill_1.update(prefill_2)
|
||||
prefill_3=model_to_dict(osoba_edit)
|
||||
prefill_1.update(prefill_3)
|
||||
if 'datum_narozeni' in prefill_1:
|
||||
prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni'])
|
||||
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
|
||||
form = PoMaturiteProfileEditForm(initial=prefill_1)
|
||||
else:
|
||||
form = ProfileEditForm(initial=prefill_1)
|
||||
## Změna údajů a jejich uložení
|
||||
if request.method == 'POST':
|
||||
POST = request.POST.copy()
|
||||
POST["username"] = osoba_edit.user.username
|
||||
|
||||
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
|
||||
form = PoMaturiteProfileEditForm(POST)
|
||||
else:
|
||||
form = ProfileEditForm(POST)
|
||||
form.username = user_edit.username
|
||||
if form.is_valid():
|
||||
## Změny v osobě
|
||||
fcd = form.cleaned_data
|
||||
form_hash = hash(frozenset(fcd.items()))
|
||||
form_logger = logging.getLogger('seminar.prihlaska.form')
|
||||
form_logger.info("EDIT:" + str(fcd) + str(form_hash)) # TODO možná logovat jinak
|
||||
osoba_edit.jmeno = fcd['jmeno']
|
||||
osoba_edit.prijmeni = fcd['prijmeni']
|
||||
osoba_edit.pohlavi_muz = fcd['pohlavi_muz']
|
||||
osoba_edit.email = fcd['email']
|
||||
osoba_edit.telefon = fcd['telefon']
|
||||
osoba_edit.ulice = fcd['ulice']
|
||||
osoba_edit.mesto = fcd['mesto']
|
||||
osoba_edit.psc = fcd['psc']
|
||||
osoba_edit.datum_narozeni = fcd['datum_narozeni']
|
||||
## Změny v osobě s podmínkami
|
||||
if fcd.get('spam',False):
|
||||
osoba_edit.datum_souhlasu_zasilani = date.today()
|
||||
if fcd.get('stat','') in ('CZ','SK'):
|
||||
osoba_edit.stat = fcd['stat']
|
||||
else:
|
||||
## Neznámá země
|
||||
msg = "Unknown country {}".format(fcd['stat_text'])
|
||||
|
||||
if resitel_edit:
|
||||
## Změny v řešiteli
|
||||
resitel_edit.skola = fcd['skola']
|
||||
resitel_edit.rok_maturity = fcd['rok_maturity']
|
||||
resitel_edit.zasilat = fcd['zasilat']
|
||||
resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
|
||||
if fcd.get('skola'):
|
||||
resitel_edit.skola = fcd['skola']
|
||||
else:
|
||||
# Unknown school - log it
|
||||
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
|
||||
resitel_edit.save()
|
||||
osoba_edit.save()
|
||||
return formularOKView(request, text=f'Údaje byly úspěšně uloženy. <a href="{reverse("profil")}">Vrátit se zpět na profil.</a>')
|
||||
|
||||
return render(request, 'personalni/udaje/edit.html', {'form': form})
|
||||
|
||||
|
||||
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
|
||||
def prihlaskaView(request):
|
||||
generic_logger = logging.getLogger('seminar.prihlaska')
|
||||
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
||||
form_logger = logging.getLogger('seminar.prihlaska.form')
|
||||
if request.method == 'POST':
|
||||
form = PrihlaskaForm(request.POST)
|
||||
# TODO vyresit, co se bude v jakych situacich zobrazovat
|
||||
if form.is_valid():
|
||||
generic_logger.info("Form valid")
|
||||
fcd = form.cleaned_data
|
||||
form_hash = hash(frozenset(fcd.items()))
|
||||
form_logger.info(str(fcd) + str(form_hash)) # TODO možná logovat jinak
|
||||
|
||||
with transaction.atomic():
|
||||
u = User.objects.create_user(
|
||||
username=fcd['username'],
|
||||
email = fcd['email'])
|
||||
u.save()
|
||||
resitel_perm = Permission.objects.filter(codename__exact='resitel').first()
|
||||
u.user_permissions.add(resitel_perm)
|
||||
resitel_grp = Group.objects.filter(name__exact='resitel').first()
|
||||
u.groups.add(resitel_grp)
|
||||
|
||||
o = s.Osoba(
|
||||
jmeno = fcd['jmeno'],
|
||||
prijmeni = fcd['prijmeni'],
|
||||
pohlavi_muz = fcd['pohlavi_muz'],
|
||||
email = fcd['email'],
|
||||
telefon = fcd.get('telefon',''),
|
||||
datum_narozeni = fcd.get('datum_narozeni',None),
|
||||
datum_souhlasu_udaje = date.today(),
|
||||
datum_registrace = date.today(),
|
||||
ulice = fcd.get('ulice',''),
|
||||
mesto = fcd.get('mesto',''),
|
||||
psc = fcd.get('psc',''),
|
||||
poznamka = str(fcd)
|
||||
)
|
||||
|
||||
if fcd.get('spam',False):
|
||||
o.datum_souhlasu_zasilani = date.today()
|
||||
if fcd.get('stat','') in ('CZ','SK'):
|
||||
o.stat = fcd['stat']
|
||||
else:
|
||||
# Unknown country - log it
|
||||
msg = "Unknown country {}".format(fcd['stat_text'])
|
||||
err_logger.warn(msg + str(form_hash))
|
||||
|
||||
|
||||
# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu
|
||||
try:
|
||||
orig_osoba = m.Osoba.objects.get(email=fcd['email'])
|
||||
orig_osoba.poznamka += '\nDOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže.'
|
||||
except m.Osoba.DoesNotExist:
|
||||
# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude.
|
||||
orig_osoba = o
|
||||
|
||||
# Porovnání údajů
|
||||
assert orig_osoba.user is None, "Právě-registrující-se osoba už má Uživatele!"
|
||||
osoba_attrs = ['jmeno', 'prijmeni', 'pohlavi_muz', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'stat', 'datum_souhlasu_udaje', 'datum_souhlasu_zasilani', 'datum_registrace']
|
||||
diffattrs = []
|
||||
for attr in osoba_attrs:
|
||||
new = getattr(o, attr)
|
||||
old = getattr(orig_osoba, attr)
|
||||
if new != old:
|
||||
orig_osoba.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
|
||||
diffattrs.append(f'Osoba.{attr}')
|
||||
setattr(orig_osoba, attr, new)
|
||||
# Datum registrace chceme původní / nižší:
|
||||
orig_osoba.datum_registrace = min(orig_osoba.datum_registrace, o.datum_registrace)
|
||||
|
||||
# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme
|
||||
o, o_form = orig_osoba, o
|
||||
|
||||
|
||||
|
||||
o.save()
|
||||
o.user = u
|
||||
o.save()
|
||||
|
||||
# Jednoduchá kvazi-kontrola duplicitních Osob
|
||||
kolize = m.Osoba.objects.filter(jmeno=o.jmeno, prijmeni=o.prijmeni)
|
||||
if kolize.count() > 1: # Jednu z nich jsme právě uložili
|
||||
err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')
|
||||
|
||||
r = s.Resitel(
|
||||
rok_maturity = fcd['rok_maturity'],
|
||||
zasilat = fcd['zasilat'],
|
||||
zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
|
||||
)
|
||||
|
||||
if fcd.get('skola'):
|
||||
r.skola = fcd['skola']
|
||||
else:
|
||||
# Unknown school - log it
|
||||
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
|
||||
err_logger.warn(msg + str(form_hash))
|
||||
|
||||
# Porovnání údajů u řešitele
|
||||
try:
|
||||
orig_resitel = o.resitel
|
||||
orig_resitel.poznamka += '\nDOREGISTRACE ŘEŠITELE, diff:'
|
||||
except m.Resitel.DoesNotExist:
|
||||
# Stejný trik:
|
||||
orig_resitel = r
|
||||
resitel_attrs = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem']
|
||||
for attr in resitel_attrs:
|
||||
new = getattr(r, attr)
|
||||
old = getattr(orig_resitel, attr)
|
||||
if new != old:
|
||||
orig_resitel.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
|
||||
diffattrs.append(f'Resitel.{attr}')
|
||||
setattr(orig_resitel, attr, new)
|
||||
r, r_form = orig_resitel, r
|
||||
|
||||
r.osoba = o # Tohle by mělo být bezpečné…
|
||||
r.save()
|
||||
|
||||
if diffattrs: err_logger.warning(f'Different fields when matching Řešitel id {r.id} or Osoba id {o.id}: {diffattrs}')
|
||||
|
||||
posli_reset_hesla(u, request)
|
||||
return formularOKView(request, text='Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla.')
|
||||
|
||||
# if a GET (or any other method) we'll create a blank form
|
||||
else:
|
||||
form = PrihlaskaForm()
|
||||
|
||||
return render(request, 'personalni/udaje/prihlaska.html', {'form': form})
|
||||
|
||||
|
||||
# Jen hloupé rozhazovátko
|
||||
def profilView(request):
|
||||
user = request.user
|
||||
if user.has_perm('auth.org'):
|
||||
return OrgoRozcestnikView.as_view()(request)
|
||||
if user.has_perm('auth.resitel'):
|
||||
return ResitelView.as_view()(request)
|
||||
else:
|
||||
return LoginView.as_view()(request)
|
186
seminar/admin.py
186
seminar/admin.py
|
@ -1,12 +1,9 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db import models
|
||||
from django.forms import widgets, ModelForm
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
|
||||
from reversion.admin import VersionAdmin
|
||||
from django_reverse_admin import ReverseModelAdmin
|
||||
from solo.admin import SingletonModelAdmin
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
@ -15,10 +12,7 @@ from seminar.utils import hlavni_problem
|
|||
# Todo: reversion
|
||||
|
||||
import seminar.models as m
|
||||
import seminar.treelib as tl
|
||||
|
||||
admin.site.register(m.Skola)
|
||||
admin.site.register(m.Prijemce)
|
||||
admin.site.register(m.Rocnik)
|
||||
|
||||
class CisloForm(ModelForm):
|
||||
|
@ -109,47 +103,6 @@ class CisloAdmin(admin.ModelAdmin):
|
|||
|
||||
force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými'
|
||||
|
||||
@admin.register(m.Osoba)
|
||||
class OsobaAdmin(admin.ModelAdmin):
|
||||
actions = ['synchronizuj_maily', 'udelej_orgem']
|
||||
|
||||
def synchronizuj_maily(self, request, queryset):
|
||||
for o in queryset:
|
||||
if o.user is not None:
|
||||
u = o.user
|
||||
u.email = o.email
|
||||
u.save()
|
||||
self.message_user(request, "E-maily synchronizovány.")
|
||||
synchronizuj_maily.short_description = "Synchronizuj vybraným osobám e-maily do uživatelů"
|
||||
|
||||
def udelej_orgem(self,request,queryset):
|
||||
org_group = Group.objects.get(name='org')
|
||||
print(queryset)
|
||||
for o in queryset:
|
||||
user = o.user
|
||||
print(user)
|
||||
user.groups.add(org_group)
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
org = m.Organizator.objects.create(osoba=o)
|
||||
org.save()
|
||||
udelej_orgem.short_description = "Udělej vybraných osob organizátory"
|
||||
|
||||
class OsobaInline(admin.TabularInline):
|
||||
model = m.Osoba
|
||||
|
||||
@admin.register(m.Organizator)
|
||||
class OrganizatorAdmin(ReverseModelAdmin):
|
||||
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
|
||||
inline_type = 'stacked'
|
||||
inline_reverse = ['osoba']
|
||||
|
||||
@admin.register(m.Resitel)
|
||||
class ResitelAdmin(ReverseModelAdmin):
|
||||
search_fields = ['osoba__jmeno', 'osoba__prijmeni', 'osoba__prezdivka']
|
||||
ordering = ('osoba__jmeno','osoba__prijmeni')
|
||||
inline_type = 'stacked'
|
||||
inline_reverse = ['osoba']
|
||||
|
||||
@admin.register(m.Problem)
|
||||
class ProblemAdmin(PolymorphicParentModelAdmin):
|
||||
|
@ -200,147 +153,8 @@ class ResitelInline(admin.TabularInline):
|
|||
model = m.Resitel
|
||||
extra = 1
|
||||
|
||||
class SoustredeniUcastniciInline(admin.TabularInline):
|
||||
model = m.Soustredeni_Ucastnici
|
||||
extra = 1
|
||||
fields = ['resitel','poznamka']
|
||||
autocomplete_fields = ['resitel']
|
||||
ordering = ['resitel__osoba__jmeno', 'resitel__osoba__prijmeni']
|
||||
formfield_overrides = {
|
||||
models.TextField: {'widget': widgets.TextInput}
|
||||
}
|
||||
|
||||
def get_queryset(self,request):
|
||||
qs = super().get_queryset(request)
|
||||
return qs.select_related('resitel','soustredeni')
|
||||
|
||||
class SoustredeniOrganizatoriInline(admin.TabularInline):
|
||||
model = m.Soustredeni.organizatori.through
|
||||
extra = 1
|
||||
fields = ['organizator','poznamka']
|
||||
autocomplete_fields = ['organizator']
|
||||
ordering = ['organizator__osoba__jmeno','organizator__prijmeni']
|
||||
formfield_overrides = {
|
||||
models.TextField: {'widget': widgets.TextInput}
|
||||
}
|
||||
|
||||
def get_queryset(self,request):
|
||||
qs = super().get_queryset(request)
|
||||
return qs.select_related('organizator', 'soustredeni')
|
||||
|
||||
|
||||
@admin.register(m.Soustredeni)
|
||||
class SoustredeniAdmin(admin.ModelAdmin):
|
||||
model = m.Soustredeni
|
||||
inline_type = 'tabular'
|
||||
inlines = [SoustredeniUcastniciInline, SoustredeniOrganizatoriInline]
|
||||
|
||||
|
||||
class PrilohaReseniInline(admin.TabularInline):
|
||||
model = m.PrilohaReseni
|
||||
extra = 1
|
||||
admin.site.register(m.PrilohaReseni)
|
||||
|
||||
class Reseni_ResiteleInline(admin.TabularInline):
|
||||
model = m.Reseni_Resitele
|
||||
|
||||
|
||||
@admin.register(m.Reseni)
|
||||
class ReseniAdmin(ReverseModelAdmin):
|
||||
base_model = m.Reseni
|
||||
inline_type = 'tabular'
|
||||
# inline_reverse = ['text_cely','resitele'] TODO vrátit zpět a zrychlit dotaz
|
||||
inline_reverse = ['resitele']
|
||||
exclude = ['text_zkraceny', 'text_zkraceny_set']
|
||||
inlines = [PrilohaReseniInline]
|
||||
# FAIL in template
|
||||
# inlines = [PrilohaReseniInline,Reseni_ResiteleInline]
|
||||
|
||||
admin.site.register(m.Hodnoceni)
|
||||
admin.site.register(m.Pohadka)
|
||||
admin.site.register(m.Obrazek)
|
||||
|
||||
# Polymorfismus pro stromy
|
||||
# TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html
|
||||
|
||||
@admin.register(m.TreeNode)
|
||||
class TreeNodeAdmin(PolymorphicParentModelAdmin):
|
||||
base_model = m.TreeNode
|
||||
child_models = [
|
||||
m.RocnikNode,
|
||||
m.CisloNode,
|
||||
m.MezicisloNode,
|
||||
m.TemaVCisleNode,
|
||||
m.UlohaZadaniNode,
|
||||
m.PohadkaNode,
|
||||
m.UlohaVzorakNode,
|
||||
m.TextNode,
|
||||
m.CastNode,
|
||||
m.OrgTextNode,
|
||||
]
|
||||
|
||||
actions = ['aktualizuj_nazvy']
|
||||
|
||||
# XXX: nejspíš je to totální DB HOG, nechcete to použít moc často.
|
||||
def aktualizuj_nazvy(self, request, queryset):
|
||||
newqs = queryset.get_real_instances()
|
||||
for tn in newqs:
|
||||
tn.aktualizuj_nazev()
|
||||
tn.save()
|
||||
self.message_user(request, "Názvy aktualizovány.")
|
||||
aktualizuj_nazvy.short_description = "Aktualizuj vybraným TreeNodům názvy"
|
||||
|
||||
@admin.register(m.RocnikNode)
|
||||
class RocnikNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.RocnikNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.CisloNode)
|
||||
class CisloNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.CisloNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.MezicisloNode)
|
||||
class MezicisloNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.MezicisloNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.TemaVCisleNode)
|
||||
class TemaVCisleNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.TemaVCisleNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.UlohaZadaniNode)
|
||||
class UlohaZadaniNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.UlohaZadaniNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.PohadkaNode)
|
||||
class PohadkaNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.PohadkaNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.UlohaVzorakNode)
|
||||
class UlohaVzorakNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.UlohaVzorakNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.TextNode)
|
||||
class TextNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.TextNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.CastNode)
|
||||
class TextNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.CastNode
|
||||
show_in_index = True
|
||||
fields = ('nadpis',)
|
||||
|
||||
@admin.register(m.OrgTextNode)
|
||||
class TextNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.OrgTextNode
|
||||
show_in_index = True
|
||||
|
||||
|
||||
admin.site.register(m.Nastaveni, SingletonModelAdmin)
|
||||
admin.site.register(m.Novinky)
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from autocomplete_light import shortcuts as autocomplete_light
|
||||
|
||||
from .models import Skola, Resitel, Problem, Organizator
|
||||
from taggit.models import Tag
|
||||
|
||||
|
||||
autocomplete_light.register(Tag)
|
||||
|
||||
|
||||
class SkolaAutocomplete(autocomplete_light.AutocompleteModelBase):
|
||||
|
||||
model = Skola
|
||||
|
||||
search_fields = ['nazev', 'mesto', 'ulice']
|
||||
|
||||
split_words = True
|
||||
|
||||
limit_choices = 15
|
||||
|
||||
attrs = {
|
||||
# This will set the input placeholder attribute:
|
||||
'placeholder': 'Škola',
|
||||
# This will set the yourlabs.Autocomplete.minimumCharacters
|
||||
# options, the naming conversion is handled by jQuery
|
||||
'data-autocomplete-minimum-characters': 1,
|
||||
}
|
||||
|
||||
widget_attrs = {
|
||||
'data-widget-maximum-values': 15,
|
||||
'class': 'modern-style',
|
||||
}
|
||||
|
||||
autocomplete_light.register(SkolaAutocomplete)
|
||||
|
||||
|
||||
class ResitelAutocomplete(autocomplete_light.AutocompleteModelBase):
|
||||
|
||||
model = Resitel
|
||||
|
||||
search_fields = ['jmeno', 'prijmeni']
|
||||
|
||||
split_words = False
|
||||
|
||||
limit_choices = 15
|
||||
|
||||
def choice_label(self, resitel):
|
||||
return "%s, %s (%s)" % (resitel.plne_jmeno(), resitel.mesto, resitel.rok_maturity)
|
||||
|
||||
attrs= {
|
||||
# This will set the input placeholder attribute:
|
||||
'placeholder': 'Řešitel',
|
||||
# This will set the yourlabs.Autocomplete.minimumCharacters
|
||||
# options, the naming conversion is handled by jQuery
|
||||
'data-autocomplete-minimum-characters': 1,
|
||||
}
|
||||
|
||||
widget_attrs = {
|
||||
'data-widget-maximum-values': 15,
|
||||
# Enable modern-style widget !
|
||||
'class': 'modern-style',
|
||||
}
|
||||
|
||||
autocomplete_light.register(ResitelAutocomplete)
|
||||
|
||||
class OrganizatorAutocomplete(autocomplete_light.AutocompleteModelBase):
|
||||
|
||||
model = Organizator
|
||||
|
||||
search_fields = ['user__first_name', 'user__last_name', 'prezdivka']
|
||||
|
||||
split_words = False
|
||||
|
||||
limit_choices = 15
|
||||
|
||||
def choice_label(self, organizator):
|
||||
return "%s '%s' %s" % (organizator.user.first_name,
|
||||
organizator.prezdivka,
|
||||
organizator.user.last_name)
|
||||
|
||||
attrs = {
|
||||
# This will set the input placeholder attribute:
|
||||
'placeholder': 'Organizátor',
|
||||
# This will set the yourlabs.Autocomplete.minimumCharacters
|
||||
# options, the naming conversion is handled by jQuery
|
||||
'data-autocomplete-minimum-characters': 1,
|
||||
}
|
||||
|
||||
widget_attrs = {
|
||||
'data-widget-maximum-values': 15,
|
||||
# Enable modern-style widget !
|
||||
'class': 'modern-style',
|
||||
}
|
||||
|
||||
autocomplete_light.register(OrganizatorAutocomplete)
|
||||
|
||||
|
||||
|
||||
class ProblemAutocomplete(autocomplete_light.AutocompleteModelBase):
|
||||
|
||||
model = Problem
|
||||
|
||||
search_fields = ['nazev']
|
||||
|
||||
split_words = False
|
||||
|
||||
limit_choices = 10
|
||||
|
||||
def choice_label(self, p):
|
||||
if p.stav == Problem.STAV_ZADANY:
|
||||
popisek = ""
|
||||
try:
|
||||
popisek = "%s (%s, %s.%s)".format(p.nazev, p.typ, p.cislo_zadani.rocnik.rocnik, p.kod_v_rocniku())
|
||||
except:
|
||||
#popisek = "%s (%s, %s.%s)".format(p.nazev, p.typ, p.stav)
|
||||
popisek = "CHYBA"
|
||||
return popisek
|
||||
else:
|
||||
return "%s (%s, %s)".format(p.nazev, p.typ, p.stav)
|
||||
|
||||
attrs = {
|
||||
# This will set the input placeholder attribute:
|
||||
'placeholder': 'Problém',
|
||||
# This will set the yourlabs.Autocomplete.minimumCharacters
|
||||
# options, the naming conversion is handled by jQuery
|
||||
'data-autocomplete-minimum-characters': 1,
|
||||
}
|
||||
|
||||
widget_attrs = {
|
||||
'data-widget-maximum-values': 10,
|
||||
# Enable modern-style widget !
|
||||
'class': 'modern-style',
|
||||
}
|
||||
|
||||
#FIXME Nefunguje, nevime proc
|
||||
#autocomplete_light.register(ProblemAutocomplete)
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ import taggit.managers
|
|||
|
||||
from datetime import date
|
||||
from django.db.models import Q
|
||||
from seminar.treelib import get_parent
|
||||
from treenode.treelib import get_parent
|
||||
import datetime as dt
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from seminar.treelib import get_parent
|
||||
from treenode.treelib import get_parent
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
1786
seminar/models.py
1786
seminar/models.py
File diff suppressed because it is too large
Load diff
8
seminar/models/__init__.py
Normal file
8
seminar/models/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from .tvorba import *
|
||||
from .odevzdavatko import *
|
||||
from .base import *
|
||||
from .personalni import *
|
||||
from .soustredeni import *
|
||||
from .pomocne import *
|
||||
from .treenode import *
|
||||
from .novinky import *
|
22
seminar/models/base.py
Normal file
22
seminar/models/base.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from django.urls import reverse
|
||||
from django.db import models
|
||||
|
||||
|
||||
class SeminarModelBase(models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def verejne(self):
|
||||
return False
|
||||
|
||||
# def get_absolute_url(self):
|
||||
# return "https://" + str(get_current_site(None)) + self.verejne_url()
|
||||
|
||||
def admin_url(self):
|
||||
model_name = self.__class__.__name__.lower()
|
||||
return reverse('admin:seminar_{}_change'.format(model_name), args=(self.id, ))
|
||||
|
||||
# def verejne_url(self):
|
||||
# return None
|
||||
|
38
seminar/models/novinky.py
Normal file
38
seminar/models/novinky.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.db import models
|
||||
from imagekit.models import ImageSpecField
|
||||
from imagekit.processors import ResizeToFit
|
||||
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from . import personalni as pm
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Novinky(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Novinka'
|
||||
verbose_name_plural = 'Novinky'
|
||||
ordering = ['-datum']
|
||||
|
||||
datum = models.DateField(auto_now_add=True)
|
||||
|
||||
text = models.TextField('Text novinky', blank=True, null=True)
|
||||
obrazek = models.ImageField('Obrázek', upload_to='image_novinky/%Y/%m/%d/',
|
||||
null=True, blank=True)
|
||||
|
||||
obrazek_maly = ImageSpecField(source='obrazek',
|
||||
processors=[
|
||||
ResizeToFit(350, 200, upscale=False)
|
||||
],
|
||||
options={'quality': 95})
|
||||
|
||||
autor = models.ForeignKey(pm.Organizator, verbose_name='Autor novinky', null=True,
|
||||
on_delete=models.SET_NULL)
|
||||
|
||||
zverejneno = models.BooleanField('Zveřejněno', default=False)
|
||||
|
||||
def __str__(self):
|
||||
if self.text:
|
||||
return '[' + str(self.datum) + '] ' + self.text[0:50]
|
||||
else:
|
||||
return '[' + str(self.datum) + '] '
|
191
seminar/models/odevzdavatko.py
Normal file
191
seminar/models/odevzdavatko.py
Normal file
|
@ -0,0 +1,191 @@
|
|||
import os
|
||||
|
||||
import reversion
|
||||
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from seminar.models import tvorba as am
|
||||
from seminar.models import personalni as pm
|
||||
from seminar.models import treenode as tm
|
||||
from seminar.models import base as bm
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Reseni(bm.SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_reseni'
|
||||
verbose_name = 'Řešení'
|
||||
verbose_name_plural = 'Řešení'
|
||||
#ordering = ['-problem', 'resitele'] # FIXME: Takhle to chceme, ale nefunguje to.
|
||||
ordering = ['-cas_doruceni']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
# 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',
|
||||
through='Hodnoceni')
|
||||
|
||||
resitele = models.ManyToManyField(pm.Resitel, verbose_name='autoři řešení',
|
||||
help_text='Seznam autorů řešení', through='Reseni_Resitele')
|
||||
|
||||
|
||||
cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True)
|
||||
|
||||
FORMA_PAPIR = 'papir'
|
||||
FORMA_EMAIL = 'email'
|
||||
FORMA_UPLOAD = 'upload'
|
||||
FORMA_CHOICES = [
|
||||
(FORMA_PAPIR, 'Papírové řešení'),
|
||||
(FORMA_EMAIL, 'Emailem'),
|
||||
(FORMA_UPLOAD, 'Upload přes web'),
|
||||
]
|
||||
forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
|
||||
default=FORMA_EMAIL)
|
||||
|
||||
text_cely = models.OneToOneField('ReseniNode', verbose_name='Plná verze textu řešení',
|
||||
blank=True, null=True, related_name="reseni_cely_set",
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k řešení (plain text)')
|
||||
|
||||
zverejneno = models.BooleanField('řešení zveřejněno', default=False,
|
||||
help_text='Udává, zda je řešení zveřejněno')
|
||||
|
||||
def verejne_url(self):
|
||||
return str(reverse_lazy('odevzdavatko_detail_reseni', args=[self.id]))
|
||||
|
||||
def absolute_url(self):
|
||||
return "https://" + str(get_current_site(None)) + self.verejne_url()
|
||||
|
||||
# má OneToOneField s:
|
||||
# Konfera
|
||||
|
||||
# má ForeignKey s:
|
||||
# Hodnoceni
|
||||
|
||||
def sum_body(self):
|
||||
return self.hodnoceni_set.all().aggregate(Sum('body'))["body__sum"]
|
||||
|
||||
def __str__(self):
|
||||
return "{}({}): {}({})".format(self.resitele.first(),len(self.resitele.all()), self.problem.first() ,len(self.problem.all()))
|
||||
# NOTE: Potenciální DB HOG (bez select_related)
|
||||
|
||||
## Pravdepodobne uz nebude potreba:
|
||||
# def save(self, *args, **kwargs):
|
||||
# if ((self.cislo_body is None) and (self.problem.cislo_reseni) and
|
||||
# (self.problem.typ == Problem.TYP_ULOHA)):
|
||||
# self.cislo_body = self.problem.cislo_reseni
|
||||
# super(Reseni, self).save(*args, **kwargs)
|
||||
|
||||
class Hodnoceni(bm.SeminarModelBase):
|
||||
class Meta:
|
||||
db_table = 'seminar_hodnoceni'
|
||||
verbose_name = 'Hodnocení'
|
||||
verbose_name_plural = 'Hodnocení'
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
|
||||
body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='body',
|
||||
blank=True, null=True)
|
||||
|
||||
cislo_body = models.ForeignKey(am.Cislo, verbose_name='číslo pro body',
|
||||
related_name='hodnoceni', blank=True, null=True, on_delete=models.PROTECT)
|
||||
|
||||
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
|
||||
|
||||
problem = models.ForeignKey(am.Problem, verbose_name='problém',
|
||||
related_name='hodnoceni', on_delete=models.PROTECT)
|
||||
|
||||
def __str__(self):
|
||||
return "{}, {}, {}".format(self.problem, self.reseni, self.body)
|
||||
|
||||
def generate_filename(self, filename):
|
||||
return os.path.join(
|
||||
settings.SEMINAR_RESENI_DIR,
|
||||
am.aux_generate_filename(self, filename)
|
||||
)
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class PrilohaReseni(bm.SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_priloha_reseni'
|
||||
verbose_name = 'Příloha řešení'
|
||||
verbose_name_plural = 'Přílohy řešení'
|
||||
ordering = ['reseni', 'vytvoreno']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
reseni = models.ForeignKey(Reseni, verbose_name='řešení', related_name='prilohy',
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False)
|
||||
|
||||
soubor = models.FileField('soubor', upload_to = generate_filename)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k příloze řešení (plain text), např. o původu')
|
||||
|
||||
res_poznamka = models.TextField('poznámka řešitele', blank=True,
|
||||
help_text='Poznámka k příloze řešení, např. co daný soubor obsahuje')
|
||||
|
||||
def __str__(self):
|
||||
return str(self.soubor)
|
||||
|
||||
def split(self):
|
||||
"Vrátí cestu rozsekanou po složkách. To se hodí v templatech"
|
||||
# Věřím, že tohle funguje, případně použít os.path nebo pathlib.
|
||||
return self.soubor.url.split('/')
|
||||
|
||||
|
||||
# Vazebna tabulka. Mozna se generuje automaticky.
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Reseni_Resitele(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_reseni_resitele'
|
||||
verbose_name = 'Řešení řešitelů'
|
||||
verbose_name_plural = 'Řešení řešitelů'
|
||||
ordering = ['reseni', 'resitele']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
resitele = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT)
|
||||
|
||||
reseni = models.ForeignKey(Reseni, verbose_name='řešení', on_delete=models.CASCADE)
|
||||
|
||||
# podil - jakou merou se ktery resitel podilel na danem reseni
|
||||
# - pouziti v budoucnu, pokud by resitele nemeli dostat vsichni stejne bodu za spolecne reseni
|
||||
|
||||
def __str__(self):
|
||||
return '{} od {}'.format(self.reseni, self.resitel)
|
||||
# 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)
|
||||
|
438
seminar/models/personalni.py
Normal file
438
seminar/models/personalni.py
Normal file
|
@ -0,0 +1,438 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from imagekit.models import ImageSpecField, ProcessedImageField
|
||||
from imagekit.processors import ResizeToFit, Transpose
|
||||
|
||||
from django_countries.fields import CountryField
|
||||
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from .base import SeminarModelBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Osoba(SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_osoby'
|
||||
verbose_name = 'Osoba'
|
||||
verbose_name_plural = 'Osoby'
|
||||
ordering = ['prijmeni','jmeno']
|
||||
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
jmeno = models.CharField('jméno', max_length=256)
|
||||
|
||||
prijmeni = models.CharField('příjmení', max_length=256)
|
||||
|
||||
prezdivka = models.CharField('přezdívka', blank=True, null=True, max_length=256)
|
||||
|
||||
# User, pokud má na webu účet
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, blank=True, null=True,
|
||||
verbose_name='uživatel', on_delete=models.DO_NOTHING)
|
||||
|
||||
# Pohlaví. Že ho neznáme se snad nestane (a ušetří to práci při programování)
|
||||
pohlavi_muz = models.BooleanField('pohlaví (muž)', default=False)
|
||||
|
||||
email = models.EmailField('e-mail', max_length=256, blank=True, default='')
|
||||
|
||||
telefon = models.CharField('telefon', max_length=256, blank=True, default='')
|
||||
|
||||
datum_narozeni = models.DateField('datum narození', blank=True, null=True)
|
||||
|
||||
# NULL dokud nedali souhlas
|
||||
datum_souhlasu_udaje = models.DateField('datum souhlasu (údaje)', blank=True, null=True,
|
||||
help_text='Datum souhlasu se zpracováním osobních údajů')
|
||||
|
||||
# NULL dokud nedali souhlas
|
||||
datum_souhlasu_zasilani = models.DateField('datum souhlasu (spam)', blank=True, null=True,
|
||||
help_text='Datum souhlasu se zasíláním MFF materiálů')
|
||||
|
||||
# Alespoň odhad (rok či i měsíc)
|
||||
datum_registrace = models.DateField('datum registrace do semináře', default=timezone.now)
|
||||
|
||||
# Ulice může být i jen číslo
|
||||
ulice = models.CharField('ulice', max_length=256, blank=True, default='')
|
||||
|
||||
mesto = models.CharField('město', max_length=256, blank=True, default='')
|
||||
|
||||
psc = models.CharField('PSČ', max_length=32, blank=True, default='')
|
||||
|
||||
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
|
||||
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
|
||||
stat = CountryField('stát', default='CZ',
|
||||
help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k osobě (plain text)')
|
||||
|
||||
foto = ProcessedImageField(verbose_name='Fotografie osoby',
|
||||
upload_to='image_osoby/velke/%Y/', null = True, blank = True,
|
||||
help_text = 'Vlož fotografii osoby o libovolné velikosti',
|
||||
processors=[
|
||||
Transpose(Transpose.AUTO),
|
||||
ResizeToFit(500, 500, upscale=False)
|
||||
],
|
||||
options={'quality': 95})
|
||||
foto_male = ImageSpecField(source='foto',
|
||||
processors=[
|
||||
ResizeToFit(200, 200, upscale=False)
|
||||
],
|
||||
options={'quality': 95})
|
||||
|
||||
# má OneToOneField nejvýše s:
|
||||
# Resitel
|
||||
# Prijemce
|
||||
# Organizator
|
||||
|
||||
def plne_jmeno(self):
|
||||
return '{} {}'.format(self.jmeno, self.prijmeni)
|
||||
|
||||
def inicial_krestni(self):
|
||||
jmena = self.jmeno.split()
|
||||
return " ".join(['{}.'.format(jmeno[0]) for jmeno in jmena])
|
||||
|
||||
def __str__(self):
|
||||
return self.plne_jmeno()
|
||||
|
||||
# Overridujeme save Osoby, aby když si změní e-mail, aby se projevil i v
|
||||
# Userovi (a tak se dal poslat mail s resetem hesla)
|
||||
def save(self, *args, **kwargs):
|
||||
if self.user is not None:
|
||||
u = self.user
|
||||
# U svatého tučňáka, prosím ať tohle funguje.
|
||||
# (Takhle se kódit asi nemá...)
|
||||
u.email = self.email
|
||||
u.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)
|
||||
class Skola(SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_skoly'
|
||||
verbose_name = 'Škola'
|
||||
verbose_name_plural = 'Školy'
|
||||
ordering = ['mesto', 'nazev']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
# Aesopi ID "izo:..." nebo "aesop:..."
|
||||
# NULL znamená v exportu do aesopa "ufo"
|
||||
aesop_id = models.CharField('Aesop ID', max_length=32, blank=True, default='',
|
||||
help_text='Aesopi ID typu "izo:..." nebo "aesop:..."')
|
||||
|
||||
# IZO školy (jen české školy)
|
||||
izo = models.CharField('IZO', max_length=32, blank=True,
|
||||
help_text='IZO školy (jen české školy)')
|
||||
|
||||
# Celý název školy
|
||||
nazev = models.CharField('název', max_length=256,
|
||||
help_text='Celý název školy')
|
||||
|
||||
# Zkraceny nazev pro zobrazení ve výsledkovce, volitelné.
|
||||
# Není v Aesopovi, musíme vytvářet sami.
|
||||
kratky_nazev = models.CharField('zkrácený název', max_length=256, blank=True,
|
||||
help_text="Zkrácený název pro zobrazení ve výsledkovce")
|
||||
|
||||
# Ulice může být jen číslo
|
||||
ulice = models.CharField('ulice', max_length=256)
|
||||
|
||||
mesto = models.CharField('město', max_length=256)
|
||||
|
||||
psc = models.CharField('PSČ', max_length=32)
|
||||
|
||||
# ISO 3166-1 dvojznakovy kod zeme velkym pismem (CZ, SK)
|
||||
# Ekvivalentní s CharField(max_length=2, default='CZ', ...)
|
||||
stat = CountryField('stát', default='CZ',
|
||||
help_text='ISO 3166-1 kód země velkými písmeny (CZ, SK, ...)')
|
||||
|
||||
# Jaké vzdělání škpla poskytuje?
|
||||
je_zs = models.BooleanField('základní stupeň', default=True)
|
||||
je_ss = models.BooleanField('střední stupeň', default=True)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka ke škole (plain text)')
|
||||
|
||||
kontaktni_osoba = models.ForeignKey(Osoba, verbose_name='Kontaktní osoba',
|
||||
blank=True, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
def __str__(self):
|
||||
return '{}, {}, {}'.format(self.nazev, self.ulice, self.mesto)
|
||||
|
||||
class Prijemce(SeminarModelBase):
|
||||
class Meta:
|
||||
db_table = 'seminar_prijemce'
|
||||
verbose_name = 'příjemce'
|
||||
verbose_name_plural = 'příjemce'
|
||||
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k příemci čísel (plain text)')
|
||||
|
||||
osoba = models.OneToOneField(Osoba, verbose_name='komu', blank=False, null=False,
|
||||
help_text='Které osobě či na jakou adresu se mají zasílat čísla',
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
# FIXME: možná chceme něco jako vazbu na osobu XOR školu a počet kusů k zaslání
|
||||
# FIXME: a možná taky posílání na mail a možná taky přes něj chceme posílat i řešitelům
|
||||
|
||||
def __str__(self):
|
||||
return self.osoba.plne_jmeno()
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Resitel(SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_resitele'
|
||||
verbose_name = 'Řešitel'
|
||||
verbose_name_plural = 'Řešitelé'
|
||||
ordering = ['osoba']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
osoba = models.OneToOneField(Osoba, blank=False, null=False, verbose_name='osoba',
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
|
||||
skola = models.ForeignKey(Skola, blank=True, null=True, verbose_name='škola',
|
||||
on_delete=models.SET_NULL)
|
||||
|
||||
# Očekávaný rok maturity a vyřazení z aktivních řešitelů
|
||||
rok_maturity = models.IntegerField('rok maturity', blank=True, null=True)
|
||||
|
||||
ZASILAT_DOMU = 'domu'
|
||||
ZASILAT_DO_SKOLY = 'do_skoly'
|
||||
ZASILAT_NIKAM = 'nikam'
|
||||
ZASILAT_CHOICES = [
|
||||
(ZASILAT_DOMU, 'Domů'),
|
||||
(ZASILAT_DO_SKOLY, 'Do školy'),
|
||||
(ZASILAT_NIKAM, 'Nikam'),
|
||||
]
|
||||
|
||||
zasilat = models.CharField('kam zasílat', max_length=32, choices=ZASILAT_CHOICES, blank=False, default=ZASILAT_DOMU)
|
||||
|
||||
zasilat_cislo_emailem = models.BooleanField('zasílat číslo emailem', help_text='True pokud chce řešitel dostávat číslo emailem', default=False)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
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):
|
||||
"""Vrati skolni rocnik resitele pro zadany Rocnik.
|
||||
Vraci '' pro neznamy rok maturity resitele, Z* pro ekvivalent ZŠ."""
|
||||
if self.rok_maturity is None:
|
||||
return ''
|
||||
rozdil = 5 - (self.rok_maturity - rocnik.prvni_rok)
|
||||
if rozdil >= 1:
|
||||
return str(rozdil)
|
||||
else:
|
||||
return 'Z' + str(rozdil + 9)
|
||||
|
||||
def vsechny_body(self):
|
||||
"Spočítá body odjakživa."
|
||||
vsechna_reseni = self.reseni_set.all()
|
||||
from .odevzdavatko import Hodnoceni
|
||||
vsechna_hodnoceni = Hodnoceni.objects.filter(
|
||||
reseni__in=vsechna_reseni)
|
||||
return sum(h.body for h in list(vsechna_hodnoceni) if h.body is not None)
|
||||
|
||||
|
||||
def get_titul(self, body=None):
|
||||
"Vrati titul jako řetězec."
|
||||
|
||||
# Nejprve si zadefinujeme titul
|
||||
from enum import Enum
|
||||
from functools import total_ordering
|
||||
@total_ordering
|
||||
class Titul(Enum):
|
||||
""" Třída reprezentující možné tituly. Hodnoty jsou dvojice (dolní hranice, stringifikace). """
|
||||
nic = (0, '')
|
||||
bc = (20, 'Bc.')
|
||||
mgr = (50, 'Mgr.')
|
||||
dr = (100, 'Dr.')
|
||||
doc = (200, 'Doc.')
|
||||
prof = (500, 'Prof.')
|
||||
akad = (1000, 'Akad.')
|
||||
|
||||
def __lt__(self, other):
|
||||
return True if self.value[0] < other.value[0] else False
|
||||
def __eq__(self, other): # Měla by být implicitní, ale klidně explicitně.
|
||||
return True if self.value[0] == other.value[0] else False
|
||||
|
||||
def __str__(self):
|
||||
return self.value[1]
|
||||
|
||||
@classmethod
|
||||
def z_bodu(cls, body):
|
||||
aktualni = cls.nic
|
||||
# TODO: ověřit, že to funguje
|
||||
for titul in cls: # Kdyžtak použít __members__.items()
|
||||
if titul.value[0] <= body:
|
||||
aktualni = titul
|
||||
else:
|
||||
break
|
||||
return aktualni
|
||||
|
||||
# Hledáme body v databázi
|
||||
# V listopadu 2020 jsme se na filosofické schůzce shodli o změně hranic titulů:
|
||||
# - 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ů
|
||||
# - 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
|
||||
hodnoceni_do_25_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=25,reseni__in=self.reseni_set.all())
|
||||
novejsi_hodnoceni = Hodnoceni.objects.filter(reseni__in=self.reseni_set.all()).difference(hodnoceni_do_25_rocniku)
|
||||
|
||||
def body_z_hodnoceni(hh : list):
|
||||
return sum(h.body for h in hh if h.body is not None)
|
||||
|
||||
stare_body = body_z_hodnoceni(hodnoceni_do_25_rocniku)
|
||||
if body is None:
|
||||
nove_body = body_z_hodnoceni(novejsi_hodnoceni)
|
||||
else:
|
||||
# Zjistíme, kolik bodů jsou staré, tedy hodnotnější
|
||||
nove_body = max(0, body - stare_body) # Všechny body nad počet původních hodnotnějších
|
||||
stare_body = min(stare_body, body) # Skutečný počet hodnotnějších bodů
|
||||
logicke_body = 2*stare_body + nove_body
|
||||
|
||||
|
||||
# Titul se určí následovně:
|
||||
# - Pokud se řeší body, které jsou starší, než do 26 ročníku (včetně), dáváme tituly postaru.
|
||||
# - Jinak dáváme tituly po novu...
|
||||
# - ... ale titul se nesmí odebrat, pokud se zmenšil.
|
||||
def titul_do_26_rocniku(body):
|
||||
""" Původní hranice bodů za tituly """
|
||||
if body < 10:
|
||||
return Titul.nic
|
||||
elif body < 20:
|
||||
return Titul.bc
|
||||
elif body < 50:
|
||||
return Titul.mgr
|
||||
elif body < 100:
|
||||
return Titul.dr
|
||||
elif body < 200:
|
||||
return Titul.doc
|
||||
elif body < 500:
|
||||
return Titul.prof
|
||||
else:
|
||||
return Titul.akad
|
||||
|
||||
from .odevzdavatko import Hodnoceni
|
||||
hodnoceni_do_26_rocniku = Hodnoceni.objects.filter(cislo_body__rocnik__rocnik__lte=26,reseni__in=self.reseni_set.all())
|
||||
novejsi_body = body_z_hodnoceni(
|
||||
Hodnoceni.objects.filter(reseni__in=self.reseni_set.all())
|
||||
.difference(hodnoceni_do_26_rocniku)
|
||||
)
|
||||
starsi_body = body_z_hodnoceni(hodnoceni_do_26_rocniku)
|
||||
if body is not None:
|
||||
# Ještě z toho vybereme ty správně staré body
|
||||
novejsi_body = max(0, body - starsi_body)
|
||||
starsi_body = min(starsi_body, body)
|
||||
|
||||
# Titul pro 26. ročník
|
||||
stary_titul = titul_do_26_rocniku(starsi_body)
|
||||
# Titul podle aktuálních pravidel
|
||||
novy_titul = Titul.z_bodu(logicke_body)
|
||||
|
||||
if novejsi_body == 0:
|
||||
# Žádné nové body -- titul podle starých pravidel
|
||||
return str(stary_titul)
|
||||
return str(max(novy_titul, stary_titul))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.osoba.plne_jmeno()
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Organizator(SeminarModelBase):
|
||||
osoba = models.OneToOneField(Osoba, verbose_name='osoba', related_name='org',
|
||||
help_text='osobní údaje organizátora', null=False, blank=False,
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
vytvoreno = models.DateTimeField(
|
||||
'Vytvořeno',
|
||||
default=timezone.now,
|
||||
blank=True,
|
||||
editable=False
|
||||
)
|
||||
|
||||
organizuje_od = models.DateTimeField('Organizuje od', blank=True, null=True)
|
||||
|
||||
organizuje_do = models.DateTimeField('Organizuje do', blank=True, null=True)
|
||||
|
||||
studuje = models.CharField('Studium aj.', max_length = 256,
|
||||
null = True, 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'")
|
||||
|
||||
strucny_popis_organizatora = models.TextField('Stručný popis organizátora',
|
||||
null = True, blank = True)
|
||||
|
||||
skola = models.CharField('Škola, kterou studuje', max_length = 256, null=True, 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ášť")
|
||||
|
||||
def clean(self):
|
||||
if self.organizuje_od and self.organizuje_do and (self.organizuje_od > self.organizuje_do):
|
||||
raise ValidationError("Organizátor nemůže skončit s organizováním dříve než začal!")
|
||||
super().clean()
|
||||
|
||||
def __str__(self):
|
||||
if self.osoba.prezdivka:
|
||||
return "{} '{}' {}".format(self.osoba.jmeno,
|
||||
self.osoba.prezdivka,
|
||||
self.osoba.prijmeni)
|
||||
else:
|
||||
return "{} {}".format(self.osoba.jmeno, self.osoba.prijmeni)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Organizátor'
|
||||
verbose_name_plural = 'Organizátoři'
|
||||
# Řadí aktivní orgy na začátek, pod tím v pořadí od nejstarších neaktivní orgy.
|
||||
# TODO: Chtěl bych spíš mít nejstarší orgy dole.
|
||||
# TODO: Zohledňovat přezdívky?
|
||||
# TODO: Sjednotit s tím, jak se řadí organizátoři v seznau orgů na webu
|
||||
ordering = ['-organizuje_do', 'osoba__jmeno', 'osoba__prijmeni']
|
67
seminar/models/pomocne.py
Normal file
67
seminar/models/pomocne.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from django.db import models
|
||||
|
||||
from .base import SeminarModelBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Text(SeminarModelBase):
|
||||
class Meta:
|
||||
db_table = 'seminar_texty'
|
||||
verbose_name = 'text'
|
||||
verbose_name_plural = 'texty'
|
||||
|
||||
na_web = models.TextField(
|
||||
'text na web', blank=True,
|
||||
help_text='Text ke zveřejnění na webu')
|
||||
|
||||
do_cisla = models.TextField(
|
||||
'text do čísla', blank=True,
|
||||
help_text='Text ke zveřejnění v čísle')
|
||||
|
||||
# má OneToOneField s:
|
||||
# Reseni (je u něj jako reseni_cele)
|
||||
|
||||
# obrázky mají návaznost opačným směrem (vazba z druhé strany)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
# *Node.save() aktualizuje název *Nodu.
|
||||
for tn in self.textnode_set.all():
|
||||
tn.save()
|
||||
|
||||
def __str__(self):
|
||||
return str(self.na_web)[:20]
|
||||
|
||||
|
||||
class Obrazek(SeminarModelBase):
|
||||
class Meta:
|
||||
db_table = 'seminar_obrazky'
|
||||
verbose_name = 'obrázek'
|
||||
verbose_name_plural = 'obrázky'
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key=True)
|
||||
|
||||
na_web = models.ImageField(
|
||||
'obrázek na web', upload_to='obrazky/%Y/%m/%d/',
|
||||
null=True, blank=True)
|
||||
|
||||
text = models.ForeignKey(
|
||||
Text, verbose_name='text',
|
||||
help_text='text, ve kterém se obrázek vyskytuje',
|
||||
null=False, blank=False, on_delete=models.CASCADE)
|
||||
|
||||
do_cisla_barevny = models.FileField(
|
||||
'barevný obrázek do čísla',
|
||||
help_text='Barevná verze obrázku do čísla',
|
||||
upload_to='obrazky/%Y/%m/%d/', blank=True, null=True)
|
||||
|
||||
do_cisla_cernobily = models.FileField(
|
||||
'černobílý obrázek do čísla',
|
||||
help_text='Černobílá verze obrázku do čísla',
|
||||
upload_to='obrazky/%Y/%m/%d/', blank=True, null=True)
|
||||
|
||||
# TODO placement hint - chci ho tady / pred textem / za textem
|
214
seminar/models/soustredeni.py
Normal file
214
seminar/models/soustredeni.py
Normal file
|
@ -0,0 +1,214 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from . import personalni as pm
|
||||
|
||||
from .base import SeminarModelBase
|
||||
from seminar.models import tvorba as am
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Soustredeni(SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_soustredeni'
|
||||
verbose_name = 'Soustředění'
|
||||
verbose_name_plural = 'Soustředění'
|
||||
ordering = ['-rocnik__rocnik', '-datum_zacatku']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
rocnik = models.ForeignKey(am.Rocnik, verbose_name='ročník', related_name='soustredeni',
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
datum_zacatku = models.DateField('datum začátku', blank=True, null=True,
|
||||
help_text='První den soustředění')
|
||||
|
||||
datum_konce = models.DateField('datum konce', blank=True, null=True,
|
||||
help_text='Poslední den soustředění')
|
||||
|
||||
verejne_db = models.BooleanField('soustředění zveřejněno', db_column='verejne', default=False)
|
||||
|
||||
misto = models.CharField('místo soustředění', max_length=256, blank=True, default='',
|
||||
help_text='Místo (název obce, volitelně též objektu')
|
||||
|
||||
ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci soustředění',
|
||||
help_text='Seznam účastníků soustředění', through='Soustredeni_Ucastnici')
|
||||
|
||||
organizatori = models.ManyToManyField(pm.Organizator,
|
||||
verbose_name='Organizátoři soustředění',
|
||||
help_text='Seznam organizátorů soustředění',
|
||||
through='Soustredeni_Organizatori')
|
||||
|
||||
text = models.TextField('text k soustředění (HTML)', blank=True, default='')
|
||||
|
||||
TYP_JARNI = 'jarni'
|
||||
TYP_PODZIMNI = 'podzimni'
|
||||
TYP_VIKEND = 'vikend'
|
||||
TYP_CHOICES = [
|
||||
(TYP_JARNI, 'Jarní soustředění'),
|
||||
(TYP_PODZIMNI, 'Podzimní soustředění'),
|
||||
(TYP_VIKEND, 'Víkendový sraz'),
|
||||
]
|
||||
typ = models.CharField('typ akce', max_length=16, choices=TYP_CHOICES, blank=False, default=TYP_PODZIMNI)
|
||||
|
||||
exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False,
|
||||
help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti)')
|
||||
|
||||
def __str__(self):
|
||||
return '{} ({})'.format(self.misto, self.datum_zacatku)
|
||||
|
||||
def verejne(self):
|
||||
return self.verejne_db
|
||||
verejne.boolean = True
|
||||
|
||||
def verejne_url(self):
|
||||
#return reverse('seminar_soustredeni', kwargs={'pk': self.id})
|
||||
return reverse('seminar_seznam_soustredeni')
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Soustredeni_Ucastnici(SeminarModelBase):
|
||||
# zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_soustredeni_ucastnici'
|
||||
verbose_name = 'Účast na soustředění'
|
||||
verbose_name_plural = 'Účasti na soustředění'
|
||||
ordering = ['soustredeni', 'resitel']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT)
|
||||
|
||||
soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k účasti (plain text)')
|
||||
|
||||
def __str__(self):
|
||||
return '{} na {}'.format(self.resitel, self.soustredeni)
|
||||
# NOTE: Poteciální DB HOG bez select_related
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Soustredeni_Organizatori(SeminarModelBase):
|
||||
# zmena dedicnosti z models.Model na SeminarModelBase, potencialni vznik bugu
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_soustredeni_organizatori'
|
||||
verbose_name = 'Účast organizátorů na soustředění'
|
||||
verbose_name_plural = 'Účasti organizátorů na soustředění'
|
||||
ordering = ['soustredeni', 'organizator']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
organizator = models.ForeignKey(pm.Organizator, verbose_name='organizátor',
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k účasti organizátora (plain text)')
|
||||
|
||||
def __str__(self):
|
||||
return '{} na {}'.format(self.organizator, self.soustredeni)
|
||||
# NOTE: Poteciální DB HOG bez select_related
|
||||
|
||||
|
||||
# FIXME cycle import
|
||||
|
||||
|
||||
# Django neumí jednoduše serializovat partial nebo třídu s __call__
|
||||
# (https://docs.djangoproject.com/en/1.8/topics/migrations/),
|
||||
# neprojdou pak migrace. Takže rozlišení funkcí generujících názvy souboru
|
||||
# podle adresáře řešíme takto.
|
||||
|
||||
##
|
||||
def generate_filename_konfera(self, filename):
|
||||
return os.path.join(
|
||||
settings.SEMINAR_KONFERY_DIR,
|
||||
am.aux_generate_filename(self, filename)
|
||||
)
|
||||
|
||||
##
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Konfera(am.Problem):
|
||||
class Meta:
|
||||
db_table = 'seminar_konfera'
|
||||
verbose_name = 'Konfera'
|
||||
verbose_name_plural = 'Konfery'
|
||||
|
||||
anotace = models.TextField('anotace', blank=True,
|
||||
help_text='Popis, o čem bude konfera.')
|
||||
|
||||
abstrakt = models.TextField('abstrakt', blank=True,
|
||||
help_text='Abstrakt konfery tak, jak byl uveden ve sborníku')
|
||||
|
||||
# FIXME: Umíme omezit jen na účastníky daného soustřeďka?
|
||||
ucastnici = models.ManyToManyField(pm.Resitel, verbose_name='účastníci konfery',
|
||||
help_text='Seznam účastníků konfery', through='Konfery_Ucastnici')
|
||||
|
||||
soustredeni = models.ForeignKey(Soustredeni, verbose_name='soustředění',
|
||||
related_name='konfery', on_delete = models.SET_NULL, null=True)
|
||||
|
||||
TYP_VELETRH = 'veletrh'
|
||||
TYP_PREZENTACE = 'prezentace'
|
||||
TYP_CHOICES = [
|
||||
(TYP_VELETRH, 'Veletrh (postery)'),
|
||||
(TYP_PREZENTACE, 'Prezentace (přednáška)'),
|
||||
]
|
||||
typ_prezentace = models.CharField('typ prezentace', max_length=16, choices=TYP_CHOICES,
|
||||
blank=False, default=TYP_VELETRH)
|
||||
|
||||
prezentace = models.FileField('prezentace',help_text = 'Prezentace nebo fotka posteru',
|
||||
upload_to = generate_filename_konfera, blank=True)
|
||||
|
||||
materialy = models.FileField('materialy',
|
||||
help_text = 'Další materiály ke konfeře zabalené do jednoho souboru',
|
||||
upload_to = generate_filename_konfera, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{}: ({})".format(self.nazev, self.soustredeni)
|
||||
|
||||
def cislo_node(self):
|
||||
return None
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Konfery_Ucastnici(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_konfery_ucastnici'
|
||||
verbose_name = 'Účast na konfeře'
|
||||
verbose_name_plural = 'Účasti na konfeře'
|
||||
ordering = ['konfera', 'resitel']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
resitel = models.ForeignKey(pm.Resitel, verbose_name='řešitel', on_delete=models.PROTECT)
|
||||
|
||||
konfera = models.ForeignKey(Konfera, verbose_name='konfera', on_delete=models.CASCADE)
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k účasti (plain text)')
|
||||
|
||||
def __str__(self):
|
||||
return '{} na {}'.format(self.resitel, self.konfera)
|
||||
# NOTE: Poteciální DB HOG bez select_related
|
266
seminar/models/treenode.py
Normal file
266
seminar/models/treenode.py
Normal file
|
@ -0,0 +1,266 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
|
||||
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
||||
from . import personalni as pm
|
||||
|
||||
from .pomocne import Text
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from seminar.models import tvorba as am
|
||||
|
||||
class TreeNode(PolymorphicModel):
|
||||
class Meta:
|
||||
db_table = "seminar_nodes_treenode"
|
||||
verbose_name = "TreeNode"
|
||||
verbose_name_plural = "TreeNody"
|
||||
|
||||
# TODO: Nechceme radši jako root vyžadovat přímo RocnikNode?
|
||||
root = models.ForeignKey('TreeNode',
|
||||
related_name="potomci_set",
|
||||
null = True,
|
||||
blank = False,
|
||||
on_delete = models.SET_NULL, # Vrcholy s null kořenem jsou sirotci bez ročníku
|
||||
verbose_name="kořen stromu")
|
||||
first_child = models.OneToOneField('TreeNode',
|
||||
related_name='father_of_first',
|
||||
null = True,
|
||||
blank = True,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name="první potomek")
|
||||
succ = models.OneToOneField('TreeNode',
|
||||
related_name="prev",
|
||||
null = True,
|
||||
blank = True,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name="další element na stejné úrovni")
|
||||
nazev = models.TextField("název tohoto node",
|
||||
help_text = "Tento název se zobrazuje v nabídkách pro výběr vhodného TreeNode",
|
||||
blank=False,
|
||||
null=True) # Nezveřejnitelný název na stránky - pouze do adminu
|
||||
zajimave = models.BooleanField(default = False,
|
||||
verbose_name = "Zajímavé",
|
||||
help_text = "Zobrazí se daná věc na rozcestníku témátek")
|
||||
srolovatelne = models.BooleanField(null = True, blank = True,
|
||||
verbose_name = "Srolovatelné",
|
||||
help_text = "Bude na stránce témátka možnost tuto položku skrýt")
|
||||
|
||||
def getOdkazStr(self): # String na rozcestník
|
||||
return self.first_child.getOdkazStr()
|
||||
|
||||
def getOdkaz(self): # ID HTML tagu, na který se bude scrollovat #{{self.getOdkaz}}
|
||||
# Jsem si vědom, že tu potenciálně vznikají kolize.
|
||||
# Přijdou mi natolik nepravděpodobné, že je neřeším
|
||||
# Chtěl jsem ale hezké odkazy
|
||||
string = unidecode(self.getOdkazStr())
|
||||
returnVal = ""
|
||||
i = 0
|
||||
while len(returnVal) < 16: # Max 15 znaků
|
||||
if i == len(string):
|
||||
break
|
||||
if string[i] == " ":
|
||||
returnVal += "-"
|
||||
if string[i].isalnum():
|
||||
returnVal += string[i].lower()
|
||||
i += 1
|
||||
return returnVal
|
||||
|
||||
def __str__(self):
|
||||
if self.nazev:
|
||||
return self.nazev
|
||||
else:
|
||||
#TODO: logování
|
||||
return "Nepojmenovaný Treenode"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.aktualizuj_nazev()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
raise NotImplementedError("Pokus o aktualizaci názvu obecného TreeNode místo konkrétní instance")
|
||||
|
||||
def get_admin_url(self):
|
||||
content_type = ContentType.objects.get_for_model(self.__class__)
|
||||
return reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,))
|
||||
|
||||
class RocnikNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_rocnik'
|
||||
verbose_name = 'Ročník (Node)'
|
||||
verbose_name_plural = 'Ročníky (Node)'
|
||||
rocnik = models.OneToOneField(am.Rocnik,
|
||||
on_delete = models.PROTECT, # Pokud chci mazat ročník, musím si Node pořešit ručně
|
||||
verbose_name = "ročník")
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "RocnikNode: "+str(self.rocnik)
|
||||
|
||||
class CisloNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_cislo'
|
||||
verbose_name = 'Číslo (Node)'
|
||||
verbose_name_plural = 'Čísla (Node)'
|
||||
cislo = models.OneToOneField(am.Cislo,
|
||||
on_delete = models.PROTECT, # Pokud chci mazat číslo, musím si Node pořešit ručně
|
||||
verbose_name = "číslo")
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "CisloNode: "+str(self.cislo)
|
||||
|
||||
def getOdkazStr(self):
|
||||
return "Číslo " + str(self.cislo)
|
||||
|
||||
class MezicisloNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_mezicislo'
|
||||
verbose_name = 'Mezičíslo (Node)'
|
||||
verbose_name_plural = 'Mezičísla (Node)'
|
||||
|
||||
# TODO: Využít TreeLib
|
||||
def aktualizuj_nazev(self):
|
||||
from treenode.treelib import safe_pred
|
||||
if safe_pred(self) is not None:
|
||||
if (self.prev.get_real_instance_class() != CisloNode and
|
||||
self.prev.get_real_instance_class() != MezicisloNode):
|
||||
raise ValueError("Předchůdce není číslo!")
|
||||
posledni = self.prev.cislo
|
||||
self.nazev = "MezicisloNode: Mezičíslo po čísle"+str(posledni)
|
||||
elif self.root:
|
||||
if self.root.get_real_instance_class() != RocnikNode:
|
||||
raise ValueError("Kořen stromu není ročník!")
|
||||
rocnik = self.root.rocnik
|
||||
self.nazev = "MezicisloNode: První mezičíslo ročníku "+str(rocnik)
|
||||
else:
|
||||
print("!!!!! Nějaké neidentifikované mezičíslo !!!!!")
|
||||
self.nazev = "MezicisloNode: Neidentifikovatelné mezičíslo!"
|
||||
def getOdkazStr(self):
|
||||
return "Obsah dostupný pouze na webu"
|
||||
|
||||
class TemaVCisleNode(TreeNode):
|
||||
""" Obsahuje příspěvky k tématu v daném čísle """
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_temavcisle'
|
||||
verbose_name = 'Téma v čísle (Node)'
|
||||
verbose_name_plural = 'Témata v čísle (Node)'
|
||||
tema = models.ForeignKey(am.Tema,
|
||||
on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
|
||||
verbose_name = "téma v čísle")
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "TemaVCisleNode: "+str(self.tema)
|
||||
|
||||
def getOdkazStr(self):
|
||||
return str(self.tema)
|
||||
|
||||
class OrgTextNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_orgtextnode'
|
||||
verbose_name = 'Organizátorský článek (Node)'
|
||||
verbose_name_plural = 'Organizátorské články (Node)'
|
||||
|
||||
organizator = models.ForeignKey(pm.Organizator,
|
||||
null=False,
|
||||
blank=False,
|
||||
on_delete=models.DO_NOTHING,
|
||||
verbose_name="Organizátor",
|
||||
)
|
||||
org_verejny = models.BooleanField(default = True,
|
||||
verbose_name = "Org je veřejný?",
|
||||
help_text = "Pokud ano, bude org pod článkem podepsaný",
|
||||
null=False,
|
||||
)
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
return f"OrgTextNode začínající následujícim: {self.first_child.nazev}"
|
||||
|
||||
# FIXME!!!
|
||||
#def getOdkazStr(self):
|
||||
# return str(self.clanek)
|
||||
|
||||
|
||||
class UlohaZadaniNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_uloha_zadani'
|
||||
verbose_name = 'Zadání úlohy (Node)'
|
||||
verbose_name_plural = 'Zadání úloh (Node)'
|
||||
uloha = models.OneToOneField(am.Uloha,
|
||||
on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
|
||||
verbose_name = "úloha",
|
||||
null=True,
|
||||
blank=False)
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "UlohaZadaniNode: "+str(self.uloha)
|
||||
|
||||
def getOdkazStr(self):
|
||||
return str(self.uloha)
|
||||
|
||||
|
||||
class PohadkaNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_pohadka'
|
||||
verbose_name = 'Pohádka (Node)'
|
||||
verbose_name_plural = 'Pohádky (Node)'
|
||||
pohadka = models.OneToOneField(am.Pohadka,
|
||||
on_delete=models.PROTECT, # Pokud chci mazat pohádku, musím si Node pořešit ručně
|
||||
verbose_name = "pohádka",
|
||||
)
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "PohadkaNode: "+str(self.pohadka)
|
||||
|
||||
class UlohaVzorakNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_uloha_vzorak'
|
||||
verbose_name = 'Vzorák úlohy (Node)'
|
||||
verbose_name_plural = 'Vzoráky úloh (Node)'
|
||||
uloha = models.OneToOneField(am.Uloha,
|
||||
on_delete=models.PROTECT, # Pokud chci mazat téma, musím si Node pořešit ručně
|
||||
verbose_name = "úloha",
|
||||
null=True,
|
||||
blank=False)
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "UlohaVzorakNode: "+str(self.uloha)
|
||||
|
||||
def getOdkazStr(self):
|
||||
return str(self.uloha)
|
||||
|
||||
|
||||
class TextNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_obsah'
|
||||
verbose_name = 'Text (Node)'
|
||||
verbose_name_plural = 'Text (Node)'
|
||||
text = models.ForeignKey(Text,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name = 'text')
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "TextNode: "+str(self.text)
|
||||
|
||||
def getOdkazStr(self):
|
||||
return str(self.text)
|
||||
|
||||
|
||||
class CastNode(TreeNode):
|
||||
class Meta:
|
||||
db_table = 'seminar_nodes_cast'
|
||||
verbose_name = 'Část (Node)'
|
||||
verbose_name_plural = 'Části (Node)'
|
||||
|
||||
nadpis = models.CharField('Nadpis', max_length=100, help_text = 'Nadpis podvěšené části obsahu')
|
||||
|
||||
def aktualizuj_nazev(self):
|
||||
self.nazev = "CastNode: "+str(self.nadpis)
|
||||
|
||||
def getOdkazStr(self):
|
||||
return str(self.nadpis)
|
645
seminar/models/tvorba.py
Normal file
645
seminar/models/tvorba.py
Normal file
|
@ -0,0 +1,645 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import subprocess
|
||||
import pathlib
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.utils.text import get_valid_filename
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from solo.models import SingletonModel
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from seminar.utils import roman
|
||||
from seminar.utils import hlavni_problem
|
||||
from treenode import treelib
|
||||
|
||||
from unidecode import unidecode # Používám pro získání ID odkazu (ještě je to někde po někom zakomentované)
|
||||
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
||||
from django.core.mail import EmailMessage
|
||||
from seminar.utils import aktivniResitele
|
||||
|
||||
from . import personalni as pm
|
||||
|
||||
from .base import SeminarModelBase
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Rocnik(SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_rocniky'
|
||||
verbose_name = 'Ročník'
|
||||
verbose_name_plural = 'Ročníky'
|
||||
ordering = ['-rocnik']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
prvni_rok = models.IntegerField('první rok', db_index=True, unique=True)
|
||||
|
||||
rocnik = models.IntegerField('číslo ročníku', db_index=True, unique=True)
|
||||
|
||||
exportovat = models.BooleanField('export do AESOPa', db_column='exportovat', default=False,
|
||||
help_text='Exportuje se jen podle tohoto flagu (ne veřejnosti),'
|
||||
' a to jen čísla s veřejnou výsledkovkou')
|
||||
|
||||
# má OneToOneField s:
|
||||
# RocnikNode
|
||||
|
||||
def __str__(self):
|
||||
return '{} ({}/{})'.format(self.rocnik, self.prvni_rok, self.prvni_rok+1)
|
||||
|
||||
# Ročník v římských číslech
|
||||
def roman(self):
|
||||
return roman(int(self.rocnik))
|
||||
|
||||
def verejne(self):
|
||||
return len(self.verejna_cisla()) > 0
|
||||
verejne.boolean = True
|
||||
verejne.short_description = 'Veřejný (jen dle čísel)'
|
||||
|
||||
def verejna_cisla(self):
|
||||
vc = [c for c in self.cisla.all() if c.verejne()]
|
||||
vc.sort(key=lambda c: c.poradi)
|
||||
return vc
|
||||
|
||||
def posledni_verejne_cislo(self):
|
||||
vc = self.verejna_cisla()
|
||||
return vc[-1] if vc else None
|
||||
|
||||
def verejne_vysledkovky_cisla(self):
|
||||
vc = list(self.cisla.filter(verejna_vysledkovka=True))
|
||||
vc.sort(key=lambda c: c.poradi)
|
||||
return vc
|
||||
|
||||
def posledni_zverejnena_vysledkovka_cislo(self):
|
||||
vc = self.verejne_vysledkovky_cisla()
|
||||
return vc[-1] if vc else None
|
||||
|
||||
def druhy_rok(self):
|
||||
return self.prvni_rok + 1
|
||||
|
||||
def verejne_url(self):
|
||||
return reverse('seminar_rocnik', kwargs={'rocnik': self.rocnik})
|
||||
|
||||
@classmethod
|
||||
def cached_rocnik(cls, r_id):
|
||||
name = 'rocnik_%s' % (r_id, )
|
||||
c = cache.get(name)
|
||||
if c is None:
|
||||
c = cls.objects.get(id=r_id)
|
||||
cache.set(name, c, 300)
|
||||
return c
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
# *Node.save() aktualizuje název *Nodu.
|
||||
try:
|
||||
self.rocniknode.save()
|
||||
except ObjectDoesNotExist:
|
||||
# Neexistující *Node nemá smysl aktualizovat.
|
||||
pass
|
||||
|
||||
def cislo_pdf_filename(self, filename):
|
||||
rocnik = str(self.rocnik.rocnik)
|
||||
return pathlib.Path('cislo', 'pdf', rocnik, '{}-{}.pdf'.format(rocnik, self.poradi))
|
||||
|
||||
def cislo_png_filename(self, filename):
|
||||
rocnik = str(self.rocnik.rocnik)
|
||||
return pathlib.Path('cislo', 'png', rocnik, '{}-{}.png'.format(rocnik, self.poradi))
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Cislo(SeminarModelBase):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_cisla'
|
||||
verbose_name = 'Číslo'
|
||||
verbose_name_plural = 'Čísla'
|
||||
ordering = ['-rocnik__rocnik', '-poradi']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník', related_name='cisla',
|
||||
db_index=True,on_delete=models.PROTECT)
|
||||
|
||||
poradi = models.CharField('název čísla', max_length=32, db_index=True,
|
||||
help_text='Většinou jen "1", vyjímečně "7-8", lexikograficky určuje pořadí v ročníku!')
|
||||
|
||||
datum_vydani = models.DateField('datum vydání', blank=True, null=True,
|
||||
help_text='Datum vydání finální verze')
|
||||
|
||||
datum_deadline_soustredeni = models.DateField(
|
||||
'datum deadline soustředění',
|
||||
blank=True, null=True,
|
||||
help_text='Datum pro příjem řešení pro účast na soustředění')
|
||||
|
||||
datum_preddeadline = models.DateField('datum předdeadline', blank=True, null=True,
|
||||
help_text='Datum pro příjem řešení, která se otisknou v dalším čísle')
|
||||
|
||||
datum_deadline = models.DateField('datum deadline', blank=True, null=True,
|
||||
help_text='Datum pro příjem řešení úloh zadaných v tomto čísle')
|
||||
|
||||
verejne_db = models.BooleanField('číslo zveřejněno',
|
||||
db_column='verejne', default=False)
|
||||
|
||||
verejna_vysledkovka = models.BooleanField(
|
||||
'zveřejněna výsledkovka',
|
||||
default=False,
|
||||
help_text='Je-li false u veřejného čísla, '
|
||||
'není výsledkovka zatím veřejná.')
|
||||
|
||||
poznamka = models.TextField('neveřejná poznámka', blank=True,
|
||||
help_text='Neveřejná poznámka k číslu (plain text)')
|
||||
|
||||
pdf = models.FileField('pdf', upload_to=cislo_pdf_filename, null=True, blank=True,
|
||||
help_text='PDF čísla, které si mohou řešitelé stáhnout')
|
||||
|
||||
titulka_nahled = models.ImageField('Obrázek titulní strany', upload_to=cislo_png_filename, null=True, blank=True,
|
||||
help_text='Obrázek titulní strany, generuje se automaticky')
|
||||
|
||||
# má OneToOneField s:
|
||||
# CisloNode
|
||||
|
||||
def kod(self):
|
||||
return '%s.%s' % (self.rocnik.rocnik, self.poradi)
|
||||
kod.short_description = 'Kód čísla'
|
||||
|
||||
def __str__(self):
|
||||
# Potenciální DB HOG, pokud by se ročník necachoval
|
||||
r = Rocnik.cached_rocnik(self.rocnik_id)
|
||||
return '{}.{}'.format(r.rocnik, self.poradi)
|
||||
|
||||
def verejne(self):
|
||||
return self.verejne_db
|
||||
verejne.boolean = True
|
||||
|
||||
def verejne_url(self):
|
||||
return reverse('seminar_cislo', kwargs={'rocnik': self.rocnik.rocnik, 'cislo': self.poradi})
|
||||
|
||||
def absolute_url(self):
|
||||
return "https://" + str(get_current_site(None)) + self.verejne_url()
|
||||
|
||||
def nasledujici(self):
|
||||
"Vrací None, pokud je toto poslední"
|
||||
return self.relativni_v_rocniku(1)
|
||||
|
||||
def predchozi(self):
|
||||
"Vrací None, pokud je toto první"
|
||||
return self.relativni_v_rocniku(-1)
|
||||
|
||||
def relativni_v_rocniku(self, rel_index):
|
||||
"Číslo o `index` dále v ročníku. None pokud neexistuje."
|
||||
cs = self.rocnik.cisla.order_by('cislo').all()
|
||||
i = list(cs).index(self) + rel_index
|
||||
if (i < 0) or (i >= len(cs)):
|
||||
return None
|
||||
return cs[i]
|
||||
|
||||
def vygeneruj_nahled(self):
|
||||
VYSKA = 594
|
||||
sirka = int(VYSKA*210/297)
|
||||
if not self.pdf:
|
||||
return
|
||||
|
||||
|
||||
# Pokud obrázek neexistuje nebo není aktuální, vytvoř jej
|
||||
if not self.titulka_nahled or os.path.getmtime(self.titulka_nahled.path) < os.path.getmtime(self.pdf.path):
|
||||
png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png')
|
||||
|
||||
subprocess.run([
|
||||
"gs",
|
||||
"-sstdout=%stderr",
|
||||
"-dSAFER",
|
||||
"-dNOPAUSE",
|
||||
"-dBATCH",
|
||||
"-dNOPROMPT",
|
||||
"-sDEVICE=png16m",
|
||||
"-r300x300",
|
||||
"-dFirstPage=1d",
|
||||
"-dLastPage=1d",
|
||||
"-sOutputFile=" + str(png_filename),
|
||||
"-f%s" % self.pdf.path
|
||||
],
|
||||
check=True,
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
with open(png_filename,'rb') as f:
|
||||
self.titulka_nahled.save('',f,True)
|
||||
|
||||
png_filename.unlink()
|
||||
png_filename.parent.rmdir()
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def get(cls, rocnik, cislo):
|
||||
try:
|
||||
r = Rocnik.objects.get(rocnik=rocnik)
|
||||
c = r.cisla.get(poradi=cislo)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
return c
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.__original_verejne = self.verejne_db
|
||||
|
||||
def posli_cislo_mailem(self):
|
||||
# parametry e-mailu
|
||||
odkaz = self.absolute_url()
|
||||
|
||||
poslat_z_mailu = 'zadani@mam.mff.cuni.cz'
|
||||
predmet = 'Vyšlo číslo {}'.format(self.kod())
|
||||
text_mailu = 'Ahoj,\n' \
|
||||
'na adrese {} najdete nejnovější číslo.\n' \
|
||||
'Vaše M&M\n'.format(odkaz)
|
||||
|
||||
# Prijemci e-mailu
|
||||
emaily = map(lambda r: r.osoba.email, filter(lambda r: r.zasilat_cislo_emailem, aktivniResitele(self)))
|
||||
|
||||
if not settings.POSLI_MAILOVOU_NOTIFIKACI:
|
||||
print("Poslal bych upozornění na tyto adresy: ", " ".join(emaily))
|
||||
return
|
||||
|
||||
email = EmailMessage(
|
||||
subject=predmet,
|
||||
body=text_mailu,
|
||||
from_email=poslat_z_mailu,
|
||||
bcc=list(emaily)
|
||||
#bcc = příjemci skryté kopie
|
||||
)
|
||||
|
||||
email.send()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.vygeneruj_nahled()
|
||||
# Při zveřejnění pošle mail
|
||||
if self.verejne_db and not self.__original_verejne:
|
||||
self.posli_cislo_mailem()
|
||||
# *Node.save() aktualizuje název *Nodu.
|
||||
try:
|
||||
self.cislonode.save()
|
||||
except ObjectDoesNotExist:
|
||||
# Neexistující *Node nemá smysl aktualizovat, ale je potřeba ho naopak vyrobit
|
||||
logger.warning(f'Číslo {self} nemělo ČísloNode, vyrábím…')
|
||||
from seminar.models.treenode import CisloNode
|
||||
CisloNode.objects.create(cislo=self)
|
||||
|
||||
def clean(self):
|
||||
# Finální deadline má být až poslední a je povinný, pokud nějaký deadline existuje.
|
||||
# Existence:
|
||||
if self.datum_deadline is None and (self.datum_preddeadline is not None or self.datum_deadline_soustredeni is not None):
|
||||
raise ValidationError({'datum_deadline': "Číslo musí mít finální deadline, pokud má nějaké deadliny"})
|
||||
if self.datum_deadline is not None:
|
||||
if self.datum_preddeadline is not None and self.datum_preddeadline > self.datum_deadline:
|
||||
raise ValidationError({'datum_preddeadline': "Předdeadline musí předcházet finálnímu deadlinu"})
|
||||
if self.datum_deadline_soustredeni is not None and self.datum_deadline_soustredeni > self.datum_deadline:
|
||||
raise ValidationError({'datum_deadline_soustredeni': "Soustřeďkový deadline musí předcházet finálnímu deadlinu"})
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
# Pozor na následující řádek. *Nekrmit, asi kouše!*
|
||||
class Problem(SeminarModelBase,PolymorphicModel):
|
||||
|
||||
class Meta:
|
||||
# Není abstraktní, protože se na něj jinak nedají dělat ForeignKeys.
|
||||
# TODO: Udělat to polymorfní (pomocí django-polymorphic), abychom dostali
|
||||
# po těch vazbách přímo tu úlohu/témátko vč. fieldů, které nejsou součástí
|
||||
# modelu Problem?
|
||||
|
||||
#abstract = True
|
||||
db_table = 'seminar_problemy'
|
||||
verbose_name = 'Problém'
|
||||
verbose_name_plural = 'Problémy'
|
||||
ordering = ['nazev']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key = True)
|
||||
|
||||
# Název
|
||||
nazev = models.CharField('název', max_length=256) # Zveřejnitelný na stránky
|
||||
|
||||
# Problém má podproblémy
|
||||
nadproblem = models.ForeignKey('self', verbose_name='nadřazený problém',
|
||||
related_name='podproblem', null=True, blank=True,
|
||||
on_delete=models.SET_NULL)
|
||||
|
||||
STAV_NAVRH = 'navrh'
|
||||
STAV_ZADANY = 'zadany'
|
||||
STAV_VYRESENY = 'vyreseny'
|
||||
STAV_SMAZANY = 'smazany'
|
||||
STAV_CHOICES = [
|
||||
(STAV_NAVRH, 'Návrh'),
|
||||
(STAV_ZADANY, 'Zadaný'),
|
||||
(STAV_VYRESENY, 'Vyřešený'),
|
||||
(STAV_SMAZANY, 'Smazaný'),
|
||||
]
|
||||
stav = models.CharField('stav problému', max_length=32, choices=STAV_CHOICES, blank=False, default=STAV_NAVRH)
|
||||
# Téma je taky Problém, takže má stavy, "zadané" témátko je aktuálně otevřené a dá se k němu něco poslat (řešení nebo článek)
|
||||
|
||||
zamereni = TaggableManager(verbose_name='zaměření',
|
||||
help_text='Zaměření M/F/I/O problému, příp. další tagy', blank=True)
|
||||
|
||||
poznamka = models.TextField('org poznámky (HTML)', blank=True,
|
||||
help_text='Neveřejný návrh úlohy, návrh řešení, text zadání, poznámky ...')
|
||||
|
||||
autor = models.ForeignKey(pm.Organizator, verbose_name='autor problému',
|
||||
related_name='autor_problemu_%(class)s', null=True, blank=True,
|
||||
on_delete=models.SET_NULL)
|
||||
|
||||
garant = models.ForeignKey(pm.Organizator, verbose_name='garant zadaného problému',
|
||||
related_name='garant_problemu_%(class)s', null=True, blank=True,
|
||||
on_delete=models.SET_NULL)
|
||||
|
||||
opravovatele = models.ManyToManyField(pm.Organizator, verbose_name='opravovatelé',
|
||||
blank=True, related_name='opravovatele_%(class)s')
|
||||
|
||||
kod = models.CharField('lokální kód', max_length=32, blank=True, default='',
|
||||
help_text='Číslo/kód úlohy v čísle nebo kód tématu/článku/seriálu v ročníku')
|
||||
|
||||
|
||||
vytvoreno = models.DateTimeField('vytvořeno', default=timezone.now, blank=True, editable=False)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.nazev
|
||||
|
||||
# Implicitini implementace, jednotlivé dědící třídy si přepíšou
|
||||
@cached_property
|
||||
def kod_v_rocniku(self):
|
||||
if self.stav == 'zadany':
|
||||
if self.nadproblem:
|
||||
return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
|
||||
return str(self.kod)
|
||||
return '<Není zadaný>'
|
||||
|
||||
# def verejne(self):
|
||||
# # aktuálně podle stavu problému
|
||||
# # FIXME pro některé problémy možná chceme override
|
||||
# # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
|
||||
# # Je to tak správně? Podle aktuální představy ano.
|
||||
# stav_verejny = False
|
||||
# if self.stav == 'zadany' or self.stav == 'vyreseny':
|
||||
# stav_verejny = True
|
||||
# print("stav_verejny: {}".format(stav_verejny))
|
||||
#
|
||||
# cislo_verejne = False
|
||||
# cislonode = self.cislo_node()
|
||||
# if cislonode is None:
|
||||
# # problém nemá vlastní node, veřejnost posuzujeme jen podle stavu
|
||||
# print("empty node")
|
||||
# return stav_verejny
|
||||
# else:
|
||||
# cislo_zadani = cislonode.cislo
|
||||
# if (cislo_zadani and cislo_zadani.verejne()):
|
||||
# print("cislo: {}".format(cislo_zadani))
|
||||
# cislo_verejne = True
|
||||
# print("stav_verejny: {}".format(stav_verejny))
|
||||
# print("cislo_verejne: {}".format(cislo_verejne))
|
||||
# return (stav_verejny and cislo_verejne)
|
||||
# verejne.boolean = True
|
||||
|
||||
def verejne_url(self):
|
||||
return reverse('seminar_problem', kwargs={'pk': self.id})
|
||||
|
||||
def admin_url(self):
|
||||
return reverse('admin:seminar_problem_change', args=(self.id, ))
|
||||
|
||||
def hlavni_problem(self):
|
||||
""" Pro daný problém vrátí jeho nejvyšší nadproblém."""
|
||||
return hlavni_problem(self)
|
||||
|
||||
# FIXME - k úloze
|
||||
def body_v_zavorce(self):
|
||||
"""Vrať string s body v závorce jsou-li u problému vyplněné, jinak ''
|
||||
|
||||
Je-li desetinná část nulová, nezobrazuj ji.
|
||||
"""
|
||||
pocet_bodu = None
|
||||
if self.body:
|
||||
b = self.body
|
||||
pocet_bodu = int(b) if int(b) == b else b
|
||||
return "({}\u2009b)".format(pocet_bodu) if self.body else ""
|
||||
|
||||
class Tema(Problem):
|
||||
class Meta:
|
||||
db_table = 'seminar_temata'
|
||||
verbose_name = 'Téma'
|
||||
verbose_name_plural = 'Témata'
|
||||
|
||||
TEMA_TEMA = 'tema'
|
||||
TEMA_SERIAL = 'serial'
|
||||
TEMA_CHOICES = [
|
||||
(TEMA_TEMA, 'Téma'),
|
||||
(TEMA_SERIAL, 'Seriál'),
|
||||
]
|
||||
tema_typ = models.CharField('Typ tématu', max_length=16, choices=TEMA_CHOICES,
|
||||
blank=False, default=TEMA_TEMA)
|
||||
|
||||
rocnik = models.ForeignKey(Rocnik, verbose_name='ročník',related_name='temata',blank=True, null=True,
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
abstrakt = models.TextField('Abstrakt na rozcestník', blank=True)
|
||||
obrazek = models.ImageField('Obrázek na rozcestník', null=True, blank=True)
|
||||
|
||||
@cached_property
|
||||
def kod_v_rocniku(self):
|
||||
if self.stav == 'zadany':
|
||||
if self.nadproblem:
|
||||
return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
|
||||
return "t{}".format(self.kod)
|
||||
return '<Není zadaný>'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
# *Node.save() aktualizuje název *Nodu.
|
||||
for tvcn in self.temavcislenode_set.all():
|
||||
tvcn.save()
|
||||
|
||||
def cislo_node(self):
|
||||
tema_node_set = self.temavcislenode_set.all()
|
||||
tema_cisla_vyskyt = []
|
||||
from seminar.models.treenode import CisloNode
|
||||
for tn in tema_node_set:
|
||||
tema_cisla_vyskyt.append(
|
||||
treelib.get_upper_node_of_type(tn, CisloNode).cislo)
|
||||
tema_cisla_vyskyt.sort(key=lambda x:x.datum_vydani)
|
||||
prvni_zadani = tema_cisla_vyskyt[0]
|
||||
return prvni_zadani.cislonode
|
||||
|
||||
class Clanek(Problem):
|
||||
class Meta:
|
||||
db_table = 'seminar_clanky'
|
||||
verbose_name = 'Článek'
|
||||
verbose_name_plural = 'Články'
|
||||
|
||||
cislo = models.ForeignKey(Cislo, blank=True, null=True, on_delete=models.PROTECT,
|
||||
verbose_name='číslo vydání', related_name='vydane_clanky')
|
||||
|
||||
@cached_property
|
||||
def kod_v_rocniku(self):
|
||||
if self.stav == 'zadany':
|
||||
# Nemělo by být potřeba
|
||||
# if self.nadproblem:
|
||||
# return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
|
||||
return "c{}".format(self.kod)
|
||||
return '<Není zadaný>'
|
||||
|
||||
def node(self):
|
||||
return None
|
||||
|
||||
|
||||
class Uloha(Problem):
|
||||
class Meta:
|
||||
db_table = 'seminar_ulohy'
|
||||
verbose_name = 'Úloha'
|
||||
verbose_name_plural = 'Úlohy'
|
||||
|
||||
cislo_zadani = models.ForeignKey(Cislo, verbose_name='číslo zadání', blank=True,
|
||||
null=True, related_name='zadane_ulohy', on_delete=models.PROTECT)
|
||||
|
||||
cislo_deadline = models.ForeignKey(Cislo, verbose_name='číslo deadlinu', blank=True,
|
||||
null=True, related_name='deadlinove_ulohy', on_delete=models.PROTECT)
|
||||
|
||||
cislo_reseni = models.ForeignKey(Cislo, verbose_name='číslo řešení', blank=True,
|
||||
null=True, related_name='resene_ulohy',
|
||||
help_text='Číslo s řešením úlohy, jen pro úlohy',
|
||||
on_delete=models.PROTECT)
|
||||
|
||||
max_body = models.DecimalField(max_digits=8, decimal_places=1, verbose_name='maximum bodů',
|
||||
blank=True, null=True)
|
||||
|
||||
# má OneToOneField s:
|
||||
# UlohaZadaniNode
|
||||
# UlohaVzorakNode
|
||||
|
||||
@cached_property
|
||||
def kod_v_rocniku(self):
|
||||
if self.stav == 'zadany':
|
||||
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
|
||||
if self.nadproblem:
|
||||
return self.nadproblem.kod_v_rocniku+name
|
||||
return name
|
||||
return '<Není zadaný>'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
# *Node.save() aktualizuje název *Nodu.
|
||||
try:
|
||||
self.ulohazadaninode.save()
|
||||
except ObjectDoesNotExist:
|
||||
# Neexistující *Node nemá smysl aktualizovat.
|
||||
pass
|
||||
try:
|
||||
self.ulohavzoraknode.save()
|
||||
except ObjectDoesNotExist:
|
||||
# Neexistující *Node nemá smysl aktualizovat.
|
||||
pass
|
||||
|
||||
def cislo_node(self):
|
||||
zadani_node = self.ulohazadaninode
|
||||
from seminar.models.treenode import CisloNode
|
||||
return treelib.get_upper_node_of_type(zadani_node, CisloNode)
|
||||
|
||||
|
||||
def aux_generate_filename(self, filename):
|
||||
"""Pomocná funkce generující ošetřený název souboru v adresáři s datem"""
|
||||
clean = get_valid_filename(
|
||||
unidecode(filename.replace('/', '-').replace('\0', ''))
|
||||
)
|
||||
datedir = timezone.now().strftime('%Y-%m')
|
||||
fname = "{}/{}".format(
|
||||
timezone.now().strftime('%Y-%m-%d-%H:%M'),
|
||||
clean)
|
||||
return os.path.join(datedir, fname)
|
||||
|
||||
|
||||
class Pohadka(SeminarModelBase):
|
||||
"""Kus pohádky před/za úlohou v čísle"""
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_pohadky'
|
||||
verbose_name = 'Pohádka'
|
||||
verbose_name_plural = 'Pohádky'
|
||||
ordering = ['vytvoreno']
|
||||
|
||||
# Interní ID
|
||||
id = models.AutoField(primary_key=True)
|
||||
|
||||
autor = models.ForeignKey(
|
||||
pm.Organizator,
|
||||
verbose_name="Autor pohádky",
|
||||
|
||||
# Při nahrávání z TeXu není vyplnění vyžadováno, v adminu je
|
||||
null=True,
|
||||
blank=False,
|
||||
on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
vytvoreno = models.DateTimeField(
|
||||
'Vytvořeno',
|
||||
default=timezone.now,
|
||||
blank=True,
|
||||
editable=False
|
||||
)
|
||||
|
||||
# má OneToOneField s:
|
||||
# PohadkaNode
|
||||
|
||||
def __str__(self):
|
||||
uryvek = self.text if len(self.text) < 50 else self.text[:(50-3)]+"..."
|
||||
return uryvek
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
# *Node.save() aktualizuje název *Nodu.
|
||||
try:
|
||||
self.pohadkanode.save()
|
||||
except ObjectDoesNotExist:
|
||||
# Neexistující *Node nemá smysl aktualizovat.
|
||||
pass
|
||||
|
||||
|
||||
@reversion.register(ignore_duplicates=True)
|
||||
class Nastaveni(SingletonModel):
|
||||
|
||||
class Meta:
|
||||
db_table = 'seminar_nastaveni'
|
||||
verbose_name = 'Nastavení semináře'
|
||||
|
||||
# aktualni_rocnik = models.ForeignKey(Rocnik, verbose_name='aktuální ročník',
|
||||
# null=False, on_delete=models.PROTECT)
|
||||
|
||||
aktualni_cislo = models.ForeignKey(Cislo, verbose_name='poslední vydané číslo',
|
||||
null=False, on_delete=models.PROTECT)
|
||||
|
||||
@property
|
||||
def aktualni_rocnik(self):
|
||||
return self.aktualni_cislo.rocnik
|
||||
|
||||
def __str__(self):
|
||||
return 'Nastavení semináře'
|
||||
|
||||
def admin_url(self):
|
||||
return reverse('admin:seminar_nastaveni_change', args=(self.id, ))
|
||||
|
||||
def verejne(self):
|
||||
return False
|
|
@ -79,73 +79,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if cislo.verejna_vysledkovka or user.je_org %}
|
||||
<table class='vysledkovka'>
|
||||
<tr class='border-b'>
|
||||
<th class='border-r'>#
|
||||
<th class='border-r'>Jméno
|
||||
{% for p in problemy %}
|
||||
<th class='border-r' id="problem{{ forloop.counter }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #}
|
||||
|
||||
{# TODELETE #}
|
||||
{% for podproblemy in podproblemy_iter.next %}
|
||||
<th class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
|
||||
{% endfor %}
|
||||
{# TODELETE #}
|
||||
|
||||
{% endfor %}
|
||||
{% if ostatni %}<th class='border-r'>Ostatní {% endif %}
|
||||
|
||||
{# TODELETE #}
|
||||
{% for podproblemy in podproblemy_iter.next %}
|
||||
<th class='border-r podproblem{{ problemy.len }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
|
||||
{% endfor %}
|
||||
{# TODELETE #}
|
||||
|
||||
|
||||
<th class='border-r'>Za číslo
|
||||
<th class='border-r'>Za ročník
|
||||
<th class='border-r'>Odjakživa
|
||||
{% for rv in radky_vysledkovky %}
|
||||
<tr>
|
||||
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
|
||||
<th class='border-r'>
|
||||
{% if rv.titul %}
|
||||
{{ rv.titul }}<sup>MM</sup>
|
||||
{% endif %}
|
||||
{{ rv.resitel.osoba.plne_jmeno }}
|
||||
{% for b in rv.body_problemy_sezn %}
|
||||
<td class='border-r'>{{ b }}
|
||||
|
||||
{# TODELETE #}
|
||||
{% for body_podproblemu in rv.body_podproblemy_iter.next %}
|
||||
<td class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{{ body_podproblemu }}
|
||||
{% endfor %}
|
||||
{# TODELETE #}
|
||||
|
||||
{% endfor %}
|
||||
<td class='border-r'>{{ rv.body_cislo }}
|
||||
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
|
||||
<td class='border-r'>{{ rv.body_celkem_odjakziva }}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{# TODELETE #}
|
||||
<script>
|
||||
{% for p in problemy %}
|
||||
$(".podproblem{{ forloop.counter }}").css("display", "none")
|
||||
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }})
|
||||
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end)
|
||||
function podproblem{{ forloop.counter }}(event) {
|
||||
$(".podproblem{{ forloop.counter }}").css("display", "")
|
||||
}
|
||||
function podproblem{{ forloop.counter }}end(event) {
|
||||
$(".podproblem{{ forloop.counter }}").css("display", "none")
|
||||
}
|
||||
{% endfor %}
|
||||
</script>
|
||||
{# TODELETE #}
|
||||
|
||||
{% include "vysledkovky/vysledkovka_cisla.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% if not cislo.verejna_vysledkovka and user.je_org %}
|
||||
|
|
|
@ -114,18 +114,14 @@
|
|||
|
||||
{% if vysledkovka %}
|
||||
<h2>Výsledková listina</h2>
|
||||
{% include "seminar/vysledkovka_rocnik.html" %}
|
||||
{% include "vysledkovky/vysledkovka_rocnik.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% if user.je_org %}
|
||||
<div class='mam-org-only'>
|
||||
<a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a>
|
||||
<h2>Výsledková listina včetně neveřejných bodů</h2>
|
||||
{% with radky_vysledkovky_s_neverejnymi as radky_vysledkovky %}
|
||||
{% with cisla_s_neverejnymi as cisla %}
|
||||
{% include "seminar/vysledkovka_rocnik.html" %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static 'seminar/prihlaska.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
<!--
|
||||
|
||||
# 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
|
||||
|
||||
-->
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
Změna osobních údajů
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
<form action="{% url 'seminar_resitel_edit' %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{form.non_field_errors}}
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Přihlašovací údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.username %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Osobní údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.jmeno %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.prijmeni %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.pohlavi_muz%}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.email %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.telefon %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.datum_narozeni %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Bydliště
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.ulice %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.mesto %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.psc %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.stat %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Škola
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.skola %}
|
||||
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
|
||||
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.rok_maturity %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Pošta
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Zasílání propagačních materiálů
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.spam %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<input type="submit" value="Změnit">
|
||||
</form>
|
||||
<script>
|
||||
$("#id_stat").on("change",addrCountryChanged);
|
||||
$("#id_skola_text_button").on("click",schoolNotInList);
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,123 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static 'seminar/prihlaska.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
<!--
|
||||
|
||||
# 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
|
||||
|
||||
-->
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
Přihláška do semináře
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
|
||||
<p><b>Tučně</b> popsaná pole jsou povinná.</p>
|
||||
|
||||
<form action="{% url 'seminar_prihlaska' %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{form.non_field_errors}}
|
||||
|
||||
|
||||
<hr>
|
||||
<h4>
|
||||
Přihlašovací údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.username %}
|
||||
{# {% include "seminar/profil/prihlaska_field.html" with field=form.password %}#}
|
||||
{# {% include "seminar/profil/prihlaska_field.html" with field=form.password_check %}#}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Osobní údaje
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.jmeno %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.prijmeni %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.pohlavi_muz%}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.email %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.telefon %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.datum_narozeni %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Bydliště
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.ulice %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.mesto %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.psc %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.stat %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Škola
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.skola %}
|
||||
<tr><td colspan="2" ><button id="id_skola_text_button" type="button">Škola není v seznamu</button></td></tr>
|
||||
<tr><td id="id_li_skola_vypln" colspan="2">Vyplň prosím celý název a adresu školy.</td></tr>
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_nazev id="id_li_skola_nazev" %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.skola_adresa id="id_li_skola_adresa" %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.rok_maturity %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Pošta
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat %}
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.zasilat_cislo_emailem %}
|
||||
</table>
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
GDPR
|
||||
</h4>
|
||||
{% include "seminar/profil/gdpr.html" %}
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.gdpr %}
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>
|
||||
Zasílání propagačních materiálů
|
||||
</h4>
|
||||
<table class="form">
|
||||
{% include "seminar/profil/prihlaska_field.html" with field=form.spam %}
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<input type="submit" value="Odeslat">
|
||||
</form>
|
||||
<script>
|
||||
$("#id_stat").on("change",addrCountryChanged);
|
||||
$("#id_skola_text_button").on("click",schoolNotInList);
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -9,7 +9,7 @@
|
|||
</h1>
|
||||
|
||||
{% if radky_vysledkovky %}
|
||||
{% include "seminar/vysledkovka_rocnik.html" %}
|
||||
{% include "vysledkovky/vysledkovka_rocnik.html" %}
|
||||
{% else %}
|
||||
<p>V tomto ročníku zatím žádné výsledky nejsou.</p>
|
||||
{% endif %}
|
||||
|
@ -22,11 +22,7 @@
|
|||
{% if user.je_org and vysledkovka_s_neverejnymi %}
|
||||
<div class='mam-org-only'>
|
||||
<h1>Výsledky včetně neveřejných</h1>
|
||||
{% with vysledkovka_s_neverejnymi as radky_vysledkovky %}
|
||||
{% with cisla_s_neverejnymi as cisla %}
|
||||
{% include "seminar/vysledkovka_rocnik.html" %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% include "vysledkovky/vysledkovka_rocnik_neverejna.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
from django import template
|
||||
|
||||
from seminar.models import Rocnik
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.inclusion_tag('results.html')
|
||||
def seminar_rocniky(parser, token):
|
||||
return {
|
||||
'rocniky': Rocnik.objects.all()
|
||||
}
|
||||
|
||||
@register.simple_tag
|
||||
def aktualni_rocniky():
|
||||
return Rocnik.objects.all()
|
|
@ -17,7 +17,7 @@ import seminar.models as m
|
|||
|
||||
from django.contrib.flatpages.models import FlatPage
|
||||
from django.contrib.sites.models import Site
|
||||
from seminar.treelib import all_children, insert_last_child, all_children_of_type, create_node_after
|
||||
from treenode.treelib import all_children, insert_last_child, all_children_of_type, create_node_after
|
||||
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
@ -752,8 +752,8 @@ def gen_clanek(rnd, organizatori, resitele):
|
|||
# Bude to celý text
|
||||
reseni.text_cely = reseninode
|
||||
reseni.save()
|
||||
|
||||
from seminar.treelib import insert_last_child, create_child
|
||||
|
||||
from treenode.treelib import insert_last_child, create_child
|
||||
insert_last_child(cislonode, reseninode)
|
||||
|
||||
# Vyrobíme nějaký obsah
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.urls import path, include, re_path
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from . import views
|
||||
from .utils import org_required, resitel_required, viewMethodSwitch, resitel_or_org_required
|
||||
from .utils import org_required
|
||||
|
||||
urlpatterns = [
|
||||
# path('aktualni/temata/', views.TemataRozcestnikView),
|
||||
|
@ -18,42 +17,6 @@ urlpatterns = [
|
|||
path('rocnik/<int:rocnik>/', views.RocnikView.as_view(), name='seminar_rocnik'),
|
||||
path('cislo/<int:rocnik>.<str:cislo>/', views.CisloView.as_view(), name='seminar_cislo'),
|
||||
path('problem/<int:pk>/', views.problemView, name='seminar_problem'),
|
||||
#path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'),
|
||||
#path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
|
||||
#path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'),
|
||||
#path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'),
|
||||
#path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'),
|
||||
#path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'),
|
||||
#path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'),
|
||||
#path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'),
|
||||
#path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'),
|
||||
#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'),
|
||||
|
||||
# Soustredeni
|
||||
path(
|
||||
'soustredeni/probehlo/',
|
||||
views.SoustredeniListView.as_view(),
|
||||
name='seminar_seznam_soustredeni'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/seznam_ucastniku',
|
||||
org_required(views.SoustredeniUcastniciView.as_view()),
|
||||
name='soustredeni_ucastnici'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/maily_ucastniku',
|
||||
org_required(views.SoustredeniMailyUcastnikuView.as_view()),
|
||||
name='maily_ucastniku'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/export_ucastniku',
|
||||
org_required(views.soustredeniUcastniciExportView),
|
||||
name='soustredeni_ucastnici_export'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/fotogalerie/',
|
||||
include('galerie.urls')
|
||||
),
|
||||
|
||||
# Zadani
|
||||
# path('aktualni/zadani/', views.AktualniZadaniView.as_view(), name='seminar_aktualni_zadani'), # Dočasně ad-hoc jednoduchá věc.
|
||||
|
@ -102,46 +65,7 @@ urlpatterns = [
|
|||
'cislo/<int:trocnik>.<str:tcislo>/odmeny/<int:frocnik>.<str:fcislo>/',
|
||||
org_required(views.OdmenyView.as_view()),
|
||||
name="seminar_archiv_odmeny"),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/obalky.pdf',
|
||||
org_required(views.soustredeniObalkyView),
|
||||
name='seminar_soustredeni_obalky'
|
||||
),
|
||||
# příprava na nestatický orgorozcestník
|
||||
path(
|
||||
'org/rozcestnik/',
|
||||
org_required(views.OrgoRozcestnikView.as_view()),
|
||||
name='seminar_org_rozcestnik'
|
||||
),
|
||||
|
||||
path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'),
|
||||
|
||||
path('resitel/odevzdana_reseni/', resitel_or_org_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'),
|
||||
|
||||
path(
|
||||
'resitel/osobni-udaje/',
|
||||
login_required(views.resitelEditView),
|
||||
name='seminar_resitel_edit'
|
||||
),
|
||||
|
||||
# Obecný view na profil -- orgům dá rozcestník, řešitelům jejich stránku
|
||||
path('profil/', views.profilView, name='profil'),
|
||||
|
||||
path('org/add_solution', org_required(views.AddSolutionView.as_view()), name='seminar_vloz_reseni'),
|
||||
path('resitel/nahraj_reseni', resitel_required(views.NahrajReseniView.as_view()), name='seminar_nahraj_reseni'),
|
||||
|
||||
re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'),
|
||||
path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()),
|
||||
|
||||
path('', views.TitulniStranaView.as_view(), name='titulni_strana'),
|
||||
path('jak-resit/', views.JakResitView.as_view(), name='jak_resit'),
|
||||
|
||||
path('org/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
||||
path('org/reseni/rocnik/<int:rocnik>/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
||||
path('org/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
|
||||
path('org/reseni/<int:pk>', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'),
|
||||
path('org/reseni/all', org_required(views.SeznamReseniView.as_view())),
|
||||
path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
|
||||
|
||||
path('resitel/reseni/<int:pk>', resitel_or_org_required(views.ResitelReseniView.as_view()), name='odevzdavatko_resitel_reseni'),
|
||||
]
|
||||
|
|
|
@ -20,7 +20,7 @@ from enum import auto
|
|||
import logging
|
||||
|
||||
import seminar.models as m
|
||||
import seminar.treelib as t
|
||||
import treenode.treelib as t
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
from .views_all import *
|
||||
from .views_rest import *
|
||||
from .odevzdavatko import *
|
||||
|
||||
# Dočsasné views
|
||||
from .docasne import *
|
||||
|
|
|
@ -1,35 +1,26 @@
|
|||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse
|
||||
from django.urls import reverse,reverse_lazy
|
||||
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
|
||||
from django.core.mail import send_mail
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.views import generic
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect
|
||||
from django.http import Http404
|
||||
from django.db.models import Q, Sum, Count
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.generic.edit import FormView, CreateView
|
||||
from django.views.generic.base import TemplateView, RedirectView
|
||||
from django.contrib.auth.models import User, Permission, Group
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import transaction
|
||||
from django.core import serializers
|
||||
from django.views.generic.base import RedirectView
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
import seminar.models as s
|
||||
import seminar.models as m
|
||||
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
|
||||
from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Organizator, Resitel, Novinky, Tema, Clanek # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
|
||||
#from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva
|
||||
from seminar import utils, treelib
|
||||
from seminar.forms import PrihlaskaForm, ProfileEditForm, PoMaturiteProfileEditForm
|
||||
import seminar.forms as f
|
||||
import seminar.templatetags.treenodes as tnltt
|
||||
import seminar.views.views_rest as vr
|
||||
from seminar.views.vysledkovka import vysledkovka_rocniku, vysledkovka_cisla, body_resitelu
|
||||
from seminar import utils
|
||||
from treenode import treelib
|
||||
import treenode.templatetags as tnltt
|
||||
import treenode.serializers as vr
|
||||
from vysledkovky.utils import body_resitelu
|
||||
from vysledkovky.views import vysledkovka_rocniku, vysledkovka_cisla
|
||||
|
||||
from datetime import timedelta, date, datetime, MAXYEAR
|
||||
from datetime import date, datetime
|
||||
from django.utils import timezone
|
||||
from itertools import groupby
|
||||
from collections import OrderedDict
|
||||
|
@ -40,16 +31,10 @@ import os
|
|||
import os.path as op
|
||||
from django.conf import settings
|
||||
import unicodedata
|
||||
import json
|
||||
import traceback
|
||||
import sys
|
||||
import csv
|
||||
import logging
|
||||
import time
|
||||
|
||||
from seminar.utils import aktivniResitele, resi_v_rocniku, problemy_rocniku, cisla_rocniku, hlavni_problemy_f
|
||||
from various.autentizace.views import LoginView
|
||||
from various.autentizace.utils import posli_reset_hesla
|
||||
from seminar.utils import aktivniResitele
|
||||
|
||||
# ze starého modelu
|
||||
#def verejna_temata(rocnik):
|
||||
|
@ -221,141 +206,6 @@ class TNLData(object):
|
|||
def __repr__(self):
|
||||
return("TNL({})".format(self.node))
|
||||
|
||||
class TreeNodeView(generic.DetailView):
|
||||
model = s.TreeNode
|
||||
template_name = 'seminar/treenode.html'
|
||||
|
||||
def get_context_data(self,**kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['tnldata'] = TNLData.from_treenode(self.object,self.request.user)
|
||||
return context
|
||||
|
||||
class TreeNodeJSONView(generic.DetailView):
|
||||
model = s.TreeNode
|
||||
|
||||
def get(self,request,*args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
data = TNLData.from_treenode(self.object,self.request.user).to_json()
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
|
||||
class TreeNodePridatView(generic.View):
|
||||
type_from_str = {
|
||||
'rocnikNode': m.RocnikNode,
|
||||
'cisloNode': m.CisloNode,
|
||||
'castNode': m.CastNode,
|
||||
'textNode': m.TextNode,
|
||||
'temaVCisleNode': m.TemaVCisleNode,
|
||||
'reseniNode': m.ReseniNode,
|
||||
'ulohaZadaniNode': m.UlohaZadaniNode,
|
||||
'ulohaVzorakNode': m.UlohaVzorakNode,
|
||||
'pohadkaNode': m.PohadkaNode,
|
||||
'orgText': m.OrgTextNode,
|
||||
}
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ###########
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
kam = self.kwargs['kam']
|
||||
co = self.kwargs['co']
|
||||
typ = self.type_from_str[co]
|
||||
|
||||
raise NotImplementedError('Neni to dopsane, dopis to!')
|
||||
|
||||
if kam not in ('pred','syn','za'):
|
||||
raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna')
|
||||
|
||||
if co == m.TextNode:
|
||||
new_obj = m.Text()
|
||||
new_obj.save()
|
||||
elif co == m.CastNode:
|
||||
new_obj = m.CastNode()
|
||||
new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam))
|
||||
new_obj.save()
|
||||
elif co == m.ReseniNode:
|
||||
new_obj = m
|
||||
pass
|
||||
elif co == m.UlohaZadaniNode:
|
||||
pass
|
||||
elif co == m.UlohaReseniNode:
|
||||
pass
|
||||
else:
|
||||
new_obj = None
|
||||
|
||||
|
||||
if kam == 'pred':
|
||||
pass
|
||||
|
||||
|
||||
if kam == 'syn':
|
||||
if typ == m.TextNode:
|
||||
text_obj = m.Text()
|
||||
text_obj.save()
|
||||
node = treelib.create_child(node,typ,text=text_obj)
|
||||
else:
|
||||
node = treelib.create_child(node,typ)
|
||||
if kam == 'za':
|
||||
if typ == m.TextNode:
|
||||
text_obj = m.Text()
|
||||
text_obj.save()
|
||||
node = treelib.create_node_after(node,typ,text=text_obj)
|
||||
else:
|
||||
node = treelib.create_node_after(node,typ)
|
||||
|
||||
return redirect(node.get_admin_url())
|
||||
|
||||
|
||||
class TreeNodeSmazatView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
if node.first_child:
|
||||
raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!')
|
||||
treelib.disconnect_node(node)
|
||||
node.delete()
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
class TreeNodeOdvesitPrycView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
treelib.disconnect_node(node)
|
||||
node.root = None
|
||||
node.save()
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
|
||||
class TreeNodePodvesitView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
kam = self.kwargs['kam']
|
||||
if kam == 'pred':
|
||||
treelib.lower_node(node)
|
||||
elif kam == 'za':
|
||||
raise NotImplementedError('Podvěsit za není zatím podporováno')
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
class TreeNodeProhoditView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
treelib.swap_succ(node)
|
||||
return redirect(request.headers.get('referer'))
|
||||
#FIXME ve formulari predat puvodni url a vratit redirect na ni
|
||||
|
||||
class SirotcinecView(generic.ListView):
|
||||
model = s.TreeNode
|
||||
template_name = 'seminar/orphanage.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None)
|
||||
|
||||
# FIXME pouzit Django REST Framework
|
||||
class TextWebView(generic.DetailView):
|
||||
model = s.Text
|
||||
|
||||
def get(self,request,*args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return JsonResponse(model_to_dict(self.object,exclude='do_cisla'))
|
||||
|
||||
|
||||
# FIXME: Pozor, níž je ještě jeden ProblemView!
|
||||
#class ProblemView(generic.DetailView):
|
||||
|
@ -494,31 +344,34 @@ def ZadaniAktualniVysledkovkaView(request):
|
|||
nastaveni = get_object_or_404(Nastaveni)
|
||||
# Aktualni verejna vysledkovka
|
||||
rocnik = nastaveni.aktualni_rocnik
|
||||
vysledkovka = vysledkovka_rocniku(rocnik)
|
||||
cisla = cisla_rocniku(rocnik)
|
||||
context = vysledkovka_rocniku(
|
||||
rocnik=rocnik,
|
||||
request=request,
|
||||
sneverejnou=True
|
||||
)
|
||||
|
||||
# kdyz neni verejna vysledkovka, tak zobraz starou
|
||||
if not vysledkovka or not any(map(lambda it: it.verejna_vysledkovka, cisla)):
|
||||
if len(context['cisla']) == 0:
|
||||
try:
|
||||
minuly_rocnik = Rocnik.objects.get(
|
||||
prvni_rok=(rocnik.prvni_rok-1))
|
||||
rocnik = minuly_rocnik
|
||||
vysledkovka = vysledkovka_rocniku(minuly_rocnik)
|
||||
cisla = cisla_rocniku(minuly_rocnik)
|
||||
|
||||
# Přepíšeme prázdnou výsledkovku výsledkovkou z minulého ročníku
|
||||
context = vysledkovka_rocniku(
|
||||
rocnik=rocnik,
|
||||
context=context,
|
||||
request=request,
|
||||
sneverejnou=True
|
||||
)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
# vysledkovka s neverejnyma vysledkama
|
||||
vysledkovka_s_neverejnymi = vysledkovka_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False)
|
||||
cisla_s_neverejnymi = cisla_rocniku(nastaveni.aktualni_rocnik, jen_verejne=False)
|
||||
|
||||
context['rocnik'] = rocnik
|
||||
return render(
|
||||
request,
|
||||
'seminar/zadani/AktualniVysledkovka.html',
|
||||
{
|
||||
'rocnik': rocnik,
|
||||
'radky_vysledkovky': vysledkovka,
|
||||
'cisla': cisla,
|
||||
'vysledkovka_s_neverejnymi': vysledkovka_s_neverejnymi,
|
||||
'cisla_s_neverejnymi': cisla_s_neverejnymi,
|
||||
}
|
||||
context
|
||||
)
|
||||
|
||||
|
||||
|
@ -664,18 +517,12 @@ class RocnikView(generic.DetailView):
|
|||
def get_context_data(self, **kwargs):
|
||||
start = time.time()
|
||||
context = super(RocnikView, self).get_context_data(**kwargs)
|
||||
|
||||
# vysledkovka = True zajistí vykreslení,
|
||||
# zkontrolovat, kdy se má a nemá vykreslovat
|
||||
cisla = cisla_rocniku(context["rocnik"])
|
||||
context['vysledkovka'] = any(map(lambda it: it.verejna_vysledkovka, cisla))
|
||||
if self.request.user.je_org:
|
||||
context['cisla_s_neverejnymi'] = cisla_rocniku(context["rocnik"], jen_verejne=False)
|
||||
context['radky_vysledkovky_s_neverejnymi'] = vysledkovka_rocniku(context["rocnik"], jen_verejne=False)
|
||||
context['hlavni_problemy_v_rocniku_s_neverejnymi'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"], jen_verejne=False))
|
||||
context['cisla'] = cisla
|
||||
context['radky_vysledkovky'] = vysledkovka_rocniku(context["rocnik"])
|
||||
context['hlavni_problemy_v_rocniku'] = hlavni_problemy_f(problemy_rocniku(context["rocnik"]))
|
||||
context = vysledkovka_rocniku(
|
||||
rocnik=context["rocnik"],
|
||||
context=context,
|
||||
request=self.request,
|
||||
sneverejnou=True
|
||||
)
|
||||
end = time.time()
|
||||
print("Kontext:", end-start)
|
||||
|
||||
|
@ -836,51 +683,6 @@ def oldObalkovaniView(request, rocnik, cislo):
|
|||
{'cislo': cislo, 'problemy': problemy, 'reseni': reseni}
|
||||
)
|
||||
|
||||
### Orgostránky
|
||||
|
||||
class OrgoRozcestnikView(TemplateView):
|
||||
''' Zobrazí organizátorský rozcestník.'''
|
||||
|
||||
template_name = 'seminar/orgorozcestnik.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first()
|
||||
nastaveni = Nastaveni.objects.first()
|
||||
aktualni_rocnik = nastaveni.aktualni_rocnik
|
||||
context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
|
||||
# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
|
||||
# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít
|
||||
# přes treenody (a dát si přitom pozor na MezicisloNode)
|
||||
|
||||
neobodovana_reseni = s.Hodnoceni.objects.filter(body__isnull=True)
|
||||
reseni_mimo_cislo = s.Hodnoceni.objects.filter(cislo_body__isnull=True)
|
||||
context['pocet_neobodovanych_reseni'] = neobodovana_reseni.count()
|
||||
context['pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.count()
|
||||
|
||||
u = self.request.user
|
||||
os = s.Osoba.objects.get(user=u)
|
||||
organizator = s.Organizator.objects.get(osoba=os)
|
||||
|
||||
context['muj_pocet_neobodovanych_reseni'] = neobodovana_reseni.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).distinct().count()
|
||||
context['muj_pocet_reseni_mimo_cislo'] = reseni_mimo_cislo.filter(Q(problem__garant=organizator) | Q(problem__autor=organizator) | Q(problem__opravovatele__in=[organizator])).count()
|
||||
|
||||
#FIXME: přidat stav='STAV_ZADANY'
|
||||
temata = s.Tema.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
||||
rocnik=aktualni_rocnik).distinct()
|
||||
ulohy = s.Uloha.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
||||
cislo_zadani__rocnik=aktualni_rocnik).distinct()
|
||||
clanky = s.Clanek.objects.filter(Q(garant=organizator) | Q(autor=organizator) | Q(opravovatele__in=[organizator]),
|
||||
cislo__rocnik=aktualni_rocnik).distinct()
|
||||
|
||||
context['temata'] = temata
|
||||
context['ulohy'] = ulohy
|
||||
context['clanky'] = clanky
|
||||
context['organizator'] = organizator
|
||||
return context
|
||||
|
||||
#content_type = 'text/plain; charset=UTF8'
|
||||
#XXX
|
||||
|
||||
### Tituly
|
||||
|
||||
|
@ -911,53 +713,6 @@ def TitulyView(request, rocnik, cislo):
|
|||
return render(request, 'seminar/archiv/tituly.tex',
|
||||
{'resitele': resitele,'jmenovci':jmenovci},content_type="text/plain")
|
||||
|
||||
### Soustredeni
|
||||
|
||||
class SoustredeniListView(generic.ListView):
|
||||
model = Soustredeni
|
||||
template_name = 'seminar/soustredeni/seznam_soustredeni.html'
|
||||
|
||||
def soustredeniObalkyView(request,soustredeni):
|
||||
soustredeni = get_object_or_404(Soustredeni,id = soustredeni)
|
||||
return obalkyView(request,soustredeni.ucastnici.all())
|
||||
|
||||
|
||||
class SoustredeniUcastniciBaseView(generic.ListView):
|
||||
model = Soustredeni_Ucastnici
|
||||
|
||||
def get_queryset(self):
|
||||
soustredeni = get_object_or_404(
|
||||
Soustredeni,
|
||||
pk=self.kwargs["soustredeni"]
|
||||
)
|
||||
return Soustredeni_Ucastnici.objects.filter(
|
||||
soustredeni=soustredeni).select_related('resitel')
|
||||
|
||||
|
||||
class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView):
|
||||
""" Seznam e-mailů řešitelů oddělených čárkami. """
|
||||
model = Soustredeni_Ucastnici
|
||||
template_name = 'seminar/soustredeni/maily_ucastniku.txt'
|
||||
|
||||
|
||||
class SoustredeniUcastniciView(SoustredeniUcastniciBaseView):
|
||||
""" HTML tabulka účastníků pro tisk. """
|
||||
model = Soustredeni_Ucastnici
|
||||
template_name = 'seminar/soustredeni/seznam_ucastniku.html'
|
||||
|
||||
def soustredeniUcastniciExportView(request,soustredeni):
|
||||
soustredeni = get_object_or_404(Soustredeni,id = soustredeni)
|
||||
ucastnici = Resitel.objects.filter(soustredeni=soustredeni)
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"'
|
||||
|
||||
writer = csv.writer(response)
|
||||
writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"])
|
||||
for u in ucastnici:
|
||||
o = u.osoba
|
||||
writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name])
|
||||
return response
|
||||
|
||||
|
||||
### Články
|
||||
def group_by_rocnik(clanky):
|
||||
|
@ -1026,361 +781,6 @@ def StavDatabazeView(request):
|
|||
})
|
||||
|
||||
|
||||
class ResitelView(LoginRequiredMixin,generic.DetailView):
|
||||
model = Resitel
|
||||
template_name = 'seminar/profil/resitel.html'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
print(self.request.user)
|
||||
return Resitel.objects.get(osoba__user=self.request.user)
|
||||
|
||||
### Formulare
|
||||
|
||||
# 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
|
||||
|
||||
class AddSolutionView(LoginRequiredMixin, FormView):
|
||||
template_name = 'seminar/org/vloz_reseni.html'
|
||||
form_class = f.VlozReseniForm
|
||||
|
||||
def form_valid(self, form):
|
||||
data = form.cleaned_data
|
||||
nove_reseni = m.Reseni.objects.create(
|
||||
cas_doruceni=data['cas_doruceni'],
|
||||
forma=data['forma'],
|
||||
poznamka=data['poznamka'],
|
||||
)
|
||||
nove_reseni.resitele.add(data['resitel'])
|
||||
nove_reseni.problem.add(data['problem'])
|
||||
nove_reseni.save()
|
||||
# Chtěl jsem, aby bylo vidět, že se to uložilo, tak přesměrovávám na profil.
|
||||
return redirect(reverse('profil'))
|
||||
|
||||
|
||||
class NahrajReseniView(LoginRequiredMixin, CreateView):
|
||||
model = s.Reseni
|
||||
template_name = 'seminar/profil/nahraj_reseni.html'
|
||||
form_class = f.NahrajReseniForm
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Zaříznutí starých řešitelů:
|
||||
# FIXME: Je to tady dost naprasené, mělo by to asi být jinde…
|
||||
osoba = m.Osoba.objects.get(user=self.request.user)
|
||||
resitel = osoba.resitel
|
||||
if resitel.rok_maturity <= m.Nastaveni.get_solo().aktualni_rocnik.prvni_rok:
|
||||
return render(request, 'universal.html', {
|
||||
'title': 'Nelze odevzdat',
|
||||
'error': 'Zdá se, že jsi již odmaturoval/a, a tedy nemůžeš odevzdat do našeho semináře řešení.',
|
||||
'text': 'Pokud se ti zdá, že to je chyba, napiš nám prosím e-mail. Díky.',
|
||||
})
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self,**kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
if self.request.POST:
|
||||
data['prilohy'] = f.ReseniSPrilohamiFormSet(self.request.POST,self.request.FILES)
|
||||
else:
|
||||
data['prilohy'] = f.ReseniSPrilohamiFormSet()
|
||||
return data
|
||||
|
||||
# FIXME prepsat tak, aby form_valid se volalo jen tehdy, kdyz je form i formset validni
|
||||
# Inspirace: https://stackoverflow.com/questions/41599809/using-a-django-filefield-in-an-inline-formset
|
||||
def form_valid(self,form):
|
||||
context = self.get_context_data()
|
||||
prilohy = context['prilohy']
|
||||
if not prilohy.is_valid():
|
||||
return super().form_invalid(form)
|
||||
with transaction.atomic():
|
||||
self.object = form.save()
|
||||
self.object.resitele.add(Resitel.objects.get(osoba__user = self.request.user))
|
||||
self.object.cas_doruceni = timezone.now()
|
||||
self.object.forma = s.Reseni.FORMA_UPLOAD
|
||||
self.object.save()
|
||||
|
||||
prilohy.instance = self.object
|
||||
prilohy.save()
|
||||
|
||||
# Pošleme mail opravovatelům a garantovi
|
||||
# FIXME: Nechat spočítat databázi? Je to pár dotazů (pravděpodobně), takže to za to možná nestojí
|
||||
prijemci = set()
|
||||
problemy = []
|
||||
for prob in form.cleaned_data['problem']:
|
||||
prijemci.update(prob.opravovatele.all())
|
||||
if prob.garant is not None:
|
||||
prijemci.add(prob.garant)
|
||||
problemy.append(prob)
|
||||
# FIXME: Možná poslat mail i relevantním orgům nadproblémů?
|
||||
if len(prijemci) < 1:
|
||||
logger.warning(f"Pozor, neposílám e-mail nikomu. Problémy: {problemy}")
|
||||
# FIXME: Víc informativní obsah mailů, možná vč. příloh?
|
||||
prijemci = map(lambda it: it.osoba.email, prijemci)
|
||||
|
||||
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_do_subjectu = "problému " + str(problemy[0]) + ("" if len(problemy) == 1 else f" (a dalším { len(problemy) - 1 })")
|
||||
|
||||
send_mail(
|
||||
subject="Nové řešení k " + seznam_do_subjectu,
|
||||
message=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í?
|
||||
recipient_list=list(prijemci),
|
||||
)
|
||||
|
||||
return formularOKView(self.request, text='Řešení úspěšně odevzdáno')
|
||||
|
||||
|
||||
def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
|
||||
msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items)))
|
||||
logger.warn(msg)
|
||||
gdpr_logger.warn(msg+", form:{}".format(form_data))
|
||||
|
||||
from django.forms.models import model_to_dict
|
||||
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
|
||||
def resitelEditView(request):
|
||||
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
||||
## Načtení objektů Osoba a Resitel patřících k aktuálně přihlášenému uživateli
|
||||
u = request.user
|
||||
osoba_edit = Osoba.objects.get(user=u)
|
||||
if hasattr(osoba_edit,'resitel'):
|
||||
resitel_edit = osoba_edit.resitel
|
||||
else:
|
||||
resitel_edit = None
|
||||
user_edit = osoba_edit.user
|
||||
## Vytvoření slovníku, kterým předvyplním formulář
|
||||
prefill_1=model_to_dict(user_edit)
|
||||
if resitel_edit:
|
||||
prefill_2=model_to_dict(resitel_edit)
|
||||
prefill_1.update(prefill_2)
|
||||
prefill_3=model_to_dict(osoba_edit)
|
||||
prefill_1.update(prefill_3)
|
||||
if 'datum_narozeni' in prefill_1:
|
||||
prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni'])
|
||||
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
|
||||
form = PoMaturiteProfileEditForm(initial=prefill_1)
|
||||
else:
|
||||
form = ProfileEditForm(initial=prefill_1)
|
||||
## Změna údajů a jejich uložení
|
||||
if request.method == 'POST':
|
||||
POST = request.POST.copy()
|
||||
POST["username"] = osoba_edit.user.username
|
||||
|
||||
if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year:
|
||||
form = PoMaturiteProfileEditForm(POST)
|
||||
else:
|
||||
form = ProfileEditForm(POST)
|
||||
form.username = user_edit.username
|
||||
if form.is_valid():
|
||||
## Změny v osobě
|
||||
fcd = form.cleaned_data
|
||||
form_hash = hash(frozenset(fcd.items()))
|
||||
form_logger = logging.getLogger('seminar.prihlaska.form')
|
||||
form_logger.info("EDIT:" + str(fcd) + str(form_hash)) # TODO možná logovat jinak
|
||||
osoba_edit.jmeno = fcd['jmeno']
|
||||
osoba_edit.prijmeni = fcd['prijmeni']
|
||||
osoba_edit.pohlavi_muz = fcd['pohlavi_muz']
|
||||
osoba_edit.email = fcd['email']
|
||||
osoba_edit.telefon = fcd['telefon']
|
||||
osoba_edit.ulice = fcd['ulice']
|
||||
osoba_edit.mesto = fcd['mesto']
|
||||
osoba_edit.psc = fcd['psc']
|
||||
osoba_edit.datum_narozeni = fcd['datum_narozeni']
|
||||
## Změny v osobě s podmínkami
|
||||
if fcd.get('spam',False):
|
||||
osoba_edit.datum_souhlasu_zasilani = date.today()
|
||||
if fcd.get('stat','') in ('CZ','SK'):
|
||||
osoba_edit.stat = fcd['stat']
|
||||
else:
|
||||
## Neznámá země
|
||||
msg = "Unknown country {}".format(fcd['stat_text'])
|
||||
|
||||
if resitel_edit:
|
||||
## Změny v řešiteli
|
||||
resitel_edit.skola = fcd['skola']
|
||||
resitel_edit.rok_maturity = fcd['rok_maturity']
|
||||
resitel_edit.zasilat = fcd['zasilat']
|
||||
resitel_edit.zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
|
||||
if fcd.get('skola'):
|
||||
resitel_edit.skola = fcd['skola']
|
||||
else:
|
||||
# Unknown school - log it
|
||||
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
|
||||
resitel_edit.save()
|
||||
osoba_edit.save()
|
||||
return formularOKView(request, text=f'Údaje byly úspěšně uloženy. <a href="{reverse("profil")}">Vrátit se zpět na profil.</a>')
|
||||
|
||||
return render(request, 'seminar/profil/edit.html', {'form': form})
|
||||
|
||||
@sensitive_post_parameters('jmeno', 'prijmeni', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'skola')
|
||||
def prihlaskaView(request):
|
||||
generic_logger = logging.getLogger('seminar.prihlaska')
|
||||
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
||||
form_logger = logging.getLogger('seminar.prihlaska.form')
|
||||
if request.method == 'POST':
|
||||
form = PrihlaskaForm(request.POST)
|
||||
# TODO vyresit, co se bude v jakych situacich zobrazovat
|
||||
if form.is_valid():
|
||||
generic_logger.info("Form valid")
|
||||
fcd = form.cleaned_data
|
||||
form_hash = hash(frozenset(fcd.items()))
|
||||
form_logger.info(str(fcd) + str(form_hash)) # TODO možná logovat jinak
|
||||
|
||||
with transaction.atomic():
|
||||
u = User.objects.create_user(
|
||||
username=fcd['username'],
|
||||
email = fcd['email'])
|
||||
u.save()
|
||||
resitel_perm = Permission.objects.filter(codename__exact='resitel').first()
|
||||
u.user_permissions.add(resitel_perm)
|
||||
resitel_grp = Group.objects.filter(name__exact='resitel').first()
|
||||
u.groups.add(resitel_grp)
|
||||
|
||||
o = Osoba(
|
||||
jmeno = fcd['jmeno'],
|
||||
prijmeni = fcd['prijmeni'],
|
||||
pohlavi_muz = fcd['pohlavi_muz'],
|
||||
email = fcd['email'],
|
||||
telefon = fcd.get('telefon',''),
|
||||
datum_narozeni = fcd.get('datum_narozeni',None),
|
||||
datum_souhlasu_udaje = date.today(),
|
||||
datum_registrace = date.today(),
|
||||
ulice = fcd.get('ulice',''),
|
||||
mesto = fcd.get('mesto',''),
|
||||
psc = fcd.get('psc',''),
|
||||
poznamka = str(fcd)
|
||||
)
|
||||
|
||||
if fcd.get('spam',False):
|
||||
o.datum_souhlasu_zasilani = date.today()
|
||||
if fcd.get('stat','') in ('CZ','SK'):
|
||||
o.stat = fcd['stat']
|
||||
else:
|
||||
# Unknown country - log it
|
||||
msg = "Unknown country {}".format(fcd['stat_text'])
|
||||
err_logger.warn(msg + str(form_hash))
|
||||
|
||||
|
||||
# Dovolujeme doregistraci uživatele pro existující mail, takže naopak chceme doplnit/aktualizovat údaje do stávajícího objektu
|
||||
try:
|
||||
orig_osoba = m.Osoba.objects.get(email=fcd['email'])
|
||||
orig_osoba.poznamka += '\nDOREGISTRACE K EXISTUJÍCÍMU E-MAILU, diff níže.'
|
||||
except m.Osoba.DoesNotExist:
|
||||
# Trik: Budeme aktualizovat údaje nové osoby, takže se asi nic nezmění, ale fungovat to bude.
|
||||
orig_osoba = o
|
||||
|
||||
# Porovnání údajů
|
||||
assert orig_osoba.user is None, "Právě-registrující-se osoba už má Uživatele!"
|
||||
osoba_attrs = ['jmeno', 'prijmeni', 'pohlavi_muz', 'email', 'telefon', 'datum_narozeni', 'ulice', 'mesto', 'psc', 'stat', 'datum_souhlasu_udaje', 'datum_souhlasu_zasilani', 'datum_registrace']
|
||||
diffattrs = []
|
||||
for attr in osoba_attrs:
|
||||
new = getattr(o, attr)
|
||||
old = getattr(orig_osoba, attr)
|
||||
if new != old:
|
||||
orig_osoba.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
|
||||
diffattrs.append(f'Osoba.{attr}')
|
||||
setattr(orig_osoba, attr, new)
|
||||
# Datum registrace chceme původní / nižší:
|
||||
orig_osoba.datum_registrace = min(orig_osoba.datum_registrace, o.datum_registrace)
|
||||
|
||||
# Od této chvíle dál je správná osoba ta "původní", novou podle formuláře si ale zachováme
|
||||
o, o_form = orig_osoba, o
|
||||
|
||||
|
||||
|
||||
o.save()
|
||||
o.user = u
|
||||
o.save()
|
||||
|
||||
# Jednoduchá kvazi-kontrola duplicitních Osob
|
||||
kolize = m.Osoba.objects.filter(jmeno=o.jmeno, prijmeni=o.prijmeni)
|
||||
if kolize.count() > 1: # Jednu z nich jsme právě uložili
|
||||
err_logger.warning(f'Zaregistrovala se osoba s kolizním jménem. ID osob: {[o.id for o in kolize]}')
|
||||
|
||||
r = Resitel(
|
||||
rok_maturity = fcd['rok_maturity'],
|
||||
zasilat = fcd['zasilat'],
|
||||
zasilat_cislo_emailem = fcd['zasilat_cislo_emailem']
|
||||
)
|
||||
|
||||
if fcd.get('skola'):
|
||||
r.skola = fcd['skola']
|
||||
else:
|
||||
# Unknown school - log it
|
||||
msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
|
||||
err_logger.warn(msg + str(form_hash))
|
||||
|
||||
# Porovnání údajů u řešitele
|
||||
try:
|
||||
orig_resitel = o.resitel
|
||||
orig_resitel.poznamka += '\nDOREGISTRACE ŘEŠITELE, diff:'
|
||||
except m.Resitel.DoesNotExist:
|
||||
# Stejný trik:
|
||||
orig_resitel = r
|
||||
resitel_attrs = ['skola', 'rok_maturity', 'zasilat', 'zasilat_cislo_emailem']
|
||||
for attr in resitel_attrs:
|
||||
new = getattr(r, attr)
|
||||
old = getattr(orig_resitel, attr)
|
||||
if new != old:
|
||||
orig_resitel.poznamka += f'\nRozdíl v {attr}: Původní {old}, nový {new}'
|
||||
diffattrs.append(f'Resitel.{attr}')
|
||||
setattr(orig_resitel, attr, new)
|
||||
r, r_form = orig_resitel, r
|
||||
|
||||
r.osoba = o # Tohle by mělo být bezpečné…
|
||||
r.save()
|
||||
|
||||
if diffattrs: err_logger.warning(f'Different fields when matching Řešitel id {r.id} or Osoba id {o.id}: {diffattrs}')
|
||||
|
||||
posli_reset_hesla(u, request)
|
||||
return formularOKView(request, text='Na tvůj e-mail jsme právě poslali odkaz pro nastavení hesla.')
|
||||
|
||||
# if a GET (or any other method) we'll create a blank form
|
||||
else:
|
||||
form = PrihlaskaForm()
|
||||
|
||||
return render(request, 'seminar/profil/prihlaska.html', {'form': form})
|
||||
|
||||
|
||||
class VueTestView(generic.TemplateView):
|
||||
template_name = 'seminar/vuetest.html'
|
||||
|
||||
class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView):
|
||||
model = s.Obrazek
|
||||
form_class = f.NahrajObrazekKTreeNoduForm
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
initial['na_web'] = self.request.FILES['upload']
|
||||
return initial
|
||||
|
||||
|
||||
def form_valid(self,form):
|
||||
print(self.request.headers)
|
||||
print(self.request.headers['Textid'])
|
||||
print(form.instance)
|
||||
print(form)
|
||||
self.object = form.save(commit=False)
|
||||
print(self.object.na_web)
|
||||
self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid']))
|
||||
self.object.save()
|
||||
|
||||
return JsonResponse({"url":self.object.na_web.url})
|
||||
|
||||
|
||||
|
||||
# Jen hloupé rozhazovátko
|
||||
def profilView(request):
|
||||
user = request.user
|
||||
if user.has_perm('auth.org'):
|
||||
return OrgoRozcestnikView.as_view()(request)
|
||||
if user.has_perm('auth.resitel'):
|
||||
return ResitelView.as_view()(request)
|
||||
else:
|
||||
return LoginView.as_view()(request)
|
||||
|
||||
# Interní, nemá se nikdy objevit v urls (jinak to účastníci vytrolí)
|
||||
def formularOKView(request, text=''):
|
||||
template_name = 'seminar/formular_ok.html'
|
||||
|
|
5
soustredeni/__init__.py
Normal file
5
soustredeni/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
"""
|
||||
Obsahuje vše (až na přednášky) ohledně soustředění.
|
||||
|
||||
TODO stvrzenky?
|
||||
"""
|
43
soustredeni/admin.py
Normal file
43
soustredeni/admin.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from django.contrib import admin
|
||||
from django.forms import widgets
|
||||
from django.db import models
|
||||
|
||||
from seminar.models import soustredeni as m
|
||||
|
||||
|
||||
class SoustredeniUcastniciInline(admin.TabularInline):
|
||||
model = m.Soustredeni_Ucastnici
|
||||
extra = 1
|
||||
fields = ['resitel','poznamka']
|
||||
autocomplete_fields = ['resitel']
|
||||
ordering = ['resitel__osoba__jmeno', 'resitel__osoba__prijmeni']
|
||||
formfield_overrides = {
|
||||
models.TextField: {'widget': widgets.TextInput}
|
||||
}
|
||||
|
||||
def get_queryset(self,request):
|
||||
qs = super().get_queryset(request)
|
||||
return qs.select_related('resitel','soustredeni')
|
||||
|
||||
|
||||
class SoustredeniOrganizatoriInline(admin.TabularInline):
|
||||
model = m.Soustredeni.organizatori.through
|
||||
extra = 1
|
||||
fields = ['organizator','poznamka']
|
||||
autocomplete_fields = ['organizator']
|
||||
ordering = ['organizator__osoba__jmeno','organizator__prijmeni']
|
||||
formfield_overrides = {
|
||||
models.TextField: {'widget': widgets.TextInput}
|
||||
}
|
||||
|
||||
def get_queryset(self,request):
|
||||
qs = super().get_queryset(request)
|
||||
return qs.select_related('organizator', 'soustredeni')
|
||||
|
||||
|
||||
@admin.register(m.Soustredeni)
|
||||
class SoustredeniAdmin(admin.ModelAdmin):
|
||||
model = m.Soustredeni
|
||||
inline_type = 'tabular'
|
||||
inlines = [SoustredeniUcastniciInline, SoustredeniOrganizatoriInline]
|
||||
|
5
soustredeni/apps.py
Normal file
5
soustredeni/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SoustredeniConfig(AppConfig):
|
||||
name = 'soustredeni'
|
0
soustredeni/migrations/__init__.py
Normal file
0
soustredeni/migrations/__init__.py
Normal file
35
soustredeni/urls.py
Normal file
35
soustredeni/urls.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from django.urls import path, include
|
||||
from . import views
|
||||
from seminar.utils import org_required
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
'soustredeni/probehlo/',
|
||||
views.SoustredeniListView.as_view(),
|
||||
name='seminar_seznam_soustredeni'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/seznam_ucastniku',
|
||||
org_required(views.SoustredeniUcastniciView.as_view()),
|
||||
name='soustredeni_ucastnici'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/maily_ucastniku',
|
||||
org_required(views.SoustredeniMailyUcastnikuView.as_view()),
|
||||
name='maily_ucastniku'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/export_ucastniku',
|
||||
org_required(views.soustredeniUcastniciExportView),
|
||||
name='soustredeni_ucastnici_export'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/obalky.pdf',
|
||||
org_required(views.soustredeniObalkyView),
|
||||
name='seminar_soustredeni_obalky'
|
||||
),
|
||||
path(
|
||||
'soustredeni/<int:soustredeni>/fotogalerie/',
|
||||
include('galerie.urls')
|
||||
),
|
||||
]
|
55
soustredeni/views.py
Normal file
55
soustredeni/views.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponse
|
||||
from django.views import generic
|
||||
from seminar.models import Soustredeni, Resitel, Soustredeni_Ucastnici # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci
|
||||
import csv
|
||||
|
||||
from seminar.views import obalkyView
|
||||
|
||||
|
||||
class SoustredeniListView(generic.ListView):
|
||||
model = Soustredeni
|
||||
template_name = 'soustredeni/seznam_soustredeni.html'
|
||||
|
||||
|
||||
def soustredeniObalkyView(request, soustredeni):
|
||||
soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
|
||||
return obalkyView(request, soustredeni.ucastnici.all())
|
||||
|
||||
|
||||
class SoustredeniUcastniciBaseView(generic.ListView):
|
||||
model = Soustredeni_Ucastnici
|
||||
|
||||
def get_queryset(self):
|
||||
soustredeni = get_object_or_404(
|
||||
Soustredeni,
|
||||
pk=self.kwargs["soustredeni"]
|
||||
)
|
||||
return Soustredeni_Ucastnici.objects.filter(
|
||||
soustredeni=soustredeni).select_related('resitel')
|
||||
|
||||
|
||||
class SoustredeniMailyUcastnikuView(SoustredeniUcastniciBaseView):
|
||||
""" Seznam e-mailů řešitelů oddělených čárkami. """
|
||||
model = Soustredeni_Ucastnici
|
||||
template_name = 'soustredeni/maily_ucastniku.txt'
|
||||
|
||||
|
||||
class SoustredeniUcastniciView(SoustredeniUcastniciBaseView):
|
||||
""" HTML tabulka účastníků pro tisk. """
|
||||
model = Soustredeni_Ucastnici
|
||||
template_name = 'soustredeni/seznam_ucastniku.html'
|
||||
|
||||
|
||||
def soustredeniUcastniciExportView(request, soustredeni):
|
||||
soustredeni = get_object_or_404(Soustredeni, id=soustredeni)
|
||||
ucastnici = Resitel.objects.filter(soustredeni=soustredeni)
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="ucastnici.csv"'
|
||||
|
||||
writer = csv.writer(response)
|
||||
writer.writerow(["jmeno", "prijmeni", "rok_maturity", "telefon", "email", "ulice", "mesto", "psc","stat"])
|
||||
for u in ucastnici:
|
||||
o = u.osoba
|
||||
writer.writerow([o.jmeno, o.prijmeni, str(u.rok_maturity), o.telefon, o.email, o.ulice, o.mesto, o.psc, o.stat.name])
|
||||
return response
|
0
treenode/__init__.py
Normal file
0
treenode/__init__.py
Normal file
88
treenode/admin.py
Normal file
88
treenode/admin.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
|
||||
|
||||
import seminar.models as m
|
||||
|
||||
# Polymorfismus pro stromy
|
||||
# TODO: Inlines podle https://django-polymorphic.readthedocs.io/en/stable/admin.html
|
||||
|
||||
@admin.register(m.TreeNode)
|
||||
class TreeNodeAdmin(PolymorphicParentModelAdmin):
|
||||
base_model = m.TreeNode
|
||||
child_models = [
|
||||
m.RocnikNode,
|
||||
m.CisloNode,
|
||||
m.MezicisloNode,
|
||||
m.TemaVCisleNode,
|
||||
m.UlohaZadaniNode,
|
||||
m.PohadkaNode,
|
||||
m.UlohaVzorakNode,
|
||||
m.TextNode,
|
||||
m.CastNode,
|
||||
m.OrgTextNode,
|
||||
]
|
||||
|
||||
actions = ['aktualizuj_nazvy']
|
||||
|
||||
# XXX: nejspíš je to totální DB HOG, nechcete to použít moc často.
|
||||
def aktualizuj_nazvy(self, request, queryset):
|
||||
newqs = queryset.get_real_instances()
|
||||
for tn in newqs:
|
||||
tn.aktualizuj_nazev()
|
||||
tn.save()
|
||||
self.message_user(request, "Názvy aktualizovány.")
|
||||
aktualizuj_nazvy.short_description = "Aktualizuj vybraným TreeNodům názvy"
|
||||
|
||||
@admin.register(m.RocnikNode)
|
||||
class RocnikNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.RocnikNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.CisloNode)
|
||||
class CisloNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.CisloNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.MezicisloNode)
|
||||
class MezicisloNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.MezicisloNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.TemaVCisleNode)
|
||||
class TemaVCisleNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.TemaVCisleNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.UlohaZadaniNode)
|
||||
class UlohaZadaniNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.UlohaZadaniNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.PohadkaNode)
|
||||
class PohadkaNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.PohadkaNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.UlohaVzorakNode)
|
||||
class UlohaVzorakNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.UlohaVzorakNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.TextNode)
|
||||
class TextNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.TextNode
|
||||
show_in_index = True
|
||||
|
||||
@admin.register(m.CastNode)
|
||||
class TextNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.CastNode
|
||||
show_in_index = True
|
||||
fields = ('nadpis',)
|
||||
|
||||
@admin.register(m.OrgTextNode)
|
||||
class TextNodeAdmin(PolymorphicChildModelAdmin):
|
||||
base_model = m.OrgTextNode
|
||||
show_in_index = True
|
||||
|
||||
|
5
treenode/apps.py
Normal file
5
treenode/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TreenodeConfig(AppConfig):
|
||||
name = 'treenode'
|
14
treenode/forms.py
Normal file
14
treenode/forms.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from django import forms
|
||||
import seminar.models as m
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class NahrajObrazekKTreeNoduForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = m.Obrazek
|
||||
fields = ('na_web',)
|
0
treenode/migrations/__init__.py
Normal file
0
treenode/migrations/__init__.py
Normal file
|
@ -1,5 +1,5 @@
|
|||
from rest_framework import routers
|
||||
from seminar import viewsets as vs
|
||||
from treenode import viewsets as vs
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
|
|
@ -2,7 +2,7 @@ from rest_framework import serializers
|
|||
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||
|
||||
import seminar.models as m
|
||||
from seminar import treelib
|
||||
from treenode import treelib
|
||||
|
||||
DEFAULT_NODE_DEPTH = 2
|
||||
|
|
@ -17,7 +17,7 @@ def nodeType(value):
|
|||
if isinstance(value,UlohaZadaniNode): return "Zadání úlohy"
|
||||
if isinstance(value,PohadkaNode): return "Pohádka"
|
||||
|
||||
### NASLEDUJICI FUNKCE SE POUZIVAJI VE views_all.py V SEKCI PRIPRAVJICI TNLData
|
||||
### NASLEDUJICI FUNKCE SE POUZIVAJI VE views.py V SEKCI PRIPRAVJICI TNLData
|
||||
### NEMAZAT, PRESUNOUT S TNLDaty NEKAM BOKEM
|
||||
|
||||
@register.filter
|
|
@ -1,5 +1,5 @@
|
|||
from django.test import TestCase
|
||||
import seminar.treelib as tl
|
||||
import treenode.treelib as tl
|
||||
import seminar.models as m
|
||||
|
||||
class SimpleTreeLibTests(TestCase):
|
18
treenode/urls.py
Normal file
18
treenode/urls.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from django.urls import path, re_path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
#path('treenode/<int:pk>/', views.TreeNodeView.as_view(), name='seminar_treenode'),
|
||||
#path('treenode/<int:pk>/json/', views.TreeNodeJSONView.as_view(), name='seminar_treenode_json'),
|
||||
#path('treenode/text/<int:pk>/', views.TextWebView.as_view(), name='seminar_textnode_web'),
|
||||
#path('treenode/editor/pridat/<str:co>/<int:pk>/<str:kam>/', views.TreeNodePridatView.as_view(), name='treenode_pridat'),
|
||||
#path('treenode/editor/smazat/<int:pk>/', views.TreeNodeSmazatView.as_view(), name='treenode_smazat'),
|
||||
#path('treenode/editor/odvesitpryc/<int:pk>/', views.TreeNodeOdvesitPrycView.as_view(), name='treenode_odvesitpryc'),
|
||||
#path('treenode/editor/podvesit/<int:pk>/<str:kam>/', views.TreeNodePodvesitView.as_view(), name='treenode_podvesit'),
|
||||
#path('treenode/editor/prohodit/<int:pk>/', views.TreeNodeProhoditView.as_view(), name='treenode_prohodit'),
|
||||
#path('treenode/sirotcinec/', views.SirotcinecView.as_view(), name='seminar_treenode_sirotcinec'),
|
||||
#path('problem/(?P<pk>\d+)/(?P<prispevek>\d+)/', views.PrispevekView.as_view(), name='seminar_problem_prispevek'),
|
||||
|
||||
re_path(r'^temp/vue/.*$',views.VueTestView.as_view(),name='vue_test_view'),
|
||||
path('temp/image_upload/', views.NahrajObrazekKTreeNoduView.as_view()),
|
||||
]
|
322
treenode/views.py
Normal file
322
treenode/views.py
Normal file
|
@ -0,0 +1,322 @@
|
|||
from django.forms import model_to_dict
|
||||
from django.shortcuts import redirect
|
||||
from django.http import JsonResponse
|
||||
from django.views import generic
|
||||
from django.views.generic.edit import CreateView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
import seminar.models as s
|
||||
import seminar.models as m
|
||||
from treenode import treelib
|
||||
import treenode.forms as f
|
||||
import treenode.templatetags as tnltt
|
||||
import treenode.serializers as vr
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TNLData(object):
|
||||
def __init__(self,anode,parent=None, index=None):
|
||||
self.node = anode
|
||||
self.sernode = vr.TreeNodeSerializer(anode)
|
||||
self.children = []
|
||||
self.parent = parent
|
||||
self.tema_in_path = False
|
||||
self.index = index
|
||||
|
||||
if parent:
|
||||
self.tema_in_path = parent.tema_in_path
|
||||
if isinstance(anode, m.TemaVCisleNode):
|
||||
self.tema_in_path = True
|
||||
|
||||
def add_edit_options(self):
|
||||
self.deletable = tnltt.deletable(self)
|
||||
self.editable_siblings = tnltt.editableSiblings(self)
|
||||
self.editable_children = tnltt.editableChildren(self)
|
||||
self.text_only_subtree = tnltt.textOnlySubtree(self)
|
||||
self.can_podvesit_za = tnltt.canPodvesitZa(self)
|
||||
self.can_podvesit_pred = tnltt.canPodvesitPred(self)
|
||||
self.appendable_children = tnltt.appendableChildren(self)
|
||||
print("appChld",self.appendable_children)
|
||||
if self.parent:
|
||||
self.appendable_siblings = tnltt.appendableChildren(self.parent)
|
||||
else:
|
||||
self.appendable_siblings = []
|
||||
@classmethod
|
||||
def public_above(cls, anode):
|
||||
""" Returns output of verejne for closest Rocnik, Cislo or Problem above.
|
||||
(All of them have method verejne.)"""
|
||||
parent = anode # chceme začít už od konkrétního node včetně
|
||||
while True:
|
||||
rocnik = isinstance(parent, s.RocnikNode)
|
||||
cislo = isinstance(parent, s.CisloNode)
|
||||
uloha = (isinstance(parent, s.UlohaVzorakNode) or
|
||||
isinstance(parent, s.UlohaZadaniNode))
|
||||
tema = isinstance(parent, s.TemaVCisleNode)
|
||||
|
||||
if (rocnik or cislo or uloha or tema) or parent==None:
|
||||
break
|
||||
else:
|
||||
parent = treelib.get_parent(parent)
|
||||
if rocnik:
|
||||
return parent.rocnik.verejne()
|
||||
elif cislo:
|
||||
return parent.cislo.verejne()
|
||||
elif uloha:
|
||||
return parent.uloha.verejne()
|
||||
elif tema:
|
||||
return parent.tema.verejne()
|
||||
elif None:
|
||||
print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou"
|
||||
"ani tématem. {}".format(anode))
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def all_public_children(cls, anode):
|
||||
for ch in treelib.all_children(anode):
|
||||
if TNLData.public_above(ch):
|
||||
yield ch
|
||||
else:
|
||||
continue
|
||||
|
||||
@classmethod
|
||||
def from_treenode(cls, anode, user, parent=None, index=None):
|
||||
if TNLData.public_above(anode) or user.has_perm('auth.org'):
|
||||
out = cls(anode,parent,index)
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
|
||||
if user.has_perm('auth.org'):
|
||||
enum_children = enumerate(treelib.all_children(anode))
|
||||
else:
|
||||
enum_children = enumerate(TNLData.all_public_children(anode))
|
||||
|
||||
for (idx,ch) in enum_children:
|
||||
outitem = cls.from_treenode(ch, user, out, idx)
|
||||
out.children.append(outitem)
|
||||
out.add_edit_options()
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def from_tnldata_list(cls, tnllist):
|
||||
"""Vyrobíme virtuální TNL, který nemá obsah, ale má za potomky všechna zadaná TNLData"""
|
||||
result = cls(None)
|
||||
for idx, tnl in enumerate(tnllist):
|
||||
result.children.append(tnl)
|
||||
tnl.parent = result
|
||||
tnl.index = idx
|
||||
result.add_edit_options()
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def filter_treenode(cls, treenode, predicate):
|
||||
tnll = cls._filter_treenode_recursive(treenode, predicate) # TreeNodeList List :-)
|
||||
return TNLData.from_tnldata_list(tnll)
|
||||
|
||||
@classmethod
|
||||
def _filter_treenode_recursive(cls, treenode, predicate):
|
||||
if predicate(treenode):
|
||||
return [cls.from_treenode(treenode)]
|
||||
else:
|
||||
found = []
|
||||
for tn in treelib.all_children(treenode):
|
||||
result = cls.filter_treenode(tn, predicate)
|
||||
# Result by v tuhle chvíli měl být seznam TNLDat odpovídající treenodům, jež matchnuly predikát.
|
||||
for tnl in result:
|
||||
found.append(tnl)
|
||||
return found
|
||||
|
||||
def to_json(self):
|
||||
#self.node = anode
|
||||
#self.children = []
|
||||
#self.parent = parent
|
||||
#self.tema_in_path = False
|
||||
#self.index = index
|
||||
out = {}
|
||||
out['node'] = self.sernode.data
|
||||
out['children'] = [n.to_json() for n in self.children]
|
||||
out['tema_in_path'] = self.tema_in_path
|
||||
out['index'] = self.index
|
||||
out['deletable'] = self.deletable
|
||||
out['editable_siblings'] = self.editable_siblings
|
||||
out['editable_children'] = self.editable_children
|
||||
out['text_only_subtree'] = self.text_only_subtree
|
||||
out['can_podvesit_za'] = self.can_podvesit_za
|
||||
out['can_podvesit_pod'] = self.can_podvesit_pred
|
||||
out['appendable_children'] = self.appendable_children
|
||||
out['appendable_siblings'] = self.appendable_siblings
|
||||
|
||||
return out
|
||||
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return("TNL({})".format(self.node))
|
||||
|
||||
|
||||
class TreeNodeView(generic.DetailView):
|
||||
model = s.TreeNode
|
||||
template_name = 'treenode/treenode.html'
|
||||
|
||||
def get_context_data(self,**kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['tnldata'] = TNLData.from_treenode(self.object,self.request.user)
|
||||
return context
|
||||
|
||||
|
||||
class TreeNodeJSONView(generic.DetailView):
|
||||
model = s.TreeNode
|
||||
|
||||
def get(self,request,*args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
data = TNLData.from_treenode(self.object,self.request.user).to_json()
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
class TreeNodePridatView(generic.View):
|
||||
type_from_str = {
|
||||
'rocnikNode': m.RocnikNode,
|
||||
'cisloNode': m.CisloNode,
|
||||
'castNode': m.CastNode,
|
||||
'textNode': m.TextNode,
|
||||
'temaVCisleNode': m.TemaVCisleNode,
|
||||
'reseniNode': m.ReseniNode,
|
||||
'ulohaZadaniNode': m.UlohaZadaniNode,
|
||||
'ulohaVzorakNode': m.UlohaVzorakNode,
|
||||
'pohadkaNode': m.PohadkaNode,
|
||||
'orgText': m.OrgTextNode,
|
||||
}
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
######## FIXME: ROZEPSANE, NEFUNGUJE, DOPSAT !!!!!! ###########
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
kam = self.kwargs['kam']
|
||||
co = self.kwargs['co']
|
||||
typ = self.type_from_str[co]
|
||||
|
||||
raise NotImplementedError('Neni to dopsane, dopis to!')
|
||||
|
||||
if kam not in ('pred','syn','za'):
|
||||
raise ValidationError('Přidat lze pouze před nebo za node nebo jako syna')
|
||||
|
||||
if co == m.TextNode:
|
||||
new_obj = m.Text()
|
||||
new_obj.save()
|
||||
elif co == m.CastNode:
|
||||
new_obj = m.CastNode()
|
||||
new_obj.nadpis = request.POST.get('pridat-castNode-{}-{}'.format(node.id,kam))
|
||||
new_obj.save()
|
||||
elif co == m.ReseniNode:
|
||||
new_obj = m
|
||||
pass
|
||||
elif co == m.UlohaZadaniNode:
|
||||
pass
|
||||
elif co == m.UlohaReseniNode:
|
||||
pass
|
||||
else:
|
||||
new_obj = None
|
||||
|
||||
|
||||
if kam == 'pred':
|
||||
pass
|
||||
|
||||
|
||||
if kam == 'syn':
|
||||
if typ == m.TextNode:
|
||||
text_obj = m.Text()
|
||||
text_obj.save()
|
||||
node = treelib.create_child(node, typ, text=text_obj)
|
||||
else:
|
||||
node = treelib.create_child(node, typ)
|
||||
if kam == 'za':
|
||||
if typ == m.TextNode:
|
||||
text_obj = m.Text()
|
||||
text_obj.save()
|
||||
node = treelib.create_node_after(node, typ, text=text_obj)
|
||||
else:
|
||||
node = treelib.create_node_after(node, typ)
|
||||
|
||||
return redirect(node.get_admin_url())
|
||||
|
||||
|
||||
class TreeNodeSmazatView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
if node.first_child:
|
||||
raise NotImplementedError('Mazání TreeNode se syny není zatím podporováno!')
|
||||
treelib.disconnect_node(node)
|
||||
node.delete()
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
|
||||
class TreeNodeOdvesitPrycView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
treelib.disconnect_node(node)
|
||||
node.root = None
|
||||
node.save()
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
|
||||
class TreeNodePodvesitView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
kam = self.kwargs['kam']
|
||||
if kam == 'pred':
|
||||
treelib.lower_node(node)
|
||||
elif kam == 'za':
|
||||
raise NotImplementedError('Podvěsit za není zatím podporováno')
|
||||
return redirect(request.headers.get('referer'))
|
||||
|
||||
|
||||
class TreeNodeProhoditView(generic.base.View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
node = s.TreeNode.objects.get(pk=self.kwargs['pk'])
|
||||
treelib.swap_succ(node)
|
||||
return redirect(request.headers.get('referer'))
|
||||
#FIXME ve formulari predat puvodni url a vratit redirect na ni
|
||||
|
||||
class SirotcinecView(generic.ListView):
|
||||
model = s.TreeNode
|
||||
template_name = 'treenode/orphanage.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return s.TreeNode.objects.not_instance_of(s.RocnikNode).filter(root=None,prev=None,succ=None,father_of_first=None)
|
||||
|
||||
# FIXME pouzit Django REST Framework
|
||||
class TextWebView(generic.DetailView):
|
||||
model = s.Text
|
||||
|
||||
def get(self,request,*args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
return JsonResponse(model_to_dict(self.object,exclude='do_cisla'))
|
||||
|
||||
|
||||
class VueTestView(generic.TemplateView):
|
||||
template_name = 'treenode/vuetest.html'
|
||||
|
||||
|
||||
class NahrajObrazekKTreeNoduView(LoginRequiredMixin, CreateView):
|
||||
model = s.Obrazek
|
||||
form_class = f.NahrajObrazekKTreeNoduForm
|
||||
|
||||
def get_initial(self):
|
||||
initial = super().get_initial()
|
||||
initial['na_web'] = self.request.FILES['upload']
|
||||
return initial
|
||||
|
||||
|
||||
def form_valid(self,form):
|
||||
print(self.request.headers)
|
||||
print(self.request.headers['Textid'])
|
||||
print(form.instance)
|
||||
print(form)
|
||||
self.object = form.save(commit=False)
|
||||
print(self.object.na_web)
|
||||
self.object.text = m.Text.objects.get(pk=int(self.request.headers['Textid']))
|
||||
self.object.save()
|
||||
|
||||
return JsonResponse({"url":self.object.na_web.url})
|
|
@ -3,10 +3,10 @@ from rest_framework import status
|
|||
from rest_framework.response import Response
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import BasePermission, AllowAny
|
||||
from . import models as m
|
||||
from . import views
|
||||
from seminar import models as m
|
||||
import treenode.serializers as views
|
||||
|
||||
from seminar.permissions import AllowWrite
|
||||
from treenode.permissions import AllowWrite
|
||||
|
||||
class PermissionMixin(object):
|
||||
""" Redefines get_permissions so that only organizers can make changes. """
|
3
vysledkovky/__init__.py
Normal file
3
vysledkovky/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Obsahuje výsledkovky a vše, co se týká sčítání bodů.
|
||||
"""
|
5
vysledkovky/apps.py
Normal file
5
vysledkovky/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class VysledkovkyConfig(AppConfig):
|
||||
name = 'vysledkovky'
|
0
vysledkovky/migrations/__init__.py
Normal file
0
vysledkovky/migrations/__init__.py
Normal file
66
vysledkovky/templates/vysledkovky/vysledkovka_cisla.html
Normal file
66
vysledkovky/templates/vysledkovky/vysledkovka_cisla.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
<table class='vysledkovka'>
|
||||
<tr class='border-b'>
|
||||
<th class='border-r'>#
|
||||
<th class='border-r'>Jméno
|
||||
{% for p in problemy %}
|
||||
<th class='border-r' id="problem{{ forloop.counter }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #}
|
||||
|
||||
{# TODELETE #}
|
||||
{% for podproblemy in podproblemy_iter.next %}
|
||||
<th class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
|
||||
{% endfor %}
|
||||
{# TODELETE #}
|
||||
|
||||
{% endfor %}
|
||||
{% if ostatni %}<th class='border-r'>Ostatní {% endif %}
|
||||
|
||||
{# TODELETE #}
|
||||
{% for podproblemy in podproblemy_iter.next %}
|
||||
<th class='border-r podproblem{{ problemy.len }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}
|
||||
{% endfor %}
|
||||
{# TODELETE #}
|
||||
|
||||
|
||||
<th class='border-r'>Za číslo
|
||||
<th class='border-r'>Za ročník
|
||||
<th class='border-r'>Odjakživa
|
||||
{% for rv in radky_vysledkovky %}
|
||||
<tr>
|
||||
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
|
||||
<th class='border-r'>
|
||||
{% if rv.titul %}
|
||||
{{ rv.titul }}<sup>MM</sup>
|
||||
{% endif %}
|
||||
{{ rv.resitel.osoba.plne_jmeno }}
|
||||
{% for b in rv.body_problemy_sezn %}
|
||||
<td class='border-r'>{{ b }}
|
||||
|
||||
{# TODELETE #}
|
||||
{% for body_podproblemu in rv.body_podproblemy_iter.next %}
|
||||
<td class='border-r podproblem{{ forloop.parentloop.counter }} podproblem'>{{ body_podproblemu }}
|
||||
{% endfor %}
|
||||
{# TODELETE #}
|
||||
|
||||
{% endfor %}
|
||||
<td class='border-r'>{{ rv.body_cislo }}
|
||||
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
|
||||
<td class='border-r'>{{ rv.body_celkem_odjakziva }}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{# TODELETE #}
|
||||
<script>
|
||||
{% for p in problemy %}
|
||||
$(".podproblem{{ forloop.counter }}").css("display", "none")
|
||||
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseover', podproblem{{ forloop.counter }})
|
||||
$("#problem{{ forloop.counter }}")[0].addEventListener('mouseout', podproblem{{ forloop.counter }}end)
|
||||
function podproblem{{ forloop.counter }}(event) {
|
||||
$(".podproblem{{ forloop.counter }}").css("display", "")
|
||||
}
|
||||
function podproblem{{ forloop.counter }}end(event) {
|
||||
$(".podproblem{{ forloop.counter }}").css("display", "none")
|
||||
}
|
||||
{% endfor %}
|
||||
</script>
|
||||
{# TODELETE #}
|
|
@ -0,0 +1 @@
|
|||
{% include "vysledkovky/vysledkovka_rocnik.html" with radky_vysledkovky=radky_vysledkovky_s_neverejnymi cisla=cisla_s_neverejnymi %}
|
|
@ -2,7 +2,6 @@ import seminar.models as m
|
|||
from django.db.models import Q, Sum, Count
|
||||
from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni_problem, hlavni_problemy_f, problemy_cisla, podproblemy_v_cislu
|
||||
import time
|
||||
### Výsledky
|
||||
|
||||
ROCNIK_ZRUSENI_TEMAT = 25
|
||||
|
||||
|
@ -142,7 +141,7 @@ def setrid_resitele_a_body(slov_resitel_body):
|
|||
setrizene_body = [dvojice[1] for dvojice in slov_resitel_body]
|
||||
return setrizeni_resitele_id, setrizene_body
|
||||
|
||||
def vysledkovka_rocniku(rocnik, jen_verejne=True):
|
||||
def data_vysledkovky_rocniku(rocnik, jen_verejne=True):
|
||||
""" Přebírá ročník (např. context["rocnik"]) a vrací výsledkovou listinu ve
|
||||
formě vhodné pro šablonu "seminar/vysledkovka_rocniku.html"
|
||||
"""
|
||||
|
@ -197,7 +196,7 @@ def vysledkovka_rocniku(rocnik, jen_verejne=True):
|
|||
end = time.time()
|
||||
print("Vysledkovka rocniku",end-start)
|
||||
|
||||
return radky_vysledkovky
|
||||
return radky_vysledkovky, cisla
|
||||
|
||||
class RadekVysledkovkyCisla(object):
|
||||
"""Obsahuje věci, které se hodí vědět při konstruování výsledkovky.
|
||||
|
@ -372,9 +371,7 @@ class FixedIterator:
|
|||
# TODELETE
|
||||
|
||||
|
||||
def vysledkovka_cisla(cislo, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
def data_vysledkovky_cisla(cislo):
|
||||
problemy = problemy_cisla(cislo)
|
||||
hlavni_problemy = hlavni_problemy_f(problemy)
|
||||
## TODO možná chytřeji vybírat aktivní řešitele
|
||||
|
@ -453,13 +450,11 @@ def vysledkovka_cisla(cislo, context=None):
|
|||
i += 1
|
||||
|
||||
# vytahané informace předáváme do kontextu
|
||||
context['cislo'] = cislo
|
||||
context['radky_vysledkovky'] = radky_vysledkovky
|
||||
context['problemy'] = temata_a_spol
|
||||
context['ostatni'] = je_nejake_ostatni
|
||||
pt = [podproblemy[it.id] for it in temata_a_spol]+[podproblemy[-1]]
|
||||
context['podproblemy'] = pt
|
||||
context['podproblemy_iter'] = FixedIterator(pt.__iter__()) # TODELETE
|
||||
#context['v_cisle_zadane'] = TODO
|
||||
#context['resene_problemy'] = resene_problemy
|
||||
return context
|
||||
return (
|
||||
radky_vysledkovky,
|
||||
temata_a_spol,
|
||||
je_nejake_ostatni,
|
||||
pt,
|
||||
FixedIterator(pt.__iter__())
|
||||
)
|
37
vysledkovky/views.py
Normal file
37
vysledkovky/views.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from .utils import data_vysledkovky_cisla, \
|
||||
data_vysledkovky_rocniku
|
||||
|
||||
|
||||
def vysledkovka_cisla(cislo, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
context['cislo'] = cislo
|
||||
|
||||
(
|
||||
context['radky_vysledkovky'],
|
||||
context['problemy'],
|
||||
context['ostatni'],
|
||||
context['podproblemy'],
|
||||
context['podproblemy_iter']
|
||||
) = data_vysledkovky_cisla(cislo)
|
||||
return context
|
||||
|
||||
|
||||
def vysledkovka_rocniku(rocnik, context=None, request=None, sneverejnou=False):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
(
|
||||
context['radky_vysledkovky'],
|
||||
context['cisla']
|
||||
) = data_vysledkovky_rocniku(rocnik)
|
||||
|
||||
context['vysledkovka'] = len(context['cisla']) != 0
|
||||
|
||||
if sneverejnou and request and request.user.je_org:
|
||||
(
|
||||
context['radky_vysledkovky_s_neverejnymi'],
|
||||
context['cisla_s_neverejnymi']
|
||||
) = data_vysledkovky_rocniku(rocnik, jen_verejne=False)
|
||||
|
||||
return context
|
Loading…
Reference in a new issue