diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html
index a5b1a89..7f6c34a 100644
a
|
b
|
|
1 | | {% load i18n adminmedia %} |
| 1 | {% load i18n adminmedia admin_modify %} |
2 | 2 | <div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group"> |
3 | 3 | <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}"> |
4 | 4 | {{ inline_admin_formset.formset.management_form }} |
… |
… |
|
18 | 18 | <tbody> |
19 | 19 | {% for inline_admin_form in inline_admin_formset %} |
20 | 20 | {% if inline_admin_form.form.non_field_errors %} |
21 | | <tr><td colspan="{{ inline_admin_form.field_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr> |
| 21 | <tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr> |
22 | 22 | {% endif %} |
23 | 23 | <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 %}" |
24 | 24 | 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 fe88043..5c00de3 100644
a
|
b
|
def submit_row(context):
|
40 | 40 | 'show_save': True |
41 | 41 | } |
42 | 42 | submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row) |
| 43 | |
| 44 | def cell_count(inline_admin_form): |
| 45 | """ Returns the number of cells used in a tabular inline """ |
| 46 | count = 1 # Hidden cell with hidden 'id' field |
| 47 | for fieldset in inline_admin_form: |
| 48 | # Loop through all the fields (one per cell) |
| 49 | for line in fieldset: |
| 50 | for field in line: |
| 51 | count += 1 |
| 52 | if inline_admin_form.formset.can_delete: |
| 53 | # Delete checkbox |
| 54 | count += 1 |
| 55 | return count |
| 56 | cell_count = register.filter(cell_count) |
diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py
index 4e5c4e3..772152a 100644
a
|
b
|
from django.db import models
|
6 | 6 | from django.contrib import admin |
7 | 7 | from django.contrib.contenttypes.models import ContentType |
8 | 8 | from django.contrib.contenttypes import generic |
| 9 | from django import forms |
9 | 10 | |
10 | 11 | class Parent(models.Model): |
11 | 12 | name = models.CharField(max_length=50) |
… |
… |
class InlineWeakness(admin.TabularInline):
|
123 | 124 | extra = 1 |
124 | 125 | |
125 | 126 | admin.site.register(Fashionista, inlines=[InlineWeakness]) |
| 127 | |
| 128 | |
| 129 | # Models for #13510 -------------------- |
| 130 | |
| 131 | class TitleCollection(models.Model): |
| 132 | pass |
| 133 | |
| 134 | class Title(models.Model): |
| 135 | collection = models.ForeignKey(TitleCollection, blank=True, null=True) |
| 136 | title1 = models.CharField(max_length=100) |
| 137 | title2 = models.CharField(max_length=100) |
| 138 | |
| 139 | class TitleForm(forms.ModelForm): |
| 140 | |
| 141 | def clean(self): |
| 142 | cleaned_data = self.cleaned_data |
| 143 | title1 = cleaned_data.get("title1") |
| 144 | title2 = cleaned_data.get("title2") |
| 145 | if title1 != title2: |
| 146 | raise forms.ValidationError("The two titles must be the same") |
| 147 | return cleaned_data |
| 148 | |
| 149 | class TitleInline(admin.TabularInline): |
| 150 | model = Title |
| 151 | form = TitleForm |
| 152 | extra = 1 |
| 153 | |
| 154 | admin.site.register(TitleCollection, inlines=[TitleInline]) |
diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py
index b10474d..69ff96c 100644
a
|
b
|
class TestInline(TestCase):
|
67 | 67 | self.assertEqual(response.status_code, 302) |
68 | 68 | self.assertEqual(len(Fashionista.objects.filter(person__firstname='Imelda')), 1) |
69 | 69 | |
| 70 | def test_tabular_non_field_errors(self): |
| 71 | """ Ensure that non_field_errors are displayed correctly, including |
| 72 | the right value for colspan. |
| 73 | Refs #13510. |
| 74 | """ |
| 75 | data = { |
| 76 | 'title_set-TOTAL_FORMS': 1, |
| 77 | 'title_set-INITIAL_FORMS': 0, |
| 78 | 'title_set-MAX_NUM_FORMS': 0, |
| 79 | '_save': u'Save', |
| 80 | 'title_set-0-title1': 'a title', |
| 81 | 'title_set-0-title2': 'a different title', |
| 82 | } |
| 83 | response = self.client.post('/test_admin/admin/admin_inlines/titlecollection/add/', data) |
| 84 | # Here colspan is "4": two fields (title1 and title2), one hidden field and the delete checkbock. |
| 85 | self.assertContains(response, '<tr><td colspan="4"><ul class="errorlist"><li>The two titles must be the same</li></ul></td></tr>') |
| 86 | |
70 | 87 | class TestInlineMedia(TestCase): |
71 | 88 | fixtures = ['admin-views-users.xml'] |
72 | 89 | |