parts/django/tests/regressiontests/admin_views/tests.py
changeset 307 c6bca38c1cbf
equal deleted inserted replaced
306:5ff1fc726848 307:c6bca38c1cbf
       
     1 # coding: utf-8
       
     2 
       
     3 import re
       
     4 import datetime
       
     5 
       
     6 from django.conf import settings
       
     7 from django.core.exceptions import SuspiciousOperation
       
     8 from django.core.files import temp as tempfile
       
     9 # Register auth models with the admin.
       
    10 from django.contrib.auth import REDIRECT_FIELD_NAME, admin
       
    11 from django.contrib.auth.models import User, Permission, UNUSABLE_PASSWORD
       
    12 from django.contrib.contenttypes.models import ContentType
       
    13 from django.contrib.admin.models import LogEntry, DELETION
       
    14 from django.contrib.admin.sites import LOGIN_FORM_KEY
       
    15 from django.contrib.admin.util import quote
       
    16 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
       
    17 from django.forms.util import ErrorList
       
    18 from django.test import TestCase
       
    19 from django.utils import formats
       
    20 from django.utils.cache import get_max_age
       
    21 from django.utils.encoding import iri_to_uri
       
    22 from django.utils.html import escape
       
    23 from django.utils.translation import activate, deactivate
       
    24 import django.template.context
       
    25 
       
    26 # local test models
       
    27 from models import Article, BarAccount, CustomArticle, EmptyModel, \
       
    28     FooAccount, Gallery, ModelWithStringPrimaryKey, \
       
    29     Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
       
    30     Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
       
    31     Category, Post, Plot, FunkyTag
       
    32 
       
    33 
       
    34 class AdminViewBasicTest(TestCase):
       
    35     fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
       
    36 
       
    37     # Store the bit of the URL where the admin is registered as a class
       
    38     # variable. That way we can test a second AdminSite just by subclassing
       
    39     # this test case and changing urlbit.
       
    40     urlbit = 'admin'
       
    41 
       
    42     def setUp(self):
       
    43         self.old_language_code = settings.LANGUAGE_CODE
       
    44         self.client.login(username='super', password='secret')
       
    45 
       
    46     def tearDown(self):
       
    47         settings.LANGUAGE_CODE = self.old_language_code
       
    48         self.client.logout()
       
    49 
       
    50     def testTrailingSlashRequired(self):
       
    51         """
       
    52         If you leave off the trailing slash, app should redirect and add it.
       
    53         """
       
    54         request = self.client.get('/test_admin/%s/admin_views/article/add' % self.urlbit)
       
    55         self.assertRedirects(request,
       
    56             '/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301
       
    57         )
       
    58 
       
    59     def testBasicAddGet(self):
       
    60         """
       
    61         A smoke test to ensure GET on the add_view works.
       
    62         """
       
    63         response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit)
       
    64         self.assertEqual(response.status_code, 200)
       
    65 
       
    66     def testAddWithGETArgs(self):
       
    67         response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'})
       
    68         self.assertEqual(response.status_code, 200)
       
    69         self.assertTrue(
       
    70             'value="My Section"' in response.content,
       
    71             "Couldn't find an input with the right value in the response."
       
    72         )
       
    73 
       
    74     def testBasicEditGet(self):
       
    75         """
       
    76         A smoke test to ensure GET on the change_view works.
       
    77         """
       
    78         response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
       
    79         self.assertEqual(response.status_code, 200)
       
    80 
       
    81     def testBasicEditGetStringPK(self):
       
    82         """
       
    83         A smoke test to ensure GET on the change_view works (returns an HTTP
       
    84         404 error, see #11191) when passing a string as the PK argument for a
       
    85         model with an integer PK field.
       
    86         """
       
    87         response = self.client.get('/test_admin/%s/admin_views/section/abc/' % self.urlbit)
       
    88         self.assertEqual(response.status_code, 404)
       
    89 
       
    90     def testBasicAddPost(self):
       
    91         """
       
    92         A smoke test to ensure POST on add_view works.
       
    93         """
       
    94         post_data = {
       
    95             "name": u"Another Section",
       
    96             # inline data
       
    97             "article_set-TOTAL_FORMS": u"3",
       
    98             "article_set-INITIAL_FORMS": u"0",
       
    99             "article_set-MAX_NUM_FORMS": u"0",
       
   100         }
       
   101         response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
       
   102         self.assertEqual(response.status_code, 302) # redirect somewhere
       
   103 
       
   104     # Post data for edit inline
       
   105     inline_post_data = {
       
   106         "name": u"Test section",
       
   107         # inline data
       
   108         "article_set-TOTAL_FORMS": u"6",
       
   109         "article_set-INITIAL_FORMS": u"3",
       
   110         "article_set-MAX_NUM_FORMS": u"0",
       
   111         "article_set-0-id": u"1",
       
   112         # there is no title in database, give one here or formset will fail.
       
   113         "article_set-0-title": u"Norske bostaver æøå skaper problemer",
       
   114         "article_set-0-content": u"<p>Middle content</p>",
       
   115         "article_set-0-date_0": u"2008-03-18",
       
   116         "article_set-0-date_1": u"11:54:58",
       
   117         "article_set-0-section": u"1",
       
   118         "article_set-1-id": u"2",
       
   119         "article_set-1-title": u"Need a title.",
       
   120         "article_set-1-content": u"<p>Oldest content</p>",
       
   121         "article_set-1-date_0": u"2000-03-18",
       
   122         "article_set-1-date_1": u"11:54:58",
       
   123         "article_set-2-id": u"3",
       
   124         "article_set-2-title": u"Need a title.",
       
   125         "article_set-2-content": u"<p>Newest content</p>",
       
   126         "article_set-2-date_0": u"2009-03-18",
       
   127         "article_set-2-date_1": u"11:54:58",
       
   128         "article_set-3-id": u"",
       
   129         "article_set-3-title": u"",
       
   130         "article_set-3-content": u"",
       
   131         "article_set-3-date_0": u"",
       
   132         "article_set-3-date_1": u"",
       
   133         "article_set-4-id": u"",
       
   134         "article_set-4-title": u"",
       
   135         "article_set-4-content": u"",
       
   136         "article_set-4-date_0": u"",
       
   137         "article_set-4-date_1": u"",
       
   138         "article_set-5-id": u"",
       
   139         "article_set-5-title": u"",
       
   140         "article_set-5-content": u"",
       
   141         "article_set-5-date_0": u"",
       
   142         "article_set-5-date_1": u"",
       
   143     }
       
   144 
       
   145     def testBasicEditPost(self):
       
   146         """
       
   147         A smoke test to ensure POST on edit_view works.
       
   148         """
       
   149         response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, self.inline_post_data)
       
   150         self.assertEqual(response.status_code, 302) # redirect somewhere
       
   151 
       
   152     def testEditSaveAs(self):
       
   153         """
       
   154         Test "save as".
       
   155         """
       
   156         post_data = self.inline_post_data.copy()
       
   157         post_data.update({
       
   158             '_saveasnew': u'Save+as+new',
       
   159             "article_set-1-section": u"1",
       
   160             "article_set-2-section": u"1",
       
   161             "article_set-3-section": u"1",
       
   162             "article_set-4-section": u"1",
       
   163             "article_set-5-section": u"1",
       
   164         })
       
   165         response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, post_data)
       
   166         self.assertEqual(response.status_code, 302) # redirect somewhere
       
   167 
       
   168     def testChangeListSortingCallable(self):
       
   169         """
       
   170         Ensure we can sort on a list_display field that is a callable
       
   171         (column 2 is callable_year in ArticleAdmin)
       
   172         """
       
   173         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2})
       
   174         self.assertEqual(response.status_code, 200)
       
   175         self.assertTrue(
       
   176             response.content.index('Oldest content') < response.content.index('Middle content') and
       
   177             response.content.index('Middle content') < response.content.index('Newest content'),
       
   178             "Results of sorting on callable are out of order."
       
   179         )
       
   180 
       
   181     def testChangeListSortingModel(self):
       
   182         """
       
   183         Ensure we can sort on a list_display field that is a Model method
       
   184         (colunn 3 is 'model_year' in ArticleAdmin)
       
   185         """
       
   186         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3})
       
   187         self.assertEqual(response.status_code, 200)
       
   188         self.assertTrue(
       
   189             response.content.index('Newest content') < response.content.index('Middle content') and
       
   190             response.content.index('Middle content') < response.content.index('Oldest content'),
       
   191             "Results of sorting on Model method are out of order."
       
   192         )
       
   193 
       
   194     def testChangeListSortingModelAdmin(self):
       
   195         """
       
   196         Ensure we can sort on a list_display field that is a ModelAdmin method
       
   197         (colunn 4 is 'modeladmin_year' in ArticleAdmin)
       
   198         """
       
   199         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4})
       
   200         self.assertEqual(response.status_code, 200)
       
   201         self.assertTrue(
       
   202             response.content.index('Oldest content') < response.content.index('Middle content') and
       
   203             response.content.index('Middle content') < response.content.index('Newest content'),
       
   204             "Results of sorting on ModelAdmin method are out of order."
       
   205         )
       
   206 
       
   207     def testLimitedFilter(self):
       
   208         """Ensure admin changelist filters do not contain objects excluded via limit_choices_to."""
       
   209         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit)
       
   210         self.assertEqual(response.status_code, 200)
       
   211         self.assertTrue(
       
   212             '<div id="changelist-filter">' in response.content,
       
   213             "Expected filter not found in changelist view."
       
   214         )
       
   215         self.assertFalse(
       
   216             '<a href="?color__id__exact=3">Blue</a>' in response.content,
       
   217             "Changelist filter not correctly limited by limit_choices_to."
       
   218         )
       
   219 
       
   220     def testIncorrectLookupParameters(self):
       
   221         """Ensure incorrect lookup parameters are handled gracefully."""
       
   222         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'})
       
   223         self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
       
   224         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
       
   225         self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
       
   226 
       
   227     def testIsNullLookups(self):
       
   228         """Ensure is_null is handled correctly."""
       
   229         Article.objects.create(title="I Could Go Anywhere", content="Versatile", date=datetime.datetime.now())
       
   230         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
       
   231         self.assertTrue('4 articles' in response.content, '"4 articles" missing from response')
       
   232         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'false'})
       
   233         self.assertTrue('3 articles' in response.content, '"3 articles" missing from response')
       
   234         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'true'})
       
   235         self.assertTrue('1 article' in response.content, '"1 article" missing from response')
       
   236 
       
   237     def testLogoutAndPasswordChangeURLs(self):
       
   238         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
       
   239         self.assertFalse('<a href="/test_admin/%s/logout/">' % self.urlbit not in response.content)
       
   240         self.assertFalse('<a href="/test_admin/%s/password_change/">' % self.urlbit not in response.content)
       
   241 
       
   242     def testNamedGroupFieldChoicesChangeList(self):
       
   243         """
       
   244         Ensures the admin changelist shows correct values in the relevant column
       
   245         for rows corresponding to instances of a model in which a named group
       
   246         has been used in the choices option of a field.
       
   247         """
       
   248         response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
       
   249         self.assertEqual(response.status_code, 200)
       
   250         self.assertTrue(
       
   251             '<a href="1/">Horizontal</a>' in response.content and
       
   252             '<a href="2/">Vertical</a>' in response.content,
       
   253             "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
       
   254         )
       
   255 
       
   256     def testNamedGroupFieldChoicesFilter(self):
       
   257         """
       
   258         Ensures the filter UI shows correctly when at least one named group has
       
   259         been used in the choices option of a model field.
       
   260         """
       
   261         response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
       
   262         self.assertEqual(response.status_code, 200)
       
   263         self.assertTrue(
       
   264             '<div id="changelist-filter">' in response.content,
       
   265             "Expected filter not found in changelist view."
       
   266         )
       
   267         self.assertTrue(
       
   268             '<a href="?surface__exact=x">Horizontal</a>' in response.content and
       
   269             '<a href="?surface__exact=y">Vertical</a>' in response.content,
       
   270             "Changelist filter isn't showing options contained inside a model field 'choices' option named group."
       
   271         )
       
   272 
       
   273     def testChangeListNullBooleanDisplay(self):
       
   274         Post.objects.create(public=None)
       
   275         # This hard-codes the URl because it'll fail if it runs
       
   276         # against the 'admin2' custom admin (which doesn't have the
       
   277         # Post model).
       
   278         response = self.client.get("/test_admin/admin/admin_views/post/")
       
   279         self.assertTrue('icon-unknown.gif' in response.content)
       
   280 
       
   281     def testI18NLanguageNonEnglishDefault(self):
       
   282         """
       
   283         Check if the Javascript i18n view returns an empty language catalog
       
   284         if the default language is non-English but the selected language
       
   285         is English. See #13388 and #3594 for more details.
       
   286         """
       
   287         settings.LANGUAGE_CODE = 'fr'
       
   288         activate('en-us')
       
   289         response = self.client.get('/test_admin/admin/jsi18n/')
       
   290         self.assertNotContains(response, 'Choisir une heure')
       
   291         deactivate()
       
   292 
       
   293     def testI18NLanguageNonEnglishFallback(self):
       
   294         """
       
   295         Makes sure that the fallback language is still working properly
       
   296         in cases where the selected language cannot be found.
       
   297         """
       
   298         settings.LANGUAGE_CODE = 'fr'
       
   299         activate('none')
       
   300         response = self.client.get('/test_admin/admin/jsi18n/')
       
   301         self.assertContains(response, 'Choisir une heure')
       
   302         deactivate()
       
   303 
       
   304     def test_disallowed_filtering(self):
       
   305         self.assertRaises(SuspiciousOperation,
       
   306             self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
       
   307         )
       
   308 
       
   309 class SaveAsTests(TestCase):
       
   310     fixtures = ['admin-views-users.xml','admin-views-person.xml']
       
   311 
       
   312     def setUp(self):
       
   313         self.client.login(username='super', password='secret')
       
   314 
       
   315     def tearDown(self):
       
   316         self.client.logout()
       
   317 
       
   318     def test_save_as_duplication(self):
       
   319         """Ensure save as actually creates a new person"""
       
   320         post_data = {'_saveasnew':'', 'name':'John M', 'gender':1}
       
   321         response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
       
   322         self.assertEqual(len(Person.objects.filter(name='John M')), 1)
       
   323         self.assertEqual(len(Person.objects.filter(id=1)), 1)
       
   324 
       
   325     def test_save_as_display(self):
       
   326         """
       
   327         Ensure that 'save as' is displayed when activated and after submitting
       
   328         invalid data aside save_as_new will not show us a form to overwrite the
       
   329         initial model.
       
   330         """
       
   331         response = self.client.get('/test_admin/admin/admin_views/person/1/')
       
   332         self.assert_(response.context['save_as'])
       
   333         post_data = {'_saveasnew':'', 'name':'John M', 'gender':3, 'alive':'checked'}
       
   334         response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
       
   335         self.assertEqual(response.context['form_url'], '../add/')
       
   336 
       
   337 class CustomModelAdminTest(AdminViewBasicTest):
       
   338     urlbit = "admin2"
       
   339 
       
   340     def testCustomAdminSiteLoginTemplate(self):
       
   341         self.client.logout()
       
   342         request = self.client.get('/test_admin/admin2/')
       
   343         self.assertTemplateUsed(request, 'custom_admin/login.html')
       
   344         self.assert_('Hello from a custom login template' in request.content)
       
   345 
       
   346     def testCustomAdminSiteLogoutTemplate(self):
       
   347         request = self.client.get('/test_admin/admin2/logout/')
       
   348         self.assertTemplateUsed(request, 'custom_admin/logout.html')
       
   349         self.assert_('Hello from a custom logout template' in request.content)
       
   350 
       
   351     def testCustomAdminSiteIndexViewAndTemplate(self):
       
   352         request = self.client.get('/test_admin/admin2/')
       
   353         self.assertTemplateUsed(request, 'custom_admin/index.html')
       
   354         self.assert_('Hello from a custom index template *bar*' in request.content)
       
   355 
       
   356     def testCustomAdminSitePasswordChangeTemplate(self):
       
   357         request = self.client.get('/test_admin/admin2/password_change/')
       
   358         self.assertTemplateUsed(request, 'custom_admin/password_change_form.html')
       
   359         self.assert_('Hello from a custom password change form template' in request.content)
       
   360 
       
   361     def testCustomAdminSitePasswordChangeDoneTemplate(self):
       
   362         request = self.client.get('/test_admin/admin2/password_change/done/')
       
   363         self.assertTemplateUsed(request, 'custom_admin/password_change_done.html')
       
   364         self.assert_('Hello from a custom password change done template' in request.content)
       
   365 
       
   366     def testCustomAdminSiteView(self):
       
   367         self.client.login(username='super', password='secret')
       
   368         response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
       
   369         self.assert_(response.content == "Django is a magical pony!", response.content)
       
   370 
       
   371 def get_perm(Model, perm):
       
   372     """Return the permission object, for the Model"""
       
   373     ct = ContentType.objects.get_for_model(Model)
       
   374     return Permission.objects.get(content_type=ct, codename=perm)
       
   375 
       
   376 class AdminViewPermissionsTest(TestCase):
       
   377     """Tests for Admin Views Permissions."""
       
   378 
       
   379     fixtures = ['admin-views-users.xml']
       
   380 
       
   381     def setUp(self):
       
   382         """Test setup."""
       
   383         # Setup permissions, for our users who can add, change, and delete.
       
   384         # We can't put this into the fixture, because the content type id
       
   385         # and the permission id could be different on each run of the test.
       
   386 
       
   387         opts = Article._meta
       
   388 
       
   389         # User who can add Articles
       
   390         add_user = User.objects.get(username='adduser')
       
   391         add_user.user_permissions.add(get_perm(Article,
       
   392             opts.get_add_permission()))
       
   393 
       
   394         # User who can change Articles
       
   395         change_user = User.objects.get(username='changeuser')
       
   396         change_user.user_permissions.add(get_perm(Article,
       
   397             opts.get_change_permission()))
       
   398 
       
   399         # User who can delete Articles
       
   400         delete_user = User.objects.get(username='deleteuser')
       
   401         delete_user.user_permissions.add(get_perm(Article,
       
   402             opts.get_delete_permission()))
       
   403 
       
   404         delete_user.user_permissions.add(get_perm(Section,
       
   405             Section._meta.get_delete_permission()))
       
   406 
       
   407         # login POST dicts
       
   408         self.super_login = {
       
   409                      LOGIN_FORM_KEY: 1,
       
   410                      'username': 'super',
       
   411                      'password': 'secret'}
       
   412         self.super_email_login = {
       
   413                      LOGIN_FORM_KEY: 1,
       
   414                      'username': 'super@example.com',
       
   415                      'password': 'secret'}
       
   416         self.super_email_bad_login = {
       
   417                       LOGIN_FORM_KEY: 1,
       
   418                       'username': 'super@example.com',
       
   419                       'password': 'notsecret'}
       
   420         self.adduser_login = {
       
   421                      LOGIN_FORM_KEY: 1,
       
   422                      'username': 'adduser',
       
   423                      'password': 'secret'}
       
   424         self.changeuser_login = {
       
   425                      LOGIN_FORM_KEY: 1,
       
   426                      'username': 'changeuser',
       
   427                      'password': 'secret'}
       
   428         self.deleteuser_login = {
       
   429                      LOGIN_FORM_KEY: 1,
       
   430                      'username': 'deleteuser',
       
   431                      'password': 'secret'}
       
   432         self.joepublic_login = {
       
   433                      LOGIN_FORM_KEY: 1,
       
   434                      'username': 'joepublic',
       
   435                      'password': 'secret'}
       
   436         self.no_username_login = {
       
   437                      LOGIN_FORM_KEY: 1,
       
   438                      'password': 'secret'}
       
   439 
       
   440     def testLogin(self):
       
   441         """
       
   442         Make sure only staff members can log in.
       
   443 
       
   444         Successful posts to the login page will redirect to the orignal url.
       
   445         Unsuccessfull attempts will continue to render the login page with
       
   446         a 200 status code.
       
   447         """
       
   448         # Super User
       
   449         request = self.client.get('/test_admin/admin/')
       
   450         self.assertEqual(request.status_code, 200)
       
   451         login = self.client.post('/test_admin/admin/', self.super_login)
       
   452         self.assertRedirects(login, '/test_admin/admin/')
       
   453         self.assertFalse(login.context)
       
   454         self.client.get('/test_admin/admin/logout/')
       
   455 
       
   456         # Test if user enters e-mail address
       
   457         request = self.client.get('/test_admin/admin/')
       
   458         self.assertEqual(request.status_code, 200)
       
   459         login = self.client.post('/test_admin/admin/', self.super_email_login)
       
   460         self.assertContains(login, "Your e-mail address is not your username")
       
   461         # only correct passwords get a username hint
       
   462         login = self.client.post('/test_admin/admin/', self.super_email_bad_login)
       
   463         self.assertContains(login, "Please enter a correct username and password")
       
   464         new_user = User(username='jondoe', password='secret', email='super@example.com')
       
   465         new_user.save()
       
   466         # check to ensure if there are multiple e-mail addresses a user doesn't get a 500
       
   467         login = self.client.post('/test_admin/admin/', self.super_email_login)
       
   468         self.assertContains(login, "Please enter a correct username and password")
       
   469 
       
   470         # Add User
       
   471         request = self.client.get('/test_admin/admin/')
       
   472         self.assertEqual(request.status_code, 200)
       
   473         login = self.client.post('/test_admin/admin/', self.adduser_login)
       
   474         self.assertRedirects(login, '/test_admin/admin/')
       
   475         self.assertFalse(login.context)
       
   476         self.client.get('/test_admin/admin/logout/')
       
   477 
       
   478         # Change User
       
   479         request = self.client.get('/test_admin/admin/')
       
   480         self.assertEqual(request.status_code, 200)
       
   481         login = self.client.post('/test_admin/admin/', self.changeuser_login)
       
   482         self.assertRedirects(login, '/test_admin/admin/')
       
   483         self.assertFalse(login.context)
       
   484         self.client.get('/test_admin/admin/logout/')
       
   485 
       
   486         # Delete User
       
   487         request = self.client.get('/test_admin/admin/')
       
   488         self.assertEqual(request.status_code, 200)
       
   489         login = self.client.post('/test_admin/admin/', self.deleteuser_login)
       
   490         self.assertRedirects(login, '/test_admin/admin/')
       
   491         self.assertFalse(login.context)
       
   492         self.client.get('/test_admin/admin/logout/')
       
   493 
       
   494         # Regular User should not be able to login.
       
   495         request = self.client.get('/test_admin/admin/')
       
   496         self.assertEqual(request.status_code, 200)
       
   497         login = self.client.post('/test_admin/admin/', self.joepublic_login)
       
   498         self.assertEqual(login.status_code, 200)
       
   499         self.assertContains(login, "Please enter a correct username and password.")
       
   500 
       
   501         # Requests without username should not return 500 errors.
       
   502         request = self.client.get('/test_admin/admin/')
       
   503         self.assertEqual(request.status_code, 200)
       
   504         login = self.client.post('/test_admin/admin/', self.no_username_login)
       
   505         self.assertEqual(login.status_code, 200)
       
   506         form = login.context[0].get('form')
       
   507         self.assert_(login.context[0].get('error_message'))
       
   508 
       
   509     def testLoginSuccessfullyRedirectsToOriginalUrl(self):
       
   510         request = self.client.get('/test_admin/admin/')
       
   511         self.assertEqual(request.status_code, 200)
       
   512         query_string = 'the-answer=42'
       
   513         redirect_url = '/test_admin/admin/?%s' % query_string
       
   514         new_next = {REDIRECT_FIELD_NAME: redirect_url}
       
   515         login = self.client.post('/test_admin/admin/', dict(self.super_login, **new_next), QUERY_STRING=query_string)
       
   516         self.assertRedirects(login, redirect_url)
       
   517 
       
   518     def testAddView(self):
       
   519         """Test add view restricts access and actually adds items."""
       
   520 
       
   521         add_dict = {'title' : 'Døm ikke',
       
   522                     'content': '<p>great article</p>',
       
   523                     'date_0': '2008-03-18', 'date_1': '10:54:39',
       
   524                     'section': 1}
       
   525 
       
   526         # Change User should not have access to add articles
       
   527         self.client.get('/test_admin/admin/')
       
   528         self.client.post('/test_admin/admin/', self.changeuser_login)
       
   529         # make sure the view removes test cookie
       
   530         self.assertEqual(self.client.session.test_cookie_worked(), False)
       
   531         request = self.client.get('/test_admin/admin/admin_views/article/add/')
       
   532         self.assertEqual(request.status_code, 403)
       
   533         # Try POST just to make sure
       
   534         post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
       
   535         self.assertEqual(post.status_code, 403)
       
   536         self.assertEqual(Article.objects.all().count(), 3)
       
   537         self.client.get('/test_admin/admin/logout/')
       
   538 
       
   539         # Add user may login and POST to add view, then redirect to admin root
       
   540         self.client.get('/test_admin/admin/')
       
   541         self.client.post('/test_admin/admin/', self.adduser_login)
       
   542         addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
       
   543         self.assertEqual(addpage.status_code, 200)
       
   544         change_list_link = '<a href="../">Articles</a> &rsaquo;'
       
   545         self.assertFalse(change_list_link in addpage.content,
       
   546                     'User restricted to add permission is given link to change list view in breadcrumbs.')
       
   547         post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
       
   548         self.assertRedirects(post, '/test_admin/admin/')
       
   549         self.assertEqual(Article.objects.all().count(), 4)
       
   550         self.client.get('/test_admin/admin/logout/')
       
   551 
       
   552         # Super can add too, but is redirected to the change list view
       
   553         self.client.get('/test_admin/admin/')
       
   554         self.client.post('/test_admin/admin/', self.super_login)
       
   555         addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
       
   556         self.assertEqual(addpage.status_code, 200)
       
   557         self.assertFalse(change_list_link not in addpage.content,
       
   558                     'Unrestricted user is not given link to change list view in breadcrumbs.')
       
   559         post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
       
   560         self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
       
   561         self.assertEqual(Article.objects.all().count(), 5)
       
   562         self.client.get('/test_admin/admin/logout/')
       
   563 
       
   564         # 8509 - if a normal user is already logged in, it is possible
       
   565         # to change user into the superuser without error
       
   566         login = self.client.login(username='joepublic', password='secret')
       
   567         # Check and make sure that if user expires, data still persists
       
   568         self.client.get('/test_admin/admin/')
       
   569         self.client.post('/test_admin/admin/', self.super_login)
       
   570         # make sure the view removes test cookie
       
   571         self.assertEqual(self.client.session.test_cookie_worked(), False)
       
   572 
       
   573     def testChangeView(self):
       
   574         """Change view should restrict access and allow users to edit items."""
       
   575 
       
   576         change_dict = {'title' : 'Ikke fordømt',
       
   577                        'content': '<p>edited article</p>',
       
   578                        'date_0': '2008-03-18', 'date_1': '10:54:39',
       
   579                        'section': 1}
       
   580 
       
   581         # add user shoud not be able to view the list of article or change any of them
       
   582         self.client.get('/test_admin/admin/')
       
   583         self.client.post('/test_admin/admin/', self.adduser_login)
       
   584         request = self.client.get('/test_admin/admin/admin_views/article/')
       
   585         self.assertEqual(request.status_code, 403)
       
   586         request = self.client.get('/test_admin/admin/admin_views/article/1/')
       
   587         self.assertEqual(request.status_code, 403)
       
   588         post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
       
   589         self.assertEqual(post.status_code, 403)
       
   590         self.client.get('/test_admin/admin/logout/')
       
   591 
       
   592         # change user can view all items and edit them
       
   593         self.client.get('/test_admin/admin/')
       
   594         self.client.post('/test_admin/admin/', self.changeuser_login)
       
   595         request = self.client.get('/test_admin/admin/admin_views/article/')
       
   596         self.assertEqual(request.status_code, 200)
       
   597         request = self.client.get('/test_admin/admin/admin_views/article/1/')
       
   598         self.assertEqual(request.status_code, 200)
       
   599         post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
       
   600         self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
       
   601         self.assertEqual(Article.objects.get(pk=1).content, '<p>edited article</p>')
       
   602 
       
   603         # one error in form should produce singular error message, multiple errors plural
       
   604         change_dict['title'] = ''
       
   605         post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
       
   606         self.assertEqual(request.status_code, 200)
       
   607         self.assertTrue('Please correct the error below.' in post.content,
       
   608                         'Singular error message not found in response to post with one error.')
       
   609         change_dict['content'] = ''
       
   610         post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
       
   611         self.assertEqual(request.status_code, 200)
       
   612         self.assertTrue('Please correct the errors below.' in post.content,
       
   613                         'Plural error message not found in response to post with multiple errors.')
       
   614         self.client.get('/test_admin/admin/logout/')
       
   615 
       
   616     def testCustomModelAdminTemplates(self):
       
   617         self.client.get('/test_admin/admin/')
       
   618         self.client.post('/test_admin/admin/', self.super_login)
       
   619 
       
   620         # Test custom change list template with custom extra context
       
   621         request = self.client.get('/test_admin/admin/admin_views/customarticle/')
       
   622         self.assertEqual(request.status_code, 200)
       
   623         self.assert_("var hello = 'Hello!';" in request.content)
       
   624         self.assertTemplateUsed(request, 'custom_admin/change_list.html')
       
   625 
       
   626         # Test custom add form template
       
   627         request = self.client.get('/test_admin/admin/admin_views/customarticle/add/')
       
   628         self.assertTemplateUsed(request, 'custom_admin/add_form.html')
       
   629 
       
   630         # Add an article so we can test delete, change, and history views
       
   631         post = self.client.post('/test_admin/admin/admin_views/customarticle/add/', {
       
   632             'content': '<p>great article</p>',
       
   633             'date_0': '2008-03-18',
       
   634             'date_1': '10:54:39'
       
   635         })
       
   636         self.assertRedirects(post, '/test_admin/admin/admin_views/customarticle/')
       
   637         self.assertEqual(CustomArticle.objects.all().count(), 1)
       
   638 
       
   639         # Test custom delete, change, and object history templates
       
   640         # Test custom change form template
       
   641         request = self.client.get('/test_admin/admin/admin_views/customarticle/1/')
       
   642         self.assertTemplateUsed(request, 'custom_admin/change_form.html')
       
   643         request = self.client.get('/test_admin/admin/admin_views/customarticle/1/delete/')
       
   644         self.assertTemplateUsed(request, 'custom_admin/delete_confirmation.html')
       
   645         request = self.client.post('/test_admin/admin/admin_views/customarticle/', data={
       
   646                 'index': 0,
       
   647                 'action': ['delete_selected'],
       
   648                 '_selected_action': ['1'],
       
   649             })
       
   650         self.assertTemplateUsed(request, 'custom_admin/delete_selected_confirmation.html')
       
   651         request = self.client.get('/test_admin/admin/admin_views/customarticle/1/history/')
       
   652         self.assertTemplateUsed(request, 'custom_admin/object_history.html')
       
   653 
       
   654         self.client.get('/test_admin/admin/logout/')
       
   655 
       
   656     def testDeleteView(self):
       
   657         """Delete view should restrict access and actually delete items."""
       
   658 
       
   659         delete_dict = {'post': 'yes'}
       
   660 
       
   661         # add user shoud not be able to delete articles
       
   662         self.client.get('/test_admin/admin/')
       
   663         self.client.post('/test_admin/admin/', self.adduser_login)
       
   664         request = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
       
   665         self.assertEqual(request.status_code, 403)
       
   666         post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
       
   667         self.assertEqual(post.status_code, 403)
       
   668         self.assertEqual(Article.objects.all().count(), 3)
       
   669         self.client.get('/test_admin/admin/logout/')
       
   670 
       
   671         # Delete user can delete
       
   672         self.client.get('/test_admin/admin/')
       
   673         self.client.post('/test_admin/admin/', self.deleteuser_login)
       
   674         response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
       
   675          # test response contains link to related Article
       
   676         self.assertContains(response, "admin_views/article/1/")
       
   677 
       
   678         response = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
       
   679         self.assertEqual(response.status_code, 200)
       
   680         post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
       
   681         self.assertRedirects(post, '/test_admin/admin/')
       
   682         self.assertEqual(Article.objects.all().count(), 2)
       
   683         article_ct = ContentType.objects.get_for_model(Article)
       
   684         logged = LogEntry.objects.get(content_type=article_ct, action_flag=DELETION)
       
   685         self.assertEqual(logged.object_id, u'1')
       
   686         self.client.get('/test_admin/admin/logout/')
       
   687 
       
   688     def testDisabledPermissionsWhenLoggedIn(self):
       
   689         self.client.login(username='super', password='secret')
       
   690         superuser = User.objects.get(username='super')
       
   691         superuser.is_active = False
       
   692         superuser.save()
       
   693 
       
   694         response = self.client.get('/test_admin/admin/')
       
   695         self.assertContains(response, 'id="login-form"')
       
   696         self.assertNotContains(response, 'Log out')
       
   697 
       
   698         response = self.client.get('/test_admin/admin/secure-view/')
       
   699         self.assertContains(response, 'id="login-form"')
       
   700 
       
   701 
       
   702 class AdminViewDeletedObjectsTest(TestCase):
       
   703     fixtures = ['admin-views-users.xml', 'deleted-objects.xml']
       
   704 
       
   705     def setUp(self):
       
   706         self.client.login(username='super', password='secret')
       
   707 
       
   708     def tearDown(self):
       
   709         self.client.logout()
       
   710 
       
   711     def test_nesting(self):
       
   712         """
       
   713         Objects should be nested to display the relationships that
       
   714         cause them to be scheduled for deletion.
       
   715         """
       
   716         pattern = re.compile(r"""<li>Plot: <a href=".+/admin_views/plot/1/">World Domination</a>\s*<ul>\s*<li>Plot details: <a href=".+/admin_views/plotdetails/1/">almost finished</a>""")
       
   717         response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
       
   718         self.assertTrue(pattern.search(response.content))
       
   719 
       
   720     def test_cyclic(self):
       
   721         """
       
   722         Cyclic relationships should still cause each object to only be
       
   723         listed once.
       
   724 
       
   725         """
       
   726         one = """<li>Cyclic one: <a href="/test_admin/admin/admin_views/cyclicone/1/">I am recursive</a>"""
       
   727         two = """<li>Cyclic two: <a href="/test_admin/admin/admin_views/cyclictwo/1/">I am recursive too</a>"""
       
   728         response = self.client.get('/test_admin/admin/admin_views/cyclicone/%s/delete/' % quote(1))
       
   729 
       
   730         self.assertContains(response, one, 1)
       
   731         self.assertContains(response, two, 1)
       
   732 
       
   733     def test_perms_needed(self):
       
   734         self.client.logout()
       
   735         delete_user = User.objects.get(username='deleteuser')
       
   736         delete_user.user_permissions.add(get_perm(Plot,
       
   737             Plot._meta.get_delete_permission()))
       
   738 
       
   739         self.assertTrue(self.client.login(username='deleteuser',
       
   740                                           password='secret'))
       
   741 
       
   742         response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(1))
       
   743         self.assertContains(response, "your account doesn't have permission to delete the following types of objects")
       
   744         self.assertContains(response, "<li>plot details</li>")
       
   745 
       
   746 
       
   747     def test_not_registered(self):
       
   748         should_contain = """<li>Secret hideout: underground bunker"""
       
   749         response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
       
   750         self.assertContains(response, should_contain, 1)
       
   751 
       
   752     def test_multiple_fkeys_to_same_model(self):
       
   753         """
       
   754         If a deleted object has two relationships from another model,
       
   755         both of those should be followed in looking for related
       
   756         objects to delete.
       
   757 
       
   758         """
       
   759         should_contain = """<li>Plot: <a href="/test_admin/admin/admin_views/plot/1/">World Domination</a>"""
       
   760         response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
       
   761         self.assertContains(response, should_contain)
       
   762         response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
       
   763         self.assertContains(response, should_contain)
       
   764 
       
   765     def test_multiple_fkeys_to_same_instance(self):
       
   766         """
       
   767         If a deleted object has two relationships pointing to it from
       
   768         another object, the other object should still only be listed
       
   769         once.
       
   770 
       
   771         """
       
   772         should_contain = """<li>Plot: <a href="/test_admin/admin/admin_views/plot/2/">World Peace</a></li>"""
       
   773         response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
       
   774         self.assertContains(response, should_contain, 1)
       
   775 
       
   776     def test_inheritance(self):
       
   777         """
       
   778         In the case of an inherited model, if either the child or
       
   779         parent-model instance is deleted, both instances are listed
       
   780         for deletion, as well as any relationships they have.
       
   781 
       
   782         """
       
   783         should_contain = [
       
   784             """<li>Villain: <a href="/test_admin/admin/admin_views/villain/3/">Bob</a>""",
       
   785             """<li>Super villain: <a href="/test_admin/admin/admin_views/supervillain/3/">Bob</a>""",
       
   786             """<li>Secret hideout: floating castle""",
       
   787             """<li>Super secret hideout: super floating castle!"""
       
   788             ]
       
   789         response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(3))
       
   790         for should in should_contain:
       
   791             self.assertContains(response, should, 1)
       
   792         response = self.client.get('/test_admin/admin/admin_views/supervillain/%s/delete/' % quote(3))
       
   793         for should in should_contain:
       
   794             self.assertContains(response, should, 1)
       
   795 
       
   796     def test_generic_relations(self):
       
   797         """
       
   798         If a deleted object has GenericForeignKeys pointing to it,
       
   799         those objects should be listed for deletion.
       
   800 
       
   801         """
       
   802         plot = Plot.objects.get(pk=3)
       
   803         tag = FunkyTag.objects.create(content_object=plot, name='hott')
       
   804         should_contain = """<li>Funky tag: hott"""
       
   805         response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(3))
       
   806         self.assertContains(response, should_contain)
       
   807 
       
   808 class AdminViewStringPrimaryKeyTest(TestCase):
       
   809     fixtures = ['admin-views-users.xml', 'string-primary-key.xml']
       
   810 
       
   811     def __init__(self, *args):
       
   812         super(AdminViewStringPrimaryKeyTest, self).__init__(*args)
       
   813         self.pk = """abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`"""
       
   814 
       
   815     def setUp(self):
       
   816         self.client.login(username='super', password='secret')
       
   817         content_type_pk = ContentType.objects.get_for_model(ModelWithStringPrimaryKey).pk
       
   818         LogEntry.objects.log_action(100, content_type_pk, self.pk, self.pk, 2, change_message='')
       
   819 
       
   820     def tearDown(self):
       
   821         self.client.logout()
       
   822 
       
   823     def test_get_history_view(self):
       
   824         "Retrieving the history for the object using urlencoded form of primary key should work"
       
   825         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/history/' % quote(self.pk))
       
   826         self.assertContains(response, escape(self.pk))
       
   827         self.assertEqual(response.status_code, 200)
       
   828 
       
   829     def test_get_change_view(self):
       
   830         "Retrieving the object using urlencoded form of primary key should work"
       
   831         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk))
       
   832         self.assertContains(response, escape(self.pk))
       
   833         self.assertEqual(response.status_code, 200)
       
   834 
       
   835     def test_changelist_to_changeform_link(self):
       
   836         "The link from the changelist referring to the changeform of the object should be quoted"
       
   837         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/')
       
   838         should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk))
       
   839         self.assertContains(response, should_contain)
       
   840 
       
   841     def test_recentactions_link(self):
       
   842         "The link from the recent actions list referring to the changeform of the object should be quoted"
       
   843         response = self.client.get('/test_admin/admin/')
       
   844         should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
       
   845         self.assertContains(response, should_contain)
       
   846 
       
   847     def test_recentactions_without_content_type(self):
       
   848         "If a LogEntry is missing content_type it will not display it in span tag under the hyperlink."
       
   849         response = self.client.get('/test_admin/admin/')
       
   850         should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
       
   851         self.assertContains(response, should_contain)
       
   852         should_contain = "Model with string primary key" # capitalized in Recent Actions
       
   853         self.assertContains(response, should_contain)
       
   854         logentry = LogEntry.objects.get(content_type__name__iexact=should_contain)
       
   855         # http://code.djangoproject.com/ticket/10275
       
   856         # if the log entry doesn't have a content type it should still be
       
   857         # possible to view the Recent Actions part
       
   858         logentry.content_type = None
       
   859         logentry.save()
       
   860 
       
   861         counted_presence_before = response.content.count(should_contain)
       
   862         response = self.client.get('/test_admin/admin/')
       
   863         counted_presence_after = response.content.count(should_contain)
       
   864         self.assertEquals(counted_presence_before - 1,
       
   865                           counted_presence_after)
       
   866 
       
   867     def test_deleteconfirmation_link(self):
       
   868         "The link from the delete confirmation page referring back to the changeform of the object should be quoted"
       
   869         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk))
       
   870         # this URL now comes through reverse(), thus iri_to_uri encoding
       
   871         should_contain = """/%s/">%s</a>""" % (iri_to_uri(quote(self.pk)), escape(self.pk))
       
   872         self.assertContains(response, should_contain)
       
   873 
       
   874     def test_url_conflicts_with_add(self):
       
   875         "A model with a primary key that ends with add should be visible"
       
   876         add_model = ModelWithStringPrimaryKey(id="i have something to add")
       
   877         add_model.save()
       
   878         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(add_model.pk))
       
   879         should_contain = """<h1>Change model with string primary key</h1>"""
       
   880         self.assertContains(response, should_contain)
       
   881 
       
   882     def test_url_conflicts_with_delete(self):
       
   883         "A model with a primary key that ends with delete should be visible"
       
   884         delete_model = ModelWithStringPrimaryKey(id="delete")
       
   885         delete_model.save()
       
   886         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(delete_model.pk))
       
   887         should_contain = """<h1>Change model with string primary key</h1>"""
       
   888         self.assertContains(response, should_contain)
       
   889 
       
   890     def test_url_conflicts_with_history(self):
       
   891         "A model with a primary key that ends with history should be visible"
       
   892         history_model = ModelWithStringPrimaryKey(id="history")
       
   893         history_model.save()
       
   894         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(history_model.pk))
       
   895         should_contain = """<h1>Change model with string primary key</h1>"""
       
   896         self.assertContains(response, should_contain)
       
   897 
       
   898 
       
   899 class SecureViewTest(TestCase):
       
   900     fixtures = ['admin-views-users.xml']
       
   901 
       
   902     def setUp(self):
       
   903         # login POST dicts
       
   904         self.super_login = {
       
   905                      LOGIN_FORM_KEY: 1,
       
   906                      'username': 'super',
       
   907                      'password': 'secret'}
       
   908         self.super_email_login = {
       
   909                      LOGIN_FORM_KEY: 1,
       
   910                      'username': 'super@example.com',
       
   911                      'password': 'secret'}
       
   912         self.super_email_bad_login = {
       
   913                       LOGIN_FORM_KEY: 1,
       
   914                       'username': 'super@example.com',
       
   915                       'password': 'notsecret'}
       
   916         self.adduser_login = {
       
   917                      LOGIN_FORM_KEY: 1,
       
   918                      'username': 'adduser',
       
   919                      'password': 'secret'}
       
   920         self.changeuser_login = {
       
   921                      LOGIN_FORM_KEY: 1,
       
   922                      'username': 'changeuser',
       
   923                      'password': 'secret'}
       
   924         self.deleteuser_login = {
       
   925                      LOGIN_FORM_KEY: 1,
       
   926                      'username': 'deleteuser',
       
   927                      'password': 'secret'}
       
   928         self.joepublic_login = {
       
   929                      LOGIN_FORM_KEY: 1,
       
   930                      'username': 'joepublic',
       
   931                      'password': 'secret'}
       
   932 
       
   933     def tearDown(self):
       
   934         self.client.logout()
       
   935 
       
   936     def test_secure_view_shows_login_if_not_logged_in(self):
       
   937         "Ensure that we see the login form"
       
   938         response = self.client.get('/test_admin/admin/secure-view/' )
       
   939         self.assertTemplateUsed(response, 'admin/login.html')
       
   940 
       
   941     def test_secure_view_login_successfully_redirects_to_original_url(self):
       
   942         request = self.client.get('/test_admin/admin/secure-view/')
       
   943         self.assertEqual(request.status_code, 200)
       
   944         query_string = 'the-answer=42'
       
   945         redirect_url = '/test_admin/admin/secure-view/?%s' % query_string
       
   946         new_next = {REDIRECT_FIELD_NAME: redirect_url}
       
   947         login = self.client.post('/test_admin/admin/secure-view/', dict(self.super_login, **new_next), QUERY_STRING=query_string)
       
   948         self.assertRedirects(login, redirect_url)
       
   949 
       
   950     def test_staff_member_required_decorator_works_as_per_admin_login(self):
       
   951         """
       
   952         Make sure only staff members can log in.
       
   953 
       
   954         Successful posts to the login page will redirect to the orignal url.
       
   955         Unsuccessfull attempts will continue to render the login page with
       
   956         a 200 status code.
       
   957         """
       
   958         # Super User
       
   959         request = self.client.get('/test_admin/admin/secure-view/')
       
   960         self.assertEqual(request.status_code, 200)
       
   961         login = self.client.post('/test_admin/admin/secure-view/', self.super_login)
       
   962         self.assertRedirects(login, '/test_admin/admin/secure-view/')
       
   963         self.assertFalse(login.context)
       
   964         self.client.get('/test_admin/admin/logout/')
       
   965         # make sure the view removes test cookie
       
   966         self.assertEqual(self.client.session.test_cookie_worked(), False)
       
   967 
       
   968         # Test if user enters e-mail address
       
   969         request = self.client.get('/test_admin/admin/secure-view/')
       
   970         self.assertEqual(request.status_code, 200)
       
   971         login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
       
   972         self.assertContains(login, "Your e-mail address is not your username")
       
   973         # only correct passwords get a username hint
       
   974         login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login)
       
   975         self.assertContains(login, "Please enter a correct username and password")
       
   976         new_user = User(username='jondoe', password='secret', email='super@example.com')
       
   977         new_user.save()
       
   978         # check to ensure if there are multiple e-mail addresses a user doesn't get a 500
       
   979         login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
       
   980         self.assertContains(login, "Please enter a correct username and password")
       
   981 
       
   982         # Add User
       
   983         request = self.client.get('/test_admin/admin/secure-view/')
       
   984         self.assertEqual(request.status_code, 200)
       
   985         login = self.client.post('/test_admin/admin/secure-view/', self.adduser_login)
       
   986         self.assertRedirects(login, '/test_admin/admin/secure-view/')
       
   987         self.assertFalse(login.context)
       
   988         self.client.get('/test_admin/admin/logout/')
       
   989 
       
   990         # Change User
       
   991         request = self.client.get('/test_admin/admin/secure-view/')
       
   992         self.assertEqual(request.status_code, 200)
       
   993         login = self.client.post('/test_admin/admin/secure-view/', self.changeuser_login)
       
   994         self.assertRedirects(login, '/test_admin/admin/secure-view/')
       
   995         self.assertFalse(login.context)
       
   996         self.client.get('/test_admin/admin/logout/')
       
   997 
       
   998         # Delete User
       
   999         request = self.client.get('/test_admin/admin/secure-view/')
       
  1000         self.assertEqual(request.status_code, 200)
       
  1001         login = self.client.post('/test_admin/admin/secure-view/', self.deleteuser_login)
       
  1002         self.assertRedirects(login, '/test_admin/admin/secure-view/')
       
  1003         self.assertFalse(login.context)
       
  1004         self.client.get('/test_admin/admin/logout/')
       
  1005 
       
  1006         # Regular User should not be able to login.
       
  1007         request = self.client.get('/test_admin/admin/secure-view/')
       
  1008         self.assertEqual(request.status_code, 200)
       
  1009         login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login)
       
  1010         self.assertEqual(login.status_code, 200)
       
  1011         # Login.context is a list of context dicts we just need to check the first one.
       
  1012         self.assert_(login.context[0].get('error_message'))
       
  1013 
       
  1014         # 8509 - if a normal user is already logged in, it is possible
       
  1015         # to change user into the superuser without error
       
  1016         login = self.client.login(username='joepublic', password='secret')
       
  1017         # Check and make sure that if user expires, data still persists
       
  1018         self.client.get('/test_admin/admin/secure-view/')
       
  1019         self.client.post('/test_admin/admin/secure-view/', self.super_login)
       
  1020         # make sure the view removes test cookie
       
  1021         self.assertEqual(self.client.session.test_cookie_worked(), False)
       
  1022 
       
  1023 class AdminViewUnicodeTest(TestCase):
       
  1024     fixtures = ['admin-views-unicode.xml']
       
  1025 
       
  1026     def setUp(self):
       
  1027         self.client.login(username='super', password='secret')
       
  1028 
       
  1029     def tearDown(self):
       
  1030         self.client.logout()
       
  1031 
       
  1032     def testUnicodeEdit(self):
       
  1033         """
       
  1034         A test to ensure that POST on edit_view handles non-ascii characters.
       
  1035         """
       
  1036         post_data = {
       
  1037             "name": u"Test lærdommer",
       
  1038             # inline data
       
  1039             "chapter_set-TOTAL_FORMS": u"6",
       
  1040             "chapter_set-INITIAL_FORMS": u"3",
       
  1041             "chapter_set-MAX_NUM_FORMS": u"0",
       
  1042             "chapter_set-0-id": u"1",
       
  1043             "chapter_set-0-title": u"Norske bostaver æøå skaper problemer",
       
  1044             "chapter_set-0-content": u"&lt;p&gt;Svært frustrerende med UnicodeDecodeError&lt;/p&gt;",
       
  1045             "chapter_set-1-id": u"2",
       
  1046             "chapter_set-1-title": u"Kjærlighet.",
       
  1047             "chapter_set-1-content": u"&lt;p&gt;La kjærligheten til de lidende seire.&lt;/p&gt;",
       
  1048             "chapter_set-2-id": u"3",
       
  1049             "chapter_set-2-title": u"Need a title.",
       
  1050             "chapter_set-2-content": u"&lt;p&gt;Newest content&lt;/p&gt;",
       
  1051             "chapter_set-3-id": u"",
       
  1052             "chapter_set-3-title": u"",
       
  1053             "chapter_set-3-content": u"",
       
  1054             "chapter_set-4-id": u"",
       
  1055             "chapter_set-4-title": u"",
       
  1056             "chapter_set-4-content": u"",
       
  1057             "chapter_set-5-id": u"",
       
  1058             "chapter_set-5-title": u"",
       
  1059             "chapter_set-5-content": u"",
       
  1060         }
       
  1061 
       
  1062         response = self.client.post('/test_admin/admin/admin_views/book/1/', post_data)
       
  1063         self.assertEqual(response.status_code, 302) # redirect somewhere
       
  1064 
       
  1065     def testUnicodeDelete(self):
       
  1066         """
       
  1067         Ensure that the delete_view handles non-ascii characters
       
  1068         """
       
  1069         delete_dict = {'post': 'yes'}
       
  1070         response = self.client.get('/test_admin/admin/admin_views/book/1/delete/')
       
  1071         self.assertEqual(response.status_code, 200)
       
  1072         response = self.client.post('/test_admin/admin/admin_views/book/1/delete/', delete_dict)
       
  1073         self.assertRedirects(response, '/test_admin/admin/admin_views/book/')
       
  1074 
       
  1075 
       
  1076 class AdminViewListEditable(TestCase):
       
  1077     fixtures = ['admin-views-users.xml', 'admin-views-person.xml']
       
  1078 
       
  1079     def setUp(self):
       
  1080         self.client.login(username='super', password='secret')
       
  1081 
       
  1082     def tearDown(self):
       
  1083         self.client.logout()
       
  1084 
       
  1085     def test_inheritance(self):
       
  1086         Podcast.objects.create(name="This Week in Django",
       
  1087             release_date=datetime.date.today())
       
  1088         response = self.client.get('/test_admin/admin/admin_views/podcast/')
       
  1089         self.assertEqual(response.status_code, 200)
       
  1090 
       
  1091     def test_inheritance_2(self):
       
  1092         Vodcast.objects.create(name="This Week in Django", released=True)
       
  1093         response = self.client.get('/test_admin/admin/admin_views/vodcast/')
       
  1094         self.assertEqual(response.status_code, 200)
       
  1095 
       
  1096     def test_custom_pk(self):
       
  1097         Language.objects.create(iso='en', name='English', english_name='English')
       
  1098         response = self.client.get('/test_admin/admin/admin_views/language/')
       
  1099         self.assertEqual(response.status_code, 200)
       
  1100 
       
  1101     def test_changelist_input_html(self):
       
  1102         response = self.client.get('/test_admin/admin/admin_views/person/')
       
  1103         # 2 inputs per object(the field and the hidden id field) = 6
       
  1104         # 3 management hidden fields = 3
       
  1105         # 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
       
  1106         # main form submit button = 1
       
  1107         # search field and search submit button = 2
       
  1108         # CSRF field = 1
       
  1109         # field to track 'select all' across paginated views = 1
       
  1110         # 6 + 3 + 4 + 1 + 2 + 1 + 1 = 18 inputs
       
  1111         self.assertEqual(response.content.count("<input"), 18)
       
  1112         # 1 select per object = 3 selects
       
  1113         self.assertEqual(response.content.count("<select"), 4)
       
  1114 
       
  1115     def test_post_messages(self):
       
  1116         # Ticket 12707: Saving inline editable should not show admin
       
  1117         # action warnings
       
  1118         data = {
       
  1119             "form-TOTAL_FORMS": "3",
       
  1120             "form-INITIAL_FORMS": "3",
       
  1121             "form-MAX_NUM_FORMS": "0",
       
  1122 
       
  1123             "form-0-gender": "1",
       
  1124             "form-0-id": "1",
       
  1125 
       
  1126             "form-1-gender": "2",
       
  1127             "form-1-id": "2",
       
  1128 
       
  1129             "form-2-alive": "checked",
       
  1130             "form-2-gender": "1",
       
  1131             "form-2-id": "3",
       
  1132 
       
  1133             "_save": "Save",
       
  1134         }
       
  1135         response = self.client.post('/test_admin/admin/admin_views/person/',
       
  1136                                     data, follow=True)
       
  1137         self.assertEqual(len(response.context['messages']), 1)
       
  1138 
       
  1139     def test_post_submission(self):
       
  1140         data = {
       
  1141             "form-TOTAL_FORMS": "3",
       
  1142             "form-INITIAL_FORMS": "3",
       
  1143             "form-MAX_NUM_FORMS": "0",
       
  1144 
       
  1145             "form-0-gender": "1",
       
  1146             "form-0-id": "1",
       
  1147 
       
  1148             "form-1-gender": "2",
       
  1149             "form-1-id": "2",
       
  1150 
       
  1151             "form-2-alive": "checked",
       
  1152             "form-2-gender": "1",
       
  1153             "form-2-id": "3",
       
  1154 
       
  1155             "_save": "Save",
       
  1156         }
       
  1157         self.client.post('/test_admin/admin/admin_views/person/', data)
       
  1158 
       
  1159         self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
       
  1160         self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 2)
       
  1161 
       
  1162         # test a filtered page
       
  1163         data = {
       
  1164             "form-TOTAL_FORMS": "2",
       
  1165             "form-INITIAL_FORMS": "2",
       
  1166             "form-MAX_NUM_FORMS": "0",
       
  1167 
       
  1168             "form-0-id": "1",
       
  1169             "form-0-gender": "1",
       
  1170             "form-0-alive": "checked",
       
  1171 
       
  1172             "form-1-id": "3",
       
  1173             "form-1-gender": "1",
       
  1174             "form-1-alive": "checked",
       
  1175 
       
  1176             "_save": "Save",
       
  1177         }
       
  1178         self.client.post('/test_admin/admin/admin_views/person/?gender__exact=1', data)
       
  1179 
       
  1180         self.assertEqual(Person.objects.get(name="John Mauchly").alive, True)
       
  1181 
       
  1182         # test a searched page
       
  1183         data = {
       
  1184             "form-TOTAL_FORMS": "1",
       
  1185             "form-INITIAL_FORMS": "1",
       
  1186             "form-MAX_NUM_FORMS": "0",
       
  1187 
       
  1188             "form-0-id": "1",
       
  1189             "form-0-gender": "1",
       
  1190 
       
  1191             "_save": "Save",
       
  1192         }
       
  1193         self.client.post('/test_admin/admin/admin_views/person/?q=mauchly', data)
       
  1194 
       
  1195         self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
       
  1196 
       
  1197     def test_non_form_errors(self):
       
  1198         # test if non-form errors are handled; ticket #12716
       
  1199         data = {
       
  1200             "form-TOTAL_FORMS": "1",
       
  1201             "form-INITIAL_FORMS": "1",
       
  1202             "form-MAX_NUM_FORMS": "0",
       
  1203 
       
  1204             "form-0-id": "2",
       
  1205             "form-0-alive": "1",
       
  1206             "form-0-gender": "2",
       
  1207 
       
  1208             # Ensure that the form processing understands this as a list_editable "Save"
       
  1209             # and not an action "Go".
       
  1210             "_save": "Save",
       
  1211         }
       
  1212         response = self.client.post('/test_admin/admin/admin_views/person/', data)
       
  1213         self.assertContains(response, "Grace is not a Zombie")
       
  1214 
       
  1215     def test_non_form_errors_is_errorlist(self):
       
  1216         # test if non-form errors are correctly handled; ticket #12878
       
  1217         data = {
       
  1218             "form-TOTAL_FORMS": "1",
       
  1219             "form-INITIAL_FORMS": "1",
       
  1220             "form-MAX_NUM_FORMS": "0",
       
  1221 
       
  1222             "form-0-id": "2",
       
  1223             "form-0-alive": "1",
       
  1224             "form-0-gender": "2",
       
  1225 
       
  1226             "_save": "Save",
       
  1227         }
       
  1228         response = self.client.post('/test_admin/admin/admin_views/person/', data)
       
  1229         non_form_errors = response.context['cl'].formset.non_form_errors()
       
  1230         self.assert_(isinstance(non_form_errors, ErrorList))
       
  1231         self.assertEqual(str(non_form_errors), str(ErrorList(["Grace is not a Zombie"])))
       
  1232 
       
  1233     def test_list_editable_ordering(self):
       
  1234         collector = Collector.objects.create(id=1, name="Frederick Clegg")
       
  1235 
       
  1236         Category.objects.create(id=1, order=1, collector=collector)
       
  1237         Category.objects.create(id=2, order=2, collector=collector)
       
  1238         Category.objects.create(id=3, order=0, collector=collector)
       
  1239         Category.objects.create(id=4, order=0, collector=collector)
       
  1240 
       
  1241         # NB: The order values must be changed so that the items are reordered.
       
  1242         data = {
       
  1243             "form-TOTAL_FORMS": "4",
       
  1244             "form-INITIAL_FORMS": "4",
       
  1245             "form-MAX_NUM_FORMS": "0",
       
  1246 
       
  1247             "form-0-order": "14",
       
  1248             "form-0-id": "1",
       
  1249             "form-0-collector": "1",
       
  1250 
       
  1251             "form-1-order": "13",
       
  1252             "form-1-id": "2",
       
  1253             "form-1-collector": "1",
       
  1254 
       
  1255             "form-2-order": "1",
       
  1256             "form-2-id": "3",
       
  1257             "form-2-collector": "1",
       
  1258 
       
  1259             "form-3-order": "0",
       
  1260             "form-3-id": "4",
       
  1261             "form-3-collector": "1",
       
  1262 
       
  1263             # Ensure that the form processing understands this as a list_editable "Save"
       
  1264             # and not an action "Go".
       
  1265             "_save": "Save",
       
  1266         }
       
  1267         response = self.client.post('/test_admin/admin/admin_views/category/', data)
       
  1268         # Successful post will redirect
       
  1269         self.assertEqual(response.status_code, 302)
       
  1270 
       
  1271         # Check that the order values have been applied to the right objects
       
  1272         self.assertEqual(Category.objects.get(id=1).order, 14)
       
  1273         self.assertEqual(Category.objects.get(id=2).order, 13)
       
  1274         self.assertEqual(Category.objects.get(id=3).order, 1)
       
  1275         self.assertEqual(Category.objects.get(id=4).order, 0)
       
  1276 
       
  1277     def test_list_editable_action_submit(self):
       
  1278         # List editable changes should not be executed if the action "Go" button is
       
  1279         # used to submit the form.
       
  1280         data = {
       
  1281             "form-TOTAL_FORMS": "3",
       
  1282             "form-INITIAL_FORMS": "3",
       
  1283             "form-MAX_NUM_FORMS": "0",
       
  1284 
       
  1285             "form-0-gender": "1",
       
  1286             "form-0-id": "1",
       
  1287 
       
  1288             "form-1-gender": "2",
       
  1289             "form-1-id": "2",
       
  1290 
       
  1291             "form-2-alive": "checked",
       
  1292             "form-2-gender": "1",
       
  1293             "form-2-id": "3",
       
  1294 
       
  1295             "index": "0",
       
  1296             "_selected_action": [u'3'],
       
  1297             "action": [u'', u'delete_selected'],
       
  1298         }
       
  1299         self.client.post('/test_admin/admin/admin_views/person/', data)
       
  1300 
       
  1301         self.assertEqual(Person.objects.get(name="John Mauchly").alive, True)
       
  1302         self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 1)
       
  1303 
       
  1304     def test_list_editable_action_choices(self):
       
  1305         # List editable changes should be executed if the "Save" button is
       
  1306         # used to submit the form - any action choices should be ignored.
       
  1307         data = {
       
  1308             "form-TOTAL_FORMS": "3",
       
  1309             "form-INITIAL_FORMS": "3",
       
  1310             "form-MAX_NUM_FORMS": "0",
       
  1311 
       
  1312             "form-0-gender": "1",
       
  1313             "form-0-id": "1",
       
  1314 
       
  1315             "form-1-gender": "2",
       
  1316             "form-1-id": "2",
       
  1317 
       
  1318             "form-2-alive": "checked",
       
  1319             "form-2-gender": "1",
       
  1320             "form-2-id": "3",
       
  1321 
       
  1322             "_save": "Save",
       
  1323             "_selected_action": [u'1'],
       
  1324             "action": [u'', u'delete_selected'],
       
  1325         }
       
  1326         self.client.post('/test_admin/admin/admin_views/person/', data)
       
  1327 
       
  1328         self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
       
  1329         self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 2)
       
  1330 
       
  1331 
       
  1332 
       
  1333 
       
  1334 class AdminSearchTest(TestCase):
       
  1335     fixtures = ['admin-views-users','multiple-child-classes']
       
  1336 
       
  1337     def setUp(self):
       
  1338         self.client.login(username='super', password='secret')
       
  1339 
       
  1340     def tearDown(self):
       
  1341         self.client.logout()
       
  1342 
       
  1343     def test_search_on_sibling_models(self):
       
  1344         "Check that a search that mentions sibling models"
       
  1345         response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
       
  1346         # confirm the search returned 1 object
       
  1347         self.assertContains(response, "\n1 recommendation\n")
       
  1348 
       
  1349 class AdminInheritedInlinesTest(TestCase):
       
  1350     fixtures = ['admin-views-users.xml',]
       
  1351 
       
  1352     def setUp(self):
       
  1353         self.client.login(username='super', password='secret')
       
  1354 
       
  1355     def tearDown(self):
       
  1356         self.client.logout()
       
  1357 
       
  1358     def testInline(self):
       
  1359         "Ensure that inline models which inherit from a common parent are correctly handled by admin."
       
  1360 
       
  1361         foo_user = u"foo username"
       
  1362         bar_user = u"bar username"
       
  1363 
       
  1364         name_re = re.compile('name="(.*?)"')
       
  1365 
       
  1366         # test the add case
       
  1367         response = self.client.get('/test_admin/admin/admin_views/persona/add/')
       
  1368         names = name_re.findall(response.content)
       
  1369         # make sure we have no duplicate HTML names
       
  1370         self.assertEqual(len(names), len(set(names)))
       
  1371 
       
  1372         # test the add case
       
  1373         post_data = {
       
  1374             "name": u"Test Name",
       
  1375             # inline data
       
  1376             "accounts-TOTAL_FORMS": u"1",
       
  1377             "accounts-INITIAL_FORMS": u"0",
       
  1378             "accounts-MAX_NUM_FORMS": u"0",
       
  1379             "accounts-0-username": foo_user,
       
  1380             "accounts-2-TOTAL_FORMS": u"1",
       
  1381             "accounts-2-INITIAL_FORMS": u"0",
       
  1382             "accounts-2-MAX_NUM_FORMS": u"0",
       
  1383             "accounts-2-0-username": bar_user,
       
  1384         }
       
  1385 
       
  1386         response = self.client.post('/test_admin/admin/admin_views/persona/add/', post_data)
       
  1387         self.assertEqual(response.status_code, 302) # redirect somewhere
       
  1388         self.assertEqual(Persona.objects.count(), 1)
       
  1389         self.assertEqual(FooAccount.objects.count(), 1)
       
  1390         self.assertEqual(BarAccount.objects.count(), 1)
       
  1391         self.assertEqual(FooAccount.objects.all()[0].username, foo_user)
       
  1392         self.assertEqual(BarAccount.objects.all()[0].username, bar_user)
       
  1393         self.assertEqual(Persona.objects.all()[0].accounts.count(), 2)
       
  1394 
       
  1395         # test the edit case
       
  1396 
       
  1397         response = self.client.get('/test_admin/admin/admin_views/persona/1/')
       
  1398         names = name_re.findall(response.content)
       
  1399         # make sure we have no duplicate HTML names
       
  1400         self.assertEqual(len(names), len(set(names)))
       
  1401 
       
  1402         post_data = {
       
  1403             "name": u"Test Name",
       
  1404 
       
  1405             "accounts-TOTAL_FORMS": "2",
       
  1406             "accounts-INITIAL_FORMS": u"1",
       
  1407             "accounts-MAX_NUM_FORMS": u"0",
       
  1408 
       
  1409             "accounts-0-username": "%s-1" % foo_user,
       
  1410             "accounts-0-account_ptr": "1",
       
  1411             "accounts-0-persona": "1",
       
  1412 
       
  1413             "accounts-2-TOTAL_FORMS": u"2",
       
  1414             "accounts-2-INITIAL_FORMS": u"1",
       
  1415             "accounts-2-MAX_NUM_FORMS": u"0",
       
  1416 
       
  1417             "accounts-2-0-username": "%s-1" % bar_user,
       
  1418             "accounts-2-0-account_ptr": "2",
       
  1419             "accounts-2-0-persona": "1",
       
  1420         }
       
  1421         response = self.client.post('/test_admin/admin/admin_views/persona/1/', post_data)
       
  1422         self.assertEqual(response.status_code, 302)
       
  1423         self.assertEqual(Persona.objects.count(), 1)
       
  1424         self.assertEqual(FooAccount.objects.count(), 1)
       
  1425         self.assertEqual(BarAccount.objects.count(), 1)
       
  1426         self.assertEqual(FooAccount.objects.all()[0].username, "%s-1" % foo_user)
       
  1427         self.assertEqual(BarAccount.objects.all()[0].username, "%s-1" % bar_user)
       
  1428         self.assertEqual(Persona.objects.all()[0].accounts.count(), 2)
       
  1429 
       
  1430 from django.core import mail
       
  1431 
       
  1432 class AdminActionsTest(TestCase):
       
  1433     fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
       
  1434 
       
  1435     def setUp(self):
       
  1436         self.client.login(username='super', password='secret')
       
  1437 
       
  1438     def tearDown(self):
       
  1439         self.client.logout()
       
  1440 
       
  1441     def test_model_admin_custom_action(self):
       
  1442         "Tests a custom action defined in a ModelAdmin method"
       
  1443         action_data = {
       
  1444             ACTION_CHECKBOX_NAME: [1],
       
  1445             'action' : 'mail_admin',
       
  1446             'index': 0,
       
  1447         }
       
  1448         response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
       
  1449         self.assertEquals(len(mail.outbox), 1)
       
  1450         self.assertEquals(mail.outbox[0].subject, 'Greetings from a ModelAdmin action')
       
  1451 
       
  1452     def test_model_admin_default_delete_action(self):
       
  1453         "Tests the default delete action defined as a ModelAdmin method"
       
  1454         action_data = {
       
  1455             ACTION_CHECKBOX_NAME: [1, 2],
       
  1456             'action' : 'delete_selected',
       
  1457             'index': 0,
       
  1458         }
       
  1459         delete_confirmation_data = {
       
  1460             ACTION_CHECKBOX_NAME: [1, 2],
       
  1461             'action' : 'delete_selected',
       
  1462             'post': 'yes',
       
  1463         }
       
  1464         confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
       
  1465         self.assertContains(confirmation, "Are you sure you want to delete the selected subscriber objects")
       
  1466         self.assertTrue(confirmation.content.count(ACTION_CHECKBOX_NAME) == 2)
       
  1467         response = self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data)
       
  1468         self.assertEqual(Subscriber.objects.count(), 0)
       
  1469 
       
  1470     def test_custom_function_mail_action(self):
       
  1471         "Tests a custom action defined in a function"
       
  1472         action_data = {
       
  1473             ACTION_CHECKBOX_NAME: [1],
       
  1474             'action' : 'external_mail',
       
  1475             'index': 0,
       
  1476         }
       
  1477         response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
       
  1478         self.assertEquals(len(mail.outbox), 1)
       
  1479         self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action')
       
  1480 
       
  1481     def test_custom_function_action_with_redirect(self):
       
  1482         "Tests a custom action defined in a function"
       
  1483         action_data = {
       
  1484             ACTION_CHECKBOX_NAME: [1],
       
  1485             'action' : 'redirect_to',
       
  1486             'index': 0,
       
  1487         }
       
  1488         response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
       
  1489         self.assertEqual(response.status_code, 302)
       
  1490 
       
  1491     def test_default_redirect(self):
       
  1492         """
       
  1493         Test that actions which don't return an HttpResponse are redirected to
       
  1494         the same page, retaining the querystring (which may contain changelist
       
  1495         information).
       
  1496         """
       
  1497         action_data = {
       
  1498             ACTION_CHECKBOX_NAME: [1],
       
  1499             'action' : 'external_mail',
       
  1500             'index': 0,
       
  1501         }
       
  1502         url = '/test_admin/admin/admin_views/externalsubscriber/?ot=asc&o=1'
       
  1503         response = self.client.post(url, action_data)
       
  1504         self.assertRedirects(response, url)
       
  1505 
       
  1506     def test_model_without_action(self):
       
  1507         "Tests a ModelAdmin without any action"
       
  1508         response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/')
       
  1509         self.assertEquals(response.context["action_form"], None)
       
  1510         self.assert_(
       
  1511             '<input type="checkbox" class="action-select"' not in response.content,
       
  1512             "Found an unexpected action toggle checkboxbox in response"
       
  1513         )
       
  1514         self.assert_('action-checkbox-column' not in response.content,
       
  1515             "Found unexpected action-checkbox-column class in response")
       
  1516 
       
  1517     def test_model_without_action_still_has_jquery(self):
       
  1518         "Tests that a ModelAdmin without any actions still gets jQuery included in page"
       
  1519         response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/')
       
  1520         self.assertEquals(response.context["action_form"], None)
       
  1521         self.assert_('jquery.min.js' in response.content,
       
  1522             "jQuery missing from admin pages for model with no admin actions"
       
  1523         )
       
  1524 
       
  1525     def test_action_column_class(self):
       
  1526         "Tests that the checkbox column class is present in the response"
       
  1527         response = self.client.get('/test_admin/admin/admin_views/subscriber/')
       
  1528         self.assertNotEqual(response.context["action_form"], None)
       
  1529         self.assert_('action-checkbox-column' in response.content,
       
  1530             "Expected an action-checkbox-column in response")
       
  1531 
       
  1532     def test_multiple_actions_form(self):
       
  1533         """
       
  1534         Test that actions come from the form whose submit button was pressed (#10618).
       
  1535         """
       
  1536         action_data = {
       
  1537             ACTION_CHECKBOX_NAME: [1],
       
  1538             # Two different actions selected on the two forms...
       
  1539             'action': ['external_mail', 'delete_selected'],
       
  1540             # ...but we clicked "go" on the top form.
       
  1541             'index': 0
       
  1542         }
       
  1543         response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
       
  1544 
       
  1545         # Send mail, don't delete.
       
  1546         self.assertEquals(len(mail.outbox), 1)
       
  1547         self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action')
       
  1548 
       
  1549     def test_user_message_on_none_selected(self):
       
  1550         """
       
  1551         User should see a warning when 'Go' is pressed and no items are selected.
       
  1552         """
       
  1553         action_data = {
       
  1554             ACTION_CHECKBOX_NAME: [],
       
  1555             'action' : 'delete_selected',
       
  1556             'index': 0,
       
  1557         }
       
  1558         response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
       
  1559         msg = """Items must be selected in order to perform actions on them. No items have been changed."""
       
  1560         self.assertContains(response, msg)
       
  1561         self.assertEqual(Subscriber.objects.count(), 2)
       
  1562 
       
  1563     def test_user_message_on_no_action(self):
       
  1564         """
       
  1565         User should see a warning when 'Go' is pressed and no action is selected.
       
  1566         """
       
  1567         action_data = {
       
  1568             ACTION_CHECKBOX_NAME: [1, 2],
       
  1569             'action' : '',
       
  1570             'index': 0,
       
  1571         }
       
  1572         response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
       
  1573         msg = """No action selected."""
       
  1574         self.assertContains(response, msg)
       
  1575         self.assertEqual(Subscriber.objects.count(), 2)
       
  1576 
       
  1577     def test_selection_counter(self):
       
  1578         """
       
  1579         Check if the selection counter is there.
       
  1580         """
       
  1581         response = self.client.get('/test_admin/admin/admin_views/subscriber/')
       
  1582         self.assertContains(response, '0 of 2 selected')
       
  1583 
       
  1584 
       
  1585 class TestCustomChangeList(TestCase):
       
  1586     fixtures = ['admin-views-users.xml']
       
  1587     urlbit = 'admin'
       
  1588 
       
  1589     def setUp(self):
       
  1590         result = self.client.login(username='super', password='secret')
       
  1591         self.assertEqual(result, True)
       
  1592 
       
  1593     def tearDown(self):
       
  1594         self.client.logout()
       
  1595 
       
  1596     def test_custom_changelist(self):
       
  1597         """
       
  1598         Validate that a custom ChangeList class can be used (#9749)
       
  1599         """
       
  1600         # Insert some data
       
  1601         post_data = {"name": u"First Gadget"}
       
  1602         response = self.client.post('/test_admin/%s/admin_views/gadget/add/' % self.urlbit, post_data)
       
  1603         self.assertEqual(response.status_code, 302) # redirect somewhere
       
  1604         # Hit the page once to get messages out of the queue message list
       
  1605         response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit)
       
  1606         # Ensure that that data is still not visible on the page
       
  1607         response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit)
       
  1608         self.assertEqual(response.status_code, 200)
       
  1609         self.assertNotContains(response, 'First Gadget')
       
  1610 
       
  1611 
       
  1612 class TestInlineNotEditable(TestCase):
       
  1613     fixtures = ['admin-views-users.xml']
       
  1614 
       
  1615     def setUp(self):
       
  1616         result = self.client.login(username='super', password='secret')
       
  1617         self.assertEqual(result, True)
       
  1618 
       
  1619     def tearDown(self):
       
  1620         self.client.logout()
       
  1621 
       
  1622     def test(self):
       
  1623         """
       
  1624         InlineModelAdmin broken?
       
  1625         """
       
  1626         response = self.client.get('/test_admin/admin/admin_views/parent/add/')
       
  1627         self.assertEqual(response.status_code, 200)
       
  1628 
       
  1629 class AdminCustomQuerysetTest(TestCase):
       
  1630     fixtures = ['admin-views-users.xml']
       
  1631 
       
  1632     def setUp(self):
       
  1633         self.client.login(username='super', password='secret')
       
  1634         self.pks = [EmptyModel.objects.create().id for i in range(3)]
       
  1635 
       
  1636     def test_changelist_view(self):
       
  1637         response = self.client.get('/test_admin/admin/admin_views/emptymodel/')
       
  1638         for i in self.pks:
       
  1639             if i > 1:
       
  1640                 self.assertContains(response, 'Primary key = %s' % i)
       
  1641             else:
       
  1642                 self.assertNotContains(response, 'Primary key = %s' % i)
       
  1643 
       
  1644     def test_change_view(self):
       
  1645         for i in self.pks:
       
  1646             response = self.client.get('/test_admin/admin/admin_views/emptymodel/%s/' % i)
       
  1647             if i > 1:
       
  1648                 self.assertEqual(response.status_code, 200)
       
  1649             else:
       
  1650                 self.assertEqual(response.status_code, 404)
       
  1651 
       
  1652 class AdminInlineFileUploadTest(TestCase):
       
  1653     fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
       
  1654     urlbit = 'admin'
       
  1655 
       
  1656     def setUp(self):
       
  1657         self.client.login(username='super', password='secret')
       
  1658 
       
  1659         # Set up test Picture and Gallery.
       
  1660         # These must be set up here instead of in fixtures in order to allow Picture
       
  1661         # to use a NamedTemporaryFile.
       
  1662         tdir = tempfile.gettempdir()
       
  1663         file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir)
       
  1664         file1.write('a' * (2 ** 21))
       
  1665         filename = file1.name
       
  1666         file1.close()
       
  1667         g = Gallery(name="Test Gallery")
       
  1668         g.save()
       
  1669         p = Picture(name="Test Picture", image=filename, gallery=g)
       
  1670         p.save()
       
  1671 
       
  1672     def tearDown(self):
       
  1673         self.client.logout()
       
  1674 
       
  1675     def test_inline_file_upload_edit_validation_error_post(self):
       
  1676         """
       
  1677         Test that inline file uploads correctly display prior data (#10002).
       
  1678         """
       
  1679         post_data = {
       
  1680             "name": u"Test Gallery",
       
  1681             "pictures-TOTAL_FORMS": u"2",
       
  1682             "pictures-INITIAL_FORMS": u"1",
       
  1683             "pictures-MAX_NUM_FORMS": u"0",
       
  1684             "pictures-0-id": u"1",
       
  1685             "pictures-0-gallery": u"1",
       
  1686             "pictures-0-name": "Test Picture",
       
  1687             "pictures-0-image": "",
       
  1688             "pictures-1-id": "",
       
  1689             "pictures-1-gallery": "1",
       
  1690             "pictures-1-name": "Test Picture 2",
       
  1691             "pictures-1-image": "",
       
  1692         }
       
  1693         response = self.client.post('/test_admin/%s/admin_views/gallery/1/' % self.urlbit, post_data)
       
  1694         self.assertTrue(response._container[0].find("Currently:") > -1)
       
  1695 
       
  1696 
       
  1697 class AdminInlineTests(TestCase):
       
  1698     fixtures = ['admin-views-users.xml']
       
  1699 
       
  1700     def setUp(self):
       
  1701         self.post_data = {
       
  1702             "name": u"Test Name",
       
  1703 
       
  1704             "widget_set-TOTAL_FORMS": "3",
       
  1705             "widget_set-INITIAL_FORMS": u"0",
       
  1706             "widget_set-MAX_NUM_FORMS": u"0",
       
  1707             "widget_set-0-id": "",
       
  1708             "widget_set-0-owner": "1",
       
  1709             "widget_set-0-name": "",
       
  1710             "widget_set-1-id": "",
       
  1711             "widget_set-1-owner": "1",
       
  1712             "widget_set-1-name": "",
       
  1713             "widget_set-2-id": "",
       
  1714             "widget_set-2-owner": "1",
       
  1715             "widget_set-2-name": "",
       
  1716 
       
  1717             "doohickey_set-TOTAL_FORMS": "3",
       
  1718             "doohickey_set-INITIAL_FORMS": u"0",
       
  1719             "doohickey_set-MAX_NUM_FORMS": u"0",
       
  1720             "doohickey_set-0-owner": "1",
       
  1721             "doohickey_set-0-code": "",
       
  1722             "doohickey_set-0-name": "",
       
  1723             "doohickey_set-1-owner": "1",
       
  1724             "doohickey_set-1-code": "",
       
  1725             "doohickey_set-1-name": "",
       
  1726             "doohickey_set-2-owner": "1",
       
  1727             "doohickey_set-2-code": "",
       
  1728             "doohickey_set-2-name": "",
       
  1729 
       
  1730             "grommet_set-TOTAL_FORMS": "3",
       
  1731             "grommet_set-INITIAL_FORMS": u"0",
       
  1732             "grommet_set-MAX_NUM_FORMS": u"0",
       
  1733             "grommet_set-0-code": "",
       
  1734             "grommet_set-0-owner": "1",
       
  1735             "grommet_set-0-name": "",
       
  1736             "grommet_set-1-code": "",
       
  1737             "grommet_set-1-owner": "1",
       
  1738             "grommet_set-1-name": "",
       
  1739             "grommet_set-2-code": "",
       
  1740             "grommet_set-2-owner": "1",
       
  1741             "grommet_set-2-name": "",
       
  1742 
       
  1743             "whatsit_set-TOTAL_FORMS": "3",
       
  1744             "whatsit_set-INITIAL_FORMS": u"0",
       
  1745             "whatsit_set-MAX_NUM_FORMS": u"0",
       
  1746             "whatsit_set-0-owner": "1",
       
  1747             "whatsit_set-0-index": "",
       
  1748             "whatsit_set-0-name": "",
       
  1749             "whatsit_set-1-owner": "1",
       
  1750             "whatsit_set-1-index": "",
       
  1751             "whatsit_set-1-name": "",
       
  1752             "whatsit_set-2-owner": "1",
       
  1753             "whatsit_set-2-index": "",
       
  1754             "whatsit_set-2-name": "",
       
  1755 
       
  1756             "fancydoodad_set-TOTAL_FORMS": "3",
       
  1757             "fancydoodad_set-INITIAL_FORMS": u"0",
       
  1758             "fancydoodad_set-MAX_NUM_FORMS": u"0",
       
  1759             "fancydoodad_set-0-doodad_ptr": "",
       
  1760             "fancydoodad_set-0-owner": "1",
       
  1761             "fancydoodad_set-0-name": "",
       
  1762             "fancydoodad_set-0-expensive": "on",
       
  1763             "fancydoodad_set-1-doodad_ptr": "",
       
  1764             "fancydoodad_set-1-owner": "1",
       
  1765             "fancydoodad_set-1-name": "",
       
  1766             "fancydoodad_set-1-expensive": "on",
       
  1767             "fancydoodad_set-2-doodad_ptr": "",
       
  1768             "fancydoodad_set-2-owner": "1",
       
  1769             "fancydoodad_set-2-name": "",
       
  1770             "fancydoodad_set-2-expensive": "on",
       
  1771 
       
  1772             "category_set-TOTAL_FORMS": "3",
       
  1773             "category_set-INITIAL_FORMS": "0",
       
  1774             "category_set-MAX_NUM_FORMS": "0",
       
  1775             "category_set-0-order": "",
       
  1776             "category_set-0-id": "",
       
  1777             "category_set-0-collector": "1",
       
  1778             "category_set-1-order": "",
       
  1779             "category_set-1-id": "",
       
  1780             "category_set-1-collector": "1",
       
  1781             "category_set-2-order": "",
       
  1782             "category_set-2-id": "",
       
  1783             "category_set-2-collector": "1",
       
  1784         }
       
  1785 
       
  1786         result = self.client.login(username='super', password='secret')
       
  1787         self.assertEqual(result, True)
       
  1788         self.collector = Collector(pk=1,name='John Fowles')
       
  1789         self.collector.save()
       
  1790 
       
  1791     def tearDown(self):
       
  1792         self.client.logout()
       
  1793 
       
  1794     def test_simple_inline(self):
       
  1795         "A simple model can be saved as inlines"
       
  1796         # First add a new inline
       
  1797         self.post_data['widget_set-0-name'] = "Widget 1"
       
  1798         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1799         self.assertEqual(response.status_code, 302)
       
  1800         self.assertEqual(Widget.objects.count(), 1)
       
  1801         self.assertEqual(Widget.objects.all()[0].name, "Widget 1")
       
  1802 
       
  1803         # Check that the PK link exists on the rendered form
       
  1804         response = self.client.get('/test_admin/admin/admin_views/collector/1/')
       
  1805         self.assertContains(response, 'name="widget_set-0-id"')
       
  1806 
       
  1807         # Now resave that inline
       
  1808         self.post_data['widget_set-INITIAL_FORMS'] = "1"
       
  1809         self.post_data['widget_set-0-id'] = "1"
       
  1810         self.post_data['widget_set-0-name'] = "Widget 1"
       
  1811         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1812         self.assertEqual(response.status_code, 302)
       
  1813         self.assertEqual(Widget.objects.count(), 1)
       
  1814         self.assertEqual(Widget.objects.all()[0].name, "Widget 1")
       
  1815 
       
  1816         # Now modify that inline
       
  1817         self.post_data['widget_set-INITIAL_FORMS'] = "1"
       
  1818         self.post_data['widget_set-0-id'] = "1"
       
  1819         self.post_data['widget_set-0-name'] = "Widget 1 Updated"
       
  1820         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1821         self.assertEqual(response.status_code, 302)
       
  1822         self.assertEqual(Widget.objects.count(), 1)
       
  1823         self.assertEqual(Widget.objects.all()[0].name, "Widget 1 Updated")
       
  1824 
       
  1825     def test_explicit_autofield_inline(self):
       
  1826         "A model with an explicit autofield primary key can be saved as inlines. Regression for #8093"
       
  1827         # First add a new inline
       
  1828         self.post_data['grommet_set-0-name'] = "Grommet 1"
       
  1829         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1830         self.assertEqual(response.status_code, 302)
       
  1831         self.assertEqual(Grommet.objects.count(), 1)
       
  1832         self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1")
       
  1833 
       
  1834         # Check that the PK link exists on the rendered form
       
  1835         response = self.client.get('/test_admin/admin/admin_views/collector/1/')
       
  1836         self.assertContains(response, 'name="grommet_set-0-code"')
       
  1837 
       
  1838         # Now resave that inline
       
  1839         self.post_data['grommet_set-INITIAL_FORMS'] = "1"
       
  1840         self.post_data['grommet_set-0-code'] = "1"
       
  1841         self.post_data['grommet_set-0-name'] = "Grommet 1"
       
  1842         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1843         self.assertEqual(response.status_code, 302)
       
  1844         self.assertEqual(Grommet.objects.count(), 1)
       
  1845         self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1")
       
  1846 
       
  1847         # Now modify that inline
       
  1848         self.post_data['grommet_set-INITIAL_FORMS'] = "1"
       
  1849         self.post_data['grommet_set-0-code'] = "1"
       
  1850         self.post_data['grommet_set-0-name'] = "Grommet 1 Updated"
       
  1851         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1852         self.assertEqual(response.status_code, 302)
       
  1853         self.assertEqual(Grommet.objects.count(), 1)
       
  1854         self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1 Updated")
       
  1855 
       
  1856     def test_char_pk_inline(self):
       
  1857         "A model with a character PK can be saved as inlines. Regression for #10992"
       
  1858         # First add a new inline
       
  1859         self.post_data['doohickey_set-0-code'] = "DH1"
       
  1860         self.post_data['doohickey_set-0-name'] = "Doohickey 1"
       
  1861         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1862         self.assertEqual(response.status_code, 302)
       
  1863         self.assertEqual(DooHickey.objects.count(), 1)
       
  1864         self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1")
       
  1865 
       
  1866         # Check that the PK link exists on the rendered form
       
  1867         response = self.client.get('/test_admin/admin/admin_views/collector/1/')
       
  1868         self.assertContains(response, 'name="doohickey_set-0-code"')
       
  1869 
       
  1870         # Now resave that inline
       
  1871         self.post_data['doohickey_set-INITIAL_FORMS'] = "1"
       
  1872         self.post_data['doohickey_set-0-code'] = "DH1"
       
  1873         self.post_data['doohickey_set-0-name'] = "Doohickey 1"
       
  1874         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1875         self.assertEqual(response.status_code, 302)
       
  1876         self.assertEqual(DooHickey.objects.count(), 1)
       
  1877         self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1")
       
  1878 
       
  1879         # Now modify that inline
       
  1880         self.post_data['doohickey_set-INITIAL_FORMS'] = "1"
       
  1881         self.post_data['doohickey_set-0-code'] = "DH1"
       
  1882         self.post_data['doohickey_set-0-name'] = "Doohickey 1 Updated"
       
  1883         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1884         self.assertEqual(response.status_code, 302)
       
  1885         self.assertEqual(DooHickey.objects.count(), 1)
       
  1886         self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1 Updated")
       
  1887 
       
  1888     def test_integer_pk_inline(self):
       
  1889         "A model with an integer PK can be saved as inlines. Regression for #10992"
       
  1890         # First add a new inline
       
  1891         self.post_data['whatsit_set-0-index'] = "42"
       
  1892         self.post_data['whatsit_set-0-name'] = "Whatsit 1"
       
  1893         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1894         self.assertEqual(response.status_code, 302)
       
  1895         self.assertEqual(Whatsit.objects.count(), 1)
       
  1896         self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1")
       
  1897 
       
  1898         # Check that the PK link exists on the rendered form
       
  1899         response = self.client.get('/test_admin/admin/admin_views/collector/1/')
       
  1900         self.assertContains(response, 'name="whatsit_set-0-index"')
       
  1901 
       
  1902         # Now resave that inline
       
  1903         self.post_data['whatsit_set-INITIAL_FORMS'] = "1"
       
  1904         self.post_data['whatsit_set-0-index'] = "42"
       
  1905         self.post_data['whatsit_set-0-name'] = "Whatsit 1"
       
  1906         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1907         self.assertEqual(response.status_code, 302)
       
  1908         self.assertEqual(Whatsit.objects.count(), 1)
       
  1909         self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1")
       
  1910 
       
  1911         # Now modify that inline
       
  1912         self.post_data['whatsit_set-INITIAL_FORMS'] = "1"
       
  1913         self.post_data['whatsit_set-0-index'] = "42"
       
  1914         self.post_data['whatsit_set-0-name'] = "Whatsit 1 Updated"
       
  1915         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1916         self.assertEqual(response.status_code, 302)
       
  1917         self.assertEqual(Whatsit.objects.count(), 1)
       
  1918         self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1 Updated")
       
  1919 
       
  1920     def test_inherited_inline(self):
       
  1921         "An inherited model can be saved as inlines. Regression for #11042"
       
  1922         # First add a new inline
       
  1923         self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1"
       
  1924         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1925         self.assertEqual(response.status_code, 302)
       
  1926         self.assertEqual(FancyDoodad.objects.count(), 1)
       
  1927         self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1")
       
  1928 
       
  1929         # Check that the PK link exists on the rendered form
       
  1930         response = self.client.get('/test_admin/admin/admin_views/collector/1/')
       
  1931         self.assertContains(response, 'name="fancydoodad_set-0-doodad_ptr"')
       
  1932 
       
  1933         # Now resave that inline
       
  1934         self.post_data['fancydoodad_set-INITIAL_FORMS'] = "1"
       
  1935         self.post_data['fancydoodad_set-0-doodad_ptr'] = "1"
       
  1936         self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1"
       
  1937         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1938         self.assertEqual(response.status_code, 302)
       
  1939         self.assertEqual(FancyDoodad.objects.count(), 1)
       
  1940         self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1")
       
  1941 
       
  1942         # Now modify that inline
       
  1943         self.post_data['fancydoodad_set-INITIAL_FORMS'] = "1"
       
  1944         self.post_data['fancydoodad_set-0-doodad_ptr'] = "1"
       
  1945         self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1 Updated"
       
  1946         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1947         self.assertEqual(response.status_code, 302)
       
  1948         self.assertEqual(FancyDoodad.objects.count(), 1)
       
  1949         self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1 Updated")
       
  1950 
       
  1951     def test_ordered_inline(self):
       
  1952         """Check that an inline with an editable ordering fields is
       
  1953         updated correctly. Regression for #10922"""
       
  1954         # Create some objects with an initial ordering
       
  1955         Category.objects.create(id=1, order=1, collector=self.collector)
       
  1956         Category.objects.create(id=2, order=2, collector=self.collector)
       
  1957         Category.objects.create(id=3, order=0, collector=self.collector)
       
  1958         Category.objects.create(id=4, order=0, collector=self.collector)
       
  1959 
       
  1960         # NB: The order values must be changed so that the items are reordered.
       
  1961         self.post_data.update({
       
  1962             "name": "Frederick Clegg",
       
  1963 
       
  1964             "category_set-TOTAL_FORMS": "7",
       
  1965             "category_set-INITIAL_FORMS": "4",
       
  1966             "category_set-MAX_NUM_FORMS": "0",
       
  1967 
       
  1968             "category_set-0-order": "14",
       
  1969             "category_set-0-id": "1",
       
  1970             "category_set-0-collector": "1",
       
  1971 
       
  1972             "category_set-1-order": "13",
       
  1973             "category_set-1-id": "2",
       
  1974             "category_set-1-collector": "1",
       
  1975 
       
  1976             "category_set-2-order": "1",
       
  1977             "category_set-2-id": "3",
       
  1978             "category_set-2-collector": "1",
       
  1979 
       
  1980             "category_set-3-order": "0",
       
  1981             "category_set-3-id": "4",
       
  1982             "category_set-3-collector": "1",
       
  1983 
       
  1984             "category_set-4-order": "",
       
  1985             "category_set-4-id": "",
       
  1986             "category_set-4-collector": "1",
       
  1987 
       
  1988             "category_set-5-order": "",
       
  1989             "category_set-5-id": "",
       
  1990             "category_set-5-collector": "1",
       
  1991 
       
  1992             "category_set-6-order": "",
       
  1993             "category_set-6-id": "",
       
  1994             "category_set-6-collector": "1",
       
  1995         })
       
  1996         response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
       
  1997         # Successful post will redirect
       
  1998         self.assertEqual(response.status_code, 302)
       
  1999 
       
  2000         # Check that the order values have been applied to the right objects
       
  2001         self.assertEqual(self.collector.category_set.count(), 4)
       
  2002         self.assertEqual(Category.objects.get(id=1).order, 14)
       
  2003         self.assertEqual(Category.objects.get(id=2).order, 13)
       
  2004         self.assertEqual(Category.objects.get(id=3).order, 1)
       
  2005         self.assertEqual(Category.objects.get(id=4).order, 0)
       
  2006 
       
  2007 
       
  2008 class NeverCacheTests(TestCase):
       
  2009     fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
       
  2010 
       
  2011     def setUp(self):
       
  2012         self.client.login(username='super', password='secret')
       
  2013 
       
  2014     def tearDown(self):
       
  2015         self.client.logout()
       
  2016 
       
  2017     def testAdminIndex(self):
       
  2018         "Check the never-cache status of the main index"
       
  2019         response = self.client.get('/test_admin/admin/')
       
  2020         self.assertEqual(get_max_age(response), 0)
       
  2021 
       
  2022     def testAppIndex(self):
       
  2023         "Check the never-cache status of an application index"
       
  2024         response = self.client.get('/test_admin/admin/admin_views/')
       
  2025         self.assertEqual(get_max_age(response), 0)
       
  2026 
       
  2027     def testModelIndex(self):
       
  2028         "Check the never-cache status of a model index"
       
  2029         response = self.client.get('/test_admin/admin/admin_views/fabric/')
       
  2030         self.assertEqual(get_max_age(response), 0)
       
  2031 
       
  2032     def testModelAdd(self):
       
  2033         "Check the never-cache status of a model add page"
       
  2034         response = self.client.get('/test_admin/admin/admin_views/fabric/add/')
       
  2035         self.assertEqual(get_max_age(response), 0)
       
  2036 
       
  2037     def testModelView(self):
       
  2038         "Check the never-cache status of a model edit page"
       
  2039         response = self.client.get('/test_admin/admin/admin_views/section/1/')
       
  2040         self.assertEqual(get_max_age(response), 0)
       
  2041 
       
  2042     def testModelHistory(self):
       
  2043         "Check the never-cache status of a model history page"
       
  2044         response = self.client.get('/test_admin/admin/admin_views/section/1/history/')
       
  2045         self.assertEqual(get_max_age(response), 0)
       
  2046 
       
  2047     def testModelDelete(self):
       
  2048         "Check the never-cache status of a model delete page"
       
  2049         response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
       
  2050         self.assertEqual(get_max_age(response), 0)
       
  2051 
       
  2052     def testLogin(self):
       
  2053         "Check the never-cache status of login views"
       
  2054         self.client.logout()
       
  2055         response = self.client.get('/test_admin/admin/')
       
  2056         self.assertEqual(get_max_age(response), 0)
       
  2057 
       
  2058     def testLogout(self):
       
  2059         "Check the never-cache status of logout view"
       
  2060         response = self.client.get('/test_admin/admin/logout/')
       
  2061         self.assertEqual(get_max_age(response), 0)
       
  2062 
       
  2063     def testPasswordChange(self):
       
  2064         "Check the never-cache status of the password change view"
       
  2065         self.client.logout()
       
  2066         response = self.client.get('/test_admin/password_change/')
       
  2067         self.assertEqual(get_max_age(response), None)
       
  2068 
       
  2069     def testPasswordChangeDone(self):
       
  2070         "Check the never-cache status of the password change done view"
       
  2071         response = self.client.get('/test_admin/admin/password_change/done/')
       
  2072         self.assertEqual(get_max_age(response), None)
       
  2073 
       
  2074     def testJsi18n(self):
       
  2075         "Check the never-cache status of the Javascript i18n view"
       
  2076         response = self.client.get('/test_admin/admin/jsi18n/')
       
  2077         self.assertEqual(get_max_age(response), None)
       
  2078 
       
  2079 
       
  2080 class ReadonlyTest(TestCase):
       
  2081     fixtures = ['admin-views-users.xml']
       
  2082 
       
  2083     def setUp(self):
       
  2084         self.client.login(username='super', password='secret')
       
  2085 
       
  2086     def tearDown(self):
       
  2087         self.client.logout()
       
  2088 
       
  2089     def test_readonly_get(self):
       
  2090         response = self.client.get('/test_admin/admin/admin_views/post/add/')
       
  2091         self.assertEqual(response.status_code, 200)
       
  2092         self.assertNotContains(response, 'name="posted"')
       
  2093         # 3 fields + 2 submit buttons + 4 inline management form fields, + 2
       
  2094         # hidden fields for inlines + 1 field for the inline + 2 empty form
       
  2095         self.assertEqual(response.content.count("<input"), 14)
       
  2096         self.assertContains(response, formats.localize(datetime.date.today()))
       
  2097         self.assertContains(response,
       
  2098             "<label>Awesomeness level:</label>")
       
  2099         self.assertContains(response, "Very awesome.")
       
  2100         self.assertContains(response, "Unkown coolness.")
       
  2101         self.assertContains(response, "foo")
       
  2102         self.assertContains(response,
       
  2103             formats.localize(datetime.date.today() - datetime.timedelta(days=7))
       
  2104         )
       
  2105 
       
  2106         self.assertContains(response, '<div class="form-row coolness">')
       
  2107         self.assertContains(response, '<div class="form-row awesomeness_level">')
       
  2108         self.assertContains(response, '<div class="form-row posted">')
       
  2109         self.assertContains(response, '<div class="form-row value">')
       
  2110         self.assertContains(response, '<div class="form-row ">')
       
  2111 
       
  2112         p = Post.objects.create(title="I worked on readonly_fields", content="Its good stuff")
       
  2113         response = self.client.get('/test_admin/admin/admin_views/post/%d/' % p.pk)
       
  2114         self.assertContains(response, "%d amount of cool" % p.pk)
       
  2115 
       
  2116     def test_readonly_post(self):
       
  2117         data = {
       
  2118             "title": "Django Got Readonly Fields",
       
  2119             "content": "This is an incredible development.",
       
  2120             "link_set-TOTAL_FORMS": "1",
       
  2121             "link_set-INITIAL_FORMS": "0",
       
  2122             "link_set-MAX_NUM_FORMS": "0",
       
  2123         }
       
  2124         response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
       
  2125         self.assertEqual(response.status_code, 302)
       
  2126         self.assertEqual(Post.objects.count(), 1)
       
  2127         p = Post.objects.get()
       
  2128         self.assertEqual(p.posted, datetime.date.today())
       
  2129 
       
  2130         data["posted"] = "10-8-1990" # some date that's not today
       
  2131         response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
       
  2132         self.assertEqual(response.status_code, 302)
       
  2133         self.assertEqual(Post.objects.count(), 2)
       
  2134         p = Post.objects.order_by('-id')[0]
       
  2135         self.assertEqual(p.posted, datetime.date.today())
       
  2136 
       
  2137     def test_readonly_manytomany(self):
       
  2138         "Regression test for #13004"
       
  2139         response = self.client.get('/test_admin/admin/admin_views/pizza/add/')
       
  2140         self.assertEqual(response.status_code, 200)
       
  2141 
       
  2142 class UserAdminTest(TestCase):
       
  2143     """
       
  2144     Tests user CRUD functionality.
       
  2145     """
       
  2146     fixtures = ['admin-views-users.xml']
       
  2147 
       
  2148     def setUp(self):
       
  2149         self.client.login(username='super', password='secret')
       
  2150 
       
  2151     def tearDown(self):
       
  2152         self.client.logout()
       
  2153 
       
  2154     def test_save_button(self):
       
  2155         user_count = User.objects.count()
       
  2156         response = self.client.post('/test_admin/admin/auth/user/add/', {
       
  2157             'username': 'newuser',
       
  2158             'password1': 'newpassword',
       
  2159             'password2': 'newpassword',
       
  2160         })
       
  2161         new_user = User.objects.order_by('-id')[0]
       
  2162         self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
       
  2163         self.assertEqual(User.objects.count(), user_count + 1)
       
  2164         self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
       
  2165 
       
  2166     def test_save_continue_editing_button(self):
       
  2167         user_count = User.objects.count()
       
  2168         response = self.client.post('/test_admin/admin/auth/user/add/', {
       
  2169             'username': 'newuser',
       
  2170             'password1': 'newpassword',
       
  2171             'password2': 'newpassword',
       
  2172             '_continue': '1',
       
  2173         })
       
  2174         new_user = User.objects.order_by('-id')[0]
       
  2175         self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
       
  2176         self.assertEqual(User.objects.count(), user_count + 1)
       
  2177         self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
       
  2178 
       
  2179     def test_password_mismatch(self):
       
  2180         response = self.client.post('/test_admin/admin/auth/user/add/', {
       
  2181             'username': 'newuser',
       
  2182             'password1': 'newpassword',
       
  2183             'password2': 'mismatch',
       
  2184         })
       
  2185         self.assertEqual(response.status_code, 200)
       
  2186         adminform = response.context['adminform']
       
  2187         self.assertTrue('password' not in adminform.form.errors)
       
  2188         self.assertEqual(adminform.form.errors['password2'],
       
  2189                           [u"The two password fields didn't match."])
       
  2190 
       
  2191     def test_user_fk_popup(self):
       
  2192         response = self.client.get('/test_admin/admin/admin_views/album/add/')
       
  2193         self.assertEqual(response.status_code, 200)
       
  2194         self.assertContains(response, '/test_admin/admin/auth/user/add')
       
  2195         self.assertContains(response, 'class="add-another" id="add_id_owner" onclick="return showAddAnotherPopup(this);"')
       
  2196         response = self.client.get('/test_admin/admin/auth/user/add/?_popup=1')
       
  2197         self.assertNotContains(response, 'name="_continue"')
       
  2198         self.assertNotContains(response, 'name="_addanother"')
       
  2199 
       
  2200     def test_save_add_another_button(self):
       
  2201         user_count = User.objects.count()
       
  2202         response = self.client.post('/test_admin/admin/auth/user/add/', {
       
  2203             'username': 'newuser',
       
  2204             'password1': 'newpassword',
       
  2205             'password2': 'newpassword',
       
  2206             '_addanother': '1',
       
  2207         })
       
  2208         new_user = User.objects.order_by('-id')[0]
       
  2209         self.assertRedirects(response, '/test_admin/admin/auth/user/add/')
       
  2210         self.assertEqual(User.objects.count(), user_count + 1)
       
  2211         self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
       
  2212 
       
  2213 try:
       
  2214     # If docutils isn't installed, skip the AdminDocs tests.
       
  2215     import docutils
       
  2216 
       
  2217     class AdminDocsTest(TestCase):
       
  2218         fixtures = ['admin-views-users.xml']
       
  2219 
       
  2220         def setUp(self):
       
  2221             self.client.login(username='super', password='secret')
       
  2222 
       
  2223         def tearDown(self):
       
  2224             self.client.logout()
       
  2225 
       
  2226         def test_tags(self):
       
  2227             response = self.client.get('/test_admin/admin/doc/tags/')
       
  2228 
       
  2229             # The builtin tag group exists
       
  2230             self.assertContains(response, "<h2>Built-in tags</h2>", count=2)
       
  2231 
       
  2232             # A builtin tag exists in both the index and detail
       
  2233             self.assertContains(response, '<h3 id="built_in-autoescape">autoescape</h3>')
       
  2234             self.assertContains(response, '<li><a href="#built_in-autoescape">autoescape</a></li>')
       
  2235 
       
  2236             # An app tag exists in both the index and detail
       
  2237             self.assertContains(response, '<h3 id="comments-get_comment_count">get_comment_count</h3>')
       
  2238             self.assertContains(response, '<li><a href="#comments-get_comment_count">get_comment_count</a></li>')
       
  2239 
       
  2240             # The admin list tag group exists
       
  2241             self.assertContains(response, "<h2>admin_list</h2>", count=2)
       
  2242 
       
  2243             # An admin list tag exists in both the index and detail
       
  2244             self.assertContains(response, '<h3 id="admin_list-admin_actions">admin_actions</h3>')
       
  2245             self.assertContains(response, '<li><a href="#admin_list-admin_actions">admin_actions</a></li>')
       
  2246 
       
  2247         def test_filters(self):
       
  2248             response = self.client.get('/test_admin/admin/doc/filters/')
       
  2249 
       
  2250             # The builtin filter group exists
       
  2251             self.assertContains(response, "<h2>Built-in filters</h2>", count=2)
       
  2252 
       
  2253             # A builtin filter exists in both the index and detail
       
  2254             self.assertContains(response, '<h3 id="built_in-add">add</h3>')
       
  2255             self.assertContains(response, '<li><a href="#built_in-add">add</a></li>')
       
  2256 
       
  2257 except ImportError:
       
  2258     pass
       
  2259 
       
  2260 class ValidXHTMLTests(TestCase):
       
  2261     fixtures = ['admin-views-users.xml']
       
  2262     urlbit = 'admin'
       
  2263 
       
  2264     def setUp(self):
       
  2265         self._context_processors = None
       
  2266         self._use_i18n, settings.USE_I18N = settings.USE_I18N, False
       
  2267         if 'django.core.context_processors.i18n' in settings.TEMPLATE_CONTEXT_PROCESSORS:
       
  2268             self._context_processors = settings.TEMPLATE_CONTEXT_PROCESSORS
       
  2269             cp = list(settings.TEMPLATE_CONTEXT_PROCESSORS)
       
  2270             cp.remove('django.core.context_processors.i18n')
       
  2271             settings.TEMPLATE_CONTEXT_PROCESSORS = tuple(cp)
       
  2272             # Force re-evaluation of the contex processor list
       
  2273             django.template.context._standard_context_processors = None
       
  2274         self.client.login(username='super', password='secret')
       
  2275 
       
  2276     def tearDown(self):
       
  2277         self.client.logout()
       
  2278         if self._context_processors is not None:
       
  2279             settings.TEMPLATE_CONTEXT_PROCESSORS = self._context_processors
       
  2280             # Force re-evaluation of the contex processor list
       
  2281             django.template.context._standard_context_processors = None
       
  2282         settings.USE_I18N = self._use_i18n
       
  2283 
       
  2284     def testLangNamePresent(self):
       
  2285         response = self.client.get('/test_admin/%s/admin_views/' % self.urlbit)
       
  2286         self.assertFalse(' lang=""' in response.content)
       
  2287         self.assertFalse(' xml:lang=""' in response.content)