Djangoで、選択したデータを一括削除

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Django
2018年11月26日0:59に更新(約17日前)
2018年11月25日20:58に作成(約18日前)

概要

データを一覧等で表示し、選択したデータを一括で削除したいというケースはよくあります。

admin管理画面でもそのような機能はありますね。
admin管理画面での選択したデータを一括削除

いくつかのアプローチがあると思いますが、2つ紹介します。

filter()で絞り込んでdelete()

QuerySetとモデルインスタンスには、delete()メソッドがあり、これで削除することができます(あまりないですが、全てのデータを削除したいならばPost.objects.all().delete()とかもできます)。

filter()はQuerySetを返しますので、これにdelete()をつければ削除ができますね。今回紹介するのはこのアプローチです。

テンプレートの例です。

    <form action="" method="POST">
        {% for post in post_list %}
            <p>記事タイトル:{{ post.title }} - 削除: <input type="checkbox" name="delete" value="{{ post.pk }}"></p>
        {% endfor %}
        {% csrf_token %}
        <button type="submit">削除</button>
    </form>

form要素で囲み、type="submit"な削除ボタンを用意します。見た目としては、以下のような感じです。
各データの横に削除チェックがつく

中身は一般的な一覧表示処理をしていますが、<input type="checkbox" name="delete" value="{{ post.pk }}">として各データ毎にチェックボックスを用意します。

チェックボックスはラジオボタンと違い、複数の値を送信することができます。今回であれば、チェックされたデータのpkをまとめてサーバーに送信し、そのpkを基に一括削除を行います。注意点として、チェックボックスは全てnameを同じにしましょう。別のnameにしてしまうと、違う種類のチェックボックスと判断されます

そして、ビューです。

from django.shortcuts import redirect
from django.views import generic
from .models import Post


class PostIndex(generic.ListView):
    model = Post

    def post(self, request):
        post_pks = request.POST.getlist('delete')  # <input type="checkbox" name="delete"のnameに対応
        Post.objects.filter(pk__in=post_pks).delete()
        return redirect('app:post_list')  # 一覧ページにリダイレクト

今回、面倒だったのでビューは1つだけです。もちろん、削除処理を別のビューで行うのもOKです。クラスベースビューにおけるpostメソッドは、POSTメソッドでのデータ送信時に呼ばれます。

post_pks = request.POST.getlist('delete')は、['1', '2', '3']のようにpkの詰まったリストを取得できます。よく使うのはrequest.POST['name']とかrequest.POST.get('email')といった書き方ですが、これは単一の値しか取得できません。今回のようにあるnameが複数の値を送信してくるかもしれない場合は、getlist()を使います。

filterの引数ですが、pk__in=post_pkspkがpost_pksのどれかという意味になります。pk__in=[1, 2, 3]ならばpkが1か2か3のものを取得しますし、title__in=['hello', 'hi']ならば、タイトルがhelloかhiのもの、という指定になります。

モデルフォームセットを使う

Djangoで、フォームセットを使うシリーズで説明していますが、モデルフォームセットはcan_deleteという引数を使うことで削除チェックボタンを作れます。

モデルフォームセットは一括でのデータ作成・編集・削除に特化した機能ですが、やろうと思えばデータの一覧表示にも使えます。一覧表示がメインだけど、削除チェックボックスや幾つかのフィールドはその場で変更したいなーと思ったらこちらの方法が便利かもしれません。

まずはビューです。

from django import forms
from django.shortcuts import redirect, render
from .models import Post

PostFormSet = forms.modelformset_factory(Post, fields='__all__', can_delete=True, extra=0)


def post_index(request):
    formset = PostFormSet(request.POST or None)
    if request.method == 'POST' and formset.is_valid():
        formset.save()
        return redirect('app:post_list')

    context = {
        'formset': formset,
    }

    return render(request, 'app/post_list.html', context)

面倒だったので、PostFormSetはviews.pyで定義しました。場合によってはビューの中で定義しますし、そうでなければforms.pyに定義するのが一般的です。処理自体は、一般的なモデルフォームセットです。

modelformset_factory関数の引数、can_delete=Trueで削除チェックボックスが作れるようになり、extra=0は新規の作成フォームを作りません。あくまで今回は、モデルフォームセットを一覧表示+削除チェックボックスのために使うので、データの新規作成フォームは要らないですね。

そしてテンプレートです。

    <form action="" method="POST">
        {{ formset.management_form }}
        {% for form in formset %}
            {% for field in form.hidden_fields %}{{ field }}{% endfor %}
            {{ form.title.as_hidden }}
            <p>記事タイトル:{{ form.instance.title }} - {{ form.DELETE }}</p>
        {% endfor %}
        {% csrf_token %}
        <button type="submit">削除</button>
    </form>

一般的なモデルフォームセットと違うのは、まず{{ form.title.as_hidden }}で記事タイトルの入力欄を見えなくします。そして案外忘れがちですが、新規作成フォームでなければ{{ form.instance}}としてそのフォームが紐づいているモデルインスタンスにアクセスできます。なので、{{ form.instance.title }}とすれば記事のタイトルだけを表示できるのです。

{{ form.DELETE }}は、モデルフォームセットに用意されている削除チェックボックスです。

Djangoで、モデルフォームセット+ページングでは、フォームセットのページングや絞り込み検索についても軽く説明しています。

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする