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.

437 lines
17 KiB

from django import forms
from dal import autocomplete
from django.contrib.auth.forms import PasswordResetForm
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User
from django.forms import formset_factory
from django.forms.models import inlineformset_factory
from .models import Skola, Resitel, Osoba, Problem
import seminar.models as m
from 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(PasswordResetForm):
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')
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)
3 years ago
ulice = forms.CharField(label='Ulice a číslo popisné', 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)
3 years ago
zasilat_cislo_emailem = forms.BooleanField(label='Chci dostávat e-mailem upozornění na vydání nového čísla', required=False)
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 = Osoba.objects.get(email=email)
msg = "Email {} exists".format(email)
if osoba.user is not None:
err_logger.info(msg)
raise forms.ValidationError('E-mail je již použit')
else:
msg += ', but currently has no User, so allowing registration.'
err_logger.info(msg)
except ObjectDoesNotExist:
pass
return email
def clean(self):
super().clean()
err_logger = logging.getLogger('seminar.prihlaska.problem')
data = self.cleaned_data
if data.get('stat') != 'other' 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,
3 years ago
required=False,
disabled=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.exclude(user__username=self.username).get(email=email)
msg = "Email {} exists (in edit)".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', required=False)
#poznamka = models.TextField('neveřejná poznámka', blank=True,
# help_text='Neveřejná poznámka k řešení (plain text)')
#TODO body do cisla
#TODO prilohy
##def __init__(self, *args, **kwargs):
## super().__init__(*args, **kwargs)
## #self.fields['favorite_color'] = forms.ChoiceField(choices=[(color.id, color.name) for color in Resitel.objects.all()])
class NahrajReseniForm(forms.ModelForm):
class Meta:
model = m.Reseni
fields = ('problem',)
help_texts = {'problem':''} # Nezobrazovat help text ve formuláři
widgets = {'problem':
autocomplete.ModelSelect2Multiple(
url='autocomplete_problem_odevzdatelny',
attrs = {'data-placeholder--id': '-1',
'data-placeholder--text' : '---',
'data-allow-clear': 'true'},
)
}
ReseniSPrilohamiFormSet = inlineformset_factory(m.Reseni,m.PrilohaReseni,
form = NahrajReseniForm,
fields = ('soubor','res_poznamka'),
widgets = {'res_poznamka':forms.TextInput()},
extra = 1,
can_delete = False,
)
class NahrajObrazekKTreeNoduForm(forms.ModelForm):
class Meta:
model = m.Obrazek
fields = ('na_web',)
class JednoHodnoceniForm(forms.ModelForm):
class Meta:
model = m.Hodnoceni
fields = ('problem', 'body', 'cislo_body')
widgets = {
'problem': autocomplete.ModelSelect2(
url='autocomplete_problem_odevzdatelny', # FIXME: Dovolit i starší?
)
}
OhodnoceniReseniFormSet = formset_factory(JednoHodnoceniForm,
extra = 0,
)
class PoznamkaReseniForm(forms.ModelForm):
class Meta:
model = m.Reseni
fields = ('poznamka',)
# FIXME: Ideálně by mělo být součástí třídy níž, ale neumím to udělat
DATE_FORMAT = '%Y-%m-%d'
class OdevzdavatkoTabulkaFiltrForm(forms.Form):
"""Form pro filtrování přehledové odevzdávátkové tabulky
Inspirováno https://kam.mff.cuni.cz/mffzoom/"""
# Věci definované níž se importují i ve views pro odevzdávátko (Inspirováno https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-choices)
RESITELE_RELEVANTNI = 'relevantni'
RESITELE_NEODMATUROVAVSI = 'neodmaturovavsi'
RESITELE_CHOICES = [
(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky
(RESITELE_NEODMATUROVAVSI, 'Všichni bez maturity'),
# Možná: všechny vč. historických?
]
PROBLEMY_MOJE = 'moje'
PROBLEMY_LETOSNI = 'letosni'
PROBLEMY_CHOICES = [
(PROBLEMY_MOJE, 'Moje problémy'), # Letošní problémy, které mají v sobě nebo v nadproblémech přiřazeného daného orga
(PROBLEMY_LETOSNI, 'Všechny letošní'),
# TODO: *hlavní problémy, možná všechny...
# XXX: Chtělo by to i "aktuálně zadané...
]
# TODO: Typy problémů (problémy, úlohy, ostatní, všechny)? Jen některá řešení (obodovaná/neobodovaná, víc řešitelů, ...)?
@classmethod
def gen_terminy(cls, rocnik=None):
import datetime
from time import strftime
from django.db.utils import OperationalError
try:
aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik
aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo
except OperationalError:
# django.db.utils.OperationalError: no such table: seminar_nastaveni
# Nemáme databázi, takže to selhalo. Pro jistotu vrátíme aspoň dvě možnosti, ať to nepadá dál
logger = logging.getLogger(__name__)
logger.error("Rozbitá databáze (před počátečními migracemi?)")
return [('broken', 'Je to rozbitý'), ('fubar', 'Nefunguje to')]
# FIXME: Tohle je hnusný monkey patch, mělo by to být nějak zahrnuto výš.
if rocnik is not None:
aktualni_rocnik = rocnik
aktualni_cislo = m.Cislo.objects.filter(rocnik=rocnik).order_by('poradi').last()
result = []
for cislo in m.Cislo.objects.filter(
rocnik=aktualni_rocnik,
poradi__lte=aktualni_cislo.poradi,
).reverse(): # Standardně se řadí od nejnovějšího čísla
# Předem je mi líto kohokoliv, kdo tyhle řádky bude číst...
if cislo.datum_vydani is not None and cislo.datum_vydani <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_vydani.timetuple()),
f"Vydání {cislo.poradi}. čísla"))
if cislo.datum_preddeadline is not None and cislo.datum_preddeadline <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_preddeadline.timetuple()),
f"Předdeadline {cislo.poradi}. čísla"))
if cislo.datum_deadline_soustredeni is not None and cislo.datum_deadline_soustredeni <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_deadline_soustredeni.timetuple()),
f"Sous. deadline {cislo.poradi}. čísla"))
if cislo.datum_deadline is not None and cislo.datum_deadline <= datetime.date.today():
result.append((
strftime(DATE_FORMAT, cislo.datum_deadline.timetuple()),
f"Finální deadline {cislo.poradi}. čísla"))
result.append((
strftime(DATE_FORMAT, datetime.date.today().timetuple()), f"Dnes"))
return result
@classmethod
def gen_initial(cls, rocnik=None):
terminy = cls.gen_terminy(rocnik)
initial = {
'resitele': cls.RESITELE_RELEVANTNI,
'problemy': cls.PROBLEMY_MOJE,
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení…
'reseni_od': terminy[-2] if rocnik is None else terminy[0],
'reseni_do': terminy[-1],
'neobodovane': False,
}
return initial
def __init__(self, *args, rocnik=None, **kwargs):
if 'initial' not in kwargs:
super().__init__(initial=self.gen_initial(rocnik), *args, **kwargs)
else:
super().__init__(*args, **kwargs)
# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem...
# A "sem" znamená do libovolné metody, protože jinak se jedná o kód, který django spustí při inicializaci a protože potřebujeme databázi, tak by spadnul při vyrábění testdat...
self.terminy = self.gen_terminy(rocnik)
self.fields['reseni_od'].widget = forms.Select(choices=self.gen_terminy(rocnik))
# Pokud chceme neaktuální ročník, tak nás nejspíš zajímají všechna řešení…
self.fields['reseni_od'].initial = self.terminy[-2] if rocnik is None else self.terminy[0]
self.fields['reseni_do'].widget = forms.Select(choices=self.gen_terminy(rocnik))
self.fields['reseni_do'].initial = self.terminy[-1]
# NOTE: Initial definuji pro jednotlivé fieldy, aby to bylo tady a nebylo potřeba to řešit ve views...
resitele = forms.ChoiceField(choices=RESITELE_CHOICES)
problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES)
reseni_od = forms.DateField(input_formats=[DATE_FORMAT])
reseni_do = forms.DateField(input_formats=[DATE_FORMAT])
neobodovane = forms.BooleanField(required=False)