Browse Source

Added class based views

pull/262/head
Armin Ronacher 14 years ago
parent
commit
dcf21989dc
  1. 11
      flask/app.py
  2. 102
      flask/views.py
  3. 37
      tests/flask_tests.py

11
flask/app.py

@ -642,7 +642,8 @@ class Flask(_PackageBoundObject):
if blueprint.name in self.blueprints: if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \ assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint\'s name collision ocurred between %r and ' \ 'A blueprint\'s name collision ocurred between %r and ' \
'%r. Both share the same name "%s"' % \ '%r. Both share the same name "%s". Blueprints that ' \
'are created on the fly need unique names.' % \
(blueprint, self.blueprints[blueprint.name], blueprint.name) (blueprint, self.blueprints[blueprint.name], blueprint.name)
else: else:
self.blueprints[blueprint.name] = blueprint self.blueprints[blueprint.name] = blueprint
@ -695,7 +696,12 @@ class Flask(_PackageBoundObject):
if endpoint is None: if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint options['endpoint'] = endpoint
methods = options.pop('methods', ('GET',)) methods = options.pop('methods', None)
# if the methods are not given and the view_func object knows its
# methods we can use that instead. If neither exists, we go with
# a tuple of only `GET` as default.
if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
provide_automatic_options = False provide_automatic_options = False
if 'OPTIONS' not in methods: if 'OPTIONS' not in methods:
methods = tuple(methods) + ('OPTIONS',) methods = tuple(methods) + ('OPTIONS',)
@ -778,7 +784,6 @@ class Flask(_PackageBoundObject):
return f return f
return decorator return decorator
def endpoint(self, endpoint): def endpoint(self, endpoint):
"""A decorator to register a function as an endpoint. """A decorator to register a function as an endpoint.
Example:: Example::

102
flask/views.py

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
"""
flask.views
~~~~~~~~~~~
This module provides class based views inspired by the ones in Django.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from .globals import request
http_method_funcs = frozenset(['get', 'post', 'head', 'options',
'delete', 'put', 'trace'])
class View(object):
"""Alternative way to use view functions. A subclass has to implement
:meth:`dispatch_request` which is called with the view arguments from
the URL routing system. If :attr:`methods` is provided the methods
do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
method explicitly::
class MyView(View):
methods = ['GET']
def dispatch_request(self, name):
return 'Hello %s!' % name
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
"""
methods = None
def dispatch_request(self):
raise NotImplementedError()
@classmethod
def as_view(cls, name, *class_args, **class_kwargs):
"""Converts the class into an actual view function that can be
used with the routing system. What it does internally is generating
a function on the fly that will instanciate the :class:`View`
on each request and call the :meth:`dispatch_request` method on it.
The arguments passed to :meth:`as_view` are forwarded to the
constructor of the class.
"""
def view(*args, **kwargs):
self = cls(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs)
view.__name__ = name
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.methods = cls.methods
return view
class MethodViewType(type):
def __new__(cls, name, bases, d):
rv = type.__new__(cls, name, bases, d)
if rv.methods is None:
methods = []
for key, value in d.iteritems():
if key in http_method_funcs:
methods.append(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
# the baseclass or another subclass of a base method view
# that does not introduce new methods).
if methods:
rv.methods = methods
return rv
class MethodView(View):
"""Like a regular class based view but that dispatches requests to
particular methods. For instance if you implement a method called
:meth:`get` it means you will response to ``'GET'`` requests and
the :meth:`dispatch_request` implementation will automatically
forward your request to that. Also :attr:`options` is set for you
automatically::
class CounterAPI(MethodView):
def get(self):
return session.get('counter', 0)
def post(self):
session['counter'] = session.get('counter', 0) + 1
return 'OK'
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
"""
__metaclass__ = MethodViewType
def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None)
assert meth is not None, 'Not implemented method'
return meth(*args, **kwargs)

37
tests/flask_tests.py

@ -14,6 +14,7 @@ import os
import re import re
import sys import sys
import flask import flask
import flask.views
import unittest import unittest
import warnings import warnings
from threading import Thread from threading import Thread
@ -23,6 +24,7 @@ from functools import update_wrapper
from datetime import datetime from datetime import datetime
from werkzeug import parse_date, parse_options_header from werkzeug import parse_date, parse_options_header
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
from werkzeug.http import parse_set_header
from jinja2 import TemplateNotFound from jinja2 import TemplateNotFound
from cStringIO import StringIO from cStringIO import StringIO
@ -1753,6 +1755,40 @@ class TestSignals(unittest.TestCase):
flask.got_request_exception.disconnect(record, app) flask.got_request_exception.disconnect(record, app)
class ViewTestCase(unittest.TestCase):
def common_test(self, app):
c = app.test_client()
self.assertEqual(c.get('/').data, 'GET')
self.assertEqual(c.post('/').data, 'POST')
self.assertEqual(c.put('/').status_code, 405)
meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
self.assertEqual(sorted(meths), ['GET', 'HEAD', 'OPTIONS', 'POST'])
def test_basic_view(self):
app = flask.Flask(__name__)
class Index(flask.views.View):
methods = ['GET', 'POST']
def dispatch_request(self):
return flask.request.method
app.add_url_rule('/', view_func=Index.as_view('index'))
self.common_test(app)
def test_method_based_view(self):
app = flask.Flask(__name__)
class Index(flask.views.MethodView):
def get(self):
return 'GET'
def post(self):
return 'POST'
app.add_url_rule('/', view_func=Index.as_view('index'))
self.common_test(app)
class DeprecationsTestCase(unittest.TestCase): class DeprecationsTestCase(unittest.TestCase):
def test_init_jinja_globals(self): def test_init_jinja_globals(self):
@ -1785,6 +1821,7 @@ def suite():
suite.addTest(unittest.makeSuite(LoggingTestCase)) suite.addTest(unittest.makeSuite(LoggingTestCase))
suite.addTest(unittest.makeSuite(ConfigTestCase)) suite.addTest(unittest.makeSuite(ConfigTestCase))
suite.addTest(unittest.makeSuite(SubdomainTestCase)) suite.addTest(unittest.makeSuite(SubdomainTestCase))
suite.addTest(unittest.makeSuite(ViewTestCase))
suite.addTest(unittest.makeSuite(DeprecationsTestCase)) suite.addTest(unittest.makeSuite(DeprecationsTestCase))
if flask.json_available: if flask.json_available:
suite.addTest(unittest.makeSuite(JSONTestCase)) suite.addTest(unittest.makeSuite(JSONTestCase))

Loading…
Cancel
Save