Merge branch 'prehlednejsi_hodnotitko'
This commit is contained in:
		
						commit
						c673d04082
					
				
					 5 changed files with 56 additions and 29 deletions
				
			
		|  | @ -1245,6 +1245,7 @@ div.gdpr { | ||||||
| .dosla_reseni tr th, .dosla_reseni tr td { | .dosla_reseni tr th, .dosla_reseni tr td { | ||||||
| 	padding: 1px 10px 1px 10px; | 	padding: 1px 10px 1px 10px; | ||||||
| 	border-collapse: collapse; | 	border-collapse: collapse; | ||||||
|  | 	min-width: 8em; /*Nastřeleno, aby se řádky s řešeními nezalamovaly. Teoreticky není potřeba pro th, ale whatever.*/ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dosla_reseni tr:nth-child(even) { | .dosla_reseni tr:nth-child(even) { | ||||||
|  |  | ||||||
|  | @ -238,6 +238,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form): | ||||||
| 			'reseni_od': terminy[-2] if rocnik is None else terminy[0], | 			'reseni_od': terminy[-2] if rocnik is None else terminy[0], | ||||||
| 			'reseni_do': terminy[-1], | 			'reseni_do': terminy[-1], | ||||||
| 			'neobodovane': False, | 			'neobodovane': False, | ||||||
|  | 			'barvicky': True, | ||||||
| 		} | 		} | ||||||
| 		return initial | 		return initial | ||||||
| 
 | 
 | ||||||
|  | @ -262,3 +263,4 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form): | ||||||
| 	reseni_od = forms.DateField(input_formats=[DATE_FORMAT]) | 	reseni_od = forms.DateField(input_formats=[DATE_FORMAT]) | ||||||
| 	reseni_do = forms.DateField(input_formats=[DATE_FORMAT]) | 	reseni_do = forms.DateField(input_formats=[DATE_FORMAT]) | ||||||
| 	neobodovane = forms.BooleanField(required=False) | 	neobodovane = forms.BooleanField(required=False) | ||||||
|  | 	barvicky = forms.BooleanField(required=False) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| {% extends "base.html" %} | {% extends "base.html" %} | ||||||
| 
 | 
 | ||||||
| {% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #} | {% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #} | ||||||
|  | {% load barvy_reseni %} | ||||||
| 
 | 
 | ||||||
| {% block content %} | {% block content %} | ||||||
| 
 | 
 | ||||||
|  | @ -11,6 +12,7 @@ | ||||||
| Od data (vyjma): {{ filtr.reseni_od }} | Od data (vyjma): {{ filtr.reseni_od }} | ||||||
| Do data (včetně): {{ filtr.reseni_do }} | Do data (včetně): {{ filtr.reseni_do }} | ||||||
| <span title="Jen neobodovaná řešení">🔨?</span> {{ filtr.neobodovane }} | <span title="Jen neobodovaná řešení">🔨?</span> {{ filtr.neobodovane }} | ||||||
|  | <span title="Obarvit shodná řešení shodně">🎨?</span> {{ filtr.barvicky }} | ||||||
| <input type=submit value="→"> | <input type=submit value="→"> | ||||||
| </form> | </form> | ||||||
| 
 | 
 | ||||||
|  | @ -36,12 +38,15 @@ Do data (včetně): {{ filtr.reseni_do }} | ||||||
| 			{# TODO: Chceme mít view i na řešení konkrétního řešitele ke všem problémům? #} | 			{# TODO: Chceme mít view i na řešení konkrétního řešitele ke všem problémům? #} | ||||||
| 			{{ resitel }} | 			{{ resitel }} | ||||||
| 		</td> | 		</td> | ||||||
| 		{% for hodn in hodnoty %} | 		{% for soucet,bunka in hodnoty %} | ||||||
| 			<td> | 			<td> | ||||||
| 			{% if hodn %} | 			{% for reseni,hodnoceni in bunka %} | ||||||
| 			<a href="{% url 'odevzdavatko_reseni_resitele_k_problemu' problem=hodn.problem_id resitel=hodn.resitel_id %}"> | 			<a {% if barvicky %} style="color: {{reseni|barva_reseni}};" {% endif %} href="{% url 'odevzdavatko_detail_reseni' pk=reseni.id %}"> | ||||||
| 				{{ hodn.pocet_reseni }} řeš.<br>{{ hodn.body }} b<br>{{ hodn.posledni_odevzdani|kratke_datum|default_if_none:"Nikdy"|default:"???"}} | 					{{reseni.cas_doruceni | date:"j. n."}} ({{ hodnoceni.body|default_if_none:"🔨"}} b) | ||||||
| 			</a> | 				</a><br> | ||||||
|  | 			{% endfor %} | ||||||
|  | 			{% if bunka|length > 1 %} | ||||||
|  | 				<b>Σ: {{soucet}} b</b> | ||||||
| 			{% endif %} | 			{% endif %} | ||||||
| 			</td> | 			</td> | ||||||
| 		{% endfor %} | 		{% endfor %} | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								odevzdavatko/templatetags/barvy_reseni.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								odevzdavatko/templatetags/barvy_reseni.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | ||||||
|  | from django import template | ||||||
|  | register = template.Library() | ||||||
|  | 
 | ||||||
|  | from functools import cache | ||||||
|  | import seminar.models as m | ||||||
|  | 
 | ||||||
|  | @register.filter | ||||||
|  | @cache | ||||||
|  | def barva_reseni(r: m.Reseni): | ||||||
|  | 	"""Vrátí nějakou barvu pro daný problém, ve tvaru '#RRGGBB' | ||||||
|  | 
 | ||||||
|  | 	Efektivně hešujeme do barev.""" | ||||||
|  | 
 | ||||||
|  | 	#TODO: ne všechny barvy jsou dobře rozlišitelné a vidět… | ||||||
|  | 	return f'#{hash(str(r.id)) & 0xffffff:06x}' | ||||||
|  | @ -13,6 +13,7 @@ from django.db.models import Q | ||||||
| 
 | 
 | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| import datetime | import datetime | ||||||
|  | from decimal import Decimal | ||||||
| from itertools import groupby | from itertools import groupby | ||||||
| import logging | import logging | ||||||
| 
 | 
 | ||||||
|  | @ -37,14 +38,6 @@ logger = logging.getLogger(__name__) | ||||||
| # Taky se může hodit: | # Taky se může hodit: | ||||||
| # - Tabulka všech řešitelů x všech problémů? | # - Tabulka všech řešitelů x všech problémů? | ||||||
| 
 | 
 | ||||||
| @dataclass |  | ||||||
| class SouhrnReseni: |  | ||||||
| 	"""Dataclass reprezentující data o odevzdaných řešeních pro zobrazení v tabulce.""" |  | ||||||
| 	pocet_reseni : int |  | ||||||
| 	posledni_odevzdani : datetime.datetime |  | ||||||
| 	body : float |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TabulkaOdevzdanychReseniView(ListView): | class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 	template_name = 'odevzdavatko/tabulka.html' | 	template_name = 'odevzdavatko/tabulka.html' | ||||||
| 	model = m.Hodnoceni | 	model = m.Hodnoceni | ||||||
|  | @ -70,6 +63,7 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 			reseni_od = fcd["reseni_od"] | 			reseni_od = fcd["reseni_od"] | ||||||
| 			reseni_do = fcd["reseni_do"] | 			reseni_do = fcd["reseni_do"] | ||||||
| 			jen_neobodovane = fcd["neobodovane"] | 			jen_neobodovane = fcd["neobodovane"] | ||||||
|  | 			self.barvicky = fcd["barvicky"] | ||||||
| 		else: | 		else: | ||||||
| 			initial = FiltrForm.gen_initial(self.aktualni_rocnik) | 			initial = FiltrForm.gen_initial(self.aktualni_rocnik) | ||||||
| 			resitele = initial['resitele'] | 			resitele = initial['resitele'] | ||||||
|  | @ -77,6 +71,7 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 			reseni_od = initial['reseni_od'][0] | 			reseni_od = initial['reseni_od'][0] | ||||||
| 			reseni_do = initial['reseni_do'][0] | 			reseni_do = initial['reseni_do'][0] | ||||||
| 			jen_neobodovane = initial["neobodovane"] | 			jen_neobodovane = initial["neobodovane"] | ||||||
|  | 			self.barvicky = initial["barvicky"] | ||||||
| 			 | 			 | ||||||
| 
 | 
 | ||||||
| 		# Chceme jen letošní problémy | 		# Chceme jen letošní problémy | ||||||
|  | @ -120,42 +115,45 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 		return qs | 		return qs | ||||||
| 
 | 
 | ||||||
| 	def get_context_data(self, *args, **kwargs): | 	def get_context_data(self, *args, **kwargs): | ||||||
|  | 		# TODO: refactor asi. Přepisoval jsem to jen syntakticky, nejspíš půlka kódu přestala dávat smysl… | ||||||
| 		# self.resitele, self.reseni a self.problemy jsou již nastavené | 		# self.resitele, self.reseni a self.problemy jsou již nastavené | ||||||
| 
 | 
 | ||||||
| 		ctx = super().get_context_data(*args, **kwargs) | 		ctx = super().get_context_data(*args, **kwargs) | ||||||
| 		ctx['problemy'] = self.problemy | 		ctx['problemy'] = self.problemy | ||||||
| 		ctx['resitele'] = self.resitele | 		ctx['resitele'] = self.resitele | ||||||
| 		tabulka = dict() | 		tabulka: dict[m.Problem, dict[m.Resitel, list[tuple[m.Reseni, m.Hodnoceni]]]] = dict() | ||||||
|  | 		soucty: dict[m.Problem, dict[m.Resitel, Decimal]] = dict() | ||||||
| 
 | 
 | ||||||
| 		def pridej_reseni(problem, resitel, body, cas): | 		def pridej_reseni(resitel, hodnoceni): | ||||||
|  | 			problem = hodnoceni.problem | ||||||
|  | 			body = hodnoceni.body | ||||||
|  | 			cas = hodnoceni.reseni.cas_doruceni | ||||||
|  | 			reseni = hodnoceni.reseni | ||||||
| 			if problem not in tabulka: | 			if problem not in tabulka: | ||||||
| 				tabulka[problem] = dict() | 				tabulka[problem] = dict() | ||||||
|  | 				soucty[problem] = dict() | ||||||
| 			if resitel not in tabulka[problem]: | 			if resitel not in tabulka[problem]: | ||||||
| 				tabulka[problem][resitel] = SouhrnReseni(pocet_reseni=1, posledni_odevzdani=cas, body=body) | 				tabulka[problem][resitel] = [(reseni, hodnoceni)] | ||||||
|  | 				soucty[problem][resitel] = hodnoceni.body or 0 # Neobodované neřešíme | ||||||
| 			else: | 			else: | ||||||
| 				tabulka[problem][resitel].posledni_odevzdani = max(tabulka[problem][resitel].posledni_odevzdani, cas) | 				tabulka[problem][resitel].append((reseni, hodnoceni)) | ||||||
| 				# Zvětšení počtu bodů o aktuální počet, pokud se tam někde nevyskytuje None – pak je součet taky None ("Pozor, nezadané body") | 				soucty[problem][resitel] += hodnoceni.body or 0 # Neobodované neřešíme | ||||||
| 				tabulka[problem][resitel].body = tabulka[problem][resitel].body + body if body is not None and tabulka[problem][resitel].body is not None else None |  | ||||||
| 				tabulka[problem][resitel].pocet_reseni += 1 |  | ||||||
| 			# Pro jednoduchost template si ještě poznamenáme ID problému a řešitele |  | ||||||
| 			tabulka[problem][resitel].problem_id = problem.id |  | ||||||
| 			tabulka[problem][resitel].resitel_id = resitel.id |  | ||||||
| 		 | 		 | ||||||
| 		for hodnoceni in self.get_queryset(): | 		for hodnoceni in self.get_queryset(): | ||||||
| 			for resitel in hodnoceni.reseni.resitele.all(): | 			for resitel in hodnoceni.reseni.resitele.all(): | ||||||
| 				pridej_reseni(hodnoceni.problem, resitel, hodnoceni.body, hodnoceni.reseni.cas_doruceni) | 				pridej_reseni(resitel, hodnoceni) | ||||||
| 
 | 
 | ||||||
| 		hodnoty = [] | 		hodnoty: list[list[tuple[Decimal,list[tuple[m.Reseni, m.Hodnoceni]]]]] = [] # Seznam řádků výsledné tabulky podle self.resitele, v každém řádku buňky v pořadí podle self.problemy + jejich součty, v každé buňce seznam řešení k danému řešiteli a problému. | ||||||
| 		resitele_do_tabulky = [] | 		resitele_do_tabulky: list[m.Resitel] = [] | ||||||
| 		for resitel in self.resitele: | 		for resitel in self.resitele: | ||||||
| 			dostal_body = False | 			dostal_body = False | ||||||
| 			resiteluv_radek = [] | 			resiteluv_radek: list[tuple[Decimal,list[tuple[m.Reseni, m.Hodnoceni]]]] = [] # podle pořadí v self.problemy | ||||||
| 			for problem in self.problemy: | 			for problem in self.problemy: | ||||||
| 				if problem in tabulka and resitel in tabulka[problem]: | 				if problem in tabulka and resitel in tabulka[problem]: | ||||||
| 					resiteluv_radek.append(tabulka[problem][resitel]) | 					resiteluv_radek.append((soucty[problem][resitel], tabulka[problem][resitel])) | ||||||
| 					dostal_body = True | 					dostal_body = True | ||||||
| 				else: | 				else: | ||||||
| 					resiteluv_radek.append(None) | 					resiteluv_radek.append((Decimal(0),[])) | ||||||
| 			if self.chteni_resitele != FiltrForm.RESITELE_RELEVANTNI or dostal_body: | 			if self.chteni_resitele != FiltrForm.RESITELE_RELEVANTNI or dostal_body: | ||||||
| 				hodnoty.append(resiteluv_radek) | 				hodnoty.append(resiteluv_radek) | ||||||
| 				resitele_do_tabulky.append(resitel) | 				resitele_do_tabulky.append(resitel) | ||||||
|  | @ -165,6 +163,7 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 		ctx['form'] = ctx['filtr'] | 		ctx['form'] = ctx['filtr'] | ||||||
| 		# Pro maximum v přesměrovátku ročníků | 		# Pro maximum v přesměrovátku ročníků | ||||||
| 		ctx['aktualni_rocnik'] = m.Nastaveni.get_solo().aktualni_rocnik | 		ctx['aktualni_rocnik'] = m.Nastaveni.get_solo().aktualni_rocnik | ||||||
|  | 		ctx['barvicky'] = self.barvicky | ||||||
| 		if 'rocnik' in self.kwargs: | 		if 'rocnik' in self.kwargs: | ||||||
| 			ctx['rocnik'] = self.kwargs['rocnik'] | 			ctx['rocnik'] = self.kwargs['rocnik'] | ||||||
| 		else: | 		else: | ||||||
|  | @ -174,6 +173,11 @@ class TabulkaOdevzdanychReseniView(ListView): | ||||||
| 
 | 
 | ||||||
| # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? | # Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji? | ||||||
| class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View): | class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View): | ||||||
|  | 	"""Rozskok mezi více řešeními téhož problému od téhož řešitele. | ||||||
|  | 	 | ||||||
|  | 	Asi už bude zastaralý v okamžiku, kdy se tenhle komentář nasadí na produkci :-) | ||||||
|  | 
 | ||||||
|  | 	V případě, že takové řešení existuje jen jedno, tak na něj přesměruje.""" | ||||||
| 	model = m.Reseni | 	model = m.Reseni | ||||||
| 	template_name = 'odevzdavatko/seznam.html' | 	template_name = 'odevzdavatko/seznam.html' | ||||||
| 	 | 	 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue