Browse Source

Merge branch 'master' into dockerizace

dockerizace
Jonas Havelka 5 months ago
parent
commit
59f7f30b99
  1. 4
      aesop/ovvpfile.py
  2. 4
      aesop/utils.py
  3. 2
      aesop/views.py
  4. 1
      docs/index.rst
  5. 25
      docs/tabulka_prerekvizit.rst
  6. 2
      docs/vyvoj.rst
  7. 97
      docs/zavislosti.rst
  8. 1
      galerie/models.py
  9. 1
      korektury/models.py
  10. 1
      korektury/views.py
  11. 4
      mamweb/admin.py
  12. 5
      mamweb/settings_common.py
  13. 4
      mamweb/settings_prod.py
  14. 1
      mamweb/static/css/mamweb.css
  15. 2
      mamweb/templates/500.html
  16. 2
      mamweb/urls.py
  17. 2
      odevzdavatko/forms.py
  18. 15
      odevzdavatko/templates/odevzdavatko/tabulka.html
  19. 15
      odevzdavatko/templatetags/barvy_reseni.py
  20. 54
      odevzdavatko/views.py
  21. 18
      personalni/admin.py
  22. 63
      personalni/tests.py
  23. 1
      prednasky/models.py
  24. 8
      requirements.txt
  25. 35
      seminar/admin.py
  26. 3
      seminar/migrations/0006_problem_add_timestamp.py
  27. 40
      seminar/migrations/0114_related_name_se_zmenilo_a_django_chce_migraci_tak_dostane_migraci.py
  28. 17
      seminar/models/tvorba.py
  29. 4
      seminar/templates/seminar/archiv/cisla.html
  30. 6
      seminar/templates/seminar/archiv/cislo.html
  31. 5
      seminar/templates/seminar/archiv/rocnik.html
  32. 2
      seminar/views/views_all.py
  33. 0
      sifrovacka/__init__.py
  34. 8
      sifrovacka/admin.py
  35. 5
      sifrovacka/apps.py
  36. 18
      sifrovacka/forms.py
  37. 34
      sifrovacka/migrations/0001_initial.py
  38. 28
      sifrovacka/migrations/0002_auto_20231015_1944.py
  39. 18
      sifrovacka/migrations/0003_odpoveducastnika_uspech.py
  40. 0
      sifrovacka/migrations/__init__.py
  41. 27
      sifrovacka/models.py
  42. 25
      sifrovacka/templates/sifrovacka/odpovedi_list.html
  43. 46
      sifrovacka/templates/sifrovacka/sifrovacka.html
  44. 17
      sifrovacka/urls.py
  45. 33
      sifrovacka/views.py
  46. 2
      soustredeni/admin.py
  47. 87
      various/static/various/img/zere_kostku.svg
  48. 19
      various/templates/various/403_csrf.html
  49. 10
      various/views.py
  50. 6
      vysledkovky/templates/vysledkovky/vysledkovka_cisla.html
  51. 8
      vysledkovky/utils.py

4
aesop/ovvpfile.py

@ -1,5 +1,5 @@
from django.http import HttpResponse
from django.utils.encoding import force_text
from django.utils.encoding import force_str
class OvvpFile:
@ -20,7 +20,7 @@ class OvvpFile:
yield '\t'.join(self.columns) + '\n'
# rows
for r in self.rows:
yield '\t'.join([force_text(r[c]) for c in self.columns]) + '\n'
yield '\t'.join([force_str(r[c]) for c in self.columns]) + '\n'
def to_string(self):
return ''.join(self.to_lines())

4
aesop/utils.py

