diff options
| author | Jacob Kaplan-Moss <jacob@jacobian.org> | 2009-03-23 20:22:56 +0000 |
|---|---|---|
| committer | Jacob Kaplan-Moss <jacob@jacobian.org> | 2009-03-23 20:22:56 +0000 |
| commit | 44f3080226888eb709cc6e027321647964ebe64e (patch) | |
| tree | 879d62de3f7c3ce91956a1b26d4f0f6dd41bc1a8 /docs | |
| parent | 4e2533436ef08ce81516e4cfd47ccbb1dddac5f8 (diff) | |
Fixed #10505: added support for bulk admin actions, including a globally-available "delete selected" action. See the documentation for details.
This work started life as Brian Beck's "django-batchadmin." It was rewritten for inclusion in Django by Alex Gaynor, Jannis Leidel (jezdez), and Martin Mahner (bartTC). Thanks, guys!
git-svn-id: http://code.djangoproject.com/svn/django/trunk@10121 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/index.txt | 2 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/_images/article_actions.png | bin | 0 -> 35643 bytes | |||
| -rw-r--r-- | docs/ref/contrib/admin/_images/article_actions_message.png | bin | 0 -> 21812 bytes | |||
| -rw-r--r-- | docs/ref/contrib/admin/_images/flatfiles_admin.png (renamed from docs/ref/contrib/_images/flatfiles_admin.png) | bin | 42243 -> 42243 bytes | |||
| -rw-r--r-- | docs/ref/contrib/admin/_images/user_actions.png | bin | 0 -> 25950 bytes | |||
| -rw-r--r-- | docs/ref/contrib/admin/_images/users_changelist.png (renamed from docs/ref/contrib/_images/users_changelist.png) | bin | 59355 -> 59355 bytes | |||
| -rw-r--r-- | docs/ref/contrib/admin/actions.txt | 239 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/index.txt (renamed from docs/ref/contrib/admin.txt) | 23 | ||||
| -rw-r--r-- | docs/ref/contrib/index.txt | 2 |
9 files changed, 262 insertions, 4 deletions
diff --git a/docs/index.txt b/docs/index.txt index 9e96422ccb..4b295558a8 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -78,7 +78,7 @@ The development process Other batteries included ======================== - * :ref:`Admin site <ref-contrib-admin>` + * :ref:`Admin site <ref-contrib-admin>` | :ref:`Admin actions <ref-contrib-admin-actions>` * :ref:`Authentication <topics-auth>` * :ref:`Cache system <topics-cache>` * :ref:`Conditional content processing <topics-conditional-processing>` diff --git a/docs/ref/contrib/admin/_images/article_actions.png b/docs/ref/contrib/admin/_images/article_actions.png Binary files differnew file mode 100644 index 0000000000..254a8ad557 --- /dev/null +++ b/docs/ref/contrib/admin/_images/article_actions.png diff --git a/docs/ref/contrib/admin/_images/article_actions_message.png b/docs/ref/contrib/admin/_images/article_actions_message.png Binary files differnew file mode 100644 index 0000000000..31c5637469 --- /dev/null +++ b/docs/ref/contrib/admin/_images/article_actions_message.png diff --git a/docs/ref/contrib/_images/flatfiles_admin.png b/docs/ref/contrib/admin/_images/flatfiles_admin.png Binary files differindex 391a629348..391a629348 100644 --- a/docs/ref/contrib/_images/flatfiles_admin.png +++ b/docs/ref/contrib/admin/_images/flatfiles_admin.png diff --git a/docs/ref/contrib/admin/_images/user_actions.png b/docs/ref/contrib/admin/_images/user_actions.png Binary files differnew file mode 100644 index 0000000000..9a62f70685 --- /dev/null +++ b/docs/ref/contrib/admin/_images/user_actions.png diff --git a/docs/ref/contrib/_images/users_changelist.png b/docs/ref/contrib/admin/_images/users_changelist.png Binary files differindex d5f9c016cc..d5f9c016cc 100644 --- a/docs/ref/contrib/_images/users_changelist.png +++ b/docs/ref/contrib/admin/_images/users_changelist.png diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt new file mode 100644 index 0000000000..4969e97a99 --- /dev/null +++ b/docs/ref/contrib/admin/actions.txt @@ -0,0 +1,239 @@ +.. _ref-contrib-admin-actions: + +============= +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 + +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 +two arguments: an :class:`~django.http.HttpRequest` representing the current +request, and a :class:`~django.db.models.QuerySet` containing the set of +objects selected by the user. Our publish-these-articles function won't need +the request object, but we will use the queryset:: + + def make_published(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(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(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 (remembering to +add the ``self`` argument!), 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 is especially nice because it gives the action +access to the :class:`ModelAdmin` itself, allowing the action to call any of +the methods provided by the admin. + +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 +the 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 +:ref:`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(request, queryset): + response = HttpResponse(mimetype="text/javascript") + serialize.serialize(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(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. + +Making actions available globally +--------------------------------- + +Some actions are best if they're made available to *any* object in the admin +-- the export action defined above would be a good candidate. You can make an +action globally available using :meth:`AdminSite.add_action()`:: + + from django.contrib import admin + + admin.site.add_action(export_selected_objects) + diff --git a/docs/ref/contrib/admin.txt b/docs/ref/contrib/admin/index.txt index 1813a191f6..1bcd31bc01 100644 --- a/docs/ref/contrib/admin.txt +++ b/docs/ref/contrib/admin/index.txt @@ -38,6 +38,14 @@ There are five steps in activating the Django admin site: ``ModelAdmin`` classes. 5. Hook the ``AdminSite`` instance into your URLconf. + +Other topics +------------ + +.. toctree:: + :maxdepth: 1 + + actions ``ModelAdmin`` objects ====================== @@ -664,6 +672,19 @@ The value is another dictionary; these arguments will be passed to that have ``raw_id_fields`` or ``radio_fields`` set. That's because ``raw_id_fields`` and ``radio_fields`` imply custom widgets of their own. +``actions`` +~~~~~~~~~~~ + +A list of actions to make available on the change list page. See +:ref:`ref-contrib-admin-actions` for details. + +``actions_on_top``, ``actions_on_buttom`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Controls where on the page the actions bar appears. By default, the admin +changelist displays actions at the top of the page (``actions_on_top = True; +actions_on_bottom = False``). + ``ModelAdmin`` methods ---------------------- @@ -1138,7 +1159,6 @@ or add anything you like. Then, simply create an instance of your Python class), and register your models and ``ModelAdmin`` subclasses with it instead of using the default. - Hooking ``AdminSite`` instances into your URLconf ------------------------------------------------- @@ -1177,7 +1197,6 @@ There is really no need to use autodiscover when using your own ``AdminSite`` instance since you will likely be importing all the per-app admin.py modules in your ``myproject.admin`` module. - Multiple admin sites in the same URLconf ---------------------------------------- diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt index 82a895523c..4f401d6836 100644 --- a/docs/ref/contrib/index.txt +++ b/docs/ref/contrib/index.txt @@ -24,7 +24,7 @@ those packages have. .. toctree:: :maxdepth: 1 - admin + admin/index auth comments/index contenttypes |
