Commit 2306efc1 authored by Jan Čermák's avatar Jan Čermák

URL reversing - closes #2787

Some notes about this code - first one - the speed of this implementation: it takes
approximately 200 ms to reverse 1000 routes in subapplication on Turris router. This
is negligible, only page it can be noticed on is the "about:config". Minor issue is that
it raises/catches same error when the name of the route can not be found and when
some URL arguments are missing (both cases raise RouteBuildError in build method).
parent 8f86867e
......@@ -38,15 +38,15 @@ def render(form, **kwargs):
handlers=handler_map.keys(), **kwargs)
@app.route("/")
@app.route("/", name="config_index")
@login_required
def index():
return template("config/index", handlers=handler_map.keys())
@app.route("/<handler_name:re:.+>/")
@app.route("/<handler_name:re:.+>/", name="config_handler")
@login_required
def config_get(handler_name):
def handler_get(handler_name):
Handler = get_handler(handler_name)
handler = Handler()
return render(handler.form, active_handler_key=handler_name)
......@@ -54,7 +54,7 @@ def config_get(handler_name):
@app.route("/<handler_name:re:.+>/", method="POST")
@login_required
def config_post(handler_name):
def handler_post(handler_name):
Handler = get_handler(handler_name)
handler = Handler(request.POST)
if request.is_xhr:
......
......@@ -7,6 +7,7 @@ import logging
from nuci import client, filters
import os
import sys
from utils.routing import reverse
logger = logging.getLogger("foris")
......@@ -24,14 +25,16 @@ gettext = trans.ugettext
bottle.SimpleTemplate.defaults["user_authenticated"] =\
lambda: bottle.request.environ["beaker.session"].get("user_authenticated")
bottle.SimpleTemplate.defaults["request"] = bottle.request
bottle.SimpleTemplate.defaults["url"] = lambda name, **kwargs: reverse(name, **kwargs)
bottle.SimpleTemplate.defaults["static"] = lambda filename, *args: reverse("static", filename=filename) % args
@bottle.route("/")
@bottle.route("/", name="index")
@bottle.view("index")
def index():
return dict()
@bottle.route("/", method="POST")
@bottle.route("/", method="POST", name="login")
def login():
session = bottle.request.environ["beaker.session"]
if _check_password(bottle.request.POST.get("password")):
......@@ -41,7 +44,7 @@ def login():
bottle.redirect("/")
@bottle.route("/logout")
@bottle.route("/logout", name="logout")
def logout():
session = bottle.request.environ["beaker.session"]
if "user_authenticated" in session:
......
......@@ -3,12 +3,12 @@
<head>
<meta charset="utf-8">
<title>Foris administration interface</title>
<link href="/static/css/screen.css" rel="stylesheet" media="screen">
<link href="{{ static("css/screen.css") }}" rel="stylesheet" media="screen">
<!--[if lt IE 9]>
<script src="/static/js/contrib/html5.js"></script>
<script src="{{ static("js/contrib/html5.js") }}"></script>
<![endif]-->
<script src="/static/js/contrib/jquery.min.js"></script>
<script src="/static/js/wizard.js"></script>
<script src="{{ static("js/contrib/jquery.min.js") }}"></script>
<script src="{{ static("js/wizard.js") }}"></script>
</head>
<body>
<div id="page">
......
......@@ -2,8 +2,8 @@
%rebase _layout **locals()
<div id="config-header">
<h1>{{ _("Settings") }}</h1>
<div class="logo-turris"><img src="/static/img/logo-turris.png"></div>
<a id="logout" href="/logout">{{ _("Log out") }}</a>
<div class="logo-turris"><img src="{{ static("img/logo-turris.png") }}"></div>
<a id="logout" href="{{ url("logout") }}">{{ _("Log out") }}</a>
</div>
......@@ -15,7 +15,7 @@
%if defined("active_handler_key") and handler == active_handler_key:
class="active" \\
%end\\
><a href="/config/{{ handler }}/">{{ handler }}</a></li>
><a href="{{ url("config_handler", handler_name=handler) }}">{{ handler }}</a></li>
%end
</ul>
%end
......
%rebase config/base **locals()
<form id="main-form" class="config-form" action="" method="post" autocomplete="off">
<form id="main-form" class="config-form" action="{{ request.fullpath }}" method="post" autocomplete="off">
<p class="config-description">{{ description }}</p>
%for field in form.active_fields:
%if field.hidden:
......@@ -10,7 +10,7 @@
{{! field.label_tag }}
{{! field.render() }}
%if field.hint:
<img class="field-hint" src="/static/img/icon-help.png" title="{{ field.hint }}">
<img class="field-hint" src="{{ static("img/icon-help.png") }}" title="{{ field.hint }}">
%end
</div>
%end
......
......@@ -3,9 +3,9 @@
<h2>roscesňýk</h2>
%if user_authenticated():
<a href="/logout">odhlásit</a>
<a href="{{ url("logout") }}">odhlásit</a>
%else:
<form action="" method="POST">
<form action="{{ request.fullpath }}" method="POST">
<label for="field-password">{{ _("Password") }}</label>
<input id="field-password" type="password" name="password">
<button class="button" type="submit">{{ _("Log in") }}</button>
......@@ -13,7 +13,7 @@
%end
<ul>
<li><a href="uci/">about:config</a></li>
<li><a href="wizard/">kuzelňik</a></li>
<li><a href="config/">dlaší natavení</a></li>
<li><a href="{{ url("uci_index") }}">about:config</a></li>
<li><a href="{{ url("wizard_index") }}">kuzelňik</a></li>
<li><a href="{{ url("config_index") }}">dlaší natavení</a></li>
</ul>
\ No newline at end of file
%rebase _layout **locals()
Adding/changing value at {{node_path}}:
<form action="" method="post">
<form action="{{ request.fullpath }}" method="post">
{{! form.render() }}
<input type="submit" value="Send">
</form>
\ No newline at end of file
......@@ -2,19 +2,19 @@
%def render_buttons(element):
%if not element.final:
%if element.tag == "section":
<a href="/uci/{{ element.path }}/create?operation=add-option" title="Add option"><i class="icon-add"></i></a>
<a href="/uci/{{ element.path }}/create?operation=add-list" title="Add list"><i class="icon-add-list"></i></a>
<a href="{{ url("uci_create", node=element.path, operation="add-option") }}" title="Add option"><i class="icon-add"></i></a>
<a href="{{ url("uci_create", node=element.path, operation="add-list") }}" title="Add list"><i class="icon-add-list"></i></a>
%else:
<a href="/uci/{{ element.path }}/create?operation=add" title="Add value"><i class="icon-add"></i></a>
<a href="{{ url("uci_create", node=element.path, operation="add") }}" title="Add value"><i class="icon-add"></i></a>
%end
%end
%if element.tag == "option" or element.tag == "value":
<a href="/uci/{{ element.path }}/edit" title="Edit"><i class="icon-edit"></i></a>
<a href="{{ url("uci_edit", node=element.path) }}" title="Edit"><i class="icon-edit"></i></a>
%end
%if element.tag != "config":
<a href="/uci/{{ element.path }}/remove" title="Remove"><i class="icon-remove"></i></a>
<a href="{{ url("uci_remove", node=element.path) }}"><i class="icon-remove"></i></a>
%end
<a href="/uci/{{ element.path }}/debug" title="Debug"><i class="icon-debug"></i></a>
<a href="{{ url("uci_debug", node=element.path) }}" title="Debug"><i class="icon-debug"></i></a>
%end
%def treenode(element, node_path, depth=0):
<li>
......
......@@ -2,12 +2,12 @@
%rebase _layout **locals()
<div id="wizard-header">
<img src="/static/img/logo-turris.png" alt="Project:Turris">
<img src="{{ static("/img/logo-turris.png") }}" alt="Project:Turris">
<span class="stepno"><span class="stepno-current">{{ stepnumber }}</span> / 7</span>
</div>
%if stepname:
<div id="wizard-icon"><img src="/static/img/wizard/step-{{ stepname }}.png"></div>
<div id="wizard-icon"><img src="{{ static("img/wizard/step-%s.png", stepname) }}"></div>
%end
<div id="wizard-content">
%end
......
%rebase wizard/base **locals()
<form id="main-form" class="wizard-form" action="" method="post" autocomplete="off">
<form id="main-form" class="wizard-form" action="{{ request.fullpath }}" method="post" autocomplete="off">
<h1>{{ first_title }}</h1>
<p class="wizard-description">{{ first_description }}</p>
%for field in form.active_fields:
......@@ -11,7 +11,7 @@
{{! field.label_tag }}
{{! field.render() }}
%if field.hint:
<img class="field-hint" src="/static/img/icon-help.png" title="{{ field.hint }}">
<img class="field-hint" src="{{ static("img/icon-help.png") }}" title="{{ field.hint }}">
%end
</div>
%end
......
%rebase wizard/base **locals()
%if form:
<form class="wizard-form" action="" method="post">
<form class="wizard-form" action="{{ request.fullpath }}" method="post">
<h1>{{ first_title }}</h1>
<p>{{ first_description }}</p>
<div class="form-fields">
......@@ -13,7 +13,7 @@
{{! field.label_tag }}
{{! field.render() }}
%if field.hint:
<img class="field-hint" src="/static/img/icon-help.png" title="{{ field.hint }}">
<img class="field-hint" src="{{ static("img/icon-help.png") }}" title="{{ field.hint }}">
%end
</div>
%end
......@@ -32,15 +32,15 @@
<div id="wizard-time">
<h1>{{ _("Time settings") }}</h1>
<div id="time-progress" class="background-progress">
<img src="/static/img/loader.gif" alt="{{ _("Loading...") }}"><br>
<img src="{{ static("img/loader.gif") }}" alt="{{ _("Loading...") }}"><br>
{{ _("Synchronizing with time in the internet.") }}<br>
{{ _("Please wait...") }}
</div>
<div id="time-success">
<img src="/static/img/success.png" alt="{{ _("Done") }}"><br>
<img src="{{ static("img/success.png") }}" alt="{{ _("Done") }}"><br>
<p>{{ _("Time was successfully synchronized, you can move to the next step.") }}</p>
<a class="button-next" href="/wizard/step/4">{{ _("Next") }}</a>
<a class="button-next" href="{{ url("wizard_step", number=4) }}">{{ _("Next") }}</a>
</div>
</div>
......
......@@ -3,23 +3,23 @@
<div id="wizard-updater">
<h1>Test připojení a kontrola aktualizací</h1>
<div id="updater-progress" class="background-progress">
<img src="/static/img/loader.gif" alt="{{ _("Loading...") }}"><br>
<img src="{{ static("img/loader.gif") }}" alt="{{ _("Loading...") }}"><br>
Probíhá kontrola dostupných aktualizací.<br>
Chvilku strpení...<br>
<div id="wizard-updater-status"></div>
</div>
<div id="updater-success">
<img src="/static/img/success.png" alt="{{ _("Done") }}"><br>
<img src="{{ static("img/success.png") }}" alt="{{ _("Done") }}"><br>
<p>Aktualizace proběhla v pořádku, můžete postoupit k dalšímu kroku.</p>
<a class="button-next" href="/wizard/step/5">{{ _("Next") }}</a>
<a class="button-next" href="{{ url("wizard_step", number=5) }}">{{ _("Next") }}</a>
</div>
<div id="updater-fail">
<img src="/static/img/fail.png" alt="{{ _("Error") }}"><br>
<img src="{{ static("img/fail.png") }}" alt="{{ _("Error") }}"><br>
<p>
Aktualizace se nepodařilo stáhnout nebo došlo k chybě. Pokud Vaše připojení...
<!-- TODO: blabla -->
</p>
<a class="button-next" href="/wizard/step/5">{{ _("Next") }}</a>
<a class="button-next" href="{{ url("wizard_step", number=5) }}">{{ _("Next") }}</a>
</div>
</div>
......
......@@ -71,7 +71,7 @@ class UciRawForm(Form):
app = Bottle()
@app.get("/")
@app.get("/", name="uci_index")
@view("uci/index")
@login_required
def index():
......@@ -81,7 +81,7 @@ def index():
node_path=node_path.split(".") if node_path else None)
@app.get("/<node:re:\w+(\.\w+)*>/edit", name="edit")
@app.get("/<node:re:\w+(\.\w+)*>/edit", name="uci_edit")
@view("uci/edit")
@login_required
def edit(node):
......@@ -94,7 +94,7 @@ def edit(node):
return dict(form=form, node_path=node)
@app.post("/<node:re:\w+(\.\w+)*>/edit", name="edit_post")
@app.post("/<node:re:\w+(\.\w+)*>/edit")
@view("uci/edit")
@login_required
def edit_post(node):
......@@ -110,7 +110,7 @@ def edit_post(node):
return dict(form=form, node_path=node)
@app.get("/<node:re:\w+(\.\w+)*>/create", name="create")
@app.get("/<node:re:\w+(\.\w+)*>/create", name="uci_create")
@view("uci/edit")
@login_required
def create(node):
......@@ -137,7 +137,7 @@ def create(node):
return dict(node_path=node, form=form)
@app.post("/<node:re:\w+(\.\w+)*>/create", name="create_post")
@app.post("/<node:re:\w+(\.\w+)*>/create")
@view("uci/edit")
@login_required
def create_post(node):
......@@ -176,7 +176,7 @@ def create_post(node):
bottle.redirect("/uci/")
@app.get("/<node:re:\w+(\.\w+)*>/remove", name="remove")
@app.get("/<node:re:\w+(\.\w+)*>/remove", name="uci_remove")
@login_required
def remove(node):
uci_model = client.get_uci_config()
......@@ -190,7 +190,7 @@ def remove(node):
bottle.redirect("/uci/?error")
@app.get("/<node:re:\w+(\.\w+)*>/debug", name="debug")
@app.get("/<node:re:\w+(\.\w+)*>/debug", name="uci_debug")
@login_required
def debug(node):
uci_model = client.get_uci_config()
......
import bottle
import logging
logger = logging.getLogger("utils.routing")
def reverse(name, **kargs):
try:
return bottle.app().router.build(name, **kargs)
except bottle.RouteBuildError:
for route in bottle.app().routes:
if route.config.get("mountpoint"):
mountpoint = route.config['mountpoint']
try:
return "%s%s" % (mountpoint['prefix'].rstrip("/"),
mountpoint['target'].router.build(name, **kargs))
except bottle.RouteBuildError:
pass
raise bottle.RouteBuildError("No route with name '%s' in main app or mounted apps." % name)
\ No newline at end of file
......@@ -5,6 +5,7 @@ from config_handlers import BaseConfigHandler, PasswordHandler, WanHandler, Time
LanHandler, WifiHandler
from nuci import client
from utils import login_required
from utils.routing import reverse
logger = logging.getLogger("wizard")
......@@ -144,13 +145,13 @@ def ajax(number=1):
raise bottle.HTTPError(404, "Unknown Wizard action.")
@app.route("/", name="wizard-step")
@app.route("/", name="wizard_index")
@login_required
def wizard():
bottle.redirect("/wizard/step/1")
bottle.redirect(reverse("wizard_step", number=1))
@app.route("/step/<number:re:\d+>", name="wizard-step")
@app.route("/step/<number:re:\d+>", name="wizard_step")
@login_required
def step(number=1):
Wizard = get_wizard(number)
......@@ -171,7 +172,7 @@ def step_post(number=1):
try:
if wiz.save():
bottle.redirect("/wizard/step/%s" % str(int(number) + 1))
bottle.redirect(reverse("wizard_step", number=int(number) + 1))
except TypeError:
# raised by Validator - could happen when the form is posted with wrong fields
pass
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment