Browse Source

Merge branch 'data_migrations' of gimli.ms.mff.cuni.cz:/akce/mam/git/mamweb into data_migrations

export_seznamu_prednasek
Kateřina Č 4 years ago
parent
commit
d5c4884a35
  1. 817
      data/sitetree_new.json
  2. 4
      mamweb/settings_common.py
  3. 1
      seminar/.~lock.profile_vysledkovka.txt#
  4. 27
      seminar/models.py
  5. 7
      seminar/permissions.py
  6. 97
      seminar/templates/seminar/archiv/cislo-normal.html
  7. 19
      seminar/templates/seminar/archiv/problem_tema.html
  8. 23
      seminar/templates/seminar/archiv/problem_uloha.html
  9. 47
      seminar/templates/seminar/odevzdavatko/detail.html
  10. 11
      seminar/templates/seminar/odevzdavatko/seznam.html
  11. 36
      seminar/templates/seminar/odevzdavatko/tabulka.html
  12. 27
      seminar/templatetags/utils.py
  13. 4
      seminar/testutils.py
  14. 5
      seminar/urls.py
  15. 14
      seminar/utils.py
  16. 1
      seminar/views/__init__.py
  17. 129
      seminar/views/odevzdavatko.py
  18. 89
      seminar/views/views_all.py
  19. 31
      seminar/viewsets.py

817
data/sitetree_new.json

File diff suppressed because one or more lines are too long

4
mamweb/settings_common.py

@ -123,6 +123,7 @@ INSTALLED_APPS = (
'webpack_loader', 'webpack_loader',
'rest_framework', 'rest_framework',
'rest_framework.authtoken',
# MaMweb # MaMweb
'mamweb', 'mamweb',
@ -294,6 +295,9 @@ LOGGING = {
}, },
} }
# Permissions for uploads
FILE_UPLOAD_PERMISSIONS = 0o0644
# MaM specific # MaM specific
SEMINAR_RESENI_DIR = os.path.join('reseni') SEMINAR_RESENI_DIR = os.path.join('reseni')

1
seminar/.~lock.profile_vysledkovka.txt#

@ -0,0 +1 @@
,anet,erebus,25.03.2020 22:21,file:///home/anet/.config/libreoffice/4;

27
seminar/models.py

@ -743,12 +743,20 @@ class Problem(SeminarModelBase,PolymorphicModel):
return '<Není zadaný>' return '<Není zadaný>'
def verejne(self): def verejne(self):
# FIXME: Tohle se liší podle typu problému, má se udělat polymorfně. # aktuálně podle stavu problému
# Zatím je tu jen dummy fail-safe default: nic není veřejné. # FIXME pro některé problémy možná chceme override
# Doporučené řešení: dělat tohle podle stavu problému a veřejnosti čísla, ve kterém je # FIXME vrací veřejnost čistě problému, nezávisle na čísle, ve kterém je.
return False # Je to tak správně?
# FIXME: Tohle je blbost stav_verejny = False
return (self.cislo_zadani and self.cislo_zadani.verejne()) 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 verejne.boolean = True
def verejne_url(self): def verejne_url(self):
@ -986,7 +994,7 @@ def aux_generate_filename(self, filename):
unidecode(filename.replace('/', '-').replace('\0', '')) unidecode(filename.replace('/', '-').replace('\0', ''))
) )
datedir = timezone.now().strftime('%Y-%m') datedir = timezone.now().strftime('%Y-%m')
fname = "{}_{}".format( fname = "{}/{}".format(
timezone.now().strftime('%Y-%m-%d-%H:%M'), timezone.now().strftime('%Y-%m-%d-%H:%M'),
clean) clean)
return os.path.join(datedir, fname) return os.path.join(datedir, fname)
@ -1039,6 +1047,11 @@ class PrilohaReseni(SeminarModelBase):
def __str__(self): def __str__(self):
return str(self.soubor) 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): class Pohadka(SeminarModelBase):
"""Kus pohádky před/za úlohou v čísle""" """Kus pohádky před/za úlohou v čísle"""

7
seminar/permissions.py

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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")

4
seminar/testutils.py

