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
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
30
class AdminViewBasicTest(TestCase):
31
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
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.
39
self.old_language_code = settings.LANGUAGE_CODE
40
self.client.login(username='super', password='secret')
43
settings.LANGUAGE_CODE = self.old_language_code
46
def testTrailingSlashRequired(self):
48
If you leave off the trailing slash, app should redirect and add it.
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
55
def testBasicAddGet(self):
57
A smoke test to ensure GET on the add_view works.
59
response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit)
60
self.failUnlessEqual(response.status_code, 200)
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)
66
'value="My Section"' in response.content,
67
"Couldn't find an input with the right value in the response."
70
def testBasicEditGet(self):
72
A smoke test to ensure GET on the change_view works.
74
response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
75
self.failUnlessEqual(response.status_code, 200)
77
def testBasicEditGetStringPK(self):
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.
83
response = self.client.get('/test_admin/%s/admin_views/section/abc/' % self.urlbit)
84
self.failUnlessEqual(response.status_code, 404)
86
def testBasicAddPost(self):
88
A smoke test to ensure POST on add_view works.
91
"name": u"Another Section",
93
"article_set-TOTAL_FORMS": u"3",
94
"article_set-INITIAL_FORMS": u"0",
95
"article_set-MAX_NUM_FORMS": u"0",
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
100
# Post data for edit inline
102
"name": u"Test section",
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"",
141
def testBasicEditPost(self):
143
A smoke test to ensure POST on edit_view works.
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
148
def testEditSaveAs(self):
152
post_data = self.inline_post_data.copy()
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",
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
164
def testChangeListSortingCallable(self):
166
Ensure we can sort on a list_display field that is a callable
167
(column 2 is callable_year in ArticleAdmin)
169
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2})
170
self.failUnlessEqual(response.status_code, 200)
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."
177
def testChangeListSortingModel(self):
179
Ensure we can sort on a list_display field that is a Model method
180
(colunn 3 is 'model_year' in ArticleAdmin)
182
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3})
183
self.failUnlessEqual(response.status_code, 200)
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."
190
def testChangeListSortingModelAdmin(self):
192
Ensure we can sort on a list_display field that is a ModelAdmin method
193
(colunn 4 is 'modeladmin_year' in ArticleAdmin)
195
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4})
196
self.failUnlessEqual(response.status_code, 200)
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."
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)
208
'<div id="changelist-filter">' in response.content,
209
"Expected filter not found in changelist view."
212
'<a href="?color__id__exact=3">Blue</a>' in response.content,
213
"Changelist filter not correctly limited by limit_choices_to."
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)
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')
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)
238
def testNamedGroupFieldChoicesChangeList(self):
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.
244
response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
245
self.failUnlessEqual(response.status_code, 200)
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."
252
def testNamedGroupFieldChoicesFilter(self):
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.
257
response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
258
self.failUnlessEqual(response.status_code, 200)
260
'<div id="changelist-filter">' in response.content,
261
"Expected filter not found in changelist view."
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."
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
274
response = self.client.get("/test_admin/admin/admin_views/post/")
275
self.failUnless('icon-unknown.gif' in response.content)
277
def testI18NLanguageNonEnglishDefault(self):
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.
283
settings.LANGUAGE_CODE = 'fr'
285
response = self.client.get('/test_admin/admin/jsi18n/')
286
self.assertNotContains(response, 'Choisir une heure')
289
def testI18NLanguageNonEnglishFallback(self):
291
Makes sure that the fallback language is still working properly
292
in cases where the selected language cannot be found.
294
settings.LANGUAGE_CODE = 'fr'
296
response = self.client.get('/test_admin/admin/jsi18n/')
297
self.assertContains(response, 'Choisir une heure')
301
class SaveAsTests(TestCase):
302
fixtures = ['admin-views-users.xml','admin-views-person.xml']
305
self.client.login(username='super', password='secret')
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)
317
def test_save_as_display(self):
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
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/')
329
class CustomModelAdminTest(AdminViewBasicTest):
332
def testCustomAdminSiteLoginTemplate(self):
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)
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)
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)
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)
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)
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)
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)
368
class AdminViewPermissionsTest(TestCase):
369
"""Tests for Admin Views Permissions."""
371
fixtures = ['admin-views-users.xml']
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.
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()))
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()))
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()))
396
delete_user.user_permissions.add(get_perm(Section,
397
Section._meta.get_delete_permission()))
403
'password': 'secret'}
404
self.super_email_login = {
406
'username': 'super@example.com',
407
'password': 'secret'}
408
self.super_email_bad_login = {
410
'username': 'super@example.com',
411
'password': 'notsecret'}
412
self.adduser_login = {
414
'username': 'adduser',
415
'password': 'secret'}
416
self.changeuser_login = {
418
'username': 'changeuser',
419
'password': 'secret'}
420
self.deleteuser_login = {
422
'username': 'deleteuser',
423
'password': 'secret'}
424
self.joepublic_login = {
426
'username': 'joepublic',
427
'password': 'secret'}
428
self.no_username_login = {
430
'password': 'secret'}
434
Make sure only staff members can log in.
436
Successful posts to the login page will redirect to the orignal url.
437
Unsuccessfull attempts will continue to render the login page with
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/')
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 '@' character")
456
new_user = User(username='jondoe', password='secret', email='super@example.com')
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 '@' character")
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/')
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/')
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/')
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'))
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'))
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)
509
def testAddView(self):
510
"""Test add view restricts access and actually adds items."""
512
add_dict = {'title' : 'Døm ikke',
513
'content': '<p>great article</p>',
514
'date_0': '2008-03-18', 'date_1': '10:54:39',
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/')
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> ›'
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/')
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/')
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)
564
def testChangeView(self):
565
"""Change view should restrict access and allow users to edit items."""
567
change_dict = {'title' : 'Ikke fordømt',
568
'content': '<p>edited article</p>',
569
'date_0': '2008-03-18', 'date_1': '10:54:39',
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/')
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>')
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/')
607
def testCustomModelAdminTemplates(self):
608
self.client.get('/test_admin/admin/')
609
self.client.post('/test_admin/admin/', self.super_login)
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')
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')
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',
627
self.assertRedirects(post, '/test_admin/admin/admin_views/customarticle/')
628
self.failUnlessEqual(CustomArticle.objects.all().count(), 1)
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={
638
'action': ['delete_selected'],
639
'_selected_action': ['1'],
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')
645
self.client.get('/test_admin/admin/logout/')
647
def testDeleteView(self):
648
"""Delete view should restrict access and actually delete items."""
650
delete_dict = {'post': 'yes'}
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/')
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/")
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/')
679
def testDisabledPermissionsWhenLoggedIn(self):
680
self.client.login(username='super', password='secret')
681
superuser = User.objects.get(username='super')
682
superuser.is_active = False
685
response = self.client.get('/test_admin/admin/')
686
self.assertContains(response, 'id="login-form"')
687
self.assertNotContains(response, 'Log out')
689
response = self.client.get('/test_admin/admin/secure-view/')
690
self.assertContains(response, 'id="login-form"')
693
class AdminViewDeletedObjectsTest(TestCase):
694
fixtures = ['admin-views-users.xml', 'deleted-objects.xml']
697
self.client.login(username='super', password='secret')
702
def test_nesting(self):
704
Objects should be nested to display the relationships that
705
cause them to be scheduled for deletion.
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))
711
def test_cyclic(self):
713
Cyclic relationships should still cause each object to only be
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))
721
self.assertContains(response, one, 1)
722
self.assertContains(response, two, 1)
724
def test_perms_needed(self):
726
delete_user = User.objects.get(username='deleteuser')
727
delete_user.user_permissions.add(get_perm(Plot,
728
Plot._meta.get_delete_permission()))
730
self.failUnless(self.client.login(username='deleteuser',
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>")
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)
743
def test_multiple_fkeys_to_same_model(self):
745
If a deleted object has two relationships from another model,
746
both of those should be followed in looking for related
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)
756
def test_multiple_fkeys_to_same_instance(self):
758
If a deleted object has two relationships pointing to it from
759
another object, the other object should still only be listed
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)
767
def test_inheritance(self):
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.
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!"""
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)
787
def test_generic_relations(self):
789
If a deleted object has GenericForeignKeys pointing to it,
790
those objects should be listed for deletion.
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)
799
class AdminViewStringPrimaryKeyTest(TestCase):
800
fixtures = ['admin-views-users.xml', 'string-primary-key.xml']
802
def __init__(self, *args):
803
super(AdminViewStringPrimaryKeyTest, self).__init__(*args)
804
self.pk = """abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`"""
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='')
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)
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)
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)
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)
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
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)
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)
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")
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)
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")
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)
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")
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)
890
class SecureViewTest(TestCase):
891
fixtures = ['admin-views-users.xml']
898
'password': 'secret'}
899
self.super_email_login = {
901
'username': 'super@example.com',
902
'password': 'secret'}
903
self.super_email_bad_login = {
905
'username': 'super@example.com',
906
'password': 'notsecret'}
907
self.adduser_login = {
909
'username': 'adduser',
910
'password': 'secret'}
911
self.changeuser_login = {
913
'username': 'changeuser',
914
'password': 'secret'}
915
self.deleteuser_login = {
917
'username': 'deleteuser',
918
'password': 'secret'}
919
self.joepublic_login = {
921
'username': 'joepublic',
922
'password': 'secret'}
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')
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)
939
def test_staff_member_required_decorator_works_as_per_admin_login(self):
941
Make sure only staff members can log in.
943
Successful posts to the login page will redirect to the orignal url.
944
Unsuccessfull attempts will continue to render the login page with
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)
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 '@' character")
965
new_user = User(username='jondoe', password='secret', email='super@example.com')
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 '@' character")
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/')
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/')
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/')
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'))
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)
1012
class AdminViewUnicodeTest(TestCase):
1013
fixtures = ['admin-views-unicode.xml']
1016
self.client.login(username='super', password='secret')
1019
self.client.logout()
1021
def testUnicodeEdit(self):
1023
A test to ensure that POST on edit_view handles non-ascii characters.
1026
"name": u"Test lærdommer",
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"<p>Svært frustrerende med UnicodeDecodeError</p>",
1034
"chapter_set-1-id": u"2",
1035
"chapter_set-1-title": u"Kjærlighet.",
1036
"chapter_set-1-content": u"<p>La kjærligheten til de lidende seire.</p>",
1037
"chapter_set-2-id": u"3",
1038
"chapter_set-2-title": u"Need a title.",
1039
"chapter_set-2-content": u"<p>Newest content</p>",
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"",
1051
response = self.client.post('/test_admin/admin/admin_views/book/1/', post_data)
1052
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
1054
def testUnicodeDelete(self):
1056
Ensure that the delete_view handles non-ascii characters
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/')
1065
class AdminViewListEditable(TestCase):
1066
fixtures = ['admin-views-users.xml', 'admin-views-person.xml']
1069
self.client.login(username='super', password='secret')
1072
self.client.logout()
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)
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)
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)
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
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)
1104
def test_post_messages(self):
1105
# Ticket 12707: Saving inline editable should not show admin
1108
"form-TOTAL_FORMS": "3",
1109
"form-INITIAL_FORMS": "3",
1110
"form-MAX_NUM_FORMS": "0",
1112
"form-0-gender": "1",
1115
"form-1-gender": "2",
1118
"form-2-alive": "checked",
1119
"form-2-gender": "1",
1124
response = self.client.post('/test_admin/admin/admin_views/person/',
1126
self.assertEqual(len(response.context['messages']), 1)
1128
def test_post_submission(self):
1130
"form-TOTAL_FORMS": "3",
1131
"form-INITIAL_FORMS": "3",
1132
"form-MAX_NUM_FORMS": "0",
1134
"form-0-gender": "1",
1137
"form-1-gender": "2",
1140
"form-2-alive": "checked",
1141
"form-2-gender": "1",
1146
self.client.post('/test_admin/admin/admin_views/person/', data)
1148
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
1149
self.failUnlessEqual(Person.objects.get(name="Grace Hopper").gender, 2)
1151
# test a filtered page
1153
"form-TOTAL_FORMS": "2",
1154
"form-INITIAL_FORMS": "2",
1155
"form-MAX_NUM_FORMS": "0",
1158
"form-0-gender": "1",
1159
"form-0-alive": "checked",
1162
"form-1-gender": "1",
1163
"form-1-alive": "checked",
1167
self.client.post('/test_admin/admin/admin_views/person/?gender__exact=1', data)
1169
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, True)
1171
# test a searched page
1173
"form-TOTAL_FORMS": "1",
1174
"form-INITIAL_FORMS": "1",
1175
"form-MAX_NUM_FORMS": "0",
1178
"form-0-gender": "1",
1182
self.client.post('/test_admin/admin/admin_views/person/?q=mauchly', data)
1184
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
1186
def test_non_form_errors(self):
1187
# test if non-form errors are handled; ticket #12716
1189
"form-TOTAL_FORMS": "1",
1190
"form-INITIAL_FORMS": "1",
1191
"form-MAX_NUM_FORMS": "0",
1194
"form-0-alive": "1",
1195
"form-0-gender": "2",
1197
# Ensure that the form processing understands this as a list_editable "Save"
1198
# and not an action "Go".
1201
response = self.client.post('/test_admin/admin/admin_views/person/', data)
1202
self.assertContains(response, "Grace is not a Zombie")
1204
def test_non_form_errors_is_errorlist(self):
1205
# test if non-form errors are correctly handled; ticket #12878
1207
"form-TOTAL_FORMS": "1",
1208
"form-INITIAL_FORMS": "1",
1209
"form-MAX_NUM_FORMS": "0",
1212
"form-0-alive": "1",
1213
"form-0-gender": "2",
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"])))
1222
def test_list_editable_ordering(self):
1223
collector = Collector.objects.create(id=1, name="Frederick Clegg")
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)
1230
# NB: The order values must be changed so that the items are reordered.
1232
"form-TOTAL_FORMS": "4",
1233
"form-INITIAL_FORMS": "4",
1234
"form-MAX_NUM_FORMS": "0",
1236
"form-0-order": "14",
1238
"form-0-collector": "1",
1240
"form-1-order": "13",
1242
"form-1-collector": "1",
1244
"form-2-order": "1",
1246
"form-2-collector": "1",
1248
"form-3-order": "0",
1250
"form-3-collector": "1",
1252
# Ensure that the form processing understands this as a list_editable "Save"
1253
# and not an action "Go".
1256
response = self.client.post('/test_admin/admin/admin_views/category/', data)
1257
# Successful post will redirect
1258
self.failUnlessEqual(response.status_code, 302)
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)
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.
1270
"form-TOTAL_FORMS": "3",
1271
"form-INITIAL_FORMS": "3",
1272
"form-MAX_NUM_FORMS": "0",
1274
"form-0-gender": "1",
1277
"form-1-gender": "2",
1280
"form-2-alive": "checked",
1281
"form-2-gender": "1",
1285
"_selected_action": [u'3'],
1286
"action": [u'', u'delete_selected'],
1288
self.client.post('/test_admin/admin/admin_views/person/', data)
1290
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, True)
1291
self.failUnlessEqual(Person.objects.get(name="Grace Hopper").gender, 1)
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.
1297
"form-TOTAL_FORMS": "3",
1298
"form-INITIAL_FORMS": "3",
1299
"form-MAX_NUM_FORMS": "0",
1301
"form-0-gender": "1",
1304
"form-1-gender": "2",
1307
"form-2-alive": "checked",
1308
"form-2-gender": "1",
1312
"_selected_action": [u'1'],
1313
"action": [u'', u'delete_selected'],
1315
self.client.post('/test_admin/admin/admin_views/person/', data)
1317
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
1318
self.failUnlessEqual(Person.objects.get(name="Grace Hopper").gender, 2)
1323
class AdminSearchTest(TestCase):
1324
fixtures = ['admin-views-users','multiple-child-classes']
1327
self.client.login(username='super', password='secret')
1330
self.client.logout()
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")
1338
class AdminInheritedInlinesTest(TestCase):
1339
fixtures = ['admin-views-users.xml',]
1342
self.client.login(username='super', password='secret')
1345
self.client.logout()
1347
def testInline(self):
1348
"Ensure that inline models which inherit from a common parent are correctly handled by admin."
1350
foo_user = u"foo username"
1351
bar_user = u"bar username"
1353
name_re = re.compile('name="(.*?)"')
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)))
1363
"name": u"Test Name",
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,
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)
1384
# test the edit case
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)))
1392
"name": u"Test Name",
1394
"accounts-TOTAL_FORMS": "2",
1395
"accounts-INITIAL_FORMS": u"1",
1396
"accounts-MAX_NUM_FORMS": u"0",
1398
"accounts-0-username": "%s-1" % foo_user,
1399
"accounts-0-account_ptr": "1",
1400
"accounts-0-persona": "1",
1402
"accounts-2-TOTAL_FORMS": u"2",
1403
"accounts-2-INITIAL_FORMS": u"1",
1404
"accounts-2-MAX_NUM_FORMS": u"0",
1406
"accounts-2-0-username": "%s-1" % bar_user,
1407
"accounts-2-0-account_ptr": "2",
1408
"accounts-2-0-persona": "1",
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)
1419
from django.core import mail
1421
class AdminActionsTest(TestCase):
1422
fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
1425
self.client.login(username='super', password='secret')
1428
self.client.logout()
1430
def test_model_admin_custom_action(self):
1431
"Tests a custom action defined in a ModelAdmin method"
1433
ACTION_CHECKBOX_NAME: [1],
1434
'action' : 'mail_admin',
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')
1441
def test_model_admin_default_delete_action(self):
1442
"Tests the default delete action defined as a ModelAdmin method"
1444
ACTION_CHECKBOX_NAME: [1, 2],
1445
'action' : 'delete_selected',
1448
delete_confirmation_data = {
1449
ACTION_CHECKBOX_NAME: [1, 2],
1450
'action' : 'delete_selected',
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)
1459
def test_custom_function_mail_action(self):
1460
"Tests a custom action defined in a function"
1462
ACTION_CHECKBOX_NAME: [1],
1463
'action' : 'external_mail',
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')
1470
def test_custom_function_action_with_redirect(self):
1471
"Tests a custom action defined in a function"
1473
ACTION_CHECKBOX_NAME: [1],
1474
'action' : 'redirect_to',
1477
response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
1478
self.failUnlessEqual(response.status_code, 302)
1480
def test_default_redirect(self):
1482
Test that actions which don't return an HttpResponse are redirected to
1483
the same page, retaining the querystring (which may contain changelist
1487
ACTION_CHECKBOX_NAME: [1],
1488
'action' : 'external_mail',
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)
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)
1500
'<input type="checkbox" class="action-select"' not in response.content,
1501
"Found an unexpected action toggle checkboxbox in response"
1503
self.assert_('action-checkbox-column' not in response.content,
1504
"Found unexpected action-checkbox-column class in response")
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"
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")
1521
def test_multiple_actions_form(self):
1523
Test that actions come from the form whose submit button was pressed (#10618).
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.
1532
response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
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')
1538
def test_user_message_on_none_selected(self):
1540
User should see a warning when 'Go' is pressed and no items are selected.
1543
ACTION_CHECKBOX_NAME: [],
1544
'action' : 'delete_selected',
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)
1552
def test_user_message_on_no_action(self):
1554
User should see a warning when 'Go' is pressed and no action is selected.
1557
ACTION_CHECKBOX_NAME: [1, 2],
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)
1566
def test_selection_counter(self):
1568
Check if the selection counter is there.
1570
response = self.client.get('/test_admin/admin/admin_views/subscriber/')
1571
self.assertContains(response, '0 of 2 selected')
1574
class TestCustomChangeList(TestCase):
1575
fixtures = ['admin-views-users.xml']
1579
result = self.client.login(username='super', password='secret')
1580
self.failUnlessEqual(result, True)
1583
self.client.logout()
1585
def test_custom_changelist(self):
1587
Validate that a custom ChangeList class can be used (#9749)
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')
1601
class TestInlineNotEditable(TestCase):
1602
fixtures = ['admin-views-users.xml']
1605
result = self.client.login(username='super', password='secret')
1606
self.failUnlessEqual(result, True)
1609
self.client.logout()
1613
InlineModelAdmin broken?
1615
response = self.client.get('/test_admin/admin/admin_views/parent/add/')
1616
self.failUnlessEqual(response.status_code, 200)
1618
class AdminCustomQuerysetTest(TestCase):
1619
fixtures = ['admin-views-users.xml']
1622
self.client.login(username='super', password='secret')
1623
self.pks = [EmptyModel.objects.create().id for i in range(3)]
1625
def test_changelist_view(self):
1626
response = self.client.get('/test_admin/admin/admin_views/emptymodel/')
1629
self.assertContains(response, 'Primary key = %s' % i)
1631
self.assertNotContains(response, 'Primary key = %s' % i)
1633
def test_change_view(self):
1635
response = self.client.get('/test_admin/admin/admin_views/emptymodel/%s/' % i)
1637
self.assertEqual(response.status_code, 200)
1639
self.assertEqual(response.status_code, 404)
1641
class AdminInlineFileUploadTest(TestCase):
1642
fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
1646
self.client.login(username='super', password='secret')
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
1656
g = Gallery(name="Test Gallery")
1658
p = Picture(name="Test Picture", image=filename, gallery=g)
1662
self.client.logout()
1664
def test_inline_file_upload_edit_validation_error_post(self):
1666
Test that inline file uploads correctly display prior data (#10002).
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": "",
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)
1686
class AdminInlineTests(TestCase):
1687
fixtures = ['admin-views-users.xml']
1691
"name": u"Test Name",
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": "",
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": "",
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": "",
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": "",
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",
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",
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()
1781
self.client.logout()
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")
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"')
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")
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")
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")
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"')
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")
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")
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")
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"')
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")
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")
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")
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"')
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")
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")
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")
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"')
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")
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")
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)
1949
# NB: The order values must be changed so that the items are reordered.
1950
self.post_data.update({
1951
"name": "Frederick Clegg",
1953
"category_set-TOTAL_FORMS": "7",
1954
"category_set-INITIAL_FORMS": "4",
1955
"category_set-MAX_NUM_FORMS": "0",
1957
"category_set-0-order": "14",
1958
"category_set-0-id": "1",
1959
"category_set-0-collector": "1",
1961
"category_set-1-order": "13",
1962
"category_set-1-id": "2",
1963
"category_set-1-collector": "1",
1965
"category_set-2-order": "1",
1966
"category_set-2-id": "3",
1967
"category_set-2-collector": "1",
1969
"category_set-3-order": "0",
1970
"category_set-3-id": "4",
1971
"category_set-3-collector": "1",
1973
"category_set-4-order": "",
1974
"category_set-4-id": "",
1975
"category_set-4-collector": "1",
1977
"category_set-5-order": "",
1978
"category_set-5-id": "",
1979
"category_set-5-collector": "1",
1981
"category_set-6-order": "",
1982
"category_set-6-id": "",
1983
"category_set-6-collector": "1",
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)
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)
1997
class NeverCacheTests(TestCase):
1998
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
2001
self.client.login(username='super', password='secret')
2004
self.client.logout()
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
2069
class ReadonlyTest(TestCase):
2070
fixtures = ['admin-views-users.xml']
2073
self.client.login(username='super', password='secret')
2076
self.client.logout()
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))
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 ">')
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)
2105
def test_readonly_post(self):
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",
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())
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())
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)
2131
class UserAdminTest(TestCase):
2133
Tests user CRUD functionality.
2135
fixtures = ['admin-views-users.xml']
2138
self.client.login(username='super', password='secret')
2141
self.client.logout()
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',
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)
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',
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."])
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"')
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',
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)
2190
# If docutils isn't installed, skip the AdminDocs tests.
2193
class AdminDocsTest(TestCase):
2194
fixtures = ['admin-views-users.xml']
2197
self.client.login(username='super', password='secret')
2200
self.client.logout()
2202
def test_tags(self):
2203
response = self.client.get('/test_admin/admin/doc/tags/')
2205
# The builtin tag group exists
2206
self.assertContains(response, "<h2>Built-in tags</h2>", count=2)
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>')
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)
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>')
2220
def test_filters(self):
2221
response = self.client.get('/test_admin/admin/doc/filters/')
2223
# The builtin filter group exists
2224
self.assertContains(response, "<h2>Built-in filters</h2>", count=2)
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>')