diff --git a/data/flat.json b/data/flat.json
index 6789d15e..3eea947a 100644
--- a/data/flat.json
+++ b/data/flat.json
@@ -136,7 +136,7 @@
},
{
"fields": {
- "content": "
Odměny za umístění v semináři \r\n\r\nKaždý rok oceňujeme 5 nejlepších řešitelů knihou a deskovou hrou dle jejich výběru. \r\nLetos můžeš být mezi nimi i ty, stačí pilně řešit! :-) \r\nNásledující knihy a deskovky si vybralo pět nejúspěšnějších řešitelů 23. ročníku semináře:
\r\n\r\n
\r\n\r\nMůžeš se podívat i na odměny z 22. a 21. ročníku.
\r\n\r\nOdměny za tituly \r\n\r\n Bc.MM (10 bodů) – propiska \r\n
\r\n\r\nMgr.MM (20 bodů) – reflexní páska \r\n
\r\n\r\nDr.MM (50 bodů) – hrneček \r\n
\r\n\r\nDoc.MM (100 bodů) – deka \r\n
\r\n\r\nProf.MM (200 bodů) – mikina
\r\n\r\n
\r\n\r\nAkad.MM (500 bodů) – tabule s nápisem „ Jsi fakt borec“ podepsaná všemi organizátory
\r\n",
+ "content": "Odměny \r\n\r\nNejvětší odměnou za řešení M&M je účast na soustředění , kromě toho však každý rok oceňujeme pět nejlepších řešitelů knihou a deskovou hrou dle jejich výběru. Letos můžeš být mezi nimi i ty, stačí pilně řešit!
\r\n\r\n
\r\n\r\nTituly \r\n\r\nZa pilné řešení semináře můžeš postupně získat různé titulyMM . Titul u tvého jména v časopisu značí, jakých úspěchů jsi za celou svoji kariéru v M&M zatím dosáhl. Kromě toho se s jeho dosažením vždy pojí nějaká drobná či větší odměna.
\r\n\r\n\r\n\tBc.MM (20 bodů) – propiska \r\n\tMgr.MM (50 bodů) – reflexní páska \r\n\tDr.MM (100 bodů) – hrneček \r\n\tDoc.MM (200 bodů) – deka \r\n\tProf.MM (500 bodů) – mikina \r\n\tAkad.MM (1000 bodů) – tabule s nápisem „ Jsi fakt borec“ podepsaná všemi organizátory \r\n \r\n\r\n
\r\n\r\nDort za článek \r\n\r\nAutorovi nebo autorům nejlepšího otištěného článku v každém ročníku upečeme lahodný dort.
",
"enable_comments": false,
"registration_required": false,
"sites": [
diff --git a/data/sitetree.json b/data/sitetree.json
index 06b92868..26c70ca3 100644
--- a/data/sitetree.json
+++ b/data/sitetree.json
@@ -782,5 +782,29 @@
},
"model": "sitetree.treeitem",
"pk": 40
+ },
+ {
+ "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": 1,
+ "sort_order": 41,
+ "title": "Odměny",
+ "tree": 1,
+ "url": "/o-nas/odmeny/",
+ "urlaspattern": false
+ },
+ "model": "sitetree.treeitem",
+ "pk": 41
}
-]
+]
\ No newline at end of file
diff --git a/seminar/models.py b/seminar/models.py
index b7147106..63c9bdfa 100644
--- a/seminar/models.py
+++ b/seminar/models.py
@@ -601,7 +601,7 @@ class Cislo(SeminarModelBase):
if not self.titulka_nahled or os.path.getmtime(self.titulka_nahled.path) < os.path.getmtime(self.pdf.path):
png_filename = pathlib.Path(tempfile.mkdtemp(), 'nahled.png')
- subprocess.call([
+ subprocess.run([
"convert",
"-density", "300x300",
"-geometry", "{}x{}".format(VYSKA, sirka),
@@ -609,7 +609,10 @@ class Cislo(SeminarModelBase):
"-flatten",
"{}[0]".format(self.pdf.path), # titulní strana
png_filename
- ])
+ ],
+ check=True,
+ capture_output=True
+ )
with open(png_filename,'rb') as f:
self.titulka_nahled.save('',f,True)
diff --git a/seminar/static/seminar/cross.png b/seminar/static/seminar/cross.png
new file mode 100644
index 00000000..f3add6aa
Binary files /dev/null and b/seminar/static/seminar/cross.png differ
diff --git a/seminar/static/seminar/plus.png b/seminar/static/seminar/plus.png
new file mode 100644
index 00000000..0af0007b
Binary files /dev/null and b/seminar/static/seminar/plus.png differ
diff --git a/seminar/templates/seminar/archiv/cislo.html b/seminar/templates/seminar/archiv/cislo.html
index 63273ea4..6326b696 100644
--- a/seminar/templates/seminar/archiv/cislo.html
+++ b/seminar/templates/seminar/archiv/cislo.html
@@ -38,8 +38,8 @@
Orgovské odkazy
diff --git a/seminar/templates/seminar/archiv/rocnik.html b/seminar/templates/seminar/archiv/rocnik.html
index 7a381253..2af9edfa 100644
--- a/seminar/templates/seminar/archiv/rocnik.html
+++ b/seminar/templates/seminar/archiv/rocnik.html
@@ -67,7 +67,7 @@
{% if vysledkovka %}
{% if user.je_org %}
{% endif %}
diff --git a/seminar/templates/seminar/odevzdavatko/detail.html b/seminar/templates/seminar/odevzdavatko/detail.html
index 6344e0a5..dadeff41 100644
--- a/seminar/templates/seminar/odevzdavatko/detail.html
+++ b/seminar/templates/seminar/odevzdavatko/detail.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+{% load static %}
{% block content %}
@@ -25,8 +26,9 @@ function deleteForm(prefix, btn) {
if (total >= 1){
btn.closest('tr').remove();
var forms = $('.hodnoceni');
- $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
- for (var i=0, formCount=forms.length; i{{ subform.problem }}
{{ subform.body }}
{{ subform.cislo_body }}
-
+
{% endfor %}
-
-
+
+
diff --git a/seminar/templates/seminar/registrace/password_reset_email.html b/seminar/templates/seminar/registrace/password_reset_email.html
new file mode 100644
index 00000000..8e7186f9
--- /dev/null
+++ b/seminar/templates/seminar/registrace/password_reset_email.html
@@ -0,0 +1,8 @@
+Ahoj!
+
+Obdrželi jsme od Tebe žádost o obnovu hesla uživatele {{ user }} na webu M&M.
+Pro zadání nového hesla přejdi na následující stránku:
+{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
+
+S pozdravem,
+organizátoři M&M
\ No newline at end of file
diff --git a/seminar/templates/seminar/registrace/password_reset_subject.txt b/seminar/templates/seminar/registrace/password_reset_subject.txt
new file mode 100644
index 00000000..52fadd6d
--- /dev/null
+++ b/seminar/templates/seminar/registrace/password_reset_subject.txt
@@ -0,0 +1 @@
+Změna hesla na webu M&M
diff --git a/seminar/utils.py b/seminar/utils.py
index 26d52665..9b291b24 100644
--- a/seminar/utils.py
+++ b/seminar/utils.py
@@ -11,6 +11,8 @@ from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
+from enum import Enum
+
import seminar.models as m
import seminar.treelib as t
@@ -282,3 +284,58 @@ def podproblemy_v_cislu(cislo, problemy=None, hlavni_problemy=None):
podproblemy[-1].append(problem)
return podproblemy
+
+class TypDeadline(Enum):
+ PredDeadline = auto()
+ SousDeadline = auto()
+ FinalDeadline = auto()
+
+def deadline_v_rocniku(datum, rocnik):
+ """Funkce pro dohledání, ke kterému deadlinu daného ročníku se datum váže.
+
+ Vrací trojici (TypDeadline, Cislo, datumDeadline: date).
+
+ V případě nevalidního volání není aktuálně chování definováno(!)
+ """
+ cisla = m.Cislo.objects.filter(rocnik=rocnik)
+ deadliny = []
+ for c in cisla:
+ if c.datum_preddeadline is not None:
+ deadliny.append((TypDeadline.PredDeadline, c, c.datum_preddeadline))
+ if c.datum_deadline_soustredeni is not None:
+ deadliny.append((TypDeadline.SousDeadline, c, c.datum_deadline_soustredeni))
+ if c.datum_deadline is not None:
+ deadliny.append((TypDeadline.FinalDeadline, c, c.datum_deadline))
+ deadliny = sorted(deadliny, key=lambda x: x[2]) # podle data
+ for dl in deadliny:
+ if datum <= dl:
+ # První takový deadline je ten nejtěsnější
+ return dl
+
+def deadline(datum):
+ """Funkce pro dohledání, ke kterému deadlinu se datum váže.
+
+ Vrací trojici (TypDeadline, Cislo, datumDeadline: date).
+ """
+
+ rok = datum.year
+ # Dva ročníky podezřelé z obsahování dat
+ pozdejsi_rocnik = m.Rocnik.filter(prvni_rok=rok)
+ drivejsi_rocnik = m.Rocnik.filter(druhy_rok=rok)
+ if any(
+ pozdejsi_rocnik.count() > 1,
+ drivejsi_rocnik.count() > 1,
+ ):
+ raise ValueError(f"Více ročníků začíná/končí stejným rokem: {rok}")
+ pozdejsi_rocnik = pozdejsi_rocnik.first() if pozdejsi_rocnik.count() > 0 else None
+ drivejsi_rocnik = drivejsi_rocnik.first() if drivejsi_rocnik.count() > 0 else None
+
+ # Předpokládáme, že neexistuje číslo, které má deadline ale nemá finální deadline.
+ posledni_deadline_drivejsiho_rocniku = m.Cislo.objects.get(rocnik=drivejsi_rocnik, datum_deadline__isnull=False).datum_deadline
+
+ if datum <= posledni_deadline_drivejsiho_rocniku:
+ return deadline_v_rocniku(datum, drivejsi_rocnik)
+ else:
+ return deadline_v_rocniku(datum, pozdejsi_rocnik)
+
+
diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index 04600d6a..49180550 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -4,6 +4,7 @@ from django.shortcuts import get_object_or_404, render, redirect
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse
from django.urls import reverse,reverse_lazy
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
+from django.core.mail import send_mail
from django.views import generic
from django.utils.translation import ugettext as _
from django.http import Http404,HttpResponseBadRequest,HttpResponseRedirect
@@ -1057,6 +1058,21 @@ class NahrajReseniView(LoginRequiredMixin, CreateView):
prilohy.instance = self.object
prilohy.save()
+
+ # Pošleme mail opravovatelům a garantovi
+ # FIXME: Nechat spočítat databázi? Je to pár dotazů (pravděpodobně), takže to za to možná nestojí
+ prijemci = set()
+ for prob in form.cleaned_data['problem']:
+ prijemci.update(prob.opravovatele.all())
+ prijemci.add(prob.garant)
+ # FIXME: Možná poslat mail i relevantním orgům nadproblémů?
+ # FIXME: Víc informativní obsah mailů, možná vč. příloh?
+ send_mail(
+ subject="Nové řešení k problému",
+ message=f"Řešitel poslal řešení...",
+ from_email="submitovatko@mam.mff.cuni.cz", # FIXME: Chceme to mít radši tady, nebo v nastavení?
+ recipient_list=list(prijemci),
+ )
return HttpResponseRedirect(self.get_success_url())
@@ -1223,6 +1239,8 @@ class PasswordResetView(auth_views.PasswordResetView):
# TODO: vlastní email_template_name a subject_template_name a html_email_template_name
success_url = reverse_lazy('reset_password_done')
from_email = 'login@mam.mff.cuni.cz'
+ email_template_name = 'seminar/registrace/password_reset_email.html'
+ subject_template_name = 'seminar/registrace/password_reset_subject.txt'
class PasswordResetDoneView(auth_views.PasswordResetDoneView):
""" Poslali jsme e-mail (pokud bylo kam)). """
diff --git a/seminar/views/vysledkovka.py b/seminar/views/vysledkovka.py
index 6a5da6a9..86b5d8f1 100644
--- a/seminar/views/vysledkovka.py
+++ b/seminar/views/vysledkovka.py
@@ -4,6 +4,8 @@ from seminar.utils import aktivniResitele, resi_v_rocniku, cisla_rocniku, hlavni
import time
### Výsledky
+ROCNIK_ZRUSENI_TEMAT = 25
+
def sloupec_s_poradim(setrizene_body):
"""
Ze seznamu obsahujícího sestupně setřízené body řešitelů za daný ročník
@@ -255,7 +257,10 @@ def secti_body_za_cislo(cislo, aktivni_resitele, hlavni_problemy=None):
inst = problem.get_real_instance()
return not(isinstance(inst, m.Clanek) or isinstance(inst, m.Konfera))
- temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
+ if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT:
+ temata_a_spol = hlavni_problemy
+ else:
+ temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
hlavni_problemy_slovnik = {}
for hp in temata_a_spol:
@@ -410,7 +415,10 @@ def vysledkovka_cisla(cislo, context=None):
return not(isinstance(problem.get_real_instance(), m.Clanek) or isinstance(problem.get_real_instance(), m.Konfera))
- temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
+ if cislo.rocnik.rocnik < ROCNIK_ZRUSENI_TEMAT:
+ temata_a_spol = hlavni_problemy
+ else:
+ temata_a_spol = list(filter(ne_clanek_ne_konfera, hlavni_problemy))
# získáme body u jednotlivých témat
podproblemy = podproblemy_v_cislu(cislo, problemy, temata_a_spol)