Django ORMでのvalues()のエラーで嵌った話
Django ORMには<Model>.objects.values()と<Model>.objects.only()というvalues()とonly()という似たようなメソッドがあるが、少しハマったので備忘録として。
ことの発端は↓のようなコードを書いたとき次のようなエラーが出たことから。
president1 = Company.objects.values(*fields)[0].president
# => エラー: AttributeError: 'dict' object has no attribute 'president'
ここではCompanyというモデルからDjango ORMのvalues()メソッドを使って、
特定のフィールドだけを抽出して生成されたクエリセット(複数のオブジェクトが入ったイテラブルなデータ型でPythonのリストのようなもの)の最初の要素のpresidentフィールドの値を取得しようとしています。
しかし、実行時にAttributeError: 'dict' object has no attribute 'president'というエラーが発生してしまいました。
モデル定義を見直しても、確かにpresidentというフィールドは存在しているのにこれはおかしいということで調べてみました。 difference between values() and only() が参考になりました。
バージョン
- Django==2.2.3
values()が返すのはQuerySetではなくValuesQuerySet
参考記事によるとvalues()メソッドが返すのはValuesQuerySetというデータ型のオブジェクトだそう。
これはQuerySetに基づいて、**オブジェクトフィールドを辞書に変換した要素の配列のようなもの(=ValuesQuerySet)**を返すそう。
つまり、先のコードでは{'president': 'xxx', ...}という辞書から.presidentを使ってフィールドデータを取り出そうとしていたので、エラーが出ていたということですね。
辞書なので['president']を使えばエラーは起きなかったということです。
エラーメッセージを読むと 'dict' objectと書いてありますね。。
only()を使うとQuerySetが返ってくる
ということで今回のケースのようにQuerySetが欲しい場合はonly()1を使えばいいということになります。今度は要素は辞書ではなくて、ちゃんとクラスのインスタンスが返ってくるのでエラーは起きないですね。
ちなみにQuerySetはイテラブルですが、データ型はlistとは異なるので注意(似ているのでこれもたまにハマる)。
クエリセットをリストに変換するときは**list(QuerySetオブジェクト)**で変換可能。
-
指定したフィールドだけを対象に抽出できる。パフォーマンスチューニングのために使用する。反対に
defer()を使うと指定したフィールドを除外することが可能。 ↩