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()
を使うと指定したフィールドを除外することが可能。 ↩