123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- import time
- from django.core.exceptions import ImproperlyConfigured
- from django.http import HttpResponse
- from django.test import RequestFactory, SimpleTestCase, override_settings
- from django.test.utils import require_jinja2
- from django.urls import resolve
- from django.views.generic import RedirectView, TemplateView, View
- from . import views
- class SimpleView(View):
- """
- A simple view with a docstring.
- """
- def get(self, request):
- return HttpResponse('This is a simple view')
- class SimplePostView(SimpleView):
- post = SimpleView.get
- class PostOnlyView(View):
- def post(self, request):
- return HttpResponse('This view only accepts POST')
- class CustomizableView(SimpleView):
- parameter = {}
- def decorator(view):
- view.is_decorated = True
- return view
- class DecoratedDispatchView(SimpleView):
- @decorator
- def dispatch(self, request, *args, **kwargs):
- return super().dispatch(request, *args, **kwargs)
- class AboutTemplateView(TemplateView):
- def get(self, request):
- return self.render_to_response({})
- def get_template_names(self):
- return ['generic_views/about.html']
- class AboutTemplateAttributeView(TemplateView):
- template_name = 'generic_views/about.html'
- def get(self, request):
- return self.render_to_response(context={})
- class InstanceView(View):
- def get(self, request):
- return self
- class ViewTest(SimpleTestCase):
- rf = RequestFactory()
- def _assert_simple(self, response):
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, b'This is a simple view')
- def test_no_init_kwargs(self):
- """
- A view can't be accidentally instantiated before deployment
- """
- msg = 'This method is available only on the class, not on instances.'
- with self.assertRaisesMessage(AttributeError, msg):
- SimpleView(key='value').as_view()
- def test_no_init_args(self):
- """
- A view can't be accidentally instantiated before deployment
- """
- msg = 'as_view() takes 1 positional argument but 2 were given'
- with self.assertRaisesMessage(TypeError, msg):
- SimpleView.as_view('value')
- def test_pathological_http_method(self):
- """
- The edge case of a http request that spoofs an existing method name is caught.
- """
- self.assertEqual(SimpleView.as_view()(
- self.rf.get('/', REQUEST_METHOD='DISPATCH')
- ).status_code, 405)
- def test_get_only(self):
- """
- Test a view which only allows GET doesn't allow other methods.
- """
- self._assert_simple(SimpleView.as_view()(self.rf.get('/')))
- self.assertEqual(SimpleView.as_view()(self.rf.post('/')).status_code, 405)
- self.assertEqual(SimpleView.as_view()(
- self.rf.get('/', REQUEST_METHOD='FAKE')
- ).status_code, 405)
- def test_get_and_head(self):
- """
- Test a view which supplies a GET method also responds correctly to HEAD.
- """
- self._assert_simple(SimpleView.as_view()(self.rf.get('/')))
- response = SimpleView.as_view()(self.rf.head('/'))
- self.assertEqual(response.status_code, 200)
- def test_setup_get_and_head(self):
- view_instance = SimpleView()
- self.assertFalse(hasattr(view_instance, 'head'))
- view_instance.setup(self.rf.get('/'))
- self.assertTrue(hasattr(view_instance, 'head'))
- self.assertEqual(view_instance.head, view_instance.get)
- def test_head_no_get(self):
- """
- Test a view which supplies no GET method responds to HEAD with HTTP 405.
- """
- response = PostOnlyView.as_view()(self.rf.head('/'))
- self.assertEqual(response.status_code, 405)
- def test_get_and_post(self):
- """
- Test a view which only allows both GET and POST.
- """
- self._assert_simple(SimplePostView.as_view()(self.rf.get('/')))
- self._assert_simple(SimplePostView.as_view()(self.rf.post('/')))
- self.assertEqual(SimplePostView.as_view()(
- self.rf.get('/', REQUEST_METHOD='FAKE')
- ).status_code, 405)
- def test_invalid_keyword_argument(self):
- """
- View arguments must be predefined on the class and can't
- be named like a HTTP method.
- """
- msg = (
- 'The method name %s is not accepted as a keyword argument to '
- 'SimpleView().'
- )
- # Check each of the allowed method names
- for method in SimpleView.http_method_names:
- with self.assertRaisesMessage(TypeError, msg % method):
- SimpleView.as_view(**{method: 'value'})
- # Check the case view argument is ok if predefined on the class...
- CustomizableView.as_view(parameter="value")
- # ...but raises errors otherwise.
- msg = (
- "CustomizableView() received an invalid keyword 'foobar'. "
- "as_view only accepts arguments that are already attributes of "
- "the class."
- )
- with self.assertRaisesMessage(TypeError, msg):
- CustomizableView.as_view(foobar="value")
- def test_calling_more_than_once(self):
- """
- Test a view can only be called once.
- """
- request = self.rf.get('/')
- view = InstanceView.as_view()
- self.assertNotEqual(view(request), view(request))
- def test_class_attributes(self):
- """
- The callable returned from as_view() has proper
- docstring, name and module.
- """
- self.assertEqual(SimpleView.__doc__, SimpleView.as_view().__doc__)
- self.assertEqual(SimpleView.__name__, SimpleView.as_view().__name__)
- self.assertEqual(SimpleView.__module__, SimpleView.as_view().__module__)
- def test_dispatch_decoration(self):
- """
- Attributes set by decorators on the dispatch method
- are also present on the closure.
- """
- self.assertTrue(DecoratedDispatchView.as_view().is_decorated)
- def test_options(self):
- """
- Views respond to HTTP OPTIONS requests with an Allow header
- appropriate for the methods implemented by the view class.
- """
- request = self.rf.options('/')
- view = SimpleView.as_view()
- response = view(request)
- self.assertEqual(200, response.status_code)
- self.assertTrue(response['Allow'])
- def test_options_for_get_view(self):
- """
- A view implementing GET allows GET and HEAD.
- """
- request = self.rf.options('/')
- view = SimpleView.as_view()
- response = view(request)
- self._assert_allows(response, 'GET', 'HEAD')
- def test_options_for_get_and_post_view(self):
- """
- A view implementing GET and POST allows GET, HEAD, and POST.
- """
- request = self.rf.options('/')
- view = SimplePostView.as_view()
- response = view(request)
- self._assert_allows(response, 'GET', 'HEAD', 'POST')
- def test_options_for_post_view(self):
- """
- A view implementing POST allows POST.
- """
- request = self.rf.options('/')
- view = PostOnlyView.as_view()
- response = view(request)
- self._assert_allows(response, 'POST')
- def _assert_allows(self, response, *expected_methods):
- "Assert allowed HTTP methods reported in the Allow response header"
- response_allows = set(response['Allow'].split(', '))
- self.assertEqual(set(expected_methods + ('OPTIONS',)), response_allows)
- def test_args_kwargs_request_on_self(self):
- """
- Test a view only has args, kwargs & request once `as_view`
- has been called.
- """
- bare_view = InstanceView()
- view = InstanceView.as_view()(self.rf.get('/'))
- for attribute in ('args', 'kwargs', 'request'):
- self.assertNotIn(attribute, dir(bare_view))
- self.assertIn(attribute, dir(view))
- def test_overridden_setup(self):
- class SetAttributeMixin:
- def setup(self, request, *args, **kwargs):
- self.attr = True
- super().setup(request, *args, **kwargs)
- class CheckSetupView(SetAttributeMixin, SimpleView):
- def dispatch(self, request, *args, **kwargs):
- assert hasattr(self, 'attr')
- return super().dispatch(request, *args, **kwargs)
- response = CheckSetupView.as_view()(self.rf.get('/'))
- self.assertEqual(response.status_code, 200)
- def test_not_calling_parent_setup_error(self):
- class TestView(View):
- def setup(self, request, *args, **kwargs):
- pass # Not calling super().setup()
- msg = (
- "TestView instance has no 'request' attribute. Did you override "
- "setup() and forget to call super()?"
- )
- with self.assertRaisesMessage(AttributeError, msg):
- TestView.as_view()(self.rf.get('/'))
- def test_setup_adds_args_kwargs_request(self):
- request = self.rf.get('/')
- args = ('arg 1', 'arg 2')
- kwargs = {'kwarg_1': 1, 'kwarg_2': 'year'}
- view = View()
- view.setup(request, *args, **kwargs)
- self.assertEqual(request, view.request)
- self.assertEqual(args, view.args)
- self.assertEqual(kwargs, view.kwargs)
- def test_direct_instantiation(self):
- """
- It should be possible to use the view by directly instantiating it
- without going through .as_view() (#21564).
- """
- view = PostOnlyView()
- response = view.dispatch(self.rf.head('/'))
- self.assertEqual(response.status_code, 405)
- @override_settings(ROOT_URLCONF='generic_views.urls')
- class TemplateViewTest(SimpleTestCase):
- rf = RequestFactory()
- def _assert_about(self, response):
- response.render()
- self.assertContains(response, '<h1>About</h1>')
- def test_get(self):
- """
- Test a view that simply renders a template on GET
- """
- self._assert_about(AboutTemplateView.as_view()(self.rf.get('/about/')))
- def test_head(self):
- """
- Test a TemplateView responds correctly to HEAD
- """
- response = AboutTemplateView.as_view()(self.rf.head('/about/'))
- self.assertEqual(response.status_code, 200)
- def test_get_template_attribute(self):
- """
- Test a view that renders a template on GET with the template name as
- an attribute on the class.
- """
- self._assert_about(AboutTemplateAttributeView.as_view()(self.rf.get('/about/')))
- def test_get_generic_template(self):
- """
- Test a completely generic view that renders a template on GET
- with the template name as an argument at instantiation.
- """
- self._assert_about(TemplateView.as_view(template_name='generic_views/about.html')(self.rf.get('/about/')))
- def test_template_name_required(self):
- """
- A template view must provide a template name.
- """
- msg = (
- "TemplateResponseMixin requires either a definition of "
- "'template_name' or an implementation of 'get_template_names()'"
- )
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- self.client.get('/template/no_template/')
- @require_jinja2
- def test_template_engine(self):
- """
- A template view may provide a template engine.
- """
- request = self.rf.get('/using/')
- view = TemplateView.as_view(template_name='generic_views/using.html')
- self.assertEqual(view(request).render().content, b'DTL\n')
- view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='django')
- self.assertEqual(view(request).render().content, b'DTL\n')
- view = TemplateView.as_view(template_name='generic_views/using.html', template_engine='jinja2')
- self.assertEqual(view(request).render().content, b'Jinja2\n')
- def test_template_params(self):
- """
- A generic template view passes kwargs as context.
- """
- response = self.client.get('/template/simple/bar/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['foo'], 'bar')
- self.assertIsInstance(response.context['view'], View)
- def test_extra_template_params(self):
- """
- A template view can be customized to return extra context.
- """
- response = self.client.get('/template/custom/bar/')
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response.context['foo'], 'bar')
- self.assertEqual(response.context['key'], 'value')
- self.assertIsInstance(response.context['view'], View)
- def test_cached_views(self):
- """
- A template view can be cached
- """
- response = self.client.get('/template/cached/bar/')
- self.assertEqual(response.status_code, 200)
- time.sleep(1.0)
- response2 = self.client.get('/template/cached/bar/')
- self.assertEqual(response2.status_code, 200)
- self.assertEqual(response.content, response2.content)
- time.sleep(2.0)
- # Let the cache expire and test again
- response2 = self.client.get('/template/cached/bar/')
- self.assertEqual(response2.status_code, 200)
- self.assertNotEqual(response.content, response2.content)
- def test_content_type(self):
- response = self.client.get('/template/content_type/')
- self.assertEqual(response['Content-Type'], 'text/plain')
- def test_resolve_view(self):
- match = resolve('/template/content_type/')
- self.assertIs(match.func.view_class, TemplateView)
- self.assertEqual(match.func.view_initkwargs['content_type'], 'text/plain')
- def test_resolve_login_required_view(self):
- match = resolve('/template/login_required/')
- self.assertIs(match.func.view_class, TemplateView)
- def test_extra_context(self):
- response = self.client.get('/template/extra_context/')
- self.assertEqual(response.context['title'], 'Title')
- @override_settings(ROOT_URLCONF='generic_views.urls')
- class RedirectViewTest(SimpleTestCase):
- rf = RequestFactory()
- def test_no_url(self):
- "Without any configuration, returns HTTP 410 GONE"
- response = RedirectView.as_view()(self.rf.get('/foo/'))
- self.assertEqual(response.status_code, 410)
- def test_default_redirect(self):
- "Default is a temporary redirect"
- response = RedirectView.as_view(url='/bar/')(self.rf.get('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_permanent_redirect(self):
- "Permanent redirects are an option"
- response = RedirectView.as_view(url='/bar/', permanent=True)(self.rf.get('/foo/'))
- self.assertEqual(response.status_code, 301)
- self.assertEqual(response.url, '/bar/')
- def test_temporary_redirect(self):
- "Temporary redirects are an option"
- response = RedirectView.as_view(url='/bar/', permanent=False)(self.rf.get('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_include_args(self):
- "GET arguments can be included in the redirected URL"
- response = RedirectView.as_view(url='/bar/')(self.rf.get('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- response = RedirectView.as_view(url='/bar/', query_string=True)(self.rf.get('/foo/?pork=spam'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/?pork=spam')
- def test_include_urlencoded_args(self):
- "GET arguments can be URL-encoded when included in the redirected URL"
- response = RedirectView.as_view(url='/bar/', query_string=True)(
- self.rf.get('/foo/?unicode=%E2%9C%93'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/?unicode=%E2%9C%93')
- def test_parameter_substitution(self):
- "Redirection URLs can be parameterized"
- response = RedirectView.as_view(url='/bar/%(object_id)d/')(self.rf.get('/foo/42/'), object_id=42)
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/42/')
- def test_named_url_pattern(self):
- "Named pattern parameter should reverse to the matching pattern"
- response = RedirectView.as_view(pattern_name='artist_detail')(self.rf.get('/foo/'), pk=1)
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response['Location'], '/detail/artist/1/')
- def test_named_url_pattern_using_args(self):
- response = RedirectView.as_view(pattern_name='artist_detail')(self.rf.get('/foo/'), 1)
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response['Location'], '/detail/artist/1/')
- def test_redirect_POST(self):
- "Default is a temporary redirect"
- response = RedirectView.as_view(url='/bar/')(self.rf.post('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_redirect_HEAD(self):
- "Default is a temporary redirect"
- response = RedirectView.as_view(url='/bar/')(self.rf.head('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_redirect_OPTIONS(self):
- "Default is a temporary redirect"
- response = RedirectView.as_view(url='/bar/')(self.rf.options('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_redirect_PUT(self):
- "Default is a temporary redirect"
- response = RedirectView.as_view(url='/bar/')(self.rf.put('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_redirect_PATCH(self):
- "Default is a temporary redirect"
- response = RedirectView.as_view(url='/bar/')(self.rf.patch('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_redirect_DELETE(self):
- "Default is a temporary redirect"
- response = RedirectView.as_view(url='/bar/')(self.rf.delete('/foo/'))
- self.assertEqual(response.status_code, 302)
- self.assertEqual(response.url, '/bar/')
- def test_redirect_when_meta_contains_no_query_string(self):
- "regression for #16705"
- # we can't use self.rf.get because it always sets QUERY_STRING
- response = RedirectView.as_view(url='/bar/')(self.rf.request(PATH_INFO='/foo/'))
- self.assertEqual(response.status_code, 302)
- def test_direct_instantiation(self):
- """
- It should be possible to use the view without going through .as_view()
- (#21564).
- """
- view = RedirectView()
- response = view.dispatch(self.rf.head('/foo/'))
- self.assertEqual(response.status_code, 410)
- class GetContextDataTest(SimpleTestCase):
- def test_get_context_data_super(self):
- test_view = views.CustomContextView()
- context = test_view.get_context_data(kwarg_test='kwarg_value')
- # the test_name key is inserted by the test classes parent
- self.assertIn('test_name', context)
- self.assertEqual(context['kwarg_test'], 'kwarg_value')
- self.assertEqual(context['custom_key'], 'custom_value')
- # test that kwarg overrides values assigned higher up
- context = test_view.get_context_data(test_name='test_value')
- self.assertEqual(context['test_name'], 'test_value')
- def test_object_at_custom_name_in_context_data(self):
- # Checks 'pony' key presence in dict returned by get_context_date
- test_view = views.CustomSingleObjectView()
- test_view.context_object_name = 'pony'
- context = test_view.get_context_data()
- self.assertEqual(context['pony'], test_view.object)
- def test_object_in_get_context_data(self):
- # Checks 'object' key presence in dict returned by get_context_date #20234
- test_view = views.CustomSingleObjectView()
- context = test_view.get_context_data()
- self.assertEqual(context['object'], test_view.object)
- class UseMultipleObjectMixinTest(SimpleTestCase):
- rf = RequestFactory()
- def test_use_queryset_from_view(self):
- test_view = views.CustomMultipleObjectMixinView()
- test_view.get(self.rf.get('/'))
- # Don't pass queryset as argument
- context = test_view.get_context_data()
- self.assertEqual(context['object_list'], test_view.queryset)
- def test_overwrite_queryset(self):
- test_view = views.CustomMultipleObjectMixinView()
- test_view.get(self.rf.get('/'))
- queryset = [{'name': 'Lennon'}, {'name': 'Ono'}]
- self.assertNotEqual(test_view.queryset, queryset)
- # Overwrite the view's queryset with queryset from kwarg
- context = test_view.get_context_data(object_list=queryset)
- self.assertEqual(context['object_list'], queryset)
- class SingleObjectTemplateResponseMixinTest(SimpleTestCase):
- def test_template_mixin_without_template(self):
- """
- We want to makes sure that if you use a template mixin, but forget the
- template, it still tells you it's ImproperlyConfigured instead of
- TemplateDoesNotExist.
- """
- view = views.TemplateResponseWithoutTemplate()
- msg = (
- "TemplateResponseMixin requires either a definition of "
- "'template_name' or an implementation of 'get_template_names()'"
- )
- with self.assertRaisesMessage(ImproperlyConfigured, msg):
- view.get_template_names()
|