【问题标题】:Django - why can't I access dynamically-generated attributes in classes in my models.py from admin.py?Django - 为什么我不能从 admin.py 访问我的 models.py 中的类中动态生成的属性?
【发布时间】:2014-12-24 10:52:30
【问题描述】:

这里是models.py

class Parent(models.Model):
    id        = models.CharField(max_length=14, primary_key=True)
    json_dump = models.TextField(null=False)

    def __init__(self, *args, **kwargs):
        super(Base, self).__init__(*args, **kwargs)
        setattr(self, 'name', json.loads(self.json_dump)['name'])

    class Meta:
        abstract = True


class Child(Parent):
    magnitude  = models.IntegerField()

在我的admin.py 中,我想为 Child 配置管理员以显示 name 属性,所以我有以下内容:

class ChildAdmin(admin.ModelAdmin):
    model = Child

    def get_list_display(self, request):
        return ('id', 'name', 'magnitude')


admin.site.register(Child, ChildAdmin)

我必须从 get_list_display 方法动态生成 list_display,否则 Django 在启动时会引发错误,抱怨 name 未在 Child 模型中定义。但是,在运行时,name 应该可用,因为只要从数据库中实例化对象,它就会在 __init__ 方法中设置。

但是,当我尝试加载管理页面时出现错误:

Unable to lookup 'name' on Child or ChildAdmin

什么给了?

【问题讨论】:

    标签: django django-models django-admin django-1.7


    【解决方案1】:
    <class 'app.admin.ChildAdmin'>: (admin.E108) The value of 'list_display[1]' 
    refers to 'name', which is not a callable, an attribute of 'ChildAdmin', 
    or an attribute or method on 'app.Child'.
    

    以上很可能是您收到的错误消息。抽象类不允许您像这样从抽象类继承实例属性。它正在 Child 类上寻找 self.name,但它不存在。

    我们要查看的错误部分是:

    ...不是可调用的

    不。它不是可调用的,它是一个属性。

    ...一个'ChildAdmin'的属性

    不。它不是ChildAdmin 类的属性。

    ...或“app.Child”上的属性或方法

    这是让你绊倒的部分。 “给出的”是它不是Child 类的属性或方法,而是Parent 类的属性或方法。

    你想做的是:

    class Parent(models.Model):
        id = models.CharField(max_length=14, primary_key=True)
        json_dump = models.TextField(null=False)
    
        class Meta:
            abstract = True
    
        @property
        def name(self):
            return json.loads(self.json_dump)['name']
    
    class Child(Parent):
        magnitude  = models.IntegerField()
    

    这样做将使该属性可用于父级的Child 类。或者,您可以创建一个名为get_name 的函数定义,而不是使用@property 装饰器。我发现第一个更简单。

    此方法的注意事项是它不会在运行时保存名称。如果您想这样做,您可能需要考虑 Django 的信号来执行 post_save 挂钩以检索名称值并将 name = models.CharField(...) 添加到您的模型中。

    为了澄清,Django 不支持这个。在启动时,以下代码会检查 list_display 属性:

    def _check_list_display_item(self, cls, model, item, label):
        """
        cls=<class 'app.admin.ChildAdmin'>
        model=<class 'app.models.Child'>
        item='name'
        """
        # 'name' is not callable
        if callable(item):  
            return []
        # <class 'app.admin.ChildAdmin'> does not have the class attribute 'name'
        elif hasattr(cls, item):
            return []
        # <class 'app.models.Child'> does not have the class attribute 'name'
        elif hasattr(model, item): 
            ...
        else:
            try:
                # <class 'app.models.Child'>.Meta does not have a field called 'name'
                model._meta.get_field(item)
            except models.FieldDoesNotExist:
                # This is where you end up.
                return [
                    # This is a deliberate repeat of E108; there's more than one path
                    # required to test this condition.
                    checks.Error(
                        "The value of '%s' refers to '%s', which is not a callable, an attribute of '%s', or an attribute or method on '%s.%s'." % (
                            label, item, cls.__name__, model._meta.app_label, model._meta.object_name
                        ),
                        hint=None,
                        obj=cls,
                        id='admin.E108',
                    )
                ]
    

    如您所见,我已将 cmets 添加到运行的代码中,以帮助您了解正在发生的事情。你是对的,Child 的实例确实有名字,但这不是 Django 想要的。它正在寻找一个类属性,而不是实例属性。

    所以,解决这个问题的另一种方法(你也不会喜欢这个)是:

    class Parent(models.Model):
        id         = models.CharField(max_length=14, primary_key=True)
        json_dump  = models.TextField(null=False)
        name       = ''
        other_item = ''
        this_too   = ''
        and_this   = ''
    
        class Meta:
            abstract = True
    
        def __init__(self, *args, **kwargs):
            super(Parent, self).__init__(*args, **kwargs)
            setattr(self, 'name', json.loads(self.json_dump)['name'])
            setattr(self, 'other_item', json.loads(self.json_dump)['other_item'])
            setattr(self, 'this_too', json.loads(self.json_dump)['this_too'])
            setattr(self, 'and_this', json.loads(self.json_dump)['and_this'])
    

    这行得通。我刚刚测试了它。 Django 会在类上找到属性。

    【讨论】:

    • 非常感谢。我不想使用@property 样式,因为我有几十个这样的属性并且想动态地填充对象。此外,在 Parent 的 __init__ 方法中,self 的类型为 Child,可以通过将行 print type(self) 添加到 __init__ 方法来验证。所以setattr(self, 'name', json.loads(self.json_dump)['name']) 应该在子对象上设置name 属性。
    • @JonCrowell 我已经用为什么 Django(包括 Django 代码)不喜欢你正在做的事情和替代(hacky)解决方案更新了我的答案。请参阅更新答案的底部。
    • 你拯救了我的一天!谢谢,它在 Django 1.11 中也适用于 annotated fields from the manager 类。
    • @MarianoRuiz,至少在带注释的字段方面,对我来说这看起来像是一个不舒服的 hack。我认为最好通过在ModelAdmin 上声明一个实例方法直接在管理员中解决问题。在管理员以外的其他地方,带注释的字段已经被视为方法的属性。
    【解决方案2】:

    doc 中所述,将Parent 模型更改为:

    class Parent(models.Model):
        id        = models.CharField(max_length=14, primary_key=True)
        json_dump = models.TextField(null=False)
    
        def name(self):
            return json.loads(self.json_dump)['name']
    
        class Meta:
            abstract = True
    

    所以name 属性会显示出来。

    【讨论】:

    • 我的例子被简化了——实际上有几十个属性和更复杂的继承层次结构。
    • 模拟一个数据属性name,方法应该是get_name,而不是name
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-30
    • 2018-04-23
    • 1970-01-01
    相关资源
    最近更新 更多