From e918b00ef0347998e8eebea81e4bf3dcf5068e49 Mon Sep 17 00:00:00 2001 From: Robin Sonnabend <robin@fsmpi.rwth-aachen.de> Date: Wed, 1 Mar 2017 06:15:17 +0100 Subject: [PATCH] Configurable metadata /close #16 --- migrations/versions/4bdc217932c3_.py | 32 ++++++++++++++ migrations/versions/b4156f71ee73_.py | 44 +++++++++++++++++++ migrations/versions/d5be0f66b32d_.py | 62 ++++++++++++++++++++++++++ models/database.py | 65 +++++++++++++++++++++------- server.py | 60 ++++++++++++++++++++++--- shared.py | 11 ++--- tasks.py | 9 +++- templates/decision.tex | 14 ++---- templates/defaultmeta-edit.html | 9 ++++ templates/defaultmeta-new.html | 9 ++++ templates/index.html | 12 ++--- templates/protocol-show.html | 12 ++--- templates/protocol-template.txt | 26 ++++++----- templates/protocol.tex | 12 ++--- templates/protocol.wiki | 16 +++++++ templates/type-show.html | 1 + views/forms.py | 11 ++++- views/tables.py | 22 +++++++++- 18 files changed, 347 insertions(+), 80 deletions(-) create mode 100644 migrations/versions/4bdc217932c3_.py create mode 100644 migrations/versions/b4156f71ee73_.py create mode 100644 migrations/versions/d5be0f66b32d_.py create mode 100644 templates/defaultmeta-edit.html create mode 100644 templates/defaultmeta-new.html diff --git a/migrations/versions/4bdc217932c3_.py b/migrations/versions/4bdc217932c3_.py new file mode 100644 index 0000000..b0dc922 --- /dev/null +++ b/migrations/versions/4bdc217932c3_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 4bdc217932c3 +Revises: d5be0f66b32d +Create Date: 2017-03-01 05:19:03.947825 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4bdc217932c3' +down_revision = 'd5be0f66b32d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('protocols', 'location') + op.drop_column('protocols', 'author') + op.drop_column('protocols', 'participants') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('protocols', sa.Column('participants', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('protocols', sa.Column('author', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('protocols', sa.Column('location', sa.VARCHAR(), autoincrement=False, nullable=True)) + # ### end Alembic commands ### diff --git a/migrations/versions/b4156f71ee73_.py b/migrations/versions/b4156f71ee73_.py new file mode 100644 index 0000000..8171a91 --- /dev/null +++ b/migrations/versions/b4156f71ee73_.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: b4156f71ee73 +Revises: 7dd3c479c048 +Create Date: 2017-03-01 04:24:24.534003 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b4156f71ee73' +down_revision = '7dd3c479c048' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('defaultmeta', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('protocoltype_id', sa.Integer(), nullable=True), + sa.Column('key', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['protocoltype_id'], ['protocoltypes.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('meta', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('protocol_id', sa.Integer(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('value', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['protocol_id'], ['protocols.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('meta') + op.drop_table('defaultmeta') + # ### end Alembic commands ### diff --git a/migrations/versions/d5be0f66b32d_.py b/migrations/versions/d5be0f66b32d_.py new file mode 100644 index 0000000..aa7913c --- /dev/null +++ b/migrations/versions/d5be0f66b32d_.py @@ -0,0 +1,62 @@ +"""empty message + +Revision ID: d5be0f66b32d +Revises: b4156f71ee73 +Create Date: 2017-03-01 04:32:20.328667 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd5be0f66b32d' +down_revision = 'b4156f71ee73' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('defaultmetas', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('protocoltype_id', sa.Integer(), nullable=True), + sa.Column('key', sa.String(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['protocoltype_id'], ['protocoltypes.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('metas', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('protocol_id', sa.Integer(), nullable=True), + sa.Column('name', sa.String(), nullable=True), + sa.Column('value', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['protocol_id'], ['protocols.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.drop_table('defaultmeta') + op.drop_table('meta') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('meta', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('protocol_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('value', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['protocol_id'], ['protocols.id'], name='meta_protocol_id_fkey'), + sa.PrimaryKeyConstraint('id', name='meta_pkey') + ) + op.create_table('defaultmeta', + sa.Column('id', sa.INTEGER(), nullable=False), + sa.Column('protocoltype_id', sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column('key', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['protocoltype_id'], ['protocoltypes.id'], name='defaultmeta_protocoltype_id_fkey'), + sa.PrimaryKeyConstraint('id', name='defaultmeta_pkey') + ) + op.drop_table('metas') + op.drop_table('defaultmetas') + # ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index 85ae45a..a95cc80 100644 --- a/models/database.py +++ b/models/database.py @@ -5,7 +5,7 @@ import math from io import StringIO, BytesIO from enum import Enum -from shared import db, date_filter, date_filter_short, escape_tex, DATE_KEY, START_TIME_KEY, END_TIME_KEY, AUTHOR_KEY, PARTICIPANTS_KEY, LOCATION_KEY +from shared import db, date_filter, date_filter_short, escape_tex, DATE_KEY, START_TIME_KEY, END_TIME_KEY from utils import random_string, url_manager, get_etherpad_url, split_terms from models.errors import DateNotMatchingException @@ -41,6 +41,7 @@ class ProtocolType(db.Model): default_tops = relationship("DefaultTOP", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="DefaultTOP.number") reminders = relationship("MeetingReminder", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="MeetingReminder.days_before") todos = relationship("Todo", backref=backref("protocoltype"), order_by="Todo.id") + metas = relationship("DefaultMeta", backref=backref("protocoltype"), cascade="all, delete-orphan") def __init__(self, name, short_name, organization, usual_time, is_public, modify_group, private_group, public_group, @@ -124,9 +125,6 @@ class Protocol(db.Model): date = db.Column(db.Date) start_time = db.Column(db.Time) end_time = db.Column(db.Time) - author = db.Column(db.String) - participants = db.Column(db.String) - location = db.Column(db.String) done = db.Column(db.Boolean) public = db.Column(db.Boolean) @@ -134,8 +132,9 @@ class Protocol(db.Model): decisions = relationship("Decision", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Decision.id") documents = relationship("Document", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Document.is_compiled") errors = relationship("Error", backref=backref("protocol"), cascade="all, delete-orphan", order_by="Error.id") + metas = relationship("Meta", backref=backref("protocol"), cascade="all, delete-orphan") - def __init__(self, protocoltype_id, date, source=None, content_public=None, content_private=None, start_time=None, end_time=None, author=None, participants=None, location=None, done=False, public=False): + def __init__(self, protocoltype_id, date, source=None, content_public=None, content_private=None, start_time=None, end_time=None, done=False, public=False): self.protocoltype_id = protocoltype_id self.date = date self.source = source @@ -143,9 +142,6 @@ class Protocol(db.Model): self.content_public = content_public self.start_time = start_time self.end_time = end_time - self.author = author - self.participants = participants - self.location = location self.done = done self.public = public @@ -188,12 +184,16 @@ class Protocol(db.Model): self.start_time = _date_or_lazy(START_TIME_KEY, get_time=True) if END_TIME_KEY in remarks: self.end_time = _date_or_lazy(END_TIME_KEY, get_time=True) - if AUTHOR_KEY in remarks: - self.author = remarks[AUTHOR_KEY].value.strip() - if PARTICIPANTS_KEY in remarks: - self.participants = remarks[PARTICIPANTS_KEY].value.strip() - if LOCATION_KEY in remarks: - self.location = remarks[LOCATION_KEY].value.strip() + old_metas = list(self.metas) + for meta in old_metas: + db.session.delete(meta) + db.session.commit() + for default_meta in self.protocoltype.metas: + if default_meta.key in remarks: + value = remarks[default_meta.key].value.strip() + meta = Meta(self.id, default_meta.name, value) + db.session.add(meta) + db.session.commit() def has_public_view_right(self, user): return ( @@ -307,12 +307,12 @@ class TOP(db.Model): planned = db.Column(db.Boolean) description = db.Column(db.String) - def __init__(self, protocol_id, name, number, planned, description): + def __init__(self, protocol_id, name, number, planned, description=None): self.protocol_id = protocol_id self.name = name self.number = number self.planned = planned - self.description = description + self.description = description if description is not None else "" def __repr__(self): return "<TOP(id={}, protocol_id={}, name={}, number={}, planned={})>".format( @@ -655,3 +655,36 @@ class OldTodo(db.Model): return ("<OldTodo(id={}, old_id={}, who='{}', description='{}', " "protocol={}".format(self.id, self.old_id, self.who, self.description, self.protocol_key)) + +class DefaultMeta(db.Model): + __tablename__ = "defaultmetas" + id = db.Column(db.Integer, primary_key=True) + protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id")) + key = db.Column(db.String) + name = db.Column(db.String) + + def __init__(self, protocoltype_id, key, name): + self.protocoltype_id = protocoltype_id + self.key = key + self.name = name + + def __repr__(self): + return ("<DefaultMeta(id={}, protocoltype_id={}, key='{}', " + "name='{}')>".format(self.id, self.protocoltype_id, self.key)) + +class Meta(db.Model): + __tablename__ = "metas" + id = db.Column(db.Integer, primary_key=True) + protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id")) + name = db.Column(db.String) + value = db.Column(db.String) + + def __init__(self, protocol_id, name, value): + self.protocol_id = protocol_id + self.name = name + self.value = value + + def __repr__(self): + return "<Meta(id={}, protocoltype_id={}, name={}, value={})>".format( + self.id, self.protocoltype_id, self.name, self.value) + diff --git a/server.py b/server.py index 1f8dce4..8b07694 100755 --- a/server.py +++ b/server.py @@ -19,11 +19,11 @@ from datetime import datetime import math import config -from shared import db, date_filter, datetime_filter, date_filter_long, time_filter, ldap_manager, security_manager, current_user, check_login, login_required, group_required, class_filter, needs_date_test, todostate_name_filter, code_filter +from shared import db, date_filter, datetime_filter, date_filter_long, date_filter_short, time_filter, ldap_manager, security_manager, current_user, check_login, login_required, group_required, class_filter, needs_date_test, todostate_name_filter, code_filter, indent_tab_filter from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms, optional_int_arg -from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState -from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm -from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable +from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState, Meta, DefaultMeta +from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm +from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable, DefaultMetasTable from legacy import import_old_todos, import_old_protocols app = Flask(__name__) @@ -60,11 +60,13 @@ app.jinja_env.lstrip_blocks = True app.jinja_env.filters["datify"] = date_filter app.jinja_env.filters["datetimify"] = datetime_filter app.jinja_env.filters["timify"] = time_filter +app.jinja_env.filters["datify_short"] = date_filter_short app.jinja_env.filters["datify_long"] = date_filter_long app.jinja_env.filters["url_complete"] = url_manager.complete app.jinja_env.filters["class"] = class_filter app.jinja_env.filters["todo_get_name"] = todostate_name_filter app.jinja_env.filters["code"] = code_filter +app.jinja_env.filters["indent_tab"] = indent_tab_filter app.jinja_env.tests["auth_valid"] = security_manager.check_user app.jinja_env.tests["needs_date"] = needs_date_test @@ -213,7 +215,8 @@ def show_type(type_id): protocoltype_table = ProtocolTypeTable(protocoltype) default_tops_table = DefaultTOPsTable(protocoltype.default_tops, protocoltype) reminders_table = MeetingRemindersTable(protocoltype.reminders, protocoltype) - return render_template("type-show.html", protocoltype=protocoltype, protocoltype_table=protocoltype_table, default_tops_table=default_tops_table, reminders_table=reminders_table, mail_active=config.MAIL_ACTIVE) + metas_table = DefaultMetasTable(protocoltype.metas, protocoltype) + return render_template("type-show.html", protocoltype=protocoltype, protocoltype_table=protocoltype_table, default_tops_table=default_tops_table, metas_table=metas_table, reminders_table=reminders_table, mail_active=config.MAIL_ACTIVE) @app.route("/type/delete/<int:type_id>") @login_required @@ -1135,6 +1138,53 @@ def delete_todomail(todomail_id): flash("Die Todo-Mail-Zuordnung für {} wurde gelöscht.".format(name), "alert-success") return redirect(request.args.get("next") or url_for("list_todomails")) +@app.route("/defaultmeta/new/<int:type_id>", methods=["GET", "POST"]) +@login_required +def new_defaultmeta(type_id): + user = current_user() + protocoltype = ProtocolType.query.filter_by(id=type_id).first() + if protocoltype is None or not protocoltype.has_modify_right(user): + flash("Invalider Protokolltyp oder unzureichende Rechte.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + form = DefaultMetaForm() + if form.validate_on_submit(): + meta = DefaultMeta(protocoltype_id=type_id, key=form.key.data, + name=form.name.data) + db.session.add(meta) + db.session.commit() + flash("Metadatenfeld hinzugefügt.", "alert-success") + return redirect(request.args.get("next") or url_for("show_type", type_id=type_id)) + return render_template("defaultmeta-new.html", form=form, protocoltype=protocoltype) + +@app.route("/defaultmeta/edit/<int:meta_id>", methods=["GET", "POST"]) +@login_required +def edit_defaultmeta(meta_id): + user = current_user() + meta = DefaultMeta.query.filter_by(id=meta_id).first() + if meta is None or not meta.protocoltype.has_modify_right(user): + flash("Invalider Protokolltyp oder unzureichende Rechte.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + form = DefaultMetaForm(obj=meta) + if form.validate_on_submit(): + form.populate_obj(meta) + db.session.commit() + return redirect(request.args.get("next") or url_for("show_type", type_id=meta.protocoltype.id)) + return render_template("defaultmeta-edit.html", form=form, meta=meta) + +@app.route("/defaultmeta/delete/<int:meta_id>") +@login_required +def delete_defaultmeta(meta_id): + user = current_user() + meta = DefaultMeta.query.filter_by(id=meta_id).first() + if meta is None or not meta.protocoltype.has_modify_right(user): + flash("Invalider Protokolltyp oder unzureichende Rechte.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + name = meta.name + type_id = meta.protocoltype.id + db.session.delete(meta) + db.session.delete() + flash("Metadatenfeld '{}' gelöscht.", "alert-error") + return redirect(request.args.get("next") or url_for("show_type", type_id=type_id)) @app.route("/login", methods=["GET", "POST"]) def login(): diff --git a/shared.py b/shared.py index c27e87f..9874755 100644 --- a/shared.py +++ b/shared.py @@ -81,9 +81,11 @@ def needs_date_test(todostate): def todostate_name_filter(todostate): return todostate.get_name() +def indent_tab_filter(text): + return "\n".join(map(lambda l: "\t{}".format(l), text.splitlines())) + def class_filter(obj): return obj.__class__.__name__ - def code_filter(text): return "<code>{}</code>".format(text) @@ -122,9 +124,4 @@ def group_required(function, group): DATE_KEY = "Datum" START_TIME_KEY = "Beginn" END_TIME_KEY = "Ende" -AUTHOR_KEY = "Autor" -PARTICIPANTS_KEY = "Anwesende" -LOCATION_KEY = "Ort" -KNOWN_KEYS = [DATE_KEY, START_TIME_KEY, END_TIME_KEY, AUTHOR_KEY, - PARTICIPANTS_KEY, LOCATION_KEY -] +KNOWN_KEYS = [DATE_KEY, START_TIME_KEY, END_TIME_KEY] diff --git a/tasks.py b/tasks.py index b8822fe..ebcd560 100644 --- a/tasks.py +++ b/tasks.py @@ -5,6 +5,7 @@ import subprocess import shutil import tempfile from datetime import datetime +import traceback from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder, TodoMail, DecisionDocument, TodoState, OldTodo from models.errors import DateNotMatchingException @@ -60,7 +61,9 @@ def parse_protocol_async(protocol_id, encoded_kwargs): raise Exception("No protocol given. Aborting parsing.") parse_protocol_async_inner(protocol, encoded_kwargs) except Exception as exc: - error = protocol.create_error("Parsing", "Exception", str(exc)) + stacktrace = traceback.format_exc() + error = protocol.create_error("Parsing", "Exception", + "{}\n\n{}".format(str(exc), stacktrace)) db.session.add(error) db.session.commit() @@ -92,6 +95,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs): return remarks = {element.name: element for element in tree.children if isinstance(element, Remark)} required_fields = KNOWN_KEYS + for default_meta in protocol.protocoltype.metas: + required_fields.append(default_meta.key) if not config.PARSER_LAZY: missing_fields = [field for field in required_fields if field not in remarks] if len(missing_fields) > 0: @@ -489,7 +494,7 @@ def push_tops_to_calendar_async(protocol_id): db.session.commit() def set_etherpad_content(protocol): - set_etherpad_content_async.delay(protocol_id) + set_etherpad_content_async.delay(protocol.id) @celery.task def set_etherpad_content_async(protocol_id): diff --git a/templates/decision.tex b/templates/decision.tex index ad042ba..6a98ccb 100644 --- a/templates/decision.tex +++ b/templates/decision.tex @@ -24,17 +24,11 @@ }{} \begin{tabular}{rp{14cm}} \ENV{if protocol.date is not none} -{\bf Datum:} & \VAR{protocol.date|datify_long|escape_tex}\\ -\ENV{endif} -\ENV{if protocol.location is not none} -{\bf Ort:} & \VAR{protocol.location|escape_tex}\\ -\ENV{endif} -\ENV{if protocol.author is not none} -{\bf Protokoll:} & \VAR{protocol.author|escape_tex}\\ -\ENV{endif} -\ENV{if protocol.participants is not none} -{\bf Anwesend:} & \VAR{protocol.participants|escape_tex}\\ + {\bf Datum:} & \VAR{protocol.date|datify_long|escape_tex}\\ \ENV{endif} +\ENV{for meta in protocol.metas} + {\bf \ENV{meta.name|escape_tex}:} & \VAR{meta.value|escape_tex}\\ +\ENV{endfor} \end{tabular} \normalsize diff --git a/templates/defaultmeta-edit.html b/templates/defaultmeta-edit.html new file mode 100644 index 0000000..9418992 --- /dev/null +++ b/templates/defaultmeta-edit.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}Metadatum ändern{% endblock %} + +{% block content %} +<div class="container"> + {{render_form(form, action_url=url_for("edit_defaultmeta", meta_id=meta.id, next=url_for("show_type", type_id=meta.protocoltype.id)), action_text="Ändern")}} +</div> +{% endblock %} diff --git a/templates/defaultmeta-new.html b/templates/defaultmeta-new.html new file mode 100644 index 0000000..8a7c0cc --- /dev/null +++ b/templates/defaultmeta-new.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}Metadatum hinzufügen{% endblock %} + +{% block content %} +<div class="container"> + {{render_form(form, action_url=url_for("new_defaultmeta", type_id=protocoltype.id, next=url_for("show_type", type_id=protocoltype.id)), action_text="Anlegen")}} +</div> +{% endblock %} diff --git a/templates/index.html b/templates/index.html index e32617a..f185605 100644 --- a/templates/index.html +++ b/templates/index.html @@ -54,15 +54,9 @@ {% if protocol.start_time is not none and protocol.end_time is not none %} <p><strong>Zeit:</strong> von {{protocol.start_time|timify}} bis {{protocol.end_time|timify}}</p> {% endif %} - {% if protocol.location is not none %} - <p><strong>Ort:</strong> {{protocol.location}}</p> - {% endif %} - {% if protocol.author is not none %} - <p><strong>Protokoll:</strong> {{protocol.author}}</p> - {% endif %} - {% if protocol.participants is not none %} - <p><strong>Anwesende:</strong> {{protocol.participants}}</p> - {% endif %} + {% for meta in protocol.metas %} + <p><strong>{{meta.name}}:</strong> {{meta.value}}</p> + {% endfor %} <h3>Tagesordnung{% if has_modify_right and not protocol.has_nonplanned_tops() %} <a href="{{url_for("new_top", protocol_id=protocol.id)}}">Top hinzufügen</a>{% endif %}</h3> {% include "protocol-tops-include.html" %} diff --git a/templates/protocol-show.html b/templates/protocol-show.html index 3185396..a78aa6d 100644 --- a/templates/protocol-show.html +++ b/templates/protocol-show.html @@ -59,15 +59,9 @@ {% if protocol.start_time is not none and protocol.end_time is not none %} <p><strong>Zeit:</strong> von {{protocol.start_time|timify}} bis {{protocol.end_time|timify}}</p> {% endif %} - {% if protocol.location is not none %} - <p><strong>Ort:</strong> {{protocol.location}}</p> - {% endif %} - {% if protocol.author is not none and has_public_view_right %} - <p><strong>Protokoll:</strong> {{protocol.author}}</p> - {% endif %} - {% if protocol.participants is not none and has_public_view_right %} - <p><strong>Anwesende:</strong> {{protocol.participants}}</p> - {% endif %} + {% for meta in protocol.metas %} + <p><strong>{{meta.name}}:</strong> {{meta.value}}</p> + {% endfor %} {% else %} {% if protocol.date is not none %}<p><strong>Geplant:</strong> {{protocol.date|datify_long}}{% endif %}</p> {% endif %} diff --git a/templates/protocol-template.txt b/templates/protocol-template.txt index 04e09c7..d250c82 100644 --- a/templates/protocol-template.txt +++ b/templates/protocol-template.txt @@ -1,9 +1,9 @@ -#Datum;{{protocol.date|datify}} -#Anwesende; -#Beginn; +#Datum;{{protocol.date|datify_short}} +#Beginn;{{protocol.protocoltype.usual_time|timify}} #Ende; -#Autor; -#Ort; +{% for defaultmeta in protocol.protocoltype.metas %} +#{{defaultmeta.key}}; +{% endfor %} {% macro render_top(top, use_description=False) %} {TOP {{top.name}} @@ -11,16 +11,20 @@ {% set todos=protocol.get_open_todos() %} {% if todos|length > 0 %} {% for todo in todos %} - {{-todo.render_template()|indent(indentfirst=True)}}; + {{todo.render_template()}}; {% endfor %} {% else %} - {% endif %} {% else %} {% if use_description %} - {{-top.description|indent(indentfirst=True)}} + {% if top.description|length > 0 %} +{{top.description|indent_tab}} + {% else %} + + {% endif %} + {% else %} + {% endif %} - {% endif %} } {% endmacro -%} @@ -33,12 +37,12 @@ {% endfor %} {% endif %} {% for top in protocol.tops %} - {{-render_top(top, use_description=True)}} +{{render_top(top, use_description=True)}} {% endfor %} {% if not protocol.has_nonplanned_tops() %} {% for default_top in protocol.protocoltype.default_tops %} {% if default_top.is_at_end() %} - {{-render_top(default_top)}} +{{render_top(default_top)}} {% endif %} {% endfor %} {% endif %} diff --git a/templates/protocol.tex b/templates/protocol.tex index 784684d..2004b24 100644 --- a/templates/protocol.tex +++ b/templates/protocol.tex @@ -26,15 +26,9 @@ \ENV{if protocol.date is not none} {\bf Datum:} & \VAR{protocol.date|datify_long|escape_tex}\\ \ENV{endif} -\ENV{if protocol.location is not none} - {\bf Ort:} & \VAR{protocol.location|escape_tex}\\ -\ENV{endif} -\ENV{if protocol.author is not none} - {\bf Protokoll:} & \VAR{protocol.author|escape_tex}\\ -\ENV{endif} -\ENV{if protocol.participants is not none} - {\bf Anwesend:} & \VAR{protocol.participants|escape_tex}\\ -\ENV{endif} +\ENV{for meta in protocol.metas} + {\bf \VAR{meta.name|escape_tex}:} & \VAR{meta.value|escape_tex}\\ +\ENV{endfor} \end{tabular} \normalsize diff --git a/templates/protocol.wiki b/templates/protocol.wiki index 7d9c816..1bfc8ef 100644 --- a/templates/protocol.wiki +++ b/templates/protocol.wiki @@ -1,3 +1,4 @@ +{# {{'{{'}}Infobox Protokoll | name = {{protocol.protocoltype.name}} {% if protocol.date is not none %} @@ -13,6 +14,21 @@ | anwesende = {{protocol.participants}} {% endif %} {{'}}'}} +#} +'''{{protocol.protocoltype.name}}''' + +{% if protocol.date is not none %} +Datum: '''{{protocol.date|datify_long}}''' +{% endif %} + +{% if protocol.start_time is not none and protocol.end_time is not none %} +Zeit: '''von {{protocol.start_time|timify}} bis {{protocol.end_time|timify}}''' +{% endif %} + +{% for meta in protocol.metas %} +{{meta.name}}: '''{{meta.value}}''' + +{% endfor %} == Beschlüsse == {% if protocol.decisions|length > 0 %} diff --git a/templates/type-show.html b/templates/type-show.html index 415ad18..6049d10 100644 --- a/templates/type-show.html +++ b/templates/type-show.html @@ -10,5 +10,6 @@ {% if mail_active %} {{render_table(reminders_table)}} {% endif %} + {{render_table(metas_table)}} </div> {% endblock %} diff --git a/views/forms.py b/views/forms.py index f33f3e9..0f7764e 100644 --- a/views/forms.py +++ b/views/forms.py @@ -43,7 +43,8 @@ def get_printer_choices(): def get_group_choices(): user = current_user() - choices = list(zip(user.groups, user.groups)) + groups = sorted(user.groups) + choices = list(zip(groups, groups)) choices.insert(0, ("", "Keine Gruppe")) return choices @@ -173,3 +174,11 @@ class TodoForm(FlaskForm): class TodoMailForm(FlaskForm): name = StringField("Name", validators=[InputRequired("Du musst den Namen angeben, der zugeordnet werden soll.")]) mail = StringField("Mail", validators=[InputRequired("Du musst die Mailadresse angeben, die zugeordnet werden soll.")]) + +class MetaForm(FlaskForm): + name = StringField("Name", validators=[InputRequired("Bitte gib den Namen der Metadaten an.")]) + value = StringField("Wert") + +class DefaultMetaForm(FlaskForm): + key = StringField("Key", validators=[InputRequired("Bitte gib den Protokoll-Syntax-Schlüssel der Metadaten an.")]) + name = StringField("Name", validators=[InputRequired("Bitte gib den Namen der Metadaten an.")]) diff --git a/views/tables.py b/views/tables.py index c0fc831..9599273 100644 --- a/views/tables.py +++ b/views/tables.py @@ -1,6 +1,6 @@ # coding: utf-8 from flask import Markup, url_for, request -from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decision +from models.database import Protocol, ProtocolType, DefaultTOP, TOP, Todo, Decision, Meta, DefaultMeta from shared import date_filter, datetime_filter, date_filter_short, current_user, check_login import config @@ -352,3 +352,23 @@ class TodoMailsTable(Table): ]) ] +class DefaultMetasTable(Table): + def __init__(self, metas, protocoltype): + super().__init__( + "Metadatenfelder", + metas, + url_for("new_defaultmeta", type_id=protocoltype.id) + ) + + def headers(self): + return ["Name", "Key", ""] + + def row(self, meta): + return [ + meta.name, + meta.key, + Table.concat([ + Table.link(url_for("edit_defaultmeta", meta_id=meta.id), "Ändern"), + Table.link(url_for("delete_defaultmeta", meta_id=meta.id, confirm="Bist du dir sicher, dass du das Metadatenfeld {} löschen willst?".format(meta.name)), "Löschen") + ]) + ] -- GitLab