Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django 管理サイトをカスタマイズする前に教えてほしかったこと / How to customize admin (DjangoCon JP 2021)

Django 管理サイトをカスタマイズする前に教えてほしかったこと / How to customize admin (DjangoCon JP 2021)

DjangoCon JP 2021 発表資料

本発表では、Django に標準搭載されている目玉機能のひとつである「管理サイト(Django Admin)」をカスタマイズする際の注意点について解説します。

コードをたった数行書き足すだけであらゆるモデルに対応したCRUD画面が追加できる管理サイトは、その手軽さで開発者からの評価も高く、「Django Developers Survey 2020」というアンケートでは有用なデフォルト機能ナンバーワンにも選ばれています。管理サイトは多くの開発者にとって最初に触れる Django アプリでもあり、開発中のデバッグから本番リリース後のデータメンテナンスまで幅広くお世話になることでしょう。

しかしながら、管理サイトは「万能」ではありません。良い面ばかりがクローズアップされがちな管理サイトですが、ここで敢えてマイナスの面を挙げてみます。

1. どんなカスタマイズが簡単にできるのか分からない
2. テンプレートを修正するのにコツが要る
3. 画面のスタイルを変えるのが大変
4. コードが断片化しやすくテストがしづらい
5. 日本語の情報が少ない

このように、管理サイトをカスタマイズをしようとしたときに特にトラブルの声が聞かれます。そこで本発表では、「Django 管理サイトをカスタマイズする前に教えてほしかったこと」と題して、管理サイトをカスタマイズするにあたって苦労するポイントとそれらの解決策について解説します。

特に、これから管理サイトをカスタマイズしようと考えている方は必聴です。

C3a47a823a1e7adcb5be27cc3df5c482?s=128

akiyoko

July 03, 2021
Tweet

