From 645163cec83d96d70f7396a4d0d605c65284c5c8 Mon Sep 17 00:00:00 2001
From: Anet <anet@erebus>
Date: Sun, 23 Aug 2020 18:46:47 +0200
Subject: [PATCH 1/5] =?UTF-8?q?views:=20zprovozn=C4=9Bn=C3=AD=20views=20?=
 =?UTF-8?q?=C5=99e=C5=A1itelsk=C3=BDch=20=C4=8Dl=C3=A1nk=C5=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 seminar/views/views_all.py | 31 ++++++++++++++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index 87c7dbe5..af34e69e 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -951,12 +951,41 @@ def soustredeniUcastniciExportView(request,soustredeni):
 
 
 ### Články
+def group_by_rocnik(clanky):
+	''' Vezme zadaný seznam článků a seskupí je podle ročníku.
+	Vrátí seznam seznamů článků ze stejného ročníku.'''
+	clanky.order_by('cislo__rocnik__rocnik')
+	skupiny_clanku = []
+	skupina = []
+
+	rocnik = clanky.first().cislo.rocnik.rocnik # první ročník
+	for clanek in clanky:
+		if clanek.cislo.rocnik.rocnik == rocnik:
+			skupina.append(clanek)
+		else:
+			skupiny_clanku.append(skupina)
+			skupina = []
+			skupina.append(clanek)
+			rocnik = clanek.cislo.rocnik.rocnik
+	skupiny_clanku.append(skupina)
+	return skupiny_clanku
+	
 
 # FIXME: clanky jsou vsechny, pokud budou i neresitelske, tak se take zobrazi
 class ClankyResitelView(generic.ListView):
 	model = Problem
 	template_name = 'seminar/clanky/resitelske_clanky.html'
-	queryset = Clanek.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo_zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod')
+	#queryset
+	clanky = Clanek.objects.filter(stav=Problem.STAV_ZADANY).select_related('cislo__rocnik').order_by('-cislo__rocnik__rocnik')
+	queryset = []
+	skupiny_clanku = group_by_rocnik(clanky)
+	for skupina in skupiny_clanku:
+		skupina.sort(key=lambda clanek: clanek.kod_v_rocniku())
+		for clanek in skupina:
+			queryset.append(clanek)
+	#XXX
+
+	#zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod')
 
 # FIXME: pokud chceme orgoclanky, tak nejak zavest do modelu a podle toho odkomentovat a upravit
 #class ClankyOrganizatorView(generic.ListView)<F12>:

From ab4148e2e271c7bd1a936e5ba7f4c3adfb01970a Mon Sep 17 00:00:00 2001
From: Anet <anet@erebus>
Date: Sun, 23 Aug 2020 23:22:17 +0200
Subject: [PATCH 2/5] orgorozcestnik: nabuseni statickych odkazu, pokus o
 nejake lehke dynamicke veci - rozdelane

---
 .../templates/seminar/clanky/resitelske_clanky.html   |  2 +-
 seminar/urls.py                                       |  3 +++
 seminar/views/views_all.py                            | 11 ++++++++++-
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/seminar/templates/seminar/clanky/resitelske_clanky.html b/seminar/templates/seminar/clanky/resitelske_clanky.html
index 1e15fcbc..84089753 100644
--- a/seminar/templates/seminar/clanky/resitelske_clanky.html
+++ b/seminar/templates/seminar/clanky/resitelske_clanky.html
@@ -17,7 +17,7 @@
 </h1>
 
 {% for clanek in object_list %}
-{% with clanek.cislo_zadani.rocnik.rocnik as rocnik %}
+{% with clanek.cislo.rocnik.rocnik as rocnik %}
   {% ifchanged rocnik %}
     {% if not forloop.first %}</ul>{% endif %}
     <h2>{{ rocnik }}. ročník</h2>
diff --git a/seminar/urls.py b/seminar/urls.py
index df2e1435..9c72717e 100644
--- a/seminar/urls.py
+++ b/seminar/urls.py
@@ -90,6 +90,9 @@ urlpatterns = [
 
 	path('org/vloz_body/<int:tema>/',
 		staff_member_required(views.VlozBodyView.as_view()),name='seminar_org_vlozbody'),
+	# příprava na nestatický orgorozcestník
+	path('org/rozcestnik',
+		staff_member_required(views.OrgoRozcestnikView.as_view()),name='seminar_org_rozcestnik'),
 	path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'),
 	path('login/', views.LoginView.as_view(), name='login'),
 	path('logout/', views.LogoutView.as_view(), name='logout'),
diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index af34e69e..a80623d7 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -873,6 +873,16 @@ def oldObalkovaniView(request, rocnik, cislo):
 		{'cislo': cislo, 'problemy': problemy, 'reseni': reseni}
 	)
 
