From 52936efacec4df05df2d9872c09c3332335bf21b Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Sun, 24 Nov 2019 23:09:07 +0100 Subject: [PATCH] Fixed #31031 -- Fixed data loss in admin changelist view when formset's prefix contains regex special chars. Regression in b18650a2634890aa758abae2f33875daa13a9ba3. --- django/contrib/admin/options.py | 4 +++- docs/releases/2.2.8.txt | 4 +++- tests/admin_changelist/tests.py | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 137e6faa0f..85896bed7e 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1631,7 +1631,9 @@ class ModelAdmin(BaseModelAdmin): def _get_edited_object_pks(self, request, prefix): """Return POST data values of list_editable primary keys.""" - pk_pattern = re.compile(r'{}-\d+-{}$'.format(prefix, self.model._meta.pk.name)) + pk_pattern = re.compile( + r'{}-\d+-{}$'.format(re.escape(prefix), self.model._meta.pk.name) + ) return [value for key, value in request.POST.items() if pk_pattern.match(key)] def _get_list_editable_queryset(self, request, prefix): diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index 0c2b3eabdf..e9bd1ed1cb 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -10,4 +10,6 @@ Django 2.2.8 fixes several bugs in 2.2.7 and adds compatibility with Python Bugfixes ======== -* ... +* Fixed a data loss possibility in the admin changelist view when a custom + :ref:`formset's prefix ` contains regular expression special + characters, e.g. `'$'` (:ticket:`31031`). diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 05490b061a..8cb6f7eff9 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -844,6 +844,26 @@ class ChangeListTests(TestCase): queryset = m._get_list_editable_queryset(request, prefix='form') self.assertEqual(queryset.count(), 2) + def test_get_list_editable_queryset_with_regex_chars_in_prefix(self): + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + Swallow.objects.create(origin='Swallow B', load=2, speed=2) + data = { + 'form$-TOTAL_FORMS': '2', + 'form$-INITIAL_FORMS': '2', + 'form$-MIN_NUM_FORMS': '0', + 'form$-MAX_NUM_FORMS': '1000', + 'form$-0-uuid': str(a.pk), + 'form$-0-load': '10', + '_save': 'Save', + } + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + m = SwallowAdmin(Swallow, custom_site) + request = self.factory.post(changelist_url, data=data) + queryset = m._get_list_editable_queryset(request, prefix='form$') + self.assertEqual(queryset.count(), 1) + def test_changelist_view_list_editable_changed_objects_uses_filter(self): """list_editable edits use a filtered queryset to limit memory usage.""" a = Swallow.objects.create(origin='Swallow A', load=4, speed=1)