1# -*- coding: utf-8 -*-
2"""
3    flask.views
4    ~~~~~~~~~~~
5
6    This module provides class-based views inspired by the ones in Django.
7
8    :copyright: 2010 Pallets
9    :license: BSD-3-Clause
10"""
11from ._compat import with_metaclass
12from .globals import request
13
14
15http_method_funcs = frozenset(
16    ["get", "post", "head", "options", "delete", "put", "trace", "patch"]
17)
18
19
20class View(object):
21    """Alternative way to use view functions.  A subclass has to implement
22    :meth:`dispatch_request` which is called with the view arguments from
23    the URL routing system.  If :attr:`methods` is provided the methods
24    do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
25    method explicitly::
26
27        class MyView(View):
28            methods = ['GET']
29
30            def dispatch_request(self, name):
31                return 'Hello %s!' % name
32
33        app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
34
35    When you want to decorate a pluggable view you will have to either do that
36    when the view function is created (by wrapping the return value of
37    :meth:`as_view`) or you can use the :attr:`decorators` attribute::
38
39        class SecretView(View):
40            methods = ['GET']
41            decorators = [superuser_required]
42
43            def dispatch_request(self):
44                ...
45
46    The decorators stored in the decorators list are applied one after another
47    when the view function is created.  Note that you can *not* use the class
48    based decorators since those would decorate the view class and not the
49    generated view function!
50    """
51
52    #: A list of methods this view can handle.
53    methods = None
54
55    #: Setting this disables or force-enables the automatic options handling.
56    provide_automatic_options = None
57
58    #: The canonical way to decorate class-based views is to decorate the
59    #: return value of as_view().  However since this moves parts of the
60    #: logic from the class declaration to the place where it's hooked
61    #: into the routing system.
62    #:
63    #: You can place one or more decorators in this list and whenever the
64    #: view function is created the result is automatically decorated.
65    #:
66    #: .. versionadded:: 0.8
67    decorators = ()
68
69    def dispatch_request(self):
70        """Subclasses have to override this method to implement the
71        actual view function code.  This method is called with all
72        the arguments from the URL rule.
73        """
74        raise NotImplementedError()
75
76    @classmethod
77    def as_view(cls, name, *class_args, **class_kwargs):
78        """Converts the class into an actual view function that can be used
79        with the routing system.  Internally this generates a function on the
80        fly which will instantiate the :class:`View` on each request and call
81        the :meth:`dispatch_request` method on it.
82
83        The arguments passed to :meth:`as_view` are forwarded to the
84        constructor of the class.
85        """
86
87        def view(*args, **kwargs):
88            self = view.view_class(*class_args, **class_kwargs)
89            return self.dispatch_request(*args, **kwargs)
90
91        if cls.decorators:
92            view.__name__ = name
93            view.__module__ = cls.__module__
94            for decorator in cls.decorators:
95                view = decorator(view)
96
97        # We attach the view class to the view function for two reasons:
98        # first of all it allows us to easily figure out what class-based
99        # view this thing came from, secondly it's also used for instantiating
100        # the view class so you can actually replace it with something else
101        # for testing purposes and debugging.
102        view.view_class = cls
103        view.__name__ = name
104        view.__doc__ = cls.__doc__
105        view.__module__ = cls.__module__
106        view.methods = cls.methods
107        view.provide_automatic_options = cls.provide_automatic_options
108        return view
109
110
111class MethodViewType(type):
112    """Metaclass for :class:`MethodView` that determines what methods the view
113    defines.
114    """
115
116    def __init__(cls, name, bases, d):
117        super(MethodViewType, cls).__init__(name, bases, d)
118
119        if "methods" not in d:
120            methods = set()
121
122            for base in bases:
123                if getattr(base, "methods", None):
124                    methods.update(base.methods)
125
126            for key in http_method_funcs:
127                if hasattr(cls, key):
128                    methods.add(key.upper())
129
130            # If we have no method at all in there we don't want to add a
131            # method list. This is for instance the case for the base class
132            # or another subclass of a base method view that does not introduce
133            # new methods.
134            if methods:
135                cls.methods = methods
136
137
138class MethodView(with_metaclass(MethodViewType, View)):
139    """A class-based view that dispatches request methods to the corresponding
140    class methods. For example, if you implement a ``get`` method, it will be
141    used to handle ``GET`` requests. ::
142
143        class CounterAPI(MethodView):
144            def get(self):
145                return session.get('counter', 0)
146
147            def post(self):
148                session['counter'] = session.get('counter', 0) + 1
149                return 'OK'
150
151        app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
152    """
153
154    def dispatch_request(self, *args, **kwargs):
155        meth = getattr(self, request.method.lower(), None)
156
157        # If the request method is HEAD and we don't have a handler for it
158        # retry with GET.
159        if meth is None and request.method == "HEAD":
160            meth = getattr(self, "get", None)
161
162        assert meth is not None, "Unimplemented method %r" % request.method
163        return meth(*args, **kwargs)
164