decorators.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. "Functions that help with dynamically creating decorators for views."
  2. from functools import wraps, update_wrapper, WRAPPER_ASSIGNMENTS
  3. from django.utils import six
  4. class classonlymethod(classmethod):
  5. def __get__(self, instance, owner):
  6. if instance is not None:
  7. raise AttributeError("This method is available only on the view class.")
  8. return super(classonlymethod, self).__get__(instance, owner)
  9. def method_decorator(decorator):
  10. """
  11. Converts a function decorator into a method decorator
  12. """
  13. # 'func' is a function at the time it is passed to _dec, but will eventually
  14. # be a method of the class it is defined it.
  15. def _dec(func):
  16. def _wrapper(self, *args, **kwargs):
  17. @decorator
  18. def bound_func(*args2, **kwargs2):
  19. return func.__get__(self, type(self))(*args2, **kwargs2)
  20. # bound_func has the signature that 'decorator' expects i.e. no
  21. # 'self' argument, but it is a closure over self so it can call
  22. # 'func' correctly.
  23. return bound_func(*args, **kwargs)
  24. # In case 'decorator' adds attributes to the function it decorates, we
  25. # want to copy those. We don't have access to bound_func in this scope,
  26. # but we can cheat by using it on a dummy function.
  27. @decorator
  28. def dummy(*args, **kwargs):
  29. pass
  30. update_wrapper(_wrapper, dummy)
  31. # Need to preserve any existing attributes of 'func', including the name.
  32. update_wrapper(_wrapper, func)
  33. return _wrapper
  34. update_wrapper(_dec, decorator, assigned=available_attrs(decorator))
  35. # Change the name to aid debugging.
  36. if hasattr(decorator, '__name__'):
  37. _dec.__name__ = 'method_decorator(%s)' % decorator.__name__
  38. else:
  39. _dec.__name__ = 'method_decorator(%s)' % decorator.__class__.__name__
  40. return _dec
  41. def decorator_from_middleware_with_args(middleware_class):
  42. """
  43. Like decorator_from_middleware, but returns a function
  44. that accepts the arguments to be passed to the middleware_class.
  45. Use like::
  46. cache_page = decorator_from_middleware_with_args(CacheMiddleware)
  47. # ...
  48. @cache_page(3600)
  49. def my_view(request):
  50. # ...
  51. """
  52. return make_middleware_decorator(middleware_class)
  53. def decorator_from_middleware(middleware_class):
  54. """
  55. Given a middleware class (not an instance), returns a view decorator. This
  56. lets you use middleware functionality on a per-view basis. The middleware
  57. is created with no params passed.
  58. """
  59. return make_middleware_decorator(middleware_class)()
  60. def available_attrs(fn):
  61. """
  62. Return the list of functools-wrappable attributes on a callable.
  63. This is required as a workaround for http://bugs.python.org/issue3445
  64. under Python 2.
  65. """
  66. if six.PY3:
  67. return WRAPPER_ASSIGNMENTS
  68. else:
  69. return tuple(a for a in WRAPPER_ASSIGNMENTS if hasattr(fn, a))
  70. def make_middleware_decorator(middleware_class):
  71. def _make_decorator(*m_args, **m_kwargs):
  72. middleware = middleware_class(*m_args, **m_kwargs)
  73. def _decorator(view_func):
  74. @wraps(view_func, assigned=available_attrs(view_func))
  75. def _wrapped_view(request, *args, **kwargs):
  76. if hasattr(middleware, 'process_request'):
  77. result = middleware.process_request(request)
  78. if result is not None:
  79. return result
  80. if hasattr(middleware, 'process_view'):
  81. result = middleware.process_view(request, view_func, args, kwargs)
  82. if result is not None:
  83. return result
  84. try:
  85. response = view_func(request, *args, **kwargs)
  86. except Exception as e:
  87. if hasattr(middleware, 'process_exception'):
  88. result = middleware.process_exception(request, e)
  89. if result is not None:
  90. return result
  91. raise
  92. if hasattr(response, 'render') and callable(response.render):
  93. if hasattr(middleware, 'process_template_response'):
  94. response = middleware.process_template_response(request, response)
  95. # Defer running of process_response until after the template
  96. # has been rendered:
  97. if hasattr(middleware, 'process_response'):
  98. callback = lambda response: middleware.process_response(request, response)
  99. response.add_post_render_callback(callback)
  100. else:
  101. if hasattr(middleware, 'process_response'):
  102. return middleware.process_response(request, response)
  103. return response
  104. return _wrapped_view
  105. return _decorator
  106. return _make_decorator