如何覆盖抽象模型的 null 和空白字段属性

Posted

技术标签:

【中文标题】如何覆盖抽象模型的 null 和空白字段属性【英文标题】:How to override null and blank field attributes of an abstract model 【发布时间】:2017-11-07 20:20:38 【问题描述】:

我想在从抽象模型继承的所有字段上将 null 和空白设置为 true。

我目前的尝试遵循类似的 SO 问题,例如overriding the 'default' attribute on ABC 和 overriding parent model's attribute,表示这是可能的。从 python 控制台初始化对象时,我得到了所需的运行时行为,但它没有反映在迁移文件或数据库中。

上下文:

我有一个系统模型,我希望能够在其中对某些数据创建特定于客户端的覆盖。我有以下型号:

抽象 BaseSystem - 定义可覆盖的字段 具体的 SystemOverride - 包含部分覆盖的记录 具体系统 - 包含“完整”系统记录。

重要的是使 SystemOverride 中的所有字段都为 null/blank = True,这样只有(由客户端)初始化的字段才会覆盖相关的 System object。

代码:

class BaseSystem(models.Model):

    class Meta:
        abstract = True

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

        # Mark all fields with 'override' attribute
        for field in self._meta.get_fields():
            field.override = True

    name = models.CharField(max_length=128)


class System(BaseSystem):
    pass


class SystemOverride(BaseSystem):

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

        # Set all overridable fields to null/blank = True. 
        for field in self._meta.get_fields():
            if(hasattr(field, 'override') and field.override):
                field.null = True
                field.blank = True

    # Override-specific fields
    system = models.ForeignKey(System)

makemigrations 的结果:

class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='System',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=128)),
            ],
            options=
                'abstract': False,
            ,
        ),
        migrations.CreateModel(
            name='SystemOverride',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=128)),
                ('system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='overide.System')),
            ],
            options=
                'abstract': False,
            ,
        ),
    ]

null=True 和 blank=True 尚未添加到 SystemOveride 中的名称字段。

【问题讨论】:

【参考方案1】:

这不能在类的 init 中完成。临时移民永远不会看到它。您需要在元类级别执行此操作。

【讨论】:

感谢您的回答,但您能否详细说明/显示一些代码来实现这一目标? 执行 makemigrations 时您的 init 不会运行。它在您创建类的实例时运行。如果要在创建类之前修改类的属性,请检查 modelbase。 github.com/django/django/blob/master/django/db/models/base.py 感谢您指出这一点。我仍在努力弄清楚如何实现这一点。我是否需要覆盖 SystemOverride 类上的 __new__(cls, name, bases, attrs) 方法,以便在属性转到 ModelBase.__new__ 之前对其进行修改?任何建议将不胜感激。谢谢【参考方案2】:

你可以像这样创建一个抽象的 CharField 类:

class AbstractCharField(models.CharField):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.null = True
        self.blank = True

    class Meta:
        abstract = True


# usage
name = AbstractCharField(max_length=128)

【讨论】:

【参考方案3】:

我最近实际上正在寻找类似的东西,但找不到解决方案。不过,我确实找到了这篇文章。这是我最终想出的解决方案:

from django.db.models.base import ModelBase


class NullableInheritanceMeta(ModelBase):

    def __new__(mcs, name, bases, attrs, **kwargs):
        cls = super().__new__(mcs, name, bases, attrs, **kwargs)
        null_fields_from = cls.null_parent_fields_classes

        for parent in null_fields_from:
            for field in parent._meta.fields:
                cls._meta.get_field(field.name).null = True

        return cls

我使用了一个元类,因为我真的希望它是 DRY 且可重复使用的。您可以使用它的方式是:

class AbstractPermissions(models.Model):

    permission1 = models.BooleanField()
    permission2 = models.BooleanField()
    permission3 = models.BooleanField()

    class Meta:
        abstract = True


class Plan(AbstractPermissions):
    pass


class PlanOverride(AbstractPermissions, metaclass=NullableInheritanceMeta):
    null_parent_fields_classes = [PermissionMixin]

感谢null_parent_fields_classes 属性,您应该能够从许多基类中实现,但选择您应该从哪个基类中清除字段。

【讨论】:

以上是关于如何覆盖抽象模型的 null 和空白字段属性的主要内容,如果未能解决你的问题,请参考以下文章

使用 findOneAndUpdate() 时,如果没有提供值,如何保持字段不变(而不是用 null 覆盖)?

如何创建作为多个查询联合的 Django 模型字段,以实现覆盖字段?

Django HStore:如何覆盖键值模型字段的 __getattr__ 和 __setattr__

如何在 django rest 框架 ModelSerializer 中覆盖模型字段验证

如何覆盖父对象的静态属性并让父对象访问 PHP 中的新值?

从覆盖报告中排除抽象属性