summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2010-10-18 13:34:47 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2010-10-18 13:34:47 +0000
commit0fcb09455729113f64a9873ca40bffd009b9bc5f (patch)
tree39930fcdc6eddc10de50048e3f5424b05a0526bb /tests
parentfa2159f85b62375f3a1e462f174523ea5e5b691e (diff)
Fixed #6735 -- Added class-based views.
This patch is the result of the work of many people, over many years. To try and thank individuals would inevitably lead to many people being left out or forgotten -- so rather than try to give a list that will inevitably be incomplete, I'd like to thank *everybody* who contributed in any way, big or small, with coding, testing, feedback and/or documentation over the multi-year process of getting this into trunk. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14254 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
-rw-r--r--tests/regressiontests/generic_views/__init__.py0
-rw-r--r--tests/regressiontests/generic_views/base.py233
-rw-r--r--tests/regressiontests/generic_views/dates.py352
-rw-r--r--tests/regressiontests/generic_views/detail.py71
-rw-r--r--tests/regressiontests/generic_views/edit.py233
-rw-r--r--tests/regressiontests/generic_views/fixtures/generic-views-test-data.json47
-rw-r--r--tests/regressiontests/generic_views/forms.py11
-rw-r--r--tests/regressiontests/generic_views/list.py129
-rw-r--r--tests/regressiontests/generic_views/models.py41
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/about.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/apple_detail.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/artist_detail.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/artist_form.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/author_confirm_delete.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/author_detail.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/author_form.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/author_list.html3
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/author_objects.html3
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/author_view.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/book_archive.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/book_archive_day.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/book_archive_month.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/book_archive_week.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/book_archive_year.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/book_detail.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/book_list.html3
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/confirm_delete.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/detail.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/form.html1
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/list.html3
-rw-r--r--tests/regressiontests/generic_views/templates/generic_views/page_template.html1
-rw-r--r--tests/regressiontests/generic_views/templates/registration/login.html1
-rw-r--r--tests/regressiontests/generic_views/tests.py5
-rw-r--r--tests/regressiontests/generic_views/urls.py186
-rw-r--r--tests/regressiontests/generic_views/views.py145
35 files changed, 1484 insertions, 0 deletions
diff --git a/tests/regressiontests/generic_views/__init__.py b/tests/regressiontests/generic_views/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/generic_views/__init__.py
diff --git a/tests/regressiontests/generic_views/base.py b/tests/regressiontests/generic_views/base.py
new file mode 100644
index 0000000000..a1da98690b
--- /dev/null
+++ b/tests/regressiontests/generic_views/base.py
@@ -0,0 +1,233 @@
+import unittest
+
+from django.core.exceptions import ImproperlyConfigured
+from django.http import HttpResponse
+from django.test import TestCase, RequestFactory
+from django.utils import simplejson
+from django.views.generic import View, TemplateView, RedirectView
+
+
+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 CustomizableView(SimpleView):
+ parameter = {}
+
+def decorator(view):
+ view.is_decorated = True
+ return view
+
+
+class DecoratedDispatchView(SimpleView):
+
+ @decorator
+ def dispatch(self, request, *args, **kwargs):
+ return super(DecoratedDispatchView, self).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(unittest.TestCase):
+ rf = RequestFactory()
+
+ def _assert_simple(self, response):
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'This is a simple view')
+
+ def test_no_init_kwargs(self):
+ """
+ Test that a view can't be accidentally instantiated before deployment
+ """
+ try:
+ view = SimpleView(key='value').as_view()
+ self.fail('Should not be able to instantiate a view')
+ except AttributeError:
+ pass
+
+ def test_no_init_args(self):
+ """
+ Test that a view can't be accidentally instantiated before deployment
+ """
+ try:
+ view = SimpleView.as_view('value')
+ self.fail('Should not be able to use non-keyword arguments instantiating a view')
+ except TypeError:
+ pass
+
+ 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_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):
+ """
+ Test that view arguments must be predefined on the class and can't
+ be named like a HTTP method.
+ """
+ # Check each of the allowed method names
+ for method in SimpleView.http_method_names:
+ kwargs = dict(((method, "value"),))
+ self.assertRaises(TypeError, SimpleView.as_view, **kwargs)
+
+ # Check the case view argument is ok if predefined on the class...
+ CustomizableView.as_view(parameter="value")
+ # ...but raises errors otherwise.
+ self.assertRaises(TypeError, 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):
+ """
+ Test that 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):
+ """
+ Test that attributes set by decorators on the dispatch method
+ are also present on the closure.
+ """
+ self.assertTrue(DecoratedDispatchView.as_view().is_decorated)
+
+
+class TemplateViewTest(TestCase):
+ urls = 'regressiontests.generic_views.urls'
+
+ rf = RequestFactory()
+
+ def _assert_about(self, response):
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, '<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_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_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['params'], {'foo': 'bar'})
+
+ 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['params'], {'foo': 'bar'})
+ self.assertEqual(response.context['key'], 'value')
+
+class RedirectViewTest(unittest.TestCase):
+ rf = RequestFactory()
+
+ def test_no_url(self):
+ "Without any configuration, returns HTTP 410 GONE"
+ response = RedirectView.as_view()(self.rf.get('/foo/'))
+ self.assertEquals(response.status_code, 410)
+
+ def test_permanaent_redirect(self):
+ "Default is a permanent redirect"
+ response = RedirectView.as_view(url='/bar/')(self.rf.get('/foo/'))
+ self.assertEquals(response.status_code, 301)
+ self.assertEquals(response['Location'], '/bar/')
+
+ def test_temporary_redirect(self):
+ "Permanent redirects are an option"
+ response = RedirectView.as_view(url='/bar/', permanent=False)(self.rf.get('/foo/'))
+ self.assertEquals(response.status_code, 302)
+ self.assertEquals(response['Location'], '/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.assertEquals(response.status_code, 301)
+ self.assertEquals(response['Location'], '/bar/')
+
+ response = RedirectView.as_view(url='/bar/', query_string=True)(self.rf.get('/foo/?pork=spam'))
+ self.assertEquals(response.status_code, 301)
+ self.assertEquals(response['Location'], '/bar/?pork=spam')
+
+ 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.assertEquals(response.status_code, 301)
+ self.assertEquals(response['Location'], '/bar/42/')
diff --git a/tests/regressiontests/generic_views/dates.py b/tests/regressiontests/generic_views/dates.py
new file mode 100644
index 0000000000..c730c763b9
--- /dev/null
+++ b/tests/regressiontests/generic_views/dates.py
@@ -0,0 +1,352 @@
+import datetime
+import random
+
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+
+from regressiontests.generic_views.models import Book
+
+class ArchiveIndexViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def _make_books(self, n, base_date):
+ for i in range(n):
+ b = Book.objects.create(
+ name='Book %d' % i,
+ slug='book-%d' % i,
+ pages=100+i,
+ pubdate=base_date - datetime.timedelta(days=1))
+
+ def test_archive_view(self):
+ res = self.client.get('/dates/books/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1])
+ self.assertEqual(list(res.context['latest']), list(Book.objects.all()))
+ self.assertTemplateUsed(res, 'generic_views/book_archive.html')
+
+ def test_archive_view_context_object_name(self):
+ res = self.client.get('/dates/books/context_object_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1])
+ self.assertEqual(list(res.context['thingies']), list(Book.objects.all()))
+ self.assertFalse('latest' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/book_archive.html')
+
+ def test_empty_archive_view(self):
+ Book.objects.all().delete()
+ res = self.client.get('/dates/books/')
+ self.assertEqual(res.status_code, 404)
+
+ def test_allow_empty_archive_view(self):
+ Book.objects.all().delete()
+ res = self.client.get('/dates/books/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [])
+ self.assertEqual(list(res.context['date_list']), [])
+ self.assertTemplateUsed(res, 'generic_views/book_archive.html')
+
+ def test_archive_view_template(self):
+ res = self.client.get('/dates/books/template_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1])
+ self.assertEqual(list(res.context['latest']), list(Book.objects.all()))
+ self.assertTemplateUsed(res, 'generic_views/list.html')
+
+ def test_archive_view_template_suffix(self):
+ res = self.client.get('/dates/books/template_name_suffix/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1])
+ self.assertEqual(list(res.context['latest']), list(Book.objects.all()))
+ self.assertTemplateUsed(res, 'generic_views/book_detail.html')
+
+ def test_archive_view_invalid(self):
+ self.assertRaises(ImproperlyConfigured, self.client.get, '/dates/books/invalid/')
+
+ def test_paginated_archive_view(self):
+ self._make_books(20, base_date=datetime.date.today())
+ res = self.client.get('/dates/books/paginated/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1])
+ self.assertEqual(list(res.context['latest']), list(Book.objects.all()[0:10]))
+ self.assertTemplateUsed(res, 'generic_views/book_archive.html')
+
+ res = self.client.get('/dates/books/paginated/?page=2')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['page_obj'].number, 2)
+ self.assertEqual(list(res.context['latest']), list(Book.objects.all()[10:20]))
+
+
+class YearArchiveViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_year_view(self):
+ res = self.client.get('/dates/books/2008/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)])
+ self.assertEqual(res.context['year'], '2008')
+ self.assertTemplateUsed(res, 'generic_views/book_archive_year.html')
+
+ def test_year_view_make_object_list(self):
+ res = self.client.get('/dates/books/2006/make_object_list/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(2006, 5, 1)])
+ self.assertEqual(list(res.context['books']), list(Book.objects.filter(pubdate__year=2006)))
+ self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2006)))
+ self.assertTemplateUsed(res, 'generic_views/book_archive_year.html')
+
+ def test_year_view_empty(self):
+ res = self.client.get('/dates/books/1999/')
+ self.assertEqual(res.status_code, 404)
+ res = self.client.get('/dates/books/1999/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [])
+ self.assertEqual(list(res.context['books']), [])
+
+ def test_year_view_allow_future(self):
+ # Create a new book in the future
+ year = datetime.date.today().year + 1
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=datetime.date(year, 1, 1))
+ res = self.client.get('/dates/books/%s/' % year)
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/%s/allow_empty/' % year)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [])
+
+ res = self.client.get('/dates/books/%s/allow_future/' % year)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(year, 1, 1)])
+
+ def test_year_view_invalid_pattern(self):
+ res = self.client.get('/dates/books/no_year/')
+ self.assertEqual(res.status_code, 404)
+
+class MonthArchiveViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_month_view(self):
+ res = self.client.get('/dates/books/2008/oct/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/book_archive_month.html')
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)])
+ self.assertEqual(list(res.context['books']),
+ list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))))
+ self.assertEqual(res.context['month'], datetime.date(2008, 10, 1))
+
+ # Since allow_empty=False, next/prev months must be valid (#7164)
+ self.assertEqual(res.context['next_month'], None)
+ self.assertEqual(res.context['previous_month'], datetime.date(2006, 5, 1))
+
+ def test_month_view_allow_empty(self):
+ # allow_empty = False, empty month
+ res = self.client.get('/dates/books/2000/jan/')
+ self.assertEqual(res.status_code, 404)
+
+ # allow_empty = True, empty month
+ res = self.client.get('/dates/books/2000/jan/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [])
+ self.assertEqual(list(res.context['books']), [])
+ self.assertEqual(res.context['month'], datetime.date(2000, 1, 1))
+
+ # Since it's allow empty, next/prev are allowed to be empty months (#7164)
+ self.assertEqual(res.context['next_month'], datetime.date(2000, 2, 1))
+ self.assertEqual(res.context['previous_month'], datetime.date(1999, 12, 1))
+
+ # allow_empty but not allow_future: next_month should be empty (#7164)
+ url = datetime.date.today().strftime('/dates/books/%Y/%b/allow_empty/').lower()
+ res = self.client.get(url)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_month'], None)
+
+ def test_month_view_allow_future(self):
+ future = (datetime.date.today() + datetime.timedelta(days=60)).replace(day=1)
+ urlbit = future.strftime('%Y/%b').lower()
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
+
+ # allow_future = False, future month
+ res = self.client.get('/dates/books/%s/' % urlbit)
+ self.assertEqual(res.status_code, 404)
+
+ # allow_future = True, valid future month
+ res = self.client.get('/dates/books/%s/allow_future/' % urlbit)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'][0].date(), b.pubdate)
+ self.assertEqual(list(res.context['books']), [b])
+ self.assertEqual(res.context['month'], future)
+
+ # Since it's allow_future but not allow_empty, next/prev are not
+ # allowed to be empty months (#7164)
+ self.assertEqual(res.context['next_month'], None)
+ self.assertEqual(res.context['previous_month'], datetime.date(2008, 10, 1))
+
+ # allow_future, but not allow_empty, with a current month. So next
+ # should be in the future (yup, #7164, again)
+ res = self.client.get('/dates/books/2008/oct/allow_future/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_month'], future)
+ self.assertEqual(res.context['previous_month'], datetime.date(2006, 5, 1))
+
+ def test_custom_month_format(self):
+ res = self.client.get('/dates/books/2008/10/')
+ self.assertEqual(res.status_code, 200)
+
+ def test_month_view_invalid_pattern(self):
+ res = self.client.get('/dates/books/2007/no_month/')
+ self.assertEqual(res.status_code, 404)
+
+class WeekArchiveViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_week_view(self):
+ res = self.client.get('/dates/books/2008/week/39/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/book_archive_week.html')
+ self.assertEqual(res.context['books'][0], Book.objects.get(pubdate=datetime.date(2008, 10, 1)))
+ self.assertEqual(res.context['week'], datetime.date(2008, 9, 28))
+
+ def test_week_view_allow_empty(self):
+ res = self.client.get('/dates/books/2008/week/12/')
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/2008/week/12/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [])
+
+ def test_week_view_allow_future(self):
+ future = datetime.date(datetime.date.today().year + 1, 1, 1)
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
+
+ res = self.client.get('/dates/books/%s/week/0/' % future.year)
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/%s/week/0/allow_future/' % future.year)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [b])
+
+ def test_week_view_invalid_pattern(self):
+ res = self.client.get('/dates/books/2007/week/no_week/')
+ self.assertEqual(res.status_code, 404)
+
+class DayArchiveViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_day_view(self):
+ res = self.client.get('/dates/books/2008/oct/01/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/book_archive_day.html')
+ self.assertEqual(list(res.context['books']),
+ list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))))
+ self.assertEqual(res.context['day'], datetime.date(2008, 10, 1))
+
+ # Since allow_empty=False, next/prev days must be valid.
+ self.assertEqual(res.context['next_day'], None)
+ self.assertEqual(res.context['previous_day'], datetime.date(2006, 5, 1))
+
+ def test_day_view_allow_empty(self):
+ # allow_empty = False, empty month
+ res = self.client.get('/dates/books/2000/jan/1/')
+ self.assertEqual(res.status_code, 404)
+
+ # allow_empty = True, empty month
+ res = self.client.get('/dates/books/2000/jan/1/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [])
+ self.assertEqual(res.context['day'], datetime.date(2000, 1, 1))
+
+ # Since it's allow empty, next/prev are allowed to be empty months (#7164)
+ self.assertEqual(res.context['next_day'], datetime.date(2000, 1, 2))
+ self.assertEqual(res.context['previous_day'], datetime.date(1999, 12, 31))
+
+ # allow_empty but not allow_future: next_month should be empty (#7164)
+ url = datetime.date.today().strftime('/dates/books/%Y/%b/%d/allow_empty/').lower()
+ res = self.client.get(url)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_day'], None)
+
+ def test_day_view_allow_future(self):
+ future = (datetime.date.today() + datetime.timedelta(days=60))
+ urlbit = future.strftime('%Y/%b/%d').lower()
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
+
+ # allow_future = False, future month
+ res = self.client.get('/dates/books/%s/' % urlbit)
+ self.assertEqual(res.status_code, 404)
+
+ # allow_future = True, valid future month
+ res = self.client.get('/dates/books/%s/allow_future/' % urlbit)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [b])
+ self.assertEqual(res.context['day'], future)
+
+ # allow_future but not allow_empty, next/prev amust be valid
+ self.assertEqual(res.context['next_day'], None)
+ self.assertEqual(res.context['previous_day'], datetime.date(2008, 10, 1))
+
+ # allow_future, but not allow_empty, with a current month.
+ res = self.client.get('/dates/books/2008/oct/01/allow_future/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_day'], future)
+ self.assertEqual(res.context['previous_day'], datetime.date(2006, 5, 1))
+
+ def test_next_prev_context(self):
+ res = self.client.get('/dates/books/2008/oct/01/')
+ self.assertEqual(res.content, "Archive for Oct. 1, 2008. Previous day is May 1, 2006")
+
+ def test_custom_month_format(self):
+ res = self.client.get('/dates/books/2008/10/01/')
+ self.assertEqual(res.status_code, 200)
+
+ def test_day_view_invalid_pattern(self):
+ res = self.client.get('/dates/books/2007/oct/no_day/')
+ self.assertEqual(res.status_code, 404)
+
+ def test_today_view(self):
+ res = self.client.get('/dates/books/today/')
+ self.assertEqual(res.status_code, 404)
+ res = self.client.get('/dates/books/today/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['day'], datetime.date.today())
+
+class DateDetailViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_date_detail_by_pk(self):
+ res = self.client.get('/dates/books/2008/oct/01/1/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Book.objects.get(pk=1))
+ self.assertEqual(res.context['book'], Book.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'generic_views/book_detail.html')
+
+ def test_date_detail_by_slug(self):
+ res = self.client.get('/dates/books/2006/may/01/byslug/dreaming-in-code/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['book'], Book.objects.get(slug='dreaming-in-code'))
+
+ def test_date_detail_custom_month_format(self):
+ res = self.client.get('/dates/books/2008/10/01/1/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['book'], Book.objects.get(pk=1))
+
+ def test_date_detail_allow_future(self):
+ future = (datetime.date.today() + datetime.timedelta(days=60))
+ urlbit = future.strftime('%Y/%b/%d').lower()
+ b = Book.objects.create(name="The New New Testement", slug="new-new", pages=600, pubdate=future)
+
+ res = self.client.get('/dates/books/%s/new-new/' % urlbit)
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/%s/%s/allow_future/' % (urlbit, b.id))
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['book'], b)
+ self.assertTemplateUsed(res, 'generic_views/book_detail.html')
+
+ def test_invalid_url(self):
+ self.assertRaises(AttributeError, self.client.get, "/dates/books/2008/oct/01/nopk/")
+
diff --git a/tests/regressiontests/generic_views/detail.py b/tests/regressiontests/generic_views/detail.py
new file mode 100644
index 0000000000..91cacf65ba
--- /dev/null
+++ b/tests/regressiontests/generic_views/detail.py
@@ -0,0 +1,71 @@
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+
+from regressiontests.generic_views.models import Author, Page
+
+
+class DetailViewTest(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_simple_object(self):
+ res = self.client.get('/detail/obj/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], {'foo': 'bar'})
+ self.assertTemplateUsed(res, 'generic_views/detail.html')
+
+ def test_detail_by_pk(self):
+ res = self.client.get('/detail/author/1/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['author'], Author.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'generic_views/author_detail.html')
+
+ def test_detail_by_slug(self):
+ res = self.client.get('/detail/author/byslug/scott-rosenberg/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(slug='scott-rosenberg'))
+ self.assertEqual(res.context['author'], Author.objects.get(slug='scott-rosenberg'))
+ self.assertTemplateUsed(res, 'generic_views/author_detail.html')
+
+ def test_template_name(self):
+ res = self.client.get('/detail/author/1/template_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['author'], Author.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'generic_views/about.html')
+
+ def test_template_name_suffix(self):
+ res = self.client.get('/detail/author/1/template_name_suffix/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['author'], Author.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'generic_views/author_view.html')
+
+ def test_template_name_field(self):
+ res = self.client.get('/detail/page/1/field/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Page.objects.get(pk=1))
+ self.assertEqual(res.context['page'], Page.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'generic_views/page_template.html')
+
+ def test_context_object_name(self):
+ res = self.client.get('/detail/author/1/context_object_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['thingy'], Author.objects.get(pk=1))
+ self.assertFalse('author' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/author_detail.html')
+
+ def test_duplicated_context_object_name(self):
+ res = self.client.get('/detail/author/1/dupe_context_object_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertFalse('author' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/author_detail.html')
+
+ def test_invalid_url(self):
+ self.assertRaises(AttributeError, self.client.get, '/detail/author/invalid/url/')
+
+ def test_invalid_queryset(self):
+ self.assertRaises(ImproperlyConfigured, self.client.get, '/detail/author/invalid/qs/')
diff --git a/tests/regressiontests/generic_views/edit.py b/tests/regressiontests/generic_views/edit.py
new file mode 100644
index 0000000000..87233ec357
--- /dev/null
+++ b/tests/regressiontests/generic_views/edit.py
@@ -0,0 +1,233 @@
+from django.core.exceptions import ImproperlyConfigured
+from django import forms
+from django.test import TestCase
+from django.utils.unittest import expectedFailure
+
+from regressiontests.generic_views.models import Artist, Author
+from regressiontests.generic_views import views
+
+
+class CreateViewTests(TestCase):
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_create(self):
+ res = self.client.get('/edit/authors/create/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTrue(isinstance(res.context['form'], forms.ModelForm))
+ self.assertFalse('object' in res.context)
+ self.assertFalse('author' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/author_form.html')
+
+ res = self.client.post('/edit/authors/create/',
+ {'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/list/authors/')
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
+
+ def test_create_invalid(self):
+ res = self.client.post('/edit/authors/create/',
+ {'name': 'A' * 101, 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_form.html')
+ self.assertEqual(len(res.context['form'].errors), 1)
+ self.assertEqual(Author.objects.count(), 0)
+
+ def test_create_with_object_url(self):
+ res = self.client.post('/edit/artists/create/',
+ {'name': 'Rene Magritte'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/detail/artist/1/')
+ self.assertQuerysetEqual(Artist.objects.all(), ['<Artist: Rene Magritte>'])
+
+ def test_create_with_redirect(self):
+ res = self.client.post('/edit/authors/create/redirect/',
+ {'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/edit/authors/create/')
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
+
+ def test_create_with_special_properties(self):
+ res = self.client.get('/edit/authors/create/special/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTrue(isinstance(res.context['form'], views.AuthorForm))
+ self.assertFalse('object' in res.context)
+ self.assertFalse('author' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/form.html')
+
+ res = self.client.post('/edit/authors/create/special/',
+ {'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/detail/author/1/')
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
+
+ def test_create_without_redirect(self):
+ try:
+ res = self.client.post('/edit/authors/create/naive/',
+ {'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ self.fail('Should raise exception -- No redirect URL provided, and no get_absolute_url provided')
+ except ImproperlyConfigured:
+ pass
+
+ def test_create_restricted(self):
+ res = self.client.post('/edit/authors/create/restricted/',
+ {'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/')
+
+class UpdateViewTests(TestCase):
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_update_post(self):
+ a = Author.objects.create(
+ name='Randall Munroe',
+ slug='randall-munroe',
+ )
+ res = self.client.get('/edit/author/1/update/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTrue(isinstance(res.context['form'], forms.ModelForm))
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['author'], Author.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'generic_views/author_form.html')
+
+ # Modification with both POST and PUT (browser compatible)
+ res = self.client.post('/edit/author/1/update/',
+ {'name': 'Randall Munroe (xkcd)', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/list/authors/')
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (xkcd)>'])
+
+ @expectedFailure
+ def test_update_put(self):
+ a = Author.objects.create(
+ name='Randall Munroe',
+ slug='randall-munroe',
+ )
+ res = self.client.get('/edit/author/1/update/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_form.html')
+
+ res = self.client.put('/edit/author/1/update/',
+ {'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/list/authors/')
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (author of xkcd)>'])
+
+ def test_update_invalid(self):
+ a = Author.objects.create(
+ name='Randall Munroe',
+ slug='randall-munroe',
+ )
+ res = self.client.post('/edit/author/1/update/',
+ {'name': 'A' * 101, 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_form.html')
+ self.assertEqual(len(res.context['form'].errors), 1)
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
+
+ def test_update_with_object_url(self):
+ a = Artist.objects.create(name='Rene Magritte')
+ res = self.client.post('/edit/artists/1/update/',
+ {'name': 'Rene Magritte'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/detail/artist/1/')
+ self.assertQuerysetEqual(Artist.objects.all(), ['<Artist: Rene Magritte>'])
+
+ def test_update_with_redirect(self):
+ a = Author.objects.create(
+ name='Randall Munroe',
+ slug='randall-munroe',
+ )
+ res = self.client.post('/edit/author/1/update/redirect/',
+ {'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/edit/authors/create/')
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (author of xkcd)>'])
+
+ def test_update_with_special_properties(self):
+ a = Author.objects.create(
+ name='Randall Munroe',
+ slug='randall-munroe',
+ )
+ res = self.client.get('/edit/author/1/update/special/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTrue(isinstance(res.context['form'], views.AuthorForm))
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['thingy'], Author.objects.get(pk=1))
+ self.assertFalse('author' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/form.html')
+
+ res = self.client.post('/edit/author/1/update/special/',
+ {'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'})
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/detail/author/1/')
+ self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe (author of xkcd)>'])
+
+ def test_update_without_redirect(self):
+ try:
+ a = Author.objects.create(
+ name='Randall Munroe',
+ slug='randall-munroe',
+ )
+ res = self.client.post('/edit/author/1/update/naive/',
+ {'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'})
+ self.fail('Should raise exception -- No redirect URL provided, and no get_absolute_url provided')
+ except ImproperlyConfigured:
+ pass
+
+class DeleteViewTests(TestCase):
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_delete_by_post(self):
+ Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ res = self.client.get('/edit/author/1/delete/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['author'], Author.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'generic_views/author_confirm_delete.html')
+
+ # Deletion with POST
+ res = self.client.post('/edit/author/1/delete/')
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/list/authors/')
+ self.assertQuerysetEqual(Author.objects.all(), [])
+
+ def test_delete_by_delete(self):
+ # Deletion with browser compatible DELETE method
+ Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ res = self.client.delete('/edit/author/1/delete/')
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/list/authors/')
+ self.assertQuerysetEqual(Author.objects.all(), [])
+
+ def test_delete_with_redirect(self):
+ Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ res = self.client.post('/edit/author/1/delete/redirect/')
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/edit/authors/create/')
+ self.assertQuerysetEqual(Author.objects.all(), [])
+
+ def test_delete_with_special_properties(self):
+ Author.objects.create(**{'name': 'Randall Munroe', 'slug': 'randall-munroe'})
+ res = self.client.get('/edit/author/1/delete/special/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Author.objects.get(pk=1))
+ self.assertEqual(res.context['thingy'], Author.objects.get(pk=1))
+ self.assertFalse('author' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/confirm_delete.html')
+
+ res = self.client.post('/edit/author/1/delete/special/')
+ self.assertEqual(res.status_code, 302)
+ self.assertRedirects(res, 'http://testserver/list/authors/')
+ self.assertQuerysetEqual(Author.objects.all(), [])
+
+ def test_delete_without_redirect(self):
+ try:
+ a = Author.objects.create(
+ name='Randall Munroe',
+ slug='randall-munroe',
+ )
+ res = self.client.post('/edit/author/1/delete/naive/')
+ self.fail('Should raise exception -- No redirect URL provided, and no get_absolute_url provided')
+ except ImproperlyConfigured:
+ pass
+
diff --git a/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json b/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json
new file mode 100644
index 0000000000..8ecfe5e1e2
--- /dev/null
+++ b/tests/regressiontests/generic_views/fixtures/generic-views-test-data.json
@@ -0,0 +1,47 @@
+[
+ {
+ "model": "generic_views.author",
+ "pk": 1,
+ "fields": {
+ "name": "Roberto BolaƱo",
+ "slug": "roberto-bolano"
+ }
+ },
+ {
+ "model": "generic_views.author",
+ "pk": 2,
+ "fields": {
+ "name": "Scott Rosenberg",
+ "slug": "scott-rosenberg"
+ }
+ },
+ {
+ "model": "generic_views.book",
+ "pk": 1,
+ "fields": {
+ "name": "2066",
+ "slug": "2066",
+ "pages": "800",
+ "authors": [1],
+ "pubdate": "2008-10-01"
+ }
+ },
+ {
+ "model": "generic_views.book",
+ "pk": 2,
+ "fields": {
+ "name": "Dreaming in Code",
+ "slug": "dreaming-in-code",
+ "pages": "300",
+ "pubdate": "2006-05-01"
+ }
+ },
+ {
+ "model": "generic_views.page",
+ "pk": 1,
+ "fields": {
+ "template": "generic_views/page_template.html",
+ "content": "I was once bitten by a moose."
+ }
+ }
+] \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/forms.py b/tests/regressiontests/generic_views/forms.py
new file mode 100644
index 0000000000..7200947781
--- /dev/null
+++ b/tests/regressiontests/generic_views/forms.py
@@ -0,0 +1,11 @@
+from django import forms
+
+from regressiontests.generic_views.models import Author
+
+
+class AuthorForm(forms.ModelForm):
+ name = forms.CharField()
+ slug = forms.SlugField()
+
+ class Meta:
+ model = Author
diff --git a/tests/regressiontests/generic_views/list.py b/tests/regressiontests/generic_views/list.py
new file mode 100644
index 0000000000..5d62aa8fb8
--- /dev/null
+++ b/tests/regressiontests/generic_views/list.py
@@ -0,0 +1,129 @@
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+
+from regressiontests.generic_views.models import Author
+
+
+class ListViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_items(self):
+ res = self.client.get('/list/dict/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/list.html')
+ self.assertEqual(res.context['object_list'][0]['first'], 'John')
+
+ def test_queryset(self):
+ res = self.client.get('/list/authors/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+ self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
+ self.assertEqual(list(res.context['authors']), list(Author.objects.all()))
+ self.assertEqual(res.context['paginator'], None)
+ self.assertEqual(res.context['page_obj'], None)
+ self.assertEqual(res.context['is_paginated'], False)
+
+ def test_paginated_queryset(self):
+ self._make_authors(100)
+ res = self.client.get('/list/authors/paginated/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+ self.assertEqual(len(res.context['authors']), 30)
+ self.assertEqual(res.context['is_paginated'], True)
+ self.assertEqual(res.context['page_obj'].number, 1)
+ self.assertEqual(res.context['paginator'].num_pages, 4)
+ self.assertEqual(res.context['authors'][0].name, 'Author 00')
+ self.assertEqual(list(res.context['authors'])[-1].name, 'Author 29')
+
+ def test_paginated_queryset_shortdata(self):
+ res = self.client.get('/list/authors/paginated/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+ self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
+ self.assertEqual(list(res.context['authors']), list(Author.objects.all()))
+ self.assertEqual(res.context['paginator'], None)
+ self.assertEqual(res.context['page_obj'], None)
+ self.assertEqual(res.context['is_paginated'], False)
+
+ def test_paginated_get_page_by_query_string(self):
+ self._make_authors(100)
+ res = self.client.get('/list/authors/paginated/', {'page': '2'})
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+ self.assertEqual(len(res.context['authors']), 30)
+ self.assertEqual(res.context['authors'][0].name, 'Author 30')
+ self.assertEqual(res.context['page_obj'].number, 2)
+
+ def test_paginated_get_last_page_by_query_string(self):
+ self._make_authors(100)
+ res = self.client.get('/list/authors/paginated/', {'page': 'last'})
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(len(res.context['authors']), 10)
+ self.assertEqual(res.context['authors'][0].name, 'Author 90')
+ self.assertEqual(res.context['page_obj'].number, 4)
+
+ def test_paginated_get_page_by_urlvar(self):
+ self._make_authors(100)
+ res = self.client.get('/list/authors/paginated/3/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+ self.assertEqual(len(res.context['authors']), 30)
+ self.assertEqual(res.context['authors'][0].name, 'Author 60')
+ self.assertEqual(res.context['page_obj'].number, 3)
+
+ def test_paginated_page_out_of_range(self):
+ self._make_authors(100)
+ res = self.client.get('/list/authors/paginated/42/')
+ self.assertEqual(res.status_code, 404)
+
+ def test_paginated_invalid_page(self):
+ self._make_authors(100)
+ res = self.client.get('/list/authors/paginated/?page=frog')
+ self.assertEqual(res.status_code, 404)
+
+ def test_allow_empty_false(self):
+ res = self.client.get('/list/authors/notempty/')
+ self.assertEqual(res.status_code, 200)
+ Author.objects.all().delete()
+ res = self.client.get('/list/authors/notempty/')
+ self.assertEqual(res.status_code, 404)
+
+ def test_template_name(self):
+ res = self.client.get('/list/authors/template_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
+ self.assertEqual(list(res.context['authors']), list(Author.objects.all()))
+ self.assertTemplateUsed(res, 'generic_views/list.html')
+
+ def test_template_name_suffix(self):
+ res = self.client.get('/list/authors/template_name_suffix/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
+ self.assertEqual(list(res.context['authors']), list(Author.objects.all()))
+ self.assertTemplateUsed(res, 'generic_views/author_objects.html')
+
+ def test_context_object_name(self):
+ res = self.client.get('/list/authors/context_object_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
+ self.assertEqual(list(res.context['author_list']), list(Author.objects.all()))
+ self.assertFalse('authors' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+
+ def test_duplicate_context_object_name(self):
+ res = self.client.get('/list/authors/dupe_context_object_name/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
+ self.assertFalse('author_list' in res.context)
+ self.assertFalse('authors' in res.context)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+
+ def test_missing_items(self):
+ self.assertRaises(ImproperlyConfigured, self.client.get, '/list/authors/invalid/')
+
+ def _make_authors(self, n):
+ Author.objects.all().delete()
+ for i in range(n):
+ Author.objects.create(name='Author %02i' % i, slug='a%s' % i)
+
diff --git a/tests/regressiontests/generic_views/models.py b/tests/regressiontests/generic_views/models.py
new file mode 100644
index 0000000000..5a8577d0f6
--- /dev/null
+++ b/tests/regressiontests/generic_views/models.py
@@ -0,0 +1,41 @@
+from django.db import models
+
+class Artist(models.Model):
+ name = models.CharField(max_length=100)
+
+ class Meta:
+ ordering = ['name']
+
+ def __unicode__(self):
+ return self.name
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('artist_detail', (), {'pk': self.id})
+
+class Author(models.Model):
+ name = models.CharField(max_length=100)
+ slug = models.SlugField()
+
+ class Meta:
+ ordering = ['name']
+
+ def __unicode__(self):
+ return self.name
+
+class Book(models.Model):
+ name = models.CharField(max_length=300)
+ slug = models.SlugField()
+ pages = models.IntegerField()
+ authors = models.ManyToManyField(Author)
+ pubdate = models.DateField()
+
+ class Meta:
+ ordering = ['-pubdate']
+
+ def __unicode__(self):
+ return self.name
+
+class Page(models.Model):
+ content = models.TextField()
+ template = models.CharField(max_length=300)
diff --git a/tests/regressiontests/generic_views/templates/generic_views/about.html b/tests/regressiontests/generic_views/templates/generic_views/about.html
new file mode 100644
index 0000000000..7d877fdd78
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/about.html
@@ -0,0 +1 @@
+<h1>About</h1> \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/apple_detail.html b/tests/regressiontests/generic_views/templates/generic_views/apple_detail.html
new file mode 100644
index 0000000000..4fd0bd933b
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/apple_detail.html
@@ -0,0 +1 @@
+This is a {% if tasty %}tasty {% endif %}{{ apple.color }} apple{{ extra }} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/artist_detail.html b/tests/regressiontests/generic_views/templates/generic_views/artist_detail.html
new file mode 100644
index 0000000000..9ea13fed83
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/artist_detail.html
@@ -0,0 +1 @@
+This is an {{ artist }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/artist_form.html b/tests/regressiontests/generic_views/templates/generic_views/artist_form.html
new file mode 100644
index 0000000000..114e9bc5cc
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/artist_form.html
@@ -0,0 +1 @@
+A form: {{ form }} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_confirm_delete.html b/tests/regressiontests/generic_views/templates/generic_views/author_confirm_delete.html
new file mode 100644
index 0000000000..d6ed99846e
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/author_confirm_delete.html
@@ -0,0 +1 @@
+Are you sure? \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_detail.html b/tests/regressiontests/generic_views/templates/generic_views/author_detail.html
new file mode 100644
index 0000000000..b6a60fd492
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/author_detail.html
@@ -0,0 +1 @@
+This is an {{ author }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_form.html b/tests/regressiontests/generic_views/templates/generic_views/author_form.html
new file mode 100644
index 0000000000..114e9bc5cc
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/author_form.html
@@ -0,0 +1 @@
+A form: {{ form }} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_list.html b/tests/regressiontests/generic_views/templates/generic_views/author_list.html
new file mode 100644
index 0000000000..42ba331802
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/author_list.html
@@ -0,0 +1,3 @@
+{% for item in object_list %}
+ {{ item }}
+{% endfor %} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_objects.html b/tests/regressiontests/generic_views/templates/generic_views/author_objects.html
new file mode 100644
index 0000000000..42ba331802
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/author_objects.html
@@ -0,0 +1,3 @@
+{% for item in object_list %}
+ {{ item }}
+{% endfor %} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/author_view.html b/tests/regressiontests/generic_views/templates/generic_views/author_view.html
new file mode 100644
index 0000000000..dcd9d6b3a6
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/author_view.html
@@ -0,0 +1 @@
+This is an alternate template_name_suffix for an {{ author }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive.html b/tests/regressiontests/generic_views/templates/generic_views/book_archive.html
new file mode 100644
index 0000000000..5fc11149bd
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/book_archive.html
@@ -0,0 +1 @@
+Archive of books from {{ date_list }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_day.html b/tests/regressiontests/generic_views/templates/generic_views/book_archive_day.html
new file mode 100644
index 0000000000..4a7b5027b5
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/book_archive_day.html
@@ -0,0 +1 @@
+Archive for {{ day }}. Previous day is {{ previous_day }} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_month.html b/tests/regressiontests/generic_views/templates/generic_views/book_archive_month.html
new file mode 100644
index 0000000000..4e1e52b35c
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/book_archive_month.html
@@ -0,0 +1 @@
+Books in {{ month }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_week.html b/tests/regressiontests/generic_views/templates/generic_views/book_archive_week.html
new file mode 100644
index 0000000000..0ddcbc98a9
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/book_archive_week.html
@@ -0,0 +1 @@
+Archive for {{ week }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_archive_year.html b/tests/regressiontests/generic_views/templates/generic_views/book_archive_year.html
new file mode 100644
index 0000000000..7705914e97
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/book_archive_year.html
@@ -0,0 +1 @@
+Archive of books from {{ year }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_detail.html b/tests/regressiontests/generic_views/templates/generic_views/book_detail.html
new file mode 100644
index 0000000000..c40af70426
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/book_detail.html
@@ -0,0 +1 @@
+This is {{ book }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/book_list.html b/tests/regressiontests/generic_views/templates/generic_views/book_list.html
new file mode 100644
index 0000000000..42ba331802
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/book_list.html
@@ -0,0 +1,3 @@
+{% for item in object_list %}
+ {{ item }}
+{% endfor %} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/confirm_delete.html b/tests/regressiontests/generic_views/templates/generic_views/confirm_delete.html
new file mode 100644
index 0000000000..87288c4495
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/confirm_delete.html
@@ -0,0 +1 @@
+Generic: Are you sure? \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/detail.html b/tests/regressiontests/generic_views/templates/generic_views/detail.html
new file mode 100644
index 0000000000..1b6b9d946b
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/detail.html
@@ -0,0 +1 @@
+Look, an {{ object }}. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/form.html b/tests/regressiontests/generic_views/templates/generic_views/form.html
new file mode 100644
index 0000000000..7249998643
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/form.html
@@ -0,0 +1 @@
+A generic form: {{ form }} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/list.html b/tests/regressiontests/generic_views/templates/generic_views/list.html
new file mode 100644
index 0000000000..42ba331802
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/list.html
@@ -0,0 +1,3 @@
+{% for item in object_list %}
+ {{ item }}
+{% endfor %} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/generic_views/page_template.html b/tests/regressiontests/generic_views/templates/generic_views/page_template.html
new file mode 100644
index 0000000000..88e20c8b50
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/generic_views/page_template.html
@@ -0,0 +1 @@
+This is some content: {{ content }} \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/templates/registration/login.html b/tests/regressiontests/generic_views/templates/registration/login.html
new file mode 100644
index 0000000000..cac78221e2
--- /dev/null
+++ b/tests/regressiontests/generic_views/templates/registration/login.html
@@ -0,0 +1 @@
+An empty login template. \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/tests.py b/tests/regressiontests/generic_views/tests.py
new file mode 100644
index 0000000000..40eec72b25
--- /dev/null
+++ b/tests/regressiontests/generic_views/tests.py
@@ -0,0 +1,5 @@
+from regressiontests.generic_views.base import ViewTest, TemplateViewTest, RedirectViewTest
+from regressiontests.generic_views.dates import ArchiveIndexViewTests, YearArchiveViewTests, MonthArchiveViewTests, WeekArchiveViewTests, DayArchiveViewTests, DateDetailViewTests
+from regressiontests.generic_views.detail import DetailViewTest
+from regressiontests.generic_views.edit import CreateViewTests, UpdateViewTests, DeleteViewTests
+from regressiontests.generic_views.list import ListViewTests \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py
new file mode 100644
index 0000000000..548d10d164
--- /dev/null
+++ b/tests/regressiontests/generic_views/urls.py
@@ -0,0 +1,186 @@
+from django.conf.urls.defaults import *
+from django.views.generic import TemplateView
+
+import views
+
+
+urlpatterns = patterns('',
+ # base
+ #(r'^about/login-required/$',
+ # views.DecoratedAboutView()),
+
+ # TemplateView
+ (r'^template/simple/(?P<foo>\w+)/$',
+ TemplateView.as_view(template_name='generic_views/about.html')),
+ (r'^template/custom/(?P<foo>\w+)/$',
+ views.CustomTemplateView.as_view(template_name='generic_views/about.html')),
+
+ # DetailView
+ (r'^detail/obj/$',
+ views.ObjectDetail.as_view()),
+ url(r'^detail/artist/(?P<pk>\d+)/$',
+ views.ArtistDetail.as_view(),
+ name="artist_detail"),
+ url(r'^detail/author/(?P<pk>\d+)/$',
+ views.AuthorDetail.as_view(),
+ name="author_detail"),
+ (r'^detail/author/byslug/(?P<slug>[\w-]+)/$',
+ views.AuthorDetail.as_view()),
+ (r'^detail/author/(?P<pk>\d+)/template_name_suffix/$',
+ views.AuthorDetail.as_view(template_name_suffix='_view')),
+ (r'^detail/author/(?P<pk>\d+)/template_name/$',
+ views.AuthorDetail.as_view(template_name='generic_views/about.html')),
+ (r'^detail/author/(?P<pk>\d+)/context_object_name/$',
+ views.AuthorDetail.as_view(context_object_name='thingy')),
+ (r'^detail/author/(?P<pk>\d+)/dupe_context_object_name/$',
+ views.AuthorDetail.as_view(context_object_name='object')),
+ (r'^detail/page/(?P<pk>\d+)/field/$',
+ views.PageDetail.as_view()),
+ (r'^detail/author/invalid/url/$',
+ views.AuthorDetail.as_view()),
+ (r'^detail/author/invalid/qs/$',
+ views.AuthorDetail.as_view(queryset=None)),
+
+ # Create/UpdateView
+ (r'^edit/artists/create/$',
+ views.ArtistCreate.as_view()),
+ (r'^edit/artists/(?P<pk>\d+)/update/$',
+ views.ArtistUpdate.as_view()),
+
+ (r'^edit/authors/create/naive/$',
+ views.NaiveAuthorCreate.as_view()),
+ (r'^edit/authors/create/redirect/$',
+ views.NaiveAuthorCreate.as_view(success_url='/edit/authors/create/')),
+ (r'^edit/authors/create/restricted/$',
+ views.AuthorCreateRestricted.as_view()),
+ (r'^edit/authors/create/$',
+ views.AuthorCreate.as_view()),
+ (r'^edit/authors/create/special/$',
+ views.SpecializedAuthorCreate.as_view()),
+
+ (r'^edit/author/(?P<pk>\d+)/update/naive/$',
+ views.NaiveAuthorUpdate.as_view()),
+ (r'^edit/author/(?P<pk>\d+)/update/redirect/$',
+ views.NaiveAuthorUpdate.as_view(success_url='/edit/authors/create/')),
+ (r'^edit/author/(?P<pk>\d+)/update/$',
+ views.AuthorUpdate.as_view()),
+ (r'^edit/author/(?P<pk>\d+)/update/special/$',
+ views.SpecializedAuthorUpdate.as_view()),
+ (r'^edit/author/(?P<pk>\d+)/delete/naive/$',
+ views.NaiveAuthorDelete.as_view()),
+ (r'^edit/author/(?P<pk>\d+)/delete/redirect/$',
+ views.NaiveAuthorDelete.as_view(success_url='/edit/authors/create/')),
+ (r'^edit/author/(?P<pk>\d+)/delete/$',
+ views.AuthorDelete.as_view()),
+ (r'^edit/author/(?P<pk>\d+)/delete/special/$',
+ views.SpecializedAuthorDelete.as_view()),
+
+ # ArchiveIndexView
+ (r'^dates/books/$',
+ views.BookArchive.as_view()),
+ (r'^dates/books/context_object_name/$',
+ views.BookArchive.as_view(context_object_name='thingies')),
+ (r'^dates/books/allow_empty/$',
+ views.BookArchive.as_view(allow_empty=True)),
+ (r'^dates/books/template_name/$',
+ views.BookArchive.as_view(template_name='generic_views/list.html')),
+ (r'^dates/books/template_name_suffix/$',
+ views.BookArchive.as_view(template_name_suffix='_detail')),
+ (r'^dates/books/invalid/$',
+ views.BookArchive.as_view(queryset=None)),
+ (r'^dates/books/paginated/$',
+ views.BookArchive.as_view(paginate_by=10)),
+
+ # ListView
+ (r'^list/dict/$',
+ views.DictList.as_view()),
+ url(r'^list/authors/$',
+ views.AuthorList.as_view(),
+ name="authors_list"),
+ (r'^list/authors/paginated/$',
+ views.AuthorList.as_view(paginate_by=30)),
+ (r'^list/authors/paginated/(?P<page>\d+)/$',
+ views.AuthorList.as_view(paginate_by=30)),
+ (r'^list/authors/notempty/$',
+ views.AuthorList.as_view(allow_empty=False)),
+ (r'^list/authors/template_name/$',
+ views.AuthorList.as_view(template_name='generic_views/list.html')),
+ (r'^list/authors/template_name_suffix/$',
+ views.AuthorList.as_view(template_name_suffix='_objects')),
+ (r'^list/authors/context_object_name/$',
+ views.AuthorList.as_view(context_object_name='author_list')),
+ (r'^list/authors/dupe_context_object_name/$',
+ views.AuthorList.as_view(context_object_name='object_list')),
+ (r'^list/authors/invalid/$',
+ views.AuthorList.as_view(queryset=None)),
+
+ # YearArchiveView
+ # Mixing keyword and possitional captures below is intentional; the views
+ # ought to be able to accept either.
+ (r'^dates/books/(?P<year>\d{4})/$',
+ views.BookYearArchive.as_view()),
+ (r'^dates/books/(?P<year>\d{4})/make_object_list/$',
+ views.BookYearArchive.as_view(make_object_list=True)),
+ (r'^dates/books/(?P<year>\d{4})/allow_empty/$',
+ views.BookYearArchive.as_view(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/allow_future/$',
+ views.BookYearArchive.as_view(allow_future=True)),
+ (r'^dates/books/no_year/$',
+ views.BookYearArchive.as_view()),
+
+ # MonthArchiveView
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/$',
+ views.BookMonthArchive.as_view()),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
+ views.BookMonthArchive.as_view(month_format='%m')),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/allow_empty/$',
+ views.BookMonthArchive.as_view(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/allow_future/$',
+ views.BookMonthArchive.as_view(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/no_month/$',
+ views.BookMonthArchive.as_view()),
+
+ # WeekArchiveView
+ (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/$',
+ views.BookWeekArchive.as_view()),
+ (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/allow_empty/$',
+ views.BookWeekArchive.as_view(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/allow_future/$',
+ views.BookWeekArchive.as_view(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/week/no_week/$',
+ views.BookWeekArchive.as_view()),
+
+ # DayArchiveView
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/$',
+ views.BookDayArchive.as_view()),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$',
+ views.BookDayArchive.as_view(month_format='%m')),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/allow_empty/$',
+ views.BookDayArchive.as_view(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/allow_future/$',
+ views.BookDayArchive.as_view(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/no_day/$',
+ views.BookDayArchive.as_view()),
+
+ # TodayArchiveView
+ (r'dates/books/today/$',
+ views.BookTodayArchive.as_view()),
+ (r'dates/books/today/allow_empty/$',
+ views.BookTodayArchive.as_view(allow_empty=True)),
+
+ # DateDetailView
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/(?P<pk>\d+)/$',
+ views.BookDetail.as_view()),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<pk>\d+)/$',
+ views.BookDetail.as_view(month_format='%m')),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/(?P<pk>\d+)/allow_future/$',
+ views.BookDetail.as_view(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/nopk/$',
+ views.BookDetail.as_view()),
+
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/byslug/(?P<slug>[\w-]+)/$',
+ views.BookDetail.as_view()),
+
+ # Useful for testing redirects
+ (r'^accounts/login/$', 'django.contrib.auth.views.login')
+) \ No newline at end of file
diff --git a/tests/regressiontests/generic_views/views.py b/tests/regressiontests/generic_views/views.py
new file mode 100644
index 0000000000..eacfb717e2
--- /dev/null
+++ b/tests/regressiontests/generic_views/views.py
@@ -0,0 +1,145 @@
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.utils.decorators import method_decorator
+from django.views import generic
+
+from regressiontests.generic_views.models import Artist, Author, Book, Page
+from regressiontests.generic_views.forms import AuthorForm
+
+
+class CustomTemplateView(generic.TemplateView):
+ template_name = 'generic_views/about.html'
+
+ def get_context_data(self, **kwargs):
+ return {
+ 'params': kwargs,
+ 'key': 'value'
+ }
+
+
+class ObjectDetail(generic.DetailView):
+ template_name = 'generic_views/detail.html'
+
+ def get_object(self):
+ return {'foo': 'bar'}
+
+
+class ArtistDetail(generic.DetailView):
+ queryset = Artist.objects.all()
+
+
+class AuthorDetail(generic.DetailView):
+ queryset = Author.objects.all()
+
+
+class PageDetail(generic.DetailView):
+ queryset = Page.objects.all()
+ template_name_field = 'template'
+
+
+class DictList(generic.ListView):
+ """A ListView that doesn't use a model."""
+ queryset = [
+ {'first': 'John', 'last': 'Lennon'},
+ {'last': 'Yoko', 'last': 'Ono'}
+ ]
+ template_name = 'generic_views/list.html'
+
+
+class AuthorList(generic.ListView):
+ queryset = Author.objects.all()
+
+
+
+class ArtistCreate(generic.CreateView):
+ model = Artist
+
+
+class NaiveAuthorCreate(generic.CreateView):
+ queryset = Author.objects.all()
+
+
+class AuthorCreate(generic.CreateView):
+ model = Author
+ success_url = '/list/authors/'
+
+
+class SpecializedAuthorCreate(generic.CreateView):
+ model = Author
+ form_class = AuthorForm
+ template_name = 'generic_views/form.html'
+ context_object_name = 'thingy'
+
+ def get_success_url(self):
+ return reverse('author_detail', args=[self.object.id,])
+
+
+class AuthorCreateRestricted(AuthorCreate):
+ post = method_decorator(login_required)(AuthorCreate.post)
+
+
+class ArtistUpdate(generic.UpdateView):
+ model = Artist
+
+
+class NaiveAuthorUpdate(generic.UpdateView):
+ queryset = Author.objects.all()
+
+
+class AuthorUpdate(generic.UpdateView):
+ model = Author
+ success_url = '/list/authors/'
+
+
+class SpecializedAuthorUpdate(generic.UpdateView):
+ model = Author
+ form_class = AuthorForm
+ template_name = 'generic_views/form.html'
+ context_object_name = 'thingy'
+
+ def get_success_url(self):
+ return reverse('author_detail', args=[self.object.id,])
+
+
+class NaiveAuthorDelete(generic.DeleteView):
+ queryset = Author.objects.all()
+
+
+class AuthorDelete(generic.DeleteView):
+ model = Author
+ success_url = '/list/authors/'
+
+
+class SpecializedAuthorDelete(generic.DeleteView):
+ queryset = Author.objects.all()
+ template_name = 'generic_views/confirm_delete.html'
+ context_object_name = 'thingy'
+
+ def get_success_url(self):
+ return reverse('authors_list')
+
+
+class BookConfig(object):
+ queryset = Book.objects.all()
+ date_field = 'pubdate'
+
+class BookArchive(BookConfig, generic.ArchiveIndexView):
+ pass
+
+class BookYearArchive(BookConfig, generic.YearArchiveView):
+ pass
+
+class BookMonthArchive(BookConfig, generic.MonthArchiveView):
+ pass
+
+class BookWeekArchive(BookConfig, generic.WeekArchiveView):
+ pass
+
+class BookDayArchive(BookConfig, generic.DayArchiveView):
+ pass
+
+class BookTodayArchive(BookConfig, generic.TodayArchiveView):
+ pass
+
+class BookDetail(BookConfig, generic.DateDetailView):
+ pass