diff --git a/db.py b/db.py index c0c13e05e32561017d5bdb96b284edfc32d9b604..acc78ed5747c08debbb10cf8c389e29f6b4f086f 100644 --- a/db.py +++ b/db.py @@ -1,74 +1,86 @@ from server import * -import sqlite3 import re if config['DB_ENGINE'] == 'sqlite': - created = not os.path.exists(config['SQLITE_DB']) - db = sqlite3.connect(config['SQLITE_DB']) - cur = db.cursor() - if config['SQLITE_INIT_SCHEMA']: - cur.executescript(open(config['DB_SCHEMA']).read()) - if config['SQLITE_INIT_DATA'] and created: - cur.executescript(open(config['DB_DATA']).read()) - db.commit() - db.close() - -# Row wrapper for sqlite -def dict_factory(cursor, row): - d = {} - for idx, col in enumerate(cursor.description): - if type(row[idx]) == str: - d[col[0].split('.')[-1]] = row[idx].replace('\\n','\n').replace('\\r','\r') - else: - d[col[0].split('.')[-1]] = row[idx] - return d - -# From sqlite3 module, but with error catching -def 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":")) - val = datetime(year, month, day, hours, minutes, seconds, 0) - except ValueError: - val = None - return val - -sqlite3.register_converter('datetime', convert_timestamp) -sqlite3.register_converter('timestamp', convert_timestamp) - -def query(operation, *params): - _params = [] - for p in params: - if isinstance(p, datetime): - p = p.replace(microsecond=0) - _params.append(p) - params = _params - if config['DB_ENGINE'] == 'mysql': - import mysql.connector - if 'db' not in g or not g.db.is_connected(): - g.db = mysql.connector.connect(user=config['MYSQL_USER'], password=config['MYSQL_PASSWD'], host=config['MYSQL_HOST'], database=config['MYSQL_DB']) - if not hasattr(request, 'db'): - request.db = g.db.cursor(dictionary=True) - request.db.execute(operation.replace('?', '%s'), params) - elif config['DB_ENGINE'] == 'sqlite': + import sqlite3 + + # From sqlite3 module, but with error catching + def 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":")) + val = datetime(year, month, day, hours, minutes, seconds, 0) + except ValueError: + val = None + return val + + sqlite3.register_converter('datetime', convert_timestamp) + sqlite3.register_converter('timestamp', convert_timestamp) + + if config['DB_ENGINE'] == 'sqlite': + created = not os.path.exists(config['SQLITE_DB']) + db = sqlite3.connect(config['SQLITE_DB']) + cur = db.cursor() + if config['SQLITE_INIT_SCHEMA']: + cur.executescript(open(config['DB_SCHEMA']).read()) + if config['SQLITE_INIT_DATA'] and created: + cur.executescript(open(config['db_data']).read()) + db.commit() + db.close() + + def get_dbcursor(): if 'db' not in g: 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'): request.db = g.db.cursor() - request.db.execute(operation, params) - else: - return [] - try: - rows = request.db.fetchall() - except: - rows = [] - if not rows and request.db.lastrowid != None: - return request.db.lastrowid - return rows + return request.db + + def fix_query(operation, params): + params = [(p.replace(microsecond=0) if isinstance(p, datetime) else p) for p in params] + return operation, params + +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(user=config['MYSQL_USER'], password=config['MYSQL_PASSWD'], host=config['MYSQL_HOST'], database=config['MYSQL_DB']) + if not hasattr(request, 'db'): + request.db = g.db.cursor() + return request.db + + def fix_query(operation, params): + operation = operation.replace('?', '%s') + params = [(p.replace(microsecond=0) if isinstance(p, datetime) else p) for p in params] + return operation, params + +def query(operation, *params, delim="sep"): + operation, params = fix_query(operation, params) + cur = get_dbcursor() + cur.execute(operation, params) + rows = cur.fetchall() + res = [] + for row in rows: + res.append({}) + ptr = res[-1] + for col, desc in zip(row, cur.description): + name = desc[0].split('.')[-1] + if name == delim: + ptr = res[-1][col] = {} + continue + if type(col) == str: + col = col.replace('\\n', '\n').replace('\\r', '\r') + ptr[name] = col + return res + +def modify(operation, *params): + operation, params = fix_query(operation, params) + cur = get_dbcursor() + cur.execute(operation, params) + return cur.lastrowid @app.teardown_request def commit_db(*args): @@ -94,15 +106,11 @@ def searchquery(text, columns, match, tables, suffix, *suffixparams): return query(expr, *params, *suffixparams) LDAP_USERRE = re.compile(r'[^a-z0-9]') -notldap = { - 'videoag':('videoag', ['users','videoag'], {'uid': 'videoag', 'givenName': 'Video', 'sn': 'Geier'}), - 'gustav':('passwort', ['users'], {'uid': 'gustav', 'givenName': 'Gustav', 'sn': 'Geier'}) -} - -def ldapauth(user, password): - user = LDAP_USERRE.sub(r'', user.lower()) - if 'LDAP_HOST' in config: - import ldap3 +if 'LDAP_HOST' in config: + import ldap3 + + def ldapauth(user, password): + user = LDAP_USERRE.sub(r'', user.lower()) try: conn = ldap3.Connection(config['LDAP_HOST'], 'uid=%s,ou=users,dc=fsmpi,dc=rwth-aachen,dc=de'%user, password, auto_bind=True) if conn.search("ou=groups,dc=fsmpi,dc=rwth-aachen,dc=de", "(&(cn=*)(memberUid=%s))"%user, attributes=['cn']): @@ -111,14 +119,9 @@ def ldapauth(user, password): return user, groups except ldap3.core.exceptions.LDAPBindError: pass - elif config.get('DEBUG') and user in notldap and password == notldap[user][0]: - return user, notldap[user][1] - return None, [] - -def ldapget(user): - user = LDAP_USERRE.sub(r'', user.lower()) - if 'LDAP_HOST' in config: - import ldap3 + + def ldapget(user): + user = LDAP_USERRE.sub(r'', user.lower()) 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) @@ -126,6 +129,19 @@ def ldapget(user): return {} e = conn.entries[0] return {'uid': user, 'givenName': e.givenName.value, 'sn':e.sn.value} - else: - return notldap[user][2] +else: + notldap = { + 'videoag':('videoag', ['users','videoag'], {'uid': 'videoag', 'givenName': 'Video', 'sn': 'Geier'}), + 'gustav':('passwort', ['users'], {'uid': 'gustav', 'givenName': 'Gustav', 'sn': 'Geier'}) + } + + def ldapauth(user, password): + user = LDAP_USERRE.sub(r'', user.lower()) + if config.get('DEBUG') and user in notldap and password == notldap[user][0]: + return user, notldap[user][1] + return None, [] + + def ldapget(user): + user = LDAP_USERRE.sub(r'', user.lower()) + return notldap[user][2] diff --git a/feeds.py b/feeds.py index 52aec6259b3e9a858f4786962ffd6785381d2730..c2520549db5c5ca9a4471d79129ca3da9e8a89a9 100644 --- a/feeds.py +++ b/feeds.py @@ -20,25 +20,22 @@ def feed(handle=None): course['atomid'] = gen_atomid('Video AG, courses['+str(course['id'])+']: '+course['handle']) id = course['id'] entries = query(''' - SELECT lectures.*, videos.file_size, videos.path, videos.id AS video_id, videos.hash, - videos.time_created AS video_created, videos.time_updated AS video_updated, - courses.title AS course_title, courses.handle AS course_handle, courses.semester, courses.organizer AS course_organizer, courses.short as course_short, - formats.description AS format_description, formats.prio + SELECT lectures.*, "video" AS sep, videos.*, formats.description AS format_description, formats.prio, "course" AS sep, courses.* FROM lectures JOIN courses ON (courses.id = lectures.course_id) JOIN videos ON (lectures.id = videos.lecture_id) JOIN formats ON (formats.id = videos.video_format) WHERE ((? IS NULL AND courses.listed) OR course_id = ?) AND courses.visible AND lectures.visible AND videos.visible - ORDER BY video_created DESC, prio ASC + ORDER BY videos.time_created DESC, prio ASC LIMIT 100''', course['id'], course['id']) updated = max(course['time_updated'], course['time_created'], key=fixdate) for entry in entries: - entry['updated'] = max(entry['video_created'], entry['video_updated'], entry['time_created'], entry['time_updated'], key=fixdate) - if len(entry['hash']) != 32: + entry['updated'] = max(entry['video']['time_created'], entry['video']['time_updated'], entry['time_created'], entry['time_updated'], key=fixdate) + if len(entry['video']['hash']) != 32: entry['atomid'] = gen_atomid('Video AG, videos['+str(entry['video_id'])+']') else: - entry['atomid'] = 'urn:md5:'+(entry['hash'].upper()) + entry['atomid'] = 'urn:md5:'+(entry['video']['hash'].upper()) updated = max(updated, entry['updated'], key=fixdate) course['updated'] = updated return Response(render_template('feed.atom', course=course, entries=entries), 200, {'Content-Type': 'application/atom+xml'}) diff --git a/importer.py b/importer.py index f98381fde70aceb70f33a25956cfb79ce3231158..b47f0f94766a327bf84981b5873129e4b065fc73 100755 --- a/importer.py +++ b/importer.py @@ -17,7 +17,7 @@ def import_from(source=None, id=None): for i in campus: if i.startswith('new'): if campus[i]['url'] != '': - query('INSERT INTO import_campus (url, type, course_id, last_checked, changed) VALUES (?, ?, ?, ?, 1)',campus[i]['url'],campus[i]['type'],id,datetime.now()) + modify('INSERT INTO import_campus (url, type, course_id, last_checked, changed) VALUES (?, ?, ?, ?, 1)',campus[i]['url'],campus[i]['type'],id,datetime.now()) else: if campus[i]['url'] != '': query('UPDATE import_campus SET url = ?, `type` = ? WHERE (course_id = ?) AND (id = ?)', campus[i]['url'],campus[i]['type'],id,int(i)) diff --git a/server.py b/server.py index de9a9083ace8feb7c71d680727dce1c9dcdf6e58..33f4749fe6ff2907135d281ac4f5836d82e807a9 100755 --- a/server.py +++ b/server.py @@ -34,7 +34,7 @@ if config['DEBUG']: if not config.get('SECRET_KEY', None): config['SECRET_KEY'] = os.urandom(24) -from db import query, searchquery, ldapauth, ldapget, convert_timestamp +from db import query, modify, searchquery, ldapauth, ldapget mod_endpoints = [] @@ -132,8 +132,8 @@ def fixnl(s): def index(): start = date.today() - timedelta(days=1) end = start + timedelta(days=7) - upcomming = query (''' - SELECT lectures.*,courses.short, courses.title AS course_title + upcomming = query(''' + SELECT lectures.*, "course" AS sep, courses.* FROM lectures JOIN courses ON (lectures.course_id = courses.id) WHERE (time > ?) AND (time < ?) and lectures.visible and courses.visible and courses.listed @@ -141,13 +141,13 @@ def index(): for i in upcomming: i['date'] = i['time'].date() latestvideos=query(''' - SELECT lectures.*, max(videos.time_updated) AS lastvidtime, courses.short, courses.downloadable, courses.title AS coursetitle + SELECT lectures.*, "course" AS sep, courses.* FROM lectures LEFT JOIN videos ON (videos.lecture_id = lectures.id) LEFT JOIN courses on (courses.id = lectures.course_id) WHERE (? OR (courses.visible AND courses.listed AND lectures.visible AND videos.visible)) GROUP BY videos.lecture_id - ORDER BY lastvidtime DESC + ORDER BY MAX(videos.time_updated) DESC LIMIT 6 ''',ismod()) featured = query('SELECT * FROM featured WHERE NOT deleted AND (? OR visible)', ismod()) return render_template('index.html', latestvideos=latestvideos, upcomming=upcomming, featured=featured) @@ -238,7 +238,7 @@ def login(): 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) + modify('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'))) @@ -262,7 +262,7 @@ def edit(prefix="", ignore=[]): 'chapters': ('chapters', 'id', ['time', 'text', 'visible', 'deleted']), 'announcements': ('announcements', 'id', ['text', 'internal', 'level', 'visible', 'deleted']) } - query('BEGIN') + modify('BEGIN') if request.is_json: changes = request.get_json().items() else: @@ -270,21 +270,19 @@ def edit(prefix="", ignore=[]): for key, val in changes: if key in ignore: continue - print('edit:', key, val) key = prefix+key - print (key,val) table, id, column = key.split('.', 2) 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 = ?, time_updated = ? WHERE %s = ?'%(tabs[table][0], column, tabs[table][1]), val, datetime.now(), id) - query('COMMIT') + 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']['givenName']) + modify('UPDATE %s SET %s = ?, time_updated = ? WHERE %s = ?'%(tabs[table][0], column, tabs[table][1]), val, datetime.now(), id) + modify('COMMIT') return "OK", 200 @app.route('/newcourse', methods=['GET', 'POST']) @mod_required def new_course(): - id = query(''' + id = modify(''' INSERT INTO courses_data (visible, title, short, handle, organizer, subject, created_by, time_created, time_updated, semester, settings, description, internal, responsible, feed_url) @@ -299,7 +297,7 @@ def new_course(): @app.route('/newlecture/<courseid>', methods=['GET', 'POST']) @mod_required def new_lecture(courseid): - id = query(''' + id = modify(''' INSERT INTO lectures_data (course_id, visible, drehplan, title, comment, internal, speaker, place, time, time_created, time_updated, jumplist, titlefile) @@ -347,7 +345,7 @@ def auth(): # For use with nginx auth_request if not types[0] or allowed or ismod() or \ (auth and check_mod(*ldapauth(auth.username, auth.password))): return 'OK', 200 - query('INSERT INTO log VALUES (?, "", ?, "video", ?, ?)', ip, datetime.now(), videos[0]['id'], url) + modify('INSERT INTO log VALUES (?, "", ?, "video", ?, ?)', ip, datetime.now(), videos[0]['id'], url) elif 'password' in types: return Response("Login required", 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}) return "Not allowed", 403 @@ -378,7 +376,7 @@ def suggest_chapter(lectureid): submitter = None if not ismod(): submitter = request.environ['REMOTE_ADDR'] - id = query('INSERT INTO chapters (lecture_id, time, text, time_created, time_updated, created_by, submitted_by) VALUES (?, ?, ?, ?, ?, ?, ?)', + id = modify('INSERT INTO chapters (lecture_id, time, text, time_created, time_updated, created_by, submitted_by) VALUES (?, ?, ?, ?, ?, ?, ?)', lectureid, time, text, datetime.now(), datetime.now(), session.get('user', {'dbid':None})['dbid'], submitter) if 'ref' in request.values: return redirect(request.values['ref']) @@ -387,7 +385,7 @@ def suggest_chapter(lectureid): @app.route('/newpsa', methods=['POST', 'GET']) @mod_required def new_announcement(): - id = query('INSERT INTO announcements (text, internal, time_created, time_updated, created_by) VALUES ("Neue Ankündigung", "", ?, ?, ?)', + id = modify('INSERT INTO announcements (text, internal, time_created, time_updated, created_by) VALUES ("Neue Ankündigung", "", ?, ?, ?)', datetime.now(), datetime.now(), session.get('user', {'dbid':None})['dbid']) if 'ref' in request.values: return redirect(request.values['ref']) @@ -396,7 +394,7 @@ def new_announcement(): @app.route('/newfeatured', methods=['POST', 'GET']) @mod_required def new_featured(): - id = query('INSERT INTO featured (time_created, time_updated, created_by) VALUES (?, ?, ?)', + id = modify('INSERT INTO featured (time_created, time_updated, created_by) VALUES (?, ?, ?)', datetime.now(), datetime.now(), session.get('user', {'dbid':None})['dbid']) if 'ref' in request.values: return redirect(request.values['ref']) diff --git a/templates/feed.atom b/templates/feed.atom index 8bd9ce8f82f4b67743638d3d5d1c5689bddeed73..0144dd00dfd3b104c564b96db86fd8ba6779082d 100644 --- a/templates/feed.atom +++ b/templates/feed.atom @@ -1,8 +1,8 @@ {% macro summary(entry) %} {% if not course.handle %} -Veranstaltung: <a href="{{ url_for('course', handle=entry.course_handle) }}">{{entry.course_title}}</a><br> - {% if entry.course_organizer %} -Veranstalter: {{ entry.course_organizer }}<br> +Veranstaltung: <a href="{{ url_for('course', handle=entry.course.handle) }}">{{entry.course.title}}</a><br> + {% if entry.course.organizer %} +Veranstalter: {{ entry.course.organizer }}<br> {% endif %} {% endif %} {% if entry.speaker %} @@ -42,10 +42,10 @@ Veranstalter: {{ course.organizer }}<br> {% for entry in entries %} <entry> - <title>{% if not course.handle %}{{ entry.course_short }} {% if entry.semester %}({{ entry.semester|semester }}){% endif %}, {% endif %}{{ entry.time|date }}: {{ entry.title }}</title> - {% if not course.handle and entry.course_organizer %} + <title>{% if not course.handle %}{{ entry.course.short }} {% if entry.course.semester %}({{ entry.course.semester|semester }}){% endif %}, {% endif %}{{ entry.time|date }}: {{ entry.title }}</title> + {% if not course.handle and entry.course.organizer %} <author> - <name>{{ entry.course_organizer }}</name> + <name>{{ entry.course.organizer }}</name> </author> {% endif %} {% if entry.speaker %} @@ -54,7 +54,7 @@ Veranstalter: {{ course.organizer }}<br> </author> {% endif %} <link rel="alternate" href="{{ url_for('lecture', id=entry.id, _external=True) }}"/> - <link rel="enclosure" href="{{ url_for('files', filename=entry.path, _external=True)}}" length="{{ entry.file_size }}"/> + <link rel="enclosure" href="{{ url_for('files', filename=entry.video.path, _external=True)}}" length="{{ entry.video.file_size }}"/> <id>{{ entry.atomid }}</id> <updated>{{ entry.updated|rfc3339 }}</updated> <summary type="html">{{ summary(entry)|e }}</summary> diff --git a/templates/index.html b/templates/index.html index 6b76efba6c892fcf530aef8be7a4daeb0560544f..15609d80fd5845e9b31e0b9264624a4c5baabef6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -77,7 +77,7 @@ <strong>{{ g.grouper|date }}</strong> {% for i in g.list %} <li class="list-group-item list-group-item-condensed"> - {{i.time|time}} {{i.place}} <a href="{{url_for('course', id=i.course_id)}}">{{i.course_title}}</a>: <a href="{{url_for('course', id=i.course_id)}}#lecture-{{i.id}}">{{i.title}}</a> + {{i.time|time}} {{i.place}} <a href="{{url_for('course', id=i.course_id)}}">{{i.course.title}}</a>: <a href="{{url_for('course', id=i.course_id)}}#lecture-{{i.id}}">{{i.title}}</a> </li> {% endfor %} diff --git a/templates/macros.html b/templates/macros.html index 7706485449fe05b2b264f6ef137f703b64685beb..38aca6b73d868bc645e6fdf34842b5ed18dbc3e9 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -1,10 +1,10 @@ {% macro preview(lecture) %} <li class="list-group-item"> - <a class="hidden-xs" href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture['coursetitle'] }}" style="color: #000"> + <a class="hidden-xs" href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture.course.title }}" style="color: #000"> <div class="row"> <img class="col-xs-4" style="max-height: 100px; width: auto;" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild" onerror="this.src='{{url_for('static',filename='no-thumbnail.png')}}'; this.onerror=''; "> <div class="col-xs-4"> - <span><strong>{{ lecture['short'] }}</strong></span><br> + <span><strong>{{ lecture.course.short }}</strong></span><br> <span>{{ lecture['time'] }}</span> {% if lecture['speaker'] %} <div class="small">Gehalten von {{ lecture['speaker'] }} </div> @@ -16,13 +16,13 @@ </div> </div> </a> - <a class="visible-xs" href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture['coursetitle'] }}" style="color: #000"> + <a class="visible-xs" href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture.course.title }}" style="color: #000"> <ul class="list-unstyled"> <li> <img style="width: 100%;" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild" onerror="this.src='{{url_for('static',filename='no-thumbnail.png')}}'; this.onerror=''; "> </li> <li> - <strong>{{ lecture['short'] }}</strong> {{ lecture['time'] }} + <strong>{{ lecture.course.short }}</strong> {{ lecture['time'] }} </li> {% if lecture['speaker'] %} <li>