PythonのCUIアプリでrichを使って進捗状況表示

Pythonでターミナル(コマンドライン)から利用するCUIのアプリケーションを作る際、処理の進捗状況を表示するときに少しリッチにしたい。

richというライブラリを使うと簡単にできました。このライブラリは、PythonCUIアプリを作る際にリッチなUIを簡単に作れる機能を提供してくれます。

GitHub - Textualize/rich: Rich is a Python library for rich text and beautiful formatting in the terminal.

インストール

richはpipでインストールできます。

(venv)$ pip install rich

進捗状況表示

ドキュメントに進捗状況表示の実装方法についてページがあります。

Progress Display — Rich 13.6.0 documentation

ドキュメントのコードを参考に、簡単な計算処理の進捗状況表示を試してみます。

main.py:

import time
from rich.progress import track

values = list(range(10))  # 0~9 の数値のリストを用意
result = 0

for value in track(values, description="処理中..."):
    result += value  # 値を足し合わせ
    time.sleep(0.5)  # 意図的に遅延させる

print(result)  # 結果表示

実行結果

Cygwinからsshで接続したUbuntu Linuxbash上:

Windows11のPowerShell 7上:

クロスプラットフォームで問題なく動作しています。便利でした。

Django 5.2で追加される複合主キーサポートを試す

Django 5.2 alpha 1がリリースされています。 Djangoのalphaリリースは、まだ開発中の扱いです。alpha, beta, rc, stable(=無印) の順でだいたいリリースされます。

Django 5.2 alpha 1 released | Weblog | Django

Django 5.2のリリースノートを見ると、 "Composite Primary Keys" (複合主キー)というのがあり、オッ、と思ったので試していきたいと思います。

Django 5.2 release notes - UNDER DEVELOPMENT | Django documentation | Django

ドキュメントにトピックとしてページが追加されています。

Composite primary keys | Django documentation | Django

これまでDjangoのORMは複合主キーをサポートしていなかったため、単一の主キー用のフィールドを追加するとか、ワークアラウンドで何とかやってきましたが、対応が追加されるのはうれしいですね。

試した環境は Python 3.13, Django 5.2a1, MySQL 8.0 です。

Django 5.2a1はpipでPyPIからインストールできます。

(venv)$ pip install Django==5.2a1

複合主キーを持つモデルを作成

ドキュメントに記載のあった Product, Order, OrderLineItem で試してみます。

myapp/models.py:

from django.db import models

class Product(models.Model):
    """製品"""
    name = models.CharField("名称", max_length=100)


class Order(models.Model):
    """注文"""
    reference = models.CharField("注文番号", max_length=20, primary_key=True)


class OrderLineItem(models.Model):
    """注文明細"""
    pk = models.CompositePrimaryKey("product_id", "order_id")
    product = models.ForeignKey(Product, verbose_name="製品", on_delete=models.CASCADE)
    order = models.ForeignKey(Order, verbose_name="注文", on_delete=models.CASCADE)
    quantity = models.IntegerField(verbose_name="数量")

スキーマを確認

makemigrations コマンドで 0001_initial.pyマイグレーションファイルを作成後、 sqlmigrate コマンドでcreate tableの内容を見てみます。

$ python manage.py sqlmigrate myapp 0001
--
-- Create model Order
--
CREATE TABLE `myapp_order` (`reference` varchar(20) NOT NULL PRIMARY KEY);
--
-- Create model Product
--
CREATE TABLE `myapp_product` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(100) NOT NULL);
--
-- Create model OrderLineItem
--
CREATE TABLE `myapp_orderlineitem` (`quantity` integer NOT NULL, `order_id` varchar(20) NOT NULL, `product_id` bigint NOT NULL, PRIMARY KEY (`product_id`, `order_id`));
ALTER TABLE `myapp_orderlineitem` ADD CONSTRAINT `myapp_orderlineitem_order_id_8feda485_fk_myapp_order_reference` FOREIGN KEY (`order_id`) REFERENCES `myapp_order` (`reference`);
ALTER TABLE `myapp_orderlineitem` ADD CONSTRAINT `myapp_orderlineitem_product_id_f0c8cf26_fk_myapp_product_id` FOREIGN KEY (`product_id`) REFERENCES `myapp_product` (`id`);

