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