diff --git a/db.py b/db.py
index db07d3397e35ddbcd677e3ea35d225d62f84bafa..55484b9e831550c75331bff91e1445236505ea46 100644
--- a/db.py
+++ b/db.py
@@ -1,6 +1,7 @@
 from server import *
 import sqlite3
 import re
+import datetime
 
 if config['DB_ENGINE'] == 'sqlite':
 	created = not os.path.exists(config['SQLITE_DB'])
@@ -23,6 +24,25 @@ def dict_factory(cursor, row):
 			d[col[0].split('.')[-1]] = row[idx]
 	return d
 
+# From sqlite3 module, but with error catching
+def sqlite_convert_timestamp(val):
+	try:
+		datepart, timepart = val.split(b" ")
+		year, month, day = map(int, datepart.split(b"-"))
+		timepart_full = timepart.split(b".")
+		hours, minutes, seconds = map(int, timepart_full[0].split(b":"))
+		if len(timepart_full) == 2:
+				microseconds = int('{:0<6.6}'.format(timepart_full[1].decode()))
+		else:
+				microseconds = 0
+		val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
+	except:
+		val = None
+	return val
+
+sqlite3.register_converter('datetime', sqlite_convert_timestamp)
+sqlite3.register_converter('timestamp', sqlite_convert_timestamp)
+
 def query(operation, *params):
 	if config['DB_ENGINE'] == 'mysql':
 		import mysql.connector
@@ -33,7 +53,7 @@ def query(operation, *params):
 		request.db.execute(operation.replace('?', '%s'), params)
 	elif config['DB_ENGINE'] == 'sqlite':
 		if 'db' not in g:
-			g.db = sqlite3.connect(config['SQLITE_DB'])
+			g.db = sqlite3.connect(config['SQLITE_DB'], detect_types=sqlite3.PARSE_DECLTYPES)
 			g.db.row_factory = dict_factory
 			g.db.isolation_level = None
 		if not hasattr(request, 'db'):
@@ -99,6 +119,8 @@ def ldapget(user):
 		conn = ldap3.Connection('ldaps://rumo.fsmpi.rwth-aachen.de', auto_bind=True)
 		conn.search("ou=users,dc=fsmpi,dc=rwth-aachen,dc=de", "(uid=%s)"%user,
 				attributes=ldap3.ALL_ATTRIBUTES)
+		if not conn.entries:
+			return {}
 		e = conn.entries[0]
 		return {'uid': user, 'givenName': e.givenName.value, 'sn':e.sn.value}
 	else:
diff --git a/server.py b/server.py
index 24597db5786d7f9ce0822bca6308d8ae6b78a47b..0bb894525274fbe54439cec74a7131c0a07794de 100755
--- a/server.py
+++ b/server.py
@@ -1,11 +1,23 @@
 #!/bin/python
 from flask import Flask, g, request, url_for, redirect, session, render_template, flash
+from werkzeug.routing import Rule
 from functools import wraps
 from datetime import date, timedelta, datetime, time
+import threading
 import os
 
 app = Flask(__name__)
 
+def timer_func():
+	with app.test_request_context():
+		pass # do something
+	timer = threading.Timer(60*60, timer_func)
+	timer.start()
+
+timer = threading.Timer(0, timer_func)
+timer.daemon = True
+timer.start()
+
 config = app.config
 config['DB_SCHEMA'] = 'db_schema.sql'
 config['DB_DATA'] = 'db_example.sql'
@@ -22,14 +34,12 @@ config.from_pyfile('config.py', silent=True)
 
 from db import query, searchquery, ldapauth, ldapget
 
-app.jinja_env.globals['videoprefix'] = config['VIDEOPREFIX']
 mod_endpoints = []
 
+@app.template_global()
 def ismod(*args):
 	return ('user' in session)
 
-app.jinja_env.globals['ismod'] = ismod
-
 def mod_required(func):
 	mod_endpoints.append(func.__name__)
 	@wraps(func)
@@ -50,6 +60,28 @@ def register_navbar(name, icon=None):
 		return func
 	return wrapper
 
