diff --git a/parser.py b/parser.py index fee6709320eeb3b5e21924ac42a959299267881a..74d44763d92d7f510e12b0d71c787292160c2756 100644 --- a/parser.py +++ b/parser.py @@ -158,7 +158,9 @@ class Content(Element): # v3: does not allow braces in the content #PATTERN = r"\s*(?<content>(?:[^\[\];\r\n{}]+)?(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)*);?" # v4: do not allow empty match (require either the first or the second part to be non-empty) - PATTERN = r"\s*(?<content>(?:(?:[^\[\];\r\n{}]+)|(?:[^\[\];\r\n{}]+)?(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)+));?" + #PATTERN = r"\s*(?<content>(?:(?:[^\[\];\r\n{}]+)|(?:[^\[\];\r\n{}]+)?(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)+));?" + # v5: do match emptystring if followed by a semi colon + PATTERN = r"\s*(?<content>(?:[^\[\];\r\n{}]+);?|(?:[^\[\];\r\n{}]+)?(?:\[[^\]\r\n{}]+\][^;\[\]\r\n{}]*)+;?|;)" class Text: def __init__(self, text, linenumber, fork): diff --git a/server.py b/server.py index a84c97bf1661bc3fdcea7b1f1be3a723ac05d6ee..93e350e852004d4f961dca6cc815f98b1f15f01f 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 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, Document, Todo, Decision, MeetingReminder, Error, TodoMail, DecisionDocument, TodoState, Meta, DefaultMeta -from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm +from views.forms import LoginForm, ProtocolTypeForm, DefaultTopForm, MeetingReminderForm, NewProtocolForm, DocumentUploadForm, KnownProtocolSourceUploadForm, NewProtocolSourceUploadForm, ProtocolForm, TopForm, SearchForm, NewProtocolFileUploadForm, NewTodoForm, TodoForm, TodoMailForm, DefaultMetaForm, MetaForm, MergeTodosForm from views.tables import ProtocolsTable, ProtocolTypesTable, ProtocolTypeTable, DefaultTOPsTable, MeetingRemindersTable, ErrorsTable, TodosTable, DocumentsTable, DecisionsTable, TodoTable, ErrorTable, TodoMailsTable, DefaultMetasTable from legacy import import_old_todos, import_old_protocols, import_old_todomails @@ -83,6 +83,40 @@ def import_legacy(): import_old_protocols(content) import_old_todomails(content) +@manager.command +def recompile_all(): + for protocol in sorted(Protocol.query.all(), key=lambda p: p.date): + if protocol.is_done(): + print(protocol.get_identifier()) + tasks.parse_protocol(protocol) + +@manager.command +def merge_todos(): + todo_by_id = {} + todos = Todo.query.all() + for todo in todos: + todo_id = todo.get_id() + if todo_id in todo_by_id: + todo1, todo2 = todo, todo_by_id[todo_id] + print(todo1) + print(todo2) + if todo2.id > todo1.id: + todo2, todo1 = todo1, todo2 + for protocol in todo2.protocols: + if protocol not in todo1.protocols: + todo1.protocols.append(protocol) + todo2.protocols.remove(protocol) + db.session.delete(todo2) + db.session.commit() + todo_by_id[todo_id] = todo1 + else: + todo_by_id[todo_id] = todo + +@manager.command +def runserver(): + app.run() + make_scheduler() + # cause uwsgi currently has a bug def send_file(file_like, cache_timeout, as_attachment, attachment_filename): mimetype, _ = mimetypes.guess_type(attachment_filename) @@ -806,6 +840,29 @@ def delete_todo(todo): flash("Todo gelöscht.", "alert-success") return redirect(request.args.get("next") or url_for("list_todos", protocoltype=type_id)) +@app.route("/todo/merge", methods=["GET", "POST"]) +@login_required +@group_required(config.ADMIN_GROUP) +def merge_todos(): + form = MergeTodosForm(request.args.get("todo_id")) + if form.validate_on_submit(): + todo1 = Todo.query.filter_by(id=form.todo1.data).first() + todo2 = Todo.query.filter_by(id=todo.todo2.data).first() + if todo1 is None or todo2 is None: + flash("Missing todos.", "alert-error") + else: + id1 = todo1.id + id2 = todo2.id + for protocol in todo2.protocols: + if protocol not in todo1.protocols: + todo1.protocols.append(protocol) + todo2.protocols.remove(protocol) + db.session.delete(todo2) + db.session.commit() + flash("Merged todos {} and {}.".format(id1, id2), "alert-success") + return redirect(request.args.get("next") or url_for("show_todos")) + return render_template("todos-merge.html", form=form, next_url=request.args.get("next")) + @app.route("/decisions/list") def list_decisions(): is_logged_In = check_login() @@ -1107,5 +1164,4 @@ def check_and_send_reminders(): tasks.send_reminder(reminder, protocol) if __name__ == "__main__": - make_scheduler() manager.run() diff --git a/tasks.py b/tasks.py index 6d4395b2dbea7ea929894737d11193917987e02d..2832c4d2abf40b35cbcc47e38c4d9a4f74cbe642 100644 --- a/tasks.py +++ b/tasks.py @@ -6,6 +6,7 @@ import shutil import tempfile from datetime import datetime import traceback +from copy import copy from models.database import Document, Protocol, Error, Todo, Decision, TOP, DefaultTOP, MeetingReminder, TodoMail, DecisionDocument, TodoState, OldTodo from models.errors import DateNotMatchingException @@ -89,8 +90,8 @@ def parse_protocol_async_inner(protocol, encoded_kwargs): for error in old_errors: protocol.errors.remove(error) db.session.commit() - if protocol.source is None: - error = protocol.create_error("Parsing", "Protocol source is None", "") + if protocol.source is None or len(protocol.source.strip()) == 0: + error = protocol.create_error("Parsing", "Protocol source is empty", "") db.session.add(error) db.session.commit() return @@ -111,7 +112,7 @@ def parse_protocol_async_inner(protocol, encoded_kwargs): db.session.commit() return remarks = {element.name: element for element in tree.children if isinstance(element, Remark)} - required_fields = KNOWN_KEYS + required_fields = copy(KNOWN_KEYS) for default_meta in protocol.protocoltype.metas: required_fields.append(default_meta.key) if not config.PARSER_LAZY: @@ -151,7 +152,6 @@ def parse_protocol_async_inner(protocol, encoded_kwargs): old_todos = list(protocol.todos) for todo in old_todos: protocol.todos.remove(todo) - print(old_todo_number_map) db.session.commit() tags = tree.get_tags() todo_tags = [tag for tag in tags if tag.name == "todo"] diff --git a/templates/todos-merge.html b/templates/todos-merge.html new file mode 100644 index 0000000000000000000000000000000000000000..5d056c9213f6533a7ec3f950d94fa9116a2562c7 --- /dev/null +++ b/templates/todos-merge.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% from "macros.html" import render_form %} +{% block title %}Todo mergen{% endblock %} + +{% block content %} +<div class="container"> + {{render_form(form, action_url=url_for("merge_todos"), action_text="Mergen")}} +</div> +{% endblock %} diff --git a/utils.py b/utils.py index 88bc0ed0aeff0e707b3a0022580373891cb67bff..d68b72cf4470fb3b25660f7b12548328a5bd08cb 100644 --- a/utils.py +++ b/utils.py @@ -12,6 +12,8 @@ from datetime import datetime, date, timedelta import requests from io import BytesIO import ipaddress +from socket import getfqdn +from uuid import uuid4 import config @@ -81,6 +83,7 @@ class MailManager: msg["From"] = self.from_addr msg["To"] = to_addr msg["Subject"] = subject + msg["Message-ID"] = "{}@{}".format(uuid4(), getfqdn()) msg.attach(MIMEText(content, _charset="utf-8")) if appendix is not None: for name, file_like in appendix: diff --git a/views/forms.py b/views/forms.py index 66437eea8845c5254128f9d203405f79acd8b845..ffc9ce634d4058ccdbd09d736cb85887be7b7ab6 100644 --- a/views/forms.py +++ b/views/forms.py @@ -220,3 +220,11 @@ 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.")]) + +class MergeTodosForm(FlaskForm): + todo1 = IntegerField("todo 1", validators=[InputRequired()]) + todo2 = IntegerField("todo 2", validators=[InputRequired()]) + + def __init__(self, todo=None): + if todo is not None: + self.todo1.data = todo.id