Merge branch 'data_migrations' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into data_migrations
This commit is contained in:
commit
d5c4884a35
19 changed files with 1341 additions and 46 deletions
File diff suppressed because one or more lines are too long
|
@ -123,6 +123,7 @@ INSTALLED_APPS = (
|
|||
|
||||
'webpack_loader',
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
|
||||
# MaMweb
|
||||
'mamweb',
|
||||
|
@ -294,6 +295,9 @@ LOGGING = {
|
|||
},
|
||||
}
|
||||
|
||||
# Permissions for uploads
|
||||
FILE_UPLOAD_PERMISSIONS = 0o0644
|
||||
|
||||
# MaM specific
|
||||
|
||||
SEMINAR_RESENI_DIR = os.path.join('reseni')
|
||||
|
|
1
seminar/.~lock.profile_vysledkovka.txt#
Normal file
1
seminar/.~lock.profile_vysledkovka.txt#
Normal file
|
@ -0,0 +1 @@
|
|||
,anet,erebus,25.03.2020 22:21,file:///home/anet/.config/libreoffice/4;
|
|
@ -743,12 +743,20 @@ class Problem(SeminarModelBase,PolymorphicModel):
|
|||
return '<Není zadaný>'
|
||||
|
||||
def verejne(self):
|
||||
# FIXME: Tohle se liší podle typu problému, má se udělat polymorfně.
|
||||
# Zatím je tu jen dummy fail-safe default: nic není veřejné.
|
||||
# Doporučené řešení: dělat tohle podle stavu problému a veřejnosti čísla, ve kterém je
|
||||
return False
|
||||
# FIXME: Tohle je blbost
|
||||
return (self.cislo_zadani and self.cislo_zadani.verejne())
|
||||
# aktuálně podle stavu problému
|
||||
# FIXME pro některé problémy možná chceme override
|
||||
# FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
|
||||
# Je to tak správně?
|
||||
stav_verejny = False
|
||||
if self.stav == 'zadany' or self.stav == 'vyreseny':
|
||||
stav_verejny = True
|
||||
return stav_verejny
|
||||
|
||||
#cislo_verejne = False
|
||||
#if (self.cislo_zadani and self.cislo_zadani.verejne()):
|
||||
# cislo_verejne = True
|
||||
|
||||
#return (stav_verejny and cislo_verejne)
|
||||
verejne.boolean = True
|
||||
|
||||
def verejne_url(self):
|
||||
|
@ -986,7 +994,7 @@ def aux_generate_filename(self, filename):
|
|||
unidecode(filename.replace('/', '-').replace('\0', ''))
|
||||
)
|
||||
datedir = timezone.now().strftime('%Y-%m')
|
||||
fname = "{}_{}".format(
|
||||
fname = "{}/{}".format(
|
||||
timezone.now().strftime('%Y-%m-%d-%H:%M'),
|
||||
clean)
|
||||
return os.path.join(datedir, fname)
|
||||
|
@ -1039,6 +1047,11 @@ class PrilohaReseni(SeminarModelBase):
|
|||
def __str__(self):
|
||||
return str(self.soubor)
|
||||
|
||||
def split(self):
|
||||
"Vrátí cestu rozsekanou po složkách. To se hodí v templatech"
|
||||
# Věřím, že tohle funguje, případně použít os.path nebo pathlib.
|
||||
return self.soubor.url.split('/')
|
||||
|
||||
|
||||
class Pohadka(SeminarModelBase):
|
||||
"""Kus pohádky před/za úlohou v čísle"""
|
||||
|
|
7
seminar/permissions.py
Normal file
7
seminar/permissions.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from rest_framework.permissions import BasePermission
|
||||
|
||||
class AllowWrite(BasePermission):
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return request.user.has_perm('auth.org')
|
||||
|
97
seminar/templates/seminar/archiv/cislo-normal.html
Normal file
97
seminar/templates/seminar/archiv/cislo-normal.html
Normal file
|
@ -0,0 +1,97 @@
|
|||
{% extends "seminar/archiv/base_cisla.html" %}
|
||||
|
||||
{# {% block content %}
|
||||
<div>
|
||||
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
Číslo {{ cislo }}
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
|
||||
{% if cislo.pdf %}
|
||||
<p><a href='{{ cislo.pdf.url }}'>Číslo v pdf</a>
|
||||
{% endif %}
|
||||
<p><a href='{{ cislo.rocnik.verejne_url }}'>Ročník {{ cislo.rocnik }}</a>
|
||||
|
||||
{% if v_cisle_zadane %}
|
||||
<h2>Zadané problémy</h2>
|
||||
<ul>
|
||||
{% for p in v_cisle_zadane %}
|
||||
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}>
|
||||
{% if user.is_staff or cislo.verejne %}
|
||||
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if resene_problemy %}
|
||||
<h2>Řešené problémy</h2>
|
||||
<ul>
|
||||
{% for p in resene_problemy %}
|
||||
<li{% if user.is_staff and not cislo.verejne %} class='mam-org-only'{% endif %}>
|
||||
{% if user.is_staff or cislo.verejne %}
|
||||
<a href='{{ p.verejne_url }}'>{% endif %}{{ p.kod_v_rocniku }} {{ p.nazev }} {{ p.body_v_zavorce }}{% if user.is_staff or cislo.verejne %}</a>{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_staff %}
|
||||
<div class="mam-org-only">
|
||||
<h2> Orgovské odkazy </h2>
|
||||
<ul>
|
||||
<li><a href="obalky.pdf">Obálky (PDF)</a></li>
|
||||
<li><a href="tituly.tex">Tituly (TeX)</a></li>
|
||||
<li><a href="vysledkovka.tex">Výsledkovka (TeX)</a></li>
|
||||
<li><a href="obalkovani">Obálkování</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if cislo.verejna_vysledkovka %}
|
||||
<h2>Výsledkovka</h2>
|
||||
{% else %}
|
||||
{% if user.is_staff %}
|
||||
<div class='mam-org-only'>
|
||||
<h2>Výsledkovka (neveřejná)</h2>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if cislo.verejna_vysledkovka or user.is_staff %}
|
||||
<table class='vysledkovka'>
|
||||
<tr class='border-b'>
|
||||
<th class='border-r'>#
|
||||
<th class='border-r'>Jméno #}
|
||||
{# problémy by měly být veřejné, když je veřejná výsledkovka #}
|
||||
{# {% for p in problemy %}
|
||||
<th class='border-r'><a href="{{ p.verejne_url }}">{{ p.kod_v_rocniku }}</a>
|
||||
{% endfor %}
|
||||
<th class='border-r'>Za číslo</sup>
|
||||
<th class='border-r'>Za ročník
|
||||
<th class='border-r'>Odjakživa
|
||||
{% for rv in radky_vysledkovky %}
|
||||
<tr>
|
||||
<td class='border-r'>{% autoescape off %}{{ rv.poradi }}{% endautoescape %}
|
||||
<th class='border-r'>
|
||||
{% if rv.resitel.titul != "" %}
|
||||
{{ rv.resitel.titul }}<sup>MM</sup>
|
||||
{% endif %}
|
||||
{{ rv.resitel.osoba.plne_jmeno }}
|
||||
{% for b in rv.hlavni_problemy_body %}
|
||||
<td class='border-r'>{{ b }}
|
||||
{% endfor %}
|
||||
<td class='border-r'>{{ rv.body_cislo }}
|
||||
<td class='border-r'><b>{{ rv.body_rocnik }}</b>
|
||||
<td class='border-r'>{{ rv.body_celkem_odjakziva }}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if not cislo.verejna_vysledkovka and user.is_staff %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock content %} #}
|
||||
|
19
seminar/templates/seminar/archiv/problem_tema.html
Normal file
19
seminar/templates/seminar/archiv/problem_tema.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "seminar/archiv/problem.html" %}
|
||||
|
||||
{% block problem %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
{{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }}
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
|
||||
<h2>Zadání</h2>
|
||||
{{ problem.text_zadani |safe }}
|
||||
{% if problem.text_reseni %}
|
||||
<h2>Řešení</h2>
|
||||
{{ problem.text_reseni |safe }}
|
||||
{% endif %}
|
||||
|
||||
{# TODO vysledkovka tematu #}
|
||||
|
||||
{% endblock %}
|
23
seminar/templates/seminar/archiv/problem_uloha.html
Normal file
23
seminar/templates/seminar/archiv/problem_uloha.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{% extends "seminar/archiv/problem.html" %}
|
||||
|
||||
{% block problem %}
|
||||
<h1>
|
||||
{% block nadpis1a %}{% block nadpis1b %}
|
||||
{{ problem.nazev_typu }} {{ problem.kod_v_rocniku }}: {{ problem.nazev }} {{ problem.body_v_zavorce }}
|
||||
{% endblock %}{% endblock %}
|
||||
</h1>
|
||||
{% if problem.cislo_zadani %}
|
||||
<p>Zadáno v čísle <a href='{{ problem.cislo_zadani.verejne_url }}'>{{ problem.cislo_zadani.kod }}</a>.
|
||||
{% endif %}
|
||||
{% if problem.cislo_reseni %}
|
||||
<p>Řešeno v čísle <a href='{{ problem.cislo_reseni.verejne_url }}'>{{ problem.cislo_reseni.kod }}</a>.
|
||||
{% endif %}
|
||||
|
||||
<h2>Zadání</h2>
|
||||
{{ problem.text_zadani |safe }}
|
||||
{% if problem.text_reseni %}
|
||||
<h2>Řešení</h2>
|
||||
{{ problem.text_reseni |safe }}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
47
seminar/templates/seminar/odevzdavatko/detail.html
Normal file
47
seminar/templates/seminar/odevzdavatko/detail.html
Normal file
|
@ -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 %}
|
11
seminar/templates/seminar/odevzdavatko/seznam.html
Normal file
11
seminar/templates/seminar/odevzdavatko/seznam.html
Normal file
|
@ -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 %}
|
36
seminar/templates/seminar/odevzdavatko/tabulka.html
Normal file
36
seminar/templates/seminar/odevzdavatko/tabulka.html
Normal file
|
@ -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 %}
|
27
seminar/templatetags/utils.py
Normal file
27
seminar/templatetags/utils.py
Normal file
|
@ -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")
|
||||
|
|
@ -140,7 +140,7 @@ def gen_resitele(rnd, osoby, skoly):
|
|||
x += 1
|
||||
os.user = user
|
||||
os.save()
|
||||
os.user.user_permissions.add(resitel_perm)
|
||||
os.user.user_permissions.add(resitel_perm)
|
||||
resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly),
|
||||
rok_maturity=rnd.randint(2019, 2029),
|
||||
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]))
|
||||
|
@ -199,7 +199,7 @@ def gen_organizatori(rnd, osoby, last_rocnik):
|
|||
x += 1
|
||||
os.user = user
|
||||
os.save()
|
||||
os.user.user_permissions.add(org_perm)
|
||||
os.user.user_permissions.add(org_perm)
|
||||
organizatori.append(Organizator.objects.create(osoba=os,
|
||||
organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga))
|
||||
return organizatori
|
||||
|
|
|
@ -168,5 +168,10 @@ urlpatterns = [
|
|||
# org_member_required(views.OrganizatorAutocomplete.as_view()),
|
||||
# name='seminar_autocomplete_organizator')
|
||||
|
||||
path('temp/reseni/', org_required(views.TabulkaOdevzdanychReseniView.as_view()), name='odevzdavatko_tabulka'),
|
||||
path('temp/reseni/<int:problem>/<int:resitel>/', org_required(views.ReseniProblemuView.as_view()), name='odevzdavatko_reseni_resitele_k_problemu'),
|
||||
path('temp/reseni/<int:pk>', org_required(views.DetailReseniView.as_view()), name='odevzdavatko_detail_reseni'),
|
||||
path('temp/reseni/all', org_required(views.SeznamReseniView.as_view())),
|
||||
path('temp/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
|
||||
|
||||
]
|
||||
|
|
|
@ -148,16 +148,12 @@ def resi_v_rocniku(rocnik, cislo=None):
|
|||
|
||||
if cislo is None:
|
||||
# filtrujeme pouze podle ročníku
|
||||
letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik)
|
||||
return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
|
||||
reseni__hodnoceni__cislo_body__rocnik=rocnik).distinct()
|
||||
else: # filtrujeme podle ročníku i čísla
|
||||
letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik,
|
||||
hodnoceni__cislo_body__poradi__lte=cislo.poradi)
|
||||
|
||||
# vygenerujeme queryset řešitelů, co letos něco poslali
|
||||
letosni_resitele = m.Resitel.objects.none()
|
||||
for reseni in letosni_reseni:
|
||||
letosni_resitele = letosni_resitele | reseni.resitele.filter(rok_maturity__gte=rocnik.druhy_rok())
|
||||
return letosni_resitele.distinct()
|
||||
return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
|
||||
reseni__hodnoceni__cislo_body__rocnik=rocnik,
|
||||
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct()
|
||||
|
||||
|
||||
def aktivniResitele(cislo, pouze_letosni=False):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from .views_all import *
|
||||
from .autocomplete import *
|
||||
from .views_rest import *
|
||||
from .odevzdavatko import *
|
||||
|
|
129
seminar/views/odevzdavatko.py
Normal file
129
seminar/views/odevzdavatko.py
Normal file
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
# coding:utf-8
|
||||
|
||||
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse
|
||||
|
@ -17,6 +17,7 @@ from django.contrib.auth.models import User, Permission
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import transaction
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
import seminar.models as s
|
||||
|
@ -120,14 +121,57 @@ class TNLData(object):
|
|||
self.appendable_siblings = tnltt.appendableChildren(self.parent)
|
||||
else:
|
||||
self.appendable_siblings = []
|
||||
@classmethod
|
||||
def public_above(cls, anode):
|
||||
""" Returns output of verejne for closest Rocnik, Cislo or Problem above.
|
||||
(All of them have method verejne.)"""
|
||||
parent = anode # chceme začít už od konkrétního node včetně
|
||||
while True:
|
||||
rocnik = isinstance(parent, s.RocnikNode)
|
||||
cislo = isinstance(parent, s.CisloNode)
|
||||
uloha = (isinstance(parent, s.UlohaVzorakNode) or
|
||||
isinstance(parent, s.UlohaZadaniNode))
|
||||
tema = isinstance(parent, s.TemaVCisleNode)
|
||||
|
||||
|
||||
if (rocnik or cislo or uloha or tema) or parent==None:
|
||||
break
|
||||
else:
|
||||
parent = treelib.get_parent(parent)
|
||||
if rocnik:
|
||||
return parent.rocnik.verejne()
|
||||
elif cislo:
|
||||
return parent.cislo.verejne()
|
||||
elif uloha:
|
||||
return parent.uloha.verejne()
|
||||
elif tema:
|
||||
return parent.tema.verejne()
|
||||
elif None:
|
||||
print("Existuje TreeNode, který není pod číslem, ročníkem, úlohou"
|
||||
"ani tématem. {}".format(anode))
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def all_public_children(cls, anode):
|
||||
for ch in treelib.all_children(anode):
|
||||
if TNLData.public_above(ch):
|
||||
yield ch
|
||||
else:
|
||||
continue
|
||||
|
||||
@classmethod
|
||||
def from_treenode(cls,anode,parent=None,index=None):
|
||||
out = cls(anode,parent,index)
|
||||
for (idx,ch) in enumerate(treelib.all_children(anode)):
|
||||
outitem = cls.from_treenode(ch,out,idx)
|
||||
def from_treenode(cls, anode, user, parent=None, index=None):
|
||||
if TNLData.public_above(anode) or user.has_perm('auth.org'):
|
||||
out = cls(anode,parent,index)
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
|
||||
if user.has_perm('auth.org'):
|
||||
enum_children = enumerate(treelib.all_children(anode))
|
||||
else:
|
||||
enum_children = enumerate(TNLData.all_public_children(anode))
|
||||
|
||||
for (idx,ch) in enum_children:
|
||||
outitem = cls.from_treenode(ch, user, out, idx)
|
||||
out.children.append(outitem)
|
||||
out.add_edit_options()
|
||||
return out
|
||||
|
@ -194,7 +238,7 @@ class TreeNodeView(generic.DetailView):
|
|||
|
||||
def get_context_data(self,**kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['tnldata'] = TNLData.from_treenode(self.object)
|
||||
context['tnldata'] = TNLData.from_treenode(self.object,self.request.user)
|
||||
return context
|
||||
|
||||
class TreeNodeJSONView(generic.DetailView):
|
||||
|
@ -202,7 +246,7 @@ class TreeNodeJSONView(generic.DetailView):
|
|||
|
||||
def get(self,request,*args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
data = TNLData.from_treenode(self.object).to_json()
|
||||
data = TNLData.from_treenode(self.object,self.request.user).to_json()
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
|
@ -331,6 +375,7 @@ class ProblemView(generic.DetailView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
user = self.request.user
|
||||
# Teď potřebujeme doplnit tnldata do kontextu.
|
||||
# Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
|
||||
if False:
|
||||
|
@ -338,11 +383,11 @@ class ProblemView(generic.DetailView):
|
|||
pass
|
||||
elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera):
|
||||
# Tyhle Problémy mají ŘešeníNode
|
||||
context['tnldata'] = TNLData.from_treenode(self.object.reseninode)
|
||||
context['tnldata'] = TNLData.from_treenode(self.object.reseninode,user)
|
||||
elif isinstance(self.object, s.Uloha):
|
||||
# FIXME: Teď vždycky zobrazujeme i vzorák! Možná by bylo hezčí/lepší mít to stejně jako pro Téma: procházet jen dosažitelné z Ročníku / čísla / whatever
|
||||
tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode)
|
||||
tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode)
|
||||
tnl_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
|
||||
tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
|
||||
context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
|
||||
elif isinstance(self.object, s.Tema):
|
||||
rocniknode = self.object.rocnik.rocniknode
|
||||
|
@ -384,16 +429,16 @@ class AktualniZadaniView(generic.TemplateView):
|
|||
# )
|
||||
#
|
||||
def ZadaniTemataView(request):
|
||||
nastaveni = get_object_or_404(Nastaveni)
|
||||
verejne = nastaveni.aktualni_cislo.verejne()
|
||||
akt_rocnik = nastaveni.aktualni_cislo.rocnik
|
||||
temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
|
||||
return render(request, 'seminar/tematka/rozcestnik.html',
|
||||
{
|
||||
'tematka': temata,
|
||||
'verejne': verejne,
|
||||
},
|
||||
)
|
||||
nastaveni = get_object_or_404(Nastaveni)
|
||||
verejne = nastaveni.aktualni_cislo.verejne()
|
||||
akt_rocnik = nastaveni.aktualni_cislo.rocnik
|
||||
temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
|
||||
return render(request, 'seminar/tematka/rozcestnik.html',
|
||||
{
|
||||
'tematka': temata,
|
||||
'verejne': verejne,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# nastaveni = get_object_or_404(Nastaveni)
|
||||
|
|
|
@ -1,7 +1,23 @@
|
|||
from rest_framework import viewsets,filters
|
||||
from rest_framework.permissions import BasePermission, AllowAny
|
||||
from . import models as m
|
||||
from . import views
|
||||
|
||||
from seminar.permissions import AllowWrite
|
||||
|
||||
class PermissionMixin(object):
|
||||
""" Redefines get_permissions so that only organizers can make changes. """
|
||||
|
||||
def get_permissions(self):
|
||||
permission_classes = []
|
||||
print("get_permissions have been called.")
|
||||
if self.action in ["create", "update", "partial_update", "destroy"]:
|
||||
permission_classes = [AllowWrite] # speciální permission na zápis - orgové
|
||||
else:
|
||||
permission_classes = [AllowAny]
|
||||
# návštěvník nemusí být zalogován, aby si prohlížel obsah
|
||||
return [permission() for permission in permission_classes]
|
||||
|
||||
class ReadWriteSerializerMixin(object):
|
||||
"""
|
||||
Overrides get_serializer_class to choose the read serializer
|
||||
|
@ -46,27 +62,27 @@ class ReadWriteSerializerMixin(object):
|
|||
)
|
||||
return self.create_serializer_class
|
||||
|
||||
class UlohaVzorakNodeViewSet(viewsets.ModelViewSet):
|
||||
class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
|
||||
queryset = m.UlohaVzorakNode.objects.all()
|
||||
serializer_class = views.UlohaVzorakNodeSerializer
|
||||
|
||||
class TextViewSet(viewsets.ModelViewSet):
|
||||
class TextViewSet(PermissionMixin, viewsets.ModelViewSet):
|
||||
queryset = m.Text.objects.all()
|
||||
serializer_class = views.TextSerializer
|
||||
|
||||
class TextNodeViewSet(ReadWriteSerializerMixin,viewsets.ModelViewSet):
|
||||
class TextNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
|
||||
queryset = m.TextNode.objects.all()
|
||||
read_serializer_class = views.TextNodeSerializer
|
||||
write_serializer_class = views.TextNodeWriteSerializer
|
||||
create_serializer_class = views.TextNodeCreateSerializer
|
||||
|
||||
class CastNodeViewSet(ReadWriteSerializerMixin,viewsets.ModelViewSet):
|
||||
class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
|
||||
queryset = m.CastNode.objects.all()
|
||||
read_serializer_class = views.CastNodeSerializer
|
||||
write_serializer_class = views.CastNodeSerializer
|
||||
create_serializer_class = views.CastNodeCreateSerializer
|
||||
|
||||
class UlohaVzorakNodeViewSet(viewsets.ModelViewSet):
|
||||
class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
|
||||
serializer_class = views.UlohaVzorakNodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -74,4 +90,7 @@ class UlohaVzorakNodeViewSet(viewsets.ModelViewSet):
|
|||
nazev = self.request.query_params.get('nazev',None)
|
||||
if nazev is not None:
|
||||
queryset = queryset.filter(nazev__contains=nazev)
|
||||
return queryset
|
||||
if self.request.user.has_perm('auth.org'):
|
||||
return queryset
|
||||
else: # pro neorgy jen zveřejněné vzoráky
|
||||
return queryset.filter(uloha__cislo_reseni__verejne_db=True)
|
||||
|
|
Loading…
Reference in a new issue