Django管理画面のオブジェクト一覧ページをカスタマイズ

# Djangoの管理画面について

Djangoは、Pythonで開発された人気のあるWebフレームワークで、開発者が効率的にWebアプリケーションを構築できます。Djangoの管理画面は、データベースの管理や操作を容易にするための強力な機能を提供しています。この記事では、管理画面のカスタマイズ可能な機能について、いくつか解説します。

この記事はDjango管理画面カスタムシリーズの1つです。
以下に本シリーズの記事を添付します。

https://qiita.com/ryo-keima/items/b250abcdd378f85e4eed

https://qiita.com/ryo-keima/items/f66a8f3acc8006389661

## GitHubレポジトリ

ここで解説しているソースコードは、GitHubにて公開しておりますので、気になる方はチェックしてみてください！

https://github.com/ryo-keima/django-admin-dashboard-demo


# Django管理画面のオブジェクト一覧ページのカスタム方法

## 概略

Djangoフレームワークの管理画面は、デフォルトで多くの機能を提供していますが、プロジェクトに応じてカスタマイズすることがよくあります。この記事では、**`django.contrib.admin.ModelAdmin`**クラスを継承して管理画面をカスタマイズする方法について解説します。


## 機能紹介

### 1. **list_display**

管理画面のオブジェクト一覧ページで表示するフィールドをリストもしくはタプルで指定します。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author") # タイトルと著者を表示するよう設定

admin.site.register(Blog, BlogAdmin)
```

すると以下の図のように、一覧ページにtitleとauthorが表示されるようになります。
![Screenshot 2023-03-21 at 19.38.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/a5f6e647-383a-e7a1-3a7b-02cec143fc88.png)


### 2. list_filter

管理画面のオブジェクト一覧ページに、指定したフィールドをもとにフィルター機能を追加します。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",) # authorでフィルター

admin.site.register(Blog, BlogAdmin)
```

画面にFILTER欄が追加されました。

![Screenshot 2023-03-21 at 19.40.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/669ef4fb-a357-0110-7dc1-b05df1ae1001.png)

testuserでフィルターすると、authorがtestuserのオブジェクトのみ表示できました！
![Screenshot 2023-03-21 at 19.41.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/089cb24f-379f-b478-b6a6-f1e76385bb7f.png)

### 3. search_fields

管理画面のオブジェクト一覧ページに、指定したフィールドをもとに検索ボックスを表示し、検索機能を追加します。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",) 
    search_fields = ("title",) #  titleで検索

admin.site.register(Blog, BlogAdmin)
```

画面にが検索ボックスが追加されました。
![Screenshot 2023-03-21 at 21.29.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/f287204d-19cf-9f9b-a391-f50206bb20b8.png)


タイトルに”3”が含まれるオブジェクトを検索すると、無事に検索できました！
![Screenshot 2023-03-21 at 21.29.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/8f8a1b43-3c46-59ab-1e00-27a5aa6d57f5.png)


### 4. list_per_page

管理画面のオブジェクト一覧ページのページネーション設定を行います。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    list_per_page = 2 # １ページあたりに表示するオブジェクト数を指定

admin.site.register(Blog, BlogAdmin)
```

上記の設定により、ページングされました。
![Screenshot 2023-03-21 at 21.34.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/376e4a57-0215-a24a-6042-7cedb298b1d9.png)


### 5. ordering

管理画面のオブジェクト一覧ページのデフォルトの並び順を設定します。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", ) # titleの昇順に並び替える(降順にするには"-title"を指定)

admin.site.register(Blog, BlogAdmin)
```

titleの昇順に並び替えられました。
![Screenshot 2023-03-21 at 21.45.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/0b526e2d-eecb-e916-f411-6bf6b04d17e3.png)


### 6. exclude

管理画面の追加・編集フォームから、指定したフィールドを除外します。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    exclude = ("body", )

admin.site.register(Blog, BlogAdmin)
```

通常、Blogオブジェクトの編集画面は以下の画面です。
![Screenshot 2023-03-21 at 21.47.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/bbca26c8-a9c2-4033-743e-6fcbcd66f4f7.png)

excludeに”body”を設定すると、編集画面から、bodyがなくなりました。
![Screenshot 2023-03-21 at 21.49.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/93a1367d-74b5-d895-8b8a-3ccde47b806c.png)


### 7. readonly_fields

管理画面の追加・編集フォームで、指定したフィールドを読み取り専用にします。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    exclude = ("body", )
    readonly_fields = ("title", ) # titleを読み取り専用にします

admin.site.register(Blog, BlogAdmin)
```

上記設定を行うと、titleが編集不可になりました。
![Screenshot 2023-03-21 at 21.50.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/1a512e92-d6c4-22ca-b973-422dffbad914.png)


### 8. fields

管理画面の追加・編集フォームで、指定したフィールドのみを表示します。表示順も指定した順になります。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    fields = ("author", "title") # authorとtitleのみを表示します

admin.site.register(Blog, BlogAdmin)
```

上記設定により、authorとtitleのみが表示されました。
![Screenshot 2023-03-21 at 21.57.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/811a6855-2f77-2e82-18a6-1c07ebd6bd44.png)


### 9. fieldsets

管理画面の追加・編集フォームで、フィールドをグループ化して表示します

タプルまたはリストで、フィールドのグループを定義します。各グループはタプルで、最初の要素がグループ名、2番目の要素が辞書で、”fields”キーに表示するフィールド名のタプルまたはリストを指定します。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    fieldsets = (
        ("Blogs", {"fields": ("author", "body")}), # authorとbodyを表示します
    )