Transcript

  1. Django 管理サイトをカスタマイズする前に 教えてほしかったこと 2021.7.3 DjangoCongress JP 2021 横瀬 明仁(akiyoko)

  2. このトークで話すこと 1 管理サイトをカスタマイズするにあたって 苦労するポイントとその解決策 (Django のバージョンは現時点で最新の 3.2 LTS)

  3. 目次 1. 導入 2. 管理サイトの基本仕様 3. 全体構造と簡単なカスタマイズ 4. テンプレートのカスタマイズ 5.

    CSSのカスタマイズ 6. 管理サイトの効率的なテスト 7. 日本語情報 8. まとめ 2 ( 3 min ) ( 3 min ) (10 min ) ( 7 min ) ( 7 min ) ( 7 min ) ( 1 min ) ( 1 min )
  4. 自己紹介 名 前 : 横瀬 明仁(akiyoko) 所 属 : 株式会社

    ワングリット Twitter : @aki_yok ブログ : akiyoko blog(https://akiyoko.hatenablog.jp/) Django 歴 : 8年くらい 過去の発表 : 「現場で使える Django のセキュリティ対策」 (DjangoCongress JP 2019) 自費出版本 : 3 『現場で使える Django の教科書《基礎編》』 『現場で使える Django の教科書《実践編》』 『現場で使える Django REST Framework の教科書』 『現場で使える Django 管理サイトのつくり方』
  5. 皆さん 管理サイト、使ってますか? 4

  6. (しまった … オンライン登壇だった) 5

  7. 開発者の9割が管理サイトを使っている 6 Q.管理サイトを使っていますか?(N=92) 約9割が「いつも使っている」 あるいは「たまに使っている」 と回答 akiyoko 独自アンケート(2020年8月実施)より

  8. 開発者からの評価も高い 7 4000名を超えるアンケートで 「管理サイト(admin)」が 最も利用価値の高い組み込み アプリケーションに選出される Q.What contrib apps are

    most valuable to you?(N=4087) https://www.djangoproject.com/weblog/2020/jul/28/community-survey-2020/ Django Developers Community Survey 2020
  9. 手軽で便利に使えるのが大きなメリット 8 Q.どんなところに管理サイトのメリットを感じますか?(N=89) akiyoko 独自アンケート(2020年8月実施)より 手軽で便利!

  10. 管理サイトの代表的な困りごと 9 Q.管理サイトのデメリットを挙げるとすればどんなものがありますか?(N=84) カスタマイズ系の 困りごとが多い… akiyoko 独自アンケート(2020年8月実施)より

  11. このトークですべて解決します 😉 困りごと①:どんなカスタマイズが簡単にできるのか分からない 困りごと②:テンプレートを修正するのにコツが要る 困りごと③:画面のスタイルを変えるのが大変 困りごと④:コードが断片化しやすくテストがしづらい 困りごと⑤:日本語の情報が少ない 10 すべて 解決します!

  12. その前に… 管理サイトって何? 11

  13. 管理サイトとは 12 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages',

    'django.contrib.staticfiles', ] ひな型プロジェクトの設定ファイル(settings.py) モデルに対応したレコードの CRUD(追加・参照・変更・削除)画面 を提供してくれる Django 組み込みのアプリケーション
  14. 基本仕様 画面遷移 画面遷移 13 [<アプリ [<モデル名>] [追加] [追加] [<モデル名> [パスワードの変更]

    [保存してもう一つ追加] [保存] リンクを押下 [保存] 編集を続ける] [保存して編集を続ける] [削除] [選択された<モデル名>の削除] [削除]or[戻る] [戻る] [履歴] [最近行った操作] モデル変更画面へ [ログアウト] [もう一度ログイン] [<モデル名>]or[変更] [保存してもう一つ追加] [パスワードの変更] モデル 追加画面 アプリケーション ホーム画面 ホーム画面 モデル 削除確認画面 パスワード 変更画面 モデル 一覧画面 モデル 変更画面 [ログイン] ログアウト 完了画面 モデル 変更履歴画面 を追加] (一覧画面から遷移した場合) [保存して パスワード 変更完了画面 (編集画面から遷移た場合) ケーション名>] or[変更] ログイン画面
  15. 基本仕様 ログイン条件 ログイン条件 • is_staff と is_active 属性の値が いずれも True

    であること 14 Django デフォルトの User モデル
  16. 基本仕様 パーミッション 15 追加(add) Book 変更(change) Book 削除(delete) Book 参照(view)

    Book ⇒ Book モデルの追加が可能 ⇒ Book モデルの参照が可能 ⇒ Book モデルの削除が可能 ⇒ Book モデルの変更が可能 モデル 操作 パーミッション × = • モデルの CRUD 操作を実行するためには、モデルごと・操作ごとの「パーミッション」と呼ばれ る権限が必要 • ユーザーの追加画面・変更画面から「ユーザーパーミッション」として紐付けることが可能 • is_superuser が True のユーザーは、すべてのパーミッションがあるとみなされる パーミッション(認可とアクセス制御) グループ ユーザー パーミッション ( User ) ( Group ) ( Permission )
  17. ポイント①:どんなカスタマイズが簡単にできるのか分からない ポイント②:テンプレートを修正するのにコツが要る ポイント③:画面のスタイルを変えるのが大変 ポイント④:コードが断片化しやすくテストがしづらい ポイント⑤:日本語の情報が少ない 16 そもそもどんなカスタマイズ ができるの? ある程度以上のカスタマイズ になると急に難易度が上がる

    簡単にカスタマイズできるかどうか のジャッジにノウハウや調査が必要
  18. ポイント① まずは全体構造を理解しよう 17 全体構造(AdminSite クラスと ModelAdmin クラス) 管理サイトをカスタマイズする前に知っておきたいこと

  19. ポイント① AdminSite と ModelAdmin 18 ルートURLconf(<設定ディレクトリ>/urls.py) from django.contrib import admin

    from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ] admin.site は AdminSite の グローバルオブジェクト shop/admin.py from django.contrib import admin from .models import Book class BookAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'price') admin.site.register(Book, BookAdmin) 管理サイトにモデルを登録する際、 ModelAdmin の派生クラスを第2引数で渡す (省略すると素の ModelAdmin が使われる) AdminSite クラス ModelAdmin クラス
  20. ポイント① 管理サイトの全体構造 19 View View View View モデル ModelAdmin View

    View 管理サイト用 テンプレート AdminSite View View モデル フォーム U R L デ ィ ス パ ッ チ ャ /admin/<アプリケーション名>/<モデル名>/… の URLパターンには ModelAdmin が対応 /admin/… の URLパターンには AdminSite が対応 全体の画面項目や挙動 ⇒ AdminSite クラスに定義 モデルごとの画面項目や挙動 ⇒ ModelAdmin クラスに定義 全体構造
  21. ポイント① 管理サイトのカスタマイズ難易度 20 簡単! コツが必要! ⇒ ポイント② ③ で説明 1)

    AdminSite を利用して、全体の設定をカスタマイズ 2) ModelAdmin を利用して、モデルごとの画面項目をカスタマイズ 3) テンプレートの仕組みを利用して、テンプレートをカスタマイズ 4) 静的ファイルの仕組みを利用して、CSS をカスタマイズ
  22. ポイント① 21 1) AdminSite を利用して、全体の設定をカスタマイズ 2) ModelAdmin を利用して、モデルごとの画面項目をカスタマイズ

  23. ポイント① 1) AdminSite の主要なクラス変数・メソッド (1/2) 22 # クラス変数 説明 1

    site_header 共通ヘッダの左側に表示されるサイト名。デフォルト値は「Django 管理サイト」 2 site_title <title> タグの一部として表示される値。デフォルト値は「Django サイト管理」 3 site_url 共通ヘッダの右側に表示される「サイトを表示」のリンク先の URL。None をセット するとリンク自体が非表示になる。デフォルト値は「/」 4 login_template ログイン画面のテンプレートのパス 5 login_form ログインビューで使われるフォームクラス 6 logout_template ログアウト完了画面のテンプレートのパス 7 index_title ホーム画面の <h1> タグに表示されるタイトル。デフォルト値は「サイト管理」 8 index_template ホーム画面のテンプレートのパス 9 app_index_template アプリケーションホーム画面のテンプレートのパス 10 password_change_template パスワード変更画面のテンプレートのパス 11 password_change_done_template パスワード変更完了画面のテンプレートのパス 12 empty_value_display モデル一覧画面で値が None の場合に表示される文字列。デフォルト値は「-」 13 enable_nav_sidebar Django 3.1 で追加されたナビゲーションサイドバーを表示するかどうか (参考)https://docs.djangoproject.com/ja/3.2/ref/contrib/admin/#adminsite-attributes AdminSite の主要なクラス変数
  24. ポイント① 1) AdminSite の主要なクラス変数・メソッド (2/2) 23 # メソッド 説明 1

    has_permission 管理サイトにアクセスするために必要な条件を定義 2 get_urls 管理サイトで使用する URLのパターンと対応するビューのリストを定義 3 each_context 各テンプレートに渡すキーと値を定義 4 login ログイン画面表示およびログイン実行時のビュー 5 logout ログアウト実行時のビュー 6 index ホーム画面を表示するためのビュー 7 app_index アプリケーションホーム画面を表示するためのビュー 8 password_change パスワード変更画面表示およびパスワード変更実行時のビュー 9 password_change_done パスワード変更完了画面を表示するためのビュー AdminSite の主要なメソッド (参考)https://docs.djangoproject.com/ja/3.2/ref/contrib/admin/#adminsite-methods
  25. ポイント① 1) AdminSite を使ったカスタマイズ例 ルートURLconf の中で admin.site の属性をダイレクトに書き換えてしまうのが簡単 24 ルートURLconf(<設定ディレクトリ>/urls.py)

    from django.contrib import admin from django.urls import path def has_permission(request): return request.user.is_active admin.site.site_header = 'システム管理者用サイト' admin.site.site_title = 'マイプロジェクト' admin.site.index_title = 'ホーム' admin.site.site_url = None admin.site.has_permission = has_permission urlpatterns = [ path('admin/', admin.site.urls), ] AdminSite 派生クラスを作成して、起動時に読み込ませることも可能 https://docs.djangoproject.com/ja/3.2/ref/contrib/admin/#overriding-the-default-admin-site
  26. ポイント① 25 1)AdminSite を利用して、全体の設定をカスタマイズ 2)ModelAdmin を利用して、モデルごとの画面項目をカスタマイズ

  27. ポイント① 2) ModelAdmin のクラス変数・メソッド (1/3) 26 # クラス変数 説明 1

    actions アクション一覧の設定 2 actions_on_bottom アクション一覧をテーブルの下側に表示するかどうかの設定(デフォルト値は False) 3 actions_on_top アクション一覧をテーブルの上側に表示するかどうかの設定(デフォルト値は True) 4 date_hierarchy 日付ドリルダウンナビゲーションの設定 5 empty_value_display フィールドの値が None の場合に表示する文字列 6 list_display モデル一覧画面に表示するフィールドの設定 7 list_display_links どの項目にモデル変更画面へのリンクを張るかを指定 8 list_editable モデル一覧画面で変更を保存したいフィールドの設定 9 list_filter 絞り込み(フィルタ)の設定 10 list_max_show_all ページネーションUIに「全件表示」リンクを表示するための検索結果件数のしきい値 11 list_per_page ページネーションUIを表示するための検索結果件数のしきい値 12 list_select_related オブジェクト検索時に関連先モデルを JOIN して検索するようにするための設定 13 ordering 初期表示時のソートの設定 14 search_fields 簡易検索の設定 15 sortable_by ソート可能な項目を限定するための設定 ModelAdmin の主要なクラス変数(モデル一覧画面用)
  28. ポイント① 2) ModelAdmin のクラス変数・メソッド (2/3) 27 # クラス変数 説明 1

    autocomplete_fields ForeignKey や ManyToManyField のフィールドのウィジェットを、オートコンプリート可能な 入力フィールドに変更するための設定 2 exclude モデル追加・変更画面に表示しないフィールドの設定 3 fields モデル追加・変更画面に表示するフィールドの設定 4 fieldsets モデル追加・変更画面のフィールドをセクション区切りで表示するための設定 5 form モデル追加画面・変更画面で利用する ModelForm クラスを差し替えるための設定 6 formfield_overrides モデルのフィールドクラスに対応するフィールドオプションのマッピングを変更するための設定 7 inlines インライン表示のための設定 8 readonly_fields テキスト表示をするフィールドの設定 9 save_as モデル変更画面に「保存してもう一つ追加」ボタンの代わりに、「新規保存」ボタンを表示する かどうかの設定(デフォルト値は False) 10 save_as_continue モデル変更画面で「新規保存」ボタンを押下した後のリダイレクト先をモデル一覧画面にするか どうかの設定(デフォルト値は True) 11 save_on_top フォーム上部に「保存」ボタンや「削除」ボタンを追加するかどうかの設定(デフォルト値は False) ModelAdmin の主要なクラス変数(モデル追加・変更画面用) (参考)https://docs.djangoproject.com/ja/3.2/ref/contrib/admin/#modeladmin-options
  29. ポイント① 2) ModelAdmin のクラス変数・メソッド (3/3) 28 # クラス変数 説明 1

    add_view モデル追加画面表示およびモデル追加実行時のビュー 2 change_view モデル変更画面表示およびモデル変更実行時のビュー 3 changelist_view モデル一覧画面を表示するためのビュー 4 delete_view モデル削除確認画面表示およびモデル削除実行時のビュー 5 get_actions アクション一覧を返す 6 get_queryset モデル一覧画面でオブジェクトを検索するための QuerySet オブジェクトを返す 7 get_urls モデルごとの URLのパターンと対応するビューのリストを返す 8 has_add_permission モデルが追加できるかどうかを判定する 9 has_change_permission モデルが変更できるかどうかを判定する 10 has_delete_permission モデルが削除できるかどうかを判定する 11 has_view_permission モデルが参照できるかどうかを判定する 12 has_module_permission ホーム画面にモデルを表示できるかどうかを判定する 13 save_model モデルを保存する 14 save_formset インライン側のモデルを保存する。インライン表示を設定している場合にのみ呼び出される 15 delete_model モデルを削除する ModelAdmin の主要なメソッド (参考)https://docs.djangoproject.com/ja/3.2/ref/contrib/admin/#modeladmin-methods
  30. ポイント① 2) ModelAdmin を使ったカスタマイズ例 (1/5) 29 shop/admin.py from django.contrib import

    admin from .models import Book class BookAdmin(admin.ModelAdmin): # 画面表示フィールド list_display = ('id', 'title', 'price', 'size', 'publish_date') admin.site.register(Book, BookAdmin) (モデル一覧画面)画面表示フィールド: list_display
  31. ポイント① 2) ModelAdmin を使ったカスタマイズ例 (2/5) 30 (モデル一覧画面)簡易検索: search_fields shop/admin.py from

    django.contrib import admin from .models import Book class BookAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'price', 'size', 'publish_date') # 簡易検索 search_fields = ('title', 'publisher__name', 'authors__name') admin.site.register(Book, BookAdmin)
  32. ポイント① 2) ModelAdmin を使ったカスタマイズ例 (3/5) 31 (モデル一覧画面)フィルター: list_filter shop/admin.py from

    django.contrib import admin from .models import Book class BookAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'price', 'size', 'publish_date') # フィルター list_filter = ('size', 'price', 'publish_date') admin.site.register(Book, BookAdmin)
  33. ポイント① 2) ModelAdmin を使ったカスタマイズ例 (4/5) 32 (モデル一覧画面)アクション一覧: actions shop/admin.py from

    django.contrib import admin from django.utils import timezone from .models import Book class BookAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'price', 'size', 'publish_date') # アクション一覧 actions = ['publish_today'] @admin.action( description='出版日を今日に更新', permissions=('change',), ) def publish_today(self, request, queryset): """選択されたレコードの出版日を今日に更新する""" queryset.update(publish_date=timezone.localdate()) admin.site.register(Book, BookAdmin)
  34. ポイント① 2) ModelAdmin を使ったカスタマイズ例 (5/5) 33 (モデル追加・変更画面)フォーム: form shop/admin.py from

    django.contrib import admin from .forms import BookAdminForm from .models import Book class BookAdmin(admin.ModelAdmin): # フォーム form = BookAdminForm admin.site.register(Book, BookAdmin) shop/forms.py class BookAdminForm(forms.ModelForm): def clean_price(self): value = self.cleaned_data.get('price', 0) if value > 10000: raise forms.ValidationError( "価格は1万円を超えないでください。") return value
  35. ポイント① まとめ 34 AdminSite や ModelAdmin のクラス変数やメソッドを利用して 特定の画面項目や挙動をカスタマイズするのは簡単 どんなカスタマイズが簡単にできるのか分からない

  36. ポイント①:どんなカスタマイズが簡単にできるのか分からない ポイント②:テンプレートを修正するのにコツが要る ポイント③:画面のスタイルを変えるのが大変 ポイント④:コードが断片化しやすくテストがしづらい ポイント⑤:日本語の情報が少ない 35 テンプレートってどうやって カスタマイズするの? どのテンプレートをカスタマイズ すればいいか分からない

  37. ポイント② まずは仕組みを理解しよう 36 テンプレートをカスタマイズする前に知っておきたいこと3つ 1) テンプレートの優先順位 2) テンプレートの継承 3) 管理サイト本体のテンプレート構成

  38. ポイント② 37 テンプレートをカスタマイズする前に知っておきたいこと3つ 1) テンプレートの優先順位 2) テンプレートの継承 3) 管理サイト本体のテンプレート構成

  39. ポイント② 1) テンプレートの優先順位 38 設定ファイルの「TEMPLATES」の「DIRS」で指定 したディレクトリ 「INSTALLED_APPS」に登録されたインストール済み アプリケーションのうち、「django.contrib.admin」より も上に追加しているアプリケーション配下の 「templates」ディレクトリ

    管理サイト本体のテンプレート用ディレクトリ(インス トールディレクトリ配下「contrib/admin/templates/」) INSTALLED_APPS = [ 'myadmin.apps.MyadminConfig', 'django.contrib.admin', ... ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, ... }, ] 優先順位 ② 優先順位 ① 優先順位 ③ mysite (← ベースディレクトリ) . |-- myadmin (←「INSTALLED_APPS」で上位に追加したアプリケーション) | . | |-- templates | | `-- admin | | `-- index.html ★ 優先順位 ② | . | |-- templates (←「TEMPLATES」の「DIRS」で指定したディレクトリ) | `-- admin | `-- index.html ★ 優先順位 ① . . | (↓ 管理サイト本体のテンプレートが配置されたディレクトリ) `-- venv/Lib/site-packages/django/contrib/admin/templates `-- admin `-- index.html ★ 優先順位 ③
  40. ポイント② 39 テンプレートをカスタマイズする前に知っておきたいこと3つ 1) テンプレートの優先順位 2) テンプレートの継承 3) 管理サイト本体のテンプレート構成

  41. ポイント② 2) テンプレートの継承 40 django/contrib/admin/templates/admin/base.html(管理サイト本体側) <ベースディレクトリ>/templates/admin/base.html ...(略)... {% block welcome-msg

    %} {% translate 'Welcome,' %} <strong>{% firstof user.get_short_name user.get_username %}</strong>. {% endblock %} ...(略)... テンプレートを extends タグで 継承して、特定の block タグの 内容を書き換えることが可能 {% extends "admin/base.html" %} {% block welcome-msg %} {% firstof user.get_short_name user.get_username %} としてログイン中 {% endblock %}
  42. ポイント② 41 テンプレートをカスタマイズする前に知っておきたいこと3つ 1) テンプレートの優先順位 2) テンプレートの継承 3) 管理サイト本体のテンプレート構成

  43. ポイント② 3) 管理サイト本体のテンプレート構成 (1/3) 42 管理サイト本体のテンプレートは Django のインストールディレクトリ配下の contrib/admin/templates/ に配置されている

    Windows の場合 venv/Lib/site-packages/django/contrib/admin/templates macOS の場合 venv/lib/python3.9/site-packages/django/contrib/admin/templates
  44. ポイント② 3) 管理サイト本体のテンプレート構成 (2/3) どの画面でどのテンプレートが使われているか? 43 # 画面 メインとなる管理サイト本体のテンプレートファイル 1

    ログイン画面 admin/login.html 2 ホーム画面 admin/index.html 3 アプリケーションホーム画面 admin/app_index.html 4 モデル一覧画面 admin/change_list.html 5 モデル追加画面 admin/change_form.html 6 モデル変更画面 admin/change_form.html 7 モデル削除確認画面 admin/delete_confirmation.html(モデル変更画面から遷移した場合) admin/delete_selected_confirmation.html(モデル一覧画面から遷移した場合) 8 モデル変更履歴画面 admin/object_history.html 9 パスワード変更画面 registration/password_change_form.html 10 パスワード変更完了画面 registration/password_change_done.html 11 ログアウト完了画面 registration/logged_out.html
  45. ポイント② 3) 管理サイト本体のテンプレート構成 (3/3) モデル一覧画面 44 admin/base.html ↑ admin/base_site.html ↑

    admin/change_list.html ↓ admin/nav_sidebar.html admin/app_list.html admin/change_list_object_tools.html admin/search_form.html admin/actions.html admin/change_list_results.html admin/pagination.html admin/filter.html 必ず継承 extendsタグだけでなく、includeタグや テンプレートタグによって、さまざまな テンプレートが使われている どのテンプレートが使われている かは「django-debug-toolbar」の 「Templates」パネルを利用すれ ば簡単に確認可能 メイン
  46. ポイント② テンプレートのカスタマイズ方針 45 View View ModelAdmin AdminSite テンプレートの優先順位 + 継承

    の仕組みを使ってカスタマイズ プロジェクト内のテンプレート (「TEMPLATES」の「DIRS」ディレクトリ) 管理サイト本体のテンプレート {% extends "admin/base.html" %} {% block xxx %} この内容で書き換え {% endblock %} admin/pagination.html admin/pagination.html 継承がうまく使えなけれ ば、管理サイト本体のも のと同名のテンプレート ファイルでまるまる置き 換えることも可能 {% block xxx %} 書き換え対象の文字列 {% endblock %} admin/base.html admin/base.html
  47. ポイント② テンプレートのカスタマイズ例 46 <ベースディレクトリ>/templates/admin/base.html {% extends "admin/base.html" %} {% block

    welcome-msg %} {% firstof user.get_short_name user.get_username %} としてログイン中 {% endblock %} <設定ディレクトリ>/settings.py TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], ... }, ]
  48. ポイント② まとめ 47 テンプレートの優先順位 + 継承 の仕組みを使ってカスタマイズ 「TEMPLATES」の「DIRS」で指定したディレクトリに 管理サイト本体のテンプレートと同名のファイルを配置 テンプレートを修正するのにコツが要る

  49. ポイント①:どんなカスタマイズが簡単にできるのか分からない ポイント②:テンプレートを修正するのにコツが要る ポイント③:画面のスタイルを変えるのが大変 ポイント④:コードが断片化しやすくテストがしづらい ポイント⑤:日本語の情報が少ない 48 どうやって新しい CSS ファイル を読み込めばいいのか分からない

    CSS ファイルは どこに置けばいいの?
  50. ポイント③ まずは仕組みを理解しよう 49 CSS をカスタマイズする前に知っておきたいこと3つ 1) 静的ファイルの優先順位 2) テンプレートを継承して CSS

    ファイルを追加するための仕組み 3) ModelAdmin で CSS ファイルを追加するための仕組み
  51. ポイント③ 50 CSS をカスタマイズする前に知っておきたいこと3つ 1) 静的ファイルの優先順位 2) テンプレートを継承して CSS ファイルを追加するための仕組み

    3) ModelAdmin で CSS ファイルを追加するための仕組み
  52. ポイント③ 1) 静的ファイルの優先順位 51 設定ファイルの「STATICFILES_DIRS」で 指定したディレクトリ 「INSTALLED_APPS」に登録されたインストール済み アプリケーションのうち、「django.contrib.admin」より も上に追加しているアプリケーション配下の「static」 ディレクトリ

    INSTALLED_APPS = [ 'myadmin.apps.MyadminConfig', 'django.contrib.admin', ... ] STATICFILES_DIRS = [BASE_DIR / 'static'] 優先順位 ① 優先順位 ② 管理サイト本体の静的ファイル用ディレクトリ(インス トールディレクトリ配下の「contrib/admin/static/」) 優先順位 ③ mysite (← ベースディレクトリ) . |-- myadmin (←「INSTALLED_APPS」で上位に追加したアプリケーション) | . | |-- static | | `-- admin | | `-- css | | `-- base.css ★ 優先順位 ② | . | |-- static (←「STATICFILES_DIRS」で指定したディレクトリ) | `-- admin | `-- css | `-- base.css ★ 優先順位 ① . . | (↓ 管理サイト本体の静的ファイルが配置されたディレクトリ) `-- venv/Lib/site-packages/django/contrib/admin/static `-- admin `-- css `-- base.css ★ 優先順位 ③
  53. ポイント③ 52 CSS をカスタマイズする前に知っておきたいこと3つ 1) 静的ファイルの優先順位 2) テンプレートを継承して CSS ファイルを追加するための仕組み

    3) ModelAdmin で CSS ファイルを追加するための仕組み
  54. ポイント③ 2) テンプレート継承で CSS ファイルを追加 (1/2) 53 django/contrib/admin/templates/admin/base.html 管理サイト本体の base.html

    の extrastyle ブロックをオーバーライドする <head> ... <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}"> ... {% block extrastyle %}{% endblock %} ... {% block extrahead %}{% endblock %} {% block responsive %} ... <link rel="stylesheet" type="text/css" href="{% static "admin/css/responsive.css" %}"> ... {% endblock %} ... </head> モバイルおよびタブレット用のスタイル定義 管理サイト全体の基本スタイルが定義されている 各画面のメインコンテンツのテンプレートからオーバーライドして追 加の CSSファイルを読み込むために用意されたブロック。例えば、 「admin/login.html」では「admin/css/login.css」が読み込まれる モデルごとの画面のテンプレートからオーバーライドして 任意の静的ファイルを読み込むために用意されたブロック。 後述する ModelAdmin の Media.css でも利用される
  55. ポイント③ 2) テンプレート継承で CSS ファイルを追加 (2/2) 54 View View ModelAdmin

    AdminSite プロジェクト内のテンプレート (「TEMPLATES」の「DIRS」ディレクトリ) 管理サイト本体のテンプレート admin/base.html admin/base.html 継承したテンプレートで extrastyle ブロックを書き換えて、CSSファイルを追加する {% extends "admin/base.html" %} {% block extrastyle %} <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base_extra.css' %}"> {% endblock %} CSS admin/css/base_extra.css プロジェクト内の静的ファイル (「STATICFILES_DIRS」ディレクトリ) CSS admin/css/base.css など CSS CSS CSS 管理サイト本体の静的ファイル
  56. ポイント③ 2) CSS のカスタマイズ例 55 <ベースディレクトリ>/static/admin/css/base_extra.css #header { background: #f08e3e;

    /* ヘッダの背景色をオレンジ系に */ } #branding h1 { font-size: 1.4em; /* ヘッダのサイト名のフォントを小さくする */ } <ベースディレクトリ>/templates/admin/base.html {% extends "admin/base.html" %} {% load static %} {% block extrastyle %} <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base_extra.css' %}"> {% endblock %} <設定ディレクトリ>/settings.py STATICFILES_DIRS = [BASE_DIR / 'static'] base.html や base_site.html 以外のテンプレート を継承する場合は、{{ block.super }} を使って親テ ンプレートの内容を残しつつ、新たなCSSファイル を読み込むための記述をするように注意!
  57. ポイント③ 56 CSS をカスタマイズする前に知っておきたいこと3つ 1) 静的ファイルの優先順位 2) テンプレートを継承して CSS ファイルを追加するための仕組み

    3) ModelAdmin で CSS ファイルを追加するための仕組み
  58. ポイント③ 3) ModelAdmin で CSS ファイルを追加 57 shop/admin.py from django.contrib

    import admin from .models import Book class BookAdmin(admin.ModelAdmin): class Media: css = { 'all': ('admin/css/book.css',) } admin.site.register(Book, BookAdmin) ModelAdmin の「Media.css」で指定した CSS ファイルは、 モデル一覧画面・追加画面・変更画面・削除確認画面のテンプレート (の extrahead ブロック)で読み込まれる
  59. ポイント③ 3) CSS のカスタマイズ例 58 <ベースディレクトリ>/static/admin/css/book.css .change-list table .field-title {

    background: #fffacd; /* タイトル列の背景色を黄色系に */ color: red; /* タイトル列の文字色を赤に */ } shop/admin.py from django.contrib import admin from .models import Book class BookAdmin(admin.ModelAdmin): class Media: css = { 'all': ('admin/css/book.css',) } admin.site.register(Book, BookAdmin) <設定ディレクトリ>/settings.py STATICFILES_DIRS = [BASE_DIR / 'static']
  60. ポイント③ まとめ 59 テンプレートで extrastyle ブロックをオーバーライドして or ModelAdmin の「Media.css」を使って 画面のスタイルを変えるのが大変

    新しい CSS ファイルを読み込む
  61. ポイント①:どんなカスタマイズが簡単にできるのか分からない ポイント②:テンプレートを修正するのにコツが要る ポイント③:画面のスタイルを変えるのが大変 ポイント④:コードが断片化しやすくテストがしづらい ポイント⑤:日本語の情報が少ない 60

  62. ポイント④ 困りごと 61 コードが断片化しやすくテストがしづらい カバレッジを100%にしても あまり意味がないのでは? shop/admin.py from django.contrib import

    admin from .models import Book class BookAdmin(admin.ModelAdmin): list_display = ('id', 'title', 'price', 'size', 'publish_date') search_fields = ('title', 'publisher__name', 'authors__name') list_filter = ('size', 'price', 'publish_date') list_per_page = 10 actions = ['publish_today'] @admin.action( description='出版日を今日に更新', permissions=('change',), ) def publish_today(self, request, queryset): queryset.update(publish_date=timezone.localdate()) admin.site.register(Book, BookAdmin) 品質を担保するためには どんなテストをすればいいの?
  63. ポイント④ 効率的なテストをしよう 62 管理サイトのテストを効率化できるパッケージを使う 1) lxml で HTML をパースして画面部品を検証 2)

    Selenium でブラウザテスト
  64. ポイント④ 63 管理サイトのテストを効率化できるパッケージを使う 1) lxml で HTML をパースして画面部品を検証 2) Selenium

    でブラウザテスト
  65. ポイント④ 1) lxml を使って画面部品を検証 lxml は、HTML や XML を解析するためのライブラリ 64

    response HTMLElement HTMLElement HTMLElement rendered_content (HTML文字列) Django 標準の TestCase クラスのテストクライアントで取得したレスポンスの rendered_content 属性 でレンダリング後の HTML が取れるので、それを lxml でパースすれば画面項目の検証が可能 管理サイト側の HTML(要素の構造や class 属性・id 属性など)は固定されているため、 画面側の都合でテストコードを書き直さなくて済む
  66. ポイント④ 1) lxml を使ったテストクラスの実装例 (1/2) 65 shop/tests/test_admin_book_change_list.py import datetime from

    django.contrib.auth import get_user_model from django.test import TestCase from .lxml_helpers import ChangeListPage from ..models import Book User = get_user_model() class TestAdminBookChangeList(TestCase): """管理サイトの Book モデル一覧画面のユニットテスト(システム管理者の場合)""" def setUp(self): # テストユーザー(システム管理者)を作成 self.superuser = User.objects.create_superuser('admin', 'admin@example.com', 'pass12345') # Bookモデルのテストレコードを作成 self.book = Book.objects.create(title='Book 1', price=1000, publish_date=datetime.date(2021, 7, 3)) def test_page_items_for_result_list(self): # 管理サイトにログイン self.client.login(username=self.superuser.username, password='pass12345') # モデル一覧画面に遷移するためのリクエストを実行 response = self.client.get('/admin/shop/book/') self.assertEqual(response.status_code, 200) # 画面項目を検証 page = ChangeListPage(response.rendered_content) # 検索結果テーブル self.assertEqual(page.result_list_header_texts, ['ID', 'タイトル', '価格', 'サイズ', '出版日']) self.assertEqual(len(page.result_list_rows_texts), 1) self.assertEqual(page.result_list_rows_texts[0], ['Book 1', '1000', '-', '2021年7月3日'])
  67. ポイント④ 1) lxml を使ったテストクラスの実装例 (2/2) 66 shop/tests/lxml_helpers.py import lxml.html class

    ChangeListPage: """モデル一覧画面の画面項目検証用クラス""" def __init__(self, rendered_content): self.parsed_content = lxml.html.fromstring(rendered_content) @property def result_list(self): """検索結果テーブルのHTMLElementオブジェクト""" elements = self.parsed_content.xpath('//table[@id="result_list"]') return elements[0] if elements else None @property def result_list_header_texts(self): """検索結果テーブルのヘッダの表示内容""" if self.result_list is None: return None head = self.result_list.xpath('thead/tr')[0] return [e.text_content() for e in head.xpath('th[contains(@class, "column-")]/div[@class="text"]')] @property def result_list_rows_texts(self): """検索結果テーブルのデータ行の表示内容""" if self.result_list is None: return None rows = self.result_list.xpath('tbody/tr') return [[e.text_content() for e in row.xpath('td[contains(@class, "field-")]')] for row in rows]
  68. ポイント④ 67 管理サイトのテストを効率化できるパッケージを使う 1) lxml で HTML をパースして画面部品を検証 2) Selenium

    でブラウザテスト
  69. ポイント④ 2) Seleinum でブラウザテスト Selenium はプログラムからブラウザを操作するためのツール 68 HTTP リクエスト HTTP

    レスポンス ブラウザ (Chrome) Selenium ChromeDriver ③’ ブラウザを操作 WebDriver Webアプリケーション (Django プロジェクト) Webサーバ Selenium には Webブラウザの API を利用するための WebDriver インタフェースと主要なブラウザ向けの実装クラスが含まれている ( Chrome を利用する場合は ChromeDriver が別途必要) $ pip install selenium chromedriver-binary==91.* ・Python では selenium パッケージのインストールが必要 ・ChromeDriver には chromedriver-binary パッケージが利用可能
  70. ポイント④ 2) AdminSeleniumTestCase クラス 69 Django unittest django.test.testcases SimpleTestCase django.test.testcases

    TransactionTestCase unittest.case TestCase django.test.testcases TestCase django.contrib.staticfiles.testing StaticLiveServerTestCase django.test.testcases LiveServerTestCase django.test.selenium SeleniumTestCase django.contrib.admin.tests AdminSeleniumTestCase Django 組み込みの TestCase クラス Django 標準の TestCase クラス • Selenium のテストには StaticLiveServerTestCaseを利用する • 特に、管理サイトの場合には AdminSeleniumTestCase を利用する setUpClass で WebDriver インスタンス(テスト メソッドからは self.selenium でアクセス可)を 作成して、さらに Webサーバを起動してくれる
  71. ポイント④ 2) AdminSeleniumTestCase テストの実装例 (1/2) 70 shop/tests/test_admin_senario.py # chromedriver_binaryをimportすることでChromeDriverのパスを通してくれる import

    chromedriver_binary from django.contrib.admin.tests import AdminSeleniumTestCase from django.contrib.auth import get_user_model from selenium import webdriver User = get_user_model() class TestAdminSenario(AdminSeleniumTestCase): """管理サイトのシナリオテスト(システム管理者の場合)""" available_apps = None browser = 'chrome' @classmethod def create_webdriver(cls): """Chrome用のWebDriverインスタンスを作成する""" chrome_options = webdriver.ChromeOptions() # ヘッドレスモード chrome_options.add_argument('--headless') return webdriver.Chrome(chrome_options=chrome_options) def setUp(self): # テストユーザー(システム管理者)を作成 self.superuser = User.objects.create_superuser('admin', 'admin@example.com', 'pass12345') def assert_title(self, text): """タイトルを検証する""" self.assertEqual(self.selenium.title.split(' | ')[0], text) (次ページに続く)
  72. ポイント④ 2) AdminSeleniumTestCase テストの実装例 (2/2) 71 def test_book_crud(self): # 1.

    システム管理者でログイン self.admin_login(self.superuser.username, 'pass12345') self.assert_title('サイト管理') # ホーム画面でスクリーンショットを撮る(1枚目) self.selenium.save_screenshot(f'{self.id()}-1.png') # 2. ホーム画面で「本」リンクを押下 self.selenium.find_element_by_link_text('本').click() self.wait_page_loaded() self.assert_title('変更する 本 を選択') # モデル一覧画面でスクリーンショットを撮る(2枚目) self.selenium.save_screenshot(f'{self.id()}-2.png') # 3. モデル一覧画面で「本 を追加」ボタンを押下 self.selenium.find_element_by_link_text('本 を追加').click() self.wait_page_loaded() self.assert_title('本 を追加') # 4. モデル追加画面で項目を入力して「保存」ボタンを押下 self.selenium.find_element_by_name('title').send_keys('Book 1') self.selenium.find_element_by_name('price').send_keys('1000') self.selenium.find_element_by_name('publish_date').send_keys('2021-07-03') # モデル追加画面でスクリーンショットを撮る(3枚目) self.selenium.save_screenshot(f'{self.id()}-3.png') self.selenium.find_element_by_xpath('//input[@value="{}"]'.format('保存')).click() self.wait_page_loaded() self.assert_title('変更する 本 を選択') # モデル一覧画面でスクリーンショットを撮る(4枚目) self.selenium.save_screenshot(f'{self.id()}-4.png') # (以下略) (前ページから続く)
  73. ポイント④ まとめ 72 生成される HTMLを lxml でパースして画面部品を検証 + Selenium でブラウザテスト

    コードが断片化しやすくテストがしづらい
  74. ポイント①:どんなカスタマイズが簡単にできるのか分からない ポイント②:テンプレートを修正するのにコツが要る ポイント③:画面のスタイルを変えるのが大変 ポイント④:コードが断片化しやすくテストがしづらい ポイント⑤:日本語の情報が少ない 73 まとまった日本語の 情報が少ない いい本がないものか・・

  75. ポイント⑤ 解決策 😉 『現場で使える Django 管理サイトのつくり方』 74 74 【 Amazon

    】 【 BOOTH 】 (電子版) (ペーパー版) • 管理サイトだけにフォーカスした、ニッチでオンリーワンな一冊 • 本文 152ページ • 2020年9月発行、Django 2.2 LTS 対応
  76. ポイント⑤ まとめ 75 『現場で使える Django 管理サイトのつくり方』を現場に一冊! 日本語の情報が少ない

  77. まとめ 76

  78. このトークで話したこと 77 管理サイトをカスタマイズするにあたって 苦労するポイントとその解決策

  79. まとめ ポイント①:どんなカスタマイズが簡単にできるのか分からない ポイント②:テンプレートを修正するのにコツが要る ポイント③:画面のスタイルを変えるのが大変 ポイント④:コードが断片化しやすくテストがしづらい ポイント⑤:日本語の情報が少ない 78

  80. まとめ 79 ポイント①:どんなカスタマイズが簡単にできるのか分からない ポイント②:テンプレートを修正するのにコツが要る ポイント③:画面のスタイルを変えるのが大変 ポイント④:コードが断片化しやすくテストがしづらい ポイント⑤:日本語の情報が少ない 全体構造(AdminSiteとModelAdmin)を把握しよう テンプレートの優先順位と継承を使ってカスタマイズしよう CSSファイルを追加する仕組みを理解しよう

    lxml や Selenium を使おう 『現場で使える Django 管理サイトのつくり方』を読もう
  81. サンプルコード 80 https://github.com/akiyoko/djangoconjp2021-customize-admin DjangoCongress JP 2021 発表用サンプルコード

  82. 宣伝 (電子版・紙の本) 『現場で使えるDjango の教科書 《基礎編》』 『現場で使えるDjango の教科書 《実践編》』 (電子版) (紙の本)

    『現場で使える Django REST Framework の教科書』 (紙の本) (紙の本) 81 【 Amazon 】 【 BOOTH 】 (電子版) 技術書典11(7/10~)にて 3.2 対応の改訂版を頒布予定