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

# 概要
DjangoではORMによって、データベーステーブルとPythonオブジェクトの間でデータのマッピングを行い、Pythonコードを使用してデータベース操作を行うことができます。
SQLクエリを書かなくてもPythonコードで行える、というのがポイントですよね。

ただし、時々モデルのインスタンスメソッド？`objects`マネージャのメソッド？とかで困惑したり、記載方法でエラー返されたりすることがあります。
ということでORM（Object-Relational Mapping）について整理します。

# サンプルコード(モデルのメソッド：`save`)

以下のコードでは、`save_response`関数は与えられた`response`データを`Response`モデルを介してデータベースに保存しています。

```py
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 ドキュメント](https://man.plustar.jp/django/topics/db/managers.html)

`objects`マネージャとは、データベースへのアクセスを提供するコンポーネントのこと。
データベーステーブルにアクセスし、レコードの作成、読み取り、更新、削除などの操作を行うためのメソッドを提供しています。

また、以下記事ではどんな場合にモデルマネージャを使うのか、という点で分かりやすく解説してくださっています。
[【初心者向け】Djangoのモデル操作でobjectsが必要な場合・不要な場合を理解する](https://qiita.com/shonansurvivors/items/12b087cf5ab591273c8c)

ということで、いくつかやってみます。
上記サンプルコードと同じ要領で増やし、DBテーブルには3つのレコードがある状態とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/a12e902e-c51d-1c99-cba5-f4f12b6949ef.png)

### データベースからレコードを取得する
```py
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)

# 結果
# これはテスト用です
```

### データベース内のすべてのレコードを取得する

```py
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`)の記述は不要です。

```py
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`を使っています。つまり、モデルマネージャを連結させていることになるので、以下のように書いても同じ結果が得られます。

```py
response = Response.objects.all().get(request_id="test0000001")
print(response)
```

もう一つ補足すると、モデルクラスに直接モデルマネージャのメソッドを使うとエラーになります。
つまり、`objects`がないとエラーになります。


```py
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`メソッドはモデルクラスにそのまま繋げることができたぞ、となるかもですが、あれは**モデルのインスタンスメソッドであり、モデルマネージャのメソッドではありません**(ここ混ざりやすい)。

```py
save -----> class Model(metaclass=ModelBase)
get  -----> class _BaseQuerySet(Generic[_T], Sized):
```

モデルのインスタンスメソッドである`save`の前に`objects`をつけてしまうと以下のエラーになります。

```
AttributeError: Manager isn't accessible via Response instances
```




### データベース内のレコードを更新する
```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/e6bc698a-5f56-7430-07e8-d459b216c7f1.png)

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

```py
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
```

### 特定の条件を満たすレコードをクエリする
```py
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`のようにクエリセットで返すものに対して連結させるようにします。

```py
response = Response.objects.get(request_id="test0000001").exists()
print(response)
# 結果
# AttributeError: 'Response' object has no attribute 'exists'
```

### データベース内のレコードを削除する
```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/151fb66b-8570-813f-492b-c7d5d123cc04.png)

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

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/d3a236e1-e746-5b41-d8d3-05f48edf71db.png)



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

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/5bf62609-2a47-9d31-fc0e-1c8b104edb56.png)

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

```py
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メソッド）を使用してクエリの数を最適化する](https://qiita.com/Ryo-0131/items/dd4b9dce699508d6740b)
[【Django】ORMのvalues_listメソッドの引数"flat=True"の使い方とは？サンプルコード解説](https://qiita.com/Ryo-0131/items/8f55c06528d84009a0e3)