myapp_orderlineitem テーブルは複合主キーで作られるようになっていますね。 migrate コマンドでテーブル作成後にMySQLのほうでも describe コマンドでスキーマ定義を見てみましょう。

mysql> describe myapp_orderlineitem;
+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| quantity   | int         | NO   |     | NULL    |       |
| order_id   | varchar(20) | NO   | PRI | NULL    |       |
| product_id | bigint      | NO   | PRI | NULL    |       |
+------------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

order_idproduct_id が主キーになっています。

ORMを試す

ドキュメントにあるサンプルコードをDjango shellで試してみます。

Django 5.2からは、Django shellを起動する際にモデルが自動インポートされた状態になります。 -v 2 オプションを指定すると、インポートされたモデルの詳細が表示されます。 django-extensionsの shell_plus にあった機能ですね。

$ python manage.py shell -v 2
9 objects imported automatically, including:

  from myapp.models import OrderLineItem, Order, Product
  from django.contrib.sessions.models import Session
  from django.contrib.contenttypes.models import ContentType
  from django.contrib.auth.models import User, Group, Permission
  from django.contrib.admin.models import LogEntry

Python 3.13.1 (main, Dec  4 2024, 08:54:14) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> Order
<class 'myapp.models.Order'>

ProductとOrderを1件作成し、作成したproductとorderを指定したOrderLineItemを1件作成します。

>>> product = Product.objects.create(name="apple")
>>> order = Order.objects.create(reference="A755H")
>>> item = OrderLineItem.objects.create(product=product, order=order, quantity=1)
>>> item.pk
(1, 'A755H')

複合主キーの pk はタプルになりました。

ドキュメント通りですが、 pk 引数をタプルで指定することもできるようです。

>>> item = OrderLineItem(pk=(2, "B142C"))
>>> item.pk
(2, 'B142C')
>>> item.product_id
2
>>> item.order_id
'B142C'

filterメソッドにpkを指定する際もタプルでの指定となるようです。

>>> OrderLineItem.objects.filter(pk=(1, "A755H"))
<QuerySet [<OrderLineItem: OrderLineItem object ((1, 'A755H'))>]>

ORMを試すのはこのぐらいにします。

Django admin

現時点では複合主キーのモデルは Django admin には非対応とドキュメントに記載があります。

試してみます。

myapp/admin.py:

from django.contrib import admin

from .models import Product, Order, OrderLineItem

admin.site.register(Product)
admin.site.register(Order)
admin.site.register(OrderLineItem)

checkを実行してみると、エラーになりました。

$ python manage.py check
# ... 中略
    admin.site.register(OrderLineItem)
    ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/home/tokibito/sample_nullpobug/django/composite_pk/venv/lib/python3.13/site-packages/django/contrib/admin/sites.py", line 117, in register
    raise ImproperlyConfigured(
    ...<2 lines>...
    )
django.core.exceptions.ImproperlyConfigured: The model OrderLineItem has a composite primary key, so it cannot be registered with admin.

やはりダメなようです。

admin組み込みのワークアラウンドチャレンジ

ここからはワークアラウンドに少しチャレンジしてみます。

正式対応か、サードパーティのモジュールなどが出るのを待ったほうがよいですが、無理やりadminに組み込みを試します。

本番環境で使うべきではないコードです。参考程度に。

model._meta.is_composite_pk をチェックしてエラーを出しているので、無理やり外してみます。 is_composite_pk プロパティはreadonlyなので、クラス側から無理やり書き換えます。

myapp/admin.py(抜粋):

OrderLineItem._meta.__class__.is_composite_pk = False
admin.site.register(OrderLineItem)
OrderLineItem._meta.__class__.is_composite_pk = True