@ -1,6 +1,6 @@
import datetime
from django.utils.encoding import force_text
from django.utils.encoding import force_str
from aesop.ovvpfile import OvvpFile
@ -9,7 +9,7 @@ def default_ovvpfile(event, rocnik):
of = OvvpFile()
of.headers['version'] = '1'
of.headers['event'] = event
of.headers['year'] = force_text(rocnik.prvni_rok)
of.headers['year'] = force_str(rocnik.prvni_rok)
of.headers['date'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
of.headers['id-scope'] = 'mam'
of.headers['id-generation'] = '1'

2
aesop/views.py

@ -8,7 +8,7 @@ from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.urls import reverse
from django.views import generic
from django.utils.encoding import force_text
from django.utils.encoding import force_str
from .utils import default_ovvpfile
from seminar.models import Rocnik, Soustredeni

1
docs/index.rst

@ -27,6 +27,7 @@ Dokumentace (jak v ``docs/``, tak přímo v kódu) je psaná ve
:titlesonly:
vyvoj
zavislosti
sphinx
skripty
modules/modules

25
docs/tabulka_prerekvizit.rst

@ -1,25 +0,0 @@
.. Není odkázaná z menu, je to záměr
Tabulka prerekvizit v různých distribucích
=========
.. admonition:: Metodika
Na čistém repozitáři (``git clean -fxd``) a čistém systému spouštíme
``make/init_local``. Když to spadne, tak do tabulky zapíšeme, co jsme
přiinstalovali. Protože větev ``makefiles`` aktuálně není mergenutá do
masteru, nefunguje synchronizace flatpages (a stejně nemáme SSH klíč), takže
tam ``make/init_local`` sestřelíme a vyzkoušíme, že ``make/test`` spustí
testy.
.. Grafické tabulky (grid-tables, simple-tables) jsou strašný porod vyrábět, dlabu na to a cpu to do CSV…
.. csv-table:: Prerekvizity v jednotlivých distribucích
:header: Distribuce / OS, Repozitář s Py3.9, venv, py knihovny, PostgreSQL knihovna, poznámky
Ubuntu 22.10, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, "Je potřeba zapnout zdroj ``universe`` a nainstalovat kompilátor C (``gcc``)?"
Linux Mint 21, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, ""
Archlinux 2022.11.01, AUR, vestavěný, vestavěné, ``postgresql-libs``, "Je potřeba céčkový kompilátor (``gcc``)"
openSUSE Leap 15.4, oficiální (``python39``), předinstalovaný?, ``python39-devel``, ??FIXME!!, "Výchozí verze pythonu je 3.6 a ta je moc stará, potřeba instalovat ``gcc``. Nevím jak sehnat pg_config."
Debian 11, "oficiální, výchozí", ??, ??, ??, "Určitě to tam rozběhat jde, protože Gimli. Nejspíš bude relativně podobné Ubuntu."

2
docs/vyvoj.rst

@ -37,7 +37,7 @@ Kromě toho je potřeba mít účet na `Gitee <https://gitea.ks.matfyz.cz>`_, kd
bydlí gitový repozitář s kódem.
.. tip:: Potřebné balíčky v různých distribucích jsou sepsané v :ref:`tabulce
prerekvizit <Tabulka prerekvizit v různých distribucích>`.
prerekvizit <Alternativní jména balíčků>`.
Doporučené
^^^^^^^^^^

97
docs/zavislosti.rst

@ -0,0 +1,97 @@
Závislosti webu
@@@@@@@@@@@@@@@
Web ke svému běhu potřebuje různé další programy. Tahle stránka se snaží je pokrýt.
Stránka je koncipována jako odrážkový seznam balíčků pro Ubuntu s případnými
komentáři, na konci stránky jsou uvedena :ref:`jména balíčků <Alternativní jména
balíčků>` v různých dalších distribucích. (Seznam mj. cílí na lokální
rozchození, proto popisuji Ubuntu a ne Debian. I tak se ale snažíme popsat web
v úplnosti.)
.. I use Arch, btw.
Základ webu
===========
- ``python3`` – Ideálně Python 3.9, jenž je na Gimlim
- ``python3-pip`` pro instalaci dalších Pythoních balíčků podle ``requirements.txt``
- ``python3-venv``
- ``gcc`` – kompilace Pythoních knihoven ze zdrojových distribucí (sdist), možná (neotestováno) jde jako alternativu použít ``python3-wheel`` a stahovat bdists
- ``python3-dev`` – taktéž
- ``libpq-dev`` do třetice…
- ``ghostscript`` TODO konverze PDF v korekturovátku
- ``pdflatex`` FIXME! generování obálek a stvrzenek
- ``git`` – používán :ref:`Make skripty`
- ``locales`` pro české formáty
Nasazení na produkci / testweb
==============================
(nejsou nutně potřeba k provozu lokální instance)
- ``rsync``
- ``pg_utils`` FIXME
- ``htpasswd`` FIXME – aby testweb nepoužívali náhodní kolemjdoucí
- ``postgresql-server`` TODO
- ``acl`` pro nastavování práv přes ``setfacl``
Pro testweb je potřeba i všechno pro :ref:`dokumentaci <Dokumentace>`, vizte níž.
Předpokládá se nasazení v uWSGI pod Nginxem a služba běžící pod systemd, nicméně to už je spíš záležitost infrastruktury a ne specifikum mamwebu.
Dokumentace
===========
- ``make`` pro zbuildění
- Pythoní balíčky podle příslušné části ``requirements.txt``
Vývojové nástroje
=================
(Nejsou nezbytně nutné, ale předpokládáme jejich užitečnost. Mohou se hodit i na produkci.)
- ``psql`` TODO pro manuální dotazy do PostgreSQL
- ``sqlite3`` TODO totéž pro SQLite3
- ``ssh``
- ``graphviz`` pro vygenerování schématu
- ``rsync``
- ``ipython3`` – hezčí interaktivní shell (stačí z ``requirements.txt``)
Potenciální usnadnění života
============================
(Úplně zbytečné, ale sdílíme pozitivní zkušenosti :-))
- ``tea`` – CLI klient pro Giteu, aby člověk nepotřeboval otevírat web pro založení PR
Alternativní jména balíčků
==========================
Různé distribuce balí SW různě, takže to, co je v jedné distribuci jeden
balíček může být v jiné rozděleno do víc. Pro usnadnění nasazení je tady
přehled známých alternativních jmen.
TODO: tabulka není úplná. Pokud na něco narazíte, tak ji prosím doplňte.
.. admonition:: Jak se pozná, že web funguje, pro účely tabulky?
Na čistém repozitáři (``git clean -fxd``) a čistém systému spouštíme
``make/init_local``. Když to spadne, tak do tabulky zapíšeme, co jsme
přiinstalovali. Protože nefunguje synchronizace flatpages (nemáme SSH klíč),
``make/init_local`` sestřelíme při pokusu o synchronizaci a vyzkoušíme, že
``make/test`` spustí testy.
.. Grafické tabulky (grid-tables, simple-tables) jsou strašný porod vyrábět, dlabu na to a cpu to do CSV…
.. csv-table:: Prerekvizity v jednotlivých distribucích
:header: Distribuce / OS, Repozitář s Py3.9, venv, py knihovny, PostgreSQL knihovna, poznámky
Ubuntu 22.10, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, "Je potřeba zapnout zdroj ``universe`` a nainstalovat kompilátor C (``gcc``)?"
Linux Mint 21, ??, ``python3-venv``, ``python3-dev``, ``libpq-dev``, ""
Archlinux 2022.11.01, AUR, vestavěný, vestavěné, ``postgresql-libs``, "Je potřeba céčkový kompilátor (``gcc``)"
openSUSE Leap 15.4, oficiální (``python39``), předinstalovaný?, ``python39-devel``, ??FIXME!!, "Výchozí verze pythonu je 3.6 a ta je moc stará, potřeba instalovat ``gcc``. Nevím jak sehnat pg_config."
Debian 11, "oficiální, výchozí", ??, ??, ??, "Určitě to tam rozběhat jde, protože Gimli. Nejspíš bude relativně podobné Ubuntu."

1
galerie/models.py

@ -2,7 +2,6 @@
from django.db import models
#from django.db.models import Q
from django.utils.encoding import force_text
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFit, Transpose

1
korektury/models.py

@ -16,7 +16,6 @@ import os
from django.db import models
from django.utils import timezone
from django.conf import settings
from django.utils.encoding import force_text
from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import cached_property
from django.utils.text import get_valid_filename

1
korektury/views.py

@ -5,7 +5,6 @@ třídy většinou rozšiřující nějakou třídu z :mod:`django.views.generic
"""
from django.shortcuts import get_object_or_404, render
from django.views import generic
from django.utils.translation import ugettext as _
from django.conf import settings
from django.http import HttpResponseForbidden
from django.core.mail import EmailMessage

4
mamweb/admin.py

@ -35,13 +35,13 @@ locale.setlocale(locale.LC_COLLATE, 'cs_CZ.UTF-8')
# https://books.agiliq.com/projects/django-admin-cookbook/en/latest/set_ordering.html
# FIXME zpraseno pomocí toho, že Python umí bez problému přepisovat funkce
def get_app_list(self, request):
def get_app_list(self, request, app_label=None):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
app_dict = self._build_app_dict(request)
app_dict = self._build_app_dict(request, label=app_label)
# Sort the apps alphabetically.
app_list = sorted(app_dict.values(), key=lambda x: locale.strxfrm('!') if (x['name'] == "Seminar") else locale.strxfrm(x['name'].lower()))

5
mamweb/settings_common.py

@ -28,7 +28,6 @@ APPEND_SLASH = True
LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
@ -54,6 +53,9 @@ LOGIN_REDIRECT_URL = 'profil'
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
DOBA_ODHLASENI_PRI_ZASKRTNUTI_NEODHLASOVAT = 365 * 24 * 3600 # rok
# View pro chybu s CSRF tokenem (např. se sušenkami)
CSRF_FAILURE_VIEW = 'various.views.csrf_error'
# Modules configuration
AUTHENTICATION_BACKENDS = (
@ -151,6 +153,7 @@ INSTALLED_APPS = (
'soustredeni',
'treenode',
'vyroci',
'sifrovacka',
# Admin upravy:

4
mamweb/settings_prod.py

@ -20,7 +20,9 @@ INSTALLED_APPS += (
)
# SECURITY WARNING: keep the secret key used in production secret!
assert not SECRET_KEY.startswith('12345')
# `'DOCUTILSCONFIG' in os.environ` kvůli sphinxu
# FIXME zjistit, zda je bezpečné a zda se to nedá udělat lépe
assert 'DOCUTILSCONFIG' in os.environ or not SECRET_KEY.startswith('12345')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

1
mamweb/static/css/mamweb.css

@ -1245,6 +1245,7 @@ div.gdpr {
.dosla_reseni tr th, .dosla_reseni tr td {
padding: 1px 10px 1px 10px;
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) {

2
mamweb/templates/500.html

@ -4,7 +4,9 @@
{% block errorheading %}
<br> {# Meníčko nedostaneme, protože dostáváme prázdný kontext. Tak alespoň ať se O-JO-JO-JO-JOJ neschovává pod ním #}
{% block nadpis1a %}
O-jo-jo-jo-joj
{% endblock %}
{% endblock %}
{% block errortext %}

2
mamweb/urls.py

@ -71,6 +71,8 @@ urlpatterns = [
# Výroční sraz
path('sraz/30-let/', include('vyroci.urls')),
# Miniapka na šifrovačku
path('sifrovacka/', include('sifrovacka.urls')),
]
# This is only needed when using runserver.

2
odevzdavatko/forms.py

@ -238,6 +238,7 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
'reseni_od': terminy[-2] if rocnik is None else terminy[0],
'reseni_do': terminy[-1],
'neobodovane': False,
'barvicky': True,
}
return initial
@ -262,3 +263,4 @@ class OdevzdavatkoTabulkaFiltrForm(forms.Form):
reseni_od = forms.DateField(input_formats=[DATE_FORMAT])
reseni_do = forms.DateField(input_formats=[DATE_FORMAT])
neobodovane = forms.BooleanField(required=False)
barvicky = forms.BooleanField(required=False)

15
odevzdavatko/templates/odevzdavatko/tabulka.html

@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load utils %} {# Možná by mohlo být někde výš v hierarchii templatů... #}
{% load barvy_reseni %}
{% block content %}
@ -11,6 +12,7 @@
Od data (vyjma): {{ filtr.reseni_od }}
Do data (včetně): {{ filtr.reseni_do }}
<span title="Jen neobodovaná řešení">🔨?</span> {{ filtr.neobodovane }}
<span title="Obarvit shodná řešení shodně">🎨?</span> {{ filtr.barvicky }}
<input type=submit value="→">
</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? #}
{{ resitel }}
</td>
{% for hodn in hodnoty %}
{% for soucet,bunka 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>
{% for reseni,hodnoceni in bunka %}
<a {% if barvicky %} style="color: {{reseni|barva_reseni}};" {% endif %} href="{% url 'odevzdavatko_detail_reseni' pk=reseni.id %}">
{{reseni.cas_doruceni | date:"j. n."}} ({{ hodnoceni.body|default_if_none:"🔨"}} b)
</a><br>
{% endfor %}
{% if bunka|length > 1 %}
<b>Σ: {{soucet}} b</b>
{% endif %}
</td>
{% endfor %}

15
odevzdavatko/templatetags/barvy_reseni.py

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

54
odevzdavatko/views.py

@ -13,6 +13,7 @@ from django.db.models import Q
from dataclasses import dataclass
import datetime
from decimal import Decimal
from itertools import groupby
import logging
@ -37,14 +38,6 @@ logger = logging.getLogger(__name__)
# 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 = 'odevzdavatko/tabulka.html'
model = m.Hodnoceni
@ -70,6 +63,7 @@ class TabulkaOdevzdanychReseniView(ListView):
reseni_od = fcd["reseni_od"]
reseni_do = fcd["reseni_do"]
jen_neobodovane = fcd["neobodovane"]
self.barvicky = fcd["barvicky"]
else:
initial = FiltrForm.gen_initial(self.aktualni_rocnik)
resitele = initial['resitele']
@ -77,6 +71,7 @@ class TabulkaOdevzdanychReseniView(ListView):
reseni_od = initial['reseni_od'][0]
reseni_do = initial['reseni_do'][0]
jen_neobodovane = initial["neobodovane"]
self.barvicky = initial["barvicky"]
# Chceme jen letošní problémy
@ -120,42 +115,45 @@ class TabulkaOdevzdanychReseniView(ListView):
return qs
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é
ctx = super().get_context_data(*args, **kwargs)
ctx['problemy'] = self.problemy
ctx['resitele'] = self.resitele
tabulka = dict()
def pridej_reseni(problem, resitel, body, cas):
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(resitel, hodnoceni):
problem = hodnoceni.problem
body = hodnoceni.body
cas = hodnoceni.reseni.cas_doruceni
reseni = hodnoceni.reseni
if problem not in tabulka:
tabulka[problem] = dict()
soucty[problem] = dict()
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:
tabulka[problem][resitel].posledni_odevzdani = max(tabulka[problem][resitel].posledni_odevzdani, cas)
# 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")
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
tabulka[problem][resitel].append((reseni, hodnoceni))
soucty[problem][resitel] += hodnoceni.body or 0 # Neobodované neřešíme
for hodnoceni in self.get_queryset():
for resitel in hodnoceni.reseni.resitele.all():
pridej_reseni(hodnoceni.problem, resitel, hodnoceni.body, hodnoceni.reseni.cas_doruceni)
pridej_reseni(resitel, hodnoceni)
hodnoty = []
resitele_do_tabulky = []
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: list[m.Resitel] = []
for resitel in self.resitele:
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:
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
else:
resiteluv_radek.append(None)
resiteluv_radek.append((Decimal(0),[]))
if self.chteni_resitele != FiltrForm.RESITELE_RELEVANTNI or dostal_body:
hodnoty.append(resiteluv_radek)
resitele_do_tabulky.append(resitel)
@ -165,6 +163,7 @@ class TabulkaOdevzdanychReseniView(ListView):
ctx['form'] = ctx['filtr']
# Pro maximum v přesměrovátku ročníků
ctx['aktualni_rocnik'] = m.Nastaveni.get_solo().aktualni_rocnik
ctx['barvicky'] = self.barvicky
if 'rocnik' in self.kwargs:
ctx['rocnik'] = self.kwargs['rocnik']
else:
@ -174,6 +173,11 @@ class TabulkaOdevzdanychReseniView(ListView):
# Velmi silně inspirováno zdrojáky, FIXME: Nedá se to udělat smysluplněji?
class ReseniProblemuView(MultipleObjectTemplateResponseMixin, MultipleObjectMixin, View):
"""Rozskok mezi více řešeními téhož problému od téhož řešitele.
Asi 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
template_name = 'odevzdavatko/seznam.html'

18
personalni/admin.py

@ -1,7 +1,9 @@
from django.contrib import admin
from django.contrib.auth.models import Group
from django_reverse_admin import ReverseModelAdmin
from django.contrib.messages import WARNING, ERROR, SUCCESS
import seminar.models as m
from datetime import datetime
@admin.register(m.Osoba)
@ -20,16 +22,24 @@ class OsobaAdmin(admin.ModelAdmin):
def udelej_orgem(self,request,queryset):
org_group = Group.objects.get(name='org')
print(queryset)
uspesne_vytvoreni_orgove = 0
for o in queryset:
if m.Organizator.objects.filter(osoba=o).exists():
# Ref: https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.message_user
self.message_user(request, f"Osoba {o} už je org, přeskakuji.", level=WARNING)
continue
user = o.user
print(user)
if user is None:
self.message_user(request, f"Osoba {o} nemá uživatele! Přeskakuji.", level=ERROR)
continue
user.groups.add(org_group)
user.is_staff = True
user.save()
org = m.Organizator.objects.create(osoba=o)
org = m.Organizator.objects.create(osoba=o, organizuje_od=datetime.now())
org.save()
udelej_orgem.short_description = "Udělej vybraných osob organizátory"
uspesne_vytvoreni_orgove += 1
self.message_user(request, f'Úspěšně vytvořeno {uspesne_vytvoreni_orgove} orgů.', level=SUCCESS)
udelej_orgem.short_description = "Udělej z vybraných osob organizátory"
class OsobaInline(admin.TabularInline):
model = m.Osoba

63
personalni/tests.py

@ -0,0 +1,63 @@
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User, Group
from django.contrib.admin.sites import AdminSite
from personalni.admin import OsobaAdmin
# Tohle bude peklo, až jednou ty modely fakt rozstřelíme… Možná vyrobit various.all_models, které půjdou importovat jako m? :-)
import seminar.models as m
import logging
logger = logging.getLogger(__name__)
class DelaniOrguTest(TestCase):
def setUp(self):
# Admin musí mít instanci
# Ref: https://www.argpar.se/posts/programming/testing-django-admin/
adm_site = AdminSite()
self.admin = OsobaAdmin(m.Osoba, adm_site)
from django.contrib.messages.storage.cookie import CookieStorage
self.request = RequestFactory().get('/admin')
self.request._messages = CookieStorage(self.request)
self.org_group = Group.objects.get(name='org')
novy_user = User.objects.create(username='osoba')
self.nova_osoba = m.Osoba.objects.create(
jmeno='Milada',
prijmeni='Von Kolej',
user = novy_user,
# Snad nic dalšího nepotřebujeme, kdyžtak se doplní…
)
stary_user = User.objects.create(username='stary_user')
stara_osoba = m.Osoba.objects.create(user=stary_user)
self.stary_org = m.Organizator.objects.create(osoba=stara_osoba)
def test_pridani_orga(self):
# Nejdřív to není org…
self.assertFalse(m.Organizator.objects.filter(osoba=self.nova_osoba).exists())
self.assertNotIn(self.org_group, self.nova_osoba.user.groups.all())
self.assertFalse(self.nova_osoba.user.has_perm('auth.org'))
self.assertFalse(self.nova_osoba.user.is_staff)
# Pak orga uděláme…
qs = m.Osoba.objects.filter(id=self.nova_osoba.id)
self.admin.udelej_orgem(self.request, qs)
# A pak už to org má být.
self.nova_osoba.refresh_from_db()
self.assertTrue(self.nova_osoba.user.is_staff)
# FIXME: V db nejsou práva. Nový org je sice ve skupině "org", ale ta nemá právo "auth.org"
# Očekávané řešení: dodat fixture, která to přidá.
#self.assertTrue(self.nova_osoba.user.has_perm('auth.org'))
self.assertIn(self.org_group, self.nova_osoba.user.groups.all())
self.assertTrue(m.Organizator.objects.filter(osoba=self.nova_osoba).exists())
novy_org = m.Organizator.objects.get(osoba=self.nova_osoba)
self.assertIsNotNone(novy_org.organizuje_od)
def test_pridani_stareho_orga(self):
self.admin.udelej_orgem(self.request, m.Osoba.objects.filter(id=self.stary_org.osoba.id)) # Ugly
# Když to spadne, tak jsem se to dozvěděl, takže už nepotřebuju nic kontrolovat.
# Jestli to funguje správně má řešit jiný test.

1
prednasky/models.py

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.utils.encoding import force_text
from seminar.models import Organizator, Soustredeni

8
requirements.txt

@ -5,6 +5,7 @@ psycopg2
html5lib
ipython
Pillow
pilkit>=3.0 # Kvůli kompatibilitě s Pillow>=10.0.0
pytz
six
pexpect
@ -13,7 +14,7 @@ Unidecode
# Django and modules
Django<3.3
Django<5.0
#django-bootstrap-sass
django-mptt
django-reversion
@ -24,7 +25,7 @@ django-ckeditor
django-cleanup # Uklízí media/ od smazaných „databázových“ souborů
django-flat-theme
django-taggit
django-autocomplete-light>=3.9.0rc1
django-autocomplete-light>=3.9.0
django-crispy-forms
django-imagekit
django-polymorphic
@ -52,9 +53,6 @@ Werkzeug
requests
# requests-oauthlib
# uWSGI
uWSGI
# Potřeba pro test data
lorem

35
seminar/admin.py

@ -12,15 +12,25 @@ from django.utils.safestring import mark_safe
import seminar.models as m
admin.site.register(m.Rocnik)
admin.site.register(m.Deadline)
admin.site.register(m.ZmrazenaVysledkovka)
@admin.register(m.Deadline)
class DeadlineAdmin(admin.ModelAdmin):
actions = ['pregeneruj_vysledkovku']
# Nikomu nezobrazovat, ale superuživatelům se může hodit :-)
@admin.action(permissions=['bazmek'], description= 'Přegeneruj výsledkovky vybraných deadlinů')
def pregeneruj_vysledkovku(self, req, qs):
for deadline in qs:
deadline.vygeneruj_vysledkovku()
def has_bazmek_permission(self, request):
# Boilerplate: potřebujeme nějakou permission, protože nějaká haluz v Djangu…
return request.user.is_superuser
class DeadlineAdminInline(admin.TabularInline):
model = m.Deadline
extra = 0
model = m.Deadline
extra = 0
class CisloForm(ModelForm):
class Meta:
@ -71,7 +81,7 @@ class CisloForm(ModelForm):
@admin.register(m.Cislo)
class CisloAdmin(admin.ModelAdmin):
form = CisloForm
actions = ['force_publish']
actions = ['force_publish', 'pregeneruj_vysledkovky']
inlines = (DeadlineAdminInline,)
def force_publish(self,request,queryset):
@ -111,6 +121,17 @@ class CisloAdmin(admin.ModelAdmin):
force_publish.short_description = 'Zveřejnit vybraná čísla a všechny návrhy úloh v nich učinit zadanými'
# Jen pro superuživatele
@admin.action(permissions=['bazmek'], description='Přegenerovat výsledkovky všech deadlinů vybraných čísel')
def pregeneruj_vysledkovky(self, req, qs):
for cislo in qs:
for deadline in cislo.deadline_v_cisle.all():
deadline.vygeneruj_vysledkovku()
def has_bazmek_permission(self, request):
# Boilerplate: potřebujeme nějakou permission, protože nějaká haluz v Djangu…
return request.user.is_superuser
@admin.register(m.Problem)
class ProblemAdmin(PolymorphicParentModelAdmin):

3
seminar/migrations/0006_problem_add_timestamp.py

@ -3,7 +3,6 @@ from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.utils.timezone import utc
class Migration(migrations.Migration):
@ -16,7 +15,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='problem',
name='timestamp',
field=models.DateTimeField(default=datetime.datetime(2015, 5, 15, 8, 54, 56, 319985, tzinfo=utc), verbose_name='vytvo\u0159eno', auto_now=True),
field=models.DateTimeField(default=datetime.datetime(2015, 5, 15, 8, 54, 56, 319985, tzinfo=datetime.timezone.utc), verbose_name='vytvo\u0159eno', auto_now=True),
preserve_default=False,
),
migrations.AlterField(

40
seminar/migrations/0114_related_name_se_zmenilo_a_django_chce_migraci_tak_dostane_migraci.py

@ -0,0 +1,40 @@
# Generated by Django 4.2.7 on 2023-11-20 21:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('seminar', '0113_resitel_zasilat_cislo_papirove'),
]
operations = [
migrations.AlterField(
model_name='problem',
name='autor',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='autor_problemu_%(class)s', to='seminar.organizator', verbose_name='autor problému'),
),
migrations.AlterField(
model_name='problem',
name='garant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='garant_problemu_%(class)s', to='seminar.organizator', verbose_name='garant zadaného problému'),
),
migrations.AlterField(
model_name='problem',
name='opravovatele',
field=models.ManyToManyField(blank=True, related_name='opravovatele_%(class)s', to='seminar.organizator', verbose_name='opravovatelé'),
),
migrations.AlterField(
model_name='problem',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
migrations.AlterField(
model_name='treenode',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
]

17
seminar/models/tvorba.py

@ -491,7 +491,7 @@ class Problem(SeminarModelBase,PolymorphicModel):
return self.nadproblem.kod_v_rocniku+".{}".format(self.kod)
return str(self.kod)
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
return f'<Není zadaný: {self.kod}>'
# def verejne(self):
# # aktuálně podle stavu problému
@ -571,9 +571,9 @@ class Tema(Problem):
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
if self.nadproblem:
return self.nadproblem.kod_v_rocniku+".t{}".format(self.kod)
return "t{}".format(self.kod)
return 't'+self.kod
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
return f'<Není zadaný: {self.kod}>'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
@ -607,9 +607,9 @@ class Clanek(Problem):
# Nemělo by být potřeba
# if self.nadproblem:
# return self.nadproblem.kod_v_rocniku+".c{}".format(self.kod)
return "c{}".format(self.kod)
return "c" + self.kod
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
return f'<Není zadaný: {self.kod}>'
def node(self):
return None
@ -642,12 +642,9 @@ class Uloha(Problem):
@cached_property
def kod_v_rocniku(self):
if self.stav == Problem.STAV_ZADANY or self.stav == Problem.STAV_VYRESENY:
name="{}.u{}".format(self.cislo_zadani.poradi,self.kod)
if self.nadproblem:
return self.nadproblem.kod_v_rocniku+name
return name
return f"{self.cislo_zadani.poradi}.{self.kod}"
logger.warning(f"K problému {self} byl vyžadován kód v ročníku, i když není zadaný ani vyřešený.")
return '<Není zadaný>'
return f'<Není zadaný: {self.kod}>'
def save(self, *args, **kwargs):
super().save(*args, **kwargs)

4
seminar/templates/seminar/archiv/cisla.html

@ -40,7 +40,9 @@
{% endif %}
{% endfor %}
</ul>
<a href='{{ rocnik.verejne_url }}'>Výsledková listina</a> <!-- FIXME: url výsledkovky-->
{% if rocnik.verejne_vysledkovky_cisla %} {# Tohle jsem asi neměl tady použít, ale šlo to… #}
<a href='{{ rocnik.verejne_url }}#vysledky'>Výsledková listina</a> <!-- FIXME: url výsledkovky-->
{% endif %}
</div>
</div>

6
seminar/templates/seminar/archiv/cislo.html

@ -38,9 +38,11 @@
<h2> Orgovské odkazy </h2>
<ul>
<li><a href="obalky.pdf">Obálky (PDF)</a></li>
<li><a href="tituly.tex" download>Tituly (TeX)</a></li>
<li><a href="vysledkovka.tex" download>Výsledkovka (TeX)</a></li>
<li><a href="tituly.tex" download>Tituly (TeX, 2. deadline předchozího čísla a 1.deadline tohoto)</a></li>
<li><a href="vysledkovka.tex" download>Výsledkovka (TeX, 2. deadline předchozího čísla a 1.deadline tohoto)</a></li>
<li><a href="odmeny/{{prevcislo.rocnik.rocnik}}.{{prevcislo.poradi}}/">Odměny</a></li>
<li><a href="{% url "seminar_rocnik_titul" rocnik=cislo.rocnik.rocnik %}" download="posledni_tituly.tex">Tituly do závěrečného čísla (TeX, 2. deadline předchozího čísla a oba tohoto)</a></li>
<li><a href="{% url "seminar_rocnik_posledni_vysledkovka" rocnik=cislo.rocnik.rocnik %}" download>Výsledkovka závěrečného čísla ročníku (TeX, 2. deadline předchozího čísla a oba tohoto)</a></li>
</ul>
</div>
{% endif %}

5
seminar/templates/seminar/archiv/rocnik.html

@ -113,15 +113,14 @@
{% if vysledkovka.radky_vysledkovky %}
<h2>Výsledková listina</h2>
<h2 id=vysledky>Výsledková listina</h2>
{% include "vysledkovky/vysledkovka_rocnik.html" %}
{% endif %}
{% if user.je_org %}
<div class='mam-org-only'>
<p><a href='vysledkovka.tex' download>Výsledkovka ročníku (LaTeX, včetně neveřejných)</a></p>
<p><a href="tituly.tex" download>Tituly (TeX, do konce ročníku = pro poslední číslo)</a></p>
<p><a href="posledni_vysledkovka.tex" download>Výsledkovka posledního čísla</a></p>
<p><a href="tituly.tex" download>Tituly (TeX, včetně neveřejných, všechny, nevhodné do mamtexu)</a></p>
{# FIXME: Sice to sem asi nepatří sémanticky, ale bylo to nejjednodušší… #}
<p><a href='{% url 'seminar_rocnik_resitele_csv' rocnik=rocnik.rocnik %}' download>CSV export řešitelů</a></p>
<h2>Výsledková listina včetně neveřejných bodů</h2>

2
seminar/views/views_all.py

@ -3,7 +3,7 @@ from django.http import HttpResponse
from django.urls import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.views import generic
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from django.http import Http404
from django.db.models import Q, Sum, Count
from django.views.generic.base import RedirectView

0
sifrovacka/__init__.py

8
sifrovacka/admin.py

@ -0,0 +1,8 @@
from django.contrib import admin
from .models import OdpovedUcastnika, SpravnaOdpoved
# Register your models here.
admin.site.register(OdpovedUcastnika)
admin.site.register(SpravnaOdpoved)

5
sifrovacka/apps.py

@ -0,0 +1,5 @@
from django.apps import AppConfig
class SifrovackaConfig(AppConfig):
name = 'sifrovacka'

18
sifrovacka/forms.py

@ -0,0 +1,18 @@
from django.core.exceptions import ValidationError
from django.forms import ModelForm, Textarea
from .models import OdpovedUcastnika, SpravnaOdpoved
class SifrovackaForm(ModelForm):
class Meta:
model = OdpovedUcastnika
fields = ["sifra", "odpoved", ]
widgets = {
"odpoved": Textarea(attrs={'rows': 1, 'cols': 30}),
}
def clean_sifra(self):
sifra = self.cleaned_data.get('sifra')
if SpravnaOdpoved.objects.filter(sifra=sifra).count() == 0:
raise ValidationError("Tohle číslo šifry v databázi nemáme. Zkontrolujte si ho prosím.")
return sifra

34
sifrovacka/migrations/0001_initial.py

@ -0,0 +1,34 @@
# Generated by Django 3.2.22 on 2023-10-14 09:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('seminar', '0113_resitel_zasilat_cislo_papirove'),
]
operations = [
migrations.CreateModel(
name='SpravnaOdpoved',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('odpoved', models.TextField()),
('sifra', models.IntegerField()),
('skryty_text', models.TextField()),
],
),
migrations.CreateModel(
name='OdpovedUcastnika',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('odpoved', models.TextField(verbose_name='Tajenka')),
('sifra', models.IntegerField(verbose_name='Číslo šifry')),
('resitel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='seminar.resitel')),
],
),
]

28
sifrovacka/migrations/0002_auto_20231015_1944.py

@ -0,0 +1,28 @@
# Generated by Django 3.2.22 on 2023-10-15 17:44
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('sifrovacka', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='odpoveducastnika',
options={'ordering': ['-timestamp']},
),
migrations.AddField(
model_name='odpoveducastnika',
name='timestamp',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Timestamp'),
),
migrations.AlterField(
model_name='odpoveducastnika',
name='odpoved',
field=models.TextField(verbose_name='Tajenka bez diakritiky'),
),
]

