概要
DjangoではORMによって、データベーステーブルとPythonオブジェクトの間でデータのマッピングを行い、Pythonコードを使用してデータベース操作を行うことができます。
SQLクエリを書かなくてもPythonコードで行える、というのがポイントですよね。
ただし、時々モデルのインスタンスメソッド?objects
マネージャのメソッド?とかで困惑したり、記載方法でエラー返されたりすることがあります。
ということでORM(Object-Relational Mapping)について整理します。
サンプルコード(モデルのメソッド:save
)
以下のコードでは、save_response
関数は与えられたresponse
データをResponse
モデルを介してデータベースに保存しています。
from response.models import Response
from datetime import datetime
def save_response(response):
Response(request_id=response['response_id'],
response_body=response['response'],
create_date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
update_date=datetime.now().strftime("%Y-%m-%d %H:%M:%S")).save()
data = {
'response_id': "test0000001",
'response': 'これはテスト用です'
}
save_response(data)
上記例のように、Djangoでは以下のことができます。
・Response
モデルの新しいインスタンスを作成し、そのインスタンスをデータベースに保存する
それは、Djangoモデルには、データベースレコードの操作を簡単に行うための便利なメソッドがいくつかあり、そのうちの一つsave
メソッドによって行われています。これにより、新しいResponse
インスタンスがデータベースに挿入されます。
ちなみに、インポートしていたResponse
モデルは以下のように定義しています。
from django.db import models
# Create your models here.
class Response(models.Model):
response_id = models.CharField(db_column="response_id",
max_length=100,
primary_key=True)
response_body = models.TextField(db_column='response_body', default='', null=True)
create_date = models.CharField(db_column='create_date',
max_length=100,
default='',
null=True)
update_date = models.CharField(db_column='update_date',
max_length=100,
default='',
null=True)
class Meta:
db_table = 'response'
objects
マネージャを利用したORMによるデータベース操作
ORMによるデータベース操作には、他にどんなものがあるか。
以下で紹介するメソッドはobjects
マネージャのメソッドです。
まず、Manager
とは、Djangoモデルとデータベースの間のインターフェースを提供するクラスのこと。データベースの操作を実行するためのメソッドを提供し、モデルのクエリを生成する役割を果たしています。
※Manager
ベースクラスはカスタマイズすることもできますので、以下記事をご参考ください。
マネージャ — Django 4.0.6 ドキュメント
objects
マネージャとは、データベースへのアクセスを提供するコンポーネントのこと。
データベーステーブルにアクセスし、レコードの作成、読み取り、更新、削除などの操作を行うためのメソッドを提供しています。
また、以下記事ではどんな場合にモデルマネージャを使うのか、という点で分かりやすく解説してくださっています。
【初心者向け】Djangoのモデル操作でobjectsが必要な場合・不要な場合を理解する
ということで、いくつかやってみます。
上記サンプルコードと同じ要領で増やし、DBテーブルには3つのレコードがある状態とします。
データベースからレコードを取得する
def get_response_by_id(response_id):
try:
response = Response.objects.get(request_id=response_id)
print(response.response_body)
# 該当するレコードが存在しない場合、Response.DoesNotExist 例外が発生
except Response.DoesNotExist:
return None
id_number = "test0000001"
get_id = get_response_by_id(id_number)
# 結果
# これはテスト用です
データベース内のすべてのレコードを取得する
def get_all_responses():
responses = Response.objects.all()
print(responses)
response_list = list(responses)
print(response_list)
print(response_list[0])
print(response_list[2].response_body)
get_all_responses()
# 結果
# <QuerySet [<Response: Response object (test0000001)>, <Response: Response object (test0000002)>, <Response: Response object (test0000003)>]>
# [<Response: Response object (test0000001)>, <Response: Response object (test0000002)>, <Response: Response object (test0000003)>]
# Response object (test0000001)
# これはテスト用です3
print(responses)
でQuerySet
が返されている点も大事なポイントです。
Response.objects.all()
はQuerySet
を返します。
そして、QuerySet
に対しては、モデルマネージャのメソッドが使えます。
なので、仮にget
を使えば、以下のように取得できます。
モデルマネージャ(objects
)の記述は不要です。
responses = Response.objects.all()
response = responses.get(request_id="test0000001")
print(response)
# 結果
# Response object (test0000002)
responses = Response.objects.all()
response = responses.objects.get(request_id="test0000001")
print(response)
# 結果
# AttributeError: 'QuerySet' object has no attribute 'objects'
上記の成功例では、all
で取得したクエリセットにget
を使っています。つまり、モデルマネージャを連結させていることになるので、以下のように書いても同じ結果が得られます。
response = Response.objects.all().get(request_id="test0000001")
print(response)
もう一つ補足すると、モデルクラスに直接モデルマネージャのメソッドを使うとエラーになります。
つまり、objects
がないとエラーになります。
response = Response.objects.get(request_id="test0000001")
print(response)
# 結果
# Response object (test0000002)
response = Response.get(request_id="test0000001")
print(response)
# 結果
# AttributeError: type object 'Response' has no attribute 'get'
モデルクラスはモデルマネージャのクエリメソッドを直接呼び出すことはできないため、仲介役のobjects
マネージャを使おう、ということですね。
あれ一番最初のsave
メソッドはモデルクラスにそのまま繋げることができたぞ、となるかもですが、あれはモデルのインスタンスメソッドであり、モデルマネージャのメソッドではありません(ここ混ざりやすい)。
save -----> class Model(metaclass=ModelBase)
get -----> class _BaseQuerySet(Generic[_T], Sized):
モデルのインスタンスメソッドであるsave
の前にobjects
をつけてしまうと以下のエラーになります。
AttributeError: Manager isn't accessible via Response instances
データベース内のレコードを更新する
def update_response(response_id, update_response_body):
response = Response.objects.get(request_id=response_id)
response.response_body = update_response_body
response.save()
print(response.response_body)
update_response("test0000001", "上書き:これはテスト用です")
# 結果
# 上書き:これはテスト用です
該当のレコードが存在するかチェックする
def check_response_exists(response_id):
return Response.objects.filter(request_id=response_id).exists()
# 結果が存在する場合に True を返し、存在しない場合に False を返す
check_id = "test0000001"
checker = check_response_exists(check_id)
print(checker)
# 結果
# True
特定の条件を満たすレコードをクエリする
def get_responses_with_filter(keyword):
# response_body__icontains は、大文字小文字を区別しない部分一致検索を実行する方法
responses = Response.objects.filter(response_body__icontains=keyword)
response_list = list(responses)
print(response_list)
print(response_list[2].response_body)
keyword = "テスト"
get_responses_with_filter(keyword)
# 結果
# [<Response: Response object (test0000001)>, <Response: Response object (test0000002)>, <Response: Response object (test0000003)>]
# これはテスト用です3
先述の例で紹介したget
でモデルインスタンスを返していますが、filter
ではクエリセットが返されます。モデルマネージャのメソッドがすべてクエリセットで返すとかモデルインスタンスで返す、というわけではない、というのも重要ポイントです。
なので、もしresponses
をプリントしたら以下のように出力されます。
<QuerySet [<Response: Response object (test0000001)>, <Response: Response object (test0000002)>, <Response: Response object (test0000003)>]>
クエリセットであることがわかります。
なので、モデルインスタンスを返すget
の後にモデルマネージャのメソッドを連結させてもエラーになります。必ず、fileter
やall
のようにクエリセットで返すものに対して連結させるようにします。
response = Response.objects.get(request_id="test0000001").exists()
print(response)
# 結果
# AttributeError: 'Response' object has no attribute 'exists'
データベース内のレコードを削除する
def delete_response(response_id):
response = Response.objects.get(request_id=response_id)
response.delete()
print(response)
delete_id = "test0000001"
delete_response(delete_id)
削除されていますね。
レコードを新規に作成する
def create_response(request_id, response_body):
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
response = Response.objects.create(
request_id=request_id,
response_body=response_body,
create_date=current_time,
update_date=current_time
)
return response
new_response = create_response('test0000004', 'createメソッドで作ったレコード')
複数のレコードを一度に作成する
def create_responses(records):
# recordsはResponseモデルのリストであると仮定します
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
for record in records:
record.create_date = current_time
record.update_date = current_time
Response.objects.bulk_create(records)
new_responses = [
Response(request_id='test0000005', response_body='bulk_createメソッドで作ったレコード'),
Response(request_id='test0000006', response_body='bulk_createメソッドで作ったレコード2'),
]
create_responses(new_responses)
bulk_create()
メソッドでは、引数として、オブジェクトのリストを渡します(上記の場合はnew_responses
)。
実行すると以下の通り、2レコード一括で登録されたのがわかります。
指定した条件に一致するレコードを取得または新規作成する
def get_or_create(request_id):
response, created = Response.objects.get_or_create(request_id=request_id)
if not created:
# レコードがすでに存在する場合の処理
print(f"既存のレコードを更新しました。")
else:
# 新しいレコードが作成された場合の処理
print(f"新しいレコードを作成しました。")
request_id = "test00000020"
get_or_create(request_id)
以上!
ということで色々紹介してきましたが、ORMは慣れるのに時間かかるなーと思っています。
他にもvalues
やvalues_list
もあります。これらを使うと、特定のフィールドの値を抽出して辞書として取得できます。以前以下の記事でも記載したのでご参考までに。
【Python】select_related(とvaluesメソッド)を使用してクエリの数を最適化する
【Django】ORMのvalues_listメソッドの引数"flat=True"の使い方とは?サンプルコード解説