mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00: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:
parent
3cef98620d
commit
e1fd42c4f8
@ -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
|
||||
|
@ -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 |
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user