Merge branch 'odevzdavatko' into data_migrations
This commit is contained in:
		
						commit
						eb8ba371f5
					
				
					 3 changed files with 125 additions and 11 deletions
				
			
		|  | @ -316,3 +316,77 @@ class JednoHodnoceniForm(forms.ModelForm): | |||
| 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_LETOSNI = 'letosni' | ||||
| 	RESITELE_CHOICES = [ | ||||
| 		(RESITELE_RELEVANTNI, 'Relevantní řešitelé'), # I.e. nezobrazovat prázdné řádky tabulky | ||||
| 		(RESITELE_LETOSNI, 'Všichni letošní'), | ||||
| 		# 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ů, ...)? | ||||
| 
 | ||||
| 
 | ||||
| 	def gen_terminy(): | ||||
| 		import datetime | ||||
| 		from time import strftime | ||||
| 
 | ||||
| 		aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik | ||||
| 		aktualni_cislo = m.Nastaveni.get_solo().aktualni_cislo | ||||
| 
 | ||||
| 		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 | ||||
| 
 | ||||
| 	# 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, initial=RESITELE_RELEVANTNI) | ||||
| 	problemy = forms.ChoiceField(choices=PROBLEMY_CHOICES, initial=PROBLEMY_MOJE) | ||||
| 	 | ||||
| 	# choices jako parametr Select widgetu neumí brát callable, jen iterable, takže si pro jednoduchost můžu rovnou uložit výsledek sem... | ||||
| 	terminy = gen_terminy() | ||||
| 	reseni_od = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-2]) | ||||
| 	reseni_do = forms.DateField(input_formats=[DATE_FORMAT], widget=forms.Select(choices=terminy), initial=terminy[-1]) | ||||
|  |  | |||
|  | @ -4,6 +4,14 @@ | |||
| 
 | ||||
| {% block content %} | ||||
| 
 | ||||
| <form method=get action=.> | ||||
| {{ filtr.resitele }} | ||||
| {{ filtr.problemy }} | ||||
| Od: {{ filtr.reseni_od }} | ||||
| Do: {{ filtr.reseni_do }} | ||||
| <input type=submit value="→"> | ||||
| </form> | ||||
| 
 | ||||
| <table> | ||||
| 	<tr> | ||||
| 		<td></td> {# Prázdná buňka v levém horním rohu #} | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import logging | |||
| 
 | ||||
| import seminar.models as m | ||||
| import seminar.forms as f | ||||
| from seminar.forms import OdevzdavatkoTabulkaFiltrForm as FiltrForm | ||||
| from seminar.utils import aktivniResitele, resi_v_rocniku | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
|  | @ -41,28 +42,55 @@ class TabulkaOdevzdanychReseniView(ListView): | |||
| 
 | ||||
| 	def inicializuj_osy_tabulky(self): | ||||
| 		"""Vyrobí prvotní querysety pro sloupce a řádky, tj. seznam všech řešitelů a problémů""" | ||||
| 		# FIXME: jméno metody není vypovídající... | ||||
| 		# NOTE: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistují ty objekty (?). TODO: Otestovat | ||||
| 		# TODO: Prefetches, Select related, ... | ||||
| 		self.resitele = m.Resitel.objects.all() | ||||
| 		self.problemy = m.Problem.objects.all() | ||||
| 		self.reseni = m.Reseni.objects.all() | ||||
| 
 | ||||
| 		form = FiltrForm(self.request.GET) | ||||
| 		if form.is_valid(): | ||||
| 			fcd = form.cleaned_data | ||||
| 			resitele = fcd["resitele"] | ||||
| 			problemy = fcd["problemy"] | ||||
| 			reseni_od = fcd["reseni_od"] | ||||
| 			reseni_do = fcd["reseni_do"] | ||||
| 		else: | ||||
| 			resitele = FiltrForm.get_initial_for_field(FormFiltr.resitele, "resitele") | ||||
| 			problemy = FiltrForm.get_initial_for_field(FormFiltr.problemy, "problemy") | ||||
| 			resitele_od = FiltrForm.get_initial_for_field(FormFiltr.resitele_od, "resitele_od") | ||||
| 			resitele_do = FiltrForm.get_initial_for_field(FormFiltr.resitele_do, "resitele_do") | ||||
| 			 | ||||
| 
 | ||||
| 		# Filtrujeme! | ||||
| 		aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci | ||||
| 		if resitele == FiltrForm.RESITELE_RELEVANTNI: | ||||
| 			logger.warning("Někdo chtěl v tabulce jen relevantní řešitele a měl smůlu :-(") | ||||
| 			resitele = FiltrForm.RESITELE_LETOSNI # Fall-through | ||||
| 		elif resitele == FiltrForm.RESITELE_LETOSNI: | ||||
| 			self.resitele = resi_v_rocniku(aktualni_rocnik) | ||||
| 
 | ||||
| 		if problemy == FiltrForm.PROBLEMY_MOJE: | ||||
| 			org = m.Organizator.objects.get(osoba__user=self.request.user) | ||||
| 			from django.db.models import Q | ||||
| 			self.problemy = self.problemy.filter(Q(autor=org)|Q(garant=org)|Q(opravovatele=org), stav=m.Problem.STAV_ZADANY) | ||||
| 		elif problemy == FiltrForm.PROBLEMY_LETOSNI: | ||||
| 			self.problemy = self.problemy.filter(stav=m.Problem.STAV_ZADANY) | ||||
| 			#self.problemy = list(filter(lambda problem: problem.rocnik() == aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník.... | ||||
| 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. | ||||
| 		self.problemy = self.problemy.non_polymorphic() | ||||
| 
 | ||||
| 		self.reseni = self.reseni.filter(cas_doruceni__date__gte=reseni_od, cas_doruceni__date__lte=reseni_do) | ||||
| 
 | ||||
| 	def get_queryset(self): | ||||
| 		self.inicializuj_osy_tabulky() | ||||
| 		self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci, asi... | ||||
| 		self.resitele = resi_v_rocniku(self.akt_rocnik) | ||||
| 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. | ||||
| 		self.problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() | ||||
| 
 | ||||
| 		qs = super().get_queryset() | ||||
| 		qs = qs.filter(problem__in=self.problemy).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') | ||||
| 		qs = qs.filter(problem__in=self.problemy, reseni__in=self.reseni, reseni__resitele__in=self.resitele).select_related('reseni', 'problem').prefetch_related('reseni__resitele__osoba') | ||||
| 		return qs | ||||
| 
 | ||||
| 	def get_context_data(self, *args, **kwargs): | ||||
| 		# FIXME: Tenhle blok nemůže být přímo ve třídě, protože před vyrobením databáze neexistuje Nastavení. | ||||
| 		self.akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik	# .get_solo() vrátí tu jedinou instanci, asi... | ||||
| 		self.resitele = resi_v_rocniku(self.akt_rocnik) | ||||
| 		# NOTE: Protože řešení odkazuje přímo na Problém a QuerySet na Hodnocení je nepolymorfní, musíme porovnávat taky s nepolymorfními Problémy. | ||||
| 		self.problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() | ||||
| 		# self.resitele, self.reseni a self.problemy jsou již nastavené | ||||
| 
 | ||||
| 		ctx = super().get_context_data(*args, **kwargs) | ||||
| 		ctx['problemy'] = self.problemy | ||||
|  | @ -100,6 +128,10 @@ class TabulkaOdevzdanychReseniView(ListView): | |||
| 			hodnoty.append(resiteluv_radek) | ||||
| 		ctx['radky'] = list(zip(self.resitele, hodnoty)) | ||||
| 
 | ||||
| 		ctx['filtr'] = FiltrForm(initial=self.request.GET) | ||||
| 		# Pro použití hacku na automatické {{form.media}} v template: | ||||
| 		ctx['form'] = ctx['filtr'] | ||||
| 
 | ||||
| 		return ctx | ||||
| 
 | ||||
| # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Pavel "LEdoian" Turinsky
						Pavel "LEdoian" Turinsky