diff --git a/editapi.py b/editapi.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b566e68d7db7a2f95fe7e55c5639f65ec1f46e6
--- /dev/null
+++ b/editapi.py
@@ -0,0 +1,80 @@
+from server import *
+
+# name: (tablename, idcolumn, [editable_fields], [fields_to_set_at_creation_time])
+tabs = {
+	'courses': ('courses_data', 'id', ['visible', 'listed', 'title', 'short',
+			'handle', 'organizer', 'subject', 'semester', 'downloadable',
+			'internal', 'responsible','deleted','description'],
+			['created_by', 'time_created', 'time_updated']),
+	'lectures': ('lectures_data', 'id', ['visible', 'title', 'comment',
+			'internal', 'speaker', 'place', 'time', 'duration', 'jumplist','deleted'],
+			['course_id', 'time_created', 'time_updated']),
+	'videos': ('videos_data', 'id', ['visible','deleted'],
+			['created_by', 'time_created', 'time_updated']),
+	'chapters': ('chapters', 'id', ['time', 'text', 'visible', 'deleted'],
+			['created_by', 'time_created', 'time_updated']),
+	'announcements': ('announcements', 'id', ['text', 'level', 'visible',
+			'deleted', 'time_publish', 'time_expire'],
+			['created_by', 'time_created', 'time_updated']),
+	'featured': ('featured', 'id', ['title', 'text', 'internal', 'visible', 'deleted', 'param', 'param2', 'order'],
+			['created_by', 'time_created', 'time_updated', 'type']),
+	'perm': ('perm', 'id', ['type', 'param1', 'param2', 'deleted'],
+			['course_id', 'lecture_id', 'video_id', 'created_by', 'time_created', 'time_updated']),
+	'sorterrorlog': ('sorterrorlog_data', 'id', ['deleted'],
+			['time_created', 'time_updated'])
+}
+
+@app.route('/internal/edit', methods=['GET', 'POST'])
+@mod_required
+@csrf_protect
+def edit(prefix='', ignore=[]):
+	# All editable tables are expected to have a 'time_updated' field
+	ignore.append('ref')
+	ignore.append('prefix')
+	ignore.append('_csrf_token')
+	if not prefix and 'prefix' in request.args:
+		prefix = request.args['prefix']
+	changes = request.values.items()
+	if (request.method == 'POST') and (request.get_json()):
+		changes = request.get_json().items()
+	for key, val in changes:
+		if key in ignore:
+			continue
+		key = prefix+key
+		table, id, column = key.split('.', 2)
+		assert table in tabs
+		assert column in tabs[table][2]
+		modify('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']['dbid'])
+		modify('UPDATE %s SET `%s` = ?, time_updated = ? WHERE `%s` = ?'%(tabs[table][0], column, tabs[table][1]), val, datetime.now(), id)
+	if 'ref' in request.values:
+		return redirect(request.values['ref'])
+	return "OK", 200
+
+@app.route('/internal/new/<table>', methods=['GET', 'POST'])
+@mod_required
+@csrf_protect
+def create(table):
+	assert table in tabs
+	defaults = {'created_by': session['user']['dbid'], 'time_created': datetime.now(), 'time_updated': datetime.now()}
+	columns = []
+	values = []
+	for column, val in defaults.items():
+		if column in tabs[table][3]:
+			columns.append(column)
+			values.append(val)
+	args = request.values.items()
+	if (request.method == 'POST') and (request.get_json()):
+		args = request.get_json().items()
+	for column, val in args:
+		if (column == 'ref') or (column == '_csrf_token'):
+			continue
+		assert column in tabs[table][2]+tabs[table][3]
+		assert column not in defaults
+		columns.append('`'+column+'`')
+		values.append(val)
+	id = modify('INSERT INTO %s (%s) VALUES (%s)'%(tabs[table][0],
+				','.join(columns), ','.join(['?']*len(values))), *values)
+	if 'ref' in request.values:
+		return redirect(request.values['ref'])
+	return str(id), 200
diff --git a/server.py b/server.py
index 1e5a1a27e6d1eadfcedb5a6c70a5216ed81c8c42..e505995385ad82812e0a3e834beadcf01a45faac 100644
--- a/server.py
+++ b/server.py
@@ -497,85 +497,6 @@ def logout():
 	session.pop('user')
 	return redirect(request.values.get('ref', url_for('index')))
 
