diff --git a/migrations/versions/162da8aeeb71_.py b/migrations/versions/162da8aeeb71_.py new file mode 100644 index 0000000000000000000000000000000000000000..e3b0effdd0639502c3dc8e602dfa202681d0beb8 --- /dev/null +++ b/migrations/versions/162da8aeeb71_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 162da8aeeb71 +Revises: a3d9d1b87ba0 +Create Date: 2017-02-22 16:52:08.142214 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '162da8aeeb71' +down_revision = 'a3d9d1b87ba0' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('meetingreminders', sa.Column('days_before', sa.Integer(), nullable=True)) + op.drop_column('meetingreminders', 'time_before') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('meetingreminders', sa.Column('time_before', postgresql.INTERVAL(), autoincrement=False, nullable=True)) + op.drop_column('meetingreminders', 'days_before') + # ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index 9d0a6f604785103567934e573a27529e4190760d..6dd11e89585e4e76a26235fda989ce1164b37a73 100644 --- a/models/database.py +++ b/models/database.py @@ -25,7 +25,7 @@ class ProtocolType(db.Model): protocols = relationship("Protocol", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="Protocol.id") 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.time_before") + reminders = relationship("MeetingReminder", backref=backref("protocoltype"), cascade="all, delete-orphan", order_by="MeetingReminder.days_before") def __init__(self, name, short_name, organization, is_public, private_group, public_group, private_mail, public_mail): @@ -207,19 +207,19 @@ class MeetingReminder(db.Model): __tablename__ = "meetingreminders" id = db.Column(db.Integer, primary_key=True) protocoltype_id = db.Column(db.Integer, db.ForeignKey("protocoltypes.id")) - time_before = db.Column(db.Interval) + days_before = db.Column(db.Integer) send_public = db.Column(db.Boolean) send_private = db.Column(db.Boolean) - def __init__(self, protocoltype_id, time_before, send_public, send_private): + def __init__(self, protocoltype_id, days_before, send_public, send_private): self.protocoltype_id = protocoltype_id - self.time_before = time_before + self.days_before = days_before self.send_public = send_public self.send_private = send_private def __repr__(self): - return "<MeetingReminder(id={}, protocoltype_id={}, time_before={}, send_public={}, send_private={})>".format( - self.id, self.protocoltype_id, self.time_before, self.send_public, self.send_private) + return "<MeetingReminder(id={}, protocoltype_id={}, days_before={}, send_public={}, send_private={})>".format( + self.id, self.protocoltype_id, self.days_before, self.send_public, self.send_private) class Error(db.Model): __tablename__ = "errors" diff --git a/server.py b/server.py index 03dccc642fd19588a6a893497d671a5d4132385d..04c5f4e784d0bc1ee4f3721ecff81dd4fa79c356 100755 --- a/server.py +++ b/server.py @@ -13,8 +13,8 @@ import config from shared import db, date_filter, datetime_filter, ldap_manager, security_manager from utils import is_past, mail_manager, url_manager from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error -from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm -from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable +from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm +from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable app = Flask(__name__) app.config.from_object(config) @@ -147,7 +147,69 @@ def show_type(type_id): return redirect(request.args.get("next") or url_for("index")) protocoltype_table = ProtocolTypeTable(protocoltype) default_tops_table = DefaultTOPsTable(protocoltype.default_tops, protocoltype) - return render_template("type-show.html", protocoltype=protocoltype, protocoltype_table=protocoltype_table, default_tops_table=default_tops_table) + 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) + +@app.route("/type/reminders/new/<int:type_id>", methods=["GET", "POST"]) +@login_required +def new_reminder(type_id): + protocoltype = ProtocolType.query.filter_by(id=type_id).first() + if protocoltype is None: + flash("Dieser Protokolltyp existiert nicht.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + user = current_user() + if not protocoltype.has_modify_right(user): + flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + form = MeetingReminderForm() + if form.validate_on_submit(): + reminder = MeetingReminder(protocoltype.id, form.days_before.data, form.send_public.data, form.send_private.data) + db.session.add(reminder) + db.session.commit() + return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id)) + return render_template("reminder-new.html", form=form, protocoltype=protocoltype) + +@app.route("/type/reminder/edit/<int:type_id>/<int:reminder_id>", methods=["GET", "POST"]) +@login_required +def edit_reminder(type_id, reminder_id): + protocoltype = ProtocolType.query.filter_by(id=type_id).first() + if protocoltype is None: + flash("Dieser Protokolltyp existiert nicht.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + user = current_user() + if not protocoltype.has_modify_right(user): + flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + reminder = MeetingReminder.query.filter_by(id=reminder_id).first() + if reminder is None or reminder.protocoltype != protocoltype: + flash("Invalide Erinnerung.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + form = MeetingReminderForm(obj=reminder) + if form.validate_on_submit(): + form.populate_obj(reminder) + db.session.commit() + return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id)) + return render_template("reminder-edit.html", form=form, protocoltype=protocoltype, reminder=reminder) + +@app.route("/type/reminder/delete/<int:type_id>/<int:reminder_id>") +@login_required +def delete_reminder(type_id, reminder_id): + protocoltype = ProtocolType.query.filter_by(id=type_id).first() + if protocoltype is None: + flash("Dieser Protokolltyp existiert nicht.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + user = current_user() + if not protocoltype.has_modify_right(user): + flash("Dir fehlen die nötigen Zugriffsrechte.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + reminder = MeetingReminder.query.filter_by(id=reminder_id).first() + if reminder is None or reminder.protocoltype != protocoltype: + flash("Invalide Erinnerung.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + db.session.delete(reminder) + db.session.commit() + return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id)) + @app.route("/type/tops/new/<int:type_id>", methods=["GET", "POST"]) @login_required @@ -183,7 +245,7 @@ def edit_default_top(type_id, top_id): default_top = DefaultTOP.query.filter_by(id=top_id).first() if default_top is None or default_top.protocoltype != protocoltype: flash("Invalider Standard-TOP.", "alert-error") - return redirect(request.args.get("nexT") or url_for("index")) + return redirect(request.args.get("next") or url_for("index")) form = DefaultTopForm(obj=default_top) if form.validate_on_submit(): form.populate_obj(default_top) diff --git a/templates/reminder-edit.html b/templates/reminder-edit.html new file mode 100644 index 0000000000000000000000000000000000000000..a954dbf33698a39e540b2ebd04638affafa870d8 --- /dev/null +++ b/templates/reminder-edit.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}Einladung ändern{% endblock %} + +{% block content %} +<div class="container"> + {{render_form(form, action_url=url_for("edit_reminder", type_id=protocoltype.id, reminder_id=reminder.id, next=url_for("show_type", type_id=protocoltype.id)), action_text="Ändern")}} +</div> +{% endblock %} diff --git a/templates/reminder-new.html b/templates/reminder-new.html new file mode 100644 index 0000000000000000000000000000000000000000..7045c3ea547639b1a5e7a20cda87923ba12f1000 --- /dev/null +++ b/templates/reminder-new.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}Einladung hinzufügen{% endblock %} + +{% block content %} +<div class="container"> + {{render_form(form, action_url=url_for("new_reminder", type_id=protocoltype.id, next=url_for("show_type", type_id=protocoltype.id)), action_text="Anlegen")}} +</div> +{% endblock %} diff --git a/templates/type-show.html b/templates/type-show.html index ee4f3aa29203f1c3fb8d0c203c3629169d7d8255..6ec51065422f58d61bee2c7d9199f08adbecb617 100644 --- a/templates/type-show.html +++ b/templates/type-show.html @@ -7,5 +7,6 @@ {{render_single_table(protocoltype_table)}} {{render_table(default_tops_table)}} Standard-TOPs mit negativer Sortierung werden vor und die mit positiver Sortierung nach den TOPs eingefügt. + {{render_table(reminders_table)}} </div> {% endblock %} diff --git a/views/forms.py b/views/forms.py index 095c5b87e4bd7c94dfca6fafc144511dfe553069..166d39b2d213d1222b6df4252a6d2cfa81c9e7fa 100644 --- a/views/forms.py +++ b/views/forms.py @@ -19,3 +19,8 @@ class ProtocolTypeForm(FlaskForm): class DefaultTopForm(FlaskForm): name = StringField("Name", validators=[InputRequired("Du musst einen Namen angeben.")]) number = IntegerField("Nummer", validators=[InputRequired("Du musst eine Nummer angeben.")]) + +class MeetingReminderForm(FlaskForm): + days_before = IntegerField("Tage vor Sitzung", validators=[InputRequired("Du musst eine Dauer angeben.")]) + send_public = BooleanField("Öffentlich einladen") + send_private = BooleanField("Intern einladen") diff --git a/views/tables.py b/views/tables.py index 034ea0a3b2c1086722ea804cbebdf12eb13931b5..50a2128c8751ded02bf98b7525ed7808f72af91a 100644 --- a/views/tables.py +++ b/views/tables.py @@ -116,3 +116,29 @@ class DefaultTOPsTable(Table): Table.link(url_for("delete_default_top", type_id=self.protocoltype.id, top_id=top.id), "Löschen", confirm="Bist du dir sicher, dass du den Standard-TOP {} löschen willst?".format(top.name)) ]) ] + +class MeetingRemindersTable(Table): + def __init__(self, reminders, protocoltype=None): + super().__init__("Einladungsmails", reminders, newlink=url_for("new_reminder", type_id=protocoltype.id) if protocoltype is not None else None) + self.protocoltype = protocoltype + + def headers(self): + return ["Zeit", "Einladen", ""] + + def row(self, reminder): + return [ + "{} Tage".format(reminder.days_before), + self.get_send_summary(reminder), + Table.concat([ + Table.link(url_for("edit_reminder", type_id=self.protocoltype.id, reminder_id=reminder.id), "Ändern"), + Table.link(url_for("delete_reminder", type_id=self.protocoltype.id, reminder_id=reminder.id), "Löschen", confirm="Bist du dir sicher, dass du die Einladungsmail {} Tage vor der Sitzung löschen willst?".format(reminder.days_before)) + ]) + ] + + def get_send_summary(self, reminder): + parts = [] + if reminder.send_public: + parts.append("Öffentlich") + if reminder.send_private: + parts.append("Intern") + return " und ".join(parts)