14日で学ぶDjango - 2日目:モデルとデータベース
By JoeVu, at: 2023年6月15日8:58
Estimated Reading Time: __READING_TIME__ minutes


モデル は Django において、データベースに保存されるデータの構造と動作を定義するために使用されます。モデルの使用方法を理解することは、堅牢で拡張性の高いWebアプリケーションを構築するために不可欠です。それでは、Djangoモデルの世界に飛び込みましょう!
1. Djangoデータベースとモデルの概要
Djangoのデータベースサポートは、強力なオブジェクトリレーショナルマッピング(ORM)レイヤーに基づいています。ORMは、さまざまなデータベース管理システム(DBMS)とのやり取りの複雑さを抽象化し、開発者が生のSQLクエリを記述する代わりにPythonコードを使用してデータベースを操作できるようにします(いずれの場合も推奨されません)。
Djangoは、PostgreSQL、MySQL、SQLite、Oracleなど、さまざまなリレーショナルデータベースをサポートしています。この柔軟性により、開発者はプロジェクトの要件に最適なデータベースバックエンドを選択できます。推奨されるDjangoデータベースはPostgreSQLです。
ここで、データベースモデルは、データベーステーブルを表すPythonクラスです。これは、基になるデータベースとのやり取りのためのフィールドとメソッドを定義します。Djangoモデルは、データベースを操作するための高レベルで直感的な方法を提供し、データの管理と操作を容易にします。
次に、追加の便利なパッケージを使用して、いくつかの高度なトピックについて説明します。
- フィールド
- https://github.com/respondcreate/django-versatileimagefield:豊富な機能を備えたImageFieldパッケージ
- https://github.com/romgar/django-dirtyfields:モデルインスタンスの変更と現在の保存済みデータベース値を追跡します。
- https://github.com/stefanfoulis/django-phonenumber-field:電話番号の検証フィールド
- モデル
- データベース設定
2. Djangoモデルの作成
Djangoモデルを作成するには、django.db.models.Model
基本クラスを継承するPythonクラスを定義する必要があります。このクラスはデータベース内のテーブルを表します。クラスの各属性は、テーブル内のフィールドを表します。
フィールドの定義
フィールドは、データベースに保存できるデータの種類を定義します。Djangoは、CharField
、IntegerField
、DateField
、ForeignKey
など、さまざまなフィールドタイプを提供しています。保存するデータに基づいて、適切なフィールドタイプを選択できます。
フィールドタイプ
Djangoは、さまざまな種類のデータを処理するための幅広いフィールドタイプを提供しています。たとえば、CharField
はテキストの保存に使用され、IntegerField
は整数の保存に使用され、DateField
は日付の保存に使用されます。正しいフィールドタイプを選択することで、データの整合性と効率的なストレージが確保されます。
フィールドオプション
フィールドには、動作をカスタマイズするための追加のオプションを含めることができます。一般的なオプションには、フィールドが空かどうかを指定するnull
、フィールドのデフォルト値を提供するdefault
、一意性を強制するunique
などがあります。これらのオプションを使用すると、モデルの動作を微調整できます。
モデルの例を以下に示します。
from django.contrib.postgres.fields import ArrayField
from django.db import models
class Book(models.Model):
author = models.ForeignKey(
"Author",
on_delete=models.CASCADE,
related_name="jobs",
)
tags = ArrayField(
models.CharField(max_length=256, null=True, blank=True),
blank=True,
null=True,
)
description = models.TextField()
title = models.CharField(max_length=256, null=True, blank=True)
category = models.CharField(max_length=256, null=True, blank=True)
price = models.DecimalField(max_digits=None, decimal_places=None)
def is_comic(self):
return "comic" in self.category.lower()
3. データベースマイグレーション
フィールドの追加や変更など、モデルに変更を加えた場合、Djangoはマイグレーションシステムを提供して、データベーススキーマのこれらの変更を管理します。
マイグレーションの生成
Djangoのマイグレーションシステムを使用すると、モデルの変更に基づいて必要なSQLステートメントを自動的に生成できます。makemigrations
コマンドを使用して、モデルで検出された変更に基づいてマイグレーションファイルを作成できます。
python manage.py makemigrations app
python manage.py makemigrations
マイグレーションの適用
マイグレーションファイルが作成されたら、migrate
コマンドを使用してデータベースに適用できます。これにより、データベーススキーマがモデルの現在の状態と一致するように更新されます。Djangoは、現在のデータベースのdjango_migrations
テーブルに適用されたマイグレーションを追跡しているため、スキーマの更新を簡単に管理できます。
python manage.py migrate
ロールバックと元に戻す
Djangoは、マイグレーションのロールバックと元に戻すもサポートしています。マイグレーションの適用後に問題が発生した場合、ロールバックして以前の状態に戻すことができます。また、マイグレーションのセットを元に戻して、特定の時点に戻すこともできます。
python manage.py migrate
python manage.py migrate bookstore 0001
python manage.py migrate bookstore zero # ゼロに戻します
偽のマイグレーション
これは、実際のデータベースの更新とdjango_migrations
テーブルに格納されているマイグレーション番号の間に競合がある場合の重要な機能です。クライアントからの緊急の要求により、すでにデータベースに変更を加えていますが、マイグレーションスクリプトを作成してデータベースに適用していない場合を考えてみましょう。これは、--fake migration
が使用される場合です。
python manage.py migrate bookstore 0002 --fake # 現在のマイグレーション番号は0001で、この変更後、実際のマイグレーションのパフォーマンスなしで0002になります。
利点
-
データベーススキーマの進化:これは、時間の経過とともにデータベーススキーマをシームレスかつ制御された方法で進化させることができます。手動のSQLスクリプトを使用したり、データベース全体を再作成したりすることなく、データベース構造に変更を加えるプロセスを簡素化します。
-
バージョン管理:マイグレーションはプロジェクトのコードベースにバージョン管理されたファイルとして格納されるため、アプリケーションのソースコードとともにデータベーススキーマの変更を簡単に追跡および管理できます。これにより、共同作業、コードレビュー、必要に応じてロールバックが容易になります。
-
データマイグレーション:構造上の変更に加えて、Djangoマイグレーションはデータマイグレーションをサポートします。これにより、スキーマを変更するときにデータをマイグレーションおよび変換するためのPythonコードを記述して、マイグレーションプロセス中のデータの整合性を確保できます。
-
依存関係の管理:マイグレーションは、異なるマイグレーションファイル間の依存関係をサポートします。つまり、マイグレーションを適用する順序を定義して、1つのマイグレーションが別のマイグレーションの完了に依存する複雑なシナリオを処理できます。
-
ロールバックと元に戻す:これにより、データベーススキーマへの変更を実行/取り消すことができます。これは、マイグレーションによって問題が発生した場合、または以前の状態に戻す必要がある場合に役立ちます。
欠点
-
複雑なマイグレーション:特に複雑なデータベース変更を処理する場合、マイグレーションの記述と管理は困難になる可能性があります。既存のフィールドの名前変更や変更など、エッジケースを処理するには、手動による介入またはカスタムマイグレーションスクリプトが必要になる場合があります。いくつかのカスタムマイグレーションは、多くのデータベース制約の更新により、数年後に壊れる可能性があります。
-
パフォーマンスへの影響:マイグレーションは、特に大規模なデータセットを処理する場合、マイグレーションプロセス中にアプリケーションのパフォーマンスに影響を与える可能性があります。複雑なマイグレーションを適用したり、大量のデータをマイグレーションしたりするには、ダウンタイムとパフォーマンスの低下を最小限に抑えるために、注意深い検討と最適化が必要です。バックグラウンドタスクでのパフォーマンスマイグレーションによって回避するためのヒントがあります(Celery + Redis)
-
既存のデータとの相互作用:既存のデータをマイグレーションすることは、特にデータ変換または複雑なロジックを必要とするデータマイグレーションを処理する場合、困難になる可能性があります。マイグレーションプロセス中のデータの整合性と一貫性を確保するには、注意深い計画とテストが必要です。
-
外部依存関係:マイグレーションは、マイグレーションシステム自体以外の外部依存関係または要因に依存する可能性があります。データベースサーバーやライブラリなどの外部システムへの変更は、互換性の問題を引き起こすことがあり、解決するために追加の努力が必要になる場合があります。
4. データベースへのクエリ
Djangoは、Pythonコードを使用してデータベースにクエリを実行できる強力なORM(オブジェクトリレーショナルマッピング)を提供します。ORMは基になるデータベースエンジンを抽象化するため、データベースに依存しない方法でデータベースクエリを簡単に記述できます。
基本的なクエリ
モデルクラスのobjects
属性を使用して、データベースからオブジェクトを取得できます。たとえば、MyModel.objects.all()
は、MyModel
クラスのすべてのオブジェクトを返します。filter()
メソッドを使用して、特定の基準に基づいてオブジェクトをフィルター処理することもできます。
Book.objects.filter(author_id__in=[1, 2, 3]) # これは、author_idが1、2、3であるすべての書籍を返します。
Book.objects.filter(author__name__contains="Joe", category__icontains="comic") # これは、著者の名前が「Joe」を含み、カテゴリが「comic」を含む(大文字と小文字を区別しない)すべての書籍を返します。
フィルタリングとソート
Djangoは、クエリ結果のフィルタリングとソートのためのさまざまなメソッドを提供します。filter()
、exclude()
、order_by()
などのメソッドを使用して、結果を絞り込み、必要な順序を指定できます。
Book.objects.filter(author__name__contains="Joe", category__icontains="comic").exclude(author__name__contains="Vu").order_by("-id") # これは、著者の名前が「Joe」を含み、カテゴリが「comic」を含む(大文字と小文字を区別しない)すべての書籍を返しますが、「Vu」を含む(大文字と小文字を区別する)書籍を除外します。返されるリストは「ID」降順でソートされます。
集計とアノテーション
DjangoのORMは、クエリ結果に対して計算や集計を実行するための集計とアノテーションもサポートしています。count()
、sum()
、avg()
、annotate()
などのメソッドを使用して、データベースから集計されたデータを取得できます。
Book.objects.aggregate(count=Count("id"))
Book.objects.aggregate(total_price=Sum("price"))
5. モデル間のリレーションシップ
Djangoでは、モデル間のリレーションシップを定義できます。これにより、アプリケーション内のさまざまなエンティティ間の接続と関連付けを確立できます。
1対1リレーションシップ
1対1リレーションシップは、1つのモデルの各レコードが別のモデルの正確に1つのレコードに関連付けられる一般的なリレーションシップの種類です。OneToOneField
フィールドタイプを使用して、1対1リレーションシップを定義できます。
1対多リレーションシップ
1対多リレーションシップは、1つのモデルの1つのレコードが別のモデルの複数のレコードに関連付けられるリレーションシップを表します。これは、ForeignKey
フィールドタイプを使用して実現されます。
多対多リレーションシップ
多対多リレーションシップは、1つのモデルの複数のレコードが別のモデルの複数のレコードに関連付けられる、より複雑なリレーションシップです。Djangoは、このタイプのリレーションシップを処理するためにManyToManyField
フィールドタイプを提供します。
6. モデルの継承
Djangoはモデルの継承をサポートしており、既存のモデルに基づいてより特殊化されたモデルを作成できます。
抽象ベースクラス
他のモデルの親として抽象ベースクラスを定義できます。抽象ベースクラスは、継承の目的のみに使用され、データベースに個別のテーブルとして作成されることはありません。
from django.db import models
class BaseModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
@property
def added(self):
return self.created_at
@property
def updated(self):
return self.updated_at
@property
def created_by(self):
if hasattr(self, "added_by"):
return self.added_by
return None
多テーブル継承
多テーブル継承では、継承階層の各モデルはデータベースに個別のテーブルとして保存されます。Djangoは、これらのテーブル間のリレーションシップを自動的に作成して、継承構造を維持します。
プロキシモデル
プロキシモデルは、元のモデルのように動作しますが、いくつかの変更を加えたモデルを作成できる、別のモデル継承です。プロキシモデルは、モデルのデフォルトの動作を追加のメソッドで追加または変更する場合に役立ちます。
from django.contrib.postgres.fields import ArrayField
from django.db import models
class Book(models.Model):
author = models.ForeignKey(
"Author",
on_delete=models.CASCADE,
related_name="jobs",
)
tags = ArrayField(
models.CharField(max_length=256, null=True, blank=True),
blank=True,
null=True,
)
description = models.TextField()
title = models.CharField(max_length=256, null=True, blank=True)
category = models.CharField(max_length=256, null=True, blank=True)
price = models.DecimalField(max_digits=None, decimal_places=None)
def is_comic(self):
return "comic" in self.category.lower()
class KidBook(Book):
class Meta:
proxy = True
def for_kid(self):
return True
7. Djangoモデルのベストプラクティス
-
命名規則
- モデルクラス名には単数名詞を使用し、記述的で直感的なものにします。
- フィールド名には小文字とアンダースコアを使用します。
- コードの可読性を向上させるために、冗長で意味のあるフィールド名を使用することを検討してください。
-
フィールドタイプと制約
- 保存されているデータを正確に表す適切なフィールドタイプを選択します。
null=True
、blank=True
、unique=True
などの制約を追加して、データの整合性を強制します。ForeignKey
とManyToManyField
を使用して、モデル間のリレーションシップを確立します。
-
インデックス
- フィルタリングや検索によく使用されるフィールドを特定し、クエリのパフォーマンスを向上させるためにデータベースインデックスを追加することを検討してください。
- クエリで頻繁に使用されるフィールドには、Djangoの
db_index=True
オプションを使用します。
-
モデルメソッドとプロパティ
- モデルにメソッドを定義して、ビジネスロジックをカプセル化したり、モデルのデータに関連する複雑な操作を実行したりします。
- プロパティを使用して、派生値を計算したり、関連データへの便利なアクセスを提供したりします。
-
Metaオプション
- Djangoの
Meta
クラスを使用して、モデルに追加のオプションとメタデータを提供します。 ordering
属性を使用して、クエリ結果の順序を指定します。unique_together
を使用して、フィールドの一意の組み合わせを強制する一意の制約を定義します。
- Djangoの
-
マイグレーションを使用する
- Djangoのマイグレーションシステムを使用して、時間の経過とともにデータベーススキーマの変更を管理します。
- モデルまたはデータベーススキーマに変更があるたびに、マイグレーションを作成して適用します。
-
テスト
- モデルの動作と相互作用が正しいことを確認するために、モデルの単体テストを作成します。
- モデルメソッド、プロパティ、リレーションシップをテストして、機能を確認します。
-
コードの構成
- 関連する機能やドメインに基づいて、モデルを個別のモジュールまたはファイルに整理します。
- Djangoのアプリケーション構造を使用して、モデルを論理的にグループ化することを検討してください。
-
ドキュメント
- モデル、フィールド、メソッドにコメントとdocstringsを追加して、明確さを提供し、コードを理解しやすくします。
- モデルの設計または使用に関する前提条件、制約、特別な考慮事項を文書化します。
-
パフォーマンスの最適化
- Djangoの
select_related
とprefetch_related
メソッドを使用して、データベースクエリの数を最小限に抑え、関連データの取得を最適化します。 - 大規模なデータセットを処理する場合、データベースへのアクセスに注意し、ページングなどの戦略を使用してクエリ結果を制限することを検討してください。
- Djangoの
-
モデルは大きくするべき
- モデルクラスは大きく、複雑にする必要があります。これにより、メソッドをさまざまな場所で呼び出すことができ、クリーンなコードと再利用可能なコードが改善されます。
-
ヒント
- コンピューティングリソースを節約したい場合は、
cached_property
を使用します。 - 可能な場合は、Djangoシグナルまたはsaveのオーバーライドを使用して、疎結合アプリケーションを改善します。
- コンピューティングリソースを節約したい場合は、
8. 高度なトピック
8.1 シグナル
Djangoシグナルは、Djangoアプリケーションのさまざまなコンポーネントが特定のイベントに関する通知を送受信できるようにします。直接的な依存関係なしにアプリケーションパーツ間の通信を促進することにより、疎結合とモジュール化を促進します。この強力なメカニズムは、機能の拡張、サードパーティアプリケーションとの統合、非同期タスクの実行に使用でき、Djangoシグナルを開発者にとって貴重なツールにします。
ただし、これはアプリケーションの複雑さを増すことも意味しますので、注意してください。
利点
- 疎結合:シグナルは、アプリケーションコンポーネントを分離することにより、モジュール化され、保守可能なコードを実現します。
- 拡張性:カスタムシグナルとレシーバーは、新しい機能の簡単な追加を容易にします。
- 非同期実行:この機能は、パフォーマンスの向上のためのバックグラウンド処理をサポートします。
- サードパーティアプリケーションとの統合:この機能により、外部コンポーネントとのシームレスな統合が可能になります。
欠点
- 複雑さ:シグナルは、コードベースの理解と相互作用の追跡を困難にする可能性があります。
- 明示性の欠如:コンポーネント間の通信は、コードでは明示的ではありません。
- 潜在的なパフォーマンスへの影響:多数のシグナルとレシーバーを処理すると、オーバーヘッドが発生する可能性があります。
- デバッグとテストの課題:直接的なメソッド呼び出しと比較して、デバッグとテストがより複雑になる可能性があります。
8.2 saveメソッドのオーバーライド
Djangoの「saveメソッドのオーバーライド」は、Djangoモデルでデータの保存のデフォルトの動作をカスタマイズする機能を指します。save()
メソッドをオーバーライドすることにより、開発者はオブジェクトをデータベースに保存する前または後に実行されるカスタムロジックを定義できます。この機能により、保存操作中にデータの検証、前処理、または後処理タスクを実行できます。Djangoモデルでデータが保存および操作される方法に対する柔軟性と制御を提供し、保存の動作を特定のアプリケーションの要件に合わせて調整するための強力なツールになります。
例
class Author(models.Model):
def save(self, *args, **kwargs):
assert self.price > 0
self.is_expensive = False
if self.price > 100:
self.is_expensive = True
return super().save(*args, **kwargs)
利点
- カスタマイズ:
save()
メソッドをオーバーライドすると、保存プロセス中にカスタムロジックを使用できるようになり、調整されたデータ処理と操作が可能になります。 - 柔軟性:開発者は、Djangoモデルでデータが保存される方法を完全に制御できるため、特定の要件を満たすことができます。
- データ検証:
save()
メソッドを使用して、保存前にデータ検証を実行し、データの整合性を確保できます。 - 前処理と後処理:
save()
をオーバーライドすることにより、開発者は保存操作の前または後(データ変換や更新など)に追加のタスクを実行できます。
欠点
- 複雑性の増加:カスタムロジックが複雑になる場合、特に
save()
メソッドをオーバーライドすると、コードベースの複雑さが増す可能性があります。 - 保守の課題:カスタム
save()
メソッドには、整合性を維持し、競合を回避するために、注意深い保守とドキュメントが必要です。 - 潜在的なエラー:
save()
のオーバーライドを不正確に実装すると、適切に処理されない場合に、意図しない結果やデータの破損につながる可能性があります。 - テストの難しさ:標準