summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/admin/templates/admin/edit_inline/tabular.html4
-rw-r--r--django/contrib/admin/templatetags/admin_modify.py18
-rw-r--r--tests/regressiontests/admin_inlines/models.py28
-rw-r--r--tests/regressiontests/admin_inlines/tests.py17
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']