mirror of https://github.com/mitsuhiko/flask.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
5.5 KiB
149 lines
5.5 KiB
# -*- coding: utf-8 -*- |
|
""" |
|
flask.views |
|
~~~~~~~~~~~ |
|
|
|
This module provides class-based views inspired by the ones in Django. |
|
|
|
:copyright: (c) 2015 by Armin Ronacher. |
|
:license: BSD, see LICENSE for more details. |
|
""" |
|
from .globals import request |
|
from ._compat import with_metaclass |
|
|
|
|
|
http_method_funcs = frozenset(['get', 'post', 'head', 'options', |
|
'delete', 'put', 'trace', 'patch']) |
|
|
|
|
|
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')) |
|
|
|
When you want to decorate a pluggable view you will have to either do that |
|
when the view function is created (by wrapping the return value of |
|
:meth:`as_view`) or you can use the :attr:`decorators` attribute:: |
|
|
|
class SecretView(View): |
|
methods = ['GET'] |
|
decorators = [superuser_required] |
|
|
|
def dispatch_request(self): |
|
... |
|
|
|
The decorators stored in the decorators list are applied one after another |
|
when the view function is created. Note that you can *not* use the class |
|
based decorators since those would decorate the view class and not the |
|
generated view function! |
|
""" |
|
|
|
#: A list of methods this view can handle. |
|
methods = None |
|
|
|
#: The canonical way to decorate class-based views is to decorate the |
|
#: return value of as_view(). However since this moves parts of the |
|
#: logic from the class declaration to the place where it's hooked |
|
#: into the routing system. |
|
#: |
|
#: You can place one or more decorators in this list and whenever the |
|
#: view function is created the result is automatically decorated. |
|
#: |
|
#: .. versionadded:: 0.8 |
|
decorators = () |
|
|
|
def dispatch_request(self): |
|
"""Subclasses have to override this method to implement the |
|
actual view function code. This method is called with all |
|
the arguments from the URL rule. |
|
""" |
|
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. Internally this generates a function on the |
|
fly which will instantiate 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 = view.view_class(*class_args, **class_kwargs) |
|
return self.dispatch_request(*args, **kwargs) |
|
|
|
if cls.decorators: |
|
view.__name__ = name |
|
view.__module__ = cls.__module__ |
|
for decorator in cls.decorators: |
|
view = decorator(view) |
|
|
|
# We attach the view class to the view function for two reasons: |
|
# first of all it allows us to easily figure out what class-based |
|
# view this thing came from, secondly it's also used for instantiating |
|
# the view class so you can actually replace it with something else |
|
# for testing purposes and debugging. |
|
view.view_class = cls |
|
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 'methods' not in d: |
|
methods = set(rv.methods or []) |
|
for key in d: |
|
if key in http_method_funcs: |
|
methods.add(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 base class or another subclass of a base method view |
|
# that does not introduce new methods). |
|
if methods: |
|
rv.methods = sorted(methods) |
|
return rv |
|
|
|
|
|
class MethodView(with_metaclass(MethodViewType, 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 it will respond 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')) |
|
""" |
|
def dispatch_request(self, *args, **kwargs): |
|
meth = getattr(self, request.method.lower(), None) |
|
# If the request method is HEAD and we don't have a handler for it |
|
# retry with GET. |
|
if meth is None and request.method == 'HEAD': |
|
meth = getattr(self, 'get', None) |
|
assert meth is not None, 'Unimplemented method %r' % request.method |
|
return meth(*args, **kwargs)
|
|
|