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