これで runserver はエラーがでずに起動できます。

一覧まではいけました。しかし、詳細ページはURLがタプルを文字列化したものになってしまっていたりして動かないです。

この辺はModelAdminやChangeListクラスをカスタマイズする必要がありそうですね。手間がかかるのでここまでにしておきます。

感想

業務システムなどで複合主キーが使われることがありますが、Djangoでは今まで扱づらかったです。

今回のDjangoの標準機能として複合主キーがサポートされるのはいいですね。

ドキュメントにはmodels.ForeignObjectを使って複合主キーに対してリレーションを設定する方法も記載されていました。

Django adminの対応は今後期待したいです。

macOSでmysqlclientのインストールでつまづいた

macOS環境でmysqlclientのインストールがうまくいかない」とチーム内で相談があり、自分の環境でもうまくいかなかったので解決までのメモを残しておきます。

環境は、Mac mini 2023 Apple M2, macOS Sonoma 14.6, Python3.12です。

MySQLクライアントとpkg-configはHomebrewでインストールしました。

brew install mysqlclient pkg-config

この状態で、venv環境でpip installでrequirements.txtからmysqlclientをインストールしたところ、pkg-configでmysqlclientのライブラリを見つけられない旨のエラーとなりました。

Collecting mysqlclient==2.2.6 (from -r requirements.txt (line 3))
  Using cached mysqlclient-2.2.6.tar.gz (91 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  error: subprocess-exited-with-error
  
  × Getting requirements to build wheel did not run successfully.
  │ exit code: 1
  ╰─> [29 lines of output]
      Trying pkg-config --exists mysqlclient
      Command 'pkg-config --exists mysqlclient' returned non-zero exit status 1.
      Trying pkg-config --exists mariadb
      Command 'pkg-config --exists mariadb' returned non-zero exit status 1.
      Trying pkg-config --exists libmariadb
      Command 'pkg-config --exists libmariadb' returned non-zero exit status 1.
      Trying pkg-config --exists perconaserverclient
      Command 'pkg-config --exists perconaserverclient' returned non-zero exit status 1.
      Traceback (most recent call last):
        File "/Users/okano/work/myproject/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
          main()
        File "/Users/okano/work/myproject/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/Users/okano/work/myproject/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 118, in get_requires_for_build_wheel
          return hook(config_settings)
                 ^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/23/9rdcmsds2d7ff4937xnd7t4h0000gn/T/pip-build-env-xtvhobpp/overlay/lib/python3.12/site-packages/setuptools/build_meta.py", line 334, in get_requires_for_build_wheel
          return self._get_build_requires(config_settings, requirements=[])
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/private/var/folders/23/9rdcmsds2d7ff4937xnd7t4h0000gn/T/pip-build-env-xtvhobpp/overlay/lib/python3.12/site-packages/setuptools/build_meta.py", line 304, in _get_build_requires
          self.run_setup()
        File "/private/var/folders/23/9rdcmsds2d7ff4937xnd7t4h0000gn/T/pip-build-env-xtvhobpp/overlay/lib/python3.12/site-packages/setuptools/build_meta.py", line 320, in run_setup
          exec(code, locals())
        File "<string>", line 155, in <module>
        File "<string>", line 49, in get_config_posix
        File "<string>", line 28, in find_package_name
      Exception: Can not find valid pkg-config name.
      Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env vars manually
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error

× Getting requirements to build wheel did not run successfully.
│ exit code: 1
╰─> See above for output.

StackOverflowに同様の事象の質問があり、回答にある PKG_CONFIG_PATH環境変数に設定したら解決しました。

stackoverflow.com

venvが有効な状態で、シェルからexportコマンドにて環境変数を設定

export PKG_CONFIG_PATH="/opt/homebrew/opt/mysql-client/lib/pkgconfig"

この状態で再度インストールを実施したところ、成功しました。

Pydanticのdataclassを試す

Pydanticは、Pythonでデータの検証(バリデーション)を実装するためのライブラリです。

基本的な入力型の検証はTypingを記述するだけで実装できるということで、最近の型ヒントを記述するPythonコードと組み合わせて使うことを想定しています。 docs.pydantic.dev 一方、Pythonには標準モジュールで、 dataclasses というのがあります。 docs.python.org dataclassesモジュールに含まれるdataclassデコレータを使うと、型ヒントを使って簡単にデータ型を記述できます。

Pydanticはこのdataclassと互換性のある機能を提供しているので、これを試してみます。

dataclassesでの記述

main.py:

from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str

user1 = User(id=123, name="foo")
print(user1)
user2 = User(id="abc", name="bar")
print(user2)

意図的に user2.id はint型ではない文字列を指定しています。実行結果はこうなります。

$ python main.py
User(id=123, name='foo')
User(id='abc', name='bar')

標準モジュールのdataclassで作成したクラスでは、特にバリデーションの処理はないので、Pythonのコードとして実行可能であればエラーは発生しません。

ただし、mypyで型チェックをしてみると、型の不正を検出できます。

$ mypy main.py
main.py:10: error: Argument "id" to "User" has incompatible type "str"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

pydantic.dataclassesでの記述

dataclassをPydanticのものに切り替えてみます。 importの1行だけ変更しました。

main.py:

from pydantic.dataclasses import dataclass  # ここだけ変更

@dataclass
class User:
    id: int
    name: str

user1 = User(id=123, name="foo")
print(user1)
user2 = User(id="abc", name="bar")
print(user2)

実行結果はこうなります。

$ python main.py
User(id=123, name='foo')
Traceback (most recent call last):
  File "/home/vagrant/tmp/pydantic/main.py", line 10, in <module>
    user2 = User(id="abc", name="bar")
            ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vagrant/tmp/pydantic/venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py", line 121, in __init__
    s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s)
