Support using the route decorator on view classes
This commit is contained in:
parent
77db3d5ede
commit
a7ed19cb6e
7 changed files with 134 additions and 6 deletions
|
|
@ -81,7 +81,8 @@ Unreleased
|
|||
- ``helpers.total_seconds()`` is deprecated. Use
|
||||
``timedelta.total_seconds()`` instead. :pr:`3962`
|
||||
- Add type hinting. :pr:`3973`.
|
||||
|
||||
- Support using the ``route`` decorator on view classes (i.e.
|
||||
``View`` and ``MethodView`` subclasses). :issue:`3404`
|
||||
|
||||
Version 1.1.2
|
||||
-------------
|
||||
|
|
|
|||
|
|
@ -233,3 +233,56 @@ registration code::
|
|||
methods=['GET', 'PUT', 'DELETE'])
|
||||
|
||||
register_api(UserAPI, 'user_api', '/users/', pk='user_id')
|
||||
|
||||
|
||||
Use the ``route`` Decorator on View Classes
|
||||
-------------------------------------------
|
||||
|
||||
For simple use cases, you can use the :func:`~flask.Flask.route` decorator
|
||||
as a shortcut of :meth:`~flask.views.View.as_view` class method to register
|
||||
a view class::
|
||||
|
||||
from flask.views import View
|
||||
|
||||
@app.route('/users/', endpoint='show_users')
|
||||
class ShowUsers(View):
|
||||
|
||||
def dispatch_request(self):
|
||||
users = User.query.all()
|
||||
return render_template('users.html', objects=users)
|
||||
|
||||
Or on a method-based view class::
|
||||
|
||||
from flask.views import MethodView
|
||||
|
||||
@app.route('/users/', endpoint='users')
|
||||
class UserAPI(MethodView):
|
||||
|
||||
def get(self):
|
||||
users = User.query.all()
|
||||
...
|
||||
|
||||
def post(self):
|
||||
user = User.from_form_data(request.form)
|
||||
...
|
||||
|
||||
You can also pass a view class to :meth:`~flask.Flask.add_url_rule`
|
||||
directly::
|
||||
|
||||
from flask.views import MethodView
|
||||
|
||||
class UserAPI(MethodView):
|
||||
|
||||
def get(self):
|
||||
users = User.query.all()
|
||||
...
|
||||
|
||||
def post(self):
|
||||
user = User.from_form_data(request.form)
|
||||
...
|
||||
|
||||
app.add_url_rule('/users/', endpoint='users', view_func=UserAPI)
|
||||
|
||||
Beware that if the ``endpoint`` argument isn't provided, the class name
|
||||
will be used as the endpoint. Also, you can't pass any class arguments in
|
||||
this way.
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ from .typing import TemplateGlobalCallable
|
|||
from .typing import TemplateTestCallable
|
||||
from .typing import URLDefaultCallable
|
||||
from .typing import URLValuePreprocessorCallable
|
||||
from .typing import ViewFuncArgument
|
||||
from .views import View
|
||||
from .wrappers import Request
|
||||
from .wrappers import Response
|
||||
|
||||
|
|
@ -1033,10 +1035,16 @@ class Flask(Scaffold):
|
|||
self,
|
||||
rule: str,
|
||||
endpoint: t.Optional[str] = None,
|
||||
view_func: t.Optional[t.Callable] = None,
|
||||
view_func: ViewFuncArgument = None,
|
||||
provide_automatic_options: t.Optional[bool] = None,
|
||||
**options: t.Any,
|
||||
) -> None:
|
||||
"""A helper method to register a rule (and optionally a view function)
|
||||
to the application.
|
||||
|
||||
.. versionchanged:: 2.0.0
|
||||
Support to pass a view class as view function.
|
||||
"""
|
||||
if endpoint is None:
|
||||
endpoint = _endpoint_from_view_func(view_func) # type: ignore
|
||||
options["endpoint"] = endpoint
|
||||
|
|
@ -1078,6 +1086,8 @@ class Flask(Scaffold):
|
|||
rule.provide_automatic_options = provide_automatic_options # type: ignore
|
||||
|
||||
self.url_map.add(rule)
|
||||
if isinstance(view_func, type) and issubclass(view_func, View):
|
||||
view_func = view_func.as_view(endpoint)
|
||||
if view_func is not None:
|
||||
old_func = self.view_functions.get(endpoint)
|
||||
if getattr(old_func, "_flask_sync_wrapper", False):
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ from .typing import TemplateGlobalCallable
|
|||
from .typing import TemplateTestCallable
|
||||
from .typing import URLDefaultCallable
|
||||
from .typing import URLValuePreprocessorCallable
|
||||
from .typing import ViewFuncArgument
|
||||
from .views import View
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .app import Flask
|
||||
|
|
@ -78,12 +80,15 @@ class BlueprintSetupState:
|
|||
self,
|
||||
rule: str,
|
||||
endpoint: t.Optional[str] = None,
|
||||
view_func: t.Optional[t.Callable] = None,
|
||||
view_func: ViewFuncArgument = None,
|
||||
**options: t.Any,
|
||||
) -> None:
|
||||
"""A helper method to register a rule (and optionally a view function)
|
||||
to the application. The endpoint is automatically prefixed with the
|
||||
blueprint's name.
|
||||
|
||||
.. versionchanged:: 2.0.0
|
||||
Support to pass a view class as view function.
|
||||
"""
|
||||
if self.url_prefix is not None:
|
||||
if rule:
|
||||
|
|
@ -96,6 +101,8 @@ class BlueprintSetupState:
|
|||
defaults = self.url_defaults
|
||||
if "defaults" in options:
|
||||
defaults = dict(defaults, **options.pop("defaults"))
|
||||
if isinstance(view_func, type) and issubclass(view_func, View):
|
||||
view_func = view_func.as_view(endpoint)
|
||||
self.app.add_url_rule(
|
||||
rule,
|
||||
f"{self.name_prefix}{self.blueprint.name}.{endpoint}",
|
||||
|
|
|
|||
|
|
@ -405,9 +405,9 @@ class Scaffold:
|
|||
return self._method_route("PATCH", rule, options)
|
||||
|
||||
def route(self, rule: str, **options: t.Any) -> t.Callable:
|
||||
"""Decorate a view function to register it with the given URL
|
||||
rule and options. Calls :meth:`add_url_rule`, which has more
|
||||
details about the implementation.
|
||||
"""Decorate a view function or view class to register it with
|
||||
the given URL rule and options. Calls :meth:`add_url_rule`, which
|
||||
has more details about the implementation.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -426,6 +426,9 @@ class Scaffold:
|
|||
:param rule: The URL rule string.
|
||||
:param options: Extra options passed to the
|
||||
:class:`~werkzeug.routing.Rule` object.
|
||||
|
||||
.. versionchanged:: 2.0.0
|
||||
The ``route`` decorator can be used on view classes.
|
||||
"""
|
||||
|
||||
def decorator(f: t.Callable) -> t.Callable:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ if t.TYPE_CHECKING:
|
|||
from werkzeug.datastructures import Headers # noqa: F401
|
||||
from wsgiref.types import WSGIApplication # noqa: F401
|
||||
from .wrappers import Response # noqa: F401
|
||||
from .views import View # noqa: F401
|
||||
|
||||
# The possible types that are directly convertible or are a Response object.
|
||||
ResponseValue = t.Union[
|
||||
|
|
@ -44,3 +45,4 @@ TemplateGlobalCallable = t.Callable[[], t.Any]
|
|||
TemplateTestCallable = t.Callable[[t.Any], bool]
|
||||
URLDefaultCallable = t.Callable[[str, dict], None]
|
||||
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
|
||||
ViewFuncArgument = t.Optional[t.Union[t.Callable, t.Type["View"]]]
|
||||
|
|
|
|||
|
|
@ -240,3 +240,55 @@ def test_remove_method_from_parent(app, client):
|
|||
assert client.get("/").data == b"GET"
|
||||
assert client.post("/").status_code == 405
|
||||
assert sorted(View.methods) == ["GET"]
|
||||
|
||||
|
||||
def test_route_on_view(app):
|
||||
@app.route("/", endpoint="index")
|
||||
class Index(flask.views.View):
|
||||
methods = ["GET", "POST"]
|
||||
|
||||
def dispatch_request(self):
|
||||
return flask.request.method
|
||||
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_route_on_methodview(app):
|
||||
@app.route("/", endpoint="index")
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
def post(self):
|
||||
return "POST"
|
||||
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_bp_route_on_view(app):
|
||||
bp = flask.Blueprint("test", __name__)
|
||||
|
||||
@bp.route("/", endpoint="index")
|
||||
class Index(flask.views.View):
|
||||
methods = ["GET", "POST"]
|
||||
|
||||
def dispatch_request(self):
|
||||
return flask.request.method
|
||||
|
||||
app.register_blueprint(bp)
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_bp_route_on_methodview(app):
|
||||
bp = flask.Blueprint("test", __name__)
|
||||
|
||||
@bp.route("/", endpoint="index")
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
return "GET"
|
||||
|
||||
def post(self):
|
||||
return "POST"
|
||||
|
||||
app.register_blueprint(bp)
|
||||
common_test(app)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue