Skip to content

Fixed #12090 -- Show admin actions on the edit pages too #16012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

mgaligniana
Copy link
Member

@mgaligniana mgaligniana commented Aug 30, 2022

Hi!

image

  • Tests added:
    • test_available_detail_actions -> Actions form displayed (with singular description)
    • test_available_list_actions -> Actions with plural description
    • test_detail_actions_are_not_present_in_add_view -> Actions form is not displayed in "Add" view
    • test_update_external_subscriber_without_select_an_action -> Select an action is not required
    • test_external_subscriber_is_not_updated_when_select_an_action -> If I edit the object and then execute an action, the object is not changed/updated
    • test_custom_function_action_streaming_response_change_view -> Happy path
    • test_action_with_permissions -> Can't run action without permissions
    • test_redirect_after_an_action -> Redirects to changelist view after ran an action
    • ...

@mgaligniana mgaligniana force-pushed the ticket_12090 branch 3 times, most recently from 464bbf2 to 07a2b76 Compare August 31, 2022 02:31
@mgaligniana mgaligniana marked this pull request as ready for review September 5, 2022 04:02
@djm
Copy link

djm commented Sep 5, 2022

Another point that is worth mentioning: Django Admin Inline Actions have the description attribute which can be used to give a more friendly human-readable name to the action.

e.g

@admin.action(description='Mark selected stories as published')
def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

The problem this poses is that this attribute was always designed to be used as part of an action against multiple instances of a given model and thus over the years people (me..) would have used names written in the plural-form like "Regenerate contracts", or "Refresh indexes".

Therefore using those names in the context of the edit page would cause them to not make sense because it's a singular object being "actioned" rather than multiple. Others would have followed this naming pattern too, especially as the documentation example uses the wording "Mark selected stories as published" which would not work in singular form.

In an ideal world, inline actions would need two variables to do a good job of this - as Django does in many other places (verbose_name vs verbose_name_plural) etc. But this poses another problem in that the known-plural version is currently called description so it's not as simple as adding a new description_plural parameter.

@felixxm
Copy link
Member

felixxm commented Jun 21, 2023

@mgaligniana Do you have time to keep working on this?

@mgaligniana
Copy link
Member Author

@felixxm Yes! This week I'm going to add some progress! 🙏 Sorry, I've been busy these months.

@mgaligniana mgaligniana force-pushed the ticket_12090 branch 3 times, most recently from 1108397 to 4a8cd47 Compare June 27, 2023 16:05
@mgaligniana mgaligniana marked this pull request as draft June 27, 2023 16:12
@mgaligniana mgaligniana force-pushed the ticket_12090 branch 3 times, most recently from 5baf1fb to dd95290 Compare July 9, 2023 10:24
@mgaligniana mgaligniana force-pushed the ticket_12090 branch 3 times, most recently from 047df76 to 8a1db0c Compare July 9, 2023 17:15
@mgaligniana
Copy link
Member Author

mgaligniana commented Jul 9, 2023

Another point that is worth mentioning: Django Admin Inline Actions have the description attribute which can be used to give a more friendly human-readable name to the action.

e.g

@admin.action(description='Mark selected stories as published')
def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

The problem this poses is that this attribute was always designed to be used as part of an action against multiple instances of a given model and thus over the years people (me..) would have used names written in the plural-form like "Regenerate contracts", or "Refresh indexes".

Therefore using those names in the context of the edit page would cause them to not make sense because it's a singular object being "actioned" rather than multiple. Others would have followed this naming pattern too, especially as the documentation example uses the wording "Mark selected stories as published" which would not work in singular form.

In an ideal world, inline actions would need two variables to do a good job of this - as Django does in many other places (verbose_name vs verbose_name_plural) etc. But this poses another problem in that the known-plural version is currently called description so it's not as simple as adding a new description_plural parameter.

Has been passed almost 1 year! Sorry djm, but finally I've returned to work on this! 🙏

As I defined other different variable for detail view actions: detail_actions. The description could be written in singular way! That solve the problem?

@mgaligniana mgaligniana marked this pull request as ready for review July 9, 2023 17:27
@mgaligniana mgaligniana force-pushed the ticket_12090 branch 2 times, most recently from a255676 to 0022785 Compare July 25, 2023 12:13
@mgaligniana mgaligniana requested a review from djm August 2, 2023 13:20
Copy link
Contributor

@nessita nessita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some initial thoughts.

@mgaligniana mgaligniana force-pushed the ticket_12090 branch 2 times, most recently from d626280 to 8ae5dbe Compare August 21, 2023 15:04
@mgaligniana mgaligniana force-pushed the ticket_12090 branch 2 times, most recently from 3cfe703 to c23c3a4 Compare August 21, 2023 15:59
@mgaligniana mgaligniana force-pushed the ticket_12090 branch 4 times, most recently from 9eb1d82 to f7178ea Compare May 5, 2025 03:08
@mgaligniana
Copy link
Member Author

