Skip to content

Commit a53e02a

Browse files
committed
Fixed #37117 -- Used ModelAdmin.get_queryset() for change form actions.
Refs #37105, django#12090.
1 parent 5364575 commit a53e02a

4 files changed

Lines changed: 40 additions & 16 deletions

File tree

django/contrib/admin/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2103,7 +2103,7 @@ def _changeform_view(self, request, object_id, form_url, extra_context):
21032103
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
21042104
if len(selected) != 1 or selected[0] != str(obj.pk):
21052105
raise BadRequest
2106-
queryset = self.model._default_manager.get_queryset()
2106+
queryset = self.get_queryset(request)
21072107
if response := self.response_action(
21082108
request, queryset, action_location=ActionLocation.CHANGE_FORM
21092109
):

tests/admin_views/admin.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1036,9 +1036,16 @@ class AttributeErrorRaisingAdmin(admin.ModelAdmin):
10361036
list_display = [callable_on_unknown]
10371037

10381038

1039+
@admin.action(description="Restore", location=ActionLocation.CHANGE_FORM)
1040+
def restore_filtered_manager(modeladmin, request, queryset):
1041+
queryset.update(deleted=False)
1042+
1043+
10391044
class CustomManagerAdmin(admin.ModelAdmin):
1045+
actions = [restore_filtered_manager]
1046+
10401047
def get_queryset(self, request):
1041-
return FilteredManager.objects
1048+
return FilteredManager.all_objects.all()
10421049

10431050

10441051
class MessageTestingAdmin(admin.ModelAdmin):

tests/admin_views/models.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -958,15 +958,17 @@ class DependentChild(models.Model):
958958

959959
class _Manager(models.Manager):
960960
def get_queryset(self):
961-
return super().get_queryset().filter(pk__gt=1)
961+
return super().get_queryset().filter(deleted=False)
962962

963963

964964
class FilteredManager(models.Model):
965+
deleted = models.BooleanField(default=False)
966+
965967
def __str__(self):
966968
return "PK=%s" % self.pk
967969

968-
pk_gt_1 = _Manager()
969-
objects = models.Manager()
970+
objects = _Manager() # Default manager uses non-deleted instances only.
971+
all_objects = models.Manager()
970972

971973

972974
class EmptyModelVisible(models.Model):

tests/admin_views/tests.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5728,26 +5728,41 @@ def test_history_view_custom_qs(self):
57285728
Custom querysets are considered for the admin history view.
57295729
"""
57305730
self.client.post(reverse("admin:login"), self.super_login)
5731-
FilteredManager.objects.create(pk=1)
5732-
FilteredManager.objects.create(pk=2)
5731+
active_pk = FilteredManager.all_objects.create(deleted=False).pk
5732+
deleted_pk = FilteredManager.all_objects.create(deleted=True).pk
57335733
response = self.client.get(
57345734
reverse("admin:admin_views_filteredmanager_changelist")
57355735
)
5736-
self.assertContains(response, "PK=1")
5737-
self.assertContains(response, "PK=2")
5736+
self.assertContains(response, f"PK={active_pk}")
5737+
self.assertContains(response, f"PK={deleted_pk}")
5738+
url_name = "admin:admin_views_filteredmanager_history"
57385739
self.assertEqual(
5739-
self.client.get(
5740-
reverse("admin:admin_views_filteredmanager_history", args=(1,))
5741-
).status_code,
5742-
200,
5740+
self.client.get(reverse(url_name, args=(active_pk,))).status_code, 200
57435741
)
57445742
self.assertEqual(
5745-
self.client.get(
5746-
reverse("admin:admin_views_filteredmanager_history", args=(2,))
5747-
).status_code,
5743+
self.client.get(reverse(url_name, args=(deleted_pk,))).status_code,
57485744
200,
57495745
)
57505746

5747+
def test_action_changeform_uses_modeladmin_queryset(self):
5748+
# Change form actions must receive the queryset from
5749+
# ModelAdmin.get_queryset(), not the model's default manager. Here,
5750+
# FilteredManager.objects excludes deleted rows while
5751+
# CustomManagerAdmin.get_queryset() uses all_objects. A restore action
5752+
# on a soft-deleted object must receive a non-empty queryset.
5753+
obj = FilteredManager.all_objects.create(deleted=True)
5754+
response = self.client.post(
5755+
reverse("admin:admin_views_filteredmanager_change", args=[obj.pk]),
5756+
{
5757+
"CHANGE_FORM-action": "restore_filtered_manager",
5758+
ACTION_CHECKBOX_NAME: [obj.pk],
5759+
"index": 0,
5760+
},
5761+
)
5762+
self.assertEqual(response.status_code, 302)
5763+
obj.refresh_from_db()
5764+
self.assertIs(obj.deleted, False)
5765+
57515766

57525767
@override_settings(ROOT_URLCONF="admin_views.urls")
57535768
class AdminInlineFileUploadTest(TestCase):

0 commit comments

Comments
 (0)