pydantic_core._pydantic_core.ValidationError: 1 validation error for User
id
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
    For further information visit https://errors.pydantic.dev/2.10/v/int_parsing

pydanticのValidationErrorが発生しました。

標準モジュールのdataclassesと同様の書き方で、pydanticをすぐに導入できるのは興味深いです。

pydanticのドキュメントには、BaseModelを継承するのとdataclassを使うのでは、機能的な差があるとも書かれているので、利用する際には気を付けておいたほうが良さそうです。

Dataclasses - Pydantic

terapyon channel podcast #104 ゲスト参加

terapyon channel podcast にkyさんと一緒にゲスト参加で話してきました。

podcast.terapyon.net

トークの内容はDjangoとDSF、DjangoCongress JPのトーク募集などについてです。

DjangoCongress JP 2025 では、Python非同期Webのテーマでもトークを募集しているので、ぜひ応募よろしくお願いします。

tokibito.hatenablog.com

前回

tokibito.hatenablog.com

Djangoから手軽にTailwindCSSをセットアップできるdjango-tailwindを試す

CSSフレームワークである TailwindCSS は、通常NodeJSを使ってCSSをビルドします。

Djangoから利用する場合、TailwindCSSのビルド環境を整備した上で、ビルドされたCSSDjangoで読み込むように実装が必要になります。

django-tailwindを使うと、このセットアップとDjangoへの組み込みを手軽に実施できます。

django-tailwind.readthedocs.io

以下、 django-admin startproject myproject で作成したmyprojectでdjango-tailwindを試します。

インストールと設定

django-tailwindは内部で npm コマンドを実行するため、あらかじめ NodeJS をインストールしておく必要があります。

pipでdjango-tailwindをインストールします。

pip install django-tailwind

tailwind アプリをsettings.pyの INSTALLED_APPS に追加します。

INSTALLED_APPS = [
    # ...
    'tailwind',
]

これで manage.py tailwind コマンドや、django-tailwindが提供するテンプレートタグが有効になりました。

次にTailwindCSSのファイル群を格納するテーマアプリを作成します。 manage.py tailwind init を実行すると、cookiecutterがインストールされて、cookiecutterでテーマアプリが作られます。

