Tomas "Jethro" Pokorny
4 years ago
14 changed files with 1167 additions and 64 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,47 @@ |
|||||
|
{% extends "base.html" %} |
||||
|
|
||||
|
{% block content %} |
||||
|
|
||||
|
<p>Řešené problémy: {{ object.problem.all | join:", " }}</p> |
||||
|
|
||||
|
<p>Řešitelé: {{ object.resitele.all | join:", " }}</p> |
||||
|
|
||||
|
{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #} |
||||
|
<p>Forma: {{ object.get_forma_display }}, doručeno {{ object.cas_doruceni }}</p> |
||||
|
|
||||
|
{# Soubory: #} |
||||
|
<h3>Přílohy:</h3> |
||||
|
{% if object.prilohy.all %} |
||||
|
<table> |
||||
|
<tr><th>Soubor</th><th>Řešitelova poznámka</th><th>Datum</th></tr> |
||||
|
{% for priloha in object.prilohy.all %} |
||||
|
<tr> |
||||
|
<td><a href="{{ priloha.soubor.url }}" download>{{ priloha.split | last }}</a></td> |
||||
|
<td>{{ priloha.res_poznamka }}</td> |
||||
|
<td>{{ priloha.vytvoreno }}</td></tr> |
||||
|
{# TODO: Orgo-poznámka, ideálně jako formulář #} |
||||
|
{% endfor %} |
||||
|
</table> |
||||
|
{% else %} |
||||
|
<p>Žádné přílohy</p> |
||||
|
{% endif %} |
||||
|
|
||||
|
{# Hodnocení: #} |
||||
|
{# FIXME: Udělat jako formulář #} |
||||
|
<h3>Hodnocení:</h3> |
||||
|
{% if object.hodnoceni_set.all %} |
||||
|
<table> |
||||
|
<tr><th>Problém</th><th>Body</th><th>Číslo pro body</th></tr> |
||||
|
{% for h in object.hodnoceni_set.all %} |
||||
|
<tr> |
||||
|
<td>{{ h.problem }}</a></td> |
||||
|
<td>{{ h.body }}</td> |
||||
|
<td>{{ h.cislo_body }}</td></tr> |
||||
|
{% endfor %} |
||||
|
</table> |
||||
|
{% else %} |
||||
|
<p>Ještě nebylo hodnoceno</p> |
||||
|
{% endif %} |
||||
|
|
||||
|
|
||||
|
{% endblock %} |
@ -0,0 +1,11 @@ |
|||||
|
{% extends "base.html" %} |
||||
|
|
||||
|
{% block content %} |
||||
|
|
||||
|
<ul> |
||||
|
{% for obj in object_list %} |
||||
|
<li><a href="{% url 'odevzdavatko_detail_reseni' pk=obj.id %}">{{ obj }}</a> ({{ obj.get_forma_display }} {{ obj.cas_doruceni }}) |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
|
||||
|
{% endblock %} |
@ -0,0 +1,36 @@ |
|||||
|
{% extends "base.html" %} |
||||
|
|
||||
|
{% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #} |
||||
|
|
||||
|
{% block content %} |
||||
|
|
||||
|
<table> |
||||
|
<tr> |
||||
|
<td></td> {# Prázdná buňka v levém horním rohu #} |
||||
|
{% for p in problemy %} |
||||
|
<th> |
||||
|
{# TODO: Přehled řešení k problému, odkázaný odsud? #} |
||||
|
{{ p }} |
||||
|
</th> |
||||
|
{% endfor %} |
||||
|
</tr> |
||||
|
{% for resitel,hodnoty in radky%} |
||||
|
<tr> |
||||
|
<td> |
||||
|
{# TODO: Chceme mít view i na řešení konkrétního řešitele ke všem problémům? #} |
||||
|
{{ resitel }} |
||||
|
</td> |
||||
|
{% for hodn in hodnoty %} |
||||
|
<td> |
||||
|
{% if hodn %} |
||||
|
<a href="{% url 'odevzdavatko_reseni_resitele_k_problemu' problem=hodn.problem_id resitel=hodn.resitel_id %}"> |
||||
|
{{ hodn.pocet_reseni }} řeš.<br>{{ hodn.body }} b<br>{{ hodn.posledni_odevzdani|kratke_datum|default_if_none:"Nikdy"|default:"???"}} |
||||
|
</a> |
||||
|
{% endif %} |
||||
|
</td> |
||||
|
{% endfor %} |
||||
|
</tr> |
||||
|
{% endfor %} |
||||
|
</table> |
||||
|
|
||||
|
{% endblock %} |
@ -0,0 +1,27 @@ |
|||||
|
from django import template |
||||
|
from datetime import datetime, timedelta |
||||
|
from pytz import timezone |
||||
|
from mamweb.settings import TIME_ZONE |
||||
|
import logging |
||||
|
register = template.Library() |
||||
|
|
||||
|
logger = logging.getLogger(__name__) |
||||
|
|
||||
|
@register.filter(name='kratke_datum', expects_localtime=True) |
||||
|
def kratke_datum(dt): |
||||
|
# None dává None, ne-datum dává False, aby se daly použít filtry typu "default". |
||||
|
if dt is None: |
||||
|
return None |
||||
|
if not isinstance(dt, datetime): |
||||
|
logger.warning(f"Špatné volání filtru {__name__}: {dt}") |
||||
|
return False |
||||
|
naive_now = datetime.now() |
||||
|
tz = timezone(TIME_ZONE) |
||||
|
now = tz.localize(naive_now) |
||||
|
delta = now - dt |
||||
|
if delta <= timedelta(days=1): |
||||
|
return dt.strftime("%k:%M") |
||||
|
if delta <= timedelta(days=365): # Timedelta neumí vyjádřit 1 rok |
||||
|
return dt.strftime("%d. %m.") |
||||
|
return dt.strftime("%d. %m. %Y") |
||||
|
|
@ -1,3 +1,4 @@ |
|||||
from .views_all import * |
from .views_all import * |
||||
from .autocomplete import * |
from .autocomplete import * |
||||
from .views_rest import * |
from .views_rest import * |
||||
|
from .odevzdavatko import * |
||||
|
@ -0,0 +1,129 @@ |
|||||
|
from django.views.generic import ListView, DetailView |
||||
|
from django.views.generic.base import TemplateView |
||||
|
|
||||
|
from dataclasses import dataclass |
||||
|
import datetime |
||||
|
|
||||
|
import seminar.models as m |
||||
|
from seminar.utils import aktivniResitele, resi_v_rocniku |
||||
|
|
||||
|
# Co chceme? |
||||
|
# - "Tabulku" aktuální řešitelé x zveřejněné problémy, v buňkách počet řešení |
||||
|
# - TabulkaOdevzdanychReseniView |
||||
|
# - Detail konkrétního problému a řešitele -- přehled všech řešení odevzdaných k tomuto problému |
||||
|
# - ReseniProblemuView |
||||
|
# - Detail konkrétního řešení -- všechny soubory, datum, ... |
||||
|
# - DetailReseniView |
||||
|
# |
||||
|
# Taky se může hodit: |
||||
|
# - 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): |
||||
|
template_name = 'seminar/odevzdavatko/tabulka.html' |
||||
|
model = m.Hodnoceni |
||||
|
|
||||
|
def get_queryset(self): |
||||
|
# 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.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() |
||||
|
|
||||
|
qs = super().get_queryset() |
||||
|
qs = qs.filter(problem__in=self.zadane_problemy).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.zadane_problemy = m.Problem.objects.filter(stav=m.Problem.STAV_ZADANY).non_polymorphic() |
||||
|
|
||||
|
ctx = super().get_context_data(*args, **kwargs) |
||||
|
ctx['problemy'] = self.zadane_problemy |
||||
|
ctx['resitele'] = self.resitele |
||||
|
tabulka = dict() |
||||
|
|
||||
|
def pridej_reseni(problem, resitel, body, cas): |
||||
|
if problem not in tabulka: |
||||
|
tabulka[problem] = dict() |
||||
|
if resitel not in tabulka[problem]: |
||||
|
tabulka[problem][resitel] = SouhrnReseni(pocet_reseni=1, posledni_odevzdani=cas, body=body) |
||||
|
else: |
||||
|
tabulka[problem][resitel].posledni_odevzdani = max(tabulka[problem][resitel].posledni_odevzdani, cas) |
||||
|
tabulka[problem][resitel].body = max(tabulka[problem][resitel].body, body, |
||||
|
key=lambda x: x if x is not None else -1 # None je malé číslo |
||||
|
# FIXME: Možná dává smysl i mít None jako velké číslo -- jakože "TODO: zadat body" |
||||
|
) |
||||
|
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 resitel in hodnoceni.reseni.resitele.all(): |
||||
|
pridej_reseni(hodnoceni.problem, resitel, hodnoceni.body, hodnoceni.reseni.cas_doruceni) |
||||
|
|
||||
|
hodnoty = [] |
||||
|
for resitel in self.resitele: |
||||
|
resiteluv_radek = [] |
||||
|
for problem in self.zadane_problemy: |
||||
|
if problem in tabulka and resitel in tabulka[problem]: |
||||
|
resiteluv_radek.append(tabulka[problem][resitel]) |
||||
|
else: |
||||
|
resiteluv_radek.append(None) |
||||
|
hodnoty.append(resiteluv_radek) |
||||
|
ctx['radky'] = list(zip(self.resitele, hodnoty)) |
||||
|
|
||||
|
return ctx |
||||
|
|
||||
|
class ReseniProblemuView(ListView): |
||||
|
model = m.Reseni |
||||
|
template_name = 'seminar/odevzdavatko/seznam.html' |
||||
|
|
||||
|
def get_queryset(self): |
||||
|
qs = super().get_queryset() |
||||
|
resitel_id = self.kwargs['resitel'] |
||||
|
if resitel_id is None: |
||||
|
raise ValueError("Nemám řešitele!") |
||||
|
problem_id = self.kwargs['problem'] |
||||
|
if problem_id is None: |
||||
|
raise ValueError("Nemám problém! (To je problém!)") |
||||
|
|
||||
|
resitel = m.Resitel.objects.get(id=resitel_id) |
||||
|
problem = m.Problem.objects.get(id=problem_id) |
||||
|
qs = qs.filter( |
||||
|
problem__in=[problem], |
||||
|
resitele__in=[resitel], |
||||
|
) |
||||
|
return qs |
||||
|
|
||||
|
# Kontext automaticky? |
||||
|
|
||||
|
class DetailReseniView(DetailView): |
||||
|
model = m.Reseni |
||||
|
template_name = 'seminar/odevzdavatko/detail.html' |
||||
|
# To je všechno? Najde se to podle pk... |
||||
|
|
||||
|
# Přehled všech řešení kvůli debugování |
||||
|
|
||||
|
class SeznamReseniView(ListView): |
||||
|
model = m.Reseni |
||||
|
template_name = 'seminar/odevzdavatko/seznam.html' |
||||
|
|
||||
|
class SeznamAktualnichReseniView(SeznamReseniView): |
||||
|
def get_queryset(self): |
||||
|
qs = super().get_queryset() |
||||
|
akt_rocnik = m.Nastaveni.get_solo().aktualni_rocnik # .get_solo() vrátí tu jedinou instanci, asi... |
||||
|
resitele = resi_v_rocniku(akt_rocnik) |
||||
|
qs = qs.filter(resitele__in=resitele) # FIXME: Najde řešení i ze starých ročníků, která odevzdal alespoň jeden aktuální řešitel |
||||
|
return qs |
Loading…
Reference in new issue