18
sifrovacka/migrations/0003_odpoveducastnika_uspech.py

@ -0,0 +1,18 @@
# Generated by Django 3.2.22 on 2023-10-16 17:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sifrovacka', '0002_auto_20231015_1944'),
]
operations = [
migrations.AddField(
model_name='odpoveducastnika',
name='uspech',
field=models.BooleanField(default=False, verbose_name='Úspěch'),
),
]

0
sifrovacka/migrations/__init__.py

27
sifrovacka/models.py

@ -0,0 +1,27 @@
from django.db import models
from django.utils import timezone
from seminar.models.personalni import Resitel
# Create your models here.
class OdpovedUcastnika(models.Model):
class Meta:
ordering = ["-timestamp"]
resitel = models.ForeignKey(Resitel, blank=False, null=False, on_delete=models.CASCADE)
odpoved = models.TextField("Tajenka bez diakritiky", blank=False, null=False,)
sifra = models.IntegerField("Číslo šifry", blank=False, null=False,)
timestamp = models.DateTimeField("Timestamp", blank=False, null=False, default=timezone.now)
uspech = models.BooleanField("Úspěch", blank=False, null=False, default=False)
class SpravnaOdpoved(models.Model):
odpoved = models.TextField(blank=False, null=False,)
sifra = models.IntegerField(blank=False, null=False,)
skryty_text = models.TextField(blank=False, null=False,)
def __str__(self):
return f"{self.sifra}: {self.odpoved}"

