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 |