Skip to content

Instantly share code, notes, and snippets.

@rafen
Last active January 28, 2024 10:32
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rafen/eff7adae38903eee76600cff40b8b659 to your computer and use it in GitHub Desktop.
Save rafen/eff7adae38903eee76600cff40b8b659 to your computer and use it in GitHub Desktop.
Django ExtendedActionsMixin allows ModelAdmin classes to execute actions with empty queryset and filtered queryset

ExtendedActionsMixin

This is a Django mixin that allows ModelAdmin classes to execute actions without selecting any objects on the change list of the Django Admin.

To implement it you need to include the Mixin as usual (see https://docs.djangoproject.com/en/1.10/topics/class-based-views/mixins/) and define a class attribute called extended_actions containing a list of string with the name of the actions that you want to be exectued with empty queryset.

If in the action you want to use the same queryset that the user is seeing, you can use the get_filtered_queryset method also provided by the mixin

Example:

@admin.register(Contact)
class ContactAdmin(ExtendedActionsMixin, admin.ModelAdmin):
    list_display = ('name', 'country', 'state')
    actions = ('export',)
    extended_actions = ('export',)
    
    def export(self, request, queryset):
        if not queryset:
            # if not queryset use the queryset filtered by the URL parameters
            queryset = self.get_filtered_queryset(request)

        # As usual do something with the queryset
class ExtendedActionsMixin:
# actions that can be executed with no items selected on the admin change list.
# The filtered queryset displayed to the user will be used instead
extended_actions = []
def changelist_view(self, request, extra_context=None):
# if a extended action is called and there's no checkbox selected, select one with
# invalid id, to get an empty queryset
if "action" in request.POST and request.POST["action"] in self.extended_actions:
if not request.POST.getlist(admin.helpers.ACTION_CHECKBOX_NAME):
post = request.POST.copy()
post.update({admin.helpers.ACTION_CHECKBOX_NAME: 0})
request._set_post(post) # pylint:disable=protected-access
return super().changelist_view(request, extra_context)
def get_changelist_instance(self, request):
"""
Returns a simple ChangeList view instance of the current ModelView.
(It's a simple instance since we don't populate the actions and list filter
as expected since those are not used by this class)
"""
list_display = self.get_list_display(request)
list_display_links = self.get_list_display_links(request, list_display)
list_filter = self.get_list_filter(request)
search_fields = self.get_search_fields(request)
list_select_related = self.get_list_select_related(request)
change_list = self.get_changelist(request)
return change_list(
request,
self.model,
list_display,
list_display_links,
list_filter,
self.date_hierarchy,
search_fields,
list_select_related,
self.list_per_page,
self.list_max_show_all,
self.list_editable,
self,
self.sortable_by,
)
def get_filtered_queryset(self, request):
"""
Returns a queryset filtered by the URLs parameters
"""
change_list = self.get_changelist_instance(request)
return change_list.get_queryset(request)
@bastiaan85
Copy link

Thank you for supplying this snippet. One question though: why even bother implementing get_changelist_instance here? It leaves out the checkboxes, so regular actions are rendered useless. After removing it, the mixin just works for both classic actions and the 'extended' ones (as expected). Python 3.8.11 Django 3.2.6.

@caramdache
Copy link

One question though: why even bother implementing get_changelist_instance here? It leaves out the checkboxes, so regular actions are rendered useless. After removing it, the mixin just works for both classic actions and the 'extended' ones (as expected). Python 3.8.11 Django 3.2.6.

+1 with Python 3.0 and Django 4.0.3.

@776166
Copy link

776166 commented Dec 2, 2022

Error:
TypeError: ChangeList.__init__() missing 1 required positional argument: 'search_help_text'

Here:
return change_list(

Python3.10, Django==4.1.3

@776166
Copy link

776166 commented Dec 2, 2022

        return change_list(
            request,
            self.model,
            list_display,
            list_display_links,
            list_filter,
            self.date_hierarchy,
            search_fields,
            list_select_related,
            self.list_per_page,
            self.list_max_show_all,
            self.list_editable,
            self,
            self.sortable_by,
            self.search_help_text,  ### <- Here the new line
        )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment