from django import forms
from dal import autocomplete
from django . forms import formset_factory
from django . forms . models import inlineformset_factory
from django . utils import timezone
from personalni . models import Resitel
from seminar . models . tvorba import Problem , Deadline , Nastaveni
from seminar . models . odevzdavatko import *
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 ) :
problem = forms . ModelMultipleChoiceField (
queryset = Problem . objects . all ( ) ,
label = " Problémy " ,
widget = autocomplete . ModelSelect2Multiple (
url = ' autocomplete_problem ' ,
attrs = {
' data-placeholder--id ' : ' -1 ' ,
' data-placeholder--text ' : ' --- ' ,
' data-close-on-select ' : ' false ' ,
' data-dropdown-css-class ' : ' s2m-se-zaskrtavatky ' ,
' data-allow-clear ' : ' true '
} ,
) ,
)
# to_field_name
#problem = models.ManyToManyField(Problem, verbose_name='problém', help_text='Problém',
# through='Hodnoceni')
resitel = forms . ModelMultipleChoiceField ( label = " Řešitelé " ,
queryset = Resitel . objects . all ( ) ,
widget = autocomplete . ModelSelect2Multiple (
url = ' autocomplete_resitel ' ,
attrs = { ' data-placeholder--id ' : ' -1 ' ,
' data-placeholder--text ' : ' --- ' ,
' data-close-on-select ' : ' false ' ,
' data-dropdown-css-class ' : ' s2m-se-zaskrtavatky ' ,
' 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 = 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)')
class NahrajReseniForm ( forms . ModelForm ) :
class Meta :
model = Reseni
fields = ( ' problem ' , ' resitele ' )
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-close-on-select ' : ' false ' ,
' data-dropdown-css-class ' : ' s2m-se-zaskrtavatky ' ,
' data-allow-clear ' : ' true ' } ,
forward = [ " nadproblem_id " ] ,
) ,
' resitele ' :
autocomplete . ModelSelect2Multiple (
url = ' autocomplete_resitel_public ' ,
attrs = { ' data-placeholder--id ' : ' -1 ' ,
' data-placeholder--text ' : ' --- ' ,
' data-close-on-select ' : ' false ' ,
' data-dropdown-css-class ' : ' s2m-se-zaskrtavatky ' ,
' data-allow-clear ' : ' true ' } ,
)
}
nadproblem_id = forms . IntegerField ( required = False , disabled = True , widget = forms . HiddenInput ( ) )
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
# FIXME Z nějakého důvodu se do této třídy dostaneme i bez resitele
if ' resitele ' in self . fields :
# FIXME Mnohem hezčí by to bylo u definice resitele výše, ale nepodařilo se mi to.
self . fields [ ' resitele ' ] . required = False
self . fields [ ' resitele ' ] . label = " Další autoři "
if ' problem ' in self . fields :
self . fields [ ' problem ' ] . label = " Všechny řešené problémy "
def clean_problem ( self ) :
problem = self . cleaned_data . get ( ' problem ' )
for p in problem :
if p . stav != Problem . STAV_ZADANY :
raise forms . ValidationError ( " Problém " + str ( p ) + " již nelze řešit! " )
return problem
ReseniSPrilohamiFormSet = inlineformset_factory ( Reseni , PrilohaReseni ,
form = NahrajReseniForm ,
fields = ( ' soubor ' , ' res_poznamka ' ) ,
widgets = { ' res_poznamka ' : forms . TextInput ( ) } ,
extra = 1 ,
can_delete = False ,
)
class JednoHodnoceniForm ( forms . ModelForm ) :
class Meta :
model = Hodnoceni
fields = ( ' problem ' , ' body ' , ' deadline_body ' , ' feedback ' , )
widgets = {
' problem ' : autocomplete . ModelSelect2 (
url = ' autocomplete_problem ' ,
) ,
' feedback ' : forms . Textarea ( attrs = { ' rows ' : 1 , ' cols ' : 30 , ' class ' : ' feedback ' } ) ,
}
body_celkem = forms . DecimalField ( required = False , decimal_places = 1 )
body_neprepocitane = forms . DecimalField ( required = False , decimal_places = 1 )
body_neprepocitane_celkem = forms . DecimalField ( required = False , decimal_places = 1 )
def __init__ ( self , * args , initial = None , * * kwargs ) :
if initial is not None :
body_max = initial [ " body_max " ]
body_neprepocitane_max = initial [ " body_neprepocitane_max " ]
del ( initial [ " body_max " ] )
del ( initial [ " body_neprepocitane_max " ] )
super ( ) . __init__ ( * args , initial = initial , * * kwargs )
if initial is not None :
self . fields [ ' body ' ] . widget . attrs [ ' placeholder ' ] = body_max
self . fields [ ' body_celkem ' ] . widget . attrs [ ' placeholder ' ] = body_max
self . fields [ ' body_neprepocitane ' ] . widget . attrs [ ' placeholder ' ] = body_neprepocitane_max
self . fields [ ' body_neprepocitane_celkem ' ] . widget . attrs [ ' placeholder ' ] = body_neprepocitane_max
OhodnoceniReseniFormSet = formset_factory ( JednoHodnoceniForm ,
extra = 0 ,
)
class PoznamkaReseniForm ( forms . ModelForm ) :
class Meta :
model = 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 = Nastaveni . get_solo ( ) . aktualni_rocnik
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
result = [ ]
result . append ( ( " 0001-01-01 " , f " Odjakživa " ) )
for deadline in Deadline . objects . filter (
deadline__lte = timezone . now ( ) ,
cislo__rocnik = aktualni_rocnik
) . order_by ( " deadline " ) :
result . append ( (
strftime ( DATE_FORMAT , deadline . deadline . timetuple ( ) ) ,
str ( deadline ) ) )
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 )