LoginSignup
0
2

【Django】ORMを使用したDB操作(objectsが必要な場合とは?モデルクラスやモデルマネージャとは)

Posted at

概要

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モデルは以下のように定義しています。

response/models.py
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つのレコードがある状態とします。
image.png

データベースからレコードを取得する

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", "上書き:これはテスト用です")

# 結果
# 上書き:これはテスト用です

上書きすると以下の通り更新されたことがわかります。
image.png

該当のレコードが存在するかチェックする

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の後にモデルマネージャのメソッドを連結させてもエラーになります。必ず、fileterallのようにクエリセットで返すものに対して連結させるようにします。

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)

削除されていますね。

image.png

レコードを新規に作成する

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メソッドで作ったレコード')

image.png

複数のレコードを一度に作成する

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レコード一括で登録されたのがわかります。

image.png

指定した条件に一致するレコードを取得または新規作成する

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は慣れるのに時間かかるなーと思っています。
他にもvaluesvalues_listもあります。これらを使うと、特定のフィールドの値を抽出して辞書として取得できます。以前以下の記事でも記載したのでご参考までに。

【Python】select_related(とvaluesメソッド)を使用してクエリの数を最適化する
【Django】ORMのvalues_listメソッドの引数"flat=True"の使い方とは?サンプルコード解説

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2