summaryrefslogtreecommitdiff
path: root/tests/regressiontests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/regressiontests')
-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