如何在Django模型子类中更改字段参数?

18

假设我有一个 Django 模型是一个抽象基类:

class Foo(models.Model):
    value=models.IntegerField()

    class Meta:
        abstract = True

它有两个派生类,我希望这个字段的默认值对于每个子类都不同。我不能简单地覆盖该字段。

class Bar(Foo):
    value=models.IntegerField(default=9)

Django不允许在子类中重写字段,我看到过关于尝试更改可用选项的帖子,但在这种情况下,我主要关心更改默认值。有什么建议吗?

5个回答

9
重新定义保存方法的问题在于,在保存模型对象之前,您的值将不会被设置。 另一个没有这个问题的选项是在子类(Bar类)中重新定义__init__
def __init__(self, *args, **kwargs):
    if 'value' not in kwargs:
        kwargs['value'] = 9
    super(Bar, self).__init__(*args, **kwargs)

这个方法有效,但在检索数据时也会被调用,这可能会导致 TypeError: Bar() 的字段 'value' 既接收位置参数又接收关键字参数。因此,最好使用另一种方法。 - Akaisteph7

3

请参考https://dev59.com/RGw15IYBdhLWcg3w0fGh#6379556

您可以按以下方式实现:

class BaseMessage(models.Model):
    is_public = models.BooleanField(default=False)
    # some more fields...

    class Meta:
        abstract = True

BaseMessage._meta.get_field('is_public').default = True

class Message(BaseMessage):
    # some fields...

我认为你的意思是 Message._meta.get_field('is_public').default = True - l0b0

2
我认为你尝试的事情在Django中并不可能,至少目前还没有解决方案。你必须将Django中的继承视为指向超类的ForeignKey(基本上就是这样),而且你不能更改FK关系中属性的默认值。
因此,你最好重新定义save()方法。具体做法如下:
def save(self, *args, **kwargs):
    if not self.value:
        self.value = 9
    super(Bar, self).save(*args, **kwargs)

同时,在Foo(超类)中:

    value = models.IntegerField(blank=True)

为避免与数据库相关的NOT NULL问题。

如果我只是在基类中为所有元素定义默认值,我们是否可以仅覆盖所有子类的save方法,或者在基类中定义的默认值会导致实例变量永远不为空?(也就是说,如果我在父类中定义了一个默认值,那么在保存或初始化对象时是否添加该默认值?) - Zxaos
1
你需要将Django中的继承视为对超类的外键引用,因此如果超类具有带有默认值的字段,则子类中的字段也将具有该默认值,因为该字段实际上不是来自子类而是来自超类。 - juliomalegria

2
这定义了一个元类(和一个基类),为所有子类提供一个给定类型的字段,其默认值可以在子类中设置,但不一定需要设置:
from django.db import models
class DefaultTextFieldMetaclass(models.base.ModelBase):
    DEFAULT_TEXT = 'this is the metaclass default'

    def __new__(mcs, name, parents, _dict):
        if not (('Meta' in _dict) and hasattr(_dict['Meta'], 'abstract') and _dict['Meta'].abstract):
            # class is concrete
            if 'DEFAULT_TEXT' in _dict:
                default = _dict['DEFAULT_TEXT']
            else:       # Use inherited DEFAULT_TEXT if available
                default_set = False
                for cls in parents:
                    if hasattr(cls, 'DEFAULT_TEXT'):
                        default = cls.DEFAULT_TEXT
                        default_set = True
                if not default_set:
                    default = mcs.DEFAULT_TEXT
            _dict['modeltext'] = models.TextField(default=default)
        return super(DefaultTextFieldMetaclass, mcs).__new__(mcs, name, parents, _dict)


class BaseTextFieldClass(models.Model):
    class Meta(object):
        abstract = True
    __metaclass__ = DefaultTextFieldMetaclass
    DEFAULT_TEXT = 'superclass default'


class TextA(BaseTextFieldClass):
    DEFAULT_TEXT = 'A default for TextA'

class TextB(BaseTextFieldClass):
    DEFAULT_TEXT = 'The default for TextB'
    number = models.IntegerField(default=43)

class TextC(BaseTextFieldClass):
    othertext = models.TextField(default='some other field')

除非您有许多子类和/或多个方法/属性以及与BaseTextFieldClass相关的假设,否则这可能过于复杂了...但它应该能够满足OP的要求。

0
这可能是一个较新的功能,但在Django 4.2.4中,我可以只需重新定义字段,Django会正确地在迁移文件中重新创建它。
class Foo(models.Model):
    value=models.IntegerField()

    class Meta:
        abstract = True

class Bar(Foo):
    value=models.IntegerField(default=9)

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