| ============= |
| Admin actions |
| ============= |
| |
| .. versionadded:: 1.1 |
| |
| .. currentmodule:: django.contrib.admin |
| |
| The basic workflow of Django's admin is, in a nutshell, "select an object, |
| then change it." This works well for a majority of use cases. However, if you |
| need to make the same change to many objects at once, this workflow can be |
| quite tedious. |
| |
| In these cases, Django's admin lets you write and register "actions" -- simple |
| functions that get called with a list of objects selected on the change list |
| page. |
| |
| If you look at any change list in the admin, you'll see this feature in |
| action; Django ships with a "delete selected objects" action available to all |
| models. For example, here's the user module from Django's built-in |
| :mod:`django.contrib.auth` app: |
| |
| .. image:: _images/user_actions.png |
| |
| .. warning:: |
| |
| The "delete selected objects" action uses :meth:`QuerySet.delete() |
| <django.db.models.QuerySet.delete>` for efficiency reasons, which has an |
| important caveat: your model's ``delete()`` method will not be called. |
| |
| If you wish to override this behavior, simply write a custom action which |
| accomplishes deletion in your preferred manner -- for example, by calling |
| ``Model.delete()`` for each of the selected items. |
| |
| For more background on bulk deletion, see the documentation on :ref:`object |
| deletion <topics-db-queries-delete>`. |
| |
| Read on to find out how to add your own actions to this list. |
| |
| Writing actions |
| =============== |
| |
| The easiest way to explain actions is by example, so let's dive in. |
| |
| A common use case for admin actions is the bulk updating of a model. Imagine a |
| simple news application with an ``Article`` model:: |
| |
| from django.db import models |
| |
| STATUS_CHOICES = ( |
| ('d', 'Draft'), |
| ('p', 'Published'), |
| ('w', 'Withdrawn'), |
| ) |
| |
| class Article(models.Model): |
| title = models.CharField(max_length=100) |
| body = models.TextField() |
| status = models.CharField(max_length=1, choices=STATUS_CHOICES) |
| |
| def __unicode__(self): |
| return self.title |
| |
| A common task we might perform with a model like this is to update an |
| article's status from "draft" to "published". We could easily do this in the |
| admin one article at a time, but if we wanted to bulk-publish a group of |
| articles, it'd be tedious. So, let's write an action that lets us change an |
| article's status to "published." |
| |
| Writing action functions |
| ------------------------ |
| |
| First, we'll need to write a function that gets called when the action is |
| trigged from the admin. Action functions are just regular functions that take |
| three arguments: |
| |
| * The current :class:`ModelAdmin` |
| * An :class:`~django.http.HttpRequest` representing the current request, |
| * A :class:`~django.db.models.QuerySet` containing the set of objects |
| selected by the user. |
| |
| Our publish-these-articles function won't need the :class:`ModelAdmin` or the |
| request object, but we will use the queryset:: |
| |
| def make_published(modeladmin, request, queryset): |
| queryset.update(status='p') |
| |
| .. note:: |
| |
| For the best performance, we're using the queryset's :ref:`update method |
| <topics-db-queries-update>`. Other types of actions might need to deal |
| with each object individually; in these cases we'd just iterate over the |
| queryset:: |
| |
| for obj in queryset: |
| do_something_with(obj) |
| |
| That's actually all there is to writing an action! However, we'll take one |
| more optional-but-useful step and give the action a "nice" title in the admin. |
| By default, this action would appear in the action list as "Make published" -- |
| the function name, with underscores replaced by spaces. That's fine, but we |
| can provide a better, more human-friendly name by giving the |
| ``make_published`` function a ``short_description`` attribute:: |
| |
| def make_published(modeladmin, request, queryset): |
| queryset.update(status='p') |
| make_published.short_description = "Mark selected stories as published" |
| |
| .. note:: |
| |
| This might look familiar; the admin's ``list_display`` option uses the |
| same technique to provide human-readable descriptions for callback |
| functions registered there, too. |
| |
| Adding actions to the :class:`ModelAdmin` |
| ----------------------------------------- |
| |
| Next, we'll need to inform our :class:`ModelAdmin` of the action. This works |
| just like any other configuration option. So, the complete ``admin.py`` with |
| the action and its registration would look like:: |
| |
| from django.contrib import admin |
| from myapp.models import Article |
| |
| def make_published(modeladmin, request, queryset): |
| queryset.update(status='p') |
| make_published.short_description = "Mark selected stories as published" |
| |
| class ArticleAdmin(admin.ModelAdmin): |
| list_display = ['title', 'status'] |
| ordering = ['title'] |
| actions = [make_published] |
| |
| admin.site.register(Article, ArticleAdmin) |
| |
| That code will give us an admin change list that looks something like this: |
| |
| .. image:: _images/article_actions.png |
| |
| 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 just covers more |
| advanced techniques. |
| |
| Advanced action techniques |
| ========================== |
| |
| There's a couple of extra options and possibilities you can exploit for more |
| advanced options. |
| |
| Actions as :class:`ModelAdmin` methods |
| -------------------------------------- |
| |
| The example above shows the ``make_published`` action defined as a simple |
| function. That's perfectly fine, but it's not perfect from a code design point |
| of view: since the action is tightly coupled to the ``Article`` object, it |
| makes sense to hook the action to the ``ArticleAdmin`` object itself. |
| |
| That's easy enough to do:: |
| |
| class ArticleAdmin(admin.ModelAdmin): |
| ... |
| |
| actions = ['make_published'] |
| |
| def make_published(self, request, queryset): |
| queryset.update(status='p') |
| make_published.short_description = "Mark selected stories as published" |
| |
| Notice first that we've moved ``make_published`` into a method and renamed the |
| `modeladmin` parameter to `self`, and second that we've now put the string |
| ``'make_published'`` in ``actions`` instead of a direct function reference. This |
| tells the :class:`ModelAdmin` to look up the action as a method. |
| |
| Defining actions as methods gives the action more straightforward, idiomatic |
| access to the :class:`ModelAdmin` itself, allowing the action to call any of the |
| methods provided by the admin. |
| |
| .. _custom-admin-action: |
| |
| For example, we can use ``self`` to flash a message to the user informing her |
| that the action was successful:: |
| |
| class ArticleAdmin(admin.ModelAdmin): |
| ... |
| |
| def make_published(self, request, queryset): |
| rows_updated = queryset.update(status='p') |
| if rows_updated == 1: |
| message_bit = "1 story was" |
| else: |
| message_bit = "%s stories were" % rows_updated |
| self.message_user(request, "%s successfully marked as published." % message_bit) |
| |
| This make the action match what the admin itself does after successfully |
| performing an action: |
| |
| .. image:: _images/article_actions_message.png |
| |
| Actions that provide intermediate pages |
| --------------------------------------- |
| |
| By default, after an action is performed the user is simply redirected back |
| to the original change list page. However, some actions, especially more |
| complex ones, will need to return intermediate pages. For example, the |
| built-in delete action asks for confirmation before deleting the selected |
| objects. |
| |
| To provide an intermediary page, simply return an |
| :class:`~django.http.HttpResponse` (or subclass) from your action. For |
| example, you might write a simple export function that uses Django's |
| :doc:`serialization functions </topics/serialization>` to dump some selected |
| objects as JSON:: |
| |
| from django.http import HttpResponse |
| from django.core import serializers |
| |
| def export_as_json(modeladmin, request, queryset): |
| response = HttpResponse(mimetype="text/javascript") |
| serializers.serialize("json", queryset, stream=response) |
| return response |
| |
| Generally, something like the above isn't considered a great idea. Most of the |
| time, the best practice will be to return an |
| :class:`~django.http.HttpResponseRedirect` and redirect the user to a view |
| you've written, passing the list of selected objects in the GET query string. |
| This allows you to provide complex interaction logic on the intermediary |
| pages. For example, if you wanted to provide a more complete export function, |
| you'd want to let the user choose a format, and possibly a list of fields to |
| include in the export. The best thing to do would be to write a small action |
| that simply redirects to your custom export view:: |
| |
| from django.contrib import admin |
| from django.contrib.contenttypes.models import ContentType |
| from django.http import HttpResponseRedirect |
| |
| def export_selected_objects(modeladmin, request, queryset): |
| selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME) |
| ct = ContentType.objects.get_for_model(queryset.model) |
| return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected))) |
| |
| As you can see, the action is the simple part; all the complex logic would |
| belong in your export view. This would need to deal with objects of any type, |
| hence the business with the ``ContentType``. |
| |
| Writing this view is left as an exercise to the reader. |
| |
| .. _adminsite-actions: |
| |
| Making actions available site-wide |
| ---------------------------------- |
| |
| .. method:: AdminSite.add_action(action[, name]) |
| |
| Some actions are best if they're made available to *any* object in the admin |
| site -- the export action defined above would be a good candidate. You can |
| make an action globally available using :meth:`AdminSite.add_action()`. For |
| example:: |
| |
| from django.contrib import admin |
| |
| admin.site.add_action(export_selected_objects) |
| |
| This makes the `export_selected_objects` action globally available as an |
| action named `"export_selected_objects"`. You can explicitly give the action |
| a name -- good if you later want to programatically :ref:`remove the action |
| <disabling-admin-actions>` -- by passing a second argument to |
| :meth:`AdminSite.add_action()`:: |
| |
| admin.site.add_action(export_selected_objects, 'export_selected') |
| |
| .. _disabling-admin-actions: |
| |
| Disabling actions |
| ----------------- |
| |
| Sometimes you need to disable certain actions -- especially those |
| :ref:`registered site-wide <adminsite-actions>` -- for particular objects. |
| There's a few ways you can disable actions: |
| |
| Disabling a site-wide action |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| .. method:: AdminSite.disable_action(name) |
| |
| If you need to disable a :ref:`site-wide action <adminsite-actions>` you can |
| call :meth:`AdminSite.disable_action()`. |
| |
| For example, you can use this method to remove the built-in "delete selected |
| objects" action:: |
| |
| admin.site.disable_action('delete_selected') |
| |
| Once you've done the above, that action will no longer be available |
| site-wide. |
| |
| If, however, you need to re-enable a globally-disabled action for one |
| particular model, simply list it explicitly in your ``ModelAdmin.actions`` |
| list:: |
| |
| # Globally disable delete selected |
| admin.site.disable_action('delete_selected') |
| |
| # This ModelAdmin will not have delete_selected available |
| class SomeModelAdmin(admin.ModelAdmin): |
| actions = ['some_other_action'] |
| ... |
| |
| # This one will |
| class AnotherModelAdmin(admin.ModelAdmin): |
| actions = ['delete_selected', 'a_third_action'] |
| ... |
| |
| |
| Disabling all actions for a particular :class:`ModelAdmin` |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| If you want *no* bulk actions available for a given :class:`ModelAdmin`, simply |
| set :attr:`ModelAdmin.actions` to ``None``:: |
| |
| class MyModelAdmin(admin.ModelAdmin): |
| actions = None |
| |
| This tells the :class:`ModelAdmin` to not display or allow any actions, |
| including any :ref:`site-wide actions <adminsite-actions>`. |
| |
| Conditionally enabling or disabling actions |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| .. method:: ModelAdmin.get_actions(request) |
| |
| Finally, you can conditionally enable or disable actions on a per-request |
| (and hence per-user basis) by overriding :meth:`ModelAdmin.get_actions`. |
| |
| This returns a dictionary of actions allowed. The keys are action names, and |
| the values are ``(function, name, short_description)`` tuples. |
| |
| Most of the time you'll use this method to conditionally remove actions from |
| the list gathered by the superclass. For example, if I only wanted users |
| whose names begin with 'J' to be able to delete objects in bulk, I could do |
| the following:: |
| |
| class MyModelAdmin(admin.ModelAdmin): |
| ... |
| |
| def get_actions(self, request): |
| actions = super(MyModelAdmin, self).get_actions(request) |
| if request.user.username[0].upper() != 'J': |
| del actions['delete_selected'] |
| return actions |
| |
| |