python manage.py tailwind init

アプリ名はデフォルトのままだと theme という名前になります。

作成した theme アプリも INSTALLED_APPS に追加します。

INSTALLED_APPS = [
    # ...
    'tailwind',
    'theme',  # 追加
]

settings.pyに TAILWIND_APP_NAME の設定を追記します。値は作成したテーマのアプリ名です。

TAILWIND_APP_NAME = 'theme'

settings.pyに INTERNAL_IPS の設定を追加します。今回は開発用なので、localhostのアドレスだけを設定します。

INTERNAL_IPS = [
    '127.0.0.1',
]

TailwindCSSの依存関係をインストールします。

python manage.py tailwind install

上記のコマンドは、内部で npm install を実行します。

これでインストールと利用設定は完了です。

テンプレートを作成して利用する

デフォルトでは theme/templates/base.html が作成されていますが、これは編集可能です。またパスがtemplates直下であることに気をつけておいたほうがよさそうです。他のアプリのために base.html を用意している場合、衝突する可能性があります。

theme/templates/base.html:

{% load static tailwind_tags %}
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>{% block title %}{% endblock %}</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    {% tailwind_css %}
  </head>

  <body class="bg-gray-50 font-serif leading-normal tracking-normal">
    <div class="container mx-auto">
      <section class="p-4">
        <h1 class="text-5xl">{% block h1 %}{% endblock %}</h1>
      </section>

      {% block main %}{% endblock %}
    </div>
  </body>
</html>

この base.html を利用するページを作ってみます。

myappというアプリを作成します。

python manage.py startapp myapp

INSTALLED_APPS にも追加しておきます。

INSTALLED_APPS = [
    # ...
    'tailwind',
    'theme',
    'myapp', # 追加
]

templatesディレクトリを作成しindex.htmlファイルを作成します。

myapp/templates/index.html:

{% extends "base.html" %}

{% block title %}Django Tailwind{% endblock %}

{% block h1 %}Django + Tailwind{% endblock %}

{% block main %}
<div class="max-w-sm rounded overflow-hidden shadow-lg border mb-2">
  <div class="px-6 py-4">
    <div class="font-bold text-xl mb-2">カードタイトル</div>
    <p class="text-gray-700 text-base">カード本文</p>
  </div>
  <div class="px-6 pt-2 pb-2">
    <a href="http://example.com/" class="bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-blue-700 mr-2 mb-2">Read more</a>
  </div>
</div>
{% endblock %}

index.html を利用するビューを用意します。

myapp/views.py:

from django.shortcuts import render

def index(request):
    return render(request, 'index.html')

ビューをプロジェクトのurls.pyに登録します。

myproject/urls.py:

from django.urls import path
from myapp.views import index

urlpatterns = [
    path('', index),
]

ここまでできたら動作確認をします。

tailwindの開発用のビルドは、 manage.py tailwind start を実行すると、django-tailwindがnpmコマンドを実行してくれます。

python manage.py tailwind start

tailwindのビルドを起動した状態で、Djangoのrunserverも起動し、ブラウザで確認してみます。

python manager.py runserver

TailwindCSSが適用されたページを表示できました。

django-tailwindを使うと、TailwindCSSをDjangoに気軽に組み込めるのがうれしいですね。

DjangoテンプレートエンジンのDjango Cottonを試す

Djangoフレームワークにはテンプレートエンジンを別のものに切り替える機能があります。 Django Cottonというテンプレートエンジンを試してみたので、メモを残しておきます。 django-cotton.com GitHub - wrabit/django-cotton: Enabling Modern UI Composition in Django

Django Cottonは、コンポーネント(部品)となるテンプレートを作って組み合わせていく、コンポーネント指向のテンプレート設計を実現するテンプレートエンジンです。

ドキュメントの説明には、 TailwindCSS (コンポーネント指向のCSSフレームワーク)とあわせて使いやすい、とも書かれています。

