【Django】admin 画面にカスタム詳細ページを追加する方法|get_urls + テンプレート差替で実装

スポンサードリンク



Django · Admin Customization · 詳細ページ追加

理サイト(Django admin)はモデルの一覧 → 編集の流れがデフォルトで提供されますが、「クリックすると関連データを集約した カスタム詳細ページ に飛ばしたい」という要望は実務でよく出てきます。本記事では、admin 一覧画面に独自リンクを追加し、get_urls() で専用エンドポイントを登録、テンプレートを差し替えてリッチな詳細画面を表示する完全実装手順を解説します。

この記事はDjango Advent Calendar 2022の17日目の記事です。

Django admin画面上でCRUDの詳細ページを実装する方法を紹介します。

ここでは、下図のような注文者毎に購入した注文明細情報一覧を表示するような詳細ページを実装する手順をご紹介します。

user@sinyblog:~/article 01_section_1.md事前準備

まず、仮想環境を作成&アクティベートして必要なモジュールのインストールとDjangoプロジェクト、アプリケーション作成まで完了させましょう。

python


python -m venv env

env\scripts\activate

Django本体と画像ファイルを扱うためPillowをインストールします。

python


pip install django Pillow

続いてDjangoプロジェクトを作成しましょう。

python


django-admin startproject config .

次にproductsという名前のアプリケーションを作成します。

python


python manage.py startapp products

config/settings.pyで以下の設定を行います。

python


INSTALLED_APPS = [

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'products.apps.ProductsConfig',   #追加

]





LANGUAGE_CODE = 'ja'  #変更



TIME_ZONE = 'Asia/Tokyo'  #変更



#以下を追加

MEDIA_URL = 'media/'

MEDIA_ROOT = BASE_DIR / 'media/'



 

user@sinyblog:~/article 02_section_2.mdモデルの定義

製品と注文、注文明細データを管理するモデルを以下の通り定義します。

python


from django.contrib.auth.models import User

from django.db import models







class Product(models.Model):



    class Meta:

        verbose_name = "製品"

        verbose_name_plural = "製品"



    title = models.CharField(max_length=255)

    price = models.IntegerField()

    image = models.ImageField()

    

    def __str__(self):

        return self.title



class Order(models.Model):



    class Meta:

        verbose_name = "注文"

        verbose_name_plural = "注文"

        

    user = models.ForeignKey(User, on_delete=models.CASCADE)

    created = models.DateTimeField(auto_now_add=True)

       

    def __str__(self):

        return f"{self.user} {self.created.date()}"



class OrderItem(models.Model):

    

    class Meta:

        verbose_name = "注文明細"

        verbose_name_plural = "注文明細"

        

    order = models.ForeignKey(Order, on_delete=models.CASCADE)

    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    count = models.PositiveIntegerField()

    

    def __str__(self):

        return f"{self.product} x {self.count}"

 

