forked from orbit-oss/flask
Added class based view documentation
This commit is contained in:
parent
775caf726d
commit
b36d7b3288
4 changed files with 192 additions and 0 deletions
13
docs/api.rst
13
docs/api.rst
|
|
@ -413,3 +413,16 @@ Signals
|
|||
operations, including connecting.
|
||||
|
||||
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||
|
||||
Class Based Views
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
.. currentmodule:: None
|
||||
|
||||
.. autoclass:: flask.views.View
|
||||
:members:
|
||||
|
||||
.. autoclass:: flask.views.MethodView
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ instructions for web development with Flask.
|
|||
errorhandling
|
||||
config
|
||||
signals
|
||||
views
|
||||
reqcontext
|
||||
blueprints
|
||||
shell
|
||||
|
|
|
|||
137
docs/views.rst
Normal file
137
docs/views.rst
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
.. _views:
|
||||
|
||||
Pluggable Views
|
||||
===============
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
Flask 0.7 introduces pluggable views inspired by the generic views from
|
||||
Django which are based on classes instead of functions. The main
|
||||
intention is that you can replace parts of the implementations and this
|
||||
way have customizable pluggable views.
|
||||
|
||||
Basic Principle
|
||||
---------------
|
||||
|
||||
Consider you have a function that loads a list of objects from the
|
||||
database and renders into a template::
|
||||
|
||||
@app.route('/users/')
|
||||
def show_users(page):
|
||||
users = User.query.all()
|
||||
return render_template('users.html', users=users)
|
||||
|
||||
This is simple and flexible, but if you want to provide this view in a
|
||||
generic fashion that can be adapted to other models and templates as well
|
||||
you might want more flexibility. This is where pluggable class based
|
||||
views come into place. As the first step to convert this into a class
|
||||
based view you would do this::
|
||||
|
||||
|
||||
from flask.views import View
|
||||
|
||||
class ShowUsers(View):
|
||||
|
||||
def dispatch_request(self):
|
||||
users = User.query.all()
|
||||
return render_template('users.html', objects=users)
|
||||
|
||||
app.add_url_rule('/users/', ShowUsers.as_view('show_users'))
|
||||
|
||||
As you can see what you have to do is to create a subclass of
|
||||
:class:`flask.views.View` and implement
|
||||
:meth:`~flask.views.View.dispatch_request`. Then we have to convert that
|
||||
class into an actual view function by using the
|
||||
:meth:`~flask.views.View.as_view` class method. The string you pass to
|
||||
that function is the name of the endpoint that view will then have. But
|
||||
this by itself is not helpful, so let's refactor the code a bit::
|
||||
|
||||
|
||||
from flask.views import View
|
||||
|
||||
class ListView(View):
|
||||
|
||||
def get_template_name(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def render_template(self, context):
|
||||
return render_template(self.get_template_name(), **context)
|
||||
|
||||
def dispatch_request(self):
|
||||
context = {'objects': self.get_objects()}
|
||||
return self.render_template(context)
|
||||
|
||||
class UserView(ListView):
|
||||
|
||||
def get_template_name(self):
|
||||
return 'users.html'
|
||||
|
||||
def get_objects(self):
|
||||
return User.query.all()
|
||||
|
||||
This of course is not that helpful for such a small example, but it's good
|
||||
enough to explain the basic principle. When you have a class based view
|
||||
the question comes up what `self` points to. The way this works is that
|
||||
whenever the request is dispatched a new instance of the class is created
|
||||
and the :meth:`~flask.views.View.dispatch_request` method is called with
|
||||
the parameters from the URL rule. The class itself is instanciated with
|
||||
the parameters passed to the :meth:`~flask.views.View.as_view` function.
|
||||
For instance you can write a class like this::
|
||||
|
||||
class RenderTemplateView(View):
|
||||
def __init__(self, template_name):
|
||||
self.template_name = template_name
|
||||
def dispatch_request(self):
|
||||
return render_template(self.template_name)
|
||||
|
||||
And then you can register it like this::
|
||||
|
||||
app.add_url_view('/about', RenderTemplateView.as_view(
|
||||
'about_page', template_name='about.html'))
|
||||
|
||||
Method Hints
|
||||
------------
|
||||
|
||||
Pluggable views are attached to the application like a regular function by
|
||||
either using :func:`~flask.Flask.route` or better
|
||||
:meth:`~flask.Flask.add_url_rule`. That however also means that you would
|
||||
have to provide the names of the HTTP methods the view supports when you
|
||||
attach this. In order to move that information to the class you can
|
||||
provide a :attr:`~flask.views.View.methods` attribute that has this
|
||||
information::
|
||||
|
||||
class MyView(View):
|
||||
methods = ['GET', 'POST']
|
||||
|
||||
def dispatch_request(self):
|
||||
if request.method == 'POST':
|
||||
...
|
||||
...
|
||||
|
||||
app.add_url_view('/myview', MyView.as_view('myview'))
|
||||
|
||||
Method Based Dispatching
|
||||
------------------------
|
||||
|
||||
For RESTful APIs it's especially helpful to execute a different function
|
||||
for each HTTP method. With the :class:`flask.views.MethodView` you can
|
||||
easily do that. Each HTTP method maps to a function with the same name
|
||||
(just in lowercase)::
|
||||
|
||||
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_view('/users/', UserAPI.as_view('users'))
|
||||
|
||||
That way you also don't have to provide the
|
||||
:attr:`~flask.views.View.methods` attribute. It's automatically set based
|
||||
on the methods defined in the class.
|
||||
|
|
@ -1787,8 +1787,49 @@ class ViewTestCase(unittest.TestCase):
|
|||
return 'POST'
|
||||
|
||||
app.add_url_rule('/', view_func=Index.as_view('index'))
|
||||
|
||||
self.common_test(app)
|
||||
|
||||
def test_view_patching(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
1/0
|
||||
def post(self):
|
||||
1/0
|
||||
|
||||
class Other(Index):
|
||||
def get(self):
|
||||
return 'GET'
|
||||
def post(self):
|
||||
return 'POST'
|
||||
|
||||
view = Index.as_view('index')
|
||||
view.view_class = Other
|
||||
app.add_url_rule('/', view_func=view)
|
||||
self.common_test(app)
|
||||
|
||||
def test_view_inheritance(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
class Index(flask.views.MethodView):
|
||||
def get(self):
|
||||
return 'GET'
|
||||
def post(self):
|
||||
return 'POST'
|
||||
|
||||
class BetterIndex(Index):
|
||||
def delete(self):
|
||||
return 'DELETE'
|
||||
|
||||
app.add_url_rule('/', view_func=BetterIndex.as_view('index'))
|
||||
c = app.test_client()
|
||||
|
||||
meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
|
||||
self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST'])
|
||||
|
||||
|
||||
class DeprecationsTestCase(unittest.TestCase):
|
||||
|
||||
def test_init_jinja_globals(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue