From d5efed838ca60c20d10ea8bce383b983e1d324b8 Mon Sep 17 00:00:00 2001 From: Robin Sonnabend <robin@fsmpi.rwth-aachen.de> Date: Tue, 18 Apr 2017 00:20:24 +0200 Subject: [PATCH] Metadata before compiling and its handling This adds default values to default metadata and the option to set it before compiling the protocol (i.e. while planning to TO). Additionally, the (dynamic) metadata fields are handled correctly in the ProtocolForm and metadata is shown while still planning. Additionally, protocol.start_time is used instead of protocoltype.usual_time. /close #47 /close #65 /close #106 --- auth.py | 5 ++--- migrations/versions/70547c924023_.py | 30 ++++++++++++++++++++++++++++ models/database.py | 10 +++++++++- server.py | 27 +++++++++++++++++++------ templates/macros.html | 26 +++++++++++++++--------- templates/protocol-show.html | 9 +++++++-- templates/reminder-mail.txt | 5 ++++- views/forms.py | 28 ++++++++++++++++---------- views/tables.py | 6 ++++-- 9 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 migrations/versions/70547c924023_.py diff --git a/auth.py b/auth.py index be1d4b2..87b4d64 100644 --- a/auth.py +++ b/auth.py @@ -131,9 +131,8 @@ class ADManager: for group_dn in result.memberOf: group_dn_parts = parse_dn(group_dn) if len(group_dn_parts) >= 1: - for group_dn in group_dn_parts: - key, group, next_char = group_dn - yield group + key, group, next_char = group_dn_parts[0] + yield group def all_groups(self): connection = self.prepare_connection() diff --git a/migrations/versions/70547c924023_.py b/migrations/versions/70547c924023_.py new file mode 100644 index 0000000..be70777 --- /dev/null +++ b/migrations/versions/70547c924023_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 70547c924023 +Revises: 4e472894cc70 +Create Date: 2017-04-17 23:31:58.499305 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '70547c924023' +down_revision = '4e472894cc70' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('defaultmetas', sa.Column('prior', sa.Boolean(), nullable=False)) + op.add_column('defaultmetas', sa.Column('value', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('defaultmetas', 'value') + op.drop_column('defaultmetas', 'prior') + # ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index a0d60ab..641433a 100644 --- a/models/database.py +++ b/models/database.py @@ -249,8 +249,14 @@ class Protocol(DatabaseModel): db.session.commit() return get_etherpad_url(self.pad_identifier) + def get_time(self): + if self.start_time is not None: + return self.start_time + return self.protocoltype.usual_time + def get_datetime(self): - return datetime(self.date.year, self.date.month, self.date.day, self.protocoltype.usual_time.hour, self.protocoltype.usual_time.minute) + time = self.get_time() + return datetime(self.date.year, self.date.month, self.date.day, time.usual_time.hour, time.usual_time.minute) def has_nonplanned_tops(self): return len([top for top in self.tops if not top.planned]) > 0 @@ -670,7 +676,9 @@ class DefaultMeta(DatabaseModel): protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id")) key = db.Column(db.String) name = db.Column(db.String) + value = db.Column(db.String) internal = db.Column(db.Boolean) + prior = db.Column(db.Boolean, default=False, nullable=False) def get_parent(self): return self.protocoltype diff --git a/server.py b/server.py index eae56d6..59197bc 100755 --- a/server.py +++ b/server.py @@ -24,7 +24,7 @@ from shared import db, date_filter, datetime_filter, date_filter_long, date_filt from utils import is_past, mail_manager, url_manager, get_first_unused_int, set_etherpad_text, get_etherpad_text, split_terms, optional_int_arg, fancy_join from decorators import db_lookup, require_public_view_right, require_private_view_right, require_modify_right, require_admin_right from models.database import ProtocolType, Protocol, DefaultTOP, TOP, LocalTOP, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState, Meta, DefaultMeta, DecisionCategory, Like -from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, LocalTopForm, SearchForm, DecisionSearchForm, ProtocolSearchForm, TodoSearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm, MergeTodosForm, DecisionCategoryForm +from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, generate_protocol_form, TopForm, LocalTopForm, SearchForm, DecisionSearchForm, ProtocolSearchForm, TodoSearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm, MergeTodosForm, DecisionCategoryForm from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable, DefaultMetasTable, DecisionCategoriesTable from legacy import import_old_todos, import_old_protocols, import_old_todomails @@ -165,9 +165,13 @@ def index(): key=_protocol_sort_key, reverse=True ) - protocol = finished_protocols[0] if len(finished_protocols) > 0 else None - show_private = protocol.has_private_view_right(user) - has_public_view_right = protocol.protocoltype.has_public_view_right(user) + protocol = None + show_private = False + has_public_view_right = False + if len(finished_protocols) > 0: + protocol = finished_protocols[0] + show_private = protocol.has_private_view_right(user) + has_public_view_right = protocol.protocoltype.has_public_view_right(user) todos = None if check_login(): todos = [ @@ -480,11 +484,18 @@ def new_protocol(): flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error") return redirect(request.args.get("next") or url_for("index")) protocol = Protocol(protocoltype_id=protocoltype.id) + print(form.start_time.data) form.populate_obj(protocol) + if form.start_time.data is None: + protocol.start_time = protocoltype.usual_time db.session.add(protocol) db.session.commit() for local_top in protocol.create_localtops(): db.session.add(local_top) + for default_meta in protocoltype.metas: + if default_meta.prior: + meta = Meta(protocol_id=protocol.id, name=default_meta.name, internal=default_meta.internal, value=default_meta.value) + db.session.add(meta) db.session.commit() tasks.push_tops_to_calendar(protocol) return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) @@ -672,12 +683,16 @@ def etherpush_protocol(protocol): @require_modify_right() def update_protocol(protocol): upload_form = KnownProtocolSourceUploadForm() - edit_form = ProtocolForm(obj=protocol) + edit_form = generate_protocol_form(protocol)(obj=protocol) if edit_form.validate_on_submit(): edit_form.populate_obj(protocol) + for meta in protocol.metas: + meta.value = getattr(edit_form.metas, meta.name).data db.session.commit() tasks.push_tops_to_calendar(protocol) return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) + for meta in protocol.metas: + getattr(edit_form.metas, meta.name).data = meta.value return render_template("protocol-update.html", upload_form=upload_form, edit_form=edit_form, protocol=protocol) @app.route("/protocol/publish/<int:protocol_id>") @@ -1351,7 +1366,7 @@ def check_and_send_reminders(): print("regular action for reminders") for protocol in Protocol.query.filter(Protocol.done != True).all(): day_difference = (protocol.date - current_day).days - usual_time = protocol.protocoltype.usual_time + usual_time = protocol.get_time() protocol_time = datetime(1, 1, 1, usual_time.hour, usual_time.minute) hour_difference = (protocol_time - current_time).seconds // 3600 print("diff: {} days, {} hours".format(day_difference, hour_difference)) diff --git a/templates/macros.html b/templates/macros.html index c750b63..ca9da0a 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -71,6 +71,22 @@ to not render a label for the CRSFTokenField --> {% endfor %} {%- endmacro %} +{% macro render_form_inner(field, labels_visible) -%} + {% if field.type == 'BooleanField' %} + {{ render_checkbox_field(field) }} + {% elif field.type == 'RadioField' %} + {{ render_radio_field(field) }} + {% elif field.type == 'TextAreaField' %} + {{ render_field(field, label_visible=labels_visible, **kwargs) }} + {% elif field.type == 'FormField' %} + {% for f in field %} + {{render_form_inner(f, labels_visible=labels_visible)}} + {% endfor %} + {% else %} + {{ render_field(field, label_visible=labels_visible) }} + {% endif %} +{%- endmacro %} + {# Renders WTForm in bootstrap way. There are two ways to call function: - as macros: it will render all field forms using cycle to iterate over them - as call: it will insert form fields as you specify: @@ -95,15 +111,7 @@ to not render a label for the CRSFTokenField --> {{ caller() }} {% else %} {% for f in form %} - {% if f.type == 'BooleanField' %} - {{ render_checkbox_field(f) }} - {% elif f.type == 'RadioField' %} - {{ render_radio_field(f) }} - {% elif f.type == 'TextAreaField' %} - {{ render_field(f, label_visible=labels_visible, rows=textarea_rows) }} - {% else %} - {{ render_field(f, label_visible=labels_visible) }} - {% endif %} + {{render_form_inner(f, labels_visible=labels_visible, textarea_rows=textarea_rows, **kwargs)}} {% endfor %} {% endif %} <button type="submit" class="{{btn_class}}">{{action_text}}</button> diff --git a/templates/protocol-show.html b/templates/protocol-show.html index 5d5a284..974bc67 100644 --- a/templates/protocol-show.html +++ b/templates/protocol-show.html @@ -79,9 +79,14 @@ <p><strong>{{meta.name}}:</strong> {{meta.value}}</p> {% endif %} {% endfor %} - {% endif %} + {% endif %} {% else %} - {% if protocol.date is not none %}<p><strong>Geplant:</strong> {{protocol.date|datify_long}}{% endif %}</p> + {% if protocol.date is not none %} + <p><strong>Geplant:</strong> {{protocol.date|datify_long}}, {{protocol.start_time|timify}}</p> + {% endif %} + {% for meta in protocol.metas %} + <p><strong>{{meta.name}}:</strong> {{meta.value}}</p> + {% endfor %} {% endif %} <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> diff --git a/templates/reminder-mail.txt b/templates/reminder-mail.txt index d0180fb..39237a3 100644 --- a/templates/reminder-mail.txt +++ b/templates/reminder-mail.txt @@ -1,4 +1,7 @@ -Die nächste {{protocol.protocoltype.name}} findet am {{protocol.date|datify}} um {{protocol.protocoltype.usual_time|timify}} statt. +Die nächste {{protocol.protocoltype.name}} findet am {{protocol.date|datify}} um {{protocol.protocoltype.get_time()|timify}} statt. +{% for meta in protocol.metas %} +{{meta.name}}: {{meta.value}} +{% endfor %} Die vorläufige Tagesordnung ist: {% for top in protocol.get_tops() %} diff --git a/views/forms.py b/views/forms.py index 78bb64b..283984a 100644 --- a/views/forms.py +++ b/views/forms.py @@ -1,5 +1,5 @@ from flask_wtf import FlaskForm -from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField, FileField, DateTimeField, TextAreaField, Field, widgets +from wtforms import StringField, PasswordField, BooleanField, DateField, HiddenField, IntegerField, SelectField, FileField, DateTimeField, TextAreaField, Field, widgets, FormField from wtforms.validators import InputRequired, Optional import ipaddress @@ -146,6 +146,7 @@ class MeetingReminderForm(FlaskForm): class NewProtocolForm(FlaskForm): protocoltype_id = SelectField("Typ", choices=[], coerce=int) date = DateField("Datum (dd.mm.yyyy)", validators=[InputRequired("Du musst ein Datum angeben.")], format="%d.%m.%Y") + start_time = DateTimeField("Uhrzeit (HH:MM, optional)", validators=[Optional()], format="%H:%M") def __init__(self, protocoltypes, **kwargs): super().__init__(**kwargs) @@ -175,15 +176,20 @@ class NewProtocolFileUploadForm(FlaskForm): super().__init__(**kwargs) self.protocoltype_id.choices = get_protocoltype_choices(protocoltypes, add_all=False) -class ProtocolForm(FlaskForm): - date = DateField("Datum (dd.mm.yyyy)", validators=[InputRequired("Bitte gib das Datum des Protkolls an.")], format="%d.%m.%Y") - start_time = DateTimeField("Beginn (%H:%M)", format="%H:%M", validators=[Optional()]) - end_time = DateTimeField("Ende (%H:%M)", format="%H:%M", validators=[Optional()]) - location = StringField("Ort") - author = StringField("Protokoll") - participants = StringField("Anwesende") - done = BooleanField("Fertig") - public = BooleanField("Veröffentlicht") +def generate_protocol_form(protocol): + class ProtocolMetasForm(FlaskForm): + pass + for meta in protocol.metas: + setattr(ProtocolMetasForm, meta.name, StringField(meta.name)) + class ProtocolForm(FlaskForm): + date = DateField("Datum (dd.mm.yyyy)", validators=[InputRequired("Bitte gib das Datum des Protkolls an.")], format="%d.%m.%Y") + start_time = DateTimeField("Beginn (%H:%M)", format="%H:%M", validators=[Optional()]) + end_time = DateTimeField("Ende (%H:%M)", format="%H:%M", validators=[Optional()]) + metas = FormField(ProtocolMetasForm) + done = BooleanField("Fertig") + public = BooleanField("Veröffentlicht") + return ProtocolForm + class TopForm(FlaskForm): name = StringField("TOP", validators=[InputRequired("Du musst den Namen des TOPs angeben.")]) @@ -248,7 +254,9 @@ class MetaForm(FlaskForm): 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.")]) + value = StringField("Standardwert") internal = BooleanField("Intern") + prior = BooleanField("Planungsrelevant") class DecisionCategoryForm(FlaskForm): name = StringField("Name", validators=[InputRequired("Bitte gib den Namen der Kategorie an.")]) diff --git a/views/tables.py b/views/tables.py index 763409a..c5682f2 100644 --- a/views/tables.py +++ b/views/tables.py @@ -479,14 +479,16 @@ class DefaultMetasTable(Table): ) def headers(self): - return ["Name", "Key", "Intern", ""] + return ["Name", "Key", "Standardwert", "Intern", "Vorher", ""] def row(self, meta): user = current_user() general_part = [ meta.name, meta.key, - Table.bool(meta.internal) + meta.value, + Table.bool(meta.internal), + Table.bool(meta.prior) ] links = [ Table.link(url_for("edit_defaultmeta", defaultmeta_id=meta.id), "Ändern"), -- GitLab