diff --git a/config.py.example b/config.py.example index 62997b3c13d11c0164cb145131baa9e40c6070a4..27254fd575acc9c1fa6df3b8cd010a416665f876 100644 --- a/config.py.example +++ b/config.py.example @@ -1,5 +1,5 @@ # Defaults for development ,do not use in production! -DEBUG = True +DEBUG = False VIDEOPREFIX = 'https://videoag.fsmpi.rwth-aachen.de' VIDEOMOUNT = 'files/' #SECRET_KEY = 'something random' @@ -16,6 +16,6 @@ DB_DATA = 'db_example.sql' DB_ENGINE = 'sqlite' SQLITE_DB = 'db.sqlite' SQLITE_INIT_SCHEMA = True -SQLITE_INIT_DATA = True +SQLITE_INIT_DATA = False #LDAP_HOST = 'ldaps://rumo.fsmpi.rwth-aachen.de' diff --git a/db_schema.sql b/db_schema.sql index 58e486bda352a9c232257e501bd73a9a65c7c5df..3b39f48cbaa602699270d623d356b1e36985cfb4 100644 --- a/db_schema.sql +++ b/db_schema.sql @@ -42,26 +42,26 @@ CREATE TABLE IF NOT EXISTS `chapters` ( ); CREATE TABLE IF NOT EXISTS `courses_data` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - `visible` INTEGER NOT NULL, + `visible` INTEGER NOT NULL DEFAULT '0', `listed` INTEGER NOT NULL DEFAULT '1', `deleted` INTEGER NOT NULL DEFAULT '0', - `title` text NOT NULL, - `short` varchar(32) NOT NULL, - `handle` varchar(32) NOT NULL, - `organizer` text NOT NULL, - `subject` varchar(32) NOT NULL, + `title` text NOT NULL DEFAULT '', + `short` varchar(32) NOT NULL DEFAULT '', + `handle` varchar(32) NOT NULL DEFAULT '', + `organizer` text NOT NULL DEFAULT '', + `subject` varchar(32) NOT NULL DEFAULT '', `credits` INTEGER NOT NULL DEFAULT '0', `created_by` INTEGER DEFAULT NULL, `time_created` datetime NOT NULL, `time_updated` datetime NOT NULL, - `semester` char(6) NOT NULL, - `settings` text NOT NULL, + `semester` char(6) NOT NULL DEFAULT '', + `settings` text NOT NULL DEFAULT '', `downloadable` INTEGER NOT NULL DEFAULT '1', `embedinvisible` INTEGER NOT NULL DEFAULT '0', - `description` text NOT NULL, - `internal` text NOT NULL, - `responsible` text NOT NULL, - `feed_url` text NOT NULL + `description` text NOT NULL DEFAULT '', + `internal` text NOT NULL DEFAULT '', + `responsible` text NOT NULL DEFAULT '', + `feed_url` text NOT NULL DEFAULT '' ); CREATE TABLE IF NOT EXISTS `filesizes` ( `path` varchar(255) NOT NULL PRIMARY KEY, @@ -185,18 +185,19 @@ CREATE TABLE IF NOT EXISTS `videos_data` ( ); CREATE TABLE IF NOT EXISTS `announcements` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - `text` text NOT NULL, - `internal` text NOT NULL, + `text` text NOT NULL DEFAULT '', `level` INTEGER NOT NULL DEFAULT 0, `visible` INTEGER NOT NULL DEFAULT 0, `deleted` INTEGER NOT NULL DEFAULT 0, + `time_publish` datetime DEFAULT "", + `time_expire` datetime DEFAULT "", `time_created` datetime NOT NULL, `time_updated` datetime NOT NULL, `created_by` INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS `featured` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - `title` text NOT NULL DEFAULT "Neuer Artikel", + `title` text NOT NULL DEFAULT '', `text` text NOT NULL DEFAULT "", `internal` text NOT NULL DEFAULT "", `visible` INTEGER NOT NULL DEFAULT 0, diff --git a/importer.py b/importer.py old mode 100755 new mode 100644 diff --git a/schedule.py b/schedule.py old mode 100755 new mode 100644 diff --git a/server.py b/server.py old mode 100755 new mode 100644 index 089177a50d3f7be963f63a5d2e09a6684d7e15b2..9edddf0ac8134e14e156fee238273177999050b5 --- a/server.py +++ b/server.py @@ -12,6 +12,9 @@ app = Flask(__name__) app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True +app.add_template_global(random.randint, name='randint') +app.add_template_global(datetime, name='datetime') +app.add_template_global(timedelta, name='timedelta') def timer_func(): with app.test_request_context(): @@ -25,9 +28,9 @@ timer.start() config = app.config config.from_pyfile('config.py.example', silent=True) -if not sys.argv[0].endswith('run.py'): - config['SQLITE_INIT_DATA'] = False - config['DEBUG'] = False +if sys.argv[0].endswith('run.py'): + config['SQLITE_INIT_DATA'] = True + config['DEBUG'] = True config.from_pyfile('config.py', silent=True) if config['DEBUG']: app.jinja_env.auto_reload = True @@ -120,7 +123,10 @@ def rfc3339(d): @app.template_global() def get_announcements(minlevel=0): - return query('SELECT * FROM announcements WHERE NOT deleted AND (? OR visible) AND level >= ? ORDER BY level DESC', ismod(), minlevel) + offset = timedelta() + if ismod(): + offset = timedelta(hours=24) + return query('SELECT * FROM announcements WHERE NOT deleted AND (time_expire ISNULL OR time_expire > ?) AND (? OR (visible AND time_publish < ?)) AND level >= ? ORDER BY level DESC', datetime.now()-offset, ismod(), datetime.now(), minlevel) @app.template_filter() def fixnl(s): @@ -248,26 +254,32 @@ def logout(): session.pop('user') return redirect(request.values.get('ref', url_for('index'))) +tabs = { + 'courses': ('courses_data', 'id', ['visible', 'listed', 'title', 'short', + 'handle', 'organizer', 'subject', 'semester', 'downloadable', + 'internal', 'responsible','deleted']), + 'lectures': ('lectures_data', 'id', ['visible', 'title', 'comment', + 'internal', 'speaker', 'place', 'time', 'duration', 'jumplist','deleted']), + 'videos': ('videos_data', 'id', ['visible','deleted']), + 'chapters': ('chapters', 'id', ['time', 'text', 'visible', 'deleted']), + 'announcements': ('announcements', 'id', ['text', 'level', 'visible', 'deleted', 'time_publish', 'time_expire']), + 'featured': ('featured', 'id', ['title', 'text', 'internal', 'visible', 'deleted']) +} + @app.route('/edit', methods=['GET', 'POST']) @mod_required -def edit(prefix="", ignore=[]): +def edit(prefix='', ignore=[]): # All editable tables are expected to have a 'time_updated' field - tabs = { - 'courses': ('courses_data', 'id', ['visible', 'listed', 'title', 'short', - 'handle', 'organizer', 'subject', 'semester', 'downloadable', - 'internal', 'responsible','deleted']), - 'lectures': ('lectures_data', 'id', ['visible', 'title', 'comment', - 'internal', 'speaker', 'place', 'time', 'duration', 'jumplist','deleted']), - 'videos': ('videos_data', 'id', ['visible','deleted']), - 'chapters': ('chapters', 'id', ['time', 'text', 'visible', 'deleted']), - 'announcements': ('announcements', 'id', ['text', 'internal', 'level', 'visible', 'deleted']), - 'featured': ('featured', 'id', ['text', 'title', 'visible', 'deleted']) - } + ignore.append('ref') + ignore.append('prefix') + if not prefix and 'prefix' in request.args: + prefix = request.args['prefix'] modify('BEGIN') if request.is_json: changes = request.get_json().items() else: changes = request.args.items() + created = {} for key, val in changes: if key in ignore: continue @@ -275,22 +287,21 @@ 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]),table,id,tabs[table][1],column,val,id,datetime.now(),session['user']['givenName']) + 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') + if 'ref' in request.values: + return redirect(request.values['ref']) return "OK", 200 -@app.route('/newcourse', methods=['GET', 'POST']) +@app.route('/new/<table>', methods=['GET', 'POST']) @mod_required -def new_course(): - 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) - VALUES (0, "Neue Veranstaltung", "Neu", ?, "", "", ?, ?, ?, "", "", "", "", ?, "") - ''', 'new'+str(random.randint(0,1000)), session['user']['dbid'], datetime.now(), datetime.now(), - session['user']['givenName']) - edit(prefix='courses.'+str(id)+'.', ignore=['ref']) +def create(table): + assert table in tabs + id = modify('INSERT INTO %s (created_by, time_created, time_updated) VALUES (?, ?, ?)'%tabs[table][0], + session['user']['dbid'], datetime.now(), datetime.now()) + edit(prefix=table+'.'+str(id)+'.') if 'ref' in request.values: return redirect(request.values['ref']) return str(id), 200 @@ -361,7 +372,9 @@ def stats(): @register_navbar('Changelog', icon='book') @mod_required def changelog(): - changelog = query('SELECT *, ( "table" || "." || id_value || "." ||field) as path FROM changelog LEFT JOIN users ON (changelog.who = users.id) ORDER BY `when` DESC LIMIT 50') + changelog = query('SELECT * FROM changelog LEFT JOIN users ON (changelog.who = users.id) ORDER BY `when` DESC LIMIT 50') + for entry in changelog: + entry['path'] = '.'.join([entry['table'], entry['id_value'], entry['field']]) return render_template('changelog.html', changelog=changelog) @app.route('/files/<filename>') @@ -383,24 +396,6 @@ def suggest_chapter(lectureid): return redirect(request.values['ref']) return 'OK', 200 -@app.route('/newpsa', methods=['POST', 'GET']) -@mod_required -def new_announcement(): - 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']) - return id, 200 - -@app.route('/newfeatured', methods=['POST', 'GET']) -@mod_required -def new_featured(): - 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']) - return id, 200 - @app.route('/sitemap.xml') def sitemap(): pages=[] diff --git a/templates/base.html b/templates/base.html index 79ef4d8d9705b4d799ea8efbf265d8120646f6aa..2f7cb790e75dca5c98fb25d62e37b5d29cb09792 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,6 +1,6 @@ {% set page_border = page_border|default(1) %} {% set min_announcement_level = min_announcement_level|default(1) %} -{% set announcement_levels = {0: 'info', 1: 'info', 2: 'warning', 3: 'danger'} %} +{% set levels = {0: ('info', 'Nur auf Hauptseite sichtbar'), 1: ('info', 'Überall sichtbar (Hinweis)'), 2: ('warning', 'Überall sichtbar (Warnung)'), 3: ('danger', 'Überall sichtbar (Wichtig)')} %} {% from 'macros.html' import valueeditor, valuecheckbox, valuedeletebtn %} <!DOCTYPE html> @@ -100,20 +100,10 @@ {% for msg in get_flashed_messages() %} <div class="alert alert-danger" role="alert">{{ msg }}</div> {% endfor %} - {% for msg in get_announcements(min_announcement_level) if (not request.cookies['alert-info-'+msg.id|string]) or ismod() %} - <div class="alert alert-{{announcement_levels.get(msg.level, 'info')}}" role="alert"> - {% if not ismod() %} - <a href="#" class="close" data-dismiss="alert" aria-label="close" onclick="Cookies.set('alert-info-{{msg.id}}', '1');">×</a> - {% endif %} - <ul class="list-unstyled"> - <li>{{ valueeditor(('announcements',msg.id,'text'), msg.text|safe) }}</li> - <li class="pull-right">{{ valuedeletebtn(('announcements',msg.id,'deleted')) }}</li> - {% if ismod() %} - <li>{{ valueeditor(('announcements',msg.id,'internal'), msg.internal) }}</li> - <li class="pull-right">Sichtbar: {{ valuecheckbox(('announcements',msg.id,'visible'),msg.visible) }}</li> - <li>Level: {{ valueeditor(('announcements',msg.id,'level'), msg.level) }}</li> - {% endif %} - </ul> + {% for msg in get_announcements(min_announcement_level) if (not request.cookies['alert-info-'+msg.id|string]) %} + <div class="alert alert-{{levels.get(msg.level, ('info', ''))[0]}}" role="alert"> + <a href="#" class="close" data-dismiss="alert" aria-label="close" onclick="Cookies.set('alert-info-{{msg.id}}', '1');">×</a> + {{ msg.text|safe }} </div> {% endfor %} {% block content %} diff --git a/templates/courses.html b/templates/courses.html index f8f07bf0b99a085af995c81da5bb273f67733179..7a1cf9175730c0c4fb79443e2c15ccfe27b46659 100644 --- a/templates/courses.html +++ b/templates/courses.html @@ -9,7 +9,7 @@ </li> {% if ismod() %} <li> - <a class="btn btn-default" href="{{ url_for('new_course', ref=request.url) }}">Neue Veranstaltung</a> + <a class="btn btn-default" href="{{ url_for('create', table='courses', handle='new'+(randint(0,1000)|string), title='Neue Veranstaltung', responsible=session.user.givenName, ref=request.url) }}">Neue Veranstaltung</a> </li> {% endif %} <li class="dropdown" style="padding-right: 0px"> diff --git a/templates/index.html b/templates/index.html index 9d3ef58ff28a58dd87eccf851e756c2f31f58d5b..f2100078abefaf535134dd61cd32c80dde9781c6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,19 +1,59 @@ {% from 'macros.html' import preview %} {% extends "base.html" %} {% set page_border = 0 %} -{% set min_announcement_level = 0 %} +{% if ismod() %} + {# Little hack to not show annoucements twice #} + {% set min_announcement_level = 999 %} +{% else %} + {% set min_announcement_level = 0 %} +{% endif %} {% block content %} +{% if ismod() %} <div class="row"> <div class="col-xs-12"> - <ul class="list-inline pull-right"> - {% if ismod() %} - <li style="padding-right: 0px;"> - <a class="btn btn-default" href="{{ url_for('new_announcement', ref=request.url) }}">Neue Ankündigung</a> + {% for msg in get_announcements() %} + <div class="alert alert-{{levels.get(msg.level, ('info', ''))[0]}}" role="alert"> + <ul class="list-unstyled"> + <li>{{ valueeditor(('announcements',msg.id,'text'), msg.text|safe) }}</li> + <li class="pull-right">{{ valuedeletebtn(('announcements',msg.id,'deleted')) }}</li> + <li class="pull-right" style="padding-right: 5px;"> + {% if not msg.visible %} + <a href="{{ url_for('edit', prefix='announcements.'+str(msg.id|string)+'.', ref=request.url, visible=1) }}" class="btn btn-primary">Veröffentlichen</a> + {% elif msg.time_expire and msg.time_expire < datetime.now() %} + <a href="#" class="btn btn-danger disabled">Abgelaufen</a> + {% elif msg.time_publish and msg.time_publish > datetime.now() %} + <a href="#" class="btn btn-default disabled">Geplant</a> + {% else %} + <a href="#" class="btn btn-success disabled">Öffentlich</a> + {% endif %} </li> - {% endif %} + <li class="dropdown pull-right" style="padding-right: 5px;"> + <span class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">{{levels.get(msg.level, ('', 'Unbekannt'))[1]}} <span class="caret"></span></span> + <ul class="dropdown-menu"> + {% for level, descr in levels.items() %} + <li><a href="{{ url_for('edit', prefix='announcements.'+(msg.id|string)+'.', ref=request.url, level=level) }}">{{ descr[1] }}</a></li> + {% endfor %} + </ul> + </li> + <li> + Aktiv von {{ valueeditor(('announcements',msg.id,'time_publish'), msg.time_publish) }} + bis {{ valueeditor(('announcements',msg.id,'time_expire'), msg.time_expire) }} + </li> + </ul> + </div> + {% endfor %} + </div> +</div> +<div class="row"> + <div class="col-xs-12"> + <ul class="list-inline pull-right"> + <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> </ul> </div> </div> +{% endif %} <div class="row"> <div class="col-md-6 panel-group"> <div class="panel panel-default"> @@ -35,7 +75,7 @@ <div class="panel-heading"> <h1 class="panel-title">Featured {% if ismod() %} - <a class="btn btn-default" href="{{ url_for('new_featured', ref=request.url) }}">Neue Empfehlung</a> + <a class="btn btn-default" href="{{ url_for('create', table='featured', title='Neuer Artikel', ref=request.url) }}">Neue Empfehlung</a> {% endif %} </h1> </div>