Django
autocomplete
python3

Django admin のフォームをサクッとオートコンプリートにする

Django admin は、外部キー参照のフォームをデフォルトですべての選択肢を含む <select></select> 要素を表示します。選択肢が数件程度であれば問題ありませんが、20件を超えたあたりで UI として使いづらくなり、5000件を超えてくると動作が遅くなりパフォーマンス的に使い物にならなくなります:frowning2:

image.png

この記事では、そんな外部キー参照のフォームに Ajax を使ったオートコンプリートを導入する方法を紹介します。
Python は 2.7, 3.4 をサポート (3.6 でも使えました)
Django は 1.8+ をサポートしています。 (2.0 でも使えました)

image.png

django-autocomplete-light をインストール

pip でインストールできます。

pip install django-autocomplete-light

settings.py への追記は INSTALLED_APPS のみです。
'dal', 'dal_select2','django.contrib.admin', より前に追加してください。

settings.py
INSTALLED_APPS = [
    'dal',
    'dal_select2',
    'django.contrib.admin',
    ...
]

モデルを用意する

モデルはすでにあると思いますが、外部キーで参照される側(選択肢)のモデルに __str__(self) が実装されている必要があります。
これはデフォルトの <select></select> でも同様ですが、文字列化メソッドが実装されていないとこのように表示されてしまいます。

image.png

今回の紹介では、例として以下のモデル定義を使います。

models.py
class Municipality(models.Model):

    code = models.CharField(max_length=6, unique=True)
    name = models.CharField(max_length=20)
    kana = models.CharField(max_length=60)

    def __str__(self):
        return "{}({})".format(self.name, self.code)


class UserProfile(models.Model):
    municipality = models.ForeignKey(Municipality, related_name='profiles', on_delete=models.DO_NOTHING, verbose_name='市区町村')

オートコンプリートのための API を用意する

Ajax で呼び出す API を用意します。
追加先のモジュールはどこでも大丈夫ですが、 urls.py から参照することに注意してください。

views.py
from dal import autocomplete
from .models import Municipality

# Create your views here.


class MunicipalityAutoComplete(autocomplete.Select2QuerySetView):
    def get_queryset(self):
        # ログインしていない接続元を遮断することを忘れないように
        if not self.request.user.is_authenticated:
            return Municipality.objects.none()

        qs = Municipality.objects.all()
        if self.q:
            qs = qs.filter(name__istartswith=self.q)

        return qs

self.q はオートコンプリートの入力値です。これをクエリフィルタとして使うことで、入力値に一致する選択肢を提供する仕組みです。絞込する際のフィールドや条件を変えたい場合は qs.filter(name__istartswith=self.q) の部分を変更してください。
続いてこの API にアクセスできるよう、 urls.py にルーティングを記述します。

urls.py
from django.urls import path
from yourappmodule import views

urlpatterns = [
    path(
        r'municipality-autocomplete/',
        views.MunicipalityAutoComplete.as_view(),
        name='municipality-autocomplete'
    ),
]

カスタムフォームを作る

admin で使うためのフォームを作ります。 forms.py を追加して、以下のように書きます。

forms.py
from dal import autocomplete
from django import forms
from .models import Municipality, UserProfile


class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile
        fields = ('__all__')
        widgets = {
            'municipality': autocomplete.ModelSelect2(url='municipality-autocomplete')
        }

widgets はフィールドごとに使うフォーム部品を設定する dict です。キーにオートコンプリートにしたいフィールド(今回は municipality)を、値に autocomplete.ModelSelect2 を指定します。
autocomplete.ModelSelect2 の初期化引数 url には urls.py で指定した name を指定します。

admin で使う

admin.py
from django.contrib import admin
from .models import UserProfile
from .forms import UserProfileForm

# Register your models here.


@admin.register(UserProfile)
class ServiceAdmin(admin.ModelAdmin):
    form = UserProfileForm

ここでは form を設定するだけです。
これでオートコンプリートになっているはずです。 admin にアクセスして試してみてください!

参考情報