diff --git a/CHANGES.rst b/CHANGES.rst index a727f947..0f0b5b76 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -71,6 +71,10 @@ Unreleased - The ``flask run`` command no longer fails if Python is not built with SSL support. Using the ``--cert`` option will show an appropriate error message. :issue:`3211` +- URL matching now occurs after the request context is pushed, rather + than when it's created. This allows custom URL converters to access + the app and request contexts, such as to query a database for an id. + :issue:`3088` .. _#2935: https://github.com/pallets/flask/issues/2935 .. _#2957: https://github.com/pallets/flask/issues/2957 diff --git a/src/flask/ctx.py b/src/flask/ctx.py index 8f040b34..a2944628 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -347,8 +347,8 @@ class RequestContext(object): of the request. """ try: - url_rule, self.request.view_args = self.url_adapter.match(return_rule=True) - self.request.url_rule = url_rule + result = self.url_adapter.match(return_rule=True) + self.request.url_rule, self.request.view_args = result except HTTPException as e: self.request.routing_exception = e @@ -381,9 +381,6 @@ class RequestContext(object): _request_ctx_stack.push(self) - if self.url_adapter is not None: - self.match_request() - # Open the session at the moment that the request context is available. # This allows a custom open_session method to use the request context. # Only open a new session if this is the first time the request was @@ -395,6 +392,9 @@ class RequestContext(object): if self.session is None: self.session = session_interface.make_null_session(self.app) + if self.url_adapter is not None: + self.match_request() + def pop(self, exc=_sentinel): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the diff --git a/tests/test_converters.py b/tests/test_converters.py index 53fd6cf1..dd6c4d68 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -1,9 +1,10 @@ -from flask.globals import _app_ctx_stack +from werkzeug.routing import BaseConverter + +from flask import has_request_context +from flask import url_for def test_custom_converters(app, client): - from werkzeug.routing import BaseConverter - class ListConverter(BaseConverter): def to_python(self, value): return value.split(",") @@ -20,19 +21,20 @@ def test_custom_converters(app, client): assert client.get("/1,2,3").data == b"1|2|3" + with app.test_request_context(): + assert url_for("index", args=[4, 5, 6]) == "/4,5,6" -def test_model_converters(app, client): - from werkzeug.routing import BaseConverter - class ModelConverterTester(BaseConverter): +def test_context_available(app, client): + class ContextConverter(BaseConverter): def to_python(self, value): - assert _app_ctx_stack.top is not None + assert has_request_context() return value - app.url_map.converters["model"] = ModelConverterTester + app.url_map.converters["ctx"] = ContextConverter - @app.route("/") - def index(user_name): - return user_name, 200 + @app.route("/") + def index(name): + return name - client.get("/admin").data + assert client.get("/admin").data == b"admin" diff --git a/tests/test_testing.py b/tests/test_testing.py index 2ea5b416..1490840c 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -154,6 +154,9 @@ def test_blueprint_with_subdomain(): ctx = app.test_request_context("/", subdomain="xxx") assert ctx.request.url == "http://xxx.example.com:1234/foo/" + with ctx: + assert ctx.request.blueprint == bp.name + rv = client.get("/", subdomain="xxx") assert rv.data == b"http://xxx.example.com:1234/foo/"