diff --git a/check_routes.py b/check_routes.py new file mode 100644 index 0000000000000000000000000000000000000000..5a577c037fb3c0078cfce2fb422e5abf7558ca0c --- /dev/null +++ b/check_routes.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +import regex as re +import os +import sys + +ROUTE_PATTERN = r'@(?:[[:alpha:]])+\.route\(\"(?<url>[^"]+)"[^)]*\)\s*(?:@[[:alpha:]_()., ]+\s*)*def\s+(?<name>[[:alpha:]][[:alnum:]_]*)\((?<params>[[:alnum:], ]*)\):' +quote_group = "[\"']" +URL_FOR_PATTERN = r'url_for\({quotes}(?<name>[[:alpha:]][[:alnum:]_]*){quotes}'.format(quotes=quote_group) + +ROOT_DIR = "." +ENDINGS = [".py", ".html", ".txt"] +MAX_DEPTH = 2 + +def list_dir(dir, level=0): + if level >= MAX_DEPTH: + return + for file in os.listdir(dir): + path = os.path.join(dir, file) + if os.path.isfile(path): + if file == sys.argv[0]: + continue + for ending in ENDINGS: + if file.endswith(ending): + yield path + elif os.path.isdir(path): + yield from list_dir(path, level+1) + +class Route: + def __init__(self, file, name, parameters): + self.file = file + self.name = name + self.parameters = parameters + + def __repr__(self): + return "Route({file}, {name}, {parameters})".format( + file=self.file, name=self.name, parameters=self.parameters) + + def get_parameter_set(self): + return {parameter.name for parameter in self.parameters} + +class Parameter: + def __init__(self, name, type=None): + self.name = name + self.type = type + + def __repr__(self): + return "Parameter({name}, {type})".format(name=self.name, type=self.type) + + @staticmethod + def from_string(text): + if ":" in text: + type, name = text.split(":", 1) + return Parameter(name, type) + return Parameter(text) + +def split_url_parameters(url): + params = [] + current_param = None + for char in url: + if current_param is None: + if char == "<": + current_param = "" + else: + if char == ">": + params.append(Parameter.from_string(current_param)) + current_param = None + else: + current_param += char + return params + +def split_function_parameters(parameters): + return list(map(str.strip, parameters.split(","))) + +def read_url_for_parameters(content): + params = [] + bracket_level = 1 + current_param = None + for char in content: + if bracket_level == 0: + if current_param is not None: + params.append(current_param.split("=")[0].strip()) + return params + if char == ",": + if current_param is not None: + params.append(current_param.split("=")[0].strip()) + current_param = "" + else: + if current_param is not None: + current_param += char + if char == "(": + bracket_level += 1 + elif char == ")": + bracket_level -= 1 + +class UrlFor: + def __init__(self, file, name, parameters): + self.file = file + self.name = name + self.parameters = parameters + + def __repr__(self): + return "UrlFor(file={file}, name={name}, parameters={parameters})".format( + file=self.file, name=self.name, parameters=self.parameters) + +routes = {} +url_fors = [] +for file in list_dir(ROOT_DIR): + with open(file, "r") as infile: + content = infile.read() + for match in re.finditer(ROUTE_PATTERN, content): + name = match.group("name") + function_parameters = split_function_parameters(match.group("params")) + url_parameters = split_url_parameters(match.group("url")) + routes[name] = Route(file, name, url_parameters) + for match in re.finditer(URL_FOR_PATTERN, content): + name = match.group("name") + begin, end = match.span() + parameters = read_url_for_parameters(content[end:]) + url_fors.append(UrlFor(file=file, name=name, parameters=parameters)) + +for url_for in url_fors: + if url_for.name not in routes: + print("Missing route '{}' (for url_for in '{}')".format(url_for.name, url_for.file)) + continue + route = routes[url_for.name] + route_parameters = route.get_parameter_set() + url_parameters = set(url_for.parameters) + if len(route_parameters ^ url_parameters) > 0: + print("Parameters not matching for '{}' in '{}:'".format(url_for.name, url_for.file)) + only_route = route_parameters - url_parameters + only_url = url_parameters - route_parameters + if len(only_route) > 0: + print("Only in route: {}".format(only_route)) + if len(only_url) > 0: + print("Only in url: {}".format(only_url)) diff --git a/server.py b/server.py index 7be156bb55a40f4e470108261c76c6a22b56f365..70a88f58434d8cb5486afca56656b68729bc2a23 100755 --- a/server.py +++ b/server.py @@ -350,7 +350,7 @@ def list_protocols(): protocoltype = None protocoltype_id = None try: - protocoltype_id = int(request.args.get("protocoltype")) + protocoltype_id = int(request.args.get("protocoltype_id")) except (ValueError, TypeError): pass search_term = request.args.get("search") @@ -456,7 +456,7 @@ def new_protocol(): db.session.commit() tasks.push_tops_to_calendar(protocol) return redirect(request.args.get("next") or url_for("show_protocol", protocol_id=protocol.id)) - type_id = request.args.get("type_id") + type_id = request.args.get("protocoltype_id") if type_id is not None: form.protocoltype.data = type_id upload_form.protocoltype.data = type_id @@ -731,7 +731,7 @@ def list_todos(): protocoltype = None protocoltype_id = None try: - protocoltype_id = int(request.args.get("protocoltype")) + protocoltype_id = int(request.args.get("protocoltype_id")) except (ValueError, TypeError): pass search_term = request.args.get("search") @@ -774,7 +774,7 @@ def list_todos(): @login_required def new_todo(): user = current_user() - protocoltype_id = optional_int_arg("type_id") + protocoltype_id = optional_int_arg("protocoltype_id") protocol_id = optional_int_arg("protocol_id") protocoltype = ProtocolType.query.filter_by(id=protocoltype_id).first() protocol = Protocol.query.filter_by(id=protocol_id).first() @@ -819,7 +819,7 @@ def edit_todo(todo): if form.validate_on_submit(): form.populate_obj(todo) db.session.commit() - return redirect(request.args.get("next") or url_for("list_todos", protocoltype=todo.protocoltype.id)) + return redirect(request.args.get("next") or url_for("list_todos", protocoltype_id=todo.protocoltype.id)) return render_template("todo-edit.html", form=form, todo=todo) @app.route("/todo/show/<int:todo_id>") @@ -839,7 +839,7 @@ def delete_todo(todo): db.session.delete(todo) db.session.commit() flash("Todo gelöscht.", "alert-success") - return redirect(request.args.get("next") or url_for("list_todos", protocoltype=type_id)) + return redirect(request.args.get("next") or url_for("list_todos", protocoltype_id=type_id)) @app.route("/todo/merge", methods=["GET", "POST"]) @login_required @@ -861,7 +861,7 @@ def merge_todos(): 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 redirect(request.args.get("next") or url_for("list_todos")) return render_template("todos-merge.html", form=form, next_url=request.args.get("next")) @app.route("/decisions/list") diff --git a/templates/default-top-edit.html b/templates/default-top-edit.html index fa90f218726998b7ca33f6e44b12e3f0351bf163..6ead1bb4d078987aaa41ea2318b7f57cc2996fc4 100644 --- a/templates/default-top-edit.html +++ b/templates/default-top-edit.html @@ -4,6 +4,6 @@ {% block content %} <div class="container"> - {{render_form(form, action_url=url_for("edit_default_top", type_id=protocoltype.id, defaulttop_id=defaulttop.id, next=url_for("show_type", protocoltype_id=protocoltype.id)), action_text="Ändern")}} + {{render_form(form, action_url=url_for("edit_default_top", protocoltype_id=protocoltype.id, defaulttop_id=defaulttop.id, next=url_for("show_type", protocoltype_id=protocoltype.id)), action_text="Ändern")}} </div> {% endblock %} diff --git a/templates/default-top-new.html b/templates/default-top-new.html index be99948f73f23dbf276eacc3f350a0bfa4f82686..147712c68b3f9fe2e7ada6b8ab4e64fa60efc2f1 100644 --- a/templates/default-top-new.html +++ b/templates/default-top-new.html @@ -4,6 +4,6 @@ {% block content %} <div class="container"> - {{render_form(form, action_url=url_for("new_default_top", type_id=protocoltype.id, next=url_for("show_type", protocoltype_id=protocoltype.id)), action_text="Anlegen")}} + {{render_form(form, action_url=url_for("new_default_top", protocoltype_id=protocoltype.id, next=url_for("show_type", protocoltype_id=protocoltype.id)), action_text="Anlegen")}} </div> {% endblock %} diff --git a/templates/defaultmeta-edit.html b/templates/defaultmeta-edit.html index 14385be6aac943e80b5348f15709adeb65976502..52cc5131ee38c6e3cff15c7df88ba17b4e4aea5f 100644 --- a/templates/defaultmeta-edit.html +++ b/templates/defaultmeta-edit.html @@ -4,6 +4,6 @@ {% block content %} <div class="container"> - {{render_form(form, action_url=url_for("edit_defaultmeta", meta_id=meta.id, next=url_for("show_type", protocoltype_id=meta.protocoltype.id)), action_text="Ändern")}} + {{render_form(form, action_url=url_for("edit_defaultmeta", defaultmeta_id=meta.id, next=url_for("show_type", protocoltype_id=meta.protocoltype.id)), action_text="Ändern")}} </div> {% endblock %} diff --git a/templates/reminder-edit.html b/templates/reminder-edit.html index 08fcb2f6d8b64f6408f2c81eed3f010c3a248951..46913fd0719d766157d4efcce6f4192eb58c17da 100644 --- a/templates/reminder-edit.html +++ b/templates/reminder-edit.html @@ -4,6 +4,6 @@ {% block content %} <div class="container"> - {{render_form(form, action_url=url_for("edit_reminder", protocoltype_id=protocoltype.id, meetingreminder_id=meetingreminder.id, next=url_for("show_type", protocoltype_id=protocoltype.id)), action_text="Ändern")}} + {{render_form(form, action_url=url_for("edit_reminder", meetingreminder_id=meetingreminder.id, next=url_for("show_type", protocoltype_id=protocoltype.id)), action_text="Ändern")}} </div> {% endblock %} diff --git a/views/tables.py b/views/tables.py index b159f5d0adb1177545693edfd094521a66ef22f7..9d40aa02b85a4299fb8d134a3d5efd50f69a710d 100644 --- a/views/tables.py +++ b/views/tables.py @@ -124,7 +124,7 @@ class ProtocolTypesTable(Table): has_modify_right = protocoltype.has_modify_right(user) protocoltype_link = url_for("show_type", protocoltype_id=protocoltype.id) protocol_link = url_for("show_protocol", protocol_id=protocol.id) - new_protocol_link = url_for("new_protocol", type_id=protocoltype.id) + new_protocol_link = url_for("new_protocol", protocoltype_id=protocoltype.id) mobile_name = "{} ({})".format(protocoltype.name, protocoltype.short_name) mobile_links = [Table.link(protocol_link, protocol.get_identifier())] if has_modify_right: @@ -221,10 +221,10 @@ class DefaultTOPsTable(Table): top.name, top.number, Table.concat([ - Table.link(url_for("move_default_top", protocoltype_id=self.protocoltype.id, defaulttop_id=top.id, diff=1), "Runter"), - Table.link(url_for("move_default_top", protocoltype_id=self.protocoltype.id, defaulttop_id=top.id, diff=-1), "Hoch"), + Table.link(url_for("move_default_top", defaulttop_id=top.id, diff=1), "Runter"), + Table.link(url_for("move_default_top", defaulttop_id=top.id, diff=-1), "Hoch"), Table.link(url_for("edit_default_top", protocoltype_id=self.protocoltype.id, defaulttop_id=top.id), "Ändern"), - Table.link(url_for("delete_default_top", protocoltype_id=self.protocoltype.id, defaulttop_id=top.id), "Löschen", confirm="Bist du dir sicher, dass du den Standard-TOP {} löschen willst?".format(top.name)) + Table.link(url_for("delete_default_top", defaulttop_id=top.id), "Löschen", confirm="Bist du dir sicher, dass du den Standard-TOP {} löschen willst?".format(top.name)) ]) ] @@ -244,8 +244,8 @@ class MeetingRemindersTable(Table): reminder.additional_text or "" ] action_links = [ - Table.link(url_for("edit_reminder", protocoltype_id=self.protocoltype.id, meetingreminder_id=reminder.id), "Ändern"), - Table.link(url_for("delete_reminder", protocoltype_id=self.protocoltype.id, meetingreminder_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)) + Table.link(url_for("edit_reminder", meetingreminder_id=reminder.id), "Ändern"), + Table.link(url_for("delete_reminder", meetingreminder_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)) ] action_part = [Table.concat(action_links)] return general_part + action_part @@ -317,7 +317,7 @@ class TodosTable(Table): todo.get_state(), Table.link(url_for("show_protocol", protocol_id=protocol.id), protocol.get_identifier()) if protocol is not None - else Table.link(url_for("list_protocols", protocoltype=todo.protocoltype.id), todo.protocoltype.short_name), + else Table.link(url_for("list_protocols", protocoltype_id=todo.protocoltype.id), todo.protocoltype.short_name), todo.who, todo.description, ] @@ -437,7 +437,7 @@ class DefaultMetasTable(Table): ] links = [ Table.link(url_for("edit_defaultmeta", defaultmeta_id=meta.id), "Ändern"), - Table.link(url_for("delete_defaultmeta", defaultmeta_id=meta.id, confirm="Bist du dir sicher, dass du das Metadatenfeld {} löschen willst?".format(meta.name)), "Löschen") + Table.link(url_for("delete_defaultmeta", defaultmeta_id=meta.id), confirm="Bist du dir sicher, dass du das Metadatenfeld {} löschen willst?".format(meta.name), "Löschen") ] link_part = [Table.concat(links)] return general_part + link_part