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" %} |
|||
{% load staticfiles %} |
|||
{% block script %} |
|||
<script src="{% static 'seminar/dynamic_formsets.js' %}"></script> |
|||
<script src="{% static 'odevzdavatko/dynamic_formsets.js' %}"></script> |
|||
{% endblock %} |
|||
|
|||
{% 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_rest import * |
|||
from .odevzdavatko import * |
|||
|
|||
# Dočsasné views |
|||
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 seminar import viewsets as vs |
|||
from treenode import viewsets as vs |
|||
|
|||
router = routers.DefaultRouter() |
|||
|
@ -1,5 +1,5 @@ |
|||
from django.test import TestCase |
|||
import seminar.treelib as tl |
|||
import treenode.treelib as tl |
|||
import seminar.models as m |
|||
|
|||
class SimpleTreeLibTests(TestCase): |
@ -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