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