如何在Django中覆盖超类模型字段的Verbose Name

32

假设我有一个继承自SuperFoo的Foo模型:

class SuperFoo(models.Model):
    name = models.CharField('name of SuperFoo instance', max_length=50)
    ...

class Foo(SuperFoo):
    ... # do something that changes verbose_name of name field of SuperFoo

在类Foo中,我想覆盖SuperFoo的名称字段(name field)的verbose_name。我可以这样做吗?如果不能,那么最好的选择是在模型表单定义内设置一个标签(label),以便在模板中显示它吗?


谢谢您的答案!因此,我们可以采用以下两种方式之一:(i)在表单内设置标签,或者(ii)通过 _meta.get_fields 方法在定义类后设置 verbose_name。 两种方法都很容易实现,但是我认为我更喜欢使用 _meta.get_fields 方法,以便我可以将所有与模型相关的内容放在模型模块中。 - shanyu
5个回答

61

我用过的一个简单技巧是:

class SuperFoo(models.Model):
    name = models.CharField('name of SuperFoo instance', max_length=50)
    ...
    class Meta: 
        abstract = True

class Foo(SuperFoo):
    ... # do something that changes verbose_name of name field of SuperFoo
Foo._meta.get_field('name').verbose_name = 'Whatever'

3
这实际上改变了SuperFoo类的字段verbose_name。例如,如果你有一个类Bar(继承自SuperFoo),那么'name'字段的verbose_name也会变成“Whatever”...即使你按照相同的步骤使用Bar._meta.get_field('name').verbose_name = 'Whatever 2'。 - bogeylicious
3
如果SuperFoo是一个抽象基类,上述评论就不正确了。只要SuperFoo类是抽象的,这种方法就能很好地工作,因此类Meta:abstract = True。 - bogeylicious
问题在于有时无法将SuperFoo抽象化。 - Sassan

10

请注意,修改Foo._meta.fields将影响超类,因此只有在超类是抽象类的情况下才真正有用。基于这个警告,我将@Gerry给出的答案包装成一个可重用的类装饰器:

def modify_fields(**kwargs):
    def wrap(cls):
        for field, prop_dict in kwargs.items():
            for prop, val in prop_dict.items():
                setattr(cls._meta.get_field(field), prop, val)
        return cls
    return wrap

像这样使用:

@modify_fields(timestamp={
    'verbose_name': 'Available From',
    'help_text': 'Earliest date you can book this'})
class Purchase(BaseOrderItem):
    pass

上面的示例更改了继承字段“时间戳”的verbose_name和help_text。您可以传入与要修改的字段数量相同的关键字参数。


谢谢!在 Django 1.11.4 上完美运行。 - emi

5
看看Django-CMS是如何做到这一点的,他们在继承自CMSPlugin的模型中覆盖了db_table字段。基本原理(我也用于自己的东西)归结为:
class SuperFooMetaClass(ModelBase):
    def __new__(cls, name, bases, attrs):
        new_class = super(SuperFooMetaClass, cls).__new__(cls, name, bases, attrs)
        new_class._meta.verbose_name = "...."   # perhaps only if not customized
        return new_class


class SuperFoo(models.Model):
    __metaclass__ = SuperFooMetaClass

    ....

您可以添加一些检查,例如仅为子类(而非直接类型)更新,或仅在值未自定义时更新。

5
你最好在表单本身中设置/更改标签。通过引用Foo模型的name字段(例如,通过在Foo._meta.fields中查找)实际上会给你一个关于SuperFooname字段的引用,因此更改其verbose_name将同时更改这两个模型中的它。
此外,在Foo类中添加name字段也行不通,因为...

覆盖父模型中的字段会导致一些困难,比如初始化新实例(指定在Model.__init__中正在初始化的字段)和序列化。这些是普通Python类继承不必以同样方式处理的特性,因此Django模型继承和Python类继承之间的区别并不仅仅是任意的。


1
我有不同的子类从同一个基类派生。而我只关心Django Admin界面中的表单视图。
对我起作用的唯一解决方案是自定义Django Admin界面并在那里设置verbose_name(或help_text)。
class FooAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        form.base_fields['name'].verbose_name = "my verbose name"
        return form

admin.site.register(models.Foo, FooAdmin)

1
只有当我将新标签分配给 label 属性而不是 verbose_name 属性时,它才有效。例如:form.base_fields['<field_name>'].label = "<custom label>"。我正在使用 Django 3.2。 - Micah Yeager

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