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 models.py

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 form.py

from movies.models import *

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

Example of admin.py

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))
                return
        else:
            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'
admin.site.register(Movie, 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">

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

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

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

    </form>
{% 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 form.py

from django.contrib.admin.helpers import ActionForm

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

Example of admin.py

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'])
        queryset.update(score=score)    
        messages.success(request, '{0} movies were updated'.format(queryset.count()))
    set_score_action.short_description = u'Update score of selected movies'    
admin.site.register(Movie, MovieAdmin)    
comments powered by Disqus