Productクラスには製品のタイトル(title)、値段(price)、製品の画像(imageフィールドを定義します。

Orderクラスはユーザの注文データを格納するモデルクラスで、ユーザ名(user)と注文日時(cerated)フィールドを定義します。

最後に注文明細を管理するためのOrderItemクラスを定義します。

フィールドは注文データ(Orderクラスを外部参照)、製品(Productクラスを外部参照)、注文数(count)です。

OrderItemはあるユーザが注文した製品の個数(注文明細)を製品毎に1レコード登録するような構成を想定しています。

モデルの定義が終わったので一旦マイグレーションを行っておきましょう。

sh


python manage.py makemigrations

Migrations for 'products':

  products\migrations\0001_initial.py

    - Create model Order

    - Create model Product

    - Create model OrderItem
sh


python manage.py

 migrate

Operations to perform:

  Apply all migrations: admin, auth, contenttypes, products, sessions

Running migrations:

  Applying contenttypes.0001_initial... OK

  Applying auth.0001_initial... OK

  Applying admin.0001_initial... OK

  Applying admin.0002_logentry_remove_auto_add... OK

  Applying admin.0003_logentry_add_action_flag_choices... OK

  Applying contenttypes.0002_remove_content_type_name... OK

  Applying auth.0002_alter_permission_name_max_length... OK

  Applying auth.0003_alter_user_email_max_length... OK

  Applying auth.0004_alter_user_username_opts... OK

  Applying auth.0005_alter_user_last_login_null... OK

  Applying auth.0006_require_contenttypes_0002... OK

  Applying auth.0007_alter_validators_add_error_messages... OK

  Applying auth.0008_alter_user_username_max_length... OK

  Applying auth.0009_alter_user_last_name_max_length... OK

  Applying auth.0010_alter_group_name_max_length... OK

  Applying auth.0011_update_proxy_permissions... OK

  Applying auth.0012_alter_user_first_name_max_length... OK

  Applying products.0001_initial... OK

  Applying sessions.0001_initial... OK

また、管理者ユーザも作成しておきましょう。

sh


 python manage.py

 createsuperuser

ユーザー名 (leave blank to use 'sinfo'): admin

メールアドレス:

Password:

Password (again):

Superuser created successfully.

 

user@sinyblog:~/article 03_admin.mdAdminサイトの初期設定

続いてadminサイトにモデルを表示させるため`products/admin.py`を以下の通り設定しましょう。

python


from django.contrib import admin

from products.models import Product, OrderItem, Order



@admin.register(Product)

class ProductAdmin(admin.ModelAdmin):

    list_display = ['title', 'price']



class OrderItemInline(admin.TabularInline):

    model = OrderItem

    

    

@admin.register(Order)

class OrderAdmin(admin.ModelAdmin):

    list_display = ['user', 'created']

    inlines = [OrderItemInline]



@admin.register(OrderItem)

class OrderItemAdmin(admin.ModelAdmin):

    list_display = ['order', 'product', 'count']

OrderItemInlinOrderAdmininlinesに指定することで注文データ(Order)を登録する際に複数の注文明細(製品と個数)を一括で登録できるようにしています。

開発サーバを起動してadminサイトにログオンします。

sh


python manage.py runserver

Watching for file changes with StatReloader

Performing system checks...



System check identified no issues (0 silenced).

December 12, 2022 - 06:28:03

Django version 3.2.4, using settings 'config.settings'

Starting development server at http://127.0.0.1:8000/

Quit the server with CTRL-BREAK.

 

以下の通り「注文、注文明細、製品」が表示されるので、まずはサンプルの注文データを登録しましょう。

以下の様に複数のユーザ、製品を登録し、いくつか注文データも登録しておきましょう。

Productには製品の画像も適宜指定しておきましょう。

adminサイト上で画像表示を行えるようにconfig/urls.py に管理サイトの URL 構成を追加します。

python


from django.contrib import admin

from django.urls import path

from django.conf import settings #追加

from django.conf.urls.static import static #追加





urlpatterns = [

    path('admin/', admin.site.urls),

]



urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)  #追加

 

 

user@sinyblog:~/article 04_section_4.md注文概要のページの作成

adminサイトの「注文」をクリックすると、現状は以下の通りユーザ名と注文作成日の一覧情報だけが表示されている状態になっています。

ここに詳細ページへのリンクを追加してユーザ毎に注文した注文明細データを一覧表示する注文概要ページを追加します。

まず、products/admin.pyに注文の詳細ページを表示させるためのOrderDetailViewクラスを以下の通り追加します。

python


from django.views.generic.detail import DetailView



class OrderDetailView(DetailView):



    template_name = "admin/products/order/detail.html"

    model = Order

 

次に、注文概要ページへリンクするURLパターンを追加します。

adminサイト内で独自のURLパターンを追加するにはModelAdminクラス内に定義されているget_urlsメソッドを使います。

以下の様にOrderAdminクラス内にget_urls メソッドを追加して、注文(Order)の詳細データを表示するURLパターンを追加します。

python


from django.urls import path, reverse  #追加

from django.utils.html import format_html #追加



@admin.register(Order)

class OrderAdmin(admin.ModelAdmin):

    list_display = ['user', 'created','order_detail']  # order_detailを追加

    inlines = [OrderItemInline]

    

    #ここから下を追加

    def get_urls(self):

        urls = super().get_urls()  # defaultのadmin urlをロード

        

        detail_urls = [

            path("<pk>/order_detail",self.admin_site.admin_view(OrderDetailView.as_view()),name=f"products_order_detail",)

        ]

        return detail_urls + urls 

     

    def order_detail(self, obj):

        url = reverse("admin:products_order_detail", args=[obj.pk])

        return format_html(f'<a href="{url}">&#x1f4dd</a>')

    #ここまでを追加

具体的には、detail_urlsというURLパターン変数を新たに定義し、その中にpathメソッドでOrderDetailViewクラスを呼び出すURLパターンを定義します。

python


path("<pk>/detail",self.admin_site.admin_view(OrderDetailView.as_view()),name=f"products_order_detail",)

上記が注文概要ページへリンクするURLパターンで「http://127.0.0.1:8000/admin/products/order/<pk>/order_detail」といったURL設計になります。

 get_urls で定義したURLは 「admin/アプリ名/モデル名/URLパターン」という設計になります。

また、が注文概要ページへリンク列をorder_detailとして定義しているのが以下の部分です。

python


from django.urls import path, reverse  #reverseを追加

from django.utils.html import format_html  #追加





