From dabb01585d9984b30eb513b9e73005fbbd1808f2 Mon Sep 17 00:00:00 2001 From: Robin Sonnabend <robin@fsmpi.rwth-aachen.de> Date: Fri, 24 Feb 2017 06:09:27 +0100 Subject: [PATCH] Upload source --- migrations/versions/aebae2c4523d_.py | 28 +++++++++++ models/database.py | 36 ++++++++++++-- server.py | 71 +++++++++++++++++++++++++-- tasks.py | 9 ++-- templates/protocol-new.html | 11 ++++- templates/protocol-show.html | 72 +++++++++++++++++----------- templates/protocol-update.html | 26 ++++++++++ views/forms.py | 13 ++++- 8 files changed, 226 insertions(+), 40 deletions(-) create mode 100644 migrations/versions/aebae2c4523d_.py create mode 100644 templates/protocol-update.html diff --git a/migrations/versions/aebae2c4523d_.py b/migrations/versions/aebae2c4523d_.py new file mode 100644 index 0000000..e70a55c --- /dev/null +++ b/migrations/versions/aebae2c4523d_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: aebae2c4523d +Revises: b114754024fb +Create Date: 2017-02-24 05:58:37.240601 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'aebae2c4523d' +down_revision = 'b114754024fb' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('documents_filename_key', 'documents', type_='unique') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint('documents_filename_key', 'documents', ['filename']) + # ### end Alembic commands ### diff --git a/models/database.py b/models/database.py index 9ee5694..067378f 100644 --- a/models/database.py +++ b/models/database.py @@ -103,8 +103,11 @@ class Protocol(db.Model): def fill_from_remarks(self, remarks): new_date = datetime.strptime(remarks["Datum"].value, "%d.%m.%Y").date() - if new_date != self.date: - raise DateNotMatchingException(original_date=self.date, protocol_date=new_date) + if self.date is not None: + if new_date != self.date: + raise DateNotMatchingException(original_date=self.date, protocol_date=new_date) + else: + self.date = new_date self.start_time = datetime.strptime(remarks["Beginn"].value, "%H:%M").time() self.end_time = datetime.strptime(remarks["Ende"].value, "%H:%M").time() self.author = remarks["Autor"].value @@ -115,11 +118,16 @@ class Protocol(db.Model): return self.done def get_identifier(self): + if self.date is None: + return None return "{}-{}".format( self.protocoltype.short_name.lower(), self.date.strftime("%y-%m-%d")) def get_etherpad_link(self): + identifier = self.get_identifier() + if identifier is None: + return "" return config.ETHERPAD_URL + self.get_identifier() def get_etherpad_source_link(self): @@ -131,6 +139,28 @@ class Protocol(db.Model): def get_originating_todos(self): return [todo for todo in self.todos if self == todo.get_first_protocol()] + def has_compiled_document(self): + candidates = [ + document for document in self.documents + if document.is_compiled + ] + return len(candidates) > 0 + + def get_compiled_document(self, private=None): + candidates = [ + document for document in self.documents + if document.is_compiled + and (private is None or document.is_private == private) + ] + print(candidates) + private_candidates = [document for document in candidates if document.is_private] + public_candidates = [document for document in candidates if not document.is_private] + if len(private_candidates) > 0: + return private_candidates[0] + elif len(public_candidates) > 0: + return public_candidates[0] + return None + def delete_orphan_todos(self): orphan_todos = [ todo for todo in self.todos @@ -187,7 +217,7 @@ class Document(db.Model): id = db.Column(db.Integer, primary_key=True) protocol_id = db.Column(db.Integer, db.ForeignKey("protocols.id")) name = db.Column(db.String) - filename = db.Column(db.String, unique=True) + filename = db.Column(db.String) is_compiled = db.Column(db.Boolean) is_private = db.Column(db.Boolean) diff --git a/server.py b/server.py index dfba0aa..2548046 100755 --- a/server.py +++ b/server.py @@ -17,7 +17,7 @@ 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 models.database import ProtocolType, Protocol, DefaultTOP, TOP, Document, Todo, Decision, MeetingReminder, Error -from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm +from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable app = Flask(__name__) @@ -293,6 +293,7 @@ def new_protocol(): if protocoltype.has_modify_right(user) ] form = NewProtocolForm(protocoltypes) + upload_form = NewProtocolSourceUploadForm(protocoltypes) if form.validate_on_submit(): protocoltype = ProtocolType.query.filter_by(id=form.protocoltype.data).first() if protocoltype is None or not protocoltype.has_modify_right(user): @@ -305,7 +306,7 @@ def new_protocol(): type_id = request.args.get("type_id") if type_id is not None: form.protocoltype.data = type_id - return render_template("protocol-new.html", form=form, protocoltypes=protocoltypes) + return render_template("protocol-new.html", form=form, upload_form=upload_form, protocoltypes=protocoltypes) @app.route("/protocol/show/<int:protocol_id>") def show_protocol(protocol_id): @@ -322,7 +323,8 @@ def show_protocol(protocol_id): ] documents_table = DocumentsTable(visible_documents) document_upload_form = DocumentUploadForm() - return render_template("protocol-show.html", protocol=protocol, errors_table=errors_table, documents_table=documents_table, document_upload_form=document_upload_form) + source_upload_form = KnownProtocolSourceUploadForm() + return render_template("protocol-show.html", protocol=protocol, errors_table=errors_table, documents_table=documents_table, document_upload_form=document_upload_form, source_upload_form=source_upload_form) @app.route("/protocol/delete/<int:protocol_id>") @login_required @@ -339,6 +341,7 @@ def delete_protocol(protocol_id): return redirect(request.args.get("next") or url_for("list_protocols")) @app.route("/protocol/etherpull/<int:protocol_id>") +@login_required def etherpull_protocol(protocol_id): user = current_user() protocol = Protocol.query.filter_by(id=protocol_id).first() @@ -353,6 +356,64 @@ def etherpull_protocol(protocol_id): flash("Das Protokoll wird kompiliert.", "alert-success") return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) +@app.route("/protocol/upload/known/<int:protocol_id>", methods=["POST"]) +@login_required +def upload_source_to_known_protocol(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 = KnownProtocolSourceUploadForm() + if form.validate_on_submit(): + if form.source.data is None: + flash("Es wurde keine Datei ausgewählt.", "alert-error") + else: + file = form.source.data + if file.filename == "": + flash("Es wurde keine Datei ausgewählt.", "alert-error") + else: + # todo: Prüfen, ob es Text ist? + source = file.stream.read().decode("utf-8") + protocol.source = source + db.session.commit() + tasks.parse_protocol(protocol) + flash("Das Protokoll wird kompiliert.", "alert-success") + return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) + +@app.route("/protocol/upload/new/", methods=["POST"]) +@login_required +def upload_new_protocol(): + user = current_user() + available_types = [ + protocoltype for protocoltype in ProtocolType.query.all() + if protocoltype.has_modify_right(user) + ] + form = NewProtocolSourceUploadForm(protocoltypes=available_types) + if form.validate_on_submit(): + if form.source.data is None: + flash("Es wurde keine Datei ausgewählt.", "alert-error") + else: + print(form.source.data) + file = form.source.data + if file.filename == "": + flash("Es wurde keine Datei ausgewählt.", "alert-error") + else: + source = file.stream.read().decode("utf-8") + protocoltype = ProtocolType.query.filter_by(id=form.protocoltype.data).first() + if protocoltype is None or not protocoltype.has_modify_right(user): + flash("Invalider Protokolltyp oder keine Rechte.", "alert-error") + else: + protocol = Protocol(protocoltype.id, None, source) + db.session.add(protocol) + db.session.commit() + 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 def get_protocol_source(protocol_id): @@ -372,8 +433,8 @@ def update_protocol(protocol_id): 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")) - # TODO: render form to upload a new version - + upload_form = KnownProtocolSourceUploadForm() + return render_template("protocol-update.html", upload_form=upload_form, protocol=protocol) @app.route("/todos/list") def list_todos(): diff --git a/tasks.py b/tasks.py index 70151d6..8a97791 100644 --- a/tasks.py +++ b/tasks.py @@ -172,9 +172,12 @@ def parse_protocol_async(protocol_id, encoded_kwargs): db.session.add(top) db.session.commit() - for show_private in [True, False]: - latex_source = texenv.get_template("protocol.tex").render(protocol=protocol, tree=tree, show_private=show_private) - compile(latex_source, protocol, show_private=show_private) + latex_source_private = texenv.get_template("protocol.tex").render(protocol=protocol, tree=tree, show_private=True) + latex_source_public = texenv.get_template("protocol.tex").render(protocol=protocol, tree=tree, show_private=False) + compile(latex_source_public, protocol, show_private=False) + if latex_source_private != latex_source_public: + compile(latex_source_private, protocol, show_private=True) + # TODO compare something that may actually be equal protocol.done = True db.session.commit() diff --git a/templates/protocol-new.html b/templates/protocol-new.html index 56d303e..c6110fd 100644 --- a/templates/protocol-new.html +++ b/templates/protocol-new.html @@ -4,6 +4,15 @@ {% block content %} <div class="container"> - {{render_form(form, action_url=url_for("new_protocol"), action_text="Anlegen")}} + <div class="row"> + <div id="left-column" class="col-lg-6"> + <h3>Neues Protokoll</h3> + {{render_form(form, action_url=url_for("new_protocol"), action_text="Anlegen")}} + </div> + <div id="left-column" class="col-lg-6"> + <h3>Neues Protokoll hochladen</h3> + {{render_form(upload_form, action_url=url_for("upload_new_protocol", fail=url_for("new_protocol")), action_text="Hochladen", enctype="multipart/form-data")}} + </div> + </div> </div> {% endblock %} diff --git a/templates/protocol-show.html b/templates/protocol-show.html index 6d1b0f2..2cd457c 100644 --- a/templates/protocol-show.html +++ b/templates/protocol-show.html @@ -5,25 +5,38 @@ {% block content %} <div class="container"> <div class="btn-group"> - <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> - {% endif %} - {% if protocol.is_done() %} - <a class="btn btn-success" href="{{url_for("update_protocol", protocol_id=protocol.id)}}">Protokoll editieren</a> + {% if protocol.protocoltype.has_modify_right(current_user()) %} + <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> + {% endif %} + {% if protocol.is_done() %} + <a class="btn btn-success" href="{{url_for("update_protocol", protocol_id=protocol.id)}}">Protokoll editieren</a> + {% endif %} + <a class="btn btn-default" href="{{protocol.get_etherpad_link()}}" target="_blank">Etherpad</a> + <a class="btn btn-default" href="{{url_for("show_type", type_id=protocol.protocoltype.id)}}">Typ</a> + <a class="btn btn-danger" href="{{url_for("delete_protocol", protocol_id=protocol.id)}}" onclick="return confirm('Bist du dir sicher, dass du das Protokoll {{protocol.get_identifier()}} löschen möchtest?');">Löschen</a> {% endif %} - <a class="btn btn-default" href="{{protocol.get_etherpad_link()}}" target="_blank">Etherpad</a> - <a class="btn btn-default" href="{{url_for("show_type", type_id=protocol.protocoltype.id)}}">Typ</a> </div> <div class="row"> <div id="left-column" class="col-lg-6"> <h2>Protokoll: {{protocol.protocoltype.name}} vom {{protocol.date|datify}}</h2> {% if protocol.is_done() %} - <p><strong>Datum:</strong> {{protocol.date|datify_long}}</p> - <p><strong>Zeit:</strong> von {{protocol.start_time|timify}} bis {{protocol.end_time|timify}}</p> - <p><strong>Ort:</strong> {{protocol.location}}</p> - <p><strong>Protokollant:</strong> {{protocol.author}}</p> - <p><strong>Anwesende:</strong> {{protocol.participants}}</p> + {% if protocol.date is not none %} + <p><strong>Datum:</strong> {{protocol.date|datify_long}}</p> + {% endif %} + {% 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>Protokollant:</strong> {{protocol.author}}</p> + {% endif %} + {% if protocol.participants is not none %} + <p><strong>Anwesende:</strong> {{protocol.participants}}</p> + {% endif %} {% else %} <p><strong>Geplant:</strong> {{protocol.date|datify_long}}</p> {% endif %} @@ -60,25 +73,30 @@ </div> <div id="right-column" class="col-lg-6"> {% if protocol.is_done() %} - <h3>Todos dieser Sitzung <a href="{{url_for("list_todos")}}">Aktuelle Todos</a></h3> - <ul> - {% if protocol.get_originating_todos()|length > 0 %} - {% for todo in protocol.get_originating_todos() %} - <li>{{todo.render_html()|safe}}</li> - {% endfor %} - {% else %} - <li>Keine Todos</li> - {% endif %} - </ul> + <h3>Todos dieser Sitzung <a href="{{url_for("list_todos")}}">Aktuelle Todos</a></h3> + <ul> + {% if protocol.get_originating_todos()|length > 0 %} + {% for todo in protocol.get_originating_todos() %} + <li>{{todo.render_html()|safe}}</li> + {% endfor %} + {% else %} + <li>Keine Todos</li> + {% endif %} + </ul> {% endif %} - {% if protocol.errors|length > 0 %} - {{render_table(errors_table)}} + {% if protocol.protocoltype.has_modify_right(current_user()) %} + {% if protocol.errors|length > 0 %} + {{render_table(errors_table)}} + {% endif %} {% endif %} {% if protocol.documents|length > 0 %} {{render_table(documents_table)}} + {% else %} + <h3>Hochladen</h3> {% endif %} - {% if protocol.is_done() %} - {{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")}} + {% if protocol.protocoltype.has_modify_right(current_user()) %} + {{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 %} </div> </div> diff --git a/templates/protocol-update.html b/templates/protocol-update.html new file mode 100644 index 0000000..fec9031 --- /dev/null +++ b/templates/protocol-update.html @@ -0,0 +1,26 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}Protokoll ändern{% endblock %} + +{% block content %} +<div class="container"> + <div class="row"> + <div id="left-column" class="col-lg-6"> + <h3>Protokoll herunterladen</h3> + <div class="btn-group"> + {% if protocol.source is not none %} + <a class="btn btn-primary" href="{{url_for("get_protocol_source", protocol_id=protocol.id)}}">Download Quelltext</a> + {% endif %} + <a class="btn btn-default" href="{{url_for("show_protocol", protocol_id=protocol.id)}}">Zurück</a> + {% if protocol.has_compiled_document() %} + <a class="btn btn-success" href="{{url_for("download_document", document_id=protocol.get_compiled_document().id)}}">Download PDF</a> + {% endif %} + </div> + </div> + <div id="right-column" class="col-lg-6"> + <h3>Neue Version hochladen</h3> + {{render_form(upload_form, action_url=url_for("upload_source_to_known_protocol", protocol_id=protocol.id), action_text="Hochladen", enctype="multipart/form-data")}} + </div> + </div> +</div> +{% endblock %} diff --git a/views/forms.py b/views/forms.py index 48e4fa8..bc7d323 100644 --- a/views/forms.py +++ b/views/forms.py @@ -34,5 +34,16 @@ class NewProtocolForm(FlaskForm): self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes] class DocumentUploadForm(FlaskForm): - document = FileField("Datei", validators=[InputRequired("Du musst eine Datei angeben.")]) + document = FileField("Datei") private = BooleanField("Intern") + +class KnownProtocolSourceUploadForm(FlaskForm): + source = FileField("Quellcode") + +class NewProtocolSourceUploadForm(FlaskForm): + source = FileField("Quellcode") + protocoltype = SelectField("Typ", choices=[], coerce=int) + + def __init__(self, protocoltypes, **kwargs): + super().__init__(**kwargs) + self.protocoltype.choices = [(protocoltype.id, protocoltype.short_name) for protocoltype in protocoltypes] -- GitLab