Djangoでadminにカスタムフィルタ追加

最近Pythonの勉強を始めてDjangoを弄っているのですが
なかなか日本語の記事も少なく困ることも多いので
今後のPythonの発展のためにもコツコツ記事を作っていこうと思います。

Pythonのチュートリアルを一通り終えて、少しカスタマイズしたくなった方
adminのfilterをカスタマイズしたい方向けになります。

Django チュートリアル

こちらも参考に。

公式サイトのリファレンス

では始めて行きましょう。
バージョン
Python:3.9.4
Django:3.2

Djangoのadmin画面

チュートリアルを完了すると、このような画面が表示されるようになっていると思います。
少しだけカスタマイズしておりますが、本編とは関係ない部分ですので割愛します。
このフィルター部分ですが、検索条件を増やしてみたいですね。
できれば独自の検索条件にしたい。
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はルールを覚えるまでに苦労はしますが、書いていて思うのは
やはり記述量の少なさ、スマートさですね。
使い方を覚えれば、開発速度が向上しそうです。

コメントを残す