+### Orgostránky
+
+def OrgoRozcestnikView(request):
+	''' Zobrazí organizátorský rozcestník.'''
+	posledni_soustredeni = Soustredeni.objects.order_by('-datum_konce').first()
+
+	template_name = 'seminar/orgorozcestnik.html'
+	content_type = 'text/plain; charset=UTF8'
+	#XXX
+
 ### Tituly
 
 def TitulyView(request, rocnik, cislo):
@@ -983,7 +993,6 @@ class ClankyResitelView(generic.ListView):
 		skupina.sort(key=lambda clanek: clanek.kod_v_rocniku())
 		for clanek in skupina:
 			queryset.append(clanek)
-	#XXX
 
 	#zadani__rocnik').order_by('-cislo_zadani__rocnik__rocnik', 'kod')
 

From 64777359142862f3bab94724e9a50b6562415f11 Mon Sep 17 00:00:00 2001
From: Anet <anet@erebus>
Date: Tue, 1 Sep 2020 23:04:19 +0200
Subject: [PATCH 3/5] =?UTF-8?q?orgorozhrani=20verze=20-1:=20z=C3=A1kladn?=
 =?UTF-8?q?=C3=AD=20v=C4=9Bci,=20u=20probl=C3=A9m=C5=AF=20je=20pot=C5=99eb?=
 =?UTF-8?q?a=20vymyslet,=20kam=20to=20m=C3=A1=20sm=C4=9Brovat,=20asi=20odk?=
 =?UTF-8?q?az=20na=20treenode=20editor?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 seminar/templates/seminar/orgorozcestnik.html |  86 +++++++++++
 seminar/urls.py                               |   2 +-
 seminar/views/views_all.py                    | 136 +++++++++++-------
 3 files changed, 169 insertions(+), 55 deletions(-)
 create mode 100644 seminar/templates/seminar/orgorozcestnik.html

