diff --git a/db_example.sql b/db_example.sql index 3c6754b52c22cf065ff3648f41b334a77557b49a..659dd748e9d609e8d28ce30a0debbf1002eeed1e 100644 --- a/db_example.sql +++ b/db_example.sql @@ -14642,10 +14642,12 @@ INSERT INTO "videos_data" VALUES(9680,7011,1,0,1,'','','','pub/16ss-dsal/16ss-ds INSERT INTO "videos_data" VALUES(9681,7012,1,0,1,'','','','pub/16ss-dsal/16ss-dsal-160715-1080p_1.mp4','2016-08-07 22:54:46','2016-08-07 21:02:37','2016-08-07 21:02:43',46,1402602183,4,'e036f7cbd51afd3ab7be10cf77747c00'); INSERT INTO "videos_data" VALUES(9682,7012,1,0,1,'','','','pub/16ss-dsal/16ss-dsal-160715-360p_1.mp4','2016-08-07 22:45:34','2016-08-07 21:02:38','2016-08-07 21:02:45',46,368611109,10,'fae2bda2da55a3005aa6329a2d0227c3'); INSERT INTO "videos_data" VALUES(9683,7012,1,0,1,'','','','pub/16ss-dsal/16ss-dsal-160715-720p_1.mp4','2016-08-07 22:46:00','2016-08-07 21:02:40','2016-08-07 21:02:44',46,721141077,5,'083c0b7693c82078c513707d1402096b'); +INSERT INTO "featured" VALUES(1,'Video AG','<p>Wir machen Vorlesungsvideos, damit du dir deine Vorlesungen angucken kannst, wann, wo und so oft <strong>du</strong> willst ;)</p><p><strong>Probleme?</strong><a href="/faq"> Hier gehts zur FAQ</a></p><p>Wenn du die Videos nützlich fandest, schreib doch bitte den Dozenten eine kurze E-Mail. Waren die Videos grauenhaft? Kritik an uns.</p><p>Wenn du mitmachen willst, Fragen oder Anregungen hast, oder nur mal schauen möchtest, komm zu unserem AG-Treffen oder schreib uns eine E-Mail. Insbesondere freuen wir uns über Studis der Mathematik und Physik, die ihre Vorlesungen filmen wollen.</p>','','plain','','',0,1,0,'2017-04-09 19:00:00','2017-04-09 19:00:00',0); INSERT INTO "sqlite_sequence" VALUES('changelog',17859); INSERT INTO "sqlite_sequence" VALUES('courses_data',303); INSERT INTO "sqlite_sequence" VALUES('formats',30); INSERT INTO "sqlite_sequence" VALUES('lectures_data',7362); INSERT INTO "sqlite_sequence" VALUES('users',77); INSERT INTO "sqlite_sequence" VALUES('videos_data',9683); +INSERT INTO "sqlite_sequence" VALUES('featured',1); COMMIT; diff --git a/db_schema.sql b/db_schema.sql index 2f2a6eac0f64fe289ae8563e815166492afbb19b..2dc1f8c376208f0e0a152ab0f7d41be9f29699eb 100644 --- a/db_schema.sql +++ b/db_schema.sql @@ -126,7 +126,6 @@ CREATE TABLE IF NOT EXISTS `site_texts` ( `modified_by` text NOT NULL ); CREATE TABLE IF NOT EXISTS `log` ( - CREATE TABLE IF NOT EXISTS `streamlog` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `time` datetime NOT NULL, `date` datetime NOT NULL, @@ -221,6 +220,10 @@ CREATE TABLE IF NOT EXISTS `featured` ( `title` text NOT NULL DEFAULT '', `text` text NOT NULL DEFAULT '', `internal` text NOT NULL DEFAULT '', + `type` varchar(32) NOT NULL DEFAULT '', + `param` text NOT NULL DEFAULT '', + `param2` text NOT NULL DEFAULT '', + `order` INTEGER DEFAULT NULL, `visible` INTEGER NOT NULL DEFAULT 0, `deleted` INTEGER NOT NULL DEFAULT 0, `time_created` datetime NOT NULL, @@ -269,4 +272,6 @@ CREATE VIEW IF NOT EXISTS `courses` AS select * from `courses_data` where (not(` CREATE VIEW IF NOT EXISTS `lectures` AS select `lectures_data`.* from `lectures_data` join `courses_data` on (`courses_data`.`id` = `course_id`) where (not(`lectures_data`.`deleted` or `courses_data`.`deleted`)); CREATE VIEW IF NOT EXISTS `videos` AS select `videos_data`.* from `videos_data` join `lectures_data` on (`lectures_data`.`id` = `lecture_id`) join `courses_data` on (`courses_data`.`id` = `course_id`) where (not(`videos_data`.`deleted` or `lectures_data`.`deleted` or `courses_data`.`deleted`)); CREATE VIEW IF NOT EXISTS `sorterrorlog` AS select * from `sorterrorlog_data` where (not(`sorterrorlog_data`.`deleted`)); +CREATE TRIGGER IF NOT EXISTS featured_unique_order AFTER INSERT ON featured FOR EACH ROW BEGIN UPDATE featured SET `order` = (SELECT MAX(`order`) FROM featured)+1 WHERE id = NEW.id; END; +CREATE TRIGGER IF NOT EXISTS courses_unique_handle BEFORE UPDATE OF handle ON courses_data FOR EACH ROW WHEN NEW.handle IN (SELECT handle FROM courses WHERE NOT deleted) BEGIN SELECT RAISE(ROLLBACK, "Handle bereits in Verwendung"); END; COMMIT; diff --git a/feeds.py b/feeds.py index 7b7767d3cefc9f7bca631825a5b3b78662b58ffd..702c57553986b66cb46bcd64a5f8e07ea9cbbde5 100644 --- a/feeds.py +++ b/feeds.py @@ -9,7 +9,7 @@ def fixdate(d): return d @app.route('/feed') -@app.route('/course/<handle>/feed') +@app.route('/<handle>/feed') @handle_errors(None, 'Diese Veranstaltung existiert nicht!', 400, IndexError) def feed(handle=None): id = None @@ -40,7 +40,7 @@ def feed(handle=None): course['updated'] = updated return Response(render_template('feed.atom', course=course, entries=entries), 200, {'Content-Type': 'application/atom+xml'}) -@app.route('/course/feed') +@app.route('/courses/feed') def courses_feed(): courses = query('SELECT * FROM courses WHERE visible AND listed ORDER BY time_created DESC LIMIT 100') atomid = gen_atomid('Video AG, courses') diff --git a/importer.py b/importer.py index ded1e9689dec21c06a3ee3b1520bc12643d2f67a..b3d3bd3ecb5004351055669ab3733e44adb3e2f0 100644 --- a/importer.py +++ b/importer.py @@ -1,6 +1,6 @@ from server import * -@app.route('/import/<int:id>', methods=['GET', 'POST']) +@app.route('/internal/import/<int:id>', methods=['GET', 'POST']) @mod_required def list_import_sources(id): courses = query('SELECT * FROM courses WHERE id = ?', id)[0] @@ -25,7 +25,7 @@ def list_import_sources(id): return render_template('import_campus.html', course=courses, import_campus=import_campus, events=[]) -@app.route('/import/<int:id>/now', methods=['GET', 'POST']) +@app.route('/internal/import/<int:id>/now', methods=['GET', 'POST']) @mod_required def import_from(id): diff --git a/jobs.py b/jobs.py index 41d0c4651e8918c6847891ceb0a104ce56f603d9..38beea101d3bbcdbf15590327560161d51d1e2f5 100644 --- a/jobs.py +++ b/jobs.py @@ -3,7 +3,7 @@ import traceback import json import random -@app.route('/jobs/overview') +@app.route('/internal/jobs/overview') @register_navbar('Jobs', iconlib='fa', icon='suitcase') @mod_required def jobs_overview(): @@ -47,13 +47,13 @@ def jobs_catch_broken(): except: pass -@app.route('/jobs/api/worker/<hostname>/ping', methods=['GET', 'POST']) +@app.route('/internal/jobs/api/worker/<hostname>/ping', methods=['GET', 'POST']) @jobs_api_token_required def jobs_worker_ping(hostname): query('INSERT OR REPLACE INTO worker (hostname, last_ping) values (?, ?)', hostname, datetime.now()) return 'OK',200 -@app.route('/jobs/api/job/<int:id>/ping', methods=['GET', 'POST']) +@app.route('/internal/jobs/api/job/<int:id>/ping', methods=['GET', 'POST']) @jobs_api_token_required def jobs_ping(id): hostname = request.values['host'] @@ -65,7 +65,7 @@ def jobs_ping(id): query('UPDATE jobs SET worker = ?, last_ping = ?, status = ?, state = ? where id = ?', hostname, datetime.now(), status, state, id) return 'OK',200 -@app.route('/jobs/api/worker/<hostname>/schedule', methods=['POST']) +@app.route('/internal/jobs/api/worker/<hostname>/schedule', methods=['POST']) @jobs_api_token_required def jobs_schedule(hostname): hostdata = request.get_json() diff --git a/l2pauth.py b/l2pauth.py index d658ee431b2273258571c0c4a4ec5e0c9e7782e8..3c971e74733e175fbb170811af10685c954b85d0 100644 --- a/l2pauth.py +++ b/l2pauth.py @@ -14,7 +14,7 @@ def oauthget(endpoint, **args): r = requests.request('POST', OAUTH_BASE+endpoint, data=args) return r.json() -@app.route('/l2pauth') +@app.route('/internal/l2pauth') def start_l2pauth(): if 'L2P_APIKEY' not in config: return render_template("500.html"), 500 @@ -23,7 +23,7 @@ def start_l2pauth(): session['oauthscope'] = 'l2p' return redirect(code['verification_url']+'?q=verify&d='+code['user_code']) -@app.route('/rwthauth') +@app.route('/internal/rwthauth') def start_rwthauth(): if 'L2P_APIKEY' not in config: return render_template("500.html"), 500 diff --git a/server.py b/server.py index 7ecee0c681763853e949ac06129d33cc0216aceb..fcdf465d2910b3d24e15eab7cc0f5f271514435a 100644 --- a/server.py +++ b/server.py @@ -299,7 +299,10 @@ def index(): return redirect(url_for('course', handle=request.args['course']),code=302) if 'view' in request.args: if (request.args['view'] == 'player') and ('lectureid' in request.args) : - return redirect(url_for('lecture', id=request.args['lectureid']),code=302) + courses = query('SELECT courses.handle FROM courses JOIN lectures ON courses.id = lectures.course_id WHERE lectures.id = ?', request.args['lectureid']) + if not courses: + return "Not found", 404 + return redirect(url_for('lecture', course=courses[0]['handle'], id=request.args['lectureid']),code=302) start = date.today() - timedelta(days=1) end = start + timedelta(days=7) @@ -326,10 +329,16 @@ def index(): JOIN courses ON courses.id = lectures.course_id WHERE streams.active AND (? OR (streams.visible AND courses.visible AND courses.listed AND lectures.visible)) ''', ismod()) - featured = query('SELECT * FROM featured WHERE NOT deleted AND (? OR visible)', ismod()) + featured = query('SELECT * FROM featured WHERE (? OR visible) ORDER BY `order`', ismod()) + featured = list(filter(lambda x: not x['deleted'], featured)) + for item in featured: + if item['type'] == 'courses': + if item['param'] not in ['title', 'semester', 'organizer', 'subject']: + continue + item['courses'] = query('SELECT * FROM courses WHERE (visible AND listed) AND `%s` = ? ORDER BY `%s`'%(item['param'], item['param']), item['param2']) return render_template('index.html', latestvideos=livestreams+latestvideos, upcomming=upcomming, featured=featured) -@app.route('/course') +@app.route('/courses') @register_navbar('Videos', icon='film') def courses(): courses = query('SELECT * FROM courses WHERE (? OR (visible AND listed)) ORDER BY title', ismod()) @@ -349,8 +358,8 @@ def genlive(streams): stream['file_size'] = 0 return streams -@app.route('/course/<handle>') -@app.route('/course/<int:id>') +@app.route('/<handle>') +@app.route('/<int:id>') @handle_errors('courses', 'Diese Veranstaltung existiert nicht!', 404, IndexError) def course(id=None, handle=None): if id: @@ -390,10 +399,12 @@ def course(id=None, handle=None): def faq(): return render_template('faq.html') -@app.route('/play/<int:id>') -@app.route('/embed/<int:id>', endpoint='embed') +@app.route('/<course>/<int:id>') +@app.route('/<int:courseid>/<int:id>') +@app.route('/<course>/<int:id>/embed', endpoint='embed') +@app.route('/<int:courseid>/<int:id>/embed', endpoint='embed') @handle_errors('course', 'Diese Vorlesung existiert nicht!', 404, IndexError) -def lecture(id): +def lecture(id, course=None, courseid=None): lecture = query('SELECT * FROM lectures WHERE id = ? AND (? OR visible)', id, ismod())[0] videos = query(''' SELECT videos.*, (videos.downloadable AND courses.downloadable) as downloadable, formats.description AS format_description, formats.player_prio, formats.prio, formats.mimetype @@ -421,9 +432,12 @@ def lecture(id): if not checkperm(perms): mode, text = permdescr(perms) if mode == 'rwth': - flash(text+'. <a target="_blank" href="'+url_for('start_rwthauth')+'">Hier authorisieren</a>.') + flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_rwthauth')+'">Hier authorisieren</a>.') elif mode == 'l2p': - flash(text+'. <a target="_blank" href="'+url_for('start_l2pauth')+'">Hier authorisieren</a>.') + if 'l2p_courses' in session: + flash(text+'. Du bist kein Teilnehmer des L2P-Kurses! <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Kurse aktualisieren</a>.') + else: + flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Hier authorisieren</a>.') else: flash(text+'.') return render_template('embed.html' if request.endpoint == 'embed' else 'lecture.html', course=courses[0], lecture=lecture, videos=videos, chapters=chapters) @@ -454,7 +468,7 @@ def search(): def check_mod(user, groups): return user and 'users' in groups -@app.route('/login', methods=['GET', 'POST']) +@app.route('/internal/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('login.html') @@ -471,7 +485,7 @@ def login(): session['_csrf_token'] = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(128)) return redirect(request.values.get('ref', url_for('index'))) -@app.route('/logout', methods=['GET', 'POST']) +@app.route('/internal/logout', methods=['GET', 'POST']) def logout(): session.pop('user') return redirect(request.values.get('ref', url_for('index'))) @@ -492,15 +506,15 @@ tabs = { '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'], - ['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('/edit', methods=['GET', 'POST']) +@app.route('/internal/edit', methods=['GET', 'POST']) @mod_required @csrf_protect def edit(prefix='', ignore=[]): @@ -510,7 +524,6 @@ def edit(prefix='', ignore=[]): ignore.append('_csrf_token') if not prefix and 'prefix' in request.args: prefix = request.args['prefix'] - modify('BEGIN') changes = request.values.items() if (request.method == 'POST') and (request.get_json()): changes = request.get_json().items() @@ -521,15 +534,14 @@ def edit(prefix='', ignore=[]): 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]), + 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) - modify('COMMIT') + 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('/new/<table>', methods=['GET', 'POST']) +@app.route('/internal/new/<table>', methods=['GET', 'POST']) @mod_required @csrf_protect def create(table): @@ -549,7 +561,7 @@ def create(table): continue assert column in tabs[table][2]+tabs[table][3] assert column not in defaults - columns.append(column) + columns.append('`'+column+'`') values.append(val) id = modify('INSERT INTO %s (%s) VALUES (%s)'%(tabs[table][0], ','.join(columns), ','.join(['?']*len(values))), *values) @@ -557,7 +569,7 @@ def create(table): return redirect(request.values['ref']) return str(id), 200 -@app.route('/auth') +@app.route('/internal/auth') def auth(): # For use with nginx auth_request if 'X-Original-Uri' not in request.headers: return 'Internal Server Error', 500 @@ -618,7 +630,7 @@ def auth(): # For use with nginx auth_request return Response("Login required", 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}) return "Not allowed", 403 -@app.route('/changelog') +@app.route('/internal/changelog') @register_navbar('Changelog', icon='book') @mod_required def changelog(): @@ -631,7 +643,7 @@ def changelog(): def files(filename): return redirect(config['VIDEOPREFIX']+'/'+filename) -@app.route('/newchapter/<int:lectureid>', methods=['POST', 'GET']) +@app.route('/internal/newchapter/<int:lectureid>', methods=['POST', 'GET']) def suggest_chapter(lectureid): time = request.values['time'] text = request.values['text'] @@ -652,7 +664,7 @@ def suggest_chapter(lectureid): return redirect(request.values['ref']) return 'OK', 200 -@app.route('/chapters/<int:lectureid>') +@app.route('/internal/chapters/<int:lectureid>') def chapters(lectureid): chapters = query("SELECT * FROM chapters WHERE lecture_id = ? AND NOT deleted AND (visible OR ?) ORDER BY time DESC", lectureid, ismod()) if not chapters: @@ -675,7 +687,7 @@ def sitemap(): for i in query('select * from courses where visible and listed'): pages.append([url_for('course',handle=i['handle'])]) for j in query('select * from lectures where (course_id = ? and visible)',i['id']): - pages.append([url_for('lecture',id=j['id'])]) + pages.append([url_for('lecture',course=i['handle'],id=j['id'])]) return Response(render_template('sitemap.xml', pages=pages), 200, {'Content-Type': 'application/atom+xml'} ) @@ -685,7 +697,10 @@ def sitemap(): @app.route('/site/<string:phpfile>') def legacy(phpfile=None): if phpfile=='embed.php' and ('lecture' in request.args): - return redirect(url_for('embed', id=request.args['lecture']),code=302) + courses = query('SELECT courses.handle FROM courses JOIN lectures ON courses.id = lectures.course_id WHERE lectures.id = ?', request.args['lecture']) + if not courses: + return render_endpoint('index', 'Diese Seite existiert nicht!'), 404 + return redirect(url_for('embed', course=courses[0]['handle'], id=request.args['lecture']),code=302) if phpfile=='feed.php' and ('all' in request.args): return redirect(url_for('feed'),code=302) if phpfile=='feed.php' and ('newcourses' in request.args): diff --git a/sorter.py b/sorter.py index 72c338e884e24a9634181b95bba648ef98f3567d..1e0f80be35c3046dace946cb0703c91b1716850e 100644 --- a/sorter.py +++ b/sorter.py @@ -1,7 +1,7 @@ from server import * import traceback -@app.route('/sort/log') +@app.route('/internal/sort/log') @register_navbar('Sortierlog', icon='sort-by-attributes-alt') @mod_required def sort_log(): @@ -53,7 +53,7 @@ def schedule_thumbnail(lectureid): data = '{"lectureid": "'+str(lectureid)+'", "path": "'+path+'"}' query('INSERT INTO jobs (type, data, time_created) VALUES ("thumbnail", ?, ?)', data, datetime.now()); -@app.route('/sort/now') +@app.route('/internal/sort/now') @mod_required @sched_func(600) def sort_now(): diff --git a/static/moderator.js b/static/moderator.js index 858b5f57d4708a44272801546d04f6aa35c01b25..ca809f1220ccbe4883b891572af6f50b67df6a9d 100644 --- a/static/moderator.js +++ b/static/moderator.js @@ -13,7 +13,7 @@ var moderator = { dict['_csrf_token'] = moderator.api.csrf_token; $.ajax({ method: "POST", - url: "/edit", + url: "/internal/edit", dataType: "text", contentType: "application/json", data: JSON.stringify(dict), @@ -29,7 +29,7 @@ var moderator = { value['_csrf_token'] = moderator.api.csrf_token; $.ajax({ method: "POST", - url: "/new/"+type, + url: "/internal/new/"+type, dataType: "text", contentType: "application/json", data: JSON.stringify(value), diff --git a/stats.py b/stats.py index b0be43cfef0fbdc5d07994ba0f0439018eafa3d7..8ef31660c07b1e13c41454b9ee5a716a23fbfa1c 100644 --- a/stats.py +++ b/stats.py @@ -3,7 +3,7 @@ import json from jobs import date_json_handler from hashlib import md5 -@app.route('/stats') +@app.route('/internal/stats') @register_navbar('Statistiken', icon='stats') @mod_required def stats(): @@ -22,8 +22,8 @@ statsqueries['live_views'] = "SELECT hlslog.segment AS x, COUNT(DISTINCT hlslog. def plotly_date_handler(obj): return obj.strftime("%Y-%m-%d %H:%M:%S") -@app.route('/stats/generic/<req>') -@app.route('/stats/generic/<req>/<param>') +@app.route('/internal/stats/generic/<req>') +@app.route('/internal/stats/generic/<req>/<param>') @mod_required def stats_generic(req, param=None): if req not in statsqueries: @@ -37,8 +37,8 @@ def stats_generic(req, param=None): res[key].append(val) return Response(json.dumps([res], default=plotly_date_handler), mimetype='application/json') -@app.route('/stats/viewsperday/<req>') -@app.route('/stats/viewsperday/<req>/<param>') +@app.route('/internal/stats/viewsperday/<req>') +@app.route('/internal/stats/viewsperday/<req>/<param>') @mod_required def stats_viewsperday(req, param=""): update_expr = 'INSERT INTO logcache (req, param, trace, date, value) SELECT "%s", ?, trace, date, y FROM (%s) AS cachetmp WHERE date < ?' diff --git a/templates/500.html b/templates/500.html index 0bbc3b2c648b23784d9522d35834dde8eaa81b91..e311fa800c9bf377babf7f6cce2480cf6f6200a5 100644 --- a/templates/500.html +++ b/templates/500.html @@ -6,10 +6,9 @@ </div> <div class="row panel-body"> <div class="col-xs-12"> - Es ist ein interner Fehler aufgetreten. - Eventuell tritt dieser nur vorübergehend auf, versuche es doch einfach in ein paar Minuten noch einmal. - Sollte das Problem länger bestehen, schreib uns bitte eine Mail an <a href="mailto:video@fsmpi.rwth-aachen.de">video@fsmpi.rwth-aachen.de</a>. - Wir werden uns dann so schnellst möglich darum kümmern. + <p>Es ist ein interner Fehler aufgetreten. Eventuell betrifft dieser nur einen Teil der Seite oder er tritt nur vorübergehend auf. Versuche es doch einfach in ein paar Minuten noch einmal.</p> + <p>Falls das Problem länger bestehen sollte, schreib uns bitte eine Mail an <a href="mailto:video@fsmpi.rwth-aachen.de">video@fsmpi.rwth-aachen.de</a> in der du uns die <b>Uhrzeit</b> und <b>aufgerufene Seite</b> nennst und, dass der Fehler auf Server <b>{{ gethostname() }}</b> aufgetreten ist. + Wir werden uns dann schnellst möglich darum kümmern.</p> </div> </div> </div> diff --git a/templates/embed.html b/templates/embed.html index 2c5a2ee6f7d88194cace46307fe2da39fbe62bb1..0871eced2af0004a6edbfd65b2d707322e68c03b 100644 --- a/templates/embed.html +++ b/templates/embed.html @@ -1,6 +1,4 @@ {% from 'macros.html' import player %} -{% from 'macros.html' import video_download_btn %} -{% from 'macros.html' import video_embed_btn %} {% set page_border = 0 -%} {% extends "base.html" %} diff --git a/templates/feed.atom b/templates/feed.atom index 0144dd00dfd3b104c564b96db86fd8ba6779082d..e24ad9404aacaf705429d9015a8c51c01d15fa4b 100644 --- a/templates/feed.atom +++ b/templates/feed.atom @@ -53,7 +53,7 @@ Veranstalter: {{ course.organizer }}<br> <name>{{ entry.speaker }}</name> </author> {% endif %} - <link rel="alternate" href="{{ url_for('lecture', id=entry.id, _external=True) }}"/> + <link rel="alternate" href="{{ url_for('lecture', course=entry.course.handle, id=entry.id, _external=True) }}"/> <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> diff --git a/templates/index.html b/templates/index.html index cdfbfbe0a36471a9ae5ee8031ae01d4b34c2c2af..b036828b4659cd5a8acbc47c82d5dc4f8ef96417 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,4 +1,4 @@ -{% from 'macros.html' import preview %} +{% from 'macros.html' import preview, featured_content %} {% extends "base.html" %} {% set page_border = 0 %} {% if ismod() %} @@ -53,6 +53,16 @@ <div class="row"> <div class="col-xs-12"> <ul class="list-inline pull-right"> + <li style="padding-right: 0px;"> + <div class="btn-group"> + <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Neues Panel <span class="caret"></span></button> + <ul class="dropdown-menu"> + <li><a href="{{ url_for('create', table='featured', title='Neues Panel', type='plain', ref=request.url) }}">Nur Text</a></li> + <li><a href="{{ url_for('create', table='featured', title='Neues Panel', type='image', ref=request.url) }}">Text mit Bild</a></li> + <li><a href="{{ url_for('create', table='featured', title='Neues Panel', type='courses', param='semester', param2='', ref=request.url) }}">Veranstaltungsliste</a></li> + </ul> + </div> + </li> <li style="padding-right: 0px;"> <a class="btn btn-default" href="{{ url_for('create', table='announcements', text='Neue Ankündigung', time_publish=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0), time_expire=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)+timedelta(days=7), ref=request.url) }}">Neue Ankündigung</a> </li> @@ -61,66 +71,32 @@ </div> {% endif %} <div class="row"> - <div class="col-md-6 panel-group"> + <div class="col-md-6"> + {% for item in featured %} <div class="panel panel-default"> <div class="panel-heading"> - <h1 class="panel-title">Video AG</h1> - </div> - <div class="panel-body"> - <p>Wir machen Vorlesungsvideos, damit du dir deine Vorlesungen angucken kannst, wann, wo und so oft <strong>du</strong> willst ;)</p> - <p><strong>Probleme?</strong><a href="{{url_for('faq')}}"> Hier gehts zur FAQ</a></p> - <p>Wenn du die Videos nützlich fandest, schreib doch bitte den Dozenten eine kurze E-Mail. Waren die Videos grauenhaft? Kritik an uns.</p> - <p>Wenn du mitmachen willst, Fragen oder Anregungen hast, oder nur mal schauen möchtest, komm zu unserem AG-Treffen oder schreib uns eine E-Mail. - Insbesondere freuen wir uns über Studis der Mathematik und Physik, die ihre Vorlesungen filmen wollen.</p> - </p> - </div> - </div> - {% if (featured|length > 0) or ismod() %} - <div class="panel panel-default"> - <div class="panel-heading"> - <h1 class="panel-title">Featured - {% if ismod() %} - <a class="btn btn-default" href="{{ url_for('create', table='featured', title='Neuer Artikel', ref=request.url) }}">Neue Empfehlung</a> + <h1 class="panel-title"> + {{ moderator_editor(('featured',item.id,'title'), item.title) }} {{ moderator_checkbox(('featured',item.id,'visible'), item.visible) }} + {% if ismod() %} + <div class="btn-group pull-right" role="group"> + {% if not loop.first %} + <a class="btn btn-default" href="{{ url_for('edit', **{'featured.'+item.id|string+'.order': featured[loop.index0-1].order, 'featured.'+featured[loop.index0-1].id|string+'.order': item.order, 'ref': request.url})}}"><span class="glyphicon glyphicon-arrow-up"></span></a> + {% else %} + <button class="btn btn-default disabled"><span class="glyphicon glyphicon-arrow-up"></span></button> {% endif %} - </h1> - </div> - <div class="panel-body"> - <div id="myCarousel" class="carousel slide" data-ride="carousel" style="background-color: #EEE; min-height: 400px;" {% if ismod() %}data-interval="false"{% endif %}> - <ol class="carousel-indicators"> - {% for i in featured %} - <li data-target="#myCarousel" data-slide-to="{{ loop.index0 }}" {% if loop.first %} class="active" {% endif %}></li> - {% endfor %} - </ol> - <div class="carousel-inner" role="listbox"> - {% for i in featured %} - <div class="item {% if loop.first %}active{% endif %}"> - {% if ismod() %} - <div class="center-block"> - {{ moderator_editor(('featured',i.id,'text'), i.text) }} - {{ moderator_editor(('featured',i.id,'title'), i.title) }} - {{ moderator_checkbox(('featured',i.id,'visible'),i.visible) }} - </div> - {% else %} - {{i.text|safe}} - {% endif %} - <div class="carousel-caption">{{ i.title }}</div> - </div> - {% endfor %} - </div> - {% if not ismod() %} - <a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev"> - <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> - <span class="sr-only">Previous</span> - </a> - <a class="right carousel-control" href="#myCarousel" role="button" data-slide="next"> - <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> - <span class="sr-only">Next</span> - </a> - {% endif %} + {% if not loop.last %} + <a class="btn btn-default" href="{{ url_for('edit', **{'featured.'+item.id|string+'.order': featured[loop.index0+1].order, 'featured.'+featured[loop.index0+1].id|string+'.order': item.order, 'ref': request.url})}}"><span class="glyphicon glyphicon-arrow-down"></span></a> + {% else %} + <button class="btn btn-default disabled"><span class="glyphicon glyphicon-arrow-down"></span></button> + {% endif %} + {{ moderator_delete(['featured',item.id,'deleted']) }} </div> - </div> + {% endif %} + </h1> </div> - {% endif %} + {{ featured_content(item) }} + </div> + {% endfor %} </div> <div class="col-md-6"> <div class="panel panel-default"> diff --git a/templates/lecture.html b/templates/lecture.html index 4007263b47de998985e68dad49d5811c18618784..71c4b1246b492aa4f2428b77810d66cc55df6841 100644 --- a/templates/lecture.html +++ b/templates/lecture.html @@ -27,7 +27,7 @@ <a href="{{url_for('course', handle=course.handle)}}#lecture-{{lecture.id}}" class="btn btn-default" >Zur Veranstaltungsseite</a> <ul class="list-inline pull-right"> <li><button class="btn btn-default" id="hintnewchapter">Kapitelmarker vorschlagen</button></li> - <li>{{ video_embed_btn(lecture.id) }}</li> + <li>{{ video_embed_btn(lecture.id, course=course.handle) }}</li> <li class="dropdown">{{ video_download_btn(videos) }}</li> </ul> </div> @@ -98,5 +98,19 @@ $(function() { }) }); +$(document).ready(function() { + $("a.reloadonclose").click(function () { + var popup = window.open(this.href, this.target); + if (!popup) + return true; + var popup_check = setInterval(function() { + if (popup.closed) { + clearInterval(popup_check); + location.reload(); + }; + }, 500); + return false; + }); +}); </script> {% endblock %} diff --git a/templates/macros.html b/templates/macros.html index 7f7c80b4cc76d18a6d1eed5753a13d08e505609a..9395650c02de2d2d6b1bebb617f30b91ff1ed312 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -1,6 +1,6 @@ {% macro preview(lecture) %} <li class="list-group-item"> - <a href="{{url_for('lecture', id=lecture['id'])}}" title="{{ lecture.course.title }}" style="color: #000"> + <a href="{{url_for('lecture', course=lecture.course.handle, id=lecture['id'])}}" title="{{ lecture.course.title }}" style="color: #000"> <div class="hidden-xs"> <div class="row"> <img class="col-xs-4" style="max-height: 120px; height: auto; width:170px" src="{{ config.VIDEOPREFIX }}/{{ lecture['titlefile'] }}" alt="Vorschaubild" onerror="this.src='{{url_for('static',filename='no-thumbnail.png')}}'; this.onerror=''; "> @@ -144,12 +144,12 @@ $(function() { {% endif %} {% endmacro %} -{%macro video_embed_btn(lectureid) %} +{%macro video_embed_btn(lectureid, course=None) %} <a class="btn btn-default" id="embedcodebtn" data-container="body" data-toggle="popover" data-placement="bottom"> <span>Einbetten</span> </a> <script> -{% set embedcode = '<iframe width="700" height="394" src="'+url_for('embed', id=lectureid, _external=True)+'" frameborder="0" allowfullscreen="true"></iframe>' %} +{% set embedcode = '<iframe width="700" height="394" src="'+url_for('embed', course=course, id=lectureid, _external=True)+'" frameborder="0" allowfullscreen="true"></iframe>' %} $('#embedcodebtn').popover( { html:true, @@ -165,7 +165,7 @@ $('#embedcodebtn').popover( <div class="row"> <div style="background-image: url('{% if not lecture.titlefile %}{{url_for('static',filename='no-thumbnail.png')}}{% else %}{{ config.VIDEOPREFIX }}/{{lecture.titlefile}}'){% endif %}" class="col-sm-2 col-xs-12 thumbnailimg"> {% if not videos|length is equalto 0 %} - <a href="{{url_for('lecture', id=lecture.id)}}"> + <a href="{{url_for('lecture', course=lecture.course.handle, id=lecture.id)}}"> <span class="glyphicon glyphicon-play-circle playpreviewbtn"></span> </a> {% endif %} @@ -214,7 +214,7 @@ $('#embedcodebtn').popover( <a class="moderator_editor_sign btn btn-default" title="{{path|join('.')}}" data-toggle="tooltip" tabindex="0" style="padding: 3px; margin-right: 5px;"> <span class="glyphicon glyphicon-pencil"></span> </a> - <span class="moderator_editor_value">{{ value|safe }}</span> + <span class="moderator_editor_value">{{ value|fixnl|safe }}</span> </span> {% else %} {{value|fixnl|safe}} @@ -306,3 +306,46 @@ $('#embedcodebtn').popover( }); </script> {% endmacro %} + +{% macro featured_content(item) %} {# This macro is used in a panel div after the panel header #} + {% if item.type == 'image' %} + <!-- Putting image tag here makes it borderless. This should be replaced by a bootstrap-compatible solution. --> + <img src="{{item.param}}" style="width: 100%;"/> + {% if ismod() %} + <p>{{moderator_editor(('featured',item.id,'param'), item.param)}}</p> + {% endif %} + {% if item.text or ismod() %} + <div class="panel-body">{{ moderator_editor(('featured',item.id,'text'), item.text) }}</div> + {% endif %} + {% elif item.type == 'courses' %} + {% if item.text or ismod() %} + <div class="panel-body">{{ moderator_editor(('featured',item.id,'text'), item.text) }}</div> + {% endif %} + <ul class="courses-list list-group"> + {% for i in item.courses %} + {{ course_list_item(i) }} + {% endfor %} + </ul> + <div class="panel-footer"> + {% if ismod() %} + <ul class="list-inline"> + <li><div class="dropdown"> + {% set params = {'semester': 'Semester', 'title': 'Veranstaltung', 'organizer': 'Dozent', 'subject': 'Kategorie'} %} + <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">{{params[item.param]}}: <span class="caret"></span></button> + <ul class="dropdown-menu"> + {% for key, name in params.items() %} + <li><a href="{{url_for('edit', **{'featured.'+item.id|string+'.param': key, 'ref': request.url})}}">{{name}}</a></li> + {% endfor %} + </ul> + </div></li> + <li>{{ moderator_editor(('featured',item.id,'param2'), item.param2) }}</li> + </ul> + {% endif %} + Die vollständige Liste findest du <a href="{{ url_for('courses') }}">hier</a>. + </div> + {% else %} + {% if item.text or ismod() %} + <div class="panel-body">{{ moderator_editor(('featured',item.id,'text'), item.text) }}</div> + {% endif %} + {% endif %} +{% endmacro %} diff --git a/timetable.py b/timetable.py index a8e18be29a810c85b67f578167cdfb3c96db08b3..2ccb548870b4a16c87876ed5b1e0b04eec5511e1 100644 --- a/timetable.py +++ b/timetable.py @@ -1,6 +1,6 @@ from server import * -@app.route('/timetable') +@app.route('/internal/timetable') @register_navbar('Drehplan', icon='calendar') @mod_required def timetable():