forked from orbit-oss/flask
Added class based views
This commit is contained in:
parent
1c8097b35a
commit
dcf21989dc
3 changed files with 147 additions and 3 deletions
11
flask/app.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
Normal file
102
flask/views.py
Normal file
|
|
@ -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)
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue