views.py 5.6 KB

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