diff --git a/aesop/ovvpfile.py b/aesop/ovvpfile.py
index 471c06c6..4d58af35 100644
--- a/aesop/ovvpfile.py
+++ b/aesop/ovvpfile.py
@@ -1,81 +1,30 @@
-# -*- coding: utf-8 -*-
-
-try:
-    from django.http import HttpResponse
-    from django.utils.encoding import force_text
-except:
-    force_text = str
+from django.http import HttpResponse
+from django.utils.encoding import force_text
 
 
-class OvvpFile(object):
-    def __init__(self):
-        # { header: value, ... }
-        self.headers = {}
-        # [ 'column-name', ... ]
-        self.columns = []
-        # [ { column: value, ...}, ...]
-        self.rows = []
+class OvvpFile:
+	def __init__(self):
+		# { header: value, ... }
+		self.headers = {}
+		# [ 'column-name', ... ]
+		self.columns = []
+		# [ { column: value, ...}, ...]
+		self.rows = []
 
-    def to_lines(self):
-        # header
-        for hk in sorted(self.headers.keys()):
-            yield '%s\t%s\n' % (hk, self.headers[hk])
-        yield '\n'
-        # columns
-        yield '\t'.join([c for c in self.columns]) + '\n'
-        # rows
-        for r in self.rows:
-            yield '\t'.join([force_text(r[c]) for c in self.columns]) + '\n'
+	def to_lines(self):
+		# header
+		for hk in sorted(self.headers.keys()):
+			yield f'{hk}\t{self.headers[hk]}\n'
+		yield '\n'
+		# columns
+		yield '\t'.join(self.columns) + '\n'
+		# rows
+		for r in self.rows:
+			yield '\t'.join([force_text(r[c]) for c in self.columns]) + '\n'
 
-    def to_string(self):
-        return ''.join(self.to_lines())
+	def to_string(self):
+		return ''.join(self.to_lines())
 
-    def to_HttpResponse(self):
-        return HttpResponse(self.to_string(), content_type='text/plain; charset=utf-8')
-
-    def parse_from(self, source, with_headers=True):
-        "Parse data from file, string or line iterator, overwriting self"
-        if isinstance(source, str) or isinstance(source, unicode):
-            return self.parse_from(source.split('\n'))
-            
-        it = iter(source)
-
-        # header
-        self.headers = {}
-        if with_headers:
-            for r in it:
-                if isinstance(r, str):
-                    r = r.decode('utf8')
-                assert isinstance(r, unicode)
-                r = r.rstrip('\n')
-                if r == u"":
-                    break
-                k, v = r.split(u'\t', 1)
-                self.headers[k] = v
-
-        # columns
-        r = it.next()
-        if isinstance(r, str):
-            r = r.decode('utf8')
-        self.columns = [cn.strip() for cn in r.split(u'\t') if cn.strip() != ""]
-
-        # rows
-        self.rows = []
-        for r in it:
-            if isinstance(r, str):
-                r = r.decode('utf8')
-            r = r.rstrip('\n')
-            if not r:
-                break
-            rtup = r.split(u'\t')
-            rdict = {}
-            for ci in range(len(self.columns)):
-                rdict[self.columns[ci]] = rtup[ci]
-            self.rows.append(rdict)
-        
-
-
-def parse(source, with_headers=True):
-    o = OvvpFile()
-    o.parse_from(source, with_headers=with_headers)
-    return o
+	# Pozn: tohle je ta jediná funkce, která se reálně používá…
+	def to_HttpResponse(self):
+		return HttpResponse(self.to_string(), content_type='text/plain; charset=utf-8')
diff --git a/checklinks.sh b/checklinks.sh
index 63898075..71b218f2 100755
--- a/checklinks.sh
+++ b/checklinks.sh
@@ -3,8 +3,16 @@
 # Props to https://www.commandlinefu.com/commands/view/8234/check-broken-links-using-wget-as-a-spider
 set -eu
 
+if test "$#" -lt 1; then
+	echo >&2 "Usage: $0 <wget parameters, especially URL to test>"
+	exit 1
+fi
+
 logfile="$(pwd)/wget.log.$(date +%FT%T)"
 tmp=$(mktemp --directory)
 cd "$tmp"
 