ドキュメントの Quickstart が参考になります。

以下、 django-admin startproject myproject で作成したDjangoプロジェクトにセットアップして試していきます。

セットアップ

PyPI上のパッケージ名は django-cotton です。pipでインストールできます。

pip install django-cotton

デフォルトの設定で使うだけなら、 settings.INSTALLED_APPSdjango_cotton を追加します。

myproject/settings.py:

INSTALLED_APPS = [
    # ...
    'django_cotton',
]

※デフォルト設定の場合、django_cottonのAppConfigがロードされる際に、Djangoのsettingsのテンプレート設定を変更する処理が実行されます。テンプレート設定を自分で変更している場合には、不具合が出ていないか、チェックしておいたほうがよさそうです。

参考: django_cotton/apps.py

もし、デフォルト設定以外を使いたい場合は、 djanog_cotton.apps.SimpleAppConfig のほうを INSTALLED_APPS に設定できます。

お試し用にDjangoアプリを準備する

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

python manage.py startapp myapp

INSTALLED_APPSにも追加しておきます。

myproject/settings.py:

INSTALLED_APPS = [
    # ...
    'django_cotton',
    'myapp',  # 追加
]

TailwindCSSのCDNを組み込む

TailwindCSSで定義されているCSSクラスを使用するので、CDNからロードするようにテンプレートを作っておきます。

Try Tailwind CSS using the Play CDN - Tailwind CSS

※本番環境で使う場合はTailwindはビルドして使ったほうがよいです。

myapp/templates/index.html:

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
  <div class="p-2">
    <h1 class="text-3xl font-bold underline">
      Django Cotton!
    </h1>
  </div>
</body>
</html>

Viewはindex.htmlを使うだけのシンプルな記述

myapp/views.py:

from django.shortcuts import render

def index(request):
    return render(request, 'index.html')

プロジェクトのurls.pyでindexを有効にします。

myproject/urls.py:

from django.urls import path
from myapp.views import index

urlpatterns = [
    path('', index),
]

runserverを起動して確認しておきます。

TailwindCSSの確認

TailwindCSSのCSSが適用されていることを確認できました。

コンポーネントとなるテンプレートを作る

カード表示のコンポーネントを作ってみます。

Django Cottonのデフォルト設定の場合、コンポーネントはtemplatesディレクトリ内にcottonというディレクトリを作って格納します。

myapp/templates/cotton/card.html:

<div class="max-w-sm rounded overflow-hidden shadow-lg border mb-2">
  <div class="px-6 py-4">
    <div class="font-bold text-xl mb-2">{{ title }}</div>
    <p class="text-gray-700 text-base">{{ slot }}</p>
  </div>
  <div class="px-6 pt-2 pb-2">
    <a href="{{ url }}" class="bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-blue-700 mr-2 mb-2">Read more</a>
  </div>
</div>

{{ title }}{{ url }}プレースホルダーは、コンポーネントのattribute(属性)で指定できるようにしています。

{{ slot }} の部分はCottonで予約された名称で、コンポーネント呼び出し側のタグの内側に記載した内容が差し込まれます。

コンポーネントを使用する

作成しておいた index.html を書き換えます。

templates/cotton/card.htmlコンポーネントを作成したので、 <c-card></c-card> というタグで呼び出せます。

myapp/templates/index.html:

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
  <div class="p-2">
    <h1 class="text-3xl font-bold underline">
      Django Cotton!
    </h1>
    <c-card title="カードタイトル" url="http://example.com/">
      カード本文
    </c-card>
    <c-card title="カード2タイトル" url="http://example.com/">
      カード2本文
    </c-card>
  </div>
</body>
</html>

画面で確認すると、コンポーネントが使用されて、カード表示ができています。

Django Cottonを使ったテンプレートでの表示

これで、Django Cottonを組み込んだ実装ができました。

フロントエンドのコーディングで馴染みのあるようなテンプレートの書き方にできるので面白いですね。