From 307d1bc4e51fc35282f2d504b9df3359604b8f8a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 15:04:35 +0100 Subject: [PATCH] Added support for basic URL generation without request contexts. --- flask/app.py | 19 ++++++++++++--- flask/ctx.py | 1 + flask/helpers.py | 50 +++++++++++++++++++++++++-------------- flask/testsuite/appctx.py | 38 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 flask/testsuite/appctx.py diff --git a/flask/app.py b/flask/app.py index 38d31f95..215bf0ad 100644 --- a/flask/app.py +++ b/flask/app.py @@ -250,7 +250,8 @@ class Flask(_PackageBoundObject): 'SESSION_COOKIE_SECURE': False, 'MAX_CONTENT_LENGTH': None, 'TRAP_BAD_REQUEST_ERRORS': False, - 'TRAP_HTTP_EXCEPTIONS': False + 'TRAP_HTTP_EXCEPTIONS': False, + 'PREFERRED_URL_SCHEME': 'http' }) #: The rule object to use for URL rules created. This is used by @@ -1370,9 +1371,21 @@ class Flask(_PackageBoundObject): so the request is passed explicitly. .. versionadded:: 0.6 + + .. versionchanged:: 0.9 + This can now also be called without a request object when the + UR adapter is created for the application context. """ - return self.url_map.bind_to_environ(request.environ, - server_name=self.config['SERVER_NAME']) + if request is not None: + return self.url_map.bind_to_environ(request.environ, + server_name=self.config['SERVER_NAME']) + # We need at the very least the server name to be set for this + # to work. + if self.config['SERVER_NAME'] is not None: + return self.url_map.bind( + self.config['SERVER_NAME'], + script_name=self.config['APPLICATION_ROOT'] or '/', + url_scheme=self.config['PREFERRED_URL_SCHEME']) def inject_url_defaults(self, endpoint, values): """Injects the URL defaults for the given endpoint directly into diff --git a/flask/ctx.py b/flask/ctx.py index 7bfd598e..a9088cf4 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -70,6 +70,7 @@ class AppContext(object): def __init__(self, app): self.app = app + self.url_adapter = app.create_url_adapter(None) def push(self): """Binds the app context to the current context.""" diff --git a/flask/helpers.py b/flask/helpers.py index 25250d26..26be5e30 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -50,7 +50,8 @@ except ImportError: from jinja2 import FileSystemLoader -from .globals import session, _request_ctx_stack, current_app, request +from .globals import session, _request_ctx_stack, _app_ctx_stack, \ + current_app, request def _assert_have_json(): @@ -197,27 +198,40 @@ def url_for(endpoint, **values): :param _anchor: if provided this is added as anchor to the URL. :param _method: if provided this explicitly specifies an HTTP method. """ - ctx = _request_ctx_stack.top - blueprint_name = request.blueprint - if not ctx.request._is_old_module: - if endpoint[:1] == '.': - if blueprint_name is not None: - endpoint = blueprint_name + endpoint - else: + appctx = _app_ctx_stack.top + reqctx = _request_ctx_stack.top + + # If request specific information is available we have some extra + # features that support "relative" urls. + if reqctx is not None: + url_adapter = reqctx.url_adapter + blueprint_name = request.blueprint + if not reqctx.request._is_old_module: + if endpoint[:1] == '.': + if blueprint_name is not None: + endpoint = blueprint_name + endpoint + else: + endpoint = endpoint[1:] + else: + # TODO: get rid of this deprecated functionality in 1.0 + if '.' not in endpoint: + if blueprint_name is not None: + endpoint = blueprint_name + '.' + endpoint + elif endpoint.startswith('.'): endpoint = endpoint[1:] + external = values.pop('_external', False) + + # Otherwise go with the url adapter from the appctx and make + # the urls external by default. else: - # TODO: get rid of this deprecated functionality in 1.0 - if '.' not in endpoint: - if blueprint_name is not None: - endpoint = blueprint_name + '.' + endpoint - elif endpoint.startswith('.'): - endpoint = endpoint[1:] - external = values.pop('_external', False) + url_adapter = appctx.url_adapter + external = values.pop('_external', True) + anchor = values.pop('_anchor', None) method = values.pop('_method', None) - ctx.app.inject_url_defaults(endpoint, values) - rv = ctx.url_adapter.build(endpoint, values, method=method, - force_external=external) + appctx.app.inject_url_defaults(endpoint, values) + rv = url_adapter.build(endpoint, values, method=method, + force_external=external) if anchor is not None: rv += '#' + url_quote(anchor) return rv diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py new file mode 100644 index 00000000..2c198047 --- /dev/null +++ b/flask/testsuite/appctx.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.appctx + ~~~~~~~~~~~~~~~~~~~~~~ + + Tests the application context. + + :copyright: (c) 2012 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class AppContextTestCase(FlaskTestCase): + + def test_basic_support(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + app.config['PREFERRED_URL_SCHEME'] = 'https' + + @app.route('/') + def index(): + pass + + with app.app_context(): + rv = flask.url_for('index') + self.assert_equal(rv, 'https://localhost/') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(AppContextTestCase)) + return suite