diff -r 8ff680917e4e django/contrib/admin/templatetags/admin_list.py
a
|
b
|
|
70 | 70 | |
71 | 71 | def result_headers(cl): |
72 | 72 | lookup_opts = cl.lookup_opts |
73 | | |
| 73 | |
74 | 74 | for i, field_name in enumerate(cl.list_display): |
75 | 75 | attr = None |
76 | 76 | try: |
… |
… |
|
97 | 97 | raise AttributeError, \ |
98 | 98 | "'%s' model or '%s' objects have no attribute '%s'" % \ |
99 | 99 | (lookup_opts.object_name, cl.model_admin.__class__, field_name) |
100 | | |
| 100 | |
101 | 101 | try: |
102 | 102 | header = attr.short_description |
103 | 103 | except AttributeError: |
… |
… |
|
205 | 205 | result_repr = EMPTY_CHANGELIST_VALUE |
206 | 206 | # Fields with choices are special: Use the representation |
207 | 207 | # of the choice. |
208 | | elif f.choices: |
209 | | result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE) |
| 208 | elif f.flatchoices: |
| 209 | result_repr = dict(f.flatchoices).get(field_val, EMPTY_CHANGELIST_VALUE) |
210 | 210 | else: |
211 | 211 | result_repr = escape(field_val) |
212 | 212 | if force_unicode(result_repr) == '': |
diff -r 8ff680917e4e tests/regressiontests/admin_views/customadmin.py
a
|
b
|
|
10 | 10 | class Admin2(admin.AdminSite): |
11 | 11 | login_template = 'custom_admin/login.html' |
12 | 12 | index_template = 'custom_admin/index.html' |
13 | | |
| 13 | |
14 | 14 | # A custom index view. |
15 | 15 | def index(self, request, extra_context=None): |
16 | 16 | return super(Admin2, self).index(request, {'foo': '*bar*'}) |
17 | | |
| 17 | |
18 | 18 | def get_urls(self): |
19 | 19 | return patterns('', |
20 | 20 | (r'^my_view/$', self.admin_view(self.my_view)), |
21 | 21 | ) + super(Admin2, self).get_urls() |
22 | | |
| 22 | |
23 | 23 | def my_view(self, request): |
24 | 24 | return HttpResponse("Django is a magical pony!") |
25 | | |
| 25 | |
26 | 26 | site = Admin2(name="admin2") |
27 | 27 | |
28 | 28 | site.register(models.Article, models.ArticleAdmin) |
29 | 29 | site.register(models.Section, inlines=[models.ArticleInline]) |
30 | 30 | site.register(models.Thing, models.ThingAdmin) |
| 31 | site.register(models.Fabric, models.FabricAdmin) |
diff -r 8ff680917e4e tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml
-
|
+
|
|
| 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <django-objects version="1.0"> |
| 3 | <object pk="1" model="admin_views.fabric"> |
| 4 | <field type="CharField" name="surface">x</field> |
| 5 | </object> |
| 6 | <object pk="2" model="admin_views.fabric"> |
| 7 | <field type="CharField" name="surface">y</field> |
| 8 | </object> |
| 9 | <object pk="3" model="admin_views.fabric"> |
| 10 | <field type="CharField" name="surface">plain</field> |
| 11 | </object> |
| 12 | </django-objects> |
diff -r 8ff680917e4e tests/regressiontests/admin_views/models.py
a
|
b
|
|
20 | 20 | |
21 | 21 | def __unicode__(self): |
22 | 22 | return self.title |
23 | | |
| 23 | |
24 | 24 | def model_year(self): |
25 | 25 | return self.date.year |
26 | 26 | model_year.admin_order_field = 'date' |
… |
… |
|
54 | 54 | |
55 | 55 | class ChapterXtra1(models.Model): |
56 | 56 | chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?') |
57 | | xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') |
| 57 | xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') |
58 | 58 | |
59 | 59 | def __unicode__(self): |
60 | 60 | return u'¿Xtra1: %s' % self.xtra |
61 | 61 | |
62 | 62 | class ChapterXtra2(models.Model): |
63 | 63 | chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?') |
64 | | xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') |
| 64 | xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') |
65 | 65 | |
66 | 66 | def __unicode__(self): |
67 | 67 | return u'¿Xtra2: %s' % self.xtra |
… |
… |
|
87 | 87 | 'extra_var': 'Hello!' |
88 | 88 | } |
89 | 89 | ) |
90 | | |
| 90 | |
91 | 91 | def modeladmin_year(self, obj): |
92 | 92 | return obj.date.year |
93 | 93 | modeladmin_year.admin_order_field = 'date' |
… |
… |
|
121 | 121 | |
122 | 122 | class Color(models.Model): |
123 | 123 | value = models.CharField(max_length=10) |
124 | | warm = models.BooleanField() |
| 124 | warm = models.BooleanField() |
125 | 125 | def __unicode__(self): |
126 | 126 | return self.value |
127 | 127 | |
… |
… |
|
134 | 134 | class ThingAdmin(admin.ModelAdmin): |
135 | 135 | list_filter = ('color',) |
136 | 136 | |
| 137 | class Fabric(models.Model): |
| 138 | NG_CHOICES = ( |
| 139 | ('Textured', ( |
| 140 | ('x', 'Horizontal'), |
| 141 | ('y', 'Vertical'), |
| 142 | ) |
| 143 | ), |
| 144 | ('plain', 'Smooth'), |
| 145 | ) |
| 146 | surface = models.CharField(max_length=20, choices=NG_CHOICES) |
| 147 | |
| 148 | class FabricAdmin(admin.ModelAdmin): |
| 149 | list_display = ('surface',) |
| 150 | list_filter = ('surface',) |
| 151 | |
137 | 152 | admin.site.register(Article, ArticleAdmin) |
138 | 153 | admin.site.register(CustomArticle, CustomArticleAdmin) |
139 | 154 | admin.site.register(Section, inlines=[ArticleInline]) |
140 | 155 | admin.site.register(ModelWithStringPrimaryKey) |
141 | 156 | admin.site.register(Color) |
142 | 157 | admin.site.register(Thing, ThingAdmin) |
| 158 | admin.site.register(Fabric, FabricAdmin) |
143 | 159 | |
144 | 160 | # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. |
145 | 161 | # That way we cover all four cases: |
diff -r 8ff680917e4e tests/regressiontests/admin_views/tests.py
a
|
b
|
|
12 | 12 | from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey |
13 | 13 | |
14 | 14 | class AdminViewBasicTest(TestCase): |
15 | | fixtures = ['admin-views-users.xml', 'admin-views-colors.xml'] |
16 | | |
17 | | # Store the bit of the URL where the admin is registered as a class |
| 15 | fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml'] |
| 16 | |
| 17 | # Store the bit of the URL where the admin is registered as a class |
18 | 18 | # variable. That way we can test a second AdminSite just by subclassing |
19 | 19 | # this test case and changing urlbit. |
20 | 20 | urlbit = 'admin' |
21 | | |
| 21 | |
22 | 22 | def setUp(self): |
23 | 23 | self.client.login(username='super', password='secret') |
24 | | |
| 24 | |
25 | 25 | def tearDown(self): |
26 | 26 | self.client.logout() |
27 | | |
| 27 | |
28 | 28 | def testTrailingSlashRequired(self): |
29 | 29 | """ |
30 | 30 | If you leave off the trailing slash, app should redirect and add it. |
… |
… |
|
33 | 33 | self.assertRedirects(request, |
34 | 34 | '/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301 |
35 | 35 | ) |
36 | | |
| 36 | |
37 | 37 | def testBasicAddGet(self): |
38 | 38 | """ |
39 | 39 | A smoke test to ensure GET on the add_view works. |
40 | 40 | """ |
41 | 41 | response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit) |
42 | 42 | self.failUnlessEqual(response.status_code, 200) |
43 | | |
| 43 | |
44 | 44 | def testAddWithGETArgs(self): |
45 | 45 | response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'}) |
46 | 46 | self.failUnlessEqual(response.status_code, 200) |
47 | 47 | self.failUnless( |
48 | | 'value="My Section"' in response.content, |
| 48 | 'value="My Section"' in response.content, |
49 | 49 | "Couldn't find an input with the right value in the response." |
50 | 50 | ) |
51 | | |
| 51 | |
52 | 52 | def testBasicEditGet(self): |
53 | 53 | """ |
54 | 54 | A smoke test to ensureGET on the change_view works. |
55 | 55 | """ |
56 | 56 | response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit) |
57 | 57 | self.failUnlessEqual(response.status_code, 200) |
58 | | |
| 58 | |
59 | 59 | def testBasicAddPost(self): |
60 | 60 | """ |
61 | 61 | A smoke test to ensure POST on add_view works. |
… |
… |
|
68 | 68 | } |
69 | 69 | response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data) |
70 | 70 | self.failUnlessEqual(response.status_code, 302) # redirect somewhere |
71 | | |
| 71 | |
72 | 72 | def testBasicEditPost(self): |
73 | 73 | """ |
74 | 74 | A smoke test to ensure POST on edit_view works. |
… |
… |
|
116 | 116 | |
117 | 117 | def testChangeListSortingCallable(self): |
118 | 118 | """ |
119 | | Ensure we can sort on a list_display field that is a callable |
| 119 | Ensure we can sort on a list_display field that is a callable |
120 | 120 | (column 2 is callable_year in ArticleAdmin) |
121 | 121 | """ |
122 | 122 | response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2}) |
… |
… |
|
126 | 126 | response.content.index('Middle content') < response.content.index('Newest content'), |
127 | 127 | "Results of sorting on callable are out of order." |
128 | 128 | ) |
129 | | |
| 129 | |
130 | 130 | def testChangeListSortingModel(self): |
131 | 131 | """ |
132 | | Ensure we can sort on a list_display field that is a Model method |
| 132 | Ensure we can sort on a list_display field that is a Model method |
133 | 133 | (colunn 3 is 'model_year' in ArticleAdmin) |
134 | 134 | """ |
135 | 135 | response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3}) |
… |
… |
|
139 | 139 | response.content.index('Middle content') < response.content.index('Oldest content'), |
140 | 140 | "Results of sorting on Model method are out of order." |
141 | 141 | ) |
142 | | |
| 142 | |
143 | 143 | def testChangeListSortingModelAdmin(self): |
144 | 144 | """ |
145 | | Ensure we can sort on a list_display field that is a ModelAdmin method |
| 145 | Ensure we can sort on a list_display field that is a ModelAdmin method |
146 | 146 | (colunn 4 is 'modeladmin_year' in ArticleAdmin) |
147 | 147 | """ |
148 | 148 | response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4}) |
149 | 149 | self.failUnlessEqual(response.status_code, 200) |
150 | 150 | self.failUnless( |
151 | | response.content.index('Oldest content') < response.content.index('Middle content') and |
| 151 | response.content.index('Oldest content') < response.content.index('Middle content') and |
152 | 152 | response.content.index('Middle content') < response.content.index('Newest content'), |
153 | 153 | "Results of sorting on ModelAdmin method are out of order." |
154 | 154 | ) |
155 | | |
| 155 | |
| 156 | def testNamedGroupFieldChoicesChangeList(self): |
| 157 | """ |
| 158 | Ensures the admin changelist shows correct values in the relevant column |
| 159 | for rows corresponding to instances of a model in which a named group |
| 160 | has been used in the choices option of a field. |
| 161 | """ |
| 162 | response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit) |
| 163 | self.failUnlessEqual(response.status_code, 200) |
| 164 | self.failUnless( |
| 165 | '<a href="1/">Horizontal</a>' in response.content and |
| 166 | '<a href="2/">Vertical</a>' in response.content, |
| 167 | "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group." |
| 168 | ) |
| 169 | |
156 | 170 | def testLimitedFilter(self): |
157 | 171 | """Ensure admin changelist filters do not contain objects excluded via limit_choices_to.""" |
158 | 172 | response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit) |
159 | 173 | self.failUnlessEqual(response.status_code, 200) |
160 | 174 | self.failUnless( |
161 | | '<div id="changelist-filter">' in response.content, |
| 175 | '<div id="changelist-filter">' in response.content, |
162 | 176 | "Expected filter not found in changelist view." |
163 | 177 | ) |
164 | 178 | self.failIf( |
165 | 179 | '<a href="?color__id__exact=3">Blue</a>' in response.content, |
166 | 180 | "Changelist filter not correctly limited by limit_choices_to." |
167 | 181 | ) |
168 | | |
| 182 | |
169 | 183 | def testIncorrectLookupParameters(self): |
170 | 184 | """Ensure incorrect lookup parameters are handled gracefully.""" |
171 | 185 | response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'}) |
172 | | self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit) |
| 186 | self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit) |
173 | 187 | response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'}) |
174 | 188 | self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit) |
175 | 189 | |
… |
… |
|
186 | 200 | request = self.client.get('/test_admin/admin2/') |
187 | 201 | self.assertTemplateUsed(request, 'custom_admin/index.html') |
188 | 202 | self.assert_('Hello from a custom index template *bar*' in request.content) |
189 | | |
| 203 | |
190 | 204 | def testCustomAdminSiteView(self): |
191 | 205 | self.client.login(username='super', password='secret') |
192 | 206 | response = self.client.get('/test_admin/%s/my_view/' % self.urlbit) |
… |
… |
|
411 | 425 | post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict) |
412 | 426 | self.assertRedirects(post, '/test_admin/admin/admin_views/article/') |
413 | 427 | self.failUnlessEqual(Article.objects.get(pk=1).content, '<p>edited article</p>') |
414 | | |
| 428 | |
415 | 429 | # one error in form should produce singular error message, multiple errors plural |
416 | 430 | change_dict['title'] = '' |
417 | 431 | post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict) |
… |
… |
|
422 | 436 | post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict) |
423 | 437 | self.failUnlessEqual(request.status_code, 200) |
424 | 438 | self.failUnless('Please correct the errors below.' in post.content, |
425 | | 'Plural error message not found in response to post with multiple errors.') |
| 439 | 'Plural error message not found in response to post with multiple errors.') |
426 | 440 | self.client.get('/test_admin/admin/logout/') |
427 | 441 | |
428 | 442 | def testCustomModelAdminTemplates(self): |
… |
… |
|
523 | 537 | response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk)) |
524 | 538 | should_contain = """<a href="../../%s/">%s</a>""" % (quote(self.pk), escape(self.pk)) |
525 | 539 | self.assertContains(response, should_contain) |
526 | | |
| 540 | |
527 | 541 | def test_url_conflicts_with_add(self): |
528 | 542 | "A model with a primary key that ends with add should be visible" |
529 | 543 | add_model = ModelWithStringPrimaryKey(id="i have something to add") |
… |
… |
|
531 | 545 | response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(add_model.pk)) |
532 | 546 | should_contain = """<h1>Change model with string primary key</h1>""" |
533 | 547 | self.assertContains(response, should_contain) |
534 | | |
| 548 | |
535 | 549 | def test_url_conflicts_with_delete(self): |
536 | 550 | "A model with a primary key that ends with delete should be visible" |
537 | 551 | delete_model = ModelWithStringPrimaryKey(id="delete") |
… |
… |
|
539 | 553 | response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(delete_model.pk)) |
540 | 554 | should_contain = """<h1>Change model with string primary key</h1>""" |
541 | 555 | self.assertContains(response, should_contain) |
542 | | |
| 556 | |
543 | 557 | def test_url_conflicts_with_history(self): |
544 | 558 | "A model with a primary key that ends with history should be visible" |
545 | 559 | history_model = ModelWithStringPrimaryKey(id="history") |
… |
… |
|
547 | 561 | response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(history_model.pk)) |
548 | 562 | should_contain = """<h1>Change model with string primary key</h1>""" |
549 | 563 | self.assertContains(response, should_contain) |
550 | | |
| 564 | |
551 | 565 | |
552 | 566 | class SecureViewTest(TestCase): |
553 | 567 | fixtures = ['admin-views-users.xml'] |
… |
… |
|
582 | 596 | LOGIN_FORM_KEY: 1, |
583 | 597 | 'username': 'joepublic', |
584 | 598 | 'password': 'secret'} |
585 | | |
| 599 | |
586 | 600 | def tearDown(self): |
587 | 601 | self.client.logout() |
588 | | |
| 602 | |
589 | 603 | def test_secure_view_shows_login_if_not_logged_in(self): |
590 | 604 | "Ensure that we see the login form" |
591 | 605 | response = self.client.get('/test_admin/admin/secure-view/' ) |
592 | 606 | self.assertTemplateUsed(response, 'admin/login.html') |
593 | | |
| 607 | |
594 | 608 | def test_secure_view_login_successfully_redirects_to_original_url(self): |
595 | 609 | request = self.client.get('/test_admin/admin/secure-view/') |
596 | 610 | self.failUnlessEqual(request.status_code, 200) |
597 | 611 | query_string = "the-answer=42" |
598 | 612 | login = self.client.post('/test_admin/admin/secure-view/', self.super_login, QUERY_STRING = query_string ) |
599 | 613 | self.assertRedirects(login, '/test_admin/admin/secure-view/?%s' % query_string) |
600 | | |
| 614 | |
601 | 615 | def test_staff_member_required_decorator_works_as_per_admin_login(self): |
602 | 616 | """ |
603 | 617 | Make sure only staff members can log in. |
604 | 618 | |
605 | 619 | Successful posts to the login page will redirect to the orignal url. |
606 | | Unsuccessfull attempts will continue to render the login page with |
| 620 | Unsuccessfull attempts will continue to render the login page with |
607 | 621 | a 200 status code. |
608 | 622 | """ |
609 | 623 | # Super User |