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


モデルはDjangoにおいて、データベースに保存されるデータの構造と振る舞いを定義するために使用されます。モデルの使用方法を理解することは、堅牢でスケーラブルなウェブアプリケーションを構築するために不可欠です。では、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:電話番号の検証フィールド
- https://github.com/respondcreate/django-versatileimagefield:豊富な機能を備えたImageFieldパッケージ
- モデル
- データベース設定
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 <app> <migration_number>
ロールバックとリバート
Djangoは、マイグレーションのロールバックとリバートもサポートしています。マイグレーションの適用後に問題が発生した場合、ロールバックして以前の状態に戻すことができます。また、マイグレーションの一式をリバートして、特定の時点に戻すこともできます。
python manage.py migrate <app> <migration_number>
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つのレコードに関連付けられている一般的なリレーションシップの種類です。OneToOneField
フィールド型を使用して、一対一リレーションシップを定義できます。
一対多リレーションシップ
一対多リレーションシップは、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
オプションを使用します。
- フィルタリングや検索によく使用されるフィールドを特定し、クエリのパフォーマンスを向上させるためにデータベースインデックスを追加することを検討してください。
-
モデルメソッドとプロパティ
- モデルにメソッドを定義して、ビジネスロジックをカプセル化するか、モデルのデータに関連する複雑な操作を実行します。
- プロパティを使用して、派生値を計算するか、関連データへの便利なアクセスを提供します。
- モデルにメソッドを定義して、ビジネスロジックをカプセル化するか、モデルのデータに関連する複雑な操作を実行します。
-
メタオプション
- Djangoの
Meta
クラスを使用して、モデルに追加のオプションとメタデータを提供します。
ordering
属性を使用して、クエリ結果の順序を指定します。
unique_together
を使用して一意のフィールドの組み合わせを強制する一意の制約を定義します。
- Djangoの
-
マイグレーションを使用する
- Djangoのマイグレーションシステムを使用して、時間の経過とともにデータベーススキーマの変更を管理します。
- モデルまたはデータベーススキーマに変更を加えるたびに、マイグレーションを作成して適用します。
- Djangoのマイグレーションシステムを使用して、時間の経過とともにデータベーススキーマの変更を管理します。
-
テスト
- モデルの動作と相互作用が正しいことを確認するために、モデルの単体テストを作成します。
- モデルメソッド、プロパティ、リレーションシップをテストして、機能を検証します。
- モデルの動作と相互作用が正しいことを確認するために、モデルの単体テストを作成します。
-
コードの構成
- 関連する機能またはドメインに基づいて、モデルを個別のモジュールまたはファイルに整理します。
- Djangoのアプリ構造を使用して、モデルを論理的にグループ化することを検討してください。
- 関連する機能またはドメインに基づいて、モデルを個別のモジュールまたはファイルに整理します。
-
ドキュメント
- モデル、フィールド、メソッドにコメントとdocstringを追加して、明確性を高め、コードの理解を容易にします。
- モデルの設計または使用に関連する仮定、制約、特別な考慮事項を文書化します。
- モデル、フィールド、メソッドにコメントとdocstringを追加して、明確性を高め、コードの理解を容易にします。
-
パフォーマンスの最適化
- Djangoの
select_related
およびprefetch_related
メソッドを使用して、データベースクエリの数を最小限に抑え、関連データの取得を最適化します。
- 大規模なデータセットを操作する際には、データベースへのアクセスに注意し、ページングまたはその他の戦略を使用してクエリ結果を制限することを検討してください。
- Djangoの
-
モデルは大きくするべき
- モデルクラスは大きく複雑にするべきです。そうすれば、そのメソッドをさまざまな場所で呼び出して、クリーンなコードと再利用可能なコードを改善できます。
- モデルクラスは大きく複雑にするべきです。そうすれば、そのメソッドをさまざまな場所で呼び出して、クリーンなコードと再利用可能なコードを改善できます。
-
ヒント
- コンピューティングリソースを節約したい場合は
cached_property
を使用します。
- 可能な場合は、Djangoシグナルまたはsaveメソッドのオーバーライドを使用して、アプリケーションの疎結合を改善します。
- コンピューティングリソースを節約したい場合は
8. 高度なトピック
8.1 シグナル
Djangoシグナルは、Djangoアプリケーションの異なるコンポーネントが特定のイベントに関する通知を送受信することを可能にし、直接的な依存関係なしにアプリケーションの部分間の通信を促進することにより、疎結合とモジュール性を促進します。この強力なメカニズムは、機能の拡張、サードパーティアプリとの統合、非同期タスクの実行に使用でき、Djangoシグナルを開発者にとって貴重なツールにしています。
ただし、これはアプリケーションの複雑性の向上も意味します。その点に注意してください。
利点
- 疎結合:シグナルは、アプリケーションコンポーネントを分離することにより、モジュール性と保守性の高いコードを可能にします。
- 拡張性:カスタムシグナルとレシーバーにより、新しい機能を簡単に追加できます。
- 非同期実行:この機能は、パフォーマンスの向上のためのバックグラウンド処理をサポートしています。
- サードパーティアプリとの統合:この機能により、外部コンポーネントとシームレスに統合できます。
欠点
- 複雑さ: