Jonas Havelka
3 years ago
100 changed files with 3715 additions and 3517 deletions
@ -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? |
||||
|
""" |
@ -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) |
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class OdevzdavatkoConfig(AppConfig): |
||||
|
name = 'odevzdavatko' |
@ -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) |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 717 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -1,7 +1,7 @@ |
|||||
{% extends "base.html" %} |
{% extends "base.html" %} |
||||
{% load staticfiles %} |
{% load staticfiles %} |
||||
{% block script %} |
{% block script %} |
||||
<script src="{% static 'seminar/dynamic_formsets.js' %}"></script> |
<script src="{% static 'odevzdavatko/dynamic_formsets.js' %}"></script> |
||||
{% endblock %} |
{% endblock %} |
||||
|
|
||||
{% block content %} |
{% block content %} |
@ -0,0 +1,20 @@ |
|||||
|
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())), |
||||
|
|
||||
|
path('resitel/reseni/<int:pk>', resitel_or_org_required(views.ResitelReseniView.as_view()), name='odevzdavatko_resitel_reseni'), |
||||
|
] |
@ -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. |
||||
|
""" |
@ -0,0 +1,50 @@ |
|||||
|
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" |
||||
|
|
||||
|
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.site.register(m.Skola) |
||||
|
admin.site.register(m.Prijemce) |
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class PersonalniConfig(AppConfig): |
||||
|
name = 'personalni' |
@ -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 %} |
@ -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 %} |
@ -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'), |
||||
|
|
||||
|
] |
@ -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) |
@ -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) |
|
||||
|
|
||||
|
|
File diff suppressed because it is too large
@ -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 * |
@ -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 |
||||
|
|
@ -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) + '] ' |
@ -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) |
||||
|
|
@ -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'] |
@ -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 |
@ -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 |
@ -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) |
@ -0,0 +1,650 @@ |
|||||
|
# -*- 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 neverejna_cisla(self): |
||||
|
vc = [c for c in self.cisla.all() if not c.verejne()] |
||||
|
vc.sort(key=lambda c: c.poradi) |
||||
|
return vc |
||||
|
|
||||
|
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 |
@ -1,97 +0,0 @@ |
|||||
{% extends "seminar/archiv/base_cisla.html" %} |
|
||||
|
|
||||
{# {% block content %} |
|
||||
<div> |
|
||||
|
|
||||
<h1> |
|
||||
{% block nadpis1a %}{% block nadpis1b %} |
|
||||
Číslo {{ cislo }} |
|
||||
{% endblock %}{% endblock %} |
|
||||
</h1> |
|
||||
|
|
||||
{% if cislo.pdf %} |
|
||||
<p><a href='{{ cislo.pdf.url }}'>Číslo v pdf</a> |
|
||||
{% endif %} |
|
||||
<p><a href='{{ cislo.rocnik.verejne_url }}'>Ročník {{ cislo.rocnik }}</a> |
|
||||
|
|
||||
{% if v_cisle_zadane %} |
|
||||
<h2>Zadané problémy</h2> |
|
||||
<ul> |
|
||||
{% for p in v_cisle_zadane %} |
|
||||
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}> |
|
||||
{% if user.is_staff or cislo.verejne %} |
|
||||
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %} |
|
||||
{% endfor %} |
|
||||
</ul> |
|
||||
{% endif %} |
|
||||
|
|
||||
{% if resene_problemy %} |
|
||||
<h2>Řešené problémy</h2> |
|
||||
<ul> |
|
||||
{% for p in resene_problemy %} |
|
||||
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}> |
|
||||
{% if user.is_staff or cislo.verejne %} |
|
||||
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %} |
|
||||
{% endfor %} |
|
||||
</ul> |
|
||||
{% endif %} |
|
||||
|
|
||||
{% if user.is_staff %} |
|
||||
<div class="mam-org-only"> |
|
||||
<h2> Orgovské odkazy </h2> |
|
||||
<ul> |
|
||||
<li><a href="obalky.pdf">Obálky (PDF)</a></li> |
|
||||
<li><a href="tituly.tex">Tituly (TeX)</a></li> |
|
||||
<li><a href="vysledkovka.tex">Výsledkovka (TeX)</a></li> |
|
||||
<li><a href="obalkovani">Obálkování</a></li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
{% endif %} |
|
||||
|
|
||||
{% if cislo.verejna_vysledkovka %} |
|
||||
<h2>Výsledkovka</h2> |
|
||||
{% else %} |
|
||||
{% if user.is_staff %} |
|
||||
<div class='mam-org-only'> |
|
||||
<h2>Výsledkovka (neveřejná)</h2> |
|
||||
{% endif %} |
|
||||
{% endif %} |
|
||||
|
|
||||
{% if cislo.verejna_vysledkovka or user.is_staff %} |
|
||||
<table class='vysledkovka'> |
|
||||
<tr class='border-b'> |
|
||||
<th class='border-r'># |
|
||||
<th class='border-r'>Jméno #} |
|
||||
{# problémy by měly být veřejné, když je veřejná výsledkovka #} |
|
||||
{# {% for p in problemy %} |
|
||||
<th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a> |
|
||||
{% endfor %} |
|
||||
<th class='border-r'>Za číslo</sup> |
|
||||
<th class='border-r'>Za ročník |
|
||||
<th class='border-r'>Odjakživa |
|
||||
{% for rv in radky_vysledkovky %} |
|
||||
<tr> |
|
||||
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %} |
|
||||
<th class='border-r'> |
|
||||
{% if rv.resitel.titul != "" %} |
|
||||
{{ rv.resitel.titul }}<sup>MM</sup> |
|
||||
{% endif %} |
|
||||
{{ rv.resitel.osoba.plne_jmeno }} |
|
||||
{% for b in rv.hlavni_problemy_body %} |
|
||||
<td class='border-r'>{{ b }} |
|
||||
{% endfor %} |
|
||||
<td class='border-r'>{{ rv.body_cislo }} |
|
||||
<td class='border-r'><b>{{ rv.body_rocnik }}</b> |
|
||||
<td class='border-r'>{{ rv.body_celkem_odjakziva }} |
|
||||
</tr> |
|
||||
{% endfor %} |
|
||||
</table> |
|
||||
{% endif %} |
|
||||
|
|
||||
{% if not cislo.verejna_vysledkovka and user.is_staff %} |
|
||||
</div> |
|
||||
{% endif %} |
|
||||
|
|
||||
</div> |
|
||||
{% endblock content %} #} |
|
||||
|
|
@ -1,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 %} |
|
@ -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() |
|
@ -1,6 +1,4 @@ |
|||||
from .views_all import * |
from .views_all import * |
||||
from .views_rest import * |
|
||||
from .odevzdavatko import * |
|
||||
|
|
||||
# Dočsasné views |
# Dočsasné views |
||||
from .docasne import * |
from .docasne import * |
||||
|
@ -0,0 +1,5 @@ |
|||||
|
""" |
||||
|
Obsahuje vše (až na přednášky) ohledně soustředění. |
||||
|
|
||||
|
TODO stvrzenky? |
||||
|
""" |
@ -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] |
||||
|
|
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class SoustredeniConfig(AppConfig): |
||||
|
name = 'soustredeni' |
@ -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') |
||||
|
), |
||||
|
] |
@ -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,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 |
||||
|
|
||||
|
|
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class TreenodeConfig(AppConfig): |
||||
|
name = 'treenode' |
@ -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',) |
@ -1,5 +1,5 @@ |
|||||
from rest_framework import routers |
from rest_framework import routers |
||||
from seminar import viewsets as vs |
from treenode import viewsets as vs |
||||
|
|
||||
router = routers.DefaultRouter() |
router = routers.DefaultRouter() |
||||
|
|
@ -1,5 +1,5 @@ |
|||||
from django.test import TestCase |
from django.test import TestCase |
||||
import seminar.treelib as tl |
import treenode.treelib as tl |
||||
import seminar.models as m |
import seminar.models as m |
||||
|
|
||||
class SimpleTreeLibTests(TestCase): |
class SimpleTreeLibTests(TestCase): |
@ -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()), |
||||
|
] |
@ -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}) |
@ -0,0 +1,3 @@ |
|||||
|
""" |
||||
|
Obsahuje výsledkovky a vše, co se týká sčítání bodů. |
||||
|
""" |
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class VysledkovkyConfig(AppConfig): |
||||
|
name = 'vysledkovky' |
@ -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 %} |
@ -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 new issue