diff --git a/flask/helpers.py b/flask/helpers.py index c744bb8c..7423aca8 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -190,7 +190,24 @@ def make_response(*args): return current_app.make_response(args) -def url_for(endpoint, **values): +def url_for(*args, **kwargs): + """ + wrapper based on https://github.com/miguelgrinberg/flack/blob/master/flack/utils.py + that also covers the case where application context exists with SERVER_NAME defined. + """ + + reqctx = _request_ctx_stack.top + appctx = _app_ctx_stack.top + if reqctx is None and (appctx is None or appctx.url_adapter is None): + if kwargs.get('_external', False): + raise RuntimeError('Cannot generate external URLs without a ' + 'request context.') + with current_app.test_request_context(): + return _url_for(*args, **kwargs) + return _url_for(*args, **kwargs) + + +def _url_for(endpoint, **values): """Generates a URL to the given endpoint with the method provided. Variable arguments that are unknown to the target endpoint are appended @@ -294,11 +311,13 @@ def url_for(endpoint, **values): # the URLs external by default. else: url_adapter = appctx.url_adapter + # Due to the new url_for wrapper, url_adapter will never be None here if url_adapter is None: raise RuntimeError('Application was not able to create a URL ' 'adapter for request independent URL generation. ' 'You might be able to fix this by setting ' 'the SERVER_NAME config variable.') + external = values.pop('_external', True) anchor = values.pop('_anchor', None) diff --git a/tests/test_appctx.py b/tests/test_appctx.py index 13b61eee..162108a7 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -16,7 +16,7 @@ import flask def test_basic_url_generation(): app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost' + app.config['SERVER_NAME'] = 'somehost' app.config['PREFERRED_URL_SCHEME'] = 'https' @app.route('/') @@ -25,13 +25,7 @@ def test_basic_url_generation(): with app.app_context(): rv = flask.url_for('index') - assert rv == 'https://localhost/' - -def test_url_generation_requires_server_name(): - app = flask.Flask(__name__) - with app.app_context(): - with pytest.raises(RuntimeError): - flask.url_for('index') + assert rv == 'https://somehost/' def test_url_generation_without_context_fails(): with pytest.raises(RuntimeError): @@ -49,6 +43,36 @@ def test_app_context_provides_current_app(): assert flask.current_app._get_current_object() == app assert flask._app_ctx_stack.top is None +def test_url_for_without_server_name_or_app_context(): + app = flask.Flask(__name__) + client = app.test_client() + + @app.route('/endpoint') + def endpoint(): + return 'Hello World!' + + with app.app_context(): + response = client.get(flask.url_for('endpoint', param='foo')) + assert response.status_code == 200 + +def test_get_with_arguments(): + app = flask.Flask(__name__) + app.debug = True + + @app.route('/echo') + def index(): + assert 'key' in flask.request.args + return flask.jsonify( {'key': flask.request.args['key']} ) + + url = None + with app.test_request_context(): + url = flask.url_for('index', key='test', _external=True) + + assert url == 'http://localhost/echo?key=test' + + rv = app.test_client().get(url) + assert b'"key": "test"' in rv.data + def test_app_tearing_down(): cleanup_stuff = [] app = flask.Flask(__name__)