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 .autocomplete 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