I've applied all the comments! + added couple of tests (I updated the PR description with more details)

Let me know any test or change to add! 💪 I will not disappear again and I will try to finish it!

@knyghty knyghty requested review from a team and removed request for djm and smiling-watermelon May 7, 2025 11:29
@mgaligniana
Copy link
Member Author

🎗️ I've noticed we tagged as selenium but I haven't added tests for that. If it is required, what test could I add?

@knyghty
Copy link
Member

knyghty commented May 20, 2025

@mgaligniana the label is just so the selenium tests run. With that in mind I've also added the screenshots label.

I'm not sure if a selenium test is needed or not. I guess it would be nice to write a test that selects an action and runs it, though I'm not sure if it's just duplicating the tests that are already written.

I will try to do an accessibility review today as I'm interested in this getting landed :)

@sarahboyce
Copy link
Contributor

Exactly ☝️ as this changes admin code, we want all admin tests to run and a number of these are selenium tests

Copy link
Member

@knyghty knyghty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a quick scan of the code but not too deep. I left some comments.

From an accessibility point of view I see no issues here because we're just re-using what already exists.

ordering. Otherwise don't specify the queryset, let the field decide
ordering. Otherwise don't specify the queryset, let the field decide
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we usually try to avoid unrelated cleanup in PRs but I'll let the fellows be the judge, I don't really know.

Comment on lines 1857 to 1858
selected_action = request.POST.get("action", "")
if actions and selected_action:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could use walrus operator here?

Copy link
Member Author

@mgaligniana mgaligniana May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it's used only for the condition I think is better don't declare a variable and use it directly in the if. Thank you!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, brain fog, just confused that I saw it twice.

Comment on lines 154 to 155
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::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reads better like this to my native English brain:

"The actions will also be available in the detail view but the description should be adjusted to show we are only talking about a single object."

def make_published(modeladmin, request, queryset):
pass

It will looks like this:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
It will looks like this:
It will look like this:

@sarahboyce sarahboyce force-pushed the ticket_12090 branch 2 times, most recently from b81d16d to ad75876 Compare July 24, 2025 08:27
Copy link
Contributor

@sarahboyce sarahboyce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the updates @mgaligniana
We need a release note in 6.0 and I have a question about the API design 😁 but I think we're close

description to show we are only talking about a single object::

