diff --git a/docs/sanic/blueprints.md b/docs/sanic/blueprints.md index 71849ac1..120aa1dd 100644 --- a/docs/sanic/blueprints.md +++ b/docs/sanic/blueprints.md @@ -158,3 +158,22 @@ app.blueprint(blueprint_v2) app.run(host='0.0.0.0', port=8000, debug=True) ``` + +## URL Building with `url_for` + +If you wish to generate a URL for a route inside of a blueprint, remember that the endpoint name +takes the format `.`. For example: + +``` +@blueprint_v1.route('/') +async def root(request): + url = app.url_for('v1.post_handler', post_id=5) + return redirect(url) + + +@blueprint_v1.route('/post/') +async def post_handler(request, post_id): + return text('Post {} in Blueprint V1'.format(post_id)) +``` + + diff --git a/docs/sanic/class_based_views.md b/docs/sanic/class_based_views.md index 5f99a0b5..02b02140 100644 --- a/docs/sanic/class_based_views.md +++ b/docs/sanic/class_based_views.md @@ -67,7 +67,7 @@ app.add_route(NameView.as_view(), '/') If you want to add any decorators to the class, you can set the `decorators` class variable. These will be applied to the class when `as_view` is called. -``` +```python class ViewWithDecorator(HTTPMethodView): decorators = [some_decorator_here] @@ -77,6 +77,27 @@ class ViewWithDecorator(HTTPMethodView): app.add_route(ViewWithDecorator.as_view(), '/url') ``` +#### URL Building + +If you wish to build a URL for an HTTPMethodView, remember that the class name will be the endpoint +that you will pass into `url_for`. For example: + +```python +@app.route('/') +def index(request): + url = app.url_for('SpecialClassView') + return redirect(url) + + +class SpecialClassView(HTTPMethodView): + def get(self, request): + return text('Hello from the Special Class View!') + + +app.add_route(SpecialClassView.as_view(), '/special_class_view') +``` + + ## Using CompositionView As an alternative to the `HTTPMethodView`, you can use `CompositionView` to @@ -106,3 +127,5 @@ view.add(['POST', 'PUT'], lambda request: text('I am a post/put method')) # Use the new view to handle requests to the base URL app.add_route(view, '/') ``` + +Note: currently you cannot build a URL for a CompositionView using `url_for`. diff --git a/docs/sanic/routing.md b/docs/sanic/routing.md index 2dda39c4..84945904 100644 --- a/docs/sanic/routing.md +++ b/docs/sanic/routing.md @@ -119,3 +119,35 @@ app.add_route(handler1, '/test') app.add_route(handler2, '/folder/') app.add_route(person_handler2, '/person/', methods=['GET']) ``` + +## URL building with `url_for`. + +Sanic provides a `url_for` method, to generate URLs based on the handler method name. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name. For example: + +``` +@app.route('/') +async def index(request): + # generate a URL for the endpoint `post_handler` + url = app.url_for('post_handler', post_id=5) + # the URL is `/posts/5`, redirect to it + return redirect(url) + + +@app.route('/posts/') +async def post_handler(request, post_id): + return text('Post - {}'.format(post_id)) +``` + +Other things to keep in mind when using `url_for`: + +- Keyword arguments passed to `url_for` that are not request parameters will be included in the URL's query string. For example: +``` +url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two') +# /posts/5?arg_one=one&arg_two=two +``` +- All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be thrown. + + + + + diff --git a/docs/sanic/static_files.md b/docs/sanic/static_files.md index d7e4866d..f0ce9d78 100644 --- a/docs/sanic/static_files.md +++ b/docs/sanic/static_files.md @@ -17,3 +17,5 @@ app.static('/the_best.png', '/home/ubuntu/test.png') app.run(host="0.0.0.0", port=8000) ``` + +Note: currently you cannot build a URL for a static file using `url_for`. diff --git a/sanic/sanic.py b/sanic/sanic.py index 11b104ac..2d2f83fe 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -195,6 +195,26 @@ class Sanic: return self.blueprint(*args, **kwargs) def url_for(self, view_name: str, **kwargs): + """Builds a URL based on a view name and the values provided. + + In order to build a URL, all request parameters must be supplied as + keyword arguments, and each parameter must pass the test for the + specified parameter type. If these conditions are not met, a + `URLBuildError` will be thrown. + + Keyword arguments that are not request parameters will be included in + the output URL's query string. + + :param view_name: A string referencing the view name + :param **kwargs: keys and values that are used to build request + parameters and query string arguments. + + :return: the built URL + + Raises: + URLBuildError + """ + # find the route by the supplied view name uri, route = self.router.find_route_by_view_name(view_name) if not uri or not route: @@ -203,15 +223,18 @@ class Sanic: view_name)) out = uri + + # find all the parameters we will need to build in the URL matched_params = re.findall( self.router.parameter_pattern, uri) for match in matched_params: name, _type, pattern = self.router.parse_parameter_string( match) + # we only want to match against each individual parameter specific_pattern = '^{}$'.format(pattern) - supplied_param = None + if kwargs.get(name): supplied_param = kwargs.get(name) del kwargs[name] @@ -221,6 +244,8 @@ class Sanic: name)) supplied_param = str(supplied_param) + # determine if the parameter supplied by the caller passes the test + # in the URL passes_pattern = re.match(specific_pattern, supplied_param) if not passes_pattern: @@ -236,6 +261,7 @@ class Sanic: supplied_param, name, pattern)) raise URLBuildError(msg) + # replace the parameter in the URL with the supplied value replacement_regex = '(<{}.*?>)'.format(name) out = re.sub( diff --git a/tests/test_url_building.py b/tests/test_url_building.py index c8209557..480313b3 100644 --- a/tests/test_url_building.py +++ b/tests/test_url_building.py @@ -3,7 +3,7 @@ from urllib.parse import urlsplit, parse_qsl from sanic import Sanic from sanic.response import text -from sanic.views import HTTPMethodView, CompositionView +from sanic.views import HTTPMethodView from sanic.blueprints import Blueprint from sanic.utils import sanic_endpoint_test from sanic.exceptions import URLBuildError