+def render_endpoint(endpoint, flashtext=None, **kargs):
+	if flashtext:
+		flash(flashtext)
+	# request.endpoint is used for navbar highlighting
+	request.url_rule = Rule(request.path, endpoint=endpoint)
+	return app.view_functions[endpoint](**kargs)
+
+def handle_errors(endpoint, text, code, *errors, **epargs):
+	def wrapper(func):
+		@wraps(func)
+		def decorator(*args, **kwargs):
+			try:
+				return func(*args, **kwargs)
+			except errors:
+				return render_endpoint(endpoint, text, **epargs), code
+		return decorator
+	return wrapper
+
+@app.errorhandler(404)
+def handle_not_found(e):
+	return render_endpoint('index', 'Diese Seite existiert nicht!'), 404
+
 @app.route('/')
 @register_navbar('Home', icon='home')
 def index():
@@ -72,16 +104,18 @@ def course():
 		if course['semester'] == '':
 			course['semester'] = 'zeitlos'
 	groupedby = request.args.get('groupedby')
-	if groupedby not in ['title','semester','organizer']:
+	if groupedby not in ['title', 'semester', 'organizer']:
 		groupedby = 'semester'
 	return render_template('course.html', courses=courses, groupedby=groupedby)
 
 @app.route('/course/<id>')