@admin.action(
description="Mark selected story as published",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description="Mark selected story as published",
description="Mark story as published",

This would need an update to the image
I also think the image dropdown format is inconsistent with other dropdowns on the page (maybe a different operating system or browser) @nessita might be able to help here

@@ -14,7 +14,8 @@

@action(
permissions=["delete"],
description=gettext_lazy("Delete selected %(verbose_name_plural)s"),
description=gettext_lazy("Delete"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description=gettext_lazy("Delete"),
description=gettext_lazy("Delete %(verbose_name)s"),

Comment on lines 1657 to 1667
# If action was executed from change view, redirect to
# list view once finished.
is_change_view = request.resolver_match.url_name.endswith("change")
return HttpResponseRedirect(
reverse(
"admin:%s_%s_changelist"
% (self.opts.app_label, self.opts.model_name)
)
if is_change_view
else request.get_full_path()
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I understand this is a nicer flow for the delete action which is provided by default, however I wonder if this is nicer for the other custom actions 🤔
The delete action on the single view is not that useful (as we already have a delete button when you have a delete permission) so I would question if we even should list it

Do you think we should have a boolean like changelist_only that can be set on the action? Perhaps defaults to True so folks need to opt in to the feature and actions like the delete case might not want to be on a singular level?

Copy link
Member Author

@mgaligniana mgaligniana Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I understand this is a nicer flow for the delete action which is provided by default, however I wonder if this is nicer for the other custom actions 🤔

Makes sense. In the last commit I've deleted the logic I had added redirecting to the list view + I've added a success message.

The delete action on the single view is not that useful (as we already have a delete button when you have a delete permission) so I would question if we even should list it
Do you think we should have a boolean like changelist_only that can be set on the action? Perhaps defaults to True so folks need to opt in to the feature and actions like the delete case might not want to be on a singular level?

Brilliant! I've added that changelist_only option. I did it in a new commit so you can check easily the new changes + new test

@mgaligniana
Copy link
Member Author

I always forget the release notes! Arggh! 😆

Thank you Sarah! I'll read and apply the comments during this week, thank you!

permissions=None,
description=None,
description_plural=None,
changelist_only=True,
Copy link
Member Author

@mgaligniana mgaligniana Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎗️ I've added some documentation for this new parameter. But I would need help!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know changelist_only was my suggestion, but I'm wondering if show_on_change_form would be a better name with a default of False. I think it's nicer to enable something than disable something

@@ -1648,23 +1652,16 @@ def response_action(self, request, queryset):

response = func(self, request, queryset)

self.message_user(request, "Action executed successfully", messages.SUCCESS)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎗️ For sure it could be better message or more detailed. I'll wait for comments!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would be tempted to remove and allow folks to write their own message in their custom actions

@redraw

This comment was marked as spam.

@edvm
Copy link

edvm commented Jul 30, 2025

This is so cool, thanks for working on this @mgaligniana , I've been waiting for this feature idk... since 2023? one more time, thank you!!! 🙌 🙌 🙌

Copy link
Contributor

@sarahboyce sarahboyce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need a release note
I made a suggestion to update changelist_only to show_on_change_form, we might want to get other peoples opinions on a range of names

Possible release notes in 6.0 admin section could be:

* The new ``show_on_change_form`` argument of
  :func:`~django.contrib.admin.action` allows the action to be available on the
  admin change form view. Defaults to ```False``.

* The new ``description_plural`` argument of
  :func:`~django.contrib.admin.action` specifies a human-readable description
  for actions applied to multiple objects on the admin changelist. Defaults to
  the ``description`` value. This is useful when ``show_on_change_form`` is
  set to ``True``, where ``description`` is then used for a single object on
  the change form.

Comment on lines -7 to +15
description='Mark selected stories as published',
description='Mark story as published',
description_plural='Mark selected stories as published',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have changelist_only, we either should add this to the example here or revert the current changes to the docstring as having description and description_plural together only makes sense when changelist_only=False

I think we can revert the changes to the docstring as it is just showing that two things are equivalent and I think this is sufficiently demonstrated in the existing example

@@ -1648,23 +1652,16 @@ def response_action(self, request, queryset):

response = func(self, request, queryset)

self.message_user(request, "Action executed successfully", messages.SUCCESS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would be tempted to remove and allow folks to write their own message in their custom actions

permissions=None,
description=None,
description_plural=None,
changelist_only=True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know changelist_only was my suggestion, but I'm wondering if show_on_change_form would be a better name with a default of False. I think it's nicer to enable something than disable something

Comment on lines +152 to +168
Actions are also available in the admin object detail page. We can adjust the
description to show we are only talking about a single object::

@admin.action(
description="Mark story as published",
description_plural="Mark selected stories as published",
)
def make_published(modeladmin, request, queryset):
pass

This will give us an admin object detail page that looks like this:

.. image:: _images/adding-actions-to-the-modeladmin-detail-view.png

.. versionchanged:: 6.0

Actions were added to the admin object detail page by setting ``changelist_only`` to False.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(note I am running with the show_on_change_form suggestion)

Suggested change
Actions are also available in the admin object detail page. We can adjust the
description to show we are only talking about a single object::
@admin.action(
description="Mark story as published",
description_plural="Mark selected stories as published",
)
def make_published(modeladmin, request, queryset):
pass
This will give us an admin object detail page that looks like this:
.. image:: _images/adding-actions-to-the-modeladmin-detail-view.png
.. versionchanged:: 6.0
Actions were added to the admin object detail page by setting ``changelist_only`` to False.
We can also make an action available in the admin change form by setting
`show_on_change_form=True``. We should adjust the descriptions for the single
object and multiple object cases::
@admin.action(
description="Mark story as published",
description_plural="Mark selected stories as published",
show_on_change_form=True,
)
def make_published(modeladmin, request, queryset):
pass
This will give us an admin change form page that looks like this:
.. image:: _images/adding-actions-to-the-modeladmin-detail-view.png
.. versionchanged:: 6.0
The ``show_on_change_form`` and ``description_plural`` arguments of
:func:`~django.contrib.admin.action` were added.

@@ -434,7 +452,7 @@ For example::
The ``action`` decorator
========================

.. function:: action(*, permissions=None, description=None)
.. function:: action(*, permissions=None, description=None, description_plural=None, changelist_only=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the bottom of this, I think it's worth describing the new parameters a bit more and we need a versionchanged note:

    The ``show_on_change_form`` argument controls where the action is available
    in the admin interface. When ``show_on_change_form`` is ``False``, the
    action is only available on the admin changelist page. When
    ``show_on_change_form`` is ``True``, the action is available on the
    admin changelist and the admin change form. Defaults to ``True``.

    When ``show_on_change_form`` is ``True``, ``description`` is used for the
    admin change form, and ``description_plural`` is used for the admin
    changelist page. If ``description_plural`` is not provided, it falls back
    to the ``description`` value.

    .. versionchanged:: 6.0

        The ``show_on_change_form`` and ``description_plural`` arguments were
        added.

This suggestion assumes we change to using show_on_change_form

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Antoliny0919 (this PR needs rebasing) but I wonder if you can help update this screenshot as the dropdown here is inconsistent with other screenshots

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
screenshots 🖼️ selenium Apply to have Selenium tests run on a PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants