diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 0e3c044e9b3..ff13aac41b6 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1575,14 +1575,19 @@ class ModelAdmin(BaseModelAdmin): else: action_failed = True + if action_failed: + # Redirect back to the changelist page to avoid resubmitting the + # form if the user refreshes the browser or uses the "No, take + # me back" button on the action confirmation page. + return HttpResponseRedirect(request.get_full_path()) + # If we're allowing changelist editing, we need to construct a formset # for the changelist given all the fields to be edited. Then we'll # use the formset to validate/process POSTed data. formset = cl.formset = None # Handle POSTed bulk-edit data. - if (request.method == "POST" and cl.list_editable and - '_save' in request.POST and not action_failed): + if request.method == 'POST' and cl.list_editable and '_save' in request.POST: FormSet = self.get_changelist_formset(request) formset = cl.formset = FormSet(request.POST, request.FILES, queryset=self.get_queryset(request)) if formset.is_valid(): diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 2d86487e517..c6894dd2a21 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -3406,7 +3406,10 @@ action) 'action': 'delete_selected', 'index': 0, } - response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data) + url = reverse('admin:admin_views_subscriber_changelist') + response = self.client.post(url, action_data) + self.assertRedirects(response, url, fetch_redirect_response=False) + response = self.client.get(response.url) msg = """Items must be selected in order to perform actions on them. No items have been changed.""" self.assertContains(response, msg) self.assertEqual(Subscriber.objects.count(), 2) @@ -3420,7 +3423,10 @@ action) 'action': '', 'index': 0, } - response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data) + url = reverse('admin:admin_views_subscriber_changelist') + response = self.client.post(url, action_data) + self.assertRedirects(response, url, fetch_redirect_response=False) + response = self.client.get(response.url) msg = """No action selected.""" self.assertContains(response, msg) self.assertEqual(Subscriber.objects.count(), 2)