T.H/ 2023年 12月 26日/ 技術

はじめに

こんにちは。T.H.です。
今回は、Djangoの管理画面のカスタマイズのうち、やや手間のかかる範囲であるテンプレートの差し替えについて説明します。
Model,ModelForm,ModelAdminを使用して管理画面を表示できている前提で進めます。

Djangoの管理画面のカスタマイズ段階

初級編のおさらいになりますが、Djangoの管理画面のカスタマイズは大きくわけて2パターンあります。
1.Model,ModelForm,ModelAdminにカスタマイズ内容を記載
2.template,css,javascriptの差し替え、機能拡張

初級編では1.の内容を記載しましたので、今回は2.の内容を取り上げたいと思います。

出来ること

  • 一覧画面への機能追加、css,jsのカスタマイズ
  • 編集画面への機能追加、css,jsのカスタマイズ

一覧画面のカスタマイズ

テンプレートへのカスタマイズ

一覧画面のテンプレートを差し替えることでテンプレートへのカスタマイズが可能です。

カスタマイズ方法

特定のフォルダに所定のファイル名を付けてテンプレートを配置します。
/templates/admin/"アプリケーション名"/"model名"/change_list.html

change_list.htmlの内容とカスタマイズ例

{% extends "admin/change_list.html" %}
{% load static %}

{% block extrahead %}
    {{ block.super }}
    # extrahead内にカスタマイズしたいheader項目を追加する
    <link rel="stylesheet" type="text/css" href="{% static 'css/admin_custom.css' %}">

{% endblock %}

{% block content %}
# content内にカスタマイズしたい項目を追加する
<div >
    <a href="{% url 'app_name:url_name' %}" class="button">一覧から飛べるリンク</a>
</div>
    # block.superが既存の一覧表示に相当する
    {{ block.super }}

{% endblock %}

上記の場合は新たにcss/admin_custom.cssを参照し、"一覧から飛べるリンク"ボタンを一覧の上部に追加する形になります。例では触れていませんが、同様にjsも追加できます。

検索項目のカスタマイズ

初級編ではsearch_fieldsを使用して一覧に検索窓を追加しました。search_fieldsでは検索対象を指定できませんでしたので、より柔軟に使いやすくするよう、検索のテンプレートをカスタマイズします。

ここでは、任意の項目に対して検索をかけるフィルターを作成します。

カスタマイズ方法

/templates/admin/input_filter.html
を配置し、以下の内容を記載します。

{% load i18n %}

<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul>
    <li>
        <form method="GET" action="">
            {% for k, v in all_choice.query_parts %}
                <input type="hidden" name="{{ k }}" value="{{ v }}" />
            {% endfor %}

            <input
                    type="text"
                    value="{{ spec.value|default_if_none:'' }}"
                    name="{{ spec.parameter_name }}"/>
        </form>
    </li>
</ul>

続いてフィルタリングをするためのクラスを作成します。こちらは自作クラスですので任意の場所に置いて構いません。

from django.contrib import admin

class InputFilter(admin.SimpleListFilter):
    template = "admin/input_filter.html"

    def lookups(self, request, model_admin):
        # Dummy, required to show the filter.
        return ((),)

    def choices(self, changelist):
        # Grab only the "all" option.
        all_choice = next(super().choices(changelist))
        all_choice["query_parts"] = (
            (k, v) for k, v in changelist.get_filters_params().items() if k != self.parameter_name
        )
        yield all_choice

class NameFilter(InputFilter):
    parameter_name = "name"
    title = "名称"

    def queryset(self, request, queryset):
        if self.value() is not None:
            name = self.value()
            if name is None:
                return
            return queryset.filter(name__icontains=name)

class TitleFilter(InputFilter):
    parameter_name = "title"
    title = "タイトル"

    def queryset(self, request, queryset):
        if self.value() is not None:
            param = self.value()
            if param is None:
                return
            return queryset.filter(title_ja__icontains=param)

InputFilterは検索のベースとなるクラスです。
NameFilterと、TitleFilterクラスはそれぞれmodel内の"name","title"という項目に対するフィルターになります。
検索結果をquerysetとして返却出来ればよいので、ある程度検索条件は柔軟に設定できます。

最後に、対象となるModelAdminのlist_filterに先ほど作成したfilterを追加します。

class NameAdmin(admin.ModelAdmin):
    list_filter = [ NameFilter, TitleFilter]

これで一覧に"名称"と"タイトル"を対象とした検索窓が追加されます。

編集画面のカスタマイズ

テンプレートをデフォルトのものから差し替えることで、機能の追加が可能です。
大々的に差し替えることはむずかしく、ちょっとした機能の追加程度に考えた方が良いと思います。

カスタマイズ方法

一覧の場合とほぼ同様です。
/templates/admin/"アプリケーション名"/"model名"/change_form.html
に配置することで編集画面のテンプレートをカスタマイズすることができます。

カスタマイズ例

例として、やや強引ですがtomselectというselectタグのカスタムツールを管理画面のリストに適用するケースを紹介します。

{% extends "admin/change_form.html" %}
{% load static %}

{% block extrahead %}
    {{ block.super }}
    <link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script>
    <link rel="stylesheet" type="text/css" href="{% static 'css/admin_custom.css' %}">

{% endblock %}

{% block content %}
    {{ block.super }}

<script>
    new TomSelect("#id_testlist",{
        create: false,
        sortField: {
            field: "text",
            direction: "asc"
        }
    });
</script>
{% endblock %}

models.pyの例
test_listを管理画面で表示するとselectタグとして出力される。

class ChildModel(models.Model):
    name = models.CharField(verbose_name="名称", default="", max_length=32)

    def __str__(self):
        return self.name

class TestModel(models.Model):
    test_list = models.ForeignKey(
        ChildModel, verbose_name="リスト", on_delete=models.SET_NULL, related_name="test_name", null=True, blank=True
    )

admin_custom.css
そのまま適用するとうまく表示されないため、cssを調整

.field-testlist .related-widget-wrapper {
    overflow: visible;
}

これで、編集画面上のtest_listにtomselectが適用されている状態となりました。管理画面上での新規作成時も同じテンプレートを使用しているため、どちらにも適用されます。

管理画面のその他のパーツのカスタマイズについて

今回と同様の手順で所定のテンプレートを差し替えることで、今回紹介した以外の画面、パーツのカスタマイズも可能です。参考URLにまとまっている記事がありますのでご参照ください。

最後に

今回、Django管理画面カスタマイズの上級編としてテンプレートの差し替えについて記述させていただきました。
今まで紹介した以外にも、インストールするだけで簡単に導入できる外部ツールなどもありますので、そちらも調査、検討されるとよいかと思います。

また、Django自体、Pythonで作られたものですので、やろうと思えば管理画面のコードを引用し完全に自由にカスタマイズすることも可能ではあります。
ですがそちらは、恐らく記事にはしないと思います。率直な感想として、テンプレートの差し替えで収まらないのであれば、ちゃんと1から作った方が良いと思います。
多少なりとも参考になれば幸いです。

参考

https://codelab.website/django-admin-add-text-filter/
https://ryu22e.org/posts/2020/12/10/how-to-override-the-django-admin-templates/
https://logmi.jp/tech/articles/326157

About T.H

North Torch株式会社 プログラマ 技術的な経歴は.NETアプリケーションが一番長い。 その他はまだまだ勉強中。