【Django】クエリセットにsetattr関数を使ってフィールドの値を正しく更新する

# 概要
Djangoにて、与えられたリクエストのデータを使ってオブジェクト(モデル)のフィールドの値を更新する際、フィールドの値が正しく更新されない状態を`setattr`を使って解決できたので、その方法を紹介します。

## 正しく更新されない関数

```py
from .models import Product

def update_objects(objects, request, apple_key, banana_key):
    for obj in objects:
        apple_value = request.get(f"{apple_key}_{obj.id}")
        banana_value = request.get(f"{banana_key}_{obj.id}")
        obj.apple_key = apple_value
        obj.banana_key = banana_value
        obj.save()

obj_product = Product.objects.get(id=id)
update_objects(obj_product.colors.all(), request, 'apple_color', 'banana_color')
update_objects(obj_product.sizes.all(), request, 'apple_size', 'banana_size')
update_objects(obj_product.sales.all(), request, 'apple_sale', 'banana_sale')
```

```
# 前提
・ Model Color/Size/Saleがあり、それぞれapple_colorやbanana_colorなどのfieldがある
・ apple_value には、requestで取得された「red」(apple_color)や「big」(apple_size)が入る
・　　objects では以下のようなクエリセットが渡される

 <QuerySet [<Color: Color object (1)>]>

・ obj には以下のようなモデルクラスのインスタンスが返される

　　Color object (1)
```


上記だとフィールドの値が正しく更新されません。

なぜか？それは、`apple_key`や`banana_key`の値そのものがフィールド名になっているからです。
（今回のように、クエリセットの場合は、オブジェクトとしてモデルインスタンスを取得し、属性としてフィールドにアクセスします）

上記のように`apple_key`が`apple_color`、`banana_key`が`banana_color`という文字列を持つ場合、実際のフィールド名は`"apple_color"`や`"banana_color"`となるべきですが、代わりに`obj.apple_key`という形でフィールドにアクセスしているため、`apple_key`の値がそのままフィールド名になってしまいます。

つまり、上記の`obj.apple_key`という表現は、`obj`オブジェクトに「`apple_key`」という名前のフィールドがあるかのように解釈されてしまう、ということです。`apple_value`の値を「`apple_key`」という名前のフィールドに設定しようとしてしまうため、結果的にフィールドが更新されません。


## 正しく更新される関数

```py
def update_objects(objects, request, apple_key, banana_key):
    for obj in objects:
        apple_value = request.get(f"{apple_key}_{obj.id}")
        banana_value = request.get(f"{banana_key}_{obj.id}")
        setattr(obj, apple_key, apple_value)
        setattr(obj, banana_key, banana_value)
        obj.save()

obj_product = Product.objects.get(id=id)
update_objects(obj_product.colors.all(), request, 'apple_color', 'banana_color')
```

この関数では、`setattr`関数を使って、フィールドの値を正しく更新しています。
`setattr`関数は、与えられたオブジェクトと属性名の文字列、そして新しい値を引数として受け取り、オブジェクトの属性を更新するために使用されます。

※フィールドは、Djangoや他のオブジェクト関連マッピング（ORM）フレームワークにおいて、モデルクラスに定義された属性を表します。Pythonのクラスにおける属性と同様です。

`setattr(obj, apple_key, apple_value)`は、`obj`オブジェクトに対して、`apple_key`という文字列で指定された属性に`apple_value`という値を設定します。`setattr(obj, banana_key, banana_value)`も同様です。
クエリセットの表現で言い換えれば、`obj`モデルインスタンスに対して、`apple_key`という文字列で指定されたフィールド(=`apple_color`)に`apple_value`という値を設定しています。


これにより、`apple_key`や`banana_key`の値自体が属性（フィールド）名になることなく、属性（フィールド）の値が適切に更新されるようになります。`apple_value`に入った`red`や`big`などが`apple_color`や`apple_size`に入るようになりました。

## 補足：`setattr`関数について
基本的な構文は以下の通り：

```python
setattr(object, name, value)
```

・`object`: 属性を設定する対象となるオブジェクト。
・`name`: 新しく作成する属性の名前を表す文字列。
・`value`: 属性に設定する値。

以下、サンプルコードです。
上記では特定のフィールドの値を更新する際に利用しましたが、以下のようにオブジェクトに動的な属性を追加することもできます。

```py
class Fruit:
    pass

fruit = Fruit()
setattr(fruit, "name", "Banana")
setattr(fruit, "color", "Yellow")

print(fruit.name)  # 出力: Banana
print(fruit.color)   # 出力: Yellow
```

ちなみに、存在しない属性を指定した場合は、`AttributeError`エラーが出ます。

```
AttributeError: 'Fruit' object has no attribute 'size'
```

必要であればエラーハンドリングを適切に設定する必要があるでしょう。