25
sifrovacka/templates/sifrovacka/odpovedi_list.html

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block content %}
<h1>{% block nadpis1a %}Šifrovačka odpovědi{% endblock nadpis1a %}</h1>
<table class="dosla_reseni">
<tr>
<th>Timestamp</th>
<th>Řešitel</th>
<th>Šifra</th>
<th>Odpověď</th>
</tr>
{% for u in object_list %}
<tr>
<td>{{ u.timestamp }}</td>
<td>{{ u.resitel }}</td>
<td>{{ u.sifra }}</td>
<td style="color: {% if u.uspech %}green{% else %}red{% endif %};">{{ u.odpoved }}</td>
</tr>
{% endfor %}
</table>
{% endblock content %}

46
sifrovacka/templates/sifrovacka/sifrovacka.html

@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block content %}
<br>
<h1>{% block nadpis1a %}M&Mí šifrovačka{% endblock nadpis1a %}</h1>
<br>
<h2>Zadat tajenku šifry:</h2>
<form action="{% url 'sifrovacka' %}" method="post">
<table class="form">
{{form.non_field_errors}}
{% for field in form %}
<tr>
<td>
<label class="field-label{% if field.field.required %} field-required{% endif %}" for="{{ field.id_for_label }}">
{{ field.label }}
</label>
</td>
<td {% if field.help_text %} class="field-with-comment"{% endif %}>
{{ field }}
<span class="field-comment">{{ field.help_text|safe }}</span>
</td>
</tr>
{% if field.errors %}
<tr>
<td colspan="2"><span class="field-error">{{ field.errors }}</span></td>
</tr>
{% endif %}
{% endfor %}
</table>
{% csrf_token %}
<input type="submit" value="Tak pravím!">
</form>
{% endblock content %}

