0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-07-27

概要

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_keybanana_keyの値そのものがフィールド名になっているからです。
(今回のように、クエリセットの場合は、オブジェクトとしてモデルインスタンスを取得し、属性としてフィールドにアクセスします)

上記のようにapple_keyapple_colorbanana_keybanana_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_keybanana_keyの値自体が属性(フィールド)名になることなく、属性(フィールド)の値が適切に更新されるようになります。apple_valueに入ったredbigなどがapple_colorapple_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'

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

0
0
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
0