概要
Djangoにて、与えられたリクエストのデータを使ってオブジェクト(モデル)のフィールドの値を更新する際、フィールドの値が正しく更新されない状態をsetattr
を使って解決できたので、その方法を紹介します。
正しく更新されない関数
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
」という名前のフィールドに設定しようとしてしまうため、結果的にフィールドが更新されません。
正しく更新される関数
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
関数について
基本的な構文は以下の通り:
setattr(object, name, value)
・object
: 属性を設定する対象となるオブジェクト。
・name
: 新しく作成する属性の名前を表す文字列。
・value
: 属性に設定する値。
以下、サンプルコードです。
上記では特定のフィールドの値を更新する際に利用しましたが、以下のようにオブジェクトに動的な属性を追加することもできます。
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'
必要であればエラーハンドリングを適切に設定する必要があるでしょう。