diff --git a/server.py b/server.py index 38bb07873ae5402f66faa1e65d31ed22f06694e6..05c8d4172793c6a7f7b93e83e986e157462890cc 100755 --- a/server.py +++ b/server.py @@ -15,9 +15,9 @@ from datetime import datetime 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 -from utils import is_past, mail_manager, url_manager +from utils import is_past, mail_manager, url_manager, get_first_unused_int from models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error -from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm +from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable app = Flask(__name__) @@ -188,7 +188,6 @@ def delete_reminder(type_id, reminder_id): 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 def new_default_top(type_id): @@ -265,8 +264,11 @@ def move_default_top(type_id, top_id, diff): 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")) - default_top.number += int(diff) - db.session.commit() + try: + default_top.number += int(diff) + db.session.commit() + except ValueError: + flash("Die angegebene Differenz ist keine Zahl.", "alert-error") return redirect(request.args.get("next") or url_for("show_type", type_id=protocoltype.id)) @@ -425,9 +427,6 @@ def upload_new_protocol(): tasks.parse_protocol(protocol) return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) return redirect(request.args.get("fail") or url_for("new_protocol")) - - - @app.route("/protocol/source/<int:protocol_id>") @login_required @@ -452,6 +451,73 @@ def update_protocol(protocol_id): edit_form = ProtocolForm(obj=protocol) return render_template("protocol-update.html", upload_form=upload_form, edit_form=edit_form, protocol=protocol) +@app.route("/protocol/tops/new/<int:protocol_id>", methods=["GET", "POST"]) +@login_required +def new_top(protocol_id): + user = current_user() + protocol = Protocol.query.filter_by(id=protocol_id).first() + if protocol is None or not protocol.protocoltype.has_modify_right(user): + flash("Invalides Protokoll oder keine Berechtigung.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + form = TopForm() + if form.validate_on_submit(): + top = TOP(protocol_id=protocol.id, name=form.name.data, number=form.number.data, planned=True) + db.session.add(top) + db.session.commit() + return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) + else: + print(form.number.data) + current_numbers = list(map(lambda t: t.number, protocol.tops)) + suggested_number = get_first_unused_int(current_numbers) + form.number.data = suggested_number + return render_template("top-new.html", form=form, protocol=protocol) + +@app.route("/protocol/top/edit/<int:top_id>", methods=["GET", "POST"]) +@login_required +def edit_top(top_id): + user = current_user() + top = TOP.query.filter_by(id=top_id).first() + if top is None or not top.protocol.protocoltype.has_modify_right(user): + flash("Invalider TOP oder keine Berechtigung.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + form = TopForm(obj=top) + if form.validate_on_submit(): + form.populate_obj(top) + db.session.commit() + return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id)) + return render_template("top-edit.html", form=form, top=top) + +@app.route("/protocol/top/delete/<int:top_id>") +@login_required +def delete_top(top_id): + user = current_user() + top = TOP.query.filter_by(id=top_id).first() + if top is None or not top.protocol.protocoltype.has_modify_right(user): + flash("Invalider TOP oder keine Berechtigung.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + name = top.name + protocol_id = top.protocol.id + db.session.delete(top) + db.session.commit() + flash("Der TOP {} wurde gelöscht.".format(name), "alert-success") + return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol_id)) + +@app.route("/protocol/top/move/<int:top_id>/<diff>") +@login_required +def move_top(top_id, diff): + user = current_user() + top = TOP.query.filter_by(id=top_id).first() + if top is None or not top.protocol.protocoltype.has_modify_right(user): + flash("Invalider TOP oder keine Berechtigung.", "alert-error") + return redirect(request.args.get("next") or url_for("index")) + try: + top.number += int(diff) + db.session.commit() + except ValueError: + flash("Die angegebene Differenz ist keine Zahl.", "alert-error") + return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id)) + + @app.route("/todos/list") def list_todos(): is_logged_in = check_login() diff --git a/shared.py b/shared.py index b58cf3219bd11fe858f7bf6931980dae69648e6c..9761b10c54092fa978f5ea3a49801aeb9963941f 100644 --- a/shared.py +++ b/shared.py @@ -111,3 +111,9 @@ def group_required(function, group): return redirect(request.args.get("next") or url_for("index")) return decorated_function +EMPTY_ETHERPAD = """Welcome to Etherpad! + +This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents! + +Get involved with Etherpad at http://etherpad.org +""" diff --git a/templates/protocol-show.html b/templates/protocol-show.html index d9bde019c5c4de308d263a7e2df3c1f6f59540e5..1bb9db94ff79269fbf8784d4d5a7845109d5b50a 100644 --- a/templates/protocol-show.html +++ b/templates/protocol-show.html @@ -2,10 +2,16 @@ {% from "macros.html" import render_table, render_form %} {% block title %}Protokoll{% endblock %} +{% set loggedin = check_login() %} +{% set user = current_user() %} +{% set has_public_view_right = protocol.protocoltype.has_public_view_right(user) %} +{% set has_private_view_right = protocol.protocoltype.has_private_view_right(user) %} +{% set has_modify_right = protocol.protocoltype.has_modify_right(user) %} + {% block content %} <div class="container"> <div class="btn-group"> - {% if protocol.protocoltype.has_modify_right(current_user()) %} + {% if has_modify_right %} <a class="btn {% if protocol.source is none %}btn-primary{% else %}btn-default{% endif %}" href="{{url_for("etherpull_protocol", protocol_id=protocol.id)}}">From etherpad</a> {% if protocol.source is not none %} <a class="btn btn-primary" href="{{url_for("get_protocol_source", protocol_id=protocol.id)}}">Download Quelltext</a> @@ -39,26 +45,8 @@ {% if protocol.date is not none %}<p><strong>Geplant:</strong> {{protocol.date|datify_long}}{% endif %}</p> {% endif %} - <h3>Tagesordnung</h3> - <ul> - {% if not protocol.has_nonplanned_tops() %} - {% for default_top in protocol.protocoltype.default_tops %} - {% if not default_top.is_at_end() %} - <li>{{default_top.name}}</li> - {% endif %} - {% endfor %} - {% endif %} - {% for top in protocol.tops %} - <li>{{top.name}}</li> - {% endfor %} - {% if not protocol.has_nonplanned_tops() %} - {% for default_top in protocol.protocoltype.default_tops %} - {% if default_top.is_at_end() %} - <li>{{default_top.name}}</li> - {% endif %} - {% endfor %} - {% endif %} - </ul> + <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" %} {% if protocol.is_done() %} <h3>Beschlüsse</h3> @@ -82,7 +70,7 @@ {% endif %} </ul> {% endif %} - {% if protocol.protocoltype.has_modify_right(current_user()) %} + {% if has_modify_right %} {% if protocol.errors|length > 0 %} {{render_table(errors_table)}} {% endif %} @@ -90,9 +78,11 @@ {% if protocol.documents|length > 0 %} {{render_table(documents_table)}} {% else %} - <h3>Hochladen</h3> + {% if has_modify_right %} + <h3>Hochladen</h3> + {% endif %} {% endif %} - {% if protocol.protocoltype.has_modify_right(current_user()) %} + {% if has_modify_right %} {{render_form(source_upload_form, action_url=url_for("upload_source_to_known_protocol", protocol_id=protocol.id, next=url_for("show_protocol", protocol_id=protocol.id)), action_text="Hochladen", enctype="multipart/form-data")}} {{render_form(document_upload_form, action_url=url_for("upload_document", protocol_id=protocol.id, next=url_for("show_protocol", protocol_id=protocol.id)), action_text="Hochladen", enctype="multipart/form-data")}} {% endif %} diff --git a/templates/protocol-tops-include.html b/templates/protocol-tops-include.html new file mode 100644 index 0000000000000000000000000000000000000000..ca9a0a63c76463789e1f57e0ee5fb7de27911b04 --- /dev/null +++ b/templates/protocol-tops-include.html @@ -0,0 +1,30 @@ +<ul> + {% if not protocol.has_nonplanned_tops() %} + {% for default_top in protocol.protocoltype.default_tops %} + {% if not default_top.is_at_end() %} + <li>{{default_top.name}}</li> + {% endif %} + {% endfor %} + {% endif %} + {% for top in protocol.tops %} + <li> + {{top.name}} + {% if has_private_view_right %} + ({{top.number}}) + {% endif %} + {% if has_modify_right %} + <a href="{{url_for('edit_top', top_id=top.id)}}">Ändern</a> + <a href="{{url_for('move_top', top_id=top.id, diff=1)}}">Runter</a> + <a href="{{url_for('move_top', top_id=top.id, diff=-1)}}">Hoch</a> + <a href="{{url_for('delete_top', top_id=top.id)}}" onclick="return confirm('Bist du dir sicher, dass du den TOP {{top.name}} löschen möchtest?');">Löschen</a> + {% endif %} + </li> + {% endfor %} + {% if not protocol.has_nonplanned_tops() %} + {% for default_top in protocol.protocoltype.default_tops %} + {% if default_top.is_at_end() %} + <li>{{default_top.name}}</li> + {% endif %} + {% endfor %} + {% endif %} +</ul> diff --git a/templates/top-edit.html b/templates/top-edit.html new file mode 100644 index 0000000000000000000000000000000000000000..31e56b9afd9ffc1435d9f52bd0a523fdd4932da8 --- /dev/null +++ b/templates/top-edit.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}TOP bearbeiten{% endblock %} + +{% block content %} +<div class="container"> + {{render_form(form, action_url=url_for("edit_top", top_id=top.id, next=request.args.get("next") or url_for("show_protocol", protocol_id=top.protocol.id)), action_text="Ändern")}} +</div> +{% endblock %} diff --git a/templates/top-new.html b/templates/top-new.html new file mode 100644 index 0000000000000000000000000000000000000000..3b604d0fb68d5b041a6aed4b5a911acb4fa820bd --- /dev/null +++ b/templates/top-new.html @@ -0,0 +1,24 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}TOP hinzufügen{% endblock %} + +{% set loggedin = check_login() %} +{% set user = current_user() %} +{% set has_public_view_right = protocol.protocoltype.has_public_view_right(user) %} +{% set has_private_view_right = protocol.protocoltype.has_private_view_right(user) %} +{% set has_modify_right = protocol.protocoltype.has_modify_right(user) %} + +{% block content %} +<div class="container"> + <div class="row"> + <div id="left-column" class="col-lg-6"> + <h3>Tagesordnung</h3> + {% include "protocol-tops-include.html" %} + </div> + <div id="right-column" class="col-lg-6"> + <h3>TOP hinzufügen</h3> + {{render_form(form, action_url=url_for("new_top", protocol_id=protocol.id, next=url_for("show_protocol", protocol_id=protocol.id)), action_text="Anlegen")}} + </div> + </div> +</div> +{% endblock %} diff --git a/utils.py b/utils.py index b1cd9f733acd060f659a27390f3059b5a70f23ec..47738f34704e7782ae55291ae7548adefc385a00 100644 --- a/utils.py +++ b/utils.py @@ -90,3 +90,13 @@ class MailManager: return True mail_manager = MailManager(config) + +def get_first_unused_int(numbers): + positive_numbers = [number for number in numbers if number >= 0] + if len(positive_numbers) == 0: + return 0 + highest = max(positive_numbers) + for given, linear in zip(positive_numbers, range(highest+1)): + if linear < given: + return linear + return highest + 1 diff --git a/views/forms.py b/views/forms.py index 5474a328e298bf63778bec3e9ad5c34b1f79c008..a8303bf4448c4ac268cc7ee3df7c9b0a93ec9900 100644 --- a/views/forms.py +++ b/views/forms.py @@ -57,3 +57,7 @@ class ProtocolForm(FlaskForm): participants = StringField("Anwesende") done = BooleanField("Fertig") +class TopForm(FlaskForm): + name = StringField("TOP", validators=[InputRequired("Du musst den Namen des TOPs angeben.")]) + number = IntegerField("Sortierung", validators=[InputRequired("Du musst eine Sortierung in der Reihenfolge angebene.")]) +