diff --git a/seminar/templates/seminar/orgorozcestnik.html b/seminar/templates/seminar/orgorozcestnik.html
new file mode 100644
index 00000000..5bd75403
--- /dev/null
+++ b/seminar/templates/seminar/orgorozcestnik.html
@@ -0,0 +1,86 @@
+{% extends "base.html" %}
+
+{% block content %}
+<h2><strong>Informace, komunikace</strong></h2>
+
+<ul>
+	<li><strong><a href="https://wiki.mam.bezva.org/">wiki</a> </strong>obsahuje různé návody a know-how</li>
+	<li><strong><a href="https://riot.im/app/#/room/#orgovna:dolujeme.eu">Riot</a> </strong>chatování s dalšími orgy</li>
+	<li><strong>Kanboard </strong>správa TODO
+	<ul>
+		<li><a href="https://kanboard.ledoian.cz/?controller=BoardViewController&amp;action=show&amp;project_id=10">webařský</a></li>
+		<li>soustředění</li>
+	</ul>
+	</li>
+	<li><a href="https://mam.mff.cuni.cz/admin/seminar/novinky/add/"><strong>přidat novinku</strong></a> na web</li>
+</ul>
+
+<hr />
+<h2><strong>Tvorba čísla</strong></h2>
+
+<ul>
+	<li><a href="https://mam.mff.cuni.cz/admin/seminar/problemnavrh/add/"><strong>přidat téma</strong></a></li>
+	<li><strong>korektury</strong>
+	<ul>
+		<li><a href="https://mam.mff.cuni.cz/korektury/">korekturování</a></li>
+		<li><a href="https://mam.mff.cuni.cz/admin/korektury/korekturovanepdf/add/">přidat pdf k opravám</a></li>
+	</ul>
+	</li>
+	<li><a href='{{ posledni_cislo_url }}'><strong>poslední vydané číslo </strong></a></li>
+</ul>
+<hr />
+
+<h2><strong>Moje problémy</strong></h2>
+
+<h3> Témata </h3>
+<ul>
+{% for t in temata %}
+<li> {{ t }} </li>
+{% endfor %}
+</ul>
+
+<h3> Úlohy </h3>
+<ul>
+{% for u in ulohy %}
+<li> {{ u }} </li>
+{% endfor %}
+</ul>
+
+<h3> Články </h3>
+<ul>
+{% for c in clanky %}
+<li> {{ c }} </li>
+{% endfor %}
+</ul>
+
+<hr />
+<h2><strong>Soustředění</strong></h2>
+
+<ul>
+	<li><strong>přednášky</strong>
+
+	<ul>
+		<li><a href="https://mam.mff.cuni.cz/admin/prednasky/prednaska/">vypisování přednášek</a></li>
+		<li>hlasování o přednáškách</li>
+	</ul>
+	</li>
+	<li><a href="https://mam.mff.cuni.cz/soustredeni/probehlo/">proběhlá soustředění</a>
+	<ul>
+		<li>vytvoření galerie</li>
+		<li>stažení seznamu účastníků</li>
+		<li>obálky</li>
+	</ul>
+	</li>
+</ul>
+
+<hr />
+<h2><strong>Můj profil</strong></h2>
+
+<ul>
+	<li><a href="http://127.0.0.1:8000/admin/seminar/organizator/{{ organizator.id }}/change/"><strong>upravit </strong></a></li>
+</ul>
+
+<hr />
+<p>Nemůžeš najít, co hledáš? Může to být v <a href="https://mam.mff.cuni.cz/admin/">administračním rozhraní webu</a>.</p>
+{% endblock content %}
+
diff --git a/seminar/urls.py b/seminar/urls.py
index 9c72717e..ed42a7f8 100644
--- a/seminar/urls.py
+++ b/seminar/urls.py
@@ -91,7 +91,7 @@ urlpatterns = [
 	path('org/vloz_body/<int:tema>/',
 		staff_member_required(views.VlozBodyView.as_view()),name='seminar_org_vlozbody'),
 	# příprava na nestatický orgorozcestník
-	path('org/rozcestnik',
+	path('org/rozcestnik/',
 		staff_member_required(views.OrgoRozcestnikView.as_view()),name='seminar_org_rozcestnik'),
 	path('prihlaska/',views.prihlaskaView, name='seminar_prihlaska'),
 	path('login/', views.LoginView.as_view(), name='login'),
diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index a80623d7..242c4962 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -10,6 +10,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.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
@@ -200,7 +201,7 @@ class AktualniZadaniView(TreeNodeView):
 #			"cisla" : cisla
 #		})
 #	return render(request, 'seminar/tematka/rozcestnik.html', {"tematka": tematka, "rocnik" : nastaveni.aktualni_rocnik().rocnik})
-#    
+#	
 
 #def ZadaniAktualniVysledkovkaView(request):
 #	nastaveni = get_object_or_404(Nastaveni)
@@ -875,12 +876,39 @@ def oldObalkovaniView(request, rocnik, cislo):
 
 ### Orgostránky
 
-def OrgoRozcestnikView(request):
+class OrgoRozcestnikView(TemplateView):
 	''' Zobrazí organizátorský rozcestník.'''
-	posledni_soustredeni = Soustredeni.objects.order_by('-datum_konce').first()
 
 	template_name = 'seminar/orgorozcestnik.html'
-	content_type = 'text/plain; charset=UTF8'
+
+	def get_context_data(self, **kwargs):
+		context = super().get_context_data(**kwargs)
+		context['posledni_soustredeni'] = Soustredeni.objects.order_by('-datum_konce').first()
+		nastaveni = Nastaveni.objects.first()
+		aktualni_rocnik = nastaveni.aktualni_rocnik
+		context['posledni_cislo_url'] = nastaveni.aktualni_cislo.verejne_url()
+		# TODO možná chceme odkazovat na právě rozpracované číslo, a ne to poslední vydané
+		# pokud nechceme haluzit kód (= poradi) dalšího čísla, bude asi potřeba jít 
+		# přes treenody (a dát si přitom pozor na MezicisloNode)
+
+		u = self.request.user
+		os = s.Osoba.objects.get(user=u)
+		organizator = s.Organizator.objects.get(osoba=os)
+		temata_garant = s.Tema.objects.filter(garant=organizator, 
+			rocnik=aktualni_rocnik)
+		#FIXME: přidat opravovatel, stav='STAV_ZADANY'
+		ulohy_garant = s.Uloha.objects.filter(garant=organizator,
+			cislo_zadani__rocnik=aktualni_rocnik)
+		clanky_garant = s.Clanek.objects.filter(garant=organizator,
+			cislo__rocnik=aktualni_rocnik)
+
+		context['temata'] = temata_garant
+		context['ulohy'] = ulohy_garant
+		context['clanky'] = clanky_garant
+		context['organizator'] = organizator
+		return context
+
+		#content_type = 'text/plain; charset=UTF8'
 	#XXX
 
 ### Tituly
@@ -1109,57 +1137,57 @@ def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
 
 from django.forms.models import model_to_dict
 def resitelEditView(request):
-    err_logger = logging.getLogger('seminar.prihlaska.problem')
-    ## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately
-    u = request.user
-    osoba_edit = Osoba.objects.get(user=u)
-    resitel_edit = osoba_edit.resitel
-    user_edit = osoba_edit.user
-    ## Vytvoření slovníku, kterým předvyplním formulář 
-    prefill_1=model_to_dict(user_edit)
-    prefill_2=model_to_dict(resitel_edit)
-    prefill_3=model_to_dict(osoba_edit)
-    prefill_1.update(prefill_2)
-    prefill_1.update(prefill_3)
-    form = ProfileEditForm(initial=prefill_1)
-    ## Změna údajů a jejich uložení
-    if request.method == 'POST':
-        form = ProfileEditForm(request.POST)
-        if form.is_valid():
-            ## Změny v osobě
-            fcd = form.cleaned_data
-            osoba_edit.jmeno = fcd['jmeno']
-            osoba_edit.prijmeni = fcd['prijmeni']
-            osoba_edit.pohlavi_muz = fcd['pohlavi_muz']
-            osoba_edit.email = fcd['email']
-            osoba_edit.telefon = fcd['telefon']
-            osoba_edit.ulice = fcd['ulice']
-            osoba_edit.mesto = fcd['mesto']
-            osoba_edit.psc = fcd['psc']
-            ## Změny v osobě s podmínkami
-            if fcd.get('spam',False):
-                osoba_edit.datum_souhlasu_zasilani = date.today()
-            if fcd.get('stat','') in ('CZ','SK'):
-                osoba_edit.stat = fcd['stat']
-            else:
-                ## Neznámá země
-                msg = "Unknown country {}".format(fcd['stat_text'])
+	err_logger = logging.getLogger('seminar.prihlaska.problem')
+	## Načtení objektu Osoba a Resitel, patrici k aktuálně přihlášenému uživately
+	u = request.user
+	osoba_edit = Osoba.objects.get(user=u)
+	resitel_edit = osoba_edit.resitel
+	user_edit = osoba_edit.user
+	## Vytvoření slovníku, kterým předvyplním formulář 
+	prefill_1=model_to_dict(user_edit)
+	prefill_2=model_to_dict(resitel_edit)
+	prefill_3=model_to_dict(osoba_edit)
+	prefill_1.update(prefill_2)
+	prefill_1.update(prefill_3)
+	form = ProfileEditForm(initial=prefill_1)
+	## Změna údajů a jejich uložení
+	if request.method == 'POST':
+		form = ProfileEditForm(request.POST)
+		if form.is_valid():
+			## Změny v osobě
+			fcd = form.cleaned_data
+			osoba_edit.jmeno = fcd['jmeno']
+			osoba_edit.prijmeni = fcd['prijmeni']
+			osoba_edit.pohlavi_muz = fcd['pohlavi_muz']
+			osoba_edit.email = fcd['email']
+			osoba_edit.telefon = fcd['telefon']
+			osoba_edit.ulice = fcd['ulice']
+			osoba_edit.mesto = fcd['mesto']
+			osoba_edit.psc = fcd['psc']
+			## Změny v osobě s podmínkami
+			if fcd.get('spam',False):
+				osoba_edit.datum_souhlasu_zasilani = date.today()
+			if fcd.get('stat','') in ('CZ','SK'):
+				osoba_edit.stat = fcd['stat']
+			else:
+				## Neznámá země
+				msg = "Unknown country {}".format(fcd['stat_text'])
 
-            ## Změny v řešiteli
-            resitel_edit.skola = fcd['skola']
-            resitel_edit.rok_maturity = fcd['rok_maturity']
-            resitel_edit.zasilat = fcd['zasilat']
-            if fcd.get('skola'):
-                resitel_edit.skola = fcd['skola']
-            else:
-                # Unknown school - log it
-                msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
-            resitel_edit.save()
-            osoba_edit.save()
-            return HttpResponseRedirect('/thanks/')
-    else:
-        ## Stránka před odeslaním formuláře = předvyplněný formulář
-        return render(request, 'seminar/profil/edit.html', {'form': form})
+			## Změny v řešiteli
+			resitel_edit.skola = fcd['skola']
+			resitel_edit.rok_maturity = fcd['rok_maturity']
+			resitel_edit.zasilat = fcd['zasilat']
+			if fcd.get('skola'):
+				resitel_edit.skola = fcd['skola']
+			else:
+				# Unknown school - log it
+				msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
+			resitel_edit.save()
+			osoba_edit.save()
+			return HttpResponseRedirect('/thanks/')
+	else:
+		## Stránka před odeslaním formuláře = předvyplněný formulář
+		return render(request, 'seminar/profil/edit.html', {'form': form})
 
 def prihlaskaView(request):
 	generic_logger = logging.getLogger('seminar.prihlaska')

From 80d608eb53c8acc9763b9b90068aa33489326df9 Mon Sep 17 00:00:00 2001
From: Jonas Havelka <jonas.havelka@moznabude.cz>
Date: Thu, 3 Sep 2020 16:07:52 +0200
Subject: [PATCH 4/5] =?UTF-8?q?Fix=20o=C5=A1et=C5=99en=C3=AD=20pr=C3=A1zdn?=
 =?UTF-8?q?=C3=A9ho=20seznamu=20a=20chybn=C3=A9ho=20logov=C3=A1n=C3=AD=20r?=
 =?UTF-8?q?egistrace?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

           = pád webu          = nemožnost registrace
---
 seminar/views/views_all.py | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index 242c4962..0144854a 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -992,6 +992,8 @@ def soustredeniUcastniciExportView(request,soustredeni):
 def group_by_rocnik(clanky):
 	''' Vezme zadaný seznam článků a seskupí je podle ročníku.
 	Vrátí seznam seznamů článků ze stejného ročníku.'''
+	if len(clanky) == 0:
+		return clanky
 	clanky.order_by('cislo__rocnik__rocnik')
 	skupiny_clanku = []
 	skupina = []
@@ -1131,7 +1133,7 @@ def logoutView(request):
 
 
 def prihlaska_log_gdpr_safe(logger, gdpr_logger, msg, form_data):
-	msg = "{}, form_hash:{}".format(msg,hash(form_data))
+	msg = "{}, form_hash:{}".format(msg,hash(frozenset(form_data.items)))
 	logger.warn(msg)
 	gdpr_logger.warn(msg+", form:{}".format(form_data))		
 
@@ -1199,8 +1201,8 @@ def prihlaskaView(request):
 		if form.is_valid():
 			generic_logger.info("Form valid")
 			fcd = form.cleaned_data
-			form_hash = hash(fcd)
-			form_logger.info(fcd,form_hash=form_hash)
+			form_hash = hash(frozenset(fcd.items()))
+			form_logger.info(fcd,form_hash)  # TODO takhle log nefunguje, ale ta předchozí varianta dokonce padala
 			
 			with transaction.atomic():
 				u = User.objects.create_user(
@@ -1230,7 +1232,7 @@ def prihlaskaView(request):
 				else:
 					# Unknown country - log it
 					msg = "Unknown country {}".format(fcd['stat_text'])
-					err_logger.warn(msg,form_hash=form_hash)
+					err_logger.warn(msg,form_hash)  # TODO viz výše
 
 				o.save()
 				o.user = u
@@ -1248,7 +1250,7 @@ def prihlaskaView(request):
 				else:
 					# Unknown school - log it
 					msg = "Unknown school {}, {}".format(fcd['skola_nazev'],fcd['skola_adresa'])
-					err_logger.warn(msg,form_hash=form_hash)
+					err_logger.warn(msg,form_hash)  # TODO viz výše
 				r.save()
 
 

From 6491d63714283df6147d61c0964e24bee67d1159 Mon Sep 17 00:00:00 2001
From: Jonas Havelka <jonas.havelka@moznabude.cz>
Date: Thu, 3 Sep 2020 16:12:50 +0200
Subject: [PATCH 5/5] =?UTF-8?q?Fix=20organiz=C3=A1tor=20musel=20m=C3=ADt?=
 =?UTF-8?q?=20vypln=C4=9Bn=C3=A9=20organizuje=20od=20/=20do,=20jinak=20vyh?=
 =?UTF-8?q?azovalo=20chybnou=20chybu?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 seminar/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/seminar/models.py b/seminar/models.py
index deb0c9b9..79d08eae 100644
--- a/seminar/models.py
+++ b/seminar/models.py
@@ -582,7 +582,7 @@ class Organizator(SeminarModelBase):
 		"školu, ale jen obor, možnost zobrazit zvlášť")
 
 	def clean(self):
-		if self.organizuje_od > self.organizuje_do:
+		if self.organizuje_od and self.organizuje_do and (self.organizuje_od > self.organizuje_do):
 			raise ValidationError("Organizátor nemůže skončit s organizováním dříve než začal!")
 		super().clean()