diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html
index dea027ce5a..faa5a2e6ba 100644
--- a/django/contrib/admin/templates/admin/change_list_results.html
+++ b/django/contrib/admin/templates/admin/change_list_results.html
@@ -15,7 +15,10 @@
{% for result in results %}
-{% for item in result %}{{ item }}{% endfor %}
+{% if result.form.non_field_errors %}
+ {{ result.form.non_field_errors }} |
+{% endif %}
+{% for item in result.row %}{{ item }}{% endfor %}
{% endfor %}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 470d691e02..dc2139821d 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -199,10 +199,10 @@ def items_for_result(cl, result, form):
def results(cl):
if cl.formset:
for res, form in zip(cl.result_list, cl.formset.forms):
- yield list(items_for_result(cl, res, form))
+ yield {'row': list(items_for_result(cl, res, form)), 'form': form}
else:
for res in cl.result_list:
- yield list(items_for_result(cl, res, None))
+ yield {'row': list(items_for_result(cl, res, None)), 'form': None}
def result_hidden_fields(cl):
if cl.formset:
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index 60d8d06942..0c3f70102d 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -632,7 +632,32 @@ class WorkHourAdmin(admin.ModelAdmin):
class Reservation(models.Model):
start_date = models.DateTimeField()
price = models.IntegerField()
-
+
+
+DRIVER_CHOICES = (
+ (u'bill', 'Bill G'),
+ (u'steve', 'Steve J'),
+)
+
+RESTAURANT_CHOICES = (
+ (u'indian', u'A Taste of India'),
+ (u'thai', u'Thai Pography'),
+ (u'pizza', u'Pizza Mama'),
+)
+
+class FoodDelivery(models.Model):
+ reference = models.CharField(max_length=100)
+ driver = models.CharField(max_length=100, choices=DRIVER_CHOICES, blank=True)
+ restaurant = models.CharField(max_length=100, choices=RESTAURANT_CHOICES, blank=True)
+
+ class Meta:
+ unique_together = (("driver", "restaurant"),)
+
+class FoodDeliveryAdmin(admin.ModelAdmin):
+ list_display=('reference', 'driver', 'restaurant')
+ list_editable = ('driver', 'restaurant')
+
+
admin.site.register(Article, ArticleAdmin)
admin.site.register(CustomArticle, CustomArticleAdmin)
admin.site.register(Section, save_as=True, inlines=[ArticleInline])
@@ -669,6 +694,7 @@ admin.site.register(CyclicOne)
admin.site.register(CyclicTwo)
admin.site.register(WorkHour, WorkHourAdmin)
admin.site.register(Reservation)
+admin.site.register(FoodDelivery, FoodDeliveryAdmin)
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
# That way we cover all four cases:
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 4caba2bfec..7d3cc33438 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -26,11 +26,12 @@ from django.utils.translation import activate, deactivate
import django.template.context
# local test models
-from models import Article, BarAccount, CustomArticle, EmptyModel, \
- FooAccount, Gallery, ModelWithStringPrimaryKey, \
- Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, \
- Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \
- Category, Post, Plot, FunkyTag, WorkHour, Employee, Inquisition, Actor
+from models import (Article, BarAccount, CustomArticle, EmptyModel,
+ FooAccount, Gallery, ModelWithStringPrimaryKey,
+ Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast,
+ Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit,
+ Category, Post, Plot, FunkyTag, WorkHour, Employee, Inquisition,
+ Actor, FoodDelivery)
class AdminViewBasicTest(TestCase):
@@ -1287,6 +1288,67 @@ class AdminViewListEditable(TestCase):
self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
+ def test_non_field_errors(self):
+ ''' Ensure that non field errors are displayed for each of the
+ forms in the changelist's formset. Refs #13126.
+ '''
+ FoodDelivery.objects.create(reference='123', driver='bill', restaurant='thai')
+ FoodDelivery.objects.create(reference='456', driver='bill', restaurant='india')
+ FoodDelivery.objects.create(reference='789', driver='bill', restaurant='pizza')
+
+ data = {
+ "form-TOTAL_FORMS": "3",
+ "form-INITIAL_FORMS": "3",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-id": "1",
+ "form-0-reference": "123",
+ "form-0-driver": "bill",
+ "form-0-restaurant": "thai",
+
+ # Same data as above: Forbidden because of unique_together!
+ "form-1-id": "2",
+ "form-1-reference": "456",
+ "form-1-driver": "bill",
+ "form-1-restaurant": "thai",
+
+ "form-2-id": "3",
+ "form-2-reference": "789",
+ "form-2-driver": "bill",
+ "form-2-restaurant": "pizza",
+
+ "_save": "Save",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/fooddelivery/', data)
+ self.assertContains(response, '- Food delivery with this Driver and Restaurant already exists.
|
', 1)
+
+ data = {
+ "form-TOTAL_FORMS": "3",
+ "form-INITIAL_FORMS": "3",
+ "form-MAX_NUM_FORMS": "0",
+
+ "form-0-id": "1",
+ "form-0-reference": "123",
+ "form-0-driver": "bill",
+ "form-0-restaurant": "thai",
+
+ # Same data as above: Forbidden because of unique_together!
+ "form-1-id": "2",
+ "form-1-reference": "456",
+ "form-1-driver": "bill",
+ "form-1-restaurant": "thai",
+
+ # Same data also.
+ "form-2-id": "3",
+ "form-2-reference": "789",
+ "form-2-driver": "bill",
+ "form-2-restaurant": "thai",
+
+ "_save": "Save",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/fooddelivery/', data)
+ self.assertContains(response, '- Food delivery with this Driver and Restaurant already exists.
|
', 2)
+
def test_non_form_errors(self):
# test if non-form errors are handled; ticket #12716
data = {