diff --git a/auth.py b/auth.py index be1d4b23bf26a9291762b571d8b3dfc1fe8ff46a..87b4d649bcfee469e6da50e2f0c58a2b6c5e6f54 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 0000000000000000000000000000000000000000..be707774cfedcfa494e0679adc4ee90edece3ab1 --- /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 a0d60abcf2d96b3ae72245cc61ca236c05aef6f5..641433a218ccb95c1d20864ef4a8199a542f6772 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 eae56d65e247baeca1267d6b4f28d69a6f0d5392..59197bc27bb54d989514b8c59f61883703057fdc 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 c750b635c2a0d0d2fd760367633870145e506296..ca9da0a58f955bba54375739be69c2b5de7797ea 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 5d5a284af779afa51114a23b6e4034b142045b24..974bc67e47e3b84ea47c7dac029d3743816a6ad0 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 d0180fbd860af98447d34f1080bc9e3a5fe9597b..39237a38b01fa45c45522df75013c388fcec0408 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 78bb64b1cc962bb304610db972166ca92361a47f..283984aab4a8b382a61a4eedfb527a2acaf1de81 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 763409a254e6412cf3fb4c3aabc7fdab6e57f6d8..c5682f24f59fa5ba1bdef81efcf961a817ec8cdb 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"),