tests.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. from functools import update_wrapper, wraps
  2. from unittest import TestCase
  3. from django.contrib.admin.views.decorators import staff_member_required
  4. from django.contrib.auth.decorators import (
  5. login_required,
  6. permission_required,
  7. user_passes_test,
  8. )
  9. from django.http import HttpResponse
  10. from django.test import SimpleTestCase
  11. from django.utils.decorators import method_decorator
  12. from django.utils.functional import keep_lazy, keep_lazy_text, lazy
  13. from django.utils.safestring import mark_safe
  14. from django.views.decorators.cache import cache_control, cache_page, never_cache
  15. from django.views.decorators.http import (
  16. condition,
  17. require_GET,
  18. require_http_methods,
  19. require_POST,
  20. require_safe,
  21. )
  22. from django.views.decorators.vary import vary_on_cookie, vary_on_headers
  23. def fully_decorated(request):
  24. """Expected __doc__"""
  25. return HttpResponse("<html><body>dummy</body></html>")
  26. fully_decorated.anything = "Expected __dict__"
  27. def compose(*functions):
  28. # compose(f, g)(*args, **kwargs) == f(g(*args, **kwargs))
  29. functions = list(reversed(functions))
  30. def _inner(*args, **kwargs):
  31. result = functions[0](*args, **kwargs)
  32. for f in functions[1:]:
  33. result = f(result)
  34. return result
  35. return _inner
  36. full_decorator = compose(
  37. # django.views.decorators.http
  38. require_http_methods(["GET"]),
  39. require_GET,
  40. require_POST,
  41. require_safe,
  42. condition(lambda r: None, lambda r: None),
  43. # django.views.decorators.vary
  44. vary_on_headers("Accept-language"),
  45. vary_on_cookie,
  46. # django.views.decorators.cache
  47. cache_page(60 * 15),
  48. cache_control(private=True),
  49. never_cache,
  50. # django.contrib.auth.decorators
  51. # Apply user_passes_test twice to check #9474
  52. user_passes_test(lambda u: True),
  53. login_required,
  54. permission_required("change_world"),
  55. # django.contrib.admin.views.decorators
  56. staff_member_required,
  57. # django.utils.functional
  58. keep_lazy(HttpResponse),
  59. keep_lazy_text,
  60. lazy,
  61. # django.utils.safestring
  62. mark_safe,
  63. )
  64. fully_decorated = full_decorator(fully_decorated)
  65. class DecoratorsTest(TestCase):
  66. def test_attributes(self):
  67. """
  68. Built-in decorators set certain attributes of the wrapped function.
  69. """
  70. self.assertEqual(fully_decorated.__name__, "fully_decorated")
  71. self.assertEqual(fully_decorated.__doc__, "Expected __doc__")
  72. self.assertEqual(fully_decorated.__dict__["anything"], "Expected __dict__")
  73. def test_user_passes_test_composition(self):
  74. """
  75. The user_passes_test decorator can be applied multiple times (#9474).
  76. """
  77. def test1(user):
  78. user.decorators_applied.append("test1")
  79. return True
  80. def test2(user):
  81. user.decorators_applied.append("test2")
  82. return True
  83. def callback(request):
  84. return request.user.decorators_applied
  85. callback = user_passes_test(test1)(callback)
  86. callback = user_passes_test(test2)(callback)
  87. class DummyUser:
  88. pass
  89. class DummyRequest:
  90. pass
  91. request = DummyRequest()
  92. request.user = DummyUser()
  93. request.user.decorators_applied = []
  94. response = callback(request)
  95. self.assertEqual(response, ["test2", "test1"])
  96. # For testing method_decorator, a decorator that assumes a single argument.
  97. # We will get type arguments if there is a mismatch in the number of arguments.
  98. def simple_dec(func):
  99. @wraps(func)
  100. def wrapper(arg):
  101. return func("test:" + arg)
  102. return wrapper
  103. simple_dec_m = method_decorator(simple_dec)
  104. # For testing method_decorator, two decorators that add an attribute to the function
  105. def myattr_dec(func):
  106. def wrapper(*args, **kwargs):
  107. return func(*args, **kwargs)
  108. wrapper.myattr = True
  109. return wrapper
  110. myattr_dec_m = method_decorator(myattr_dec)
  111. def myattr2_dec(func):
  112. def wrapper(*args, **kwargs):
  113. return func(*args, **kwargs)
  114. wrapper.myattr2 = True
  115. return wrapper
  116. myattr2_dec_m = method_decorator(myattr2_dec)
  117. class ClsDec:
  118. def __init__(self, myattr):
  119. self.myattr = myattr
  120. def __call__(self, f):
  121. def wrapper():
  122. return f() and self.myattr
  123. return update_wrapper(wrapper, f)
  124. class MethodDecoratorTests(SimpleTestCase):
  125. """
  126. Tests for method_decorator
  127. """
  128. def test_preserve_signature(self):
  129. class Test:
  130. @simple_dec_m
  131. def say(self, arg):
  132. return arg
  133. self.assertEqual("test:hello", Test().say("hello"))
  134. def test_preserve_attributes(self):
  135. # Sanity check myattr_dec and myattr2_dec
  136. @myattr_dec
  137. def func():
  138. pass
  139. self.assertIs(getattr(func, "myattr", False), True)
  140. @myattr2_dec
  141. def func():
  142. pass
  143. self.assertIs(getattr(func, "myattr2", False), True)
  144. @myattr_dec
  145. @myattr2_dec
  146. def func():
  147. pass
  148. self.assertIs(getattr(func, "myattr", False), True)
  149. self.assertIs(getattr(func, "myattr2", False), False)
  150. # Decorate using method_decorator() on the method.
  151. class TestPlain:
  152. @myattr_dec_m
  153. @myattr2_dec_m
  154. def method(self):
  155. "A method"
  156. pass
  157. # Decorate using method_decorator() on both the class and the method.
  158. # The decorators applied to the methods are applied before the ones
  159. # applied to the class.
  160. @method_decorator(myattr_dec_m, "method")
  161. class TestMethodAndClass:
  162. @method_decorator(myattr2_dec_m)
  163. def method(self):
  164. "A method"
  165. pass
  166. # Decorate using an iterable of function decorators.
  167. @method_decorator((myattr_dec, myattr2_dec), "method")
  168. class TestFunctionIterable:
  169. def method(self):
  170. "A method"
  171. pass
  172. # Decorate using an iterable of method decorators.
  173. decorators = (myattr_dec_m, myattr2_dec_m)
  174. @method_decorator(decorators, "method")
  175. class TestMethodIterable:
  176. def method(self):
  177. "A method"
  178. pass
  179. tests = (
  180. TestPlain,
  181. TestMethodAndClass,
  182. TestFunctionIterable,
  183. TestMethodIterable,
  184. )
  185. for Test in tests:
  186. with self.subTest(Test=Test):
  187. self.assertIs(getattr(Test().method, "myattr", False), True)
  188. self.assertIs(getattr(Test().method, "myattr2", False), True)
  189. self.assertIs(getattr(Test.method, "myattr", False), True)
  190. self.assertIs(getattr(Test.method, "myattr2", False), True)
  191. self.assertEqual(Test.method.__doc__, "A method")
  192. self.assertEqual(Test.method.__name__, "method")
  193. def test_new_attribute(self):
  194. """A decorator that sets a new attribute on the method."""
  195. def decorate(func):
  196. func.x = 1
  197. return func
  198. class MyClass:
  199. @method_decorator(decorate)
  200. def method(self):
  201. return True
  202. obj = MyClass()
  203. self.assertEqual(obj.method.x, 1)
  204. self.assertIs(obj.method(), True)
  205. def test_bad_iterable(self):
  206. decorators = {myattr_dec_m, myattr2_dec_m}
  207. msg = "'set' object is not subscriptable"
  208. with self.assertRaisesMessage(TypeError, msg):
  209. @method_decorator(decorators, "method")
  210. class TestIterable:
  211. def method(self):
  212. "A method"
  213. pass
  214. # Test for argumented decorator
  215. def test_argumented(self):
  216. class Test:
  217. @method_decorator(ClsDec(False))
  218. def method(self):
  219. return True
  220. self.assertIs(Test().method(), False)
  221. def test_descriptors(self):
  222. def original_dec(wrapped):
  223. def _wrapped(arg):
  224. return wrapped(arg)
  225. return _wrapped
  226. method_dec = method_decorator(original_dec)
  227. class bound_wrapper:
  228. def __init__(self, wrapped):
  229. self.wrapped = wrapped
  230. self.__name__ = wrapped.__name__
  231. def __call__(self, arg):
  232. return self.wrapped(arg)
  233. def __get__(self, instance, cls=None):
  234. return self
  235. class descriptor_wrapper:
  236. def __init__(self, wrapped):
  237. self.wrapped = wrapped
  238. self.__name__ = wrapped.__name__
  239. def __get__(self, instance, cls=None):
  240. return bound_wrapper(self.wrapped.__get__(instance, cls))
  241. class Test:
  242. @method_dec
  243. @descriptor_wrapper
  244. def method(self, arg):
  245. return arg
  246. self.assertEqual(Test().method(1), 1)
  247. def test_class_decoration(self):
  248. """
  249. @method_decorator can be used to decorate a class and its methods.
  250. """
  251. def deco(func):
  252. def _wrapper(*args, **kwargs):
  253. return True
  254. return _wrapper
  255. @method_decorator(deco, name="method")
  256. class Test:
  257. def method(self):
  258. return False
  259. self.assertTrue(Test().method())
  260. def test_tuple_of_decorators(self):
  261. """
  262. @method_decorator can accept a tuple of decorators.
  263. """
  264. def add_question_mark(func):
  265. def _wrapper(*args, **kwargs):
  266. return func(*args, **kwargs) + "?"
  267. return _wrapper
  268. def add_exclamation_mark(func):
  269. def _wrapper(*args, **kwargs):
  270. return func(*args, **kwargs) + "!"
  271. return _wrapper
  272. # The order should be consistent with the usual order in which
  273. # decorators are applied, e.g.
  274. # @add_exclamation_mark
  275. # @add_question_mark
  276. # def func():
  277. # ...
  278. decorators = (add_exclamation_mark, add_question_mark)
  279. @method_decorator(decorators, name="method")
  280. class TestFirst:
  281. def method(self):
  282. return "hello world"
  283. class TestSecond:
  284. @method_decorator(decorators)
  285. def method(self):
  286. return "hello world"
  287. self.assertEqual(TestFirst().method(), "hello world?!")
  288. self.assertEqual(TestSecond().method(), "hello world?!")
  289. def test_invalid_non_callable_attribute_decoration(self):
  290. """
  291. @method_decorator on a non-callable attribute raises an error.
  292. """
  293. msg = (
  294. "Cannot decorate 'prop' as it isn't a callable attribute of "
  295. "<class 'Test'> (1)"
  296. )
  297. with self.assertRaisesMessage(TypeError, msg):
  298. @method_decorator(lambda: None, name="prop")
  299. class Test:
  300. prop = 1
  301. @classmethod
  302. def __module__(cls):
  303. return "tests"
  304. def test_invalid_method_name_to_decorate(self):
  305. """
  306. @method_decorator on a nonexistent method raises an error.
  307. """
  308. msg = (
  309. "The keyword argument `name` must be the name of a method of the "
  310. "decorated class: <class 'Test'>. Got 'nonexistent_method' instead"
  311. )
  312. with self.assertRaisesMessage(ValueError, msg):
  313. @method_decorator(lambda: None, name="nonexistent_method")
  314. class Test:
  315. @classmethod
  316. def __module__(cls):
  317. return "tests"
  318. def test_wrapper_assignments(self):
  319. """@method_decorator preserves wrapper assignments."""
  320. func_name = None
  321. func_module = None
  322. def decorator(func):
  323. @wraps(func)
  324. def inner(*args, **kwargs):
  325. nonlocal func_name, func_module
  326. func_name = getattr(func, "__name__", None)
  327. func_module = getattr(func, "__module__", None)
  328. return func(*args, **kwargs)
  329. return inner
  330. class Test:
  331. @method_decorator(decorator)
  332. def method(self):
  333. return "tests"
  334. Test().method()
  335. self.assertEqual(func_name, "method")
  336. self.assertIsNotNone(func_module)