17
sifrovacka/urls.py

@ -0,0 +1,17 @@
from django.urls import path
from seminar.utils import org_required, resitel_or_org_required
from .views import SifrovackaView, SifrovackaListView
urlpatterns = [
path(
'',
resitel_or_org_required(SifrovackaView.as_view()),
name='sifrovacka'
),
path(
'odpovedi/',
org_required(SifrovackaListView.as_view()),
name='sifrovacka_odpovedi'
),
]

33
sifrovacka/views.py

@ -0,0 +1,33 @@
from django.urls import reverse
from django.views.generic import FormView, ListView
from seminar.views import formularOKView
from .forms import SifrovackaForm
from .models import OdpovedUcastnika, SpravnaOdpoved
from seminar.models.personalni import Resitel
# Create your views here.
class SifrovackaView(FormView):
template_name = 'sifrovacka/sifrovacka.html'
form_class = SifrovackaForm
def form_valid(self, form):
instance = form.save(commit=False)
resitel = Resitel.objects.get(osoba__user=self.request.user)
instance.resitel = resitel
instance.save()
sifra = SpravnaOdpoved.objects.filter(sifra=instance.sifra, odpoved__iexact=instance.odpoved.strip()).first()
if sifra is None:
return formularOKView(self.request, f'<h1>Bohužel vám hvězdy nebyly nakloněny. Rozumějte <i>máte to blbě</i>.</h1> <p><a href="{reverse("sifrovacka")}">Zkusit znovu.</a></p><br><br><br>')
instance.uspech = True
instance.save()
return formularOKView(self.request, f'<h1>{sifra.skryty_text}</h1> <p><a href="{reverse("sifrovacka")}">Odevzdat další.</a></p><br><br><br>')
class SifrovackaListView(ListView):
template_name = 'sifrovacka/odpovedi_list.html'
model = OdpovedUcastnika

