Web M&M
https://mam.matfyz.cz
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
16 KiB
428 lines
16 KiB
from django import forms
|
|
from dal import autocomplete
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.contrib.auth.models import User
|
|
from django.forms import formset_factory
|
|
from django.forms.models import inlineformset_factory
|
|
|
|
from .models import Skola, Resitel, Osoba, Problem
|
|
import seminar.models as m
|
|
|
|
from datetime import date
|
|
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 TelInput(forms.TextInput):
|
|
# tohle je možná k niřemu, ale alepsoň to mění input type a nic to nekazí
|
|
input_type = 'tel'
|
|
input_pattern="^[+]?[()/0-9. -]{9,}$"
|
|
|
|
class LoginForm(forms.Form):
|
|
username = forms.CharField(label='Přihlašovací jméno',
|
|
max_length=256,
|
|
required=True)
|
|
password = forms.CharField(
|
|
label='Heslo',
|
|
max_length=256,
|
|
required=True,
|
|
widget=forms.PasswordInput())
|
|
|
|
|
|
class PrihlaskaForm(forms.Form):
|
|
username = forms.CharField(label='Přihlašovací jméno',
|
|
max_length=256,
|
|
required=True,
|
|
help_text='Tímto jménem se následně budeš přihlašovat pro odevzdání řešení a další činnosti v semináři')
|
|
password = forms.CharField(
|
|
label='Heslo',
|
|
max_length=256,
|
|
required=True,
|
|
widget=forms.PasswordInput())
|
|
password_check = forms.CharField(
|
|
label='Ověření hesla',
|
|
max_length=256,
|
|
required=True,
|
|
widget=forms.PasswordInput())
|
|
|
|
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
|
|
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
|
|
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
|
choices = ((True,'muž'),(False,'žena')), required=True)
|
|
email = forms.EmailField(label='E-mail',max_length=256, required=True)
|
|
telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False)
|
|
datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False)
|
|
ulice = forms.CharField(label='Ulice', max_length=256, required=False)
|
|
mesto = forms.CharField(label='Město', max_length=256, required=False)
|
|
psc = forms.CharField(label='PSČ', max_length=32, required=False)
|
|
stat = forms.ChoiceField(label='Stát',
|
|
choices = (('CZ', 'Česká Republika'),
|
|
('SK', 'Slovenská Republika'),
|
|
('other', 'Jiné')),
|
|
required=False)
|
|
stat_text = forms.CharField(label='Stát', max_length=256, required=False)
|
|
|
|
skola = forms.ModelChoiceField(label="Škola",
|
|
queryset=Skola.objects.all(),
|
|
widget=autocomplete.ModelSelect2(
|
|
url='autocomplete_skola',
|
|
attrs = {'data-placeholder--id': '-1',
|
|
'data-placeholder--text' : '---',
|
|
'data-allow-clear': 'true'})
|
|
,required=False)
|
|
|
|
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
|
|
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
|
|
|
|
# trida = forms.CharField(label='Třída',max_length=10, required=True)
|
|
|
|
rok_maturity = forms.IntegerField(
|
|
label='Rok maturity',
|
|
min_value=date.today().year,
|
|
max_value=date.today().year+8,
|
|
required=True)
|
|
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
|
|
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat emailem upozornění na vydání nového čísla', required=True)
|
|
|
|
gdpr = forms.BooleanField(label='Souhlasím se zpracováním osobních údajů', required=True)
|
|
spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False)
|
|
|
|
def clean_username(self):
|
|
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
|
username = self.cleaned_data.get('username')
|
|
try:
|
|
User.objects.get(username=username)
|
|
msg = "Username {} exists".format(username)
|
|
err_logger.info(msg)
|
|
raise forms.ValidationError('Přihlašovací jméno je již použito')
|
|
|
|
except ObjectDoesNotExist:
|
|
pass
|
|
return username
|
|
|
|
def clean_email(self):
|
|
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
|
email = self.cleaned_data.get('email')
|
|
try:
|
|
Osoba.objects.get(email=email)
|
|
msg = "Email {} exists".format(email)
|
|
err_logger.info(msg)
|
|
raise forms.ValidationError('Email je již použit')
|
|
|
|
except ObjectDoesNotExist:
|
|
pass
|
|
return email
|
|
|
|
|
|
def clean(self):
|
|
super().clean()
|
|
|
|
err_logger = logging.getLogger('seminar.prihlaska.problem')
|
|
|
|
data = self.cleaned_data
|
|
if data.get('password') != data.get('password_check'):
|
|
self.add_error('password_check',forms.ValidationError('Hesla se neshodují'))
|
|
if data.get('stat') != '' and data.get('stat_text') != '':
|
|
self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem'))
|
|
if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')):
|
|
self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)'))
|
|
if not data.get('skola'):
|
|
if data.get('skola_nazev')=='' and data.get('skola_adresa')=='':
|
|
self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu'))
|
|
elif data.get('skola_nazev')=='':
|
|
self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy'))
|
|
elif data.get('skola_adresa')=='':
|
|
self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
|
|
|
|
|
|
class ProfileEditForm(forms.Form):
|
|
username = forms.CharField(label='Přihlašovací jméno',
|
|
max_length=256,
|
|
required=True)
|
|
|
|
jmeno = forms.CharField(label='Jméno', max_length=256, required=True)
|
|
prijmeni = forms.CharField(label='Příjmení', max_length=256, required=True)
|
|
pohlavi_muz = forms.ChoiceField(label='Pohlaví',
|
|
choices = ((True,'muž'),(False,'žena')), required=True)
|
|
email = forms.EmailField(label='E-mail',max_length=256, required=True)
|
|
telefon = forms.CharField(widget=TelInput(),label='Telefon',max_length=256, required=False)
|
|
datum_narozeni = forms.DateField(widget=DateInput(),label='Datum narození', required=False)
|
|
ulice = forms.CharField(label='Ulice', max_length=256, required=False)
|
|
mesto = forms.CharField(label='Město', max_length=256, required=False)
|
|
psc = forms.CharField(label='PSČ', max_length=32, required=False)
|
|
stat = forms.ChoiceField(label='Stát',
|
|
choices = (('CZ', 'Česká Republika'),
|
|
('SK', 'Slovenská Republika'),
|
|
('other', 'Jiné')),
|
|
required=False)
|
|
stat_text = forms.CharField(label='Stát', max_length=256, required=False)
|
|
|
|
skola = forms.ModelChoiceField(label="Škola",
|
|
queryset=Skola.objects.all(),
|
|
widget=autocomplete.ModelSelect2(
|
|
url='autocomplete_skola',
|
|
attrs = {'data-placeholder--id': '-1',
|
|
'data-placeholder--text' : '---',
|
|
'data-allow-clear': 'true'})
|
|
,required=False)
|
|
|
|
skola_nazev = forms.CharField(label='Název školy', max_length=256, required=False)
|
|
skola_adresa = forms.CharField(label='Adresa školy', max_length=256, required=False)
|
|
|
|
# trida = forms.CharField(label='Třída',max_length=10, required=True)
|
|
|
|
rok_maturity = forms.IntegerField(
|
|
label='Rok maturity',
|
|
min_value=date.today().year,
|
|
max_value=date.today().year+8,
|
|
required=True)
|
|
zasilat = forms.ChoiceField(label='Kam zasílat čísla a řešení',choices = Resitel.ZASILAT_CHOICES, required=True)
|
|
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat email s upozorněním na vydání nového čísla', required=False)
|
|
|
|
spam = forms.BooleanField(label='Souhlasím se zasíláním materiálů od MFF UK', required=False)
|
|
# def clean_username(self):
|
|
# err_logger = logging.getLogger('seminar.prihlaska.problem')
|
|
# username = self.cleaned_data.get('username')
|
|
# try:
|
|
# User.objects.get(username=username)
|
|
# msg = "Username {} exists".format(username)
|
|
# err_logger.info(msg)
|
|
# raise forms.ValidationError('Přihlašovací jméno je již použito')
|
|
#
|
|
# except ObjectDoesNotExist:
|
|
# pass
|
|
# return username
|
|
#
|
|
# def clean_email(self):
|
|
# err_logger = logging.getLogger('seminar.prihlaska.problem')
|
|
# email = self.cleaned_data.get('email')
|
|
# try:
|
|
# Osoba.objects.get(email=email)
|
|
# msg = "Email {} exists".format(email)
|
|
# err_logger.info(msg)
|
|
# raise forms.ValidationError('Email je již použit')
|
|
#
|
|
# except ObjectDoesNotExist:
|
|
# pass
|
|
# return email
|
|
#def clean(self):
|
|
# super().clean()
|
|
#
|
|
# err_logger = logging.getLogger('seminar.prihlaska.problem')
|
|
|
|
# data = self.cleaned_data
|
|
# if data.get('password') != data.get('password_check'):
|
|
# self.add_error('password_check',forms.ValidationError('Hesla se neshodují'))
|
|
# if data.get('stat') != '' and data.get('stat_text') != '':
|
|
# self.add_error('stat',forms.ValidationError('Nelze mít vybraný stát z menu a zároven zapsaný textem'))
|
|
# if data.get('skola') and (data.get('skola_nazev') or data.get('skola_adresa')):
|
|
# self.add_error('skola',forms.ValidationError('Pokud je škola v seznamu, nevypisujte ji ručně, pokud není, zrušte výběr ze seznamu (křížek vpravo)'))
|
|
# if not data.get('skola'):
|
|
# if data.get('skola_nazev')=='' and data.get('skola_adresa')=='':
|
|
# self.add_error('skola',forms.ValidationError('Je nutné vyplnit školu'))
|
|
# elif data.get('skola_nazev')=='':
|
|
# self.add_error('skola_nazev',forms.ValidationError('Je nutné vyplnit název školy'))
|
|
# elif data.get('skola_adresa')=='':
|
|
# self.add_error('skola_adresa',forms.ValidationError('Je nutné vyplnit adresu školy'))
|
|
|
|
class PoMaturiteProfileEditForm(ProfileEditForm):
|
|
rok_maturity = forms.IntegerField(
|
|
label='Rok maturity',
|
|
required=True)
|
|
|
|
class VlozReseniForm(forms.Form):
|
|
#FIXME jen podproblémy daného problému
|
|
problem = forms.ModelChoiceField(label='Problém',queryset=m.Problem.objects.all())
|
|
# to_field_name
|
|
#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
|
|
# through='Hodnoceni')
|
|
|
|
# FIXME pridat vice resitelu
|
|
resitel = forms.ModelChoiceField(label="Řešitel",
|
|
queryset=Resitel.objects.all(),
|
|
widget=autocomplete.ModelSelect2(
|
|
url='autocomplete_resitel',
|
|
attrs = {'data-placeholder--id': '-1',
|
|
'data-placeholder--text' : '---',
|
|
'data-allow-clear': 'true'})
|
|
)
|
|
|
|
|
|
#resitele = models.ManyToManyField(Resitel, verbose_name='autoři řešení',
|
|
# help_text='Seznam autorů řešení', through='Reseni_Resitele')
|
|
|
|
cas_doruceni = forms.DateField(widget=DateInput(),label="Čas doručení")
|
|
|
|
#cas_doruceni = models.DateTimeField('čas_doručení', default=timezone.now, blank=True)
|
|
|
|
forma = forms.ChoiceField(label="Forma řešení",choices = m.Reseni.FORMA_CHOICES)
|
|
#forma = models.CharField('forma řešení', max_length=16, choices=FORMA_CHOICES, blank=False,
|
|
# default=FORMA_EMAIL)
|
|
|
|
poznamka = forms.CharField(label='Neveřejná poznámka')
|
|
#poznamka = models.TextField('neveřejná poznámka', blank=True,
|
|
# help_text='Neveřejná poznámka k řešení (plain text)')
|
|
|
|
#TODO body do cisla
|
|
#TODO prilohy
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
#self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()])
|
|
|
|
class NahrajReseniForm(forms.ModelForm):
|
|
class Meta:
|
|
model = m.Reseni
|
|
fields = ('problem',)
|
|
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
|
|
|
|
widgets = {'problem':
|
|
autocomplete.ModelSelect2Multiple(
|
|
url='autocomplete_problem_odevzdatelny',
|
|
attrs = {'data-placeholder--id': '-1',
|
|
'data-placeholder--text' : '---',
|
|
'data-allow-clear': 'true'},
|
|
)
|
|
}
|
|
|
|
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
|
|
form = NahrajReseniForm,
|
|
fields = ('soubor','res_poznamka'),
|
|
widgets = {'res_poznamka':forms.TextInput()},
|
|
extra = 1,
|
|
can_delete = False,
|
|
|
|
)
|
|
|
|
class NahrajObrazekKTreeNoduForm(forms.ModelForm):
|
|
class Meta:
|
|
model = m.Obrazek
|
|
fields = ('na_web',)
|
|
|
|
|
|
class JednoHodnoceniForm(forms.ModelForm):
|
|
class Meta:
|
|
model = m.Hodnoceni
|
|
fields = ('problem', 'body', 'cislo_body')
|
|
widgets = {
|
|
'problem': autocomplete.ModelSelect2(
|
|
url='autocomplete_problem_odevzdatelny', # FIXME: Dovolit i starší?
|
|
)
|
|
}
|
|
|
|
OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
|
|
extra = 0,
|
|
)
|
|
|
|
# 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_LETOSNI = 'letosni'
|
|
RESITELE_CHOICES = [
|
|
(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky
|
|
(RESITELE_LETOSNI, 'Všichni letošní'),
|
|
# 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):
|
|
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')]
|
|
|
|
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):
|
|
terminy = cls.gen_terminy()
|
|
initial = {
|
|
'resitele': cls.RESITELE_RELEVANTNI,
|
|
'problemy': cls.PROBLEMY_MOJE,
|
|
'reseni_od': terminy[-2],
|
|
'reseni_do': terminy[-1],
|
|
}
|
|
return initial
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
if 'initial' not in kwargs:
|
|
super().__init__(initial=self.gen_initial(), *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()
|
|
self.fields['reseni_od'].widget = forms.Select(choices=self.gen_terminy())
|
|
self.fields['reseni_od'].initial = self.terminy[-2]
|
|
self.fields['reseni_do'].widget = forms.Select(choices=self.gen_terminy())
|
|
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])
|
|
|