@ -140,7 +140,7 @@ def gen_resitele(rnd, osoby, skoly):
x += 1 x += 1
os.user = user os.user = user
os.save() 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), resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly),
rok_maturity=rnd.randint(2019, 2029), rok_maturity=rnd.randint(2019, 2029),
zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0])) zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]))
@ -199,7 +199,7 @@ def gen_organizatori(rnd, osoby, last_rocnik):
x += 1 x += 1
os.user = user os.user = user
os.save() os.save()
os.user.user_permissions.add(org_perm) os.user.user_permissions.add(org_perm)
organizatori.append(Organizator.objects.create(osoba=os, organizatori.append(Organizator.objects.create(osoba=os,
organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga)) organizuje_od=od, organizuje_do=do, strucny_popis_organizatora = popis_orga))
return organizatori return organizatori

5
seminar/urls.py

@ -168,5 +168,10 @@ urlpatterns = [
# org_member_required(views.OrganizatorAutocomplete.as_view()), # org_member_required(views.OrganizatorAutocomplete.as_view()),
# name='seminar_autocomplete_organizator') # 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())),
] ]

14
seminar/utils.py

@ -148,16 +148,12 @@ def resi_v_rocniku(rocnik, cislo=None):
if cislo is None: if cislo is None:
# filtrujeme pouze podle ročníku # 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 else: # filtrujeme podle ročníku i čísla
letosni_reseni = m.Reseni.objects.filter(hodnoceni__cislo_body__rocnik=rocnik, return m.Resitel.objects.filter(rok_maturity__gte=rocnik.druhy_rok(),
hodnoceni__cislo_body__poradi__lte=cislo.poradi) reseni__hodnoceni__cislo_body__rocnik=rocnik,
reseni__hodnoceni__cislo_body__poradi__lte=cislo.poradi).distinct()
# 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()
def aktivniResitele(cislo, pouze_letosni=False): def aktivniResitele(cislo, pouze_letosni=False):

1
seminar/views/__init__.py

@ -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 *

129
seminar/views/odevzdavatko.py

@ -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

89
seminar/views/views_all.py

@ -1,4 +1,4 @@
# coding:utf-8
from django.shortcuts import get_object_or_404, render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse 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.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction from django.db import transaction
from django.core import serializers from django.core import serializers
from django.core.exceptions import PermissionDenied
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
import seminar.models as s import seminar.models as s
@ -120,14 +121,57 @@ class TNLData(object):
self.appendable_siblings = tnltt.appendableChildren(self.parent) self.appendable_siblings = tnltt.appendableChildren(self.parent)
else: else:
self.appendable_siblings = [] 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 @classmethod
def from_treenode(cls,anode,parent=None,index=None): def from_treenode(cls, anode, user, parent=None, index=None):
out = cls(anode,parent,index) if TNLData.public_above(anode) or user.has_perm('auth.org'):
for (idx,ch) in enumerate(treelib.all_children(anode)): out = cls(anode,parent,index)
outitem = cls.from_treenode(ch,out,idx) 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.children.append(outitem)
out.add_edit_options() out.add_edit_options()
return out return out
@ -194,7 +238,7 @@ class TreeNodeView(generic.DetailView):
def get_context_data(self,**kwargs): def get_context_data(self,**kwargs):
context = super().get_context_data(**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 return context
class TreeNodeJSONView(generic.DetailView): class TreeNodeJSONView(generic.DetailView):
@ -202,7 +246,7 @@ class TreeNodeJSONView(generic.DetailView):
def get(self,request,*args, **kwargs): def get(self,request,*args, **kwargs):
self.object = self.get_object() 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) return JsonResponse(data)
@ -331,6 +375,7 @@ class ProblemView(generic.DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
user = self.request.user
# Teď potřebujeme doplnit tnldata do kontextu. # Teď potřebujeme doplnit tnldata do kontextu.
# Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME. # Ošklivý type switch, hezčí by bylo udělat to polymorfni. FIXME.
if False: if False:
@ -338,11 +383,11 @@ class ProblemView(generic.DetailView):
pass pass
elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera): elif isinstance(self.object, s.Clanek) or isinstance(self.object, s.Konfera):
# Tyhle Problémy mají ŘešeníNode # 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): 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 # 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_zadani = TNLData.from_treenode(self.object.ulohazadaninode,user)
tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode) tnl_vzorak = TNLData.from_treenode(self.object.ulohavzoraknode,user)
context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak]) context['tnldata'] = TNLData.from_tnldata_list([tnl_zadani, tnl_vzorak])
elif isinstance(self.object, s.Tema): elif isinstance(self.object, s.Tema):
rocniknode = self.object.rocnik.rocniknode rocniknode = self.object.rocnik.rocniknode
@ -384,16 +429,16 @@ class AktualniZadaniView(generic.TemplateView):
# ) # )
# #
def ZadaniTemataView(request): def ZadaniTemataView(request):
nastaveni = get_object_or_404(Nastaveni) nastaveni = get_object_or_404(Nastaveni)
verejne = nastaveni.aktualni_cislo.verejne() verejne = nastaveni.aktualni_cislo.verejne()
akt_rocnik = nastaveni.aktualni_cislo.rocnik akt_rocnik = nastaveni.aktualni_cislo.rocnik
temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany') temata = s.Tema.objects.filter(rocnik=akt_rocnik, stav='zadany')
return render(request, 'seminar/tematka/rozcestnik.html', return render(request, 'seminar/tematka/rozcestnik.html',
{ {
'tematka': temata, 'tematka': temata,
'verejne': verejne, 'verejne': verejne,
}, },
) )
# nastaveni = get_object_or_404(Nastaveni) # nastaveni = get_object_or_404(Nastaveni)

