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', 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=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, ) # 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): 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], 'neobodovane': False, } 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]) neobodovane = forms.BooleanField(required=False)