diff --git a/data/sitetree.json b/data/sitetree.json index b910e5be..9417ae9a 100644 --- a/data/sitetree.json +++ b/data/sitetree.json @@ -334,7 +334,7 @@ "inmenu": true, "insitetree": true, "parent": 3, - "sort_order": 33, + "sort_order": 43, "title": "Výsledková listina", "tree": 1, "url": "seminar_aktualni_vysledky", @@ -695,13 +695,13 @@ "alias": null, "description": "", "hidden": false, - "hint": "To, co ŘEŠITELÉ poslali", + "hint": "", "inbreadcrumbs": true, "inmenu": true, "insitetree": true, "parent": 21, - "sort_order": 38, - "title": "Došlá řešení", + "sort_order": 37, + "title": "Odevzdaná řešení", "tree": 1, "url": "odevzdavatko_tabulka", "urlaspattern": true @@ -724,7 +724,7 @@ "inmenu": true, "insitetree": true, "parent": 21, - "sort_order": 42, + "sort_order": 38, "title": "Odhlásit se", "tree": 1, "url": "logout", @@ -832,5 +832,29 @@ }, "model": "sitetree.treeitem", "pk": 42 + }, + { + "fields": { + "access_guest": false, + "access_loggedin": false, + "access_perm_type": 1, + "access_permissions": [], + "access_restricted": false, + "alias": null, + "description": "", + "hidden": false, + "hint": "", + "inbreadcrumbs": true, + "inmenu": true, + "insitetree": true, + "parent": 3, + "sort_order": 33, + "title": "Aktuální ročník", + "tree": 1, + "url": "seminar_aktualni_rocnik", + "urlaspattern": true + }, + "model": "sitetree.treeitem", + "pk": 43 } ] \ No newline at end of file diff --git a/mamweb/settings_common.py b/mamweb/settings_common.py index 925d5961..569d5e13 100644 --- a/mamweb/settings_common.py +++ b/mamweb/settings_common.py @@ -44,6 +44,7 @@ STATICFILES_FINDERS = ( # Where redirect for login required services LOGIN_URL = 'login' +LOGIN_REDIRECT_URL = 'profil' # Modules configuration diff --git a/mamweb/static/css/mamweb.css b/mamweb/static/css/mamweb.css index 62d4bd44..b42aa615 100644 --- a/mamweb/static/css/mamweb.css +++ b/mamweb/static/css/mamweb.css @@ -1149,11 +1149,13 @@ div.gdpr { } /* tabulka odevzdaných řešení */ -.dosla_reseni tr th { - text-align: center; -} .dosla_reseni tr th, .dosla_reseni tr td { border: 1px solid black; padding: 1px 10px 1px 10px; border-collapse: collapse; + text-align: center; +} + +.dosla_reseni tr td#problem { + text-align: left; } \ No newline at end of file diff --git a/mamweb/static/images/MSMT_logo_bez_textu_black.eps b/mamweb/static/images/MSMT_logo_bez_textu_black.eps new file mode 100644 index 00000000..f4bbac51 Binary files /dev/null and b/mamweb/static/images/MSMT_logo_bez_textu_black.eps differ diff --git a/mamweb/static/images/mozaika-footer.svg b/mamweb/static/images/mozaika-footer.svg index f7be05be..4e426f0f 100644 --- a/mamweb/static/images/mozaika-footer.svg +++ b/mamweb/static/images/mozaika-footer.svg @@ -1,6 +1,4 @@ - - image/svg+xml \ No newline at end of file + sodipodi:nodetypes="cccc" /> diff --git a/mamweb/templates/graph.svg b/mamweb/templates/graph.svg index d5d59408..00bdee27 100644 --- a/mamweb/templates/graph.svg +++ b/mamweb/templates/graph.svg @@ -409,7 +409,7 @@ Označení deadlinů + + +
+ {% for rocnik, hodnoceni in podle_rocniku %}

Ročník {{ rocnik }}

@@ -14,12 +23,15 @@ {% for hodn in hodnoceni %} - - + + {% endfor %}
{{ hodn.reseni.cas_doruceni }}{{ hodn.problem }}{{ hodn.reseni.cas_doruceni | date:"d.m.Y H:i"}}{{ hodn.problem.nazev | zkrat_nazev_problemu }} {{ hodn.body|default_if_none:"---" }} {{ hodn.reseni.cas_doruceni | deadline_html }}
+ +
+ {% endfor %} {% endblock %} diff --git a/seminar/templates/seminar/profil/edit.html b/seminar/templates/seminar/profil/edit.html index a5aacd0e..9f94090e 100644 --- a/seminar/templates/seminar/profil/edit.html +++ b/seminar/templates/seminar/profil/edit.html @@ -61,7 +61,6 @@ {% include "seminar/profil/prihlaska_field.html" with field=form.stat_text id="id_li_stat_text"%} -{% if not po_maturite %} {# Vysloužilým účastníkům skrýt editaci školy apod. #}

@@ -96,7 +95,6 @@
-{% endif %} diff --git a/seminar/templates/seminar/profil/login.html b/seminar/templates/seminar/profil/login.html index 63d16eed..75926519 100644 --- a/seminar/templates/seminar/profil/login.html +++ b/seminar/templates/seminar/profil/login.html @@ -8,6 +8,10 @@ Přihlášení {% endblock %}{% endblock %}

+{# Obšlehnuto z Admina :-) #} +{% if user.is_authenticated %} +

K této stránce nejspíš nemáte přístup. Můžete se zkusit přihlásit jako uživatel, který přístup má.

+{% endif %}
{% csrf_token %} diff --git a/seminar/templatetags/deadliny.py b/seminar/templatetags/deadliny.py index 81500d73..37cf77af 100644 --- a/seminar/templatetags/deadliny.py +++ b/seminar/templatetags/deadliny.py @@ -5,6 +5,8 @@ register = template.Library() @register.filter(name='deadline') def deadline_text(datum): + if deadline(datum) is None: + return 'Neznámý deadline' typ, cislo, dl = deadline(datum) strings = { TypDeadline.PredDeadline: f"1. deadline čísla {cislo} ({dl})", @@ -15,16 +17,20 @@ def deadline_text(datum): @register.filter(name='deadline_kratseji') def deadline_kratsi_text(datum): + if deadline(datum) is None: + return 'NONE' typ, cislo, dl = deadline(datum) strings = { - TypDeadline.PredDeadline: f"1. deadline {cislo}", - TypDeadline.SousDeadline: f"Soustřeďkový deadline {cislo}", - TypDeadline.FinalDeadline: f"Finální deadline {cislo}", + TypDeadline.PredDeadline: f"{cislo} ♲", + TypDeadline.SousDeadline: f"{cislo} Ⓢ", + TypDeadline.FinalDeadline: f"{cislo} ✓", } return strings[typ] @register.filter(name='deadline_html') def deadline_html(datum): + if deadline(datum) is None: + return 'Neznámý deadline' typ, _, _ = deadline(datum) text = deadline_kratsi_text(datum) classes = { @@ -33,3 +39,12 @@ def deadline_html(datum): TypDeadline.FinalDeadline: 'final_deadline', } return mark_safe(f'{text}') + +@register.filter(name='zkrat_nazev_problemu') +def zkrat_nazev_problemu(nazev): + if len(nazev) > 10: + if nazev[9] == " ": + nazev = nazev[:9] + "..." + else: + nazev = nazev[:10] + "..." + return nazev diff --git a/seminar/test_deadlines.py b/seminar/test_deadlines.py index c2a9ff03..0c0b713b 100644 --- a/seminar/test_deadlines.py +++ b/seminar/test_deadlines.py @@ -144,3 +144,6 @@ class DeadlineTestCase(TestCase): def test_deadline_pro_datetime(self): """Testuje, že i pro datetime dostáváme správné deadliny""" self.skipTest('Chybí implementace testu') + + def test_moc_pozdni_deadline(self): + self.assertIsNone(deadline(date.max)) diff --git a/seminar/urls.py b/seminar/urls.py index 755e1efd..77b46b66 100644 --- a/seminar/urls.py +++ b/seminar/urls.py @@ -61,6 +61,7 @@ urlpatterns = [ path('aktualni/zadani/', views.AktualniZadaniView, name='seminar_aktualni_zadani'), #path('aktualni/temata/', views.ZadaniTemataView, name='seminar_temata'), path('aktualni/vysledkova-listina/', views.ZadaniAktualniVysledkovkaView, name='seminar_aktualni_vysledky'), + path('aktualni/rocnik/', views.AktualniRocnikRedirectView.as_view(), name='seminar_aktualni_rocnik'), path('stare-novinky/', views.StareNovinkyView.as_view(), name='stare_novinky'), # Clanky @@ -133,7 +134,9 @@ urlpatterns = [ path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'), path('prihlasit/', views.LoginView.as_view(), name='login'), + path('login/', RedirectView.as_view(pattern_name='login', permanent=True, query_string=True)), path('odhlasit/', views.LogoutView.as_view(), name='logout'), + path('logout/', RedirectView.as_view(pattern_name='login', permanent=True, query_string=True)), path('resitel/', resitel_required(views.ResitelView.as_view()), name='seminar_resitel'), path('resitel/odevzdana_reseni/', resitel_required(views.PrehledOdevzdanychReseni.as_view()), name='seminar_resitel_odevzdana_reseni'), path('reset-hesla/', views.PasswordResetView.as_view(), name='reset_password'), diff --git a/seminar/utils.py b/seminar/utils.py index 9390f1a3..fc8e43a1 100644 --- a/seminar/utils.py +++ b/seminar/utils.py @@ -14,11 +14,15 @@ from django.core.exceptions import ObjectDoesNotExist from enum import Enum from enum import auto +import logging + import seminar.models as m import seminar.treelib as t -org_required = permission_required('auth.org', raise_exception=True) -resitel_required = permission_required('auth.resitel', raise_exception=True) +logger = logging.getLogger(__name__) + +org_required = permission_required('auth.org') +resitel_required = permission_required('auth.resitel') User = get_user_model() # Není to úplně hezké, ale budeme doufat, že to je funkční... User.je_org = property(lambda self: self.has_perm('auth.org')) @@ -312,11 +316,12 @@ def deadline_v_rocniku(datum, rocnik): if datum <= dl[2]: # První takový deadline je ten nejtěsnější return dl + logger.error(f'Pro datum {datum} v ročníku {rocnik} neexistuje deadline.') def deadline(datum): """Funkce pro dohledání, ke kterému deadlinu se datum váže. - Vrací trojici (TypDeadline, Cislo, datumDeadline: date). + Vrací trojici (TypDeadline, Cislo, datumDeadline: date). Pokud se deadline nenajde, vrátí None """ if isinstance(datum, datetime.datetime): @@ -338,9 +343,12 @@ def deadline(datum): # Seznam čísel je potřeba ručně setřídit chronologicky, protože Model říká, že se řadí od nejnovějšího posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.filter(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).order_by('poradi').last().datum_deadline + logger.debug(f'Nalezené ročníky: {drivejsi_rocnik}, {pozdejsi_rocnik}') if drivejsi_rocnik is not None and datum <= posledni_deadline_drivejsiho_rocniku: + logger.debug(f'Hledám v dřívějším ročníku: {drivejsi_rocnik}') return deadline_v_rocniku(datum, drivejsi_rocnik) else: + logger.debug(f'Hledám v pozdějším ročníku: {pozdejsi_rocnik}') return deadline_v_rocniku(datum, pozdejsi_rocnik) diff --git a/seminar/views/odevzdavatko.py b/seminar/views/odevzdavatko.py index 585727a5..09b4293f 100644 --- a/seminar/views/odevzdavatko.py +++ b/seminar/views/odevzdavatko.py @@ -257,7 +257,7 @@ class PrehledOdevzdanychReseni(ListView): # Ročník určujeme podle čísla, do jehož deadlinu došlo řešení. # Chceme to mít seřazené, takže místo comphrerehsion ručně postavíme pole polí. Django templates neumí použít OrderedDict :-/ podle_rocniku = [] - for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik): + for rocnik, hodnoceni in groupby(ctx['object_list'], lambda ho: deadline(ho.reseni.cas_doruceni)[1].rocnik if deadline(ho.reseni.cas_doruceni) is not None else None): podle_rocniku.append((rocnik, list(hodnoceni))) ctx['podle_rocniku'] = reversed(podle_rocniku) # Od nejnovějšího ročníku # TODO: Umožnit stažení / zobrazení řešení diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py index 3ba73886..5c51c517 100644 --- a/seminar/views/views_all.py +++ b/seminar/views/views_all.py @@ -11,7 +11,7 @@ from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect from django.db.models import Q, Sum, Count from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic.edit import FormView, CreateView -from django.views.generic.base import TemplateView +from django.views.generic.base import TemplateView, RedirectView from django.contrib.auth import authenticate, login, get_user_model, logout from django.contrib.auth import views as auth_views from django.contrib.auth.models import User, Permission @@ -26,7 +26,7 @@ import seminar.models as m from seminar.models import Problem, Cislo, Reseni, Nastaveni, Rocnik, Soustredeni, Organizator, Resitel, Novinky, Soustredeni_Ucastnici, Pohadka, Tema, Clanek, Osoba, Skola # Tohle je stare a chceme se toho zbavit. Pouzivejte s.ToCoChci #from .models import VysledkyZaCislo, VysledkyKCisluZaRocnik, VysledkyKCisluOdjakziva from seminar import utils, treelib -from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm, ProfileEditFormPoMaturite +from seminar.forms import PrihlaskaForm, LoginForm, ProfileEditForm, PoMaturiteProfileEditForm import seminar.forms as f import seminar.templatetags.treenodes as tnltt import seminar.views.views_rest as vr @@ -1026,7 +1026,20 @@ class ResitelView(LoginRequiredMixin,generic.DetailView): class AddSolutionView(LoginRequiredMixin, FormView): template_name = 'seminar/org/vloz_reseni.html' form_class = f.VlozReseniForm - success_url = '/' + + def form_valid(self, form): + data = form.cleaned_data + nove_reseni = m.Reseni.objects.create( + cas_doruceni=data['cas_doruceni'], + forma=data['forma'], + poznamka=data['poznamka'], + ) + nove_reseni.resitele.add(data['resitel']) + nove_reseni.problem.add(data['problem']) + nove_reseni.save() + # Chtěl jsem, aby bylo vidět, že se to uložilo, tak přesměrovávám na profil. + return redirect(reverse('profil')) + class NahrajReseniView(LoginRequiredMixin, CreateView): model = s.Reseni @@ -1095,21 +1108,21 @@ def resitelEditView(request): user_edit = osoba_edit.user ## Vytvoření slovníku, kterým předvyplním formulář prefill_1=model_to_dict(user_edit) - if resitel_edit and resitel_edit.rok_maturity >= date.today().year: + if resitel_edit: prefill_2=model_to_dict(resitel_edit) prefill_1.update(prefill_2) prefill_3=model_to_dict(osoba_edit) prefill_1.update(prefill_3) if 'datum_narozeni' in prefill_1: prefill_1['datum_narozeni'] = str(prefill_1['datum_narozeni']) - if resitel_edit and resitel_edit.rok_maturity < date.today().year: - form = ProfileEditFormPoMaturite(initial=prefill_1) + if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year: + form = PoMaturiteProfileEditForm(initial=prefill_1) else: form = ProfileEditForm(initial=prefill_1) ## Změna údajů a jejich uložení if request.method == 'POST': - if resitel_edit and resitel_edit.rok_maturity < date.today().year: - form = ProfileEditFormPoMaturite(request.POST) + if 'rok_maturity' not in prefill_1 or prefill_1['rok_maturity'] < date.today().year: + form = PoMaturiteProfileEditForm(request.POST) else: form = ProfileEditForm(request.POST) if form.is_valid(): @@ -1133,7 +1146,7 @@ def resitelEditView(request): ## Neznámá země msg = "Unknown country {}".format(fcd['stat_text']) - if resitel_edit and resitel_edit.rok_maturity >= date.today().year: + if resitel_edit: ## Změny v řešiteli resitel_edit.skola = fcd['skola'] resitel_edit.rok_maturity = fcd['rok_maturity'] @@ -1149,7 +1162,7 @@ def resitelEditView(request): return formularOKView(request) else: ## Stránka před odeslaním formuláře = předvyplněný formulář - return render(request, 'seminar/profil/edit.html', {'form': form, 'po_maturite': resitel_edit and resitel_edit.rok_maturity < date.today().year}) + return render(request, 'seminar/profil/edit.html', {'form': form}) def prihlaskaView(request): generic_logger = logging.getLogger('seminar.prihlaska') @@ -1230,12 +1243,6 @@ class LoginView(auth_views.LoginView): # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL template_name = 'seminar/profil/login.html' - # Přesměrovací URL má být v kontextu: - def get_context_data(self, **kwargs): - ctx = super().get_context_data(**kwargs) - ctx['next'] = reverse('profil') - return ctx - class LogoutView(auth_views.LogoutView): # Jen vezmeme vestavěný a dáme mu vhodný template a přesměrovací URL template_name = 'seminar/profil/logout.html' @@ -1327,3 +1334,11 @@ class JakResitView(generic.ListView): def get_queryset(self): return None + +class AktualniRocnikRedirectView(RedirectView): + permanent=False + pattern_name = 'seminar_rocnik' + + def get_redirect_url(self, *args, **kwargs): + aktualni_rocnik = m.Nastaveni.get_solo().aktualni_rocnik.rocnik + return super().get_redirect_url(rocnik=aktualni_rocnik, *args, **kwargs)