Django 管理画面でのカスタムフィルター admin.SimpleListFilter 

---
## 環境
Windows 11 Home
Python 3.10.2
Django 4.0.2
venv利用あり

## 背景
管理画面において、フィルターの名称を変えたりカスタマイズしたフィルターを使用したかった。
例えばBool値のフィルタにおいて、「はい」「いいえ」だけでなく、データ登録されていないものを抽出するために
「はい か いいえ＝登録あり」「はいでもいいえでもない＝登録なし」といったものを用意したかった。

## models.py
```python:mysite\models.py
from django.db import models

class TestModel(models.Model):
    class Meta:
        verbose_name = 'テストモデル'
        verbose_name_plural = 'テストモデル'

    columnA = models.CharField(max_length=30, unique=False, primary_key=True)
    columnB = models.EmailField(blank=True, default="")
    columnC = models.BooleanField(blank=True, null=True)
    columnD = models.CharField(max_length=15, blank=True, default="")
    columnE = models.CharField(max_length=15, blank=True, default="")
```

## admin.py
```python:mysite\admin.py
from django.contrib import admin
from .models import TestModel

class TestProxy(TestModel):
    def __str__(self):
        return "TestProxy"

    class Meta:
        verbose_name = ('00_テストモデル')
        verbose_name_plural = ('00_テストモデル')
        
@admin.register(TestProxy)
class TestAdmin(admin.ModelAdmin):
    ordering = ['columnA']
    list_display = ['columnA', 'columnB', 'columnC','columnD','columnE',]
    list_filter = ["columnC", "columnD"]
```

## データ登録
フィルター動作確認のため適当に管理画面から値を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/b78ce07a-2ad1-abac-5350-df059c40fdf2.png)

`list_filter` を設定しているためデフォルトの画面右側にフィルタが表示される。

## 名前の変更
例えば"columnCで絞り込む"というデフォルトのフィルタで表示される「はい」「いいえ」という文言がわかりにくいと思ったら、
名前を変更することができる。
ここでは「はい」を「有効」、「いいえ」を「無効」にする。

```python:mysite\admin.py

class BoolFilter(admin.SimpleListFilter):
    title = 'columnC、Bool名前変更フィルタ'
    parameter_name = 'columnC'

    def lookups(self, request, model_admin):
        return (
            ('True', '有効'),
            ('False', '無効')
        )

    def queryset(self, request, queryset):
        if self.value() == 'True':
            return queryset.filter(columnC=True)
        elif self.value() == 'False':
            return queryset.filter(columnC=False)
        else:
            return queryset.all()
     
```
```
    list_filter = [BoolFilter]
```
ここで注意してほしいのが
```
    list_filter = ["BoolFilter"]
```
としてしまうとエラーになる。カラム名ではなくCLASS名を記載するのでダブルクォーテーションは不要。2時間ハマった。
`django.core.management.base.SystemCheckError: SystemCheckError: System check identified some issues:`

`ERRORS:`
`<class 'myApp.admin.TestAdmin'>: (admin.E116) The value of 'list_filter[1]' refers to 'BoolFilter', which does not refer to a Field.`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/c3b32f73-b994-5caf-93c0-7771466fa9aa.png)


正しく記載をするとフィルタに今回追加したフィルタが追加された。機能は「columnC で絞り込む」と全く同じだが、「有効」「無効」としたことでより機能が明確になった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/88a35218-f0c0-94fc-db27-876325e1d33a.png)

## カスタムフィルタ
「はい か いいえ＝登録あり」「はいでもいいえでもない＝登録なし」といったものを用意する。

```python:mysite\admin.py

class CharFilter(admin.SimpleListFilter):
    title = 'columnD、登録有無フィルタ'
    parameter_name = 'columnD'

    def lookups(self, request, model_admin):
        return (
            ('True', '登録あり'),
            ('False', '登録なし')
        )

    def queryset(self, request, queryset):
        if self.value() == 'True':
            return queryset.exclude(columnD__exact="")
        elif self.value() == 'False':
            return queryset.filter(columnD="")
        else:
            return queryset.all()
     
```
```
    list_filter = [CharFilter]
```

新たにカスタムフィルタが追加された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/90f90b8e-6045-8e6c-1cee-5894714bec1d.png)

実際にボタンを押して動作を確認してみる。
COLUMND列に注目してほしい。まずは「登録あり」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/1d81fea1-e920-e5ec-cfcf-c6e165b7933b.png)

次は「登録なし」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/679f8817-d78e-184f-820a-6f446771f0b0.png)

ちゃんと動作している。ここで注意があって
```
    list_filter = ["columnD", CharFilter]
```
のように同じカラムを`list_filter`に追加するとカスタムフィルターが機能しない。
(CharFilterは`parameter_name = 'columnD'`を指定している)

## カスタムフィルタ2
`BoolFilter`を改良して「有効」「無効」「設定済み（有効か無効）」「未設定（null）」にする。

```python:mysite\admin.py

class BoolFilter(admin.SimpleListFilter):
    title = 'columnC、Bool名前変更フィルタ'
    parameter_name = 'columnC'

    def lookups(self, request, model_admin):
        return (
            ('True', '有効'),
            ('False', '無効'),
            ('Both', '設定済み'),
            ('None', '未設定'),
        )

    def queryset(self, request, queryset):
        if self.value() == 'True':
            return queryset.filter(columnC=True)
        elif self.value() == 'False':
            return queryset.filter(columnC=False)
        elif self.value() == 'Both':
            return queryset.exclude(columnC__exact="")
        elif self.value() == 'None':
            return queryset.filter(columnC=None)
        else:
            return queryset.all()
     
```
```
    list_filter = [BoolFilter]
```
columnCをnullのデータを追加しておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/d5302c75-8191-d4ab-fffc-d1aa5622a170.png)

有効
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/b1057baf-e2f3-a1a6-4c4b-2449cb4132f1.png)
無効
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/eee56dde-23b0-65cd-75c7-aa964ca57d83.png)
設定済み
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/20835505-5c76-35d6-c78d-7afe0a521908.png)
未設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515586/d2939ff1-e4ff-e720-a565-f6b840a14b44.png)

正しく動作しているようだ。

以上

## 参考
[[Django]管理者サイトの一覧のフィルタを追加する](https://codelab.website/django-admin-filter/)