-def course_id(id):
-	courses = query('SELECT * FROM courses WHERE ((handle = ?) or id = ?) AND (? OR visible)', id, id, ismod())
-	if not courses:
-		flash('Diese Veranstaltung existiert nicht!')
-		return app.view_functions['course'](), 404
+@app.route('/course/<int:numid>')
+@handle_errors('course', 'Diese Veranstaltung existiert nicht!', 404, IndexError)
+def course_id(numid=None, id=None):
+	if numid:
+		courses = query('SELECT * FROM courses WHERE id = ? AND (? OR visible)', numid, ismod())
+	else:
+		courses = query('SELECT * FROM courses WHERE handle = ? AND (? OR visible)', id, ismod())
 	lectures = query('SELECT * FROM lectures WHERE course_id = ? AND (? OR visible)', courses[0]['id'], ismod())
 	videos = query('''
 			SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, formats.description AS format_description
@@ -100,18 +134,15 @@ def faq():
 	return render_template('faq.html')
 
 @app.route('/play/<int:id>')
+@handle_errors('course', 'Diese Vorlesung existiert nicht!', 404, IndexError)
 def play(id):
 	lectures = query('SELECT * FROM lectures WHERE id = ? AND (? OR visible)', id, ismod())
 	videos = query('SELECT * FROM videos WHERE lecture_id = ? AND (? OR visible)', id, ismod())
-	if not lectures:
-		flash('Diese Vorlesung existiert nicht!')
-		return app.view_functions['course'](), 404
 	if not videos:
 		flash('Zu dieser Vorlesung wurden noch keine Videos veröffentlicht!')
 	courses = query('SELECT * FROM courses WHERE id = ? AND (? OR (visible AND listed))', lectures[0]['course_id'], ismod())
 	if not courses:
-		flash('Diese Veranstaltung existiert nicht!')
-		return app.view_functions['course'](), 404
+		return render_endpoint('course', 'Diese Veranstaltung existiert nicht!'), 404
 	return render_template('play.html', course=courses[0], lecture=lectures[0], videos=videos)
 
 @app.route('/search')
@@ -132,10 +163,15 @@ def login():
 	if request.method == 'GET':
 		return render_template('login.html')
 	user, groups = ldapauth(request.form.get('user'), request.form.get('password'))
-	if user and 'users' in groups:
-		session['user'] = ldapget(user)
-	else:
+	if not user or not 'users' in groups:
 		flash('Login fehlgeschlagen!')
+		return render_template('login.html')
+	session['user'] = ldapget(user)
+	dbuser = query('SELECT * FROM users WHERE name = ?', user)
+	if not dbuser:
+		query('INSERT INTO users (name, realname, fsacc, level, calendar_key, rfc6238) VALUES (?, ?, ?, 1, "", "")', user, session['user']['givenName'], user)
+		dbuser = query('SELECT * FROM users WHERE name = ?', user)
+	session['user']['dbid'] = dbuser[0]['id']
 	return redirect(request.values.get('ref', url_for('index')))
 
 @app.route('/logout', methods=['GET', 'POST'])
@@ -165,8 +201,7 @@ def edit():
 		assert table in tabs
 		assert column in tabs[table][2]
 		query('INSERT INTO changelog ("table",id_value,id_key,field,value_new,value_old,"when",who,executed) VALUES (?,?,?,?,?,(SELECT %s FROM %s WHERE %s = ?),?,?,1)'%(column,tabs[table][0],tabs[table][1]),table,id,tabs[table][1],column,val,id,datetime.now(),session['user']['givenName'])
-		query('UPDATE %s SET %s = ? WHERE %s = ?'%(tabs[table][0], column,
-					tabs[table][1]), val, id)
+		query('UPDATE %s SET %s = ? WHERE %s = ?'%(tabs[table][0], column,tabs[table][1]), val, id)
 
 	query('COMMIT')
 	return "OK", 200
@@ -224,10 +259,9 @@ def schedule():
 		curcol=0;
 		freecol=[];
 		for l in i['lectures']:
-			l['time_asdate'] = datetime.strptime(l['time'],'%Y-%m-%d %H:%M:%S')
 			# who the hell inserts lectures with zero length?!?!?
-			l['end_asdate'] = l['time_asdate']+timedelta(minutes=max(l['duration'],1))
-		for l in sorted([(l['time_asdate'],True,l) for l in i['lectures']] + [(l['end_asdate'],False,l) for l in i['lectures']],key=lambda t:(t[0],t[1])):
+			l['time_end'] = l['time']+timedelta(minutes=max(l['duration'],1))
+		for l in sorted([(l['time'],True,l) for l in i['lectures']] + [(l['time_end'],False,l) for l in i['lectures']],key=lambda t:(t[0],t[1])):
 			if l[1]:
 				curcol += 1
 				if curcol > maxcol:
@@ -262,4 +296,5 @@ def stats():
 @register_navbar('Changelog', 'book')
 @mod_required
 def log():
-	return render_template('log.html', changelog=query('SELECT *, ( "table" || "." || id_value || "." ||field) as path FROM changelog ORDER BY "when" DESC LIMIT 50'))
+	changelog = query('SELECT *, ( "table" || "." || id_value || "." ||field) as path FROM changelog LEFT JOIN users ON (changelog.who = users.id) ORDER BY "when" DESC LIMIT 50')
+	return render_template('log.html', changelog=changelog)
diff --git a/templates/base.html b/templates/base.html
index 7996514e0afcbfd0f6706d8373100a31c591be44..726ec229268b803ca2aea1f15fc631b9f65d0d7e 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -49,7 +49,7 @@
 									{% for endpoint, caption, gly, visible in navbar %}
 									{% if visible or ismod() %}
 									 <li{% if endpoint == request.endpoint %} class="active"{% endif %}>
-										 <a href="{{ url_for(endpoint)|e }}">{% if gly != '' %}<span class="glyphicon glyphicon-{{ gly }}"></span> {% endif %}{{ caption }}</a>
+										 <a href="{{ url_for(endpoint) }}">{% if gly != '' %}<span class="glyphicon glyphicon-{{ gly }}"></span> {% endif %}{{ caption }}</a>
 									</li>
 									{% endif %}
 									{% endfor %}
@@ -63,12 +63,12 @@
 													{
 														html:true,
 														title:'Login für Moderatoren',
-														content:'<form method="post" action="{{url_for('login')}}"><input placeholder="User" name="user" type="text"><br><input placeholder="Password" name="password" type="password"><br><input type="hidden" name="ref" value="{{ request.url|e }}"><input type="submit" value="Login"></form>'
+														content:'<form method="post" action="{{url_for('login', ref=request.url)}}"><input placeholder="User" name="user" type="text"><br><input placeholder="Password" name="password" type="password"><br><input type="submit" value="Login"></form>'
 													}
 											)
 										</script>
 										{% else %}
-										<a href="{{url_for('logout')}}?ref={{ request.url|urlencode }}">
+										<a href="{{url_for('logout', ref=request.url)}}">
 											{{ session.user.givenName }}
 											<span class="glyphicon glyphicon-log-out"></span>
 										</a>
diff --git a/templates/log.html b/templates/log.html
index d67e0033fe1024f1806c81dd90b41e4754b9aa72..a3970c2a2205c1c583eff2344421a4b1854d3140 100644
--- a/templates/log.html
+++ b/templates/log.html
@@ -19,7 +19,11 @@
 				{% for i in changelog %}
 					<tr>
 						<td>{{i.when}}</td>
-						<td>{{i.who}}</td>
+						{% if i.realname %}
+							<td>{{i.realname}} ({{i.who}})</td>
+						{% else %}
+							<td>{{i.who}}</td>
+						{% endif %}
 						<td>{{i.path}}</td>
 						<td>"{{i.value_old}}"</td>
 						<td>"{{i.value_new}}"</td>
diff --git a/templates/macros.html b/templates/macros.html
index dac9b903b24a6ae24f301b3f873ef11fb25b357c..2f205d05e6b82c034a4599fa33793c5100b4574f 100644
--- a/templates/macros.html
+++ b/templates/macros.html
@@ -2,7 +2,7 @@
 <li class="list-group-item">
 	<a class="hidden-xs" href="{{url_for('play', id=lecture['id'])}}" title="{{ lecture['coursetitle'] }}">
 		<div class="row">
-			<img class="col-xs-4" src="{{ videoprefix }}/{{ lecture['titlefile'] }}" alt="Vorschaubild">
+			<img class="col-xs-4" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild">
 			<div class="col-xs-4">
 				<span style="color: #000;"><strong>{{ lecture['short'] }}</strong></span><br>
 				<span style="color: #000;">{{ lecture['time'] }}</span>
@@ -18,7 +18,7 @@
 	</a>
 	<a class="visible-xs" href="{{url_for('play', id=lecture['id'])}}" title="{{ lecture['coursetitle'] }}">
 		<div class="row">
-			<img class="col-xs-12" src="{{ videoprefix }}/{{ lecture['titlefile'] }}" alt="Vorschaubild">
+			<img class="col-xs-12" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild">
 		</div>
 		<div class="row">
 			<div class="col-xs-12">
@@ -53,7 +53,7 @@
 <link rel="stylesheet" href="{{url_for('static', filename='mediaelementjs/mediaelementplayer.css')}}" />
 <video class="mejs-player" width="640" height="360" style="width: 100%; height: 100%;">
 	{% for v in videos %}
-		<source type="video/mp4" src="{{ videoprefix }}/{{ v.path }}" />
+		<source type="video/mp4" src="{{ config.VIDEOPREFIX }}/{{ v.path }}" />
 	{% endfor %}
 </video>
 <script>
@@ -67,7 +67,7 @@
 {% macro course_list_item(course,show_semester=False) %}
 <li class="list-group-item {% if (not course.visible) or (not course.listed) %}list-group-item-danger{% endif %}">
 	<div class="row">
-		<a href="{{url_for('course_id', id=course.handle)}}">
+		<a href="{{url_for('course_id', numid=course.id)}}">
 			{% if show_semester %}
 				<span class="col-xs-1">
 					{{ course.semester }}
@@ -93,13 +93,13 @@
 <button class="btn btn-primary dropdown-toggle {% if videos|length is equalto 0 %}disabled{% endif %}" type="button" data-toggle="dropdown">Download <span class="caret"></span></button>
 <ul class="dropdown-menu">
 	{% for v in videos %}
-	{% if v.downloadable %}	<li><a href="{{ videoprefix }}/{{v.path}}">{{ valuecheckbox(['videos',v.id,'visible'], v.visible) }} {{v.format_description}} ({{v.file_size|filesizeformat(true)}})</a></li>{% endif %}
+	{% if v.downloadable %}	<li><a href="{{ config.VIDEOPREFIX }}/{{v.path}}">{{ valuecheckbox(['videos',v.id,'visible'], v.visible) }} {{v.format_description}} ({{v.file_size|filesizeformat(true)}})</a></li>{% endif %}
 	{% endfor %}
 </ul>
 <noscript>
 	<ul class="pull-right list-unstyled" style="margin-left:10px;">
 		{% for v in videos %}
-		<li><a href="{{ videoprefix }}/{{v.path}}">{{v.format_description}} ({{v.file_size|filesizeformat(true)}})</a></li>
+		<li><a href="{{ config.VIDEOPREFIX }}/{{v.path}}">{{v.format_description}} ({{v.file_size|filesizeformat(true)}})</a></li>
 		{% endfor %}
 	</ul>
 </noscript>
diff --git a/templates/play.html b/templates/play.html
index 6ffa50688b04ebf0ed70bf4a9622290f538132af..59a9bc11bf4014371f3fb762c0b9debdc0ef2b45 100644
--- a/templates/play.html
+++ b/templates/play.html
@@ -11,7 +11,7 @@
 	</div>
 	<div class="panel-body">
 		<p class="col-xs-12" style="padding: 0px;">
-			<span><a href="{{url_for('course_id', id=course.handle)}}" class="btn btn-default" >Zur Veranstaltungsseite</a><span>
+			<span><a href="{{url_for('course_id', numid=course.id)}}#lecture-{{lecture.id}}" class="btn btn-default" >Zur Veranstaltungsseite</a><span>
 			<span class="pull-right">
 				<span>{{ video_embed_btn(lecture.id) }}</span>
 				<span>{{ video_download_btn(videos) }}</span>
diff --git a/templates/schedule.html b/templates/schedule.html
index dbd4123463705f73214091cc3f330893b92bb92c..8a6a883b4a1af38e966e617bbc5cf16e7ad2d928 100644
--- a/templates/schedule.html
+++ b/templates/schedule.html
@@ -21,15 +21,15 @@
 					{% if ((loop.index - 1) is divisibleby 4)  %} <td rowspan="4" style="vertical-align: top;">{{ t.strftime("%H:%M") }}</td> {% endif %}
 					{% for d in days  if (d.index < 5) or (d.lectures|length) > 0 %}
 						{% for i in range(1,d.maxcol+1) %}
-						{% for l in d.lectures|selectattr('schedule_col','equalto',i) if (((l.time_asdate.time() > t) and (l.time_asdate.time() < times[time_loop.index+1])) != (l.time_asdate.time() == t ) ) %}
+						{% for l in d.lectures|selectattr('schedule_col','equalto',i) if (((l.time.time() > t) and (l.time.time() < times[time_loop.index+1])) != (l.time.time() == t ) ) %}
 						<td rowspan="{{l.duration / 15}}" style="background: lightgrey;">
 							<p class="small">
-							<strong><a href="{{url_for('course_id', id=l['course_id'])}}#lecture-{{l.id}}">{{l.short}}</a></strong><br>
-								{{l.time_asdate.strftime("%H:%M")}} - {{l.end_asdate.strftime("%H:%M")}}<br>
+							<strong><a href="{{url_for('course_id', numid=l['course_id'])}}#lecture-{{l.id}}">{{l.short}}</a></strong><br>
+								{{l.time.strftime("%H:%M")}} - {{l.time_end.strftime("%H:%M")}}<br>
 								{{l.place}}</p>
 						</td>
 							{% else %}
-								{% for l in d.lectures|selectattr('schedule_col','equalto',i) if (l.time_asdate.time() < t) and (l.end_asdate.time() > t) %}
+								{% for l in d.lectures|selectattr('schedule_col','equalto',i) if (l.time.time() < t) and (l.time_end.time() > t) %}
 								{% else %}
 									<td></td>
 								{% endfor %}