Djangoのモデルクラスにフィールドやメソッドを定義しておいて、さらに属性値を代入していて、この値を取得するコードを書く場合。
メソッドのほうはgetattrでとればいいのだけど、同様にDjangoのフィールドのほうはget_fieldでフィールドを取得しないといけない。
メソッドから取得する方法に失敗したら、Djangoのフィールドとみなしてget_fieldメソッドを呼ぶようなコードを書いていた。
しかし、Django1.10以降だとModel.フィールド名でアクセスすると、DeferredAttributeが返され、hasattrがTrueになるため、はまったりしていた。
検証コード
test.py:
def main(): import django django.setup() from django.db import models class MyModel(models.Model): class Meta: app_label = "__main__" def method1(self): pass method1.attr2 = "egg" # この値をとりたい field1 = models.CharField(max_length=10) field1.attr1 = "spam" # この値も取りたい # メソッドに指定された属性の取得 print("method: ", MyModel.method1) print("hasattr: ", hasattr(MyModel, "method1")) print("getattr: ", getattr(MyModel.method1, "attr2", "invalid")) # Model.フィールド名だとDeferredAttributeが返されるので同じやり方ではダメ print("field: ", MyModel.field1) print("hasattr: ", hasattr(MyModel, "field1")) print("getattr: ", getattr(MyModel.field1, "attr1", "invalid")) # _meta.get_fieldを使うのが正解 print("attr1: ", MyModel._meta.get_field("field1").attr1) if __name__ == '__main__': main()
実行結果
Django1.9だとDeferredAttributeは返されずエラー(当時はここからフォールバックして_meta.get_fieldでアクセスするコードを書いていた)
$ DJANGO_SETTINGS_MODULE=project.settings venv-dj19/bin/python test.py method: <function main.<locals>.MyModel.method1 at 0x7fe61a6b90d0> hasattr: True getattr: egg Traceback (most recent call last): File "test.py", line 32, in <module> main() File "test.py", line 23, in main print("field: ", MyModel.field1) AttributeError: type object 'MyModel' has no attribute 'field1'
Django1.10以降はエラーにならなくなった。挙動の違いでハマった。
$ DJANGO_SETTINGS_MODULE=project.settings venv-dj110/bin/python test.py method: <function main.<locals>.MyModel.method1 at 0x7f6ac886bea0> hasattr: True getattr: egg field: <django.db.models.query_utils.DeferredAttribute object at 0x7f6ac8de19e8> hasattr: True getattr: invalid attr1: spam
Django1.11
$ DJANGO_SETTINGS_MODULE=project.settings venv-dj111/bin/python test.py method: <function main.<locals>.MyModel.method1 at 0x7fd012e3e1e0> hasattr: True getattr: egg field: <django.db.models.query_utils.DeferredAttribute object at 0x7fd01342cbe0> hasattr: True getattr: invalid attr1: spam