setattr(self, field.attname, val) AttributeError: can't set attribute

※ 解決方法の部分を修正しました。

django.db.models.Modelを継承したクラス、つまりDB用のモデルクラスのインスタンスを生成する際に、「setattr(self, field.attname, val) AttributeError: can't set attribute」というエラーが発生しました。このエラーが発生するのは色々なケースがあるのだと思われますが、自分の場合は「hogeという名前のForeignKeyがあり、hoge_idという名前のプロパティを宣言している」場合に発生していました。

--- ファイル: test/models.py ---

from django.db import models

class Hoge(models.Model):
    name = models.CharField(max_length=64)


class Fuga(models.Model):
    name = models.CharField(max_length=64)
    hoge = models.ForeignKey('Hoge', null=True)

    @property
    def hoge_id(self):
        return self.hoge.id if self.hoge else None
$ python manage.py shell
>>> from test.models import *
>>> fuga = Fuga(name='FUGA')
Traceback (most recent call last):
  File "", line 1, in 
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/Django-1.3-py2.7.egg/django/db/models/base.py", line 354, in __init__
    setattr(self, field.attname, val)
AttributeError: can't set attribute

SQLを見ると、hogeというForeignKeyの宣言により、hoge_idというカラムを生成するようになっていることがわかります。これとプロパティhoge_idとバッティングしてしまっています。

$ python manage.py sql test
BEGIN;
CREATE TABLE "test_hoge" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(64) NOT NULL
)
;
CREATE TABLE "test_fuga" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(64) NOT NULL,
    "hoge_id" integer REFERENCES "test_hoge" ("id")
)
;
COMMIT;

なお、インスタンス変数とプロパティのバッティングによりエラーが発生するのは、django.db.models.を継承したクラスでなくても確認できました。

$ python
>>> class Fuga(object):
...    def __init__(self):
...        self.hoge = 'HOGE'
...
...    @property
...    def hoge(self):
...        return self.hoge
>>> fuga = Fuga()
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in __init__
AttributeError: can't set attribute

解決方法は、バッティングしないように気をつけるだけです。 ※ メモ「Foreignkeyのpkを返すpropertyについて」に書きましたが、そもそもプロパティを定義する意味がなさそうです。また、試しては無いけれども、恐らくOneToOneFieldでも発生すると思われます(ManyToManyFieldは大丈夫だと思います)。

この記事が役に立った場合、シェアしていただけると励みになります!!