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