31
seminar/viewsets.py

@ -1,7 +1,23 @@
from rest_framework import viewsets,filters from rest_framework import viewsets,filters
from rest_framework.permissions import BasePermission, AllowAny
from . import models as m from . import models as m
from . import views 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): class ReadWriteSerializerMixin(object):
""" """
Overrides get_serializer_class to choose the read serializer Overrides get_serializer_class to choose the read serializer
@ -46,27 +62,27 @@ class ReadWriteSerializerMixin(object):
) )
return self.create_serializer_class return self.create_serializer_class
class UlohaVzorakNodeViewSet(viewsets.ModelViewSet): class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
queryset = m.UlohaVzorakNode.objects.all() queryset = m.UlohaVzorakNode.objects.all()
serializer_class = views.UlohaVzorakNodeSerializer serializer_class = views.UlohaVzorakNodeSerializer
class TextViewSet(viewsets.ModelViewSet): class TextViewSet(PermissionMixin, viewsets.ModelViewSet):
queryset = m.Text.objects.all() queryset = m.Text.objects.all()
serializer_class = views.TextSerializer serializer_class = views.TextSerializer
class TextNodeViewSet(ReadWriteSerializerMixin,viewsets.ModelViewSet): class TextNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
queryset = m.TextNode.objects.all() queryset = m.TextNode.objects.all()
read_serializer_class = views.TextNodeSerializer read_serializer_class = views.TextNodeSerializer
write_serializer_class = views.TextNodeWriteSerializer write_serializer_class = views.TextNodeWriteSerializer
create_serializer_class = views.TextNodeCreateSerializer create_serializer_class = views.TextNodeCreateSerializer
class CastNodeViewSet(ReadWriteSerializerMixin,viewsets.ModelViewSet): class CastNodeViewSet(PermissionMixin, ReadWriteSerializerMixin,viewsets.ModelViewSet):
queryset = m.CastNode.objects.all() queryset = m.CastNode.objects.all()
read_serializer_class = views.CastNodeSerializer read_serializer_class = views.CastNodeSerializer
write_serializer_class = views.CastNodeSerializer write_serializer_class = views.CastNodeSerializer
create_serializer_class = views.CastNodeCreateSerializer create_serializer_class = views.CastNodeCreateSerializer
class UlohaVzorakNodeViewSet(viewsets.ModelViewSet): class UlohaVzorakNodeViewSet(PermissionMixin, viewsets.ModelViewSet):
serializer_class = views.UlohaVzorakNodeSerializer serializer_class = views.UlohaVzorakNodeSerializer
def get_queryset(self): def get_queryset(self):
@ -74,4 +90,7 @@ class UlohaVzorakNodeViewSet(viewsets.ModelViewSet):
nazev = self.request.query_params.get('nazev',None) nazev = self.request.query_params.get('nazev',None)
if nazev is not None: if nazev is not None:
queryset = queryset.filter(nazev__contains=nazev) 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…
Cancel
Save