Commit e918b00e authored by Robin Sonnabend's avatar Robin Sonnabend
Browse files

Configurable metadata

/close #16
parent 24d493b7
"""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 ###
"""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 ###
"""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 ###
......@@ -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)
......@@ -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():
......
......@@ -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]
......@@ -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):
......
......@@ -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
......
{% 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 %}
{% 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 %}
......@@ -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" %}
......
......@@ -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 %}
......
#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 %}
......@@ -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
......
{#
{{'{{'}}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 %}
......