admin.site.register(Blog, BlogAdmin)
```

上記の設定により、Blogオブジェクトの編集画面には、authorとbodyのみ表示されるようになりました。
![Screenshot 2023-03-21 at 21.58.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/415be646-bdfa-86ef-5d66-46b087f044c9.png)


### 10. forms

管理画面の追加・編集フォームで、指定したフォームクラスを使用します。

カスタムフォームを作成します。今回は、bodyを必須とするフォームを作成します。

```python
from django import forms
from .models import Blog

class BlogForm(forms.ModelForm):
    class Meta:
        model = Blog
        fields = "__all__"

    def clean_body(self):
        body = self.cleaned_data.get("body")
        if not body:
            raise forms.ValidationError("Body is required.")
        return body
```

カスタムフォームを作成したら、`form = BlogForm`として設定します。

```python
from django.contrib import admin

from .models import Blog
from .forms import BlogForm

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    form = BlogForm # カスタムフォームを設定する

admin.site.register(Blog, BlogAdmin)
```

Bodyを空にして、SAVEを押してみると、カスタムフォームが設定されていることが確認できました。
![Screenshot 2023-03-21 at 22.05.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/1aa77fd7-0362-4651-c33a-019a3ea92d66.png)

### 11. inlines

 管理画面の追加・編集フォームで、関連するモデルのインライン編集フォームを表示します。

ここではユーザーオブジェクトの編集画面から、そのユーザーのブログを編集できるよう設定します。

```python
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from .models import User
from blog.models import Blog

# inline用のBlogクラス
class BlogInline(admin.StackedInline):
    model = Blog 
    extra = 1

class UserAdmin(BaseUserAdmin):
    list_display = ("username", "email", "is_superuser")
    list_filter = ("username",)
    inlines = [BlogInline] # inline設定を追加

    fieldsets = (
        (None, {"fields": ("username", "email", "password")}),
        ("Permissions", {
         "fields": ("is_superuser", "is_staff", "user_permissions")
         }),
    )

    add_fieldsets = (
        (None, {
            "classes": ("wide",),
            "fields": ("username", "email", "password1", "password2"),
        },
        ),
    )

    search_fields = ("username", "email")
    ordering = ("username", "email")

    filter_horizontal = ()

admin.site.register(User, UserAdmin)
```

上記設定を行った後ユーザーオブジェクトを確認すると、そのユーザーのブログが表示されるようになりました。
![Screenshot 2023-03-21 at 22.18.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/b53087ae-a7f7-157a-217c-5faf228e96d9.png)


### 12. save_on_top

管理画面の追加・編集フォームで、保存ボタンをページの上部にも表示します。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    save_on_top = True # この設定を追加

admin.site.register(Blog, BlogAdmin)
```

以下のように、画面上部に保存ボタンが表示されました。
![Something went wrong]()


### 13.  prepopulated_fields

管理画面の追加・編集フォームで、指定したフィールドの値を他のフィールドの値に基づいて自動入力します。例えば、スラッグフィールドの自動生成に便利です。

Blogモデルにスラッグフィールドを作成

```python
from django.db import models
from django.utils.translation import gettext_lazy as _

from account.models import User

class Blog(models.Model):
    title = models.CharField(
        verbose_name=_("title"),
        max_length=50,
        blank=False,
        null=False)
    body = models.TextField(
        verbose_name=_("body"),
        blank=True,
        null=True)
    author = models.ForeignKey(
        User,
        verbose_name=_("author"),
        on_delete=models.CASCADE)
    # 以下のカラムを作成
    slug = models.SlugField(
        verbose_name=_("slug"),
        unique=True) 
    created_at = models.DateTimeField(
        verbose_name=_("created_at"),
        auto_now_add=True)
    updated_at = models.DateTimeField(
        verbose_name=_("updateded_at"),
        auto_now=True
    )
```

blog/admin.pyにprepopulated_fieldsを設定

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    prepopulated_fields = {"slug": ("title",)} # 自動生成用

admin.site.register(Blog, BlogAdmin)
```

Blog作成画面で、Titleを入力すると、自動で、slugフィールドにtitleの文字列が反映されます。
![Screenshot 2023-03-21 at 22.32.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/1ff64014-04fa-73f5-744a-0b72cb9c0765.png)


### 14. raw_id_fields

 管理画面の追加・編集フォームで、指定したフィールドを検索可能な選択ボックスに変更します。大量のリレーションがある場合に便利です。

```python
from django.contrib import admin

from .models import Blog

class BlogAdmin(admin.ModelAdmin):
    list_display = ("title", "author")
    list_filter = ("author",)
    search_fields = ("title",)
    ordering = ("title", )
    raw_id_fields = ('author',) # 外部キーを指定します

admin.site.register(Blog, BlogAdmin)
```

この設定で、Authorの表示が変わりました。
![Screenshot 2023-03-21 at 22.38.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951524/a6ab9b4e-bbe0-4299-3dec-3bfc44f1e563.png)


これで、Django管理画面で`Book`モデルを追加・編集する際に、`author`フィールドが検索可能なテキストボックスとして表示されます。`Author`のIDを直接入力するか、横にある[検索]アイコンをクリックして別ウィンドウで検索し、関連付ける`Author`を選択できます。
このように、`raw_id_fields`を設定することで、外部キーまたは多対多フィールドを簡単に検索して選択できるようになります。これは、選択可能なオブジェクトが大量に存在する場合に特に便利です。

## まとめ

Djangoの管理画面は非常に強力で柔軟な機能を持っており、`django.contrib.admin.ModelAdmin`クラスを継承してカスタマイズすることが可能です。
この記事で紹介した機能を用いて、プロジェクトに合わせた管理画面を作成しましょう！
適切なカスタマイズによって、開発者や管理者が効率的にデータを管理できるようになりますね！😊