Commit a4abf7cf authored by Julian Rother's avatar Julian Rother Committed by Andreas Valder

Improve/fix CI pipeline and linter

parent 49055faf
linter:
image: debian:buster
image: registry.git.fsmpi.rwth-aachen.de/videoaginfra/testenvs/stretch
stage: test
script:
- apt update
- apt install -y python3
- python3 -V
- uname -a
- apt install -y sqlite3 locales-all git python3-flask python3-ldap3 python3-requests python3-lxml python3-icalendar python3-requests python3-coverage pylint3
- pylint3 --indent-string='\t' --indent-after-paren=1 --max-line-length=160 --disable=missing-docstring,unused-wildcard-import --output-format=text *.py | tee pylint.txt
- python3 -V
- pylint --version
- pylint --rcfile=.pylintrc *.py | tee pylint.txt
artifacts:
paths:
- pylint.txt
unittest: &unittest
image: debian:stretch
image: registry.git.fsmpi.rwth-aachen.de/videoaginfra/testenvs/stretch
stage: test
script:
- apt update
- apt install -y python3
- python3 -V
- uname -a
- apt install -y sqlite3 locales-all git python3-flask python3-ldap3 python3-requests python3-lxml python3-icalendar python3-mysql.connector python3-requests python3-coverage
- python3 -m coverage run runTests.py
- python3 -V
- python3 -m coverage run run_tests.py
- python3 -m coverage report --include "./*"
- python3 -m coverage report -m --include "./*" > report.txt
- python3 -m coverage html --include "./*"
......@@ -36,21 +31,21 @@ unittest: &unittest
#unittest_buster:
# <<: *unittest
# image: debian:buster
livetest:
image: debian:stretch
stage: test
script:
- apt update
- apt install -y python3
- python3 -V
- uname -a
- apt install -y python3-requests
#
#livetest:
# image: debian:stretch
# stage: test
# script:
# - apt update
# - apt install -y python3
# - python3 -V
# - uname -a
# - apt install -y python3-requests
# - ./tests/urlcheck_sinf.py
deploy_staging:
image: archlinux/base
stage: deploy
script:
- pacman --noconfirm -Sy ansible git
#
#deploy_staging:
# image: archlinux/base
# stage: deploy
# script:
# - pacman --noconfirm -Sy ansible git
#
This diff is collapsed.
......@@ -15,9 +15,9 @@ Hinweis: diese Variante startet eine lokale Testversion der Website, es sind nic
Alternativ, insbesondere zum Testen der Zugriffsbeschränkungen: Siehe `nginx.example.conf`.
### Unittests
Tests können mittels `./runTests.py` ausgeführt werden.
Tests können mittels `./run_tests.py` ausgeführt werden.
Coverage Tests können mittels `rm .coverage; python -m coverage run runTests.py; python -m coverage html` ausgeführt werden. Dies erstellt einen Ordner `htmlcov` in dem HTML Output liegt.
Coverage Tests können mittels `rm .coverage; python -m coverage run run_tests.py; python -m coverage html` ausgeführt werden. Dies erstellt einen Ordner `htmlcov` in dem HTML Output liegt.
### Zum Mitmachen:
1. Repo für den eigenen User forken, dafür den "Fork-Button" auf der Website verwenden
......
......@@ -2,7 +2,7 @@ import json
from server import *
@job_handler('probe', 'probe-raw')
def import_xmp_chapters(jobid, jobtype, data, state, status):
def import_xmp_chapters(jobid, jobtype, data, state, status): #pylint: disable=unused-argument
if 'lecture_id' not in data or not data.get('import-chapters', False):
return
times = set()
......@@ -57,7 +57,7 @@ def chapters(lectureid):
last = None
for chapter in chapters:
chapter['start'] = chapter['time']
chapter['end'] = last['start'] if last else 9999
chapter['end'] = last['start'] if last else 9999 #pylint: disable=unsubscriptable-object
last = chapter
if 'json' in request.values:
return Response(json.dumps([{'time': c['time'], 'text': c['text']} for c in chapters]), mimetype='application/json')
......
......@@ -24,7 +24,7 @@ def cutprogress(user=None):
ORDER BY users.realname ASC
''', course['id'])
if not people:
people = [{ 'realname': 'Niemand', 'id': -1 }]
people = [{'realname': 'Niemand', 'id': -1}]
course['responsible'] = people
if user is not None:
courses = [
......@@ -33,7 +33,7 @@ def cutprogress(user=None):
]
# Fetch lectures for all courses
lectures = []
for c in courses:
for course in courses:
lectures += query('''
SELECT
lectures.id,
......@@ -50,7 +50,7 @@ def cutprogress(user=None):
AND NOT lectures.norecording
GROUP BY lectures.id
ORDER BY lectures.time ASC, lectures.id ASC
''', c['id'], datetime.now())
''', course['id'], datetime.now())
# Generate list of days, figure out when weeks change
dates = sorted({row['time'].date() for row in lectures}, reverse=True)
is_new_weeks = [
......
import sqlite3
from flask import g
from server import *
if config['DB_ENGINE'] == 'sqlite':
import sqlite3
# From sqlite3 module, but with error catching
def convert_timestamp(val):
try:
......@@ -19,13 +20,13 @@ if config['DB_ENGINE'] == 'sqlite':
sqlite3.register_converter('timestamp', convert_timestamp)
if config['DB_ENGINE'] == 'sqlite':
created = not os.path.exists(config['SQLITE_DB'])
DBCREATED = not os.path.exists(config['SQLITE_DB'])
db = sqlite3.connect(config['SQLITE_DB'])
cur = db.cursor()
if config['SQLITE_INIT_SCHEMA']:
print('Init db schema')
cur.executescript(open(config['DB_SCHEMA']).read())
if config['SQLITE_INIT_DATA'] and created:
if config['SQLITE_INIT_DATA'] and DBCREATED:
print('Init db data')
cur.executescript(open(config['DB_DATA']).read())
db.commit()
......@@ -43,12 +44,11 @@ if config['DB_ENGINE'] == 'sqlite':
params = [(p.replace(microsecond=0) if isinstance(p, datetime) else p) for p in params]
return operation, params
def show(operation, host=None):
def show(operation, host=None): #pylint: disable=unused-argument
return {}
elif config['DB_ENGINE'] == 'mysql':
import mysql.connector
def get_dbcursor():
if 'db' not in g or not g.db.is_connected():
g.db = mysql.connector.connect(
......@@ -77,8 +77,8 @@ elif config['DB_ENGINE'] == 'mysql':
rows = []
try:
rows = cur.fetchall()
except mysql.connector.errors.InterfaceError as ie:
if ie.msg == 'No result set to fetch from.':
except mysql.connector.errors.InterfaceError as e:
if e.msg == 'No result set to fetch from.':
# no problem, we were just at the end of the result set
pass
else:
......@@ -93,22 +93,23 @@ elif config['DB_ENGINE'] == 'mysql':
def query(operation, *params, delim="sep", nlfix=True):
operation, params = fix_query(operation, params)
tries = 0
while (tries < 10):
retry = True
while tries < 10 and retry:
retry = False
try:
cur = get_dbcursor()
cur.execute(operation, params)
except mysql.connector.errors.InternalError as e:
if e.msg == 'Deadlock found when trying to get lock; try restarting transaction':
tries += 1
continue
retry = True
else:
raise
break
rows = []
try:
rows = cur.fetchall()
except mysql.connector.errors.InterfaceError as ie:
if ie.msg == 'No result set to fetch from.':
except mysql.connector.errors.InterfaceError as e:
if e.msg == 'No result set to fetch from.':
# no problem, we were just at the end of the result set
pass
else:
......@@ -122,7 +123,7 @@ def query(operation, *params, delim="sep", nlfix=True):
if name == delim:
ptr = res[-1][col] = {}
continue
if type(col) == str and nlfix:
if isinstance(col, str) and nlfix:
col = col.replace('\\n', '\n').replace('\\r', '\r')
ptr[name] = col
return res
......@@ -134,13 +135,13 @@ def modify(operation, *params):
return cur.lastrowid
@app.teardown_request
def commit_db(*args):
def commit_db(*args): #pylint: disable=unused-argument
if hasattr(request, 'db'):
request.db.close()
g.db.commit()
@app.teardown_appcontext
def close_db(*args):
def close_db(*args): #pylint: disable=unused-argument
if 'db' in g:
g.db.close()
del g.db
......
......@@ -155,6 +155,7 @@ CREATE TABLE IF NOT EXISTS `log` (
CREATE TABLE IF NOT EXISTS `hlslog` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`time` datetime NOT NULL,
`segment` INTEGER,
`source` INTEGER,
`lecture` INTEGER,
`handle` varchar(32),
......
import math
from server import *
# field types:
......@@ -7,7 +9,8 @@ from server import *
# datetime
# duration
# videotime
editable_tables = {
editable_tables = { #pylint: disable=invalid-name
'courses': {
'table': 'courses_data',
'idcolumn': 'id',
......@@ -29,7 +32,7 @@ editable_tables = {
'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?'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated'] },
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'lectures': {
'table': 'lectures_data',
'idcolumn': 'id',
......@@ -48,14 +51,14 @@ editable_tables = {
'norecording': {'type': 'boolean', 'description:': 'Führt dazu, dass der Termin ausgegraut wird.'},
'stream_settings': {'type': 'text'}
},
'creationtime_fields': ['course_id', 'time_created', 'time_updated'] },
'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'] },
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'chapters': {
'table': 'chapters',
'idcolumn': 'id',
......@@ -64,7 +67,7 @@ editable_tables = {
'text': {'type': 'shortstring'},
'visible': {'type': 'boolean'},
'deleted': {'type': 'boolean'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated'] },
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'announcements': {
'table': 'announcements',
'idcolumn': 'id',
......@@ -75,7 +78,7 @@ editable_tables = {
'deleted': {'type': 'boolean'},
'time_publish': {'type': 'datetime'},
'time_expire': {'type': 'datetime'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated'] },
'creationtime_fields': ['created_by', 'time_created', 'time_updated']},
'featured': {
'table': 'featured',
'idcolumn': 'id',
......@@ -87,8 +90,8 @@ editable_tables = {
'deleted': {'type': 'boolean'},
'param': {'type': 'shortstring'},
'param2': {'type': 'shortstring'},
'order': {'type': 'integer' }},
'creationtime_fields': ['created_by', 'time_created', 'time_updated', 'type'] },
'order': {'type': 'integer'}},
'creationtime_fields': ['created_by', 'time_created', 'time_updated', 'type']},
'perm': {
'table': 'perm',
'idcolumn': 'id',
......@@ -97,13 +100,13 @@ editable_tables = {
'param1': {'type': 'shortstring'},
'param2': {'type': 'shortstring'},
'deleted': {'type': 'boolean'}},
'creationtime_fields': ['course_id', 'lecture_id', 'video_id', 'created_by', 'time_created', 'time_updated'] },
'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'] },
'creationtime_fields': ['time_created', 'time_updated']},
'users': {
'table': 'users',
'idcolumn': 'id',
......@@ -113,7 +116,7 @@ editable_tables = {
'notify_new_video': {'type': 'boolean'},
'notify_edit': {'type': 'boolean'}
},
'creationtime_fields': [] },
'creationtime_fields': []},
'live_sources': {
'table': 'live_sources',
'idcolumn': 'id',
......@@ -122,7 +125,7 @@ editable_tables = {
'description': {'type': 'text'},
'deleted': {'type': 'boolean'}
},
'creationtime_fields': ['created_by', 'time_created', 'time_updated'] }
'creationtime_fields': ['created_by', 'time_created', 'time_updated']}
}
#parses the path to a dict, containing the table, id, field and field type
......@@ -135,25 +138,31 @@ def parseeditpath(path):
return {'table': table, 'id': id, 'column': column, 'type': type, 'tableinfo': editable_tables[table]}
@app.template_filter(name='getfielddescription')
def getfielddescription(path):
p = parseeditpath(path)
desc = p['tableinfo']['editable_fields'][p['column']].get('description', '')
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(path):
p = parseeditpath(path)
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', p['table'], p['id'], p['column'])
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=[]):
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')
......@@ -168,10 +177,24 @@ def edit(prefix='', 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 = ?),?,?,1)'%(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'])
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)'%(
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'], []):
......@@ -196,7 +219,7 @@ def create(table):
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'):
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
......@@ -214,14 +237,8 @@ def create(table):
@register_navbar('Changelog', icon='book', group='weitere')
@mod_required
def changelog():
if 'page' in request.args:
page = max(0, int(request.args['page']))
else:
page = 0
if 'pagesize' in request.args:
pagesize = min(500, int(request.args['pagesize']))
else:
pagesize = 50
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:
......@@ -234,12 +251,12 @@ def changelog():
@csrf_protect
def set_responsible(course_id, user_id, value):
if value:
modify('REPLACE INTO responsible (course_id, user_id) values (?, ?)', course_id, user_id);
modify('REPLACE 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);
modify('DELETE FROM responsible WHERE course_id = ? AND user_id = ?', course_id, user_id)
return "OK", 200
edit_handlers = {}
edit_handlers = {} #pylint: disable=invalid-name
def edit_handler(*tables, field=None):
def wrapper(func):
for table in tables:
......
from server import *
from sorter import insert_video
import os.path
import json
from server import *
from sorter import insert_video
from edit import edit_handler
def set_metadata(dest, course, lecture):
chapters = query('SELECT text, time FROM chapters WHERE lecture_id = ? AND visible ORDER BY time', lecture['id'])
metadata = {'title': lecture['title'], 'album': course['title'],
......@@ -12,12 +14,13 @@ def set_metadata(dest, course, lecture):
dest['metadata'] = metadata
dest['chapters'] = chapters
def schedule_intro(lectureid):
lecture = query('SELECT * FROM lectures where id = ?', lectureid)
course = query('SELECT * FROM course where id = ?', lecture['course_id'])
data = {'path': path, 'lecture_id': lectureid}
set_metadata(data, course, lecture)
schedule_job('intro', data)
# Incomplete and not enabled currently
#def schedule_intro(lectureid):
# lecture = query('SELECT * FROM lectures where id = ?', lectureid)
# course = query('SELECT * FROM course where id = ?', lecture['course_id'])
# data = {'path': path, 'lecture_id': lectureid}
# set_metadata(data, course, lecture)
# schedule_job('intro', data)
def schedule_remux(lectureid, videoid=None):
lecture = query('SELECT * FROM lectures WHERE id = ?', lectureid)[0]
......@@ -55,8 +58,8 @@ def add_remux_job():
def schedule_transcode(source, fmt_id=None, video=None):
if video:
fmt_id = video['video_format']
assert(video['lecture_id'] == source['lecture_id'])
assert(fmt_id != None)
assert video['lecture_id'] == source['lecture_id']
assert fmt_id is not None
fmt = query('SELECT * FROM formats WHERE id = ?', fmt_id)[0]
lecture = query('SELECT * FROM lectures WHERE id = ?', source['lecture_id'])[0]
course = query('SELECT * FROM courses WHERE id = ?', lecture['course_id'])[0]
......@@ -67,7 +70,7 @@ def schedule_transcode(source, fmt_id=None, video=None):
stream = {'name': 'audio', 'type': 'audio'}
data['input']['streams'].append(stream)
else:
assert(False)
assert False
set_metadata(data['output'], course, lecture)
basename = os.path.basename(source['path']).rsplit('.', 1)[0]
data['output']['path'] = 'pub/'+course['handle']+'/'+basename+fmt['suffix']
......@@ -84,12 +87,19 @@ def schedule_transcode(source, fmt_id=None, video=None):
return schedule_job('transcode', data, queue="background")
@job_handler('transcode')
def insert_transcoded_video(jobid, jobtype, data, state, status):
def insert_transcoded_video(jobid, jobtype, data, state, status): #pylint: disable=unused-argument
if 'lecture_id' not in data or 'source_id' not in data or 'format_id' not in data:
return
if 'video_id' in data:
return
video_id = insert_video(data['lecture_id'], data['output']['path'], data['format_id'], status['hash'], status['filesize'], status['duration'], data['source_id'])
video_id = insert_video(
data['lecture_id'],
data['output']['path'],
data['format_id'],
status['hash'],
status['filesize'],
status['duration'],
data['source_id'])
schedule_remux(data['lecture_id'], video_id)
@app.route('/internal/jobs/add/reencode', methods=['GET', 'POST'])
......@@ -106,7 +116,7 @@ def add_reencode_job():
return redirect(request.values.get('ref', url_for('jobs_overview')))
@job_handler('probe-raw', 'intro')
def update_lecture_videos(jobid, jobtype, data, state, status):
def update_lecture_videos(jobid, jobtype, data, state, status): #pylint: disable=unused-argument
if 'lecture_id' not in data:
return
if jobtype == 'probe-raw':
......@@ -117,26 +127,25 @@ def update_lecture_videos(jobid, jobtype, data, state, status):
if not sources:
return
latest = sources[-1]
lecture = query('SELECT * FROM lectures where id = ?', data['lecture_id'])
if False and jobtype == 'probe-raw':
schedule_intro(data['lecture_id'])
else:
videos = query('SELECT * FROM videos WHERE videos.lecture_id = ?', data['lecture_id'])
current_fmts = [v['video_format'] for v in videos]
formats = query('''SELECT formats.* FROM formats
JOIN profiles ON formats.id = profiles.format
JOIN courses ON profiles.name = courses.profile
JOIN lectures ON courses.id = lectures.course_id
WHERE lectures.id = ?''', data['lecture_id'])
for fmt in formats:
if fmt['id'] not in current_fmts:
schedule_transcode(latest, fmt_id=fmt['id'])
for video in videos:
if video['source'] != latest['id']:
schedule_transcode(latest, video=video)
# Incomplete and not enabled currently
#if False and jobtype == 'probe-raw':
# schedule_intro(data['lecture_id'])
videos = query('SELECT * FROM videos WHERE videos.lecture_id = ?', data['lecture_id'])
current_fmts = [v['video_format'] for v in videos]
formats = query('''SELECT formats.* FROM formats
JOIN profiles ON formats.id = profiles.format
JOIN courses ON profiles.name = courses.profile
JOIN lectures ON courses.id = lectures.course_id
WHERE lectures.id = ?''', data['lecture_id'])
for fmt in formats:
if fmt['id'] not in current_fmts:
schedule_transcode(latest, fmt_id=fmt['id'])
for video in videos:
if video['source'] != latest['id']:
schedule_transcode(latest, video=video)
@edit_handler('chapters')
def chapter_changed(table, column, value, id, user):
def chapter_changed(table, column, value, id, user): #pylint: disable=unused-argument
chapters = query('SELECT * FROM chapters WHERE id = ?', id)
if not chapters:
return
......@@ -145,7 +154,7 @@ def chapter_changed(table, column, value, id, user):
schedule_remux(chapter['lecture_id'])
@edit_handler('courses')
def course_changed(table, column, value, id, user):
def course_changed(table, column, value, id, user): #pylint: disable=unused-argument
if column not in ['title', 'organizer']:
return
lectures = query('SELECT * FROM lectures WHERE course_id = ?', id)
......@@ -153,7 +162,6 @@ def course_changed(table, column, value, id, user):
schedule_remux(lecture['id'])
@edit_handler('lectures')
def lecture_changed(table, column, value, id, user):
def lecture_changed(table, column, value, id, user): #pylint: disable=unused-argument
if column in ['title', 'comment', 'time', 'speaker']:
schedule_remux(id)
import hashlib
from datetime import MINYEAR
from server import *
def gen_atomid(s):
return 'urn:md5:'+hashlib.md5(s.encode('utf-8')).hexdigest().upper()
def gen_atomid(value):
return 'urn:md5:'+hashlib.md5(value.encode('utf-8')).hexdigest().upper()
def fixdate(d):
if not isinstance(d, datetime):
def fixdate(value):
if not isinstance(value, datetime):
return datetime(MINYEAR, 1, 1)
return d
return value
@app.route('/feed')
@app.route('/<handle>/feed')
@handle_errors(None, 'Diese Veranstaltung existiert nicht!', 404, IndexError)
def feed(handle=None):
id = None
course = {'id': None, 'title': 'Neueste Videos', 'time_created': None, 'time_updated': None}
course['atomid'] = gen_atomid('FROM videos SELECT *')
if handle:
course = query('SELECT * FROM courses WHERE handle = ? AND visible', handle)[0]
course['atomid'] = gen_atomid('Video AG, courses['+str(course['id'])+']: '+course['handle'])
id = course['id']
entries = query('''
SELECT lectures.*, "video" AS sep, videos.*, formats.description AS format_description, formats.prio, "course" AS sep, courses.*
FROM lectures
......@@ -62,10 +63,16 @@ def rss_feed(handle):
WHERE courses.id = ? AND videos.video_format = ? AND courses.visible AND lectures.visible AND videos.visible
ORDER BY lectures.time DESC
LIMIT 100''', course['id'], fmt['id'])
chapters = query('SELECT chapters.* FROM chapters JOIN lectures ON lectures.id = chapters.lecture_id WHERE lectures.course_id = ? AND NOT chapters.deleted AND chapters.visible ORDER BY time ASC', course['id'])
chapters = query('SELECT chapters.* FROM chapters \
JOIN lectures ON lectures.id = chapters.lecture_id \
WHERE lectures.course_id = ? AND NOT chapters.deleted AND chapters.visible \
ORDER BY time ASC', course['id'])
for item in items:
item['updated'] = max(item['video']['time_created'], item['video']['time_updated'], item['time_created'], item['time_updated'], key=fixdate)