An error occurred while loading the blob controls.
Select Git revision
edit.py
-
Simon Künzel authoredSimon Künzel authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
edit.py 11.23 KiB
import math
from server import *
# field types:
# boolean
# shortstring
# text
# datetime
# duration
# videotime
editable_tables = { #pylint: disable=invalid-name
'courses': {
'table': 'courses_data',
'idcolumn': 'id',
'editable_fields': {
'visible': {'type': 'boolean', 'description': 'Wenn ein Kurs nicht sichtbar ist sind alle Videos davon nicht abrufbar.'},
'listed': {'type': 'boolean', 'description': 'Soll die Veranstaltung auf der Hauptseite gelistet werden?'},
'title': {'type': 'shortstring'},
'short': {'type': 'shortstring', 'description': 'Abkürzung für die Veranstaltung, z.B. für den Drehplan'},
'handle': {'type': 'shortstring'},
'organizer': {'type': 'shortstring'},
'subject': {'type': 'shortstring'},
'semester': {'type': 'shortstring'},
'downloadable': {'type': 'boolean', 'description': 'Hiermit kann der Download-Button disabled werden'},
'internal': {'type': 'text'},
'responsible': {'type': 'shortstring'},
'deleted': {'type': 'boolean'},
'description': {'type': 'text'},
'external': {'type': 'boolean', 'description': 'Soll die Veranstaltung nicht im Drehplan angezeigt werden?'},
'coursechapters': {'type': 'boolean', 'description': 'Sollen auf der Kursseite die Kapitelmarker der Videos angezeigt werden?'},
'autopublish': {'type': 'boolean', 'description': 'Sollen encodete Videos automatisch verschoben werden?'},
'autovisible': {'type': 'boolean', 'description': 'Sollen neue Videos automatisch sichtbar sein?'},
'login_info': {'type': 'text', 'description': 'Zusätliche Informationen, die dem Nutzer angezeigt werden, wenn er sich anmelden muss.'}
},
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'lectures': {
'table': 'lectures_data',
'idcolumn': 'id',
'editable_fields': {
'visible': {'type': 'boolean', 'description': 'Wenn eine lecture nicht sichtbar ist sind alle Videos davon nicht abrufbar'},
'title': {'type': 'shortstring'},
'comment': {'type': 'text'},
'internal': {'type': 'text'},
'speaker': {'type': 'shortstring'},
'place': {'type': 'shortstring'},
'time': {'type': 'datetime'},
'duration': {'type': 'duration'},
'jumplist': {'type': ''},
'deleted': {'type': 'boolean'},
'live': {'type': 'boolean', 'description': 'Ist ein Livestream geplant? Muss gesetzt sein damit der RTMP Stream zugeordnet wird.'},
'norecording': {'type': 'boolean', 'description': 'Führt dazu, dass der Termin ausgegraut wird.'},
'stream_settings': {'type': 'text'}
},
'creationtime_fields': ['course_id', 'time_created', 'time_updated']},
'videos': {
'table': 'videos_data',
'idcolumn': 'id',
'editable_fields': {
'visible': {'type': 'boolean', 'description': 'Ein nicht sichtbares Video kann nicht abgerufen werden.'},
'deleted': {'type': 'boolean'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'chapters': {
'table': 'chapters',
'idcolumn': 'id',
'editable_fields': {
'time': {'type': 'videotime'},
'text': {'type': 'shortstring'},
'visible': {'type': 'boolean'},
'deleted': {'type': 'boolean'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'announcements': {
'table': 'announcements',
'idcolumn': 'id',
'editable_fields': {
'text': {'type': 'text'},
'level': {'type': 'integer'},
'visible': {'type': 'boolean'},
'deleted': {'type': 'boolean'},
'time_publish': {'type': 'datetime'},
'time_expire': {'type': 'datetime'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'featured': {
'table': 'featured',
'idcolumn': 'id',
'editable_fields': {
'title': {'type': 'shortstring'},
'text': {'type': 'text'},
'internal': {'type': 'text'},
'visible': {'type': 'boolean'},
'deleted': {'type': 'boolean'},
'param': {'type': 'shortstring'},
'param2': {'type': 'shortstring'},
'order': {'type': 'integer'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated', 'type']},
'perm': {
'table': 'perm',
'idcolumn': 'id',
'editable_fields': {
'type': {'type': 'shortstring'},
'param1': {'type': 'shortstring'},
'param2': {'type': 'shortstring'},
'deleted': {'type': 'boolean'}},
'creationtime_fields': ['course_id', 'lecture_id', 'video_id', 'created_by', 'time_created', 'time_updated']},
'sorterrorlog': {
'table': 'sorterrorlog_data',
'idcolumn': 'id',
'editable_fields': {
'deleted': {'type': 'boolean'}},
'creationtime_fields': ['time_created', 'time_updated']},
'users': {
'table': 'users',
'idcolumn': 'id',
'editable_fields': {
'mail_notifications': {'type': 'boolean'},
'notify_chapter_submitted': {'type': 'boolean'},
'notify_new_video': {'type': 'boolean'},
'notify_edit': {'type': 'boolean'}
},
'creationtime_fields': []},
'live_sources': {
'table': 'live_sources',
'idcolumn': 'id',
'editable_fields': {
'name': {'type': 'shortstring'},
'description': {'type': 'text'},
'deleted': {'type': 'boolean'}
},
'creationtime_fields': ['created_by', 'time_created', 'time_updated']}
}
#parses the path to a dict, containing the table, id, field and field type
@app.template_filter(name='parseeditpath')
def parseeditpath(path):
table, id, column = path.split('.', 2)
assert table in editable_tables
assert column in editable_tables[table]['editable_fields']
type = editable_tables[table]['editable_fields'][column]['type']
return {'table': table, 'id': id, 'column': column, 'type': type, 'tableinfo': editable_tables[table]}
@app.template_filter(name='getfielddescription')
def getfielddescription(inputpath):
path = parseeditpath(inputpath)
desc = path['tableinfo']['editable_fields'][path['column']].get('description', '')
if desc != '':
desc = '<br>'+desc
return desc
@app.template_filter(name='getfieldchangelog')
def getfieldchangelog(inputpath):
path = parseeditpath(inputpath)
changelog = query('SELECT * FROM changelog \
LEFT JOIN users ON (changelog.who = users.id) WHERE "table" = ? AND "id_value" = ? and "field" = ? \
ORDER BY "when" DESC LIMIT 5', path['table'], path['id'], path['column'])
for entry in changelog:
entry['id_value'] = str(entry['id_value'])
entry['value_new'] = str(entry['value_new'])
entry['path'] = '.'.join([entry['table'], entry['id_value'], entry['field']])
return changelog
@app.route('/internal/edit', methods=['GET', 'POST'])
@mod_required
@csrf_protect
def edit(prefix='', ignore=None):
if not ignore:
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
path = parseeditpath(key)
modify('INSERT INTO changelog \
("table",id_value, id_key, field, value_new, value_old, "when", who, executed) \
VALUES (?,?,?,?,?, \
(SELECT "%s" FROM %s WHERE %s = ?),?,?,true)'%(
path['column'],
path['tableinfo']['table'],
path['tableinfo']['idcolumn']
),
path['table'],
path['id'],
path['tableinfo']['idcolumn'],
path['column'],
val,
path['id'],
datetime.now(),
session['user']['dbid'])
modify('UPDATE %s SET "%s" = ?, time_updated = ? WHERE "%s" = ?'%(path['tableinfo']['table'], path['column'], path['tableinfo']['idcolumn']),
val, datetime.now(), path['id'])
for func in edit_handlers.get(path['table'], {}).get(None, []):
func(path['table'], path['column'], val, path['id'], session['user']['dbid'])
for func in edit_handlers.get(path['table'], {}).get(path['column'], []):
func(path['table'], path['column'], val, path['id'], session['user']['dbid'])
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 editable_tables
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 editable_tables[table]['creationtime_fields']:
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 in ['ref', '_csrf_token']:
continue
assert column in list(editable_tables[table]['editable_fields'].keys())+editable_tables[table]['creationtime_fields']
assert column not in defaults
columns.append('"'+column+'"')
values.append(val)
assert editable_tables[table]['idcolumn'] == 'id'
id = modify('INSERT INTO %s (%s) VALUES (%s)'%(editable_tables[table]['table'],
','.join(columns), ','.join(['?']*len(values))), *values,
get_id=True)
if table == 'courses':
set_responsible(id, session['user']['dbid'], 1)
if 'ref' in request.values:
return redirect(request.values['ref'])
return str(id), 200
@app.route('/internal/changelog')
@register_navbar('Changelog', icon='book', group='weitere')
@mod_required
def changelog():
page = max(0, int(request.args.get('page', 0)))
pagesize = min(500, int(request.args.get('pagesize', 50)))
changelog = query('SELECT * FROM changelog LEFT JOIN users ON (changelog.who = users.id) ORDER BY "when" DESC LIMIT ? OFFSET ?', pagesize, page*pagesize)
pagecount = math.ceil(query('SELECT count(id) as count FROM changelog')[0]['count']/pagesize)
for entry in changelog:
entry['path'] = '.'.join([entry['table'], entry['id_value'], entry['field']])
return render_template('changelog.html', changelog=changelog, page=page, pagesize=pagesize, pagecount=pagecount)
@app.route('/internal/set/responsible/<int:course_id>/<int:user_id>', defaults={'value': True}, methods=['GET', 'POST'])
@app.route('/internal/unset/responsible/<int:course_id>/<int:user_id>', defaults={'value': False}, methods=['GET', 'POST'])
@mod_required
@csrf_protect
def set_responsible(course_id, user_id, value):
if value:
if not query('SELECT id FROM responsible WHERE course_id = ? AND user_id = ?', course_id, user_id):
modify('INSERT INTO responsible (course_id, user_id) VALUES (?, ?)', course_id, user_id)
else:
modify('DELETE FROM responsible WHERE course_id = ? AND user_id = ?', course_id, user_id)
return "OK", 200
edit_handlers = {} #pylint: disable=invalid-name
def edit_handler(*tables, field=None):
def wrapper(func):
for table in tables:
if table not in edit_handlers:
edit_handlers[table] = {}
if field not in edit_handlers[table]:
edit_handlers[table][field] = []
edit_handlers[table][field].append(func)
return func
return wrapper
@edit_handler('courses')
@edit_handler('lectures')
def notify_edit(table, column, value, id, user_id):
lecture = None
if table == 'lectures':
lecture = query('SELECT * FROM lectures_data WHERE id = ?', id)[0]
course_id = lecture['course_id']
elif table == 'courses':
course_id = id
course = query('SELECT * FROM courses_data WHERE id = ?', course_id)[0]
user = query('SELECT * FROM users WHERE id = ?', user_id)[0]
notify_mods('edit', course_id, exclude_uids=[user_id], course=course, lecture=lecture, table=table, column=column, value=value, user=user)