diff --git a/migrations/versions/201dda3f23e_.py b/migrations/versions/201dda3f23e_.py new file mode 100644 index 0000000000000000000000000000000000000000..ac5fe0bd8aad6299b4f4e1ee45ebecf7ff8bf87e --- /dev/null +++ b/migrations/versions/201dda3f23e_.py @@ -0,0 +1,26 @@ +"""empty message + +Revision ID: 201dda3f23e +Revises: 48e6d548f0f +Create Date: 2015-11-07 15:01:10.159383 + +""" + +# revision identifiers, used by Alembic. +revision = '201dda3f23e' +down_revision = '48e6d548f0f' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('speakers', sa.Column('number', sa.Integer(), nullable=True)) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_column('speakers', 'number') + ### end Alembic commands ### diff --git a/migrations/versions/48e6d548f0f_.py b/migrations/versions/48e6d548f0f_.py new file mode 100644 index 0000000000000000000000000000000000000000..50eae7c88dc2bae6176a888501bbc6082a887f32 --- /dev/null +++ b/migrations/versions/48e6d548f0f_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 48e6d548f0f +Revises: 7670a05866 +Create Date: 2015-11-07 14:03:03.505241 + +""" + +# revision identifiers, used by Alembic. +revision = '48e6d548f0f' +down_revision = '7670a05866' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('statements', sa.Column('topic_id', sa.Integer(), nullable=False)) + op.drop_constraint('statements_event_id_fkey', 'statements', type_='foreignkey') + op.create_foreign_key(None, 'statements', 'topics', ['topic_id'], ['id']) + op.drop_column('statements', 'event_id') + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('statements', sa.Column('event_id', sa.INTEGER(), autoincrement=False, nullable=False)) + op.drop_constraint(None, 'statements', type_='foreignkey') + op.create_foreign_key('statements_event_id_fkey', 'statements', 'events', ['event_id'], ['id']) + op.drop_column('statements', 'topic_id') + ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index 6b8af909cf835f721d2a7e9ba4980deae27dfe10..6a9cf5d3d7ff63241e3f099d04cf4a5d9d2d3c81 100644 --- a/models/database.py +++ b/models/database.py @@ -54,7 +54,6 @@ class Topic(db.Model): event_id = db.Column(db.Integer, db.ForeignKey("events.id"), nullable=False) event = relationship("Event", backref=backref("topics",order_by=id)) - def __init__(self, name, mode, event_id): self.name = name self.mode = mode @@ -68,16 +67,27 @@ class Topic(db.Model): self.event_id ) + def sorted_statements(self): + statements = [statement for statement in self.statements if not statement.executed] + if self.mode == "fifo": + return sorted(statements, key=lambda st: st.id) + elif self.mode == "balanced": + return sorted(statements, key=lambda st: st.speaker.count(self)) + else: + return statements + class Speaker(db.Model): __tablename__ = "speakers" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String) + number = db.Column(db.Integer) event_id = db.Column(db.Integer, db.ForeignKey("events.id"), nullable=False) event = relationship("Event", backref=backref("speakers",order_by=id)) - def __init__(self, name, event_id): + def __init__(self, name, number, event_id): self.name = name + self.number = number self.event_id = event_id def __repr__(self): @@ -86,32 +96,45 @@ class Speaker(db.Model): self.name, self.event_id ) + + def identifier(self): + if self.number == 0: + return self.name + elif self.name == "": + return self.number + else: + return "{} ({})".format(self.name, self.number) + + def count(self, topic): + return len([statement for statement in self.statements if statement.topic == topic]) + + def count_active(self, topic): + return len([statement for statement in self.statements if statement.topic == topic and not statement.executed]) class Statement(db.Model): __tablename__ = "statements" id = db.Column(db.Integer, primary_key=True) speaker_id = db.Column(db.Integer, db.ForeignKey("speakers.id"), nullable=False) - event_id = db.Column(db.Integer, db.ForeignKey("events.id"), nullable=False) + topic_id = db.Column(db.Integer, db.ForeignKey("topics.id"), nullable=False) insertion_time = db.Column(db.DateTime) executed = db.Column(db.Boolean) execution_time = db.Column(db.DateTime) speaker = relationship("Speaker", backref=backref("statements",order_by=id)) - event = relationship("Event", backref=backref("statements",order_by=id)) + topic = relationship("Topic", backref=backref("statements",order_by=id)) - def __init__(self, speaker_id, event_id, insertion_time=None, executed=False, execution_time=None): + def __init__(self, speaker_id, topic_id, insertion_time=None, executed=False, execution_time=None): self.speaker_id = speaker_id - self.event_id = event_id + self.topic_id = topic_id self.insertion_time = insertion_time or datetime.now() self.executed = executed self.execution_time = execution_time or datetime.now() def __repr__(self): - return "<Statement(id={}, speaker={}, event_id={}, topic_id={}, insertion_time={}, executed={}, execution_time={})>".format( + return "<Statement(id={}, speaker={}, topic_id={}, insertion_time={}, executed={}, execution_time={})>".format( self.id, self.speaker, - self.event_id, self.topic_id, self.insertion_time, self.executed, diff --git a/modules/admin.py b/modules/admin.py index 62f9959ab3cca925250fa51dfd273581aa5bc07b..b590b237f0735c8811ba933b666f01303d847a24 100644 --- a/modules/admin.py +++ b/modules/admin.py @@ -2,10 +2,11 @@ from flask import Blueprint, redirect, url_for, request, flash, abort, send_file from flask.ext.login import login_required from passlib.hash import pbkdf2_sha256 -from models.database import User, Topic, Event -from models.forms import AdminUserForm, NewUserForm, NewTopicForm, NewEventForm +from models.database import User, Topic, Event, Speaker, Statement +from models.forms import AdminUserForm, NewUserForm, NewTopicForm, NewEventForm, AddStatementForm -from shared import db, admin_permission, render_layout +from shared import db, admin_permission +from utils import render_layout, speaker_by_name_or_number admin = Blueprint("admin", __name__) @@ -84,6 +85,7 @@ def event_show(): if event_id is not None: event = Event.query.filter_by(id=event_id).first() return render_layout("admin_event_show.html", event=event) + return redirect(url_for(".index")) @admin.route("/event/new", methods=["GET", "POST"]) @@ -132,6 +134,19 @@ def event_edit(): return redirect(url_for(".index")) +@admin.route("/topic/show") +@login_required +@admin_permission.require() +def topic_show(): + topic_id = request.args.get("id", None) + if topic_id is not None: + topic = Topic.query.filter_by(id=topic_id).first() + form = AddStatementForm() + form.topic.data = topic.id + return render_layout("admin_topic_show.html", topic=topic, form=form) + return redirect(url_for(".index")) + + @admin.route("/topic/new", methods=["GET", "POST"]) @login_required @admin_permission.require() @@ -186,3 +201,58 @@ def topic_edit(): def topic(): topics = Topic.query.all() return render_layout("admin_topic_index.html", topics=topics) + + +@admin.route("/statement/") +@login_required +@admin_permission.require() +def statement(): + statements = Statement.query.all() + return render_layout("admin_statement_index.html", statement=statement) + +@admin.route("/statement/new", methods=["GET", "POST"]) +@login_required +@admin_permission.require() +def statement_new(): + form = AddStatementForm() + if form.validate_on_submit(): + topic = Topic.query.filter_by(id=form.topic.data).first() + speaker = speaker_by_name_or_number(form.speaker_name.data, topic.event.id) + if topic is not None and speaker is not None: + if speaker.count_active(topic) == 0: + statement = Statement(speaker.id, topic.id) + db.session.add(statement) + db.session.commit() + return redirect(url_for(".topic_show", id=topic.id)) + return render_layout("admin_statement_new.html", form=form) + +@admin.route("/statement/done") +@login_required +@admin_permission.require() +def statement_done(): + statement_id = request.args.get("id", None) + if statement_id is not None: + statement = Statement.query.filter_by(id=statement_id).first() + if statement is not None: + statement.done() + db.session.commit() + topic_id = request.args.get("topic_id", None) + if topic_id is not None: + return redirect(url_for(".topic_show", id=topic_id)) + return redirect(url_for(".index")) + +@admin.route("/statement/delete") +@login_required +@admin_permission.require() +def statement_delete(): + statement_id = request.args.get("id", None) + if statement_id is not None: + statement = Statement.query.filter_by(id=statement_id).first() + if statement is not None: + db.session.delete(statement) + db.session.commit() + topic_id = request.args.get("topic_id", None) + if topic_id is not None: + return redirect(url_for(".topic_show", id=topic_id)) + return redirect(url_for(".index")) + diff --git a/modules/speech.py b/modules/speech.py index d840f3d2a0ed2970aedc9a05f260811682797500..5479a5cf1b9712f82732226aaf7cec95bb51e25f 100644 --- a/modules/speech.py +++ b/modules/speech.py @@ -4,7 +4,8 @@ from flask.ext.login import login_required from models.database import User, Statement, Speaker, Topic from models.forms import AddStatementForm -from shared import db, admin_permission, user_permission, render_layout +from shared import db, admin_permission, user_permission +from utils import render_layout from datetime import datetime import json @@ -13,9 +14,10 @@ import config speech = Blueprint("speech", __name__) +""" def query_statements(mode, topic_id): - statements = db.session.query(Statement).filter_by(topic=topic_id).all() - speakers = db.session.query(Speaker).filter_by(topic=topic_id).all() + statements = db.session.query(Statement).filter_by(topic_id=topic_id).all() + speakers = db.session.query(Speaker).filter_by(topic_id=topic_id).all() if mode == "balanced" or mode == "pending": count = { speaker.id: 0 for speaker in speakers } for statement in statements: @@ -37,6 +39,7 @@ def query_statements(mode, topic_id): return result print("unknown querying mode {}".format(mode)) +""" @speech.route("/index") def index(): diff --git a/server.py b/server.py index 06e249c4d4ba4cefceaac5eb4d894c313ed9d56e..56fe469342b41d741a53afec9e7bc1f8699fd653 100755 --- a/server.py +++ b/server.py @@ -8,7 +8,8 @@ from flask.ext.migrate import Migrate, MigrateCommand from passlib.hash import pbkdf2_sha256 import config -from shared import db, login_manager, render_layout +from shared import db, login_manager +from utils import render_layout from models.forms import LoginForm, NewUserForm from models.database import User, Statement, Speaker, Topic @@ -61,13 +62,7 @@ def adduser(): @app.route("/") def index(): - topics = Topic.query.all() meta = [] - for topic in topics: - ls = speech.query_statements(topic.mode, topic.id) - no_speaker = Speaker("No Speaker", topic) - no_statement = Statement(no_speaker, topic) - meta.append((ls[0] if len(ls) > 0 else (no_statement, no_speaker, ()), topic)) return render_layout("index.html", meta=meta) @app.route("/update") diff --git a/shared.py b/shared.py index 9235757da538355e9c870a3d808a059c01033519..555920aa87e1e7644b780798eb59133be8f51250 100644 --- a/shared.py +++ b/shared.py @@ -1,8 +1,6 @@ -from flask import abort, render_template from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.login import LoginManager from flask.ext.principal import Permission, RoleNeed -from datetime import datetime db = SQLAlchemy() login_manager = LoginManager() @@ -11,6 +9,3 @@ admin_permission = Permission(RoleNeed("admin")) user_permission = Permission(RoleNeed("user")) roles = ["user", "admin"] -def render_layout(template, **kwargs): - current_time = datetime.now() - return render_template(template, current_time=current_time, **kwargs) diff --git a/templates/admin_event_index.html b/templates/admin_event_index.html index d6660b99f9c2930b2a3c78a0cf6d2cb0500d8704..42782b69572b489fd450a322908555366b7eb0ad 100644 --- a/templates/admin_event_index.html +++ b/templates/admin_event_index.html @@ -7,6 +7,7 @@ <thead> <tr> <th class="mdl-data-table__cell--non-numeric">Name</th> + <th class="mdl-data-table__cell--non-numeric">Mode</th> <th class="mdl-data-table__cell--non-numeric">Edit</th> <th class="mdl-data-table__cell--non-numeric">Delete</th> </tr> diff --git a/templates/admin_event_show.html b/templates/admin_event_show.html index 2d5a924730d7e77962498fd868357ed730823cde..9456c03e98ea293c3f1a5502688bf57e5fcaac50 100644 --- a/templates/admin_event_show.html +++ b/templates/admin_event_show.html @@ -16,6 +16,7 @@ {% for topic in event.topics %} <tr> <td class="mdl-data-table__cell--non-numeric"><a href="{{ url_for(".topic_show", id=topic.id) }}">{{ topic.name }}</a></td> + <td class="mdl-data-table__cell--non-numeric">{{ topic.mode }}</td> <td class="mdl-data-table__cell--non-numeric"> <a href="{{ url_for(".topic_edit", id=topic.id) }}"> <i class="material-icons">edit</i> diff --git a/templates/admin_statement_new.html b/templates/admin_statement_new.html new file mode 100644 index 0000000000000000000000000000000000000000..5510387be675765f396714ad1222bc589c0aaae0 --- /dev/null +++ b/templates/admin_statement_new.html @@ -0,0 +1,7 @@ +{% extends "admin_index.html" %} +{% from "macros.html" import render_form %} +{% block admin_title %}Add Statement{% endblock %} + +{% block content %} + {{ render_form(form, action_url=url_for(".statement_new"), action_text="Add", title="Add Statement") }} +{% endblock %} diff --git a/templates/admin_topic_index.html b/templates/admin_topic_index.html index 4f850de541e3d172cf19eff2da11f3f4d98cac47..1efc34a347d42e07d7c405bb3705c9ed055be733 100644 --- a/templates/admin_topic_index.html +++ b/templates/admin_topic_index.html @@ -8,6 +8,8 @@ <tr> <th class="mdl-data-table__cell--non-numeric">Name</th> <th class="mdl-data-table__cell--non-numeric">Mode</th> + <th class="mdl-data-table__cell--non-numeric">Event</th> + <th class="mdl-data-table__cell--non-numeric">Edit</th> <th class="mdl-data-table__cell--non-numeric">Delete</th> </tr> </thead> @@ -16,6 +18,12 @@ <tr> <td class="mdl-data-table__cell--non-numeric"><a href="{{ url_for(".topic_edit", id=topic.id) }}">{{ topic.name }}</a></td> <td class="mdl-data-table__cell--non-numeric">{{ topic.mode }}</td> + <td class="mdl-data-table__cell--non-numeric">{{ topic.event.name }}</td> + <td class="mdl-data-table__cell--non-numeric"> + <a href="{{ url_for('.topic_edit', id=topic.id) }}"> + <i class="material-icons">edit</i> + </a> + </td> <td class="mdl-data-table__cell--non-numeric"> <a href="{{ url_for('.topic_delete', id=topic.id) }}"> <i class="material-icons">delete</i> diff --git a/templates/admin_topic_show.html b/templates/admin_topic_show.html new file mode 100644 index 0000000000000000000000000000000000000000..f0d2990806a80747fb8417a22dda15da15c024e7 --- /dev/null +++ b/templates/admin_topic_show.html @@ -0,0 +1,39 @@ +{% extends "admin_index.html" %} +{% from "macros.html" import render_form %} +{% block admin_title %}Topic - {{ topic.name }}{% endblock %} + +{% block content %} + <div class="mdl-cell mdl-cell--6-col mdl-cell--5-col-tablet mdl-cell--4-col-phone mdl-grid mdl-grid--no-spacing"> + <table class="mdl-data-table mdl-js-table mdl-shadow--2dp mdl-cell mdl-cell--12-col"> + <thead> + <tr> + <th class="mdl-data-table__cell--non-numeric">Speaker</th> + <th class="mdl-data-table__cell--non-numeric">Topic</th> + <th class="mdl-data-table__cell--non-numeric">Count</th> + <th class="mdl-data-table__cell--non-numeric">Done</th> + <th class="mdl-data-table__cell--non-numeric">Remove</th> + </tr> + </thead> + <tbody> + {% for statement in topic.sorted_statements() %} + <tr> + <td class="mdl-data-table__cell--non-numeric">{{ statement.speaker.identifier() }}</td> + <td class="mdl-data-table__cell--non-numeric">{{ statement.topic.name }}</td> + <td class="mdl-data-table__cell--non-numeric">{{ statement.speaker.count(statement.topic) }}</td> + <td class="mdl-data-table__cell--non-numeric"> + <a href="{{ url_for(".statement_done", id=statement.id, topic_id=topic.id) }}"> + <i class="material-icons">done</i> + </a> + </td> + <td class="mdl-data-table__cell--non-numeric"> + <a href="{{ url_for('.statement_delete', id=statement.id, topic_id=topic.id) }}"> + <i class="material-icons">cancel</i> + </a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {{ render_form(form, action_url=url_for(".statement_new"), action_text="Add", title="Add Statement", class_="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--3-col mdl-cell-3-col-tablet mdl-cell--4-col-phone") }} +{% endblock %} diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000000000000000000000000000000000000..bc3c6f61d47d124bf749d939b138a60c3ea1c2a6 --- /dev/null +++ b/todo.txt @@ -0,0 +1,2 @@ +themen (event -> topic, +event in topic) +multiple sorting algorithms diff --git a/utils.py b/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..a4c0554bb7e770bbbff9c325b761ae43ea0aff6c --- /dev/null +++ b/utils.py @@ -0,0 +1,33 @@ +from flask import abort, render_template +from datetime import datetime + +from models.database import Speaker + +from shared import db + +def render_layout(template, **kwargs): + current_time = datetime.now() + return render_template(template, current_time=current_time, **kwargs) + +def speaker_by_name_or_number(name_or_number, event_id): + if name_or_number.isnumeric(): + number = int(name_or_number) + speaker = Speaker.query.filter_by(number=number).first() + if speaker is not None: + return speaker + else: + speaker = Speaker("", number, event_id) + db.session.add(speaker) + db.session.commit() + return speaker + else: + name = name_or_number + speaker = Speaker.query.filter_by(name=name).first() + if speaker is not None: + return speaker + else: + speaker = Speaker(name, 0, event_id) + db.session.add(speaker) + db.session.commit() + return speaker +