Admin actions in Django with custom form

Admin actions are useful for actions on set of objects. Django ships with a 'delete selected objects' action available to all models.

For example, you have Genres and Movies. You want to change genres for selected movies at once.

Action with intermediate page

Exmaple of

class Genre(models.Model):
    title = models.CharField(u'Title', max_length=100)

    def __unicode__(self):
        return self.title

class Movie(models.Model):
    title = models.CharField(u'Title', max_length=200)
    genre = models.ForeignKey(Genre, verbose_name=u'Genre')
    score = models.IntegerField(u'Score')

    def __unicode__(self):
        return self.title

Example of

from movies.models import *

class GenreForm(forms.Form):
    genre = forms.ModelChoiceField(queryset=Genre.objects.all())

Example of

from django.contrib import messages
from django.shortcuts import render

from movies.forms import *

class MovieAdmin(admin.ModelAdmin):
    list_display = ('title', 'genre',)
    actions = ['set_genre_action']

    def set_genre_action(self, request, queryset):
        if 'do_action' in request.POST:
            form = GenreForm(request.POST)
            if form.is_valid():
                genre = form.cleaned_data['genre']
                updated = queryset.update(genre=genre)
                messages.success(request, '{0} movies were updated'.format(updated))
            form = GenreForm()

        return render(request, 'admin/movies/action_genre.html',
            {'title': u'Choose genre',
             'objects': queryset,
             'form': form})
    set_genre_action.short_description = u'Update genre of selected movies', MovieAdmin)    

Example of template

{% extends "admin/base_site.html" %}

{% block content %}
    <form action="" method="post">

        {% csrf_token %}
        <input type="hidden" name="action" value="set_genre_action">
        <input type="hidden" name="do_action" value="yes">

            {{ form.genre }}
            <input type="submit" class="default" style="float: none" value="Change">
            {{ form.genre.errors }}

        <h2>Change genre for next movies</h2>

            {% for object in objects %}
                    <a href="{{ }}/">{{ object.title }}</a> - {{ object.genre }}
                    <input type="hidden" name="_selected_action" value="{{ }}">
            {% endfor %}

{% endblock %}

_selected_action contains id of objects for queryset.

Action with input field in change list

Model for this example is the same.

Example of

from django.contrib.admin.helpers import ActionForm

class UpdateScoreForm(ActionForm):
    score = forms.IntegerField()

Example of

from django.contrib import messages
from movies.forms import *

class MovieAdmin(admin.ModelAdmin):
    action_form = UpdateScoreForm
    list_display = ('title', 'genre',)
    actions = ['set_score_action']

    def set_score_action(self, request, queryset):
        score = int(request.POST['score'])
        messages.success(request, '{0} movies were updated'.format(queryset.count()))
    set_score_action.short_description = u'Update score of selected movies', MovieAdmin)    
comments powered by Disqus