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)