0
0
mirror of https://github.com/django/django.git synced 2024-11-21 19:09:18 +01:00

Made changes asked in review by sarahboyce:

- Action gets queryset of 1 object as param when is change view
- Add permissions test
- Add documentation
This commit is contained in:
Marcelo Galigniana 2024-07-14 23:57:54 -03:00
parent 3cef98620d
commit e1fd42c4f8
6 changed files with 97 additions and 27 deletions

View File

@ -1681,7 +1681,16 @@ class ModelAdmin(BaseModelAdmin):
if isinstance(response, HttpResponseBase):
return response
else:
return HttpResponseRedirect(request.get_full_path())
# If action was executed from change view, redirect to
# list view once finished.
return HttpResponseRedirect(
request.get_full_path()
if not change
else reverse(
"admin:%s_%s_changelist"
% (self.opts.app_label, self.opts.model_name)
)
)
else:
msg = _("No action selected.")
self.message_user(request, msg, messages.WARNING)
@ -1873,12 +1882,15 @@ class ModelAdmin(BaseModelAdmin):
if request.method == "POST":
if actions and request.POST.get("action", ""):
action_failed = False
response = self.response_action(request, obj, change=not add)
response = self.response_action(
request,
self.get_queryset(request).filter(id=object_id),
change=not add,
)
if response:
return response
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

View File

@ -86,14 +86,3 @@ def admin_actions_tag(parser, token):
return InclusionAdminNode(
parser, token, func=admin_actions, template_name="actions.html"
)
@register.tag(name="change_list_object_tools")
def change_list_object_tools_tag(parser, token):
"""Display the row of change list object tools."""
return InclusionAdminNode(
parser,
token,
func=lambda context: context,
template_name="change_list_object_tools.html",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -149,6 +149,24 @@ That code will give us an admin change list that looks something like this:
.. image:: _images/adding-actions-to-the-modeladmin.png
The actions will be available also in the detail view but the description should be
adjusted to make sense we are talking about only one object::
@admin.action(
description="Mark selected story as published",
description_plural="Mark selected stories as published",
)
def make_published(modeladmin, request, queryset):
pass
It will looks like this:
.. image:: _images/adding-actions-to-the-modeladmin-detail-view.png
.. note::
In this case ``queryset`` will be a :class:`~django.db.models.query.QuerySet` of one object.
That's really all there is to it! If you're itching to write your own actions,
you now know enough to get started. The rest of this document covers more
advanced techniques.

View File

@ -6,6 +6,7 @@ from django import forms
from django.contrib import admin
from django.contrib.admin import BooleanFieldListFilter
from django.contrib.admin.views.main import ChangeList
from django.contrib.auth import get_permission_codename
from django.contrib.auth.admin import GroupAdmin, UserAdmin
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
@ -416,11 +417,10 @@ def redirect_to(modeladmin, request, selected):
description_plural="Download selected subscriptions",
)
def download(modeladmin, request, selected):
from django.db.models.query import QuerySet
if isinstance(selected, QuerySet):
if selected.count() > 1:
buf = StringIO("This is the content of the file")
else:
selected = selected.get()
buf = StringIO(f"This is the content of the file written by {selected.name}")
return StreamingHttpResponse(FileWrapper(buf))
@ -431,8 +431,19 @@ def no_perm(modeladmin, request, selected):
return HttpResponse(content="No permission to perform this action", status=403)
@admin.action(permissions=["custom"])
def custom_action(modeladmin, request, selected):
return HttpResponse(content="OK", status=200)
class ExternalSubscriberAdmin(admin.ModelAdmin):
actions = [redirect_to, external_mail, download, no_perm]
actions = [redirect_to, external_mail, download, no_perm, custom_action]
def has_custom_permission(self, request):
"""Does the user have the custom permission?"""
opts = self.opts
codename = get_permission_codename("custom", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
class PodcastAdmin(admin.ModelAdmin):

View File

@ -3,6 +3,7 @@ import json
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.views.main import IS_POPUP_VAR
from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.core import mail
from django.db import connection
from django.template.loader import render_to_string
@ -269,7 +270,9 @@ class AdminActionsTest(TestCase):
reverse("admin:admin_views_externalsubscriber_changelist"), action_data
)
content = b"".join(list(response))
self.assertEqual(content, b"This is the content of the file")
self.assertEqual(
content, b"This is the content of the file written by John Doe"
)
self.assertEqual(response.status_code, 200)
def test_custom_function_action_no_perm_response(self):
@ -294,13 +297,12 @@ class AdminActionsTest(TestCase):
response,
"""<label>Action: <select name="action" required>
<option value="" selected>---------</option>
<option value="delete_selected">Delete selected external
subscribers</option>
<option value="delete_selected">Delete selected external subscribers</option>
<option value="redirect_to">Redirect to (Awesome action)</option>
<option value="external_mail">External mail (Another awesome
action)</option>
<option value="external_mail">External mail (Another awesome action)</option>
<option value="download">Download selected subscriptions</option>
<option value="no_perm">No permission to run</option>
<option value="custom_action">Custom action</option>
</select>""",
html=True,
)
@ -550,15 +552,25 @@ class AdminActionsPermissionTests(TestCase):
class AdminDetailActionsTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.superuser = User.objects.create_superuser(
username="super", password="secret", email="super@example.com"
cls.user = User.objects.create_user(
username="user",
password="secret",
email="user@example.com",
is_staff=True,
)
cls.s1 = ExternalSubscriber.objects.create(
name="John Doe", email="john@example.org"
)
content_type = ContentType.objects.get_for_model(ExternalSubscriber)
for permission_type in ("view", "add", "change", "delete"):
permission = Permission.objects.get(
codename=f"{permission_type}_externalsubscriber",
content_type=content_type,
)
cls.user.user_permissions.add(permission)
def setUp(self):
self.client.force_login(self.superuser)
self.client.force_login(self.user)
def test_available_detail_actions(self):
"""
@ -677,11 +689,16 @@ class AdminDetailActionsTest(TestCase):
self.assertEqual(response.status_code, 200)
def test_delete_action_in_detail_view(self):
content_type = ContentType.objects.get_for_model(Subscriber)
permission = Permission.objects.get(
codename="delete_subscriber", content_type=content_type
)
self.user.user_permissions.add(permission)
response = self.client.post(
reverse("admin:admin_views_externalsubscriber_change", args=[self.s1.pk]),
{"action": "delete_selected"},
)
self.assertTrue(response.status_code, 200)
self.assertEqual(response.status_code, 200)
self.assertContains(
response,
"Are you sure you want to delete the selected external subscriber?",
@ -696,3 +713,26 @@ class AdminDetailActionsTest(TestCase):
)
self.assertTrue(response.status_code, 200)
self.assertEqual(ExternalSubscriber.objects.count(), 0)
def test_permissions(self):
# User doesn't have the permission to run the custom action.
response = self.client.post(
reverse("admin:admin_views_externalsubscriber_change", args=[self.s1.pk]),
{"action": "custom_action"},
)
self.assertEqual(response.status_code, 302)
# Now user has the custom permission to run the custom action.
content_type = ContentType.objects.get_for_model(ExternalSubscriber)
permission = Permission.objects.create(
name="custom",
codename="custom_externalsubscriber",
content_type=content_type,
)
self.user.user_permissions.add(permission)
response = self.client.post(
reverse("admin:admin_views_externalsubscriber_change", args=[self.s1.pk]),
{"action": "custom_action"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b"OK")