2
soustredeni/admin.py

@ -25,7 +25,7 @@ class SoustredeniOrganizatoriInline(admin.TabularInline):
extra = 1
fields = ['organizator','poznamka']
autocomplete_fields = ['organizator']
ordering = ['organizator__osoba__jmeno','organizator__prijmeni']
ordering = ['organizator__osoba__jmeno','organizator__osoba__prijmeni']
formfield_overrides = {
models.TextField: {'widget': widgets.TextInput}
}

87
various/static/various/img/zere_kostku.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

19
various/templates/various/403_csrf.html

@ -0,0 +1,19 @@
{#{% extends "error_base.html" %} Z toho nedědíme, protože se nemá přecházet na titulní stránku. #}
{% extends "base.html" %}
{% load static %}
{% block content %}
<h2>{% block nadpis1a %}O-jo-jo-jo-joj{% endblock nadpis1a %}</h2>
<p>
Problém se sušenkami či něčím podobným. Zkuste {% if url %}to prosím znovu: <a href="{{ url }}">{{ url }}</a>. Případně můžete {% endif %}přejít na <a href="/">titulní stránku</a>.
</p>
<p>Pokud problém přetrvává obraťte se na nás přes e-mail: <a href="mailto:mam@matfyz.cz">mailto:mam@matfyz.cz</a> a pošlete nám následující popis chyby: <code>{{ reason }}</code></p>
<img src="{% static 'various/img/zere_kostku.svg' %}">
{% endblock %}

10
various/views.py

@ -1,3 +1,13 @@
from django.http import HttpResponseForbidden
from django.shortcuts import render
# Create your views here.
def csrf_error(request, reason=""):
""" Jednoduchý „template view“ (třída to být nemůže) pro CSRF chyby """
return render(
request, 'various/403_csrf.html',
{"url": request.META.get("HTTP_REFERER", None), "reason": reason},
status=HttpResponseForbidden.status_code,
)

6
vysledkovky/templates/vysledkovky/vysledkovka_cisla.html

@ -4,11 +4,11 @@
<th class='border-r'>#</th>
<th class='border-r'>Jméno</th>
{% for p in vysledkovka.temata_a_spol%}
<th class='border-r' id="problem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}">{# <a href="{{ p.verejne_url }}"> #}{{ p.kod_v_rocniku }}{# </a> #}</th>
<th class='border-r' id="problem{{ oznaceni_vysledkovky }}_{{ forloop.counter0 }}">{# <a href="{{ p.verejne_url }}"> #}<span title="{{ p }}">{{ p.kod_v_rocniku }}</span>{# </a> #}</th>
{# TODELETE #}
{% for podproblemy in vysledkovka.podproblemy_iter.next %}
<th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ forloop.parentloop.counter0 }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}</th>
<th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ forloop.parentloop.counter0 }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}<span title="{{ podproblemy }}">{{ podproblemy.kod_v_rocniku }}</span>{# </a> #}</th>
{% endfor %}
{# TODELETE #}
@ -17,7 +17,7 @@
{# TODELETE #}
{% for podproblemy in vysledkovka.podproblemy_iter.next %}
<th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}{{ podproblemy.kod_v_rocniku }}{# </a> #}</th>
<th class='border-r podproblem{{ oznaceni_vysledkovky }}_{{ vysledkovka.temata_a_spol| length }} podproblem'>{# <a href="{{ podproblemy.verejne_url }}"> #}<span title="{{ podproblemy }}">{{ podproblemy.kod_v_rocniku }}</span>{# </a> #}</th>
{% endfor %}
{# TODELETE #}

8
vysledkovky/utils.py

@ -257,7 +257,7 @@ class VysledkovkaCisla(Vysledkovka):
# (mají vlastní sloupeček ve výsledkovce, nemají nadproblém)
hlavni_problemy = set()
for p in self.problemy:
hlavni_problemy.add(p.hlavni_problem)
hlavni_problemy.add(p.hlavni_problem) # FIXME: proč tohle nemůže obsahovat reálné instance? Ve výsledkovce by se pak zobrazovaly správné kódy…
# zunikátnění
hlavni_problemy = list(hlavni_problemy)
@ -313,7 +313,7 @@ class VysledkovkaCisla(Vysledkovka):
# Sečteme hodnocení
for hodnoceni in self.hodnoceni_do_cisla:
prob = hodnoceni.problem
prob = hodnoceni.problem.get_real_instance()
nadproblem = prob.hlavni_problem.id
# Když nadproblém není "téma", pak je "Ostatní"
@ -366,9 +366,9 @@ class VysledkovkaCisla(Vysledkovka):
for problem in self.problemy:
h_problem = problem.hlavni_problem
if h_problem in temata_a_spol:
podproblemy[h_problem.id].append(problem)
podproblemy[h_problem.id].append(problem.get_real_instance())
else:
podproblemy[-1].append(problem)
podproblemy[-1].append(problem.get_real_instance())
for podproblem in podproblemy.keys():
podproblemy[podproblem] = sorted(podproblemy[podproblem], key=lambda p: p.kod_v_rocniku)

Loading…
Cancel
Save