Merge pull request #2252 from davidism/method-view-inheritance
Continue #1936: Add the ability to combine MethodViews
This commit is contained in:
commit
9b892e2225
3 changed files with 66 additions and 18 deletions
2
CHANGES
2
CHANGES
|
|
@ -24,8 +24,10 @@ Major release, unreleased
|
||||||
- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable
|
- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable
|
||||||
adding OPTIONS method when the ``view_func`` argument is not a class.
|
adding OPTIONS method when the ``view_func`` argument is not a class.
|
||||||
(`#1489`_).
|
(`#1489`_).
|
||||||
|
- ``MethodView`` can inherit method handlers from base classes. (`#1936`_)
|
||||||
|
|
||||||
.. _#1489: https://github.com/pallets/flask/pull/1489
|
.. _#1489: https://github.com/pallets/flask/pull/1489
|
||||||
|
.. _#1936: https://github.com/pallets/flask/pull/1936
|
||||||
.. _#2017: https://github.com/pallets/flask/pull/2017
|
.. _#2017: https://github.com/pallets/flask/pull/2017
|
||||||
.. _#2223: https://github.com/pallets/flask/pull/2223
|
.. _#2223: https://github.com/pallets/flask/pull/2223
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,33 +103,34 @@ class View(object):
|
||||||
|
|
||||||
|
|
||||||
class MethodViewType(type):
|
class MethodViewType(type):
|
||||||
|
"""Metaclass for :class:`MethodView` that determines what methods the view
|
||||||
|
defines.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(cls, name, bases, d):
|
||||||
|
super(MethodViewType, cls).__init__(name, bases, d)
|
||||||
|
|
||||||
def __new__(cls, name, bases, d):
|
|
||||||
rv = type.__new__(cls, name, bases, d)
|
|
||||||
if 'methods' not in d:
|
if 'methods' not in d:
|
||||||
methods = set(rv.methods or [])
|
methods = set()
|
||||||
for key in d:
|
|
||||||
if key in http_method_funcs:
|
for key in http_method_funcs:
|
||||||
|
if hasattr(cls, key):
|
||||||
methods.add(key.upper())
|
methods.add(key.upper())
|
||||||
# If we have no method at all in there we don't want to
|
|
||||||
# add a method list. (This is for instance the case for
|
# If we have no method at all in there we don't want to add a
|
||||||
# the base class or another subclass of a base method view
|
# method list. This is for instance the case for the base class
|
||||||
# that does not introduce new methods).
|
# or another subclass of a base method view that does not introduce
|
||||||
|
# new methods.
|
||||||
if methods:
|
if methods:
|
||||||
rv.methods = sorted(methods)
|
cls.methods = methods
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class MethodView(with_metaclass(MethodViewType, View)):
|
class MethodView(with_metaclass(MethodViewType, View)):
|
||||||
"""Like a regular class-based view but that dispatches requests to
|
"""A class-based view that dispatches request methods to the corresponding
|
||||||
particular methods. For instance if you implement a method called
|
class methods. For example, if you implement a ``get`` method, it will be
|
||||||
:meth:`get` it means it will respond to ``'GET'`` requests and
|
used to handle ``GET`` requests. ::
|
||||||
the :meth:`dispatch_request` implementation will automatically
|
|
||||||
forward your request to that. Also :attr:`options` is set for you
|
|
||||||
automatically::
|
|
||||||
|
|
||||||
class CounterAPI(MethodView):
|
class CounterAPI(MethodView):
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return session.get('counter', 0)
|
return session.get('counter', 0)
|
||||||
|
|
||||||
|
|
@ -139,11 +140,14 @@ class MethodView(with_metaclass(MethodViewType, View)):
|
||||||
|
|
||||||
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def dispatch_request(self, *args, **kwargs):
|
def dispatch_request(self, *args, **kwargs):
|
||||||
meth = getattr(self, request.method.lower(), None)
|
meth = getattr(self, request.method.lower(), None)
|
||||||
|
|
||||||
# If the request method is HEAD and we don't have a handler for it
|
# If the request method is HEAD and we don't have a handler for it
|
||||||
# retry with GET.
|
# retry with GET.
|
||||||
if meth is None and request.method == 'HEAD':
|
if meth is None and request.method == 'HEAD':
|
||||||
meth = getattr(self, 'get', None)
|
meth = getattr(self, 'get', None)
|
||||||
|
|
||||||
assert meth is not None, 'Unimplemented method %r' % request.method
|
assert meth is not None, 'Unimplemented method %r' % request.method
|
||||||
return meth(*args, **kwargs)
|
return meth(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -160,3 +160,45 @@ def test_endpoint_override():
|
||||||
|
|
||||||
# But these tests should still pass. We just log a warning.
|
# But these tests should still pass. We just log a warning.
|
||||||
common_test(app)
|
common_test(app)
|
||||||
|
|
||||||
|
def test_multiple_inheritance():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
class GetView(flask.views.MethodView):
|
||||||
|
def get(self):
|
||||||
|
return 'GET'
|
||||||
|
|
||||||
|
class DeleteView(flask.views.MethodView):
|
||||||
|
def delete(self):
|
||||||
|
return 'DELETE'
|
||||||
|
|
||||||
|
class GetDeleteView(GetView, DeleteView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.add_url_rule('/', view_func=GetDeleteView.as_view('index'))
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
assert c.get('/').data == b'GET'
|
||||||
|
assert c.delete('/').data == b'DELETE'
|
||||||
|
assert sorted(GetDeleteView.methods) == ['DELETE', 'GET']
|
||||||
|
|
||||||
|
def test_remove_method_from_parent():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
class GetView(flask.views.MethodView):
|
||||||
|
def get(self):
|
||||||
|
return 'GET'
|
||||||
|
|
||||||
|
class OtherView(flask.views.MethodView):
|
||||||
|
def post(self):
|
||||||
|
return 'POST'
|
||||||
|
|
||||||
|
class View(GetView, OtherView):
|
||||||
|
methods = ['GET']
|
||||||
|
|
||||||
|
app.add_url_rule('/', view_func=View.as_view('index'))
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
assert c.get('/').data == b'GET'
|
||||||
|
assert c.post('/').status_code == 405
|
||||||
|
assert sorted(View.methods) == ['GET']
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue