Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added class based view documentation

  • Loading branch information...
commit b36d7b3288442173bcc999ebb4310674869a526d 1 parent 775caf7
@mitsuhiko authored
View
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:
View
1  docs/contents.rst.inc
@@ -17,6 +17,7 @@ instructions for web development with Flask.
errorhandling
config
signals
+ views
reqcontext
blueprints
shell
View
137 docs/views.rst
@@ -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.
View
41 tests/flask_tests.py
@@ -1787,8 +1787,49 @@ def post(self):
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):
Please sign in to comment.
Something went wrong with that request. Please try again.