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

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/25145/327c5c6a-bdab-f03e-c95e-a59be18d1aeb.png)


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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/25145/6796df58-f10d-1cc0-dc3d-3cdd1b6d7f46.png)

## [django-autocomplete-light](https://github.com/yourlabs/django-autocomplete-light) をインストール

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

```bash
pip install django-autocomplete-light
```

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

```python:settings.py

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

## モデルを用意する

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/25145/732c1fae-7ec1-1556-11c9-390f1ee4e0f0.png)

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

```python: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 から参照することに注意してください。

```python: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 にルーティングを記述します。

```python: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` を追加して、以下のように書きます。

```python: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 で使う

```python: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 にアクセスして試してみてください！

## 参考情報

- [Django Packages : Auto-Complete](https://djangopackages.org/grids/g/auto-complete/) (オートコンプリートライブラリの比較が載ってます)
- [django-autocomplete-light 3.1.3 documentation インストール方法](http://django-autocomplete-light.readthedocs.io/en/3.1.3/install.html#install-the-demo-project)
- [django-autocomplete-light 3.1.3 documentation チュートリアル](http://django-autocomplete-light.readthedocs.io/en/3.1.3/tutorial.html)