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 )
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 )
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 ,
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 )