-# name: (tablename, idcolumn, [editable_fields], [fields_to_set_at_creation_time])
-tabs = {
-	'courses': ('courses_data', 'id', ['visible', 'listed', 'title', 'short',
-			'handle', 'organizer', 'subject', 'semester', 'downloadable',
-			'internal', 'responsible','deleted','description'],
-			['created_by', 'time_created', 'time_updated']),
-	'lectures': ('lectures_data', 'id', ['visible', 'title', 'comment',
-			'internal', 'speaker', 'place', 'time', 'duration', 'jumplist','deleted'],
-			['course_id', 'time_created', 'time_updated']),
-	'videos': ('videos_data', 'id', ['visible','deleted'],
-			['created_by', 'time_created', 'time_updated']),
-	'chapters': ('chapters', 'id', ['time', 'text', 'visible', 'deleted'],
-			['created_by', 'time_created', 'time_updated']),
-	'announcements': ('announcements', 'id', ['text', 'level', 'visible',
-			'deleted', 'time_publish', 'time_expire'],
-			['created_by', 'time_created', 'time_updated']),
-	'featured': ('featured', 'id', ['title', 'text', 'internal', 'visible', 'deleted', 'param', 'param2', 'order'],
-			['created_by', 'time_created', 'time_updated', 'type']),
-	'perm': ('perm', 'id', ['type', 'param1', 'param2', 'deleted'],
-			['course_id', 'lecture_id', 'video_id', 'created_by', 'time_created', 'time_updated']),
-	'sorterrorlog': ('sorterrorlog_data', 'id', ['deleted'],
-			['time_created', 'time_updated'])
-}
-
-@app.route('/internal/edit', methods=['GET', 'POST'])
-@mod_required
-@csrf_protect
-def edit(prefix='', ignore=[]):
-	# All editable tables are expected to have a 'time_updated' field
-	ignore.append('ref')
-	ignore.append('prefix')
-	ignore.append('_csrf_token')
-	if not prefix and 'prefix' in request.args:
-		prefix = request.args['prefix']
-	changes = request.values.items()
-	if (request.method == 'POST') and (request.get_json()):
-		changes = request.get_json().items()
-	for key, val in changes:
-		if key in ignore:
-			continue
-		key = prefix+key
-		table, id, column = key.split('.', 2)
-		assert table in tabs
-		assert column in tabs[table][2]
-		modify('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']['dbid'])
-		modify('UPDATE %s SET `%s` = ?, time_updated = ? WHERE `%s` = ?'%(tabs[table][0], column, tabs[table][1]), val, datetime.now(), id)
-	if 'ref' in request.values:
-		return redirect(request.values['ref'])
-	return "OK", 200
-
-@app.route('/internal/new/<table>', methods=['GET', 'POST'])
-@mod_required
-@csrf_protect
-def create(table):
-	assert table in tabs
-	defaults = {'created_by': session['user']['dbid'], 'time_created': datetime.now(), 'time_updated': datetime.now()}
-	columns = []
-	values = []
-	for column, val in defaults.items():
-		if column in tabs[table][3]:
-			columns.append(column)
-			values.append(val)
-	args = request.values.items()
-	if (request.method == 'POST') and (request.get_json()):
-		args = request.get_json().items()
-	for column, val in args:
-		if (column == 'ref') or (column == '_csrf_token'):
-			continue
-		assert column in tabs[table][2]+tabs[table][3]
-		assert column not in defaults
-		columns.append('`'+column+'`')
-		values.append(val)
-	id = modify('INSERT INTO %s (%s) VALUES (%s)'%(tabs[table][0],
-				','.join(columns), ','.join(['?']*len(values))), *values)
-	if 'ref' in request.values:
-		return redirect(request.values['ref'])
-	return str(id), 200
-
 @app.route('/internal/auth')
 def auth(): # For use with nginx auth_request
 	if 'X-Original-Uri' not in request.headers:
@@ -724,7 +645,7 @@ def legacy(phpfile=None):
 	print("Unknown legacy url:",request.url)
 	return redirect(url_for('index'),code=302)
 	
-
+import editapi
 import feeds
 import importer
 import stats