-wget --spider -o "$logfile" -r -p -X '/soustredeni/*/fotogalerie/' "$@"
+wget --spider -o "$logfile" -r -p -X '/soustredeni/*/fotogalerie/' "$@" || true # wget nejspíš skončí s chybou, že něco nestáhl…
+
+echo "Result: (a last few lines of the file $logfile)"
+sed -ne '/^Found [0-9]* broken links/,$ p' "$logfile"
diff --git a/galerie/views.py b/galerie/views.py
index 4570b8fb..f0d9b53b 100644
--- a/galerie/views.py
+++ b/galerie/views.py
@@ -24,7 +24,7 @@ def zobrazit(galerie, request):
 def cesta_od_korene(g):
 	"""Vrátí seznam galerií od kořene ke g"""
 	cesta = []
-	while g != None:
+	while g is not None:
 		cesta.append(g)
 		g = g.galerie_up
 	return reversed(cesta)
@@ -53,7 +53,7 @@ def nahled(request, pk, soustredeni):
 	for g in sourozenci:
 		if g.pk == galerie.pk:
 			predchozi = minuly
-		if minuly != None and minuly.pk == galerie.pk:
+		if minuly is not None and minuly.pk == galerie.pk:
 			nasledujici = g
 			break
 		minuly = g
diff --git a/korektury/migrations/0011_prevod_autora_z_charField_na_Organizator.py b/korektury/migrations/0011_prevod_autora_z_charField_na_Organizator.py
index 9f64bbd5..53082df9 100644
--- a/korektury/migrations/0011_prevod_autora_z_charField_na_Organizator.py
+++ b/korektury/migrations/0011_prevod_autora_z_charField_na_Organizator.py
@@ -4,7 +4,6 @@ from __future__ import unicode_literals
 from django.db import migrations, models
 
 def transform_autor(apps, schema_editor):
-    print
     Organizator = apps.get_model('seminar', 'Organizator')
 
     # preorgovani oprav
diff --git a/seminar/management/commands/auth.py b/seminar/management/commands/auth.py
deleted file mode 100644
index 71757418..00000000
--- a/seminar/management/commands/auth.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from django.core.management.base import BaseCommand
-from django.contrib.sessions.models import Session
-from django.contrib.auth.models import User
-
-class Command(BaseCommand):
-    u"""Vypiš username přihlášeného orga s daným session_key.
-
-    Příkaz pro manage.py, který ze vstupu přečte session_key (tak, jak je
-    uložen v cookie sessionid) a pokud session existuje a příslušný přihlášený
-    uživatel má právo přihlásit se do admina, vypíše jeho username.
-    """
-    def handle(self, *args, **options):
-        session_key = raw_input()
-        s = Session.objects.get(pk=session_key).get_decoded()
-        user_id = s['_auth_user_id']
-        user = User.objects.get(pk=user_id)
-        if user.is_staff:
-            print(user.username)
diff --git a/seminar/models.py b/seminar/models.py
index d4c3ad81..daef62e6 100644
--- a/seminar/models.py
+++ b/seminar/models.py
@@ -1139,6 +1139,7 @@ class Reseni(SeminarModelBase):
 
 	# má ForeignKey s:
 	# Hodnoceni
+	# ReseniNode
 
 	def sum_body(self):
 		return self.hodnoceni_set.all().aggregate(Sum('body'))["body__sum"]
diff --git a/seminar/testutils.py b/seminar/testutils.py
index 8fe8fe0c..019f66ac 100644
--- a/seminar/testutils.py
+++ b/seminar/testutils.py
@@ -77,8 +77,8 @@ def gen_osoby(rnd, size):
 		prezdivka = rnd.choice(prezdivky)
 		email = "@".join([unidecode.unidecode(jmeno), rnd.choice(domain)])
 		telefon = "".join([str(rnd.choice([k for k in range(10)])) for i in range(9)])
-		narozeni = datetime.date(rnd.randint(1980, 2020), rnd.randint(1, 12),
-						rnd.randint(1, 28))
+		narozeni = datetime.date(rnd.randint(1980, datetime.datetime.now().year), 
+				rnd.randint(1, 12), rnd.randint(1, 28))
 		ulic = rnd.choice(seznam_ulic)
 		cp = rnd.randint(1, 99)
 		ulice = " ".join([ulic, str(cp)])
@@ -142,7 +142,7 @@ def gen_resitele(rnd, osoby, skoly):
 				os.save()
 				os.user.user_permissions.add(resitel_perm)
 			resitele.append(Resitel.objects.create(osoba=os, skola=rnd.choice(skoly),
-				rok_maturity=rnd.randint(2019, 2029),
+				rok_maturity=os.datum_narozeni.year + rnd.randint(18, 21),
 				zasilat=rnd.choice(Resitel.ZASILAT_CHOICES)[0]))
 	return resitele
 
@@ -890,6 +890,9 @@ def create_test_data(size = 6, rnd = None):
 	# Dohackované vytvoření jednoho článku
 	gen_clanek(rnd, organizatori, resitele)
 
+			# TODO: přidat články včetně zařazení do struktury treenodů,
+			#	a následně otestovat konsistency check databáze z utils.py
+			# 	pomocí stránky /stav
 
 	# obecné nastavení semináře, musí být už přidané ročníky a čísla, jinak se nastaví divně
 	nastaveni = Nastaveni.objects.create(
diff --git a/seminar/utils.py b/seminar/utils.py
index 4604245c..01469a1f 100644
--- a/seminar/utils.py
+++ b/seminar/utils.py
@@ -88,6 +88,11 @@ def from_roman(rom):
 
 
 def seznam_problemu():
+	"""Funkce pro hledání nekonzistencí v databázi a dalších nežádoucích stavů webu/databáze.
+
+	Nijak nesouvisí s Problémy zadanými řešitelům."""
+	# FIXME: přejmenovat funkci?
+	# FIXME: Tak, jak je napsaná, asi spíš patří někam k views a ne do utils (?)
 	problemy = []
 
 	# Pomocna fce k formatovani problemovych hlasek
diff --git a/seminar/views/views_all.py b/seminar/views/views_all.py
index 56e2aaae..9392f49a 100644
--- a/seminar/views/views_all.py
+++ b/seminar/views/views_all.py
@@ -891,7 +891,7 @@ def TitulyView(request, rocnik, cislo):
 	asciijmena = []
 	jmenovci = False # detekuje, zda jsou dva řešitelé jmenovci (modulo nabodeníčka),
 		# pokud ano, vrátí se jako true
-	slovnik_s_body = body_resitelu(resitele, rocnik_obj)
+	slovnik_s_body = body_resitelu(resitele, cislo_obj)
 
 	for resitel in resitele:
 		resitel.titul = resitel.get_titul(slovnik_s_body[resitel.id])
diff --git a/setup/README b/setup/README
new file mode 100644
index 00000000..3b736cdc
--- /dev/null
+++ b/setup/README
@@ -0,0 +1,3 @@
+Tato složka obsahuje různé konfiguráky potřebné k rozběhnutí webu na serveru.
+
+TODO: Napsat sem i přehled toho, jak to funguje.
diff --git a/setup/nginx/mam-test.ks.matfyz.cz b/setup/nginx/mam-test.ks.matfyz.cz
new file mode 100644
index 00000000..46f9b2ec
--- /dev/null
+++ b/setup/nginx/mam-test.ks.matfyz.cz
@@ -0,0 +1,50 @@
+server {
+    listen 195.113.20.177:80;
+    listen [2001:718:1e03:801::b1]:80;
+    server_name mam-test.ks.matfyz.cz;
+    return 301 https://$server_name$request_uri;
+
+}
+server {
+    # SSL configuration
+    listen 195.113.20.177:443 ssl;
+    listen [2001:718:1e03:801::b1]:443 ssl;
+
+    # SSL keys
+    ssl on;
+    ssl_certificate /etc/letsencrypt/live/mam-test.ks.matfyz.cz/fullchain.pem; # managed by Certbot
+    ssl_certificate_key /etc/letsencrypt/live/mam-test.ks.matfyz.cz/privkey.pem; # managed by Certbot
+    ssl_dhparam /etc/ssl/dhparams.pem;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 5m;
+
+    server_name mam-test.ks.matfyz.cz;
+
+    client_max_body_size 50M;
+
+    auth_basic "MaMweb test - access restricted";
+    auth_basic_user_file /akce/mam/www/mamweb-test/.htpasswd;
+
+    location /static/ {
+        root /akce/mam/www/mamweb-test/;
+    }
+
+    location /media/ {
+        root /akce/mam/www/mamweb-test/;
+    }
+
+    location /aesop-export/ {
+        auth_basic "AESOP API";
+        auth_basic_user_file /akce/mam/www/mamweb-test/.htpasswd-aesop;
+        try_files $uri @mamweb_test;
+    }
+
+    
+    location / { try_files $uri @mamweb_test; }
+
+    location @mamweb_test {
+        include uwsgi_params;
+        uwsgi_pass unix:/tmp/uwsgi-mamweb_test.sock;
+    }
+
+}
diff --git a/setup/nginx/mam.mff.cuni.cz b/setup/nginx/mam.mff.cuni.cz
new file mode 100644
index 00000000..99292a67
--- /dev/null
+++ b/setup/nginx/mam.mff.cuni.cz
@@ -0,0 +1,65 @@
+
+server {
+    listen 195.113.20.177:80;
+    listen [2001:718:1e03:801::b1]:80;
+    server_name mam.mff.cuni.cz;
+    return 301 https://$server_name$request_uri;
+
+}
+server {
+    # SSL configuration
+    #
+    listen 195.113.20.177:443 ssl;
+    listen [2001:718:1e03:801::b1]:443 ssl;
+
+    # SSL keys
+    ssl on;
+    ssl_certificate /etc/ssl/domains/mam.mff.cuni.cz/bundle.pem;
+    ssl_certificate_key /etc/ssl/domains/mam.mff.cuni.cz/privkey.pem;
+    ssl_dhparam /etc/ssl/dhparams.pem;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_timeout 5m;
+    
+
+    server_name mam.mff.cuni.cz;
+    # server_name mamweb.bezva.org;
+
+    client_max_body_size 50M;
+
+    location /aesop-export/ {
+        auth_basic "AESOP API";
+        auth_basic_user_file /akce/mam/www/mamweb-prod/.htpasswd-aesop;
+        try_files $uri @mamweb_prod;
+    }
+
+    location /static/ {
+        root /akce/mam/www/mamweb-prod/;
+    }
+
+    location /media/ {
+        root /akce/mam/www/mamweb-prod/;
+    }
+
+    location /wiki/ {
+        proxy_pass http://127.0.0.1:5001/;
+        proxy_set_header X-Real_IP $remote_addr;
+        proxy_redirect / /wiki/;
+        #rewrite '/' '/wiki'; 
+        sub_filter_once off;
+        sub_filter 'href="/' 'href="/wiki/';
+        sub_filter 'src="/' 'src="/wiki/';
+        sub_filter 'action="/' 'action="/wiki/';
+        # Overkill:
+        #sub_filter '="/' '="/wiki/';
+        #sub_filter ':5001/' '/wiki/';	
+        #sub_filter 'Location: /' 'Location: /wiki/';	
+        #sub_filter '_login' '_test';
+    }
+    
+    location / { try_files $uri @mamweb_prod; }
+
+    location @mamweb_prod {
+        include uwsgi_params;
+        uwsgi_pass unix:/tmp/uwsgi-mamweb_prod.sock;
+    }
+}
diff --git a/setup/systemd/mamweb-prod.service b/setup/systemd/mamweb-prod.service
new file mode 100644
index 00000000..89af2a5c
--- /dev/null
+++ b/setup/systemd/mamweb-prod.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=uWSGI instance to serve mam.mff.cuni.cz
+After=network.target
+
+[Service]
+WorkingDirectory=/akce/mam/www/mamweb-prod
+ExecStart=/usr/bin/uwsgi --ini mamweb_prod.ini
+
+[Install]
+WantedBy=default.target
diff --git a/setup/systemd/mamweb-test.service b/setup/systemd/mamweb-test.service
new file mode 100644
index 00000000..616605bb
--- /dev/null
+++ b/setup/systemd/mamweb-test.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=uWSGI instance to serve mam-test.kam.mff.cuni.cz
+After=network.target
+
+[Service]
+WorkingDirectory=/akce/mam/www/mamweb-test
+ExecStart=/usr/bin/uwsgi --ini mamweb_test.ini
+
+[Install]
+WantedBy=default.target
diff --git a/mamweb_prod.ini b/setup/uwsgi/mamweb_prod.ini
similarity index 100%
rename from mamweb_prod.ini
rename to setup/uwsgi/mamweb_prod.ini
diff --git a/mamweb_test.ini b/setup/uwsgi/mamweb_test.ini
similarity index 100%
rename from mamweb_test.ini
rename to setup/uwsgi/mamweb_test.ini