Djangoでadminにカスタムフィルタ追加
最近Pythonの勉強を始めてDjangoを弄っているのですが なかなか日本語の記事も少なく困ることも多いので 今後のPythonの発展のためにもコツコツ記事を作っていこうと思います。 Pythonのチュートリアルを一通り終えて、少しカスタマイズしたくなった方 adminのfilterをカスタマイズしたい方向けになります。
こちらも参考に。
では始めて行きましょう。 バージョン Python:3.9.4 Django:3.2
Djangoのadmin画面
チュートリアルを完了すると、このような画面が表示されるようになっていると思います。 少しだけカスタマイズしておりますが、本編とは関係ない部分ですので割愛します。 このフィルター部分ですが、検索条件を増やしてみたいですね。 できれば独自の検索条件にしたい。

filterカスタマイズ
修正前のソース
from django.contrib import admin
from .models import Choice, Question
from django.utils.translation import gettext_lazy as _
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 30
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
inlines = [ChoiceInline]
list_display = ('question_text', 'pub_date', 'was_published_recently','decorate_text')
list_filter = ['pub_date']
search_fields = ['question_text']
admin.site.register(Question, QuestionAdmin)
list_filter部分に「pub_date」が指定されているため、現在はpub_dateに対する検索条件のみ設定されています。
修正後のソース
from django.contrib import admin
from .models import Choice, Question
from django.utils.translation import gettext_lazy as _
"""2.カスタムフィルターの宣言"""
class QuestionTextFilter(admin.SimpleListFilter):
title = _('テスト用のフィルター')
parameter_name = 'question_text'
def lookups(self, request, model_admin):
return (
('test', _('in test')),
('not test', _('in not test')),
)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
if self.value() == 'test':
return queryset.filter(question_text__icontains = 'test')
if self.value() == 'not test':
return queryset.exclude(question_text__icontains = 'test')
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 30
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
inlines = [ChoiceInline]
list_display = ('question_text', 'pub_date', 'was_published_recently','decorate_text')
"""1.list_filter にカスタムフィルターを追加"""
list_filter = [QuestionTextFilter,'pub_date']
search_fields = ['question_text']
admin.site.register(Question, QuestionAdmin)
修正点
1.list_filter にカスタムフィルターを追加
フィルターを追加するためには、「list_filter」に宣言を追加します。 修正前のソースで「pub_date」のみが入っていた部分ですね。 ここに追加するカスタムフィルターのclassを指定します。
2.カスタムフィルターの宣言
宣言したカスタムフィルターの中身です。 下記2つの定義が必要になります。サンプルソースのlookupsに記載している「test」「not test」の文字列は querysetの条件分岐に利用しています。 lookups:検索ラベルの設定 queryset:検索条件 修正後の画面はこのようになります。

ハマったエラー集
The value of 'list_filter[0]' refers to 'QuestionTextFilter', which does not refer to a Field.
ググってもなかなか解決策がわかりませんでしたが、単純なことでした。 下記のような書き方をするとエラーになります。
list_filter = ['QuestionTextFilter','pub_date']
原因は
フィルタの宣言でシングルクオートで囲ってしまっていること
シングルクオートで囲ってしまっているために、フィールド名と判断されますが 『そんなフィールドないよ』と怒られます。 カスタムフィルタはclassの宣言なのでシングルクオートなしで記載しましょう。
too many values to unpack (expected 2)
どこかのサイトからコピペして作ったらエラーになった部分 lookupsの記述方法が間違っていました。
def lookups(self, request, model_admin): return ( 'test', 'not test' )
こちらが正しいソースです。
def lookups(self, request, model_admin):
return (
('test', _('in test')),
('not test', _('in not test')),
)
returnの書き方が違いますね。引数の数が違っているためにエラーとなったようです。 Pythonはルールを覚えるまでに苦労はしますが、書いていて思うのは やはり記述量の少なさ、スマートさですね。 使い方を覚えれば、開発速度が向上しそうです。

