Django 3.2:AttributeError: 'NoneType' object has no attribute 'attname'

6
我正在使用 Django 3.2,并遇到了一个问题,似乎有答案在这里这里。我已经尝试了被接受答案中提供的解决方案,但是无法解决我遇到的问题(主题标题中的错误信息)。
下面是我的模型代码:
#### models.py
class ActionableModel:

    def __init__(self, child_object):
        self.child = child_object

        # FIX: This might be an expensive operation ... need to investigate
        if isinstance(child_object, django.contrib.auth.get_user_model()):
            self.owner = child_object
            self.actionable_object = child_object
        else:
            self.owner = child_object.get_owner()
            self.actionable_object = child_object.get_actionable_object()

        self.owner_id = self.owner.id
        
        self.ct = ContentType.objects.get_for_model(self.actionable_object)
        self.object_id = self.actionable_object.id

    # ...


class Prey(models.Model):
    # ... fields
    pass


class Predator(models.Model, ActionableModel):
    catches = GenericRelation(Prey)
    catch_count = models.PositiveIntegerField(default=0, db_index=True)
    last_catch = models.DateTimeField(editable=False, blank=True, null=True, default=None)
     

    def __init__(self, *args, **kwargs):
        ActionableModel.__init__(self, *args, **kwargs)  


    # ...
    
    class Meta:
        abstract = True


# Classes used for testing - naming similar to [mommy](https://model-mommy.readthedocs.io/en/latest/basic_usage.html)
class Daddy():
    
    def __init__(self, *args, **kwargs):
        ActionableModel.__init__(self, *args, **kwargs)      





class Foo(Daddy, Predator):
    # ...
    pass

在 shell 中,我输入以下内容:

from myapp.models import Foo
admin = django.contrib.auth.get_user_model().objects.all().first()
foo = Foo.objects.create(child_object=admin)

以下是导致主题标题中错误的堆栈跟踪:

Foo.objects.create(child_object=admin)                                                                            
Traceback (most recent call last):                                                                                                            
  File "<console>", line 1, in <module>                                                                                                       
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method  
    return getattr(self.get_queryset(), name)(*args, **kwargs)                                                                                
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/query.py", line 453, in create
    obj.save(force_insert=True, using=self.db)
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/base.py", line 726, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/base.py", line 763, in save_base
    updated = self._save_table(
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/base.py", line 822, in _save_table
    pk_val = self._get_pk_val(meta) 
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/base.py", line 575, in _get_pk_val
    return getattr(self, meta.pk.attname)
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/query_utils.py", line 148, in __get__
    val = self._check_parent_chain(instance)
  File "/path/to/myapp/env/lib/python3.8/site-packages/django/db/models/query_utils.py", line 164, in _check_parent_chain
    return getattr(instance, link_field.attname)
AttributeError: 'NoneType' object has no attribute 'attname'

什么导致了这个错误,我该如何解决它?

4
我猜这是因为 Foo 继承自 Model,但你定义了自己的 __init__() 方法,因此 Model.__init__() 从未被调用,而 attname 可能就是在那里定义的。 - John Gordon
1
一般来说,如果你继承了一个类但是没有调用那个类的 __init__ 方法,那么你就会遇到麻烦。 - John Gordon
为什么你需要在__init__方法中特别执行所有这些操作,难道save方法不是一个选项吗?通常情况下,模型类可以实例化而无需传递任何实际值。 - Abdul Aziz Barkat
1
你说过你已经尝试了链接的重复问题中的被接受的解决方案,但是你似乎没有尝试通过调用超类的__init__方法来尝试链接的第一个问题的被接受的答案? - D Malan
你检查过赋值后的admin是什么吗?它是一个None值吗? - Edo Akse
显示剩余3条评论
1个回答

1
问题似乎是从Django的Model类中__init__方法从未被调用。这是因为在您的派生类中定义了一个__init__方法,但没有显式调用super()方法。
需要两件事情:
  • 设置一条super调用链。
  • 确保方法解析顺序允许super链到达Model.__init__方法。
将这些点应用于提供的源代码:
class ActionableModel:

    def __init__(self, child_object, *args, **kwargs):
        # need to call super here
        super().__init__(*args, **kwargs)
        self.child = child_object
        ...


class Prey(models.Model):
    pass


class Predator(ActionableModel, models.Model):
    catches = GenericRelation(Prey)
    ...    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)  


class Daddy:        
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)      


class Foo(Daddy, Predator):
    pass

你如何调用创建 Foo 实例的方法(并将 parent_object 变量传递给父类 Predator,该父类还将 parent_object 变量传递给 ActionableModel 类)? - Homunculus Reticulli
你为什么决定改变派生顺序?我记得在某个地方(想不起来是哪里了)读到过,使用Django中的多重继承时,最好总是先从models.Model派生。您是否有一份权威参考支持您选择的派生顺序? - Homunculus Reticulli
我更新了答案以反映我认为你想要做的事情。给定一个 parent_object 实例,您将执行 foo = Foo(parent_object) 来获取“本地”对象。要推送到数据库,您将需要调用 foo.save() 方法,假设您正确分配了满足数据库约束所需的其他字段。 - Sergio
我回到源代码并看到 models.Model.__init__ 实际上调用了其超类方法。这意味着您可以首先从 models.Model 派生。然而,鉴于 super().__init__(*args, **kwargs) 调用是在 ActionableModel 中执行的第一件事,models.Model 初始化将在运行 ActionableModel.__init__ 的其余部分之前完成。换句话说,鉴于上面的代码编写方式,交换派生顺序应产生相同的结果。 - Sergio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接