diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 7c5346df42..6eb5060414 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1580,7 +1580,7 @@ class ModelAdmin(BaseModelAdmin):
if (request.method == "POST" and cl.list_editable and
'_save' in request.POST and not action_failed):
FormSet = self.get_changelist_formset(request)
- formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
+ formset = cl.formset = FormSet(request.POST, request.FILES, queryset=self.get_queryset(request))
if formset.is_valid():
changecount = 0
for form in formset.forms:
diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py
index 51d183dfdb..9402ff7291 100644
--- a/tests/admin_changelist/admin.py
+++ b/tests/admin_changelist/admin.py
@@ -111,6 +111,8 @@ site.register(Parent, NoListDisplayLinksParentAdmin)
class SwallowAdmin(admin.ModelAdmin):
actions = None # prevent ['action_checkbox'] + list(list_display)
list_display = ('origin', 'load', 'speed', 'swallowonetoone')
+ list_editable = ['load', 'speed']
+ list_per_page = 3
site.register(Swallow, SwallowAdmin)
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index aa59d8adab..1eb1d160ef 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -58,7 +58,7 @@ class ChangeListTests(TestCase):
self.factory = RequestFactory()
def _create_superuser(self, username):
- return User.objects.create(username=username, is_superuser=True)
+ return User.objects.create_superuser(username=username, email='a@b.com', password='xxx')
def _mocked_authenticated_request(self, url, user):
request = self.factory.get(url)
@@ -608,6 +608,73 @@ class ChangeListTests(TestCase):
self.assertContains(response, '
- | ')
self.assertContains(response, '%s | ' % swallow_o2o)
+ def test_multiuser_edit(self):
+ """
+ Simultaneous edits of list_editable fields on the changelist by
+ different users must not result in one user's edits creating a new
+ object instead of modifying the correct existing object (#11313).
+ """
+ # To replicate this issue, simulate the following steps:
+ # 1. User1 opens an admin changelist with list_editable fields.
+ # 2. User2 edits object "Foo" such that it moves to another page in
+ # the pagination order and saves.
+ # 3. User1 edits object "Foo" and saves.
+ # 4. The edit made by User1 does not get applied to object "Foo" but
+ # instead is used to create a new object (bug).
+
+ # For this test, order the changelist by the 'speed' attribute and
+ # display 3 objects per page (SwallowAdmin.list_per_page = 3).
+
+ # Setup the test to reflect the DB state after step 2 where User2 has
+ # edited the first swallow object's speed from '4' to '1'.
+ a = Swallow.objects.create(origin='Swallow A', load=4, speed=1)
+ b = Swallow.objects.create(origin='Swallow B', load=2, speed=2)
+ c = Swallow.objects.create(origin='Swallow C', load=5, speed=5)
+ d = Swallow.objects.create(origin='Swallow D', load=9, speed=9)
+
+ superuser = self._create_superuser('superuser')
+ self.client.force_login(superuser)
+ changelist_url = reverse('admin:admin_changelist_swallow_changelist')
+
+ # Send the POST from User1 for step 3. It's still using the changelist
+ # ordering from before User2's edits in step 2.
+ data = {
+ 'form-TOTAL_FORMS': '3',
+ 'form-INITIAL_FORMS': '3',
+ 'form-MIN_NUM_FORMS': '0',
+ 'form-MAX_NUM_FORMS': '1000',
+ 'form-0-id': str(d.pk),
+ 'form-1-id': str(c.pk),
+ 'form-2-id': str(a.pk),
+ 'form-0-load': '9.0',
+ 'form-0-speed': '9.0',
+ 'form-1-load': '5.0',
+ 'form-1-speed': '5.0',
+ 'form-2-load': '5.0',
+ 'form-2-speed': '4.0',
+ '_save': 'Save',
+ }
+ response = self.client.post(changelist_url, data, follow=True, extra={'o': '-2'})
+
+ # The object User1 edited in step 3 is displayed on the changelist and
+ # has the correct edits applied.
+ self.assertContains(response, '1 swallow was changed successfully.')
+ self.assertContains(response, a.origin)
+ a.refresh_from_db()
+ self.assertEqual(a.load, float(data['form-2-load']))
+ self.assertEqual(a.speed, float(data['form-2-speed']))
+ b.refresh_from_db()
+ self.assertEqual(b.load, 2)
+ self.assertEqual(b.speed, 2)
+ c.refresh_from_db()
+ self.assertEqual(c.load, float(data['form-1-load']))
+ self.assertEqual(c.speed, float(data['form-1-speed']))
+ d.refresh_from_db()
+ self.assertEqual(d.load, float(data['form-0-load']))
+ self.assertEqual(d.speed, float(data['form-0-speed']))
+ # No new swallows were created.
+ self.assertEqual(len(Swallow.objects.all()), 4)
+
def test_deterministic_order_for_unordered_model(self):
"""
Ensure that the primary key is systematically used in the ordering of