diff options
Diffstat (limited to 'parts/django/docs/ref/contrib/admin/actions.txt')
-rw-r--r-- | parts/django/docs/ref/contrib/admin/actions.txt | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/parts/django/docs/ref/contrib/admin/actions.txt b/parts/django/docs/ref/contrib/admin/actions.txt new file mode 100644 index 0000000..0fab59e --- /dev/null +++ b/parts/django/docs/ref/contrib/admin/actions.txt @@ -0,0 +1,351 @@ +============= +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 + + |