Simple Django Admin Bulk Editing

Django Admin is awesome but it lacks an important feature: Editing multiple items in batch. There are two django add-ons this functionality but both seem a bit out of date.

Here I would like to show an example of how simple batch editing can be added to django admin with minimal amount of code, taking advantage of django admin’s own get_form().

# admin.py
def batch_update_view(model_admin, request, queryset, field_name):

        # removes all other fields from the django admin form for a model
    def remove_fields(form):
        for field in list(form.base_fields.keys()):
            if not field == field_name:
                del form.base_fields[field]
        return form

        # the return value is the form class, not the form class instance
    form_class = remove_fields(model_admin.get_form(request))

    if request.method == 'POST':
        form = form_class()

        # the view is already called via POST from the django admin changelist
        # here we have to distinguish between just showing the intermediary view via post
        # and actually confirming the bulk edits
        # for this there is a hidden field 'form-post' in the html template
        if 'form-post' in request.POST:
            form = form_class(request.POST)
            if form.is_valid():
                for item in queryset.all():
                    setattr(item, field_name, form.cleaned_data[field_name])
                    item.save()
                model_admin.message_user(request, "Changed offer on {} items".format(queryset.count()))
                return HttpResponseRedirect(request.get_full_path())

        return render(
            request,
            'admin/batch_editing_intermediary.html',
            context={
                'form': form,
                'items': queryset,
                'media': model_admin.media,
            }
        )

The template for the intermediary view could be made model agnostic by passing a list of (id, string) tuples to the template instead of item objects:

templates/admin/batch_editing_intermediary.html

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

{% block extrahead %}{{ block.super }}
    <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
    {{ media }}
{% endblock %}

{% block content %}
    <form action="" method="post">
        {% csrf_token %}
        <p>
            {{ form }}
        </p>
        <ul>
            {% for item in items %}
                <li>
                    {{ item.id }} - {{ item.date }} - {{ item.who }} - {{ item.offer }} - {{ item.offer_work_type }}
                </li>
                {# this is required by Django Admin custom action framework #}
                <input type="hidden" name="_selected_action" value="{{ item.pk }}"/>
            {% endfor %}
        </ul>
                {# this is required by Django Admin custom action framework #}
        <input type="hidden" name="action" value="{{ request.POST.action }}"/>
        <input type="hidden" name="form-post" value="True"/>
        <input type="submit" name="apply" value="Save"/>
    </form>
{% endblock %}
class YourModelAdmin(admin.ModelAdmin):
    actions = [
        'custom_batch_editing_field1_admin_action',
        'custom_batch_editing_field2_admin_action',
    ]

    def custom_batch_editing_field1_admin_action(self, request, queryset):
        return batch_update_view(
            model_admin=self,
            request=request,
            queryset=queryset,
            # this is the name of the field on the YourModel model
            field_name='field1',
        )
    batch_update_offer.short_description = "batch update field1"