test_base.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. from __future__ import unicode_literals
  2. import time
  3. import unittest
  4. from django.core.exceptions import ImproperlyConfigured
  5. from django.http import HttpResponse
  6. from django.test import TestCase, RequestFactory, override_settings
  7. from django.views.generic import View, TemplateView, RedirectView
  8. from . import views
  9. class SimpleView(View):
  10. """
  11. A simple view with a docstring.
  12. """
  13. def get(self, request):
  14. return HttpResponse('This is a simple view')
  15. class SimplePostView(SimpleView):
  16. post = SimpleView.get
  17. class PostOnlyView(View):
  18. def post(self, request):
  19. return HttpResponse('This view only accepts POST')
  20. class CustomizableView(SimpleView):
  21. parameter = {}
  22. def decorator(view):
  23. view.is_decorated = True
  24. return view
  25. class DecoratedDispatchView(SimpleView):
  26. @decorator
  27. def dispatch(self, request, *args, **kwargs):
  28. return super(DecoratedDispatchView, self).dispatch(request, *args, **kwargs)
  29. class AboutTemplateView(TemplateView):
  30. def get(self, request):
  31. return self.render_to_response({})
  32. def get_template_names(self):
  33. return ['generic_views/about.html']
  34. class AboutTemplateAttributeView(TemplateView):
  35. template_name = 'generic_views/about.html'
  36. def get(self, request):
  37. return self.render_to_response(context={})
  38. class InstanceView(View):
  39. def get(self, request):
  40. return self
  41. class ViewTest(unittest.TestCase):
  42. rf = RequestFactory()
  43. def _assert_simple(self, response):
  44. self.assertEqual(response.status_code, 200)
  45. self.assertEqual(response.content, b'This is a simple view')
  46. def test_no_init_kwargs(self):
  47. """
  48. Test that a view can't be accidentally instantiated before deployment
  49. """
  50. try:
  51. SimpleView(key='value').as_view()
  52. self.fail('Should not be able to instantiate a view')
  53. except AttributeError:
  54. pass
  55. def test_no_init_args(self):
  56. """
  57. Test that a view can't be accidentally instantiated before deployment
  58. """
  59. try:
  60. SimpleView.as_view('value')
  61. self.fail('Should not be able to use non-keyword arguments instantiating a view')
  62. except TypeError:
  63. pass
  64. def test_pathological_http_method(self):
  65. """
  66. The edge case of a http request that spoofs an existing method name is caught.
  67. """
  68. self.assertEqual(SimpleView.as_view()(
  69. self.rf.get('/', REQUEST_METHOD='DISPATCH')
  70. ).status_code, 405)
  71. def test_get_only(self):
  72. """
  73. Test a view which only allows GET doesn't allow other methods.
  74. """
  75. self._assert_simple(SimpleView.as_view()(self.rf.get('/')))
  76. self.assertEqual(SimpleView.as_view()(self.rf.post('/')).status_code, 405)
  77. self.assertEqual(SimpleView.as_view()(
  78. self.rf.get('/', REQUEST_METHOD='FAKE')
  79. ).status_code, 405)
  80. def test_get_and_head(self):
  81. """
  82. Test a view which supplies a GET method also responds correctly to HEAD.
  83. """
  84. self._assert_simple(SimpleView.as_view()(self.rf.get('/')))
  85. response = SimpleView.as_view()(self.rf.head('/'))
  86. self.assertEqual(response.status_code, 200)
  87. def test_head_no_get(self):
  88. """
  89. Test a view which supplies no GET method responds to HEAD with HTTP 405.
  90. """
  91. response = PostOnlyView.as_view()(self.rf.head('/'))
  92. self.assertEqual(response.status_code, 405)
  93. def test_get_and_post(self):
  94. """
  95. Test a view which only allows both GET and POST.
  96. """
  97. self._assert_simple(SimplePostView.as_view()(self.rf.get('/')))
  98. self._assert_simple(SimplePostView.as_view()(self.rf.post('/')))
  99. self.assertEqual(SimplePostView.as_view()(
  100. self.rf.get('/', REQUEST_METHOD='FAKE')
  101. ).status_code, 405)
  102. def test_invalid_keyword_argument(self):
  103. """
  104. Test that view arguments must be predefined on the class and can't
  105. be named like a HTTP method.
  106. """
  107. # Check each of the allowed method names
  108. for method in SimpleView.http_method_names:
  109. kwargs = dict(((method, "value"),))
  110. self.assertRaises(TypeError, SimpleView.as_view, **kwargs)
  111. # Check the case view argument is ok if predefined on the class...
  112. CustomizableView.as_view(parameter="value")
  113. # ...but raises errors otherwise.
  114. self.assertRaises(TypeError, CustomizableView.as_view, foobar="value")
  115. def test_calling_more_than_once(self):
  116. """
  117. Test a view can only be called once.
  118. """
  119. request = self.rf.get('/')
  120. view = InstanceView.as_view()
  121. self.assertNotEqual(view(request), view(request))
  122. def test_class_attributes(self):
  123. """
  124. Test that the callable returned from as_view() has proper
  125. docstring, name and module.
  126. """
  127. self.assertEqual(SimpleView.__doc__, SimpleView.as_view().__doc__)
  128. self.assertEqual(SimpleView.__name__, SimpleView.as_view().__name__)
  129. self.assertEqual(SimpleView.__module__, SimpleView.as_view().__module__)
  130. def test_dispatch_decoration(self):
  131. """
  132. Test that attributes set by decorators on the dispatch method
  133. are also present on the closure.
  134. """
  135. self.assertTrue(DecoratedDispatchView.as_view().is_decorated)
  136. def test_options(self):
  137. """
  138. Test that views respond to HTTP OPTIONS requests with an Allow header
  139. appropriate for the methods implemented by the view class.
  140. """
  141. request = self.rf.options('/')
  142. view = SimpleView.as_view()
  143. response = view(request)
  144. self.assertEqual(200, response.status_code)
  145. self.assertTrue(response['Allow'])
  146. def test_options_for_get_view(self):
  147. """
  148. Test that a view implementing GET allows GET and HEAD.
  149. """
  150. request = self.rf.options('/')
  151. view = SimpleView.as_view()
  152. response = view(request)
  153. self._assert_allows(response, 'GET', 'HEAD')
  154. def test_options_for_get_and_post_view(self):
  155. """
  156. Test that a view implementing GET and POST allows GET, HEAD, and POST.
  157. """
  158. request = self.rf.options('/')
  159. view = SimplePostView.as_view()
  160. response = view(request)
  161. self._assert_allows(response, 'GET', 'HEAD', 'POST')
  162. def test_options_for_post_view(self):
  163. """
  164. Test that a view implementing POST allows POST.
  165. """
  166. request = self.rf.options('/')
  167. view = PostOnlyView.as_view()
  168. response = view(request)
  169. self._assert_allows(response, 'POST')
  170. def _assert_allows(self, response, *expected_methods):
  171. "Assert allowed HTTP methods reported in the Allow response header"
  172. response_allows = set(response['Allow'].split(', '))
  173. self.assertEqual(set(expected_methods + ('OPTIONS',)), response_allows)
  174. def test_args_kwargs_request_on_self(self):
  175. """
  176. Test a view only has args, kwargs & request once `as_view`
  177. has been called.
  178. """
  179. bare_view = InstanceView()
  180. view = InstanceView.as_view()(self.rf.get('/'))
  181. for attribute in ('args', 'kwargs', 'request'):
  182. self.assertNotIn(attribute, dir(bare_view))
  183. self.assertIn(attribute, dir(view))
  184. def test_direct_instantiation(self):
  185. """
  186. It should be possible to use the view by directly instantiating it
  187. without going through .as_view() (#21564).
  188. """
  189. view = PostOnlyView()
  190. response = view.dispatch(self.rf.head('/'))
  191. self.assertEqual(response.status_code, 405)
  192. @override_settings(ROOT_URLCONF='generic_views.urls')
  193. class TemplateViewTest(TestCase):
  194. rf = RequestFactory()
  195. def _assert_about(self, response):
  196. response.render()
  197. self.assertEqual(response.status_code, 200)
  198. self.assertContains(response, '<h1>About</h1>')
  199. def test_get(self):
  200. """
  201. Test a view that simply renders a template on GET
  202. """
  203. self._assert_about(AboutTemplateView.as_view()(self.rf.get('/about/')))
  204. def test_head(self):
  205. """
  206. Test a TemplateView responds correctly to HEAD
  207. """
  208. response = AboutTemplateView.as_view()(self.rf.head('/about/'))
  209. self.assertEqual(response.status_code, 200)
  210. def test_get_template_attribute(self):
  211. """
  212. Test a view that renders a template on GET with the template name as
  213. an attribute on the class.
  214. """
  215. self._assert_about(AboutTemplateAttributeView.as_view()(self.rf.get('/about/')))
  216. def test_get_generic_template(self):
  217. """
  218. Test a completely generic view that renders a template on GET
  219. with the template name as an argument at instantiation.
  220. """
  221. self._assert_about(TemplateView.as_view(template_name='generic_views/about.html')(self.rf.get('/about/')))
  222. def test_template_name_required(self):
  223. """
  224. A template view must provide a template name
  225. """
  226. self.assertRaises(ImproperlyConfigured, self.client.get, '/template/no_template/')
  227. def test_template_params(self):
  228. """
  229. A generic template view passes kwargs as context.
  230. """
  231. response = self.client.get('/template/simple/bar/')
  232. self.assertEqual(response.status_code, 200)
  233. self.assertEqual(response.context['foo'], 'bar')
  234. self.assertIsInstance(response.context['view'], View)
  235. def test_extra_template_params(self):
  236. """
  237. A template view can be customized to return extra context.
  238. """
  239. response = self.client.get('/template/custom/bar/')
  240. self.assertEqual(response.status_code, 200)
  241. self.assertEqual(response.context['foo'], 'bar')
  242. self.assertEqual(response.context['key'], 'value')
  243. self.assertIsInstance(response.context['view'], View)
  244. def test_cached_views(self):
  245. """
  246. A template view can be cached
  247. """
  248. response = self.client.get('/template/cached/bar/')
  249. self.assertEqual(response.status_code, 200)
  250. time.sleep(1.0)
  251. response2 = self.client.get('/template/cached/bar/')
  252. self.assertEqual(response2.status_code, 200)
  253. self.assertEqual(response.content, response2.content)
  254. time.sleep(2.0)
  255. # Let the cache expire and test again
  256. response2 = self.client.get('/template/cached/bar/')
  257. self.assertEqual(response2.status_code, 200)
  258. self.assertNotEqual(response.content, response2.content)
  259. def test_content_type(self):
  260. response = self.client.get('/template/content_type/')
  261. self.assertEqual(response['Content-Type'], 'text/plain')
  262. @override_settings(ROOT_URLCONF='generic_views.urls')
  263. class RedirectViewTest(TestCase):
  264. rf = RequestFactory()
  265. def test_no_url(self):
  266. "Without any configuration, returns HTTP 410 GONE"
  267. response = RedirectView.as_view()(self.rf.get('/foo/'))
  268. self.assertEqual(response.status_code, 410)
  269. def test_permanent_redirect(self):
  270. "Default is a permanent redirect"
  271. response = RedirectView.as_view(url='/bar/')(self.rf.get('/foo/'))
  272. self.assertEqual(response.status_code, 301)
  273. self.assertEqual(response.url, '/bar/')
  274. def test_temporary_redirect(self):
  275. "Permanent redirects are an option"
  276. response = RedirectView.as_view(url='/bar/', permanent=False)(self.rf.get('/foo/'))
  277. self.assertEqual(response.status_code, 302)
  278. self.assertEqual(response.url, '/bar/')
  279. def test_include_args(self):
  280. "GET arguments can be included in the redirected URL"
  281. response = RedirectView.as_view(url='/bar/')(self.rf.get('/foo/'))
  282. self.assertEqual(response.status_code, 301)
  283. self.assertEqual(response.url, '/bar/')
  284. response = RedirectView.as_view(url='/bar/', query_string=True)(self.rf.get('/foo/?pork=spam'))
  285. self.assertEqual(response.status_code, 301)
  286. self.assertEqual(response.url, '/bar/?pork=spam')
  287. def test_include_urlencoded_args(self):
  288. "GET arguments can be URL-encoded when included in the redirected URL"
  289. response = RedirectView.as_view(url='/bar/', query_string=True)(
  290. self.rf.get('/foo/?unicode=%E2%9C%93'))
  291. self.assertEqual(response.status_code, 301)
  292. self.assertEqual(response.url, '/bar/?unicode=%E2%9C%93')
  293. def test_parameter_substitution(self):
  294. "Redirection URLs can be parameterized"
  295. response = RedirectView.as_view(url='/bar/%(object_id)d/')(self.rf.get('/foo/42/'), object_id=42)
  296. self.assertEqual(response.status_code, 301)
  297. self.assertEqual(response.url, '/bar/42/')
  298. def test_named_url_pattern(self):
  299. "Named pattern parameter should reverse to the matching pattern"
  300. response = RedirectView.as_view(pattern_name='artist_detail')(self.rf.get('/foo/'), pk=1)
  301. self.assertEqual(response.status_code, 301)
  302. self.assertEqual(response['Location'], '/detail/artist/1/')
  303. def test_named_url_pattern_using_args(self):
  304. response = RedirectView.as_view(pattern_name='artist_detail')(self.rf.get('/foo/'), 1)
  305. self.assertEqual(response.status_code, 301)
  306. self.assertEqual(response['Location'], '/detail/artist/1/')
  307. def test_wrong_named_url_pattern(self):
  308. "A wrong pattern name returns 410 GONE"
  309. response = RedirectView.as_view(pattern_name='wrong.pattern_name')(self.rf.get('/foo/'))
  310. self.assertEqual(response.status_code, 410)
  311. def test_redirect_POST(self):
  312. "Default is a permanent redirect"
  313. response = RedirectView.as_view(url='/bar/')(self.rf.post('/foo/'))
  314. self.assertEqual(response.status_code, 301)
  315. self.assertEqual(response.url, '/bar/')
  316. def test_redirect_HEAD(self):
  317. "Default is a permanent redirect"
  318. response = RedirectView.as_view(url='/bar/')(self.rf.head('/foo/'))
  319. self.assertEqual(response.status_code, 301)
  320. self.assertEqual(response.url, '/bar/')
  321. def test_redirect_OPTIONS(self):
  322. "Default is a permanent redirect"
  323. response = RedirectView.as_view(url='/bar/')(self.rf.options('/foo/'))
  324. self.assertEqual(response.status_code, 301)
  325. self.assertEqual(response.url, '/bar/')
  326. def test_redirect_PUT(self):
  327. "Default is a permanent redirect"
  328. response = RedirectView.as_view(url='/bar/')(self.rf.put('/foo/'))
  329. self.assertEqual(response.status_code, 301)
  330. self.assertEqual(response.url, '/bar/')
  331. def test_redirect_PATCH(self):
  332. "Default is a permanent redirect"
  333. response = RedirectView.as_view(url='/bar/')(self.rf.patch('/foo/'))
  334. self.assertEqual(response.status_code, 301)
  335. self.assertEqual(response.url, '/bar/')
  336. def test_redirect_DELETE(self):
  337. "Default is a permanent redirect"
  338. response = RedirectView.as_view(url='/bar/')(self.rf.delete('/foo/'))
  339. self.assertEqual(response.status_code, 301)
  340. self.assertEqual(response.url, '/bar/')
  341. def test_redirect_when_meta_contains_no_query_string(self):
  342. "regression for #16705"
  343. # we can't use self.rf.get because it always sets QUERY_STRING
  344. response = RedirectView.as_view(url='/bar/')(self.rf.request(PATH_INFO='/foo/'))
  345. self.assertEqual(response.status_code, 301)
  346. def test_direct_instantiation(self):
  347. """
  348. It should be possible to use the view without going through .as_view()
  349. (#21564).
  350. """
  351. view = RedirectView()
  352. response = view.dispatch(self.rf.head('/foo/'))
  353. self.assertEqual(response.status_code, 410)
  354. class GetContextDataTest(unittest.TestCase):
  355. def test_get_context_data_super(self):
  356. test_view = views.CustomContextView()
  357. context = test_view.get_context_data(kwarg_test='kwarg_value')
  358. # the test_name key is inserted by the test classes parent
  359. self.assertTrue('test_name' in context)
  360. self.assertEqual(context['kwarg_test'], 'kwarg_value')
  361. self.assertEqual(context['custom_key'], 'custom_value')
  362. # test that kwarg overrides values assigned higher up
  363. context = test_view.get_context_data(test_name='test_value')
  364. self.assertEqual(context['test_name'], 'test_value')
  365. def test_object_at_custom_name_in_context_data(self):
  366. # Checks 'pony' key presence in dict returned by get_context_date
  367. test_view = views.CustomSingleObjectView()
  368. test_view.context_object_name = 'pony'
  369. context = test_view.get_context_data()
  370. self.assertEqual(context['pony'], test_view.object)
  371. def test_object_in_get_context_data(self):
  372. # Checks 'object' key presence in dict returned by get_context_date #20234
  373. test_view = views.CustomSingleObjectView()
  374. context = test_view.get_context_data()
  375. self.assertEqual(context['object'], test_view.object)
  376. class UseMultipleObjectMixinTest(unittest.TestCase):
  377. rf = RequestFactory()
  378. def test_use_queryset_from_view(self):
  379. test_view = views.CustomMultipleObjectMixinView()
  380. test_view.get(self.rf.get('/'))
  381. # Don't pass queryset as argument
  382. context = test_view.get_context_data()
  383. self.assertEqual(context['object_list'], test_view.queryset)
  384. def test_overwrite_queryset(self):
  385. test_view = views.CustomMultipleObjectMixinView()
  386. test_view.get(self.rf.get('/'))
  387. queryset = [{'name': 'Lennon'}, {'name': 'Ono'}]
  388. self.assertNotEqual(test_view.queryset, queryset)
  389. # Overwrite the view's queryset with queryset from kwarg
  390. context = test_view.get_context_data(object_list=queryset)
  391. self.assertEqual(context['object_list'], queryset)
  392. class SingleObjectTemplateResponseMixinTest(unittest.TestCase):
  393. def test_template_mixin_without_template(self):
  394. """
  395. We want to makes sure that if you use a template mixin, but forget the
  396. template, it still tells you it's ImproperlyConfigured instead of
  397. TemplateDoesNotExist.
  398. """
  399. view = views.TemplateResponseWithoutTemplate()
  400. self.assertRaises(ImproperlyConfigured, view.get_template_names)