Skip to content

Commit 9b892e2

Browse files
authored
Merge pull request pallets#2252 from davidism/method-view-inheritance
Continue pallets#1936: Add the ability to combine MethodViews
2 parents 9569e8c + 648344d commit 9b892e2

File tree

3 files changed

+66
-18
lines changed

3 files changed

+66
-18
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ Major release, unreleased
2424
- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable
2525
adding OPTIONS method when the ``view_func`` argument is not a class.
2626
(`#1489`_).
27+
- ``MethodView`` can inherit method handlers from base classes. (`#1936`_)
2728

2829
.. _#1489: https://github.com/pallets/flask/pull/1489
30+
.. _#1936: https://github.com/pallets/flask/pull/1936
2931
.. _#2017: https://github.com/pallets/flask/pull/2017
3032
.. _#2223: https://github.com/pallets/flask/pull/2223
3133

flask/views.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -103,33 +103,34 @@ def view(*args, **kwargs):
103103

104104

105105
class MethodViewType(type):
106+
"""Metaclass for :class:`MethodView` that determines what methods the view
107+
defines.
108+
"""
109+
110+
def __init__(cls, name, bases, d):
111+
super(MethodViewType, cls).__init__(name, bases, d)
106112

107-
def __new__(cls, name, bases, d):
108-
rv = type.__new__(cls, name, bases, d)
109113
if 'methods' not in d:
110-
methods = set(rv.methods or [])
111-
for key in d:
112-
if key in http_method_funcs:
114+
methods = set()
115+
116+
for key in http_method_funcs:
117+
if hasattr(cls, key):
113118
methods.add(key.upper())
114-
# If we have no method at all in there we don't want to
115-
# add a method list. (This is for instance the case for
116-
# the base class or another subclass of a base method view
117-
# that does not introduce new methods).
119+
120+
# If we have no method at all in there we don't want to add a
121+
# method list. This is for instance the case for the base class
122+
# or another subclass of a base method view that does not introduce
123+
# new methods.
118124
if methods:
119-
rv.methods = sorted(methods)
120-
return rv
125+
cls.methods = methods
121126

122127

123128
class MethodView(with_metaclass(MethodViewType, View)):
124-
"""Like a regular class-based view but that dispatches requests to
125-
particular methods. For instance if you implement a method called
126-
:meth:`get` it means it will respond to ``'GET'`` requests and
127-
the :meth:`dispatch_request` implementation will automatically
128-
forward your request to that. Also :attr:`options` is set for you
129-
automatically::
129+
"""A class-based view that dispatches request methods to the corresponding
130+
class methods. For example, if you implement a ``get`` method, it will be
131+
used to handle ``GET`` requests. ::
130132
131133
class CounterAPI(MethodView):
132-
133134
def get(self):
134135
return session.get('counter', 0)
135136
@@ -139,11 +140,14 @@ def post(self):
139140
140141
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
141142
"""
143+
142144
def dispatch_request(self, *args, **kwargs):
143145
meth = getattr(self, request.method.lower(), None)
146+
144147
# If the request method is HEAD and we don't have a handler for it
145148
# retry with GET.
146149
if meth is None and request.method == 'HEAD':
147150
meth = getattr(self, 'get', None)
151+
148152
assert meth is not None, 'Unimplemented method %r' % request.method
149153
return meth(*args, **kwargs)

tests/test_views.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,45 @@ def dispatch_request(self):
160160

161161
# But these tests should still pass. We just log a warning.
162162
common_test(app)
163+
164+
def test_multiple_inheritance():
165+
app = flask.Flask(__name__)
166+
167+
class GetView(flask.views.MethodView):
168+
def get(self):
169+
return 'GET'
170+
171+
class DeleteView(flask.views.MethodView):
172+
def delete(self):
173+
return 'DELETE'
174+
175+
class GetDeleteView(GetView, DeleteView):
176+
pass
177+
178+
app.add_url_rule('/', view_func=GetDeleteView.as_view('index'))
179+
180+
c = app.test_client()
181+
assert c.get('/').data == b'GET'
182+
assert c.delete('/').data == b'DELETE'
183+
assert sorted(GetDeleteView.methods) == ['DELETE', 'GET']
184+
185+
def test_remove_method_from_parent():
186+
app = flask.Flask(__name__)
187+
188+
class GetView(flask.views.MethodView):
189+
def get(self):
190+
return 'GET'
191+
192+
class OtherView(flask.views.MethodView):
193+
def post(self):
194+
return 'POST'
195+
196+
class View(GetView, OtherView):
197+
methods = ['GET']
198+
199+
app.add_url_rule('/', view_func=View.as_view('index'))
200+
201+
c = app.test_client()
202+
assert c.get('/').data == b'GET'
203+
assert c.post('/').status_code == 405
204+
assert sorted(View.methods) == ['GET']

0 commit comments

Comments
 (0)