~ubuntu-branches/ubuntu/quantal/python-django/quantal-security

« back to all changes in this revision

Viewing changes to .pc/05_fix_regression_tests.diff/tests/regressiontests/admin_views/tests.py

  • Committer: Bazaar Package Importer
  • Author(s): Jamie Strandboge
  • Date: 2010-10-12 11:34:35 UTC
  • mfrom: (1.1.12 upstream) (29.1.1 maverick-security)
  • Revision ID: james.westby@ubuntu.com-20101012113435-yy57c8tx6g9anf3e
Tags: 1.2.3-1ubuntu0.1
* SECURITY UPDATE: XSS in CSRF protections. New upstream release
  - CVE-2010-3082
* debian/patches/01_disable_url_verify_regression_tests.diff:
  - updated to disable another test that fails without internet connection
  - patch based on work by Kai Kasurinen and Krzysztof Klimonda
* debian/control: don't Build-Depends on locales-all, which doesn't exist
  in maverick

Show diffs side-by-side

added added

removed removed

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