diff --git a/CHANGES.rst b/CHANGES.rst index adf2623d..bcec74a7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,8 @@ Version 2.3.0 Unreleased +- Ensure subdomains are applied with nested blueprints. :issue:`4834` + Version 2.2.3 ------------- diff --git a/docs/blueprints.rst b/docs/blueprints.rst index af368bac..d5cf3d82 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -140,6 +140,19 @@ name, and child URLs will be prefixed with the parent's URL prefix. url_for('parent.child.create') /parent/child/create +In addition a child blueprint's will gain their parent's subdomain, +with their subdomain as prefix if present i.e. + +.. code-block:: python + + parent = Blueprint('parent', __name__, subdomain='parent') + child = Blueprint('child', __name__, subdomain='child') + parent.register_blueprint(child) + app.register_blueprint(parent) + + url_for('parent.child.create', _external=True) + "child.parent.domain.tld" + Blueprint-specific before request functions, etc. registered with the parent will trigger for the child. If a child does not have an error handler that can handle a given exception, the parent's will be tried. diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index f6d62ba8..2403be1c 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -358,6 +358,9 @@ class Blueprint(Scaffold): :param options: Keyword arguments forwarded from :meth:`~Flask.register_blueprint`. + .. versionchanged:: 2.3 + Nested blueprints now correctly apply subdomains. + .. versionchanged:: 2.0.1 Nested blueprints are registered with their dotted name. This allows different blueprints with the same name to be @@ -453,6 +456,17 @@ class Blueprint(Scaffold): for blueprint, bp_options in self._blueprints: bp_options = bp_options.copy() bp_url_prefix = bp_options.get("url_prefix") + bp_subdomain = bp_options.get("subdomain") + + if bp_subdomain is None: + bp_subdomain = blueprint.subdomain + + if state.subdomain is not None and bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + "." + state.subdomain + elif bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + elif state.subdomain is not None: + bp_options["subdomain"] = state.subdomain if bp_url_prefix is None: bp_url_prefix = blueprint.url_prefix diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 1bf130f5..dbe00b97 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -950,6 +950,55 @@ def test_nesting_url_prefixes( assert response.status_code == 200 +def test_nesting_subdomains(app, client) -> None: + subdomain = "api" + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + + @child.route("/child/") + def index(): + return "child" + + parent.register_blueprint(child) + app.register_blueprint(parent, subdomain=subdomain) + + client.allow_subdomain_redirects = True + + domain_name = "domain.tld" + app.config["SERVER_NAME"] = domain_name + response = client.get("/child/", base_url="http://api." + domain_name) + + assert response.status_code == 200 + + +def test_child_and_parent_subdomain(app, client) -> None: + child_subdomain = "api" + parent_subdomain = "parent" + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__, subdomain=child_subdomain) + + @child.route("/") + def index(): + return "child" + + parent.register_blueprint(child) + app.register_blueprint(parent, subdomain=parent_subdomain) + + client.allow_subdomain_redirects = True + + domain_name = "domain.tld" + app.config["SERVER_NAME"] = domain_name + response = client.get( + "/", base_url=f"http://{child_subdomain}.{parent_subdomain}.{domain_name}" + ) + + assert response.status_code == 200 + + response = client.get("/", base_url=f"http://{parent_subdomain}.{domain_name}") + + assert response.status_code == 404 + + def test_unique_blueprint_names(app, client) -> None: bp = flask.Blueprint("bp", __name__) bp2 = flask.Blueprint("bp", __name__)