diff --git a/api/views/autocomplete.py b/api/views/autocomplete.py
index a97fbbab..630e9106 100644
--- a/api/views/autocomplete.py
+++ b/api/views/autocomplete.py
@@ -13,7 +13,9 @@ class SkolaAutocomplete(autocomplete.Select2QuerySetView):
if self.q:
words = self.q.split(' ') #TODO re split podle bileho znaku
partq = Q()
- for w in words: # Hledej po slovech, zahoď čárky a tečky z konců.
+ for w in words: # Hledej po slovech, zahoď čárky a tečky z konců.
+ if len(w) == 0:
+ continue
if w[-1] in (".",","):
w = w[:-1]
@@ -26,11 +28,15 @@ class ResitelAutocomplete(LoginRequiredAjaxMixin,autocomplete.Select2QuerySetVie
def get_queryset(self):
qs = m.Resitel.objects.all()
if self.q:
- qs = qs.filter(
- Q(osoba__jmeno__istartswith=self.q)|
- Q(osoba__prijmeni__istartswith=self.q)|
- Q(osoba__prezdivka__istartswith=self.q)
+ parts = self.q.split()
+ query = Q()
+ for part in parts:
+ query &= (
+ Q(osoba__jmeno__istartswith=self.q)|
+ Q(osoba__prijmeni__istartswith=self.q)|
+ Q(osoba__prezdivka__istartswith=self.q)
)
+ qs = qs.filter(query)
return qs
class OdevzdatelnyProblemAutocomplete(autocomplete.Select2QuerySetView):
diff --git a/data/sitetree.json b/data/sitetree.json
index 5702031a..0416f54b 100644
--- a/data/sitetree.json
+++ b/data/sitetree.json
@@ -977,5 +977,29 @@
},
"model": "sitetree.treeitem",
"pk": 50
+ },
+ {
+ "fields": {
+ "access_guest": false,
+ "access_loggedin": false,
+ "access_perm_type": 1,
+ "access_permissions": [],
+ "access_restricted": true,
+ "alias": null,
+ "description": "",
+ "hidden": false,
+ "hint": "",
+ "inbreadcrumbs": true,
+ "inmenu": true,
+ "insitetree": true,
+ "parent": 42,
+ "sort_order": 51,
+ "title": "Detail řešení {{ reseni.id }}",
+ "tree": 1,
+ "url": "odevzdavatko_resitel_reseni reseni.id",
+ "urlaspattern": true
+ },
+ "model": "sitetree.treeitem",
+ "pk": 51
}
-]
+]
\ No newline at end of file
diff --git a/mamweb/admin.py b/mamweb/admin.py
index 12d063f7..9c421309 100644
--- a/mamweb/admin.py
+++ b/mamweb/admin.py
@@ -1,4 +1,6 @@
+import locale
from django.contrib import admin
+from django.contrib.admin import AdminSite
from django.contrib.flatpages.models import FlatPage
# Note: we are renaming the original Admin and Form as we import them!
@@ -24,3 +26,24 @@ class FlatPageAdmin(FlatPageAdminOld):
admin.site.unregister(FlatPage)
admin.site.register(FlatPage, FlatPageAdmin)
+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):
+ """
+ Return a sorted list of all the installed apps that have been
+ registered in this site.
+ """
+
+ app_dict = self._build_app_dict(request)
+ # 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()))
+
+ # Sort the models alphabetically within each app.
+ for app in app_list:
+ app['models'].sort(key=lambda x: locale.strxfrm('žž' + x['name'].lower()) if (x['name'].endswith("(Node)")) else locale.strxfrm(x['name'].lower()))
+
+ return app_list
+
+AdminSite.get_app_list = get_app_list
diff --git a/seminar/models.py b/seminar/models.py
index daef62e6..b04b31fa 100644
--- a/seminar/models.py
+++ b/seminar/models.py
@@ -466,6 +466,11 @@ class Rocnik(SeminarModelBase):
vc.sort(key=lambda c: c.poradi)
return vc
+ def neverejna_cisla(self):
+ vc = [c for c in self.cisla.all() if not c.verejne()]
+ vc.sort(key=lambda c: c.poradi)
+ return vc
+
def posledni_verejne_cislo(self):
vc = self.verejna_cisla()
return vc[-1] if vc else None
diff --git a/seminar/templates/seminar/archiv/cisla.html b/seminar/templates/seminar/archiv/cisla.html
index 4cba99ee..35fd5666 100644
--- a/seminar/templates/seminar/archiv/cisla.html
+++ b/seminar/templates/seminar/archiv/cisla.html
@@ -35,9 +35,9 @@
Jednotlivá čísla:
Výsledková listina
diff --git a/seminar/templates/seminar/archiv/rocnik.html b/seminar/templates/seminar/archiv/rocnik.html
index aa2f1dbc..01a9f5ac 100644
--- a/seminar/templates/seminar/archiv/rocnik.html
+++ b/seminar/templates/seminar/archiv/rocnik.html
@@ -63,20 +63,63 @@
{% endfor %}
+{% if user.je_org and rocnik.neverejna_cisla %}
+
+
+ {% for c in rocnik.neverejna_cisla %}
+
+
+
Číslo {{ c.kod }}
+
+
+
+
+
+
+
+ {% if c.titulka_nahled %}
+
+ {% else %}
+ {% load static %}
+ {% endif %}
+
+
+
+
+
+
+
+
+ {% endfor %}
+
+
+{% endif %}
- {% if vysledkovka %}
- {% if user.je_org %}
-
- {% endif %}
+ {% if vysledkovka %}
Výsledková listina
{% include "seminar/vysledkovka_rocnik.html" %}
{% endif %}
{% if user.je_org %}
+
Výsledkovka ročníku (LaTeX, včetně neveřejných)
Výsledková listina včetně neveřejných bodů
{% with radky_vysledkovky_s_neverejnymi as radky_vysledkovky %}
{% with cisla_s_neverejnymi as cisla %}
diff --git a/seminar/templates/seminar/archiv/rocnik_vysledkovka.tex b/seminar/templates/seminar/archiv/rocnik_vysledkovka.tex
index 61c9abbb..217127de 100644
--- a/seminar/templates/seminar/archiv/rocnik_vysledkovka.tex
+++ b/seminar/templates/seminar/archiv/rocnik_vysledkovka.tex
@@ -1,5 +1,6 @@
{% with lb="{" %}
{% with rb="}" %}
+{% with radky_vysledkovky=radky_vysledkovky_s_neverejnymi cisla=cisla_s_neverejnymi %}
\setlength{\tabcolsep}{3pt}
\begin{longtable}{|r|l|c|r|{% for cislo in cisla %}c{% if not forloop.last %}@{\hskip.5em}{% endif %}{% endfor %}|r|}\hline
& & & & \multicolumn{{ lb }}{{ cisla|length }}}{c|}{\textbf{Číslo}} & \\\textbf{Poř.} & \textbf{Jméno} & \textbf{R.} & \raisebox{0.7mm}{$\sum_{-1}$} & {% for cislo in cisla %}\textbf{{ lb }}{{ cislo.poradi }}{{ rb }} & {% endfor %}\raisebox{0.7mm}{$\sum_1$} \\\hline
@@ -10,3 +11,4 @@
{% endfor %}\end{longtable}
{% endwith %}
{% endwith %}
+{% endwith %}
diff --git a/seminar/templates/seminar/odevzdavatko/detail.html b/seminar/templates/seminar/odevzdavatko/detail.html
index f80f0cae..45c3ce54 100644
--- a/seminar/templates/seminar/odevzdavatko/detail.html
+++ b/seminar/templates/seminar/odevzdavatko/detail.html
@@ -134,6 +134,15 @@ $(document).ready(function(){
return false;
}
}
+
+ function problem_is_empty(elem, index, array) {return elem.firstElementChild.children.length !== 1 && elem.firstElementChild.children[1].textContent === "";}
+
+ if ($('.hodnoceni').toArray().some(problem_is_empty)) {
+ alert("Neuloženo! Nezadal jsi problém, ke kterému posíláš hodnocení. Pokud je toto hodnocení navíc, smaž ho prosím křížkem a znovu odešli.")
+ event.preventDefault()
+ return false;
+ }
+
return true;
}
diff --git a/seminar/templates/seminar/odevzdavatko/detail_resitele.html b/seminar/templates/seminar/odevzdavatko/detail_resitele.html
new file mode 100644
index 00000000..4e5d7848
--- /dev/null
+++ b/seminar/templates/seminar/odevzdavatko/detail_resitele.html
@@ -0,0 +1,50 @@
+{% extends "base.html" %}
+{% load static %}
+{% load deadliny %}
+
+{% block content %}
+
+
Řešené problémy: {{ object.problem.all | join:", " }}
+
+
Řešitelé: {% for r in object.resitele.all %} {{ r }}
+ {% if forloop.revcounter0 != 0 %}, {% endif %} {% endfor %}
+
+{# https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display #}
+
Forma: {{ object.get_forma_display }}
+
+
Doručeno {{ object.cas_doruceni }}, deadline: {{object.cas_doruceni | deadline_html }}
+
+{# Soubory: #}
+
Přílohy:
+{% if object.prilohy.all %}
+
+Soubor | Řešitelova poznámka | Datum |
+{% for priloha in object.prilohy.all %}
+
+ {{ priloha.split | last }} |
+ {{ priloha.res_poznamka }} |
+ {{ priloha.vytvoreno }} |
+ {# TODO: Orgo-poznámka, ideálně jako formulář #}
+{% endfor %}
+
+{% else %}
+
Žádné přílohy
+{% endif %}
+
+{#
Poznámka:
#}
+{#
{{ poznamka }}
#}
+
+{# Hodnocení: #}
+
Hodnocení:
+
+
+{% endblock %}
diff --git a/seminar/templates/seminar/odevzdavatko/resitel_prehled.html b/seminar/templates/seminar/odevzdavatko/resitel_prehled.html
index 229b036c..773aa2a3 100644
--- a/seminar/templates/seminar/odevzdavatko/resitel_prehled.html
+++ b/seminar/templates/seminar/odevzdavatko/resitel_prehled.html
@@ -25,7 +25,7 @@
{{ hodn.reseni.cas_doruceni | date:"d.m.Y H:i"}} |
{{ hodn.problem.nazev | zkrat_nazev_problemu }} |
- {{ hodn.body|default_if_none:"---" }} |
+ {{ hodn.body|default_if_none:"---" }} |
{{ hodn.reseni.cas_doruceni | deadline_html }} |
{% endfor %}
diff --git a/seminar/urls.py b/seminar/urls.py
index 8a6aacf4..5b71d2b3 100644
--- a/seminar/urls.py
+++ b/seminar/urls.py
@@ -142,4 +142,6 @@ urlpatterns = [
path('org/reseni/
', org_required(viewMethodSwitch(get=views.DetailReseniView.as_view(), post=views.hodnoceniReseniView)), name='odevzdavatko_detail_reseni'),
path('org/reseni/all', org_required(views.SeznamReseniView.as_view())),
path('org/reseni/akt', org_required(views.SeznamAktualnichReseniView.as_view())),
+
+ path('resitel/reseni/', resitel_or_org_required(views.ResitelReseniView.as_view()), name='odevzdavatko_resitel_reseni'),
]
diff --git a/seminar/views/odevzdavatko.py b/seminar/views/odevzdavatko.py
index ce86958c..b3e1947d 100644
--- a/seminar/views/odevzdavatko.py
+++ b/seminar/views/odevzdavatko.py
@@ -1,3 +1,4 @@
+from django.core.exceptions import PermissionDenied
from django.views.generic import ListView, DetailView, FormView
from django.views.generic.list import MultipleObjectTemplateResponseMixin,MultipleObjectMixin
from django.views.generic.base import View
@@ -75,7 +76,6 @@ class TabulkaOdevzdanychReseniView(ListView):
# Chceme jen letošní problémy
- # FIXME: Neexistuje metoda, jak dostat starší problémy…
self.problemy = self.problemy.filter(Q(Tema___rocnik=self.aktualni_rocnik) | Q(Uloha___cislo_zadani__rocnik = self.aktualni_rocnik) | Q(Clanek___cislo__rocnik = self.aktualni_rocnik) | Q(Konfera___soustredeni__rocnik = self.aktualni_rocnik))
self.chteni_resitele = resitele # Zapamatování pro get_context_data
@@ -88,9 +88,14 @@ class TabulkaOdevzdanychReseniView(ListView):
if problemy == FiltrForm.PROBLEMY_MOJE:
org = m.Organizator.objects.get(osoba__user=self.request.user)
- self.problemy = self.problemy.filter(Q(autor=org)|Q(garant=org)|Q(opravovatele=org), stav=m.Problem.STAV_ZADANY)
+ self.problemy = self.problemy.filter(
+ Q(autor=org)|Q(garant=org)|Q(opravovatele=org),
+ Q(stav=m.Problem.STAV_ZADANY)|Q(stav=m.Problem.STAV_VYRESENY),
+ )
elif problemy == FiltrForm.PROBLEMY_LETOSNI:
- self.problemy = self.problemy.filter(stav=m.Problem.STAV_ZADANY)
+ self.problemy = self.problemy.filter(
+ Q(stav=m.Problem.STAV_ZADANY)|Q(stav=m.Problem.STAV_VYRESENY),
+ )
#self.problemy = list(filter(lambda problem: problem.rocnik() == self.aktualni_rocnik, self.problemy)) # DB HOG? # FIXME: některé problémy nemají ročník....
# 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.problemy = self.problemy.non_polymorphic()
@@ -264,6 +269,34 @@ def hodnoceniReseniView(request, pk, *args, **kwargs):
return redirect(success_url)
+class ResitelReseniView(DetailView):
+ model = m.Reseni
+ template_name = 'seminar/odevzdavatko/detail_resitele.html'
+
+ def aktualni_hodnoceni(self):
+ self.reseni = get_object_or_404(m.Reseni, id=self.kwargs['pk'])
+ result = []
+ for hodn in m.Hodnoceni.objects.filter(reseni=self.reseni):
+ result.append(
+ {
+ "problem": hodn.problem,
+ "body": hodn.body,
+ # "cislo_body": hodn.cislo_body,
+ }
+ )
+ return result
+
+ def get_context_data(self, **kw):
+ ctx = super().get_context_data(**kw)
+ hodnoceni = self.aktualni_hodnoceni()
+ if not self.reseni.resitele.filter(osoba__user=self.request.user).exists():
+ raise PermissionDenied()
+ # ctx['poznamka'] = f.PoznamkaReseniForm(instance=self.reseni)
+ ctx["hodnoceni"] = hodnoceni
+ return ctx
+
+
+
class PrehledOdevzdanychReseni(ListView):
model = m.Hodnoceni
template_name = 'seminar/odevzdavatko/resitel_prehled.html'
diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index 9392f49a..85a7f9f2 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -630,6 +630,8 @@ class ArchivView(generic.ListView):
context = super(ArchivView, self).get_context_data(**kwargs)
cisla = Cislo.objects.filter(poradi=1)
+ if not self.request.user.je_org:
+ cisla = cisla.filter(verejne_db=True)
urls ={}
for i, c in enumerate(cisla):