diff options
| -rw-r--r-- | django/contrib/admin/templates/admin/edit_inline/tabular.html | 4 | ||||
| -rw-r--r-- | django/contrib/admin/templatetags/admin_modify.py | 18 | ||||
| -rw-r--r-- | tests/regressiontests/admin_inlines/models.py | 28 | ||||
| -rw-r--r-- | tests/regressiontests/admin_inlines/tests.py | 17 |
4 files changed, 63 insertions, 4 deletions
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index a5b1a899f5..7f6c34ad36 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -1,4 +1,4 @@ -{% load i18n adminmedia %} +{% load i18n adminmedia admin_modify %} <div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"> <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}"> {{ inline_admin_formset.formset.management_form }} @@ -18,7 +18,7 @@ <tbody> {% for inline_admin_form in inline_admin_formset %} {% if inline_admin_form.form.non_field_errors %} - <tr><td colspan="{{ inline_admin_form.field_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr> + <tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr> {% endif %} <tr class="{% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}"> diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index fe88043c9f..8e6f0ab5ce 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -21,7 +21,7 @@ prepopulated_fields_js = register.inclusion_tag('admin/prepopulated_fields_js.ht def submit_row(context): """ - Displays the row of buttons for delete and save. + Displays the row of buttons for delete and save. """ opts = context['opts'] change = context['change'] @@ -33,10 +33,24 @@ def submit_row(context): 'show_delete_link': (not is_popup and context['has_delete_permission'] and (change or context['show_delete'])), 'show_save_as_new': not is_popup and change and save_as, - 'show_save_and_add_another': context['has_add_permission'] and + 'show_save_and_add_another': context['has_add_permission'] and not is_popup and (not save_as or context['add']), 'show_save_and_continue': not is_popup and context['has_change_permission'], 'is_popup': is_popup, 'show_save': True } submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row) + +def cell_count(inline_admin_form): + """Returns the number of cells used in a tabular inline""" + count = 1 # Hidden cell with hidden 'id' field + for fieldset in inline_admin_form: + # Loop through all the fields (one per cell) + for line in fieldset: + for field in line: + count += 1 + if inline_admin_form.formset.can_delete: + # Delete checkbox + count += 1 + return count +cell_count = register.filter(cell_count) diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py index 4e5c4e3ae3..bb299f32b8 100644 --- a/tests/regressiontests/admin_inlines/models.py +++ b/tests/regressiontests/admin_inlines/models.py @@ -6,6 +6,7 @@ from django.db import models from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic +from django import forms class Parent(models.Model): name = models.CharField(max_length=50) @@ -123,3 +124,30 @@ class InlineWeakness(admin.TabularInline): extra = 1 admin.site.register(Fashionista, inlines=[InlineWeakness]) + +# Models for #13510 + +class TitleCollection(models.Model): + pass + +class Title(models.Model): + collection = models.ForeignKey(TitleCollection, blank=True, null=True) + title1 = models.CharField(max_length=100) + title2 = models.CharField(max_length=100) + +class TitleForm(forms.ModelForm): + + def clean(self): + cleaned_data = self.cleaned_data + title1 = cleaned_data.get("title1") + title2 = cleaned_data.get("title2") + if title1 != title2: + raise forms.ValidationError("The two titles must be the same") + return cleaned_data + +class TitleInline(admin.TabularInline): + model = Title + form = TitleForm + extra = 1 + +admin.site.register(TitleCollection, inlines=[TitleInline]) diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py index b10474d001..915c6fac8d 100644 --- a/tests/regressiontests/admin_inlines/tests.py +++ b/tests/regressiontests/admin_inlines/tests.py @@ -67,6 +67,23 @@ class TestInline(TestCase): self.assertEqual(response.status_code, 302) self.assertEqual(len(Fashionista.objects.filter(person__firstname='Imelda')), 1) + def test_tabular_non_field_errors(self): + """ + Ensure that non_field_errors are displayed correctly, including the + right value for colspan. Refs #13510. + """ + data = { + 'title_set-TOTAL_FORMS': 1, + 'title_set-INITIAL_FORMS': 0, + 'title_set-MAX_NUM_FORMS': 0, + '_save': u'Save', + 'title_set-0-title1': 'a title', + 'title_set-0-title2': 'a different title', + } + response = self.client.post('/test_admin/admin/admin_inlines/titlecollection/add/', data) + # Here colspan is "4": two fields (title1 and title2), one hidden field and the delete checkbock. + self.assertContains(response, '<tr><td colspan="4"><ul class="errorlist"><li>The two titles must be the same</li></ul></td></tr>') + class TestInlineMedia(TestCase): fixtures = ['admin-views-users.xml'] |
