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 %} + {{ c.kod }} + {% else %} + {% load static %} no-picture + {% endif %} +
+ +
+
+ +
+ +
+ + +
+
+
+
+ + {% endfor %} +
+
+{% endif %} - {% if vysledkovka %} - {% if user.je_org %} -
- Výsledkovka ročníku (LaTeX) -
- {% 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 %} + + +{% for priloha in object.prilohy.all %} + + + + + {# TODO: Orgo-poznámka, ideálně jako formulář #} +{% endfor %} +
SouborŘešitelova poznámkaDatum
{{ priloha.split | last }}{{ priloha.res_poznamka }}{{ priloha.vytvoreno }}
+{% else %} +

Žádné přílohy

+{% endif %} + +{#

Poznámka:

#} +{#

{{ poznamka }}

#} + +{# Hodnocení: #} +

Hodnocení:

+ +{# #} +{% for h in hodnoceni %} + + + +{# #} + +{% endfor %} +
ProblémBodyČíslo pro body
{{ h.problem }}{{ h.body }}{{ h.cislo_body }}
+ +{% 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):