def order_detail(self, obj):

    url = reverse("admin:products_order_detail", args=[obj.pk])

    return format_html(f'<a href="{url}">&#x1f4dd</a>')

order_detailという名称でメソッドを定義し、引数にobjを指定します。

reverseメソッドにURLパターンとargsにリンク先のOrderインスタンスのpk番号を指定しリンク先となる情報をurl変数に格納します。

最後にreturnでformat_htmlを使ってhtml形式で注文概要ページへのリンクを戻り値とします。

そして、OrderAdminクラスlist_displayorder_detailを追加することでadminサイトの「注文」のページにorder_detail列が追加されます。

adminサイト画面を更新して「注文」画面を開くと以下の通りORDER_DETAIL列が追加されます。

試しにORDER_DETAIL列をクリックすると以下のようなエラーが表示されると思います。

TemplateDoesNotExist at /admin/products/order/3/order_detail

admin/products/order/detail.html

このエラーはリンク先となる注文概要ページのテンプレート(detail.html)が存在しないためです。

そこでproducts/templates/admin/products/order/detail.htmlを以下の通り作成しましょう。

default


{% extends 'admin/base_site.html' %}



{% block content %}



    <h1>Order by {{ object.user }} {{ object.created.date }}</h1>

    <dl>

        <dt>氏名:</dt><dd>{{ object.user.username }}</dd>

        

        <dt>メールアドレス:</dt><dd>{{ object.user.email }}</dd>

        

    </dl>

{% endblock %}

 

再度、adminサイト画面を更新して「注文」画面のORDER_DETAIL列のリンクボタンを押してみましょう。

「http://127.0.0.1:8000/admin/products/order/<pk>/order_detail」に遷移して以下のような注文概要ページが表示されます。

 

現時点では、注文者の氏名メールアドレスだけが表示されるようになっています。

このページに注文した製品と注文数とその合計金額、さらに複数商品を注文した場合のTotalの合計金額を表示させるようにします。

products/models.pyを開いてOrderクラスに注文合計金額を算出するプロパティメソッド(total_amount)を、OrderItemクラス内に1明細あたりの合計金額を算出するプロパティメソッド(total_amount)を追加します。

python


class Order(models.Model):



    class Meta:

        verbose_name = "注文"

        verbose_name_plural = "注文"

        

    user = models.ForeignKey(User, on_delete=models.CASCADE)

    created = models.DateTimeField(auto_now_add=True)

       

    def __str__(self):

        return f"{self.user} {self.created.date()}"

    

    #ここから下を追加

    @property

    def total_amount(self):

        return sum([item.total_amount for item in self.orderitem_set.all()])

    

    

class OrderItem(models.Model):

    

    class Meta:

        verbose_name = "注文明細"

        verbose_name_plural = "注文明細"

        

    order = models.ForeignKey(Order, on_delete=models.CASCADE)

    product = models.ForeignKey(Product, on_delete=models.CASCADE)

    count = models.PositiveIntegerField()

    

    def __str__(self):

        return f"{self.product} x {self.count}"



    #ここから下を追加

    @property

    def total_amount(self):

        return self.product.price * self.count

 

最後にdetail.htmlを以下の通り修正します。

default


{% extends 'admin/base_site.html' %}



{% block content %}



<h1>Order by {{ object.user }} {{ object.created.date }}</h1>

<dl>

    <dt>氏名:</dt>

    <dd>{{ object.user.username }}</dd>



    <dt>メールアドレス:</dt>

    <dd>{{ object.user.email }}</dd>



</dl>

<!-- ここから下を追加 -->

<table style="width: 100%; margin-bottom: 10px;">

    <thead>

        <tr>

            <td></td>

            <td>製品名</td>

            <td>価格</td>

            <td>注文数量</td>

            <td>小計金額</td>

        </tr>

    </thead>

    <tbody>

        {% for item in object.orderitem_set.all %}

        <tr>

            <td><img src="{{ item.product.image.url }}" height="50"></td>

            <td>{{ item.product.title }}</td>

            <td>{{ item.product.price }}円</td>

            <td>x {{ item.count }}</td>

            <td>{{ item.total_amount }}円</td>

        </tr>

        {% endfor %}

    </tbody>

</table>



<p style="font-size: 18px;text-align: end;padding-right: 60px;">

    合計金額: {{ object.total_amount }}円

</p>

<!-- ここまでを追加 -->



{% endblock %}

 

以下の通り、注文画面のORDER DETAIL列のリンクアイコンをクリックすると該当ユーザが注文した注文明細の一覧と合計金額が表示されるようになります。

以上、「Django admin画面で詳細ページを実装する方法」のご紹介でした。

参考になれば幸いです。

 

おすすめの記事