将Django字段描述从现有模型复制到新模型

时间:2012-08-31 20:21:24

标签: django django-models

我尝试根据现有模型中的字段动态生成新模型。两者都在/apps/main/models.py中定义。现有模型看起来像这样:

from django.db import models

class People(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    height = models.IntegerField()

我有一个列表,其中包含我要复制的字段名称:

target_fields = ["name", "age"]

我想生成一个新模型,其中包含target_fields中命名的所有字段,但在这种情况下,它们应该被编入索引(db_index = True)。

我原本希望我能够遍历People的类属性并使用copy.copy来复制在其上定义的字段描述。像这样:

from copy import copy

d = {}
for field_name in target_fields:
    old_field = getattr(People, field_name) # alas, AttributeError
    new_field = copy(old_field)
    new_field.db_index = True
    d[field_name] = new_field

IndexedPeople = type("IndexedPeople", (models.Model,), d)

我不确定copy.copy()字段是否可行,但我还没有找到答案:课程定义中列出的字段不是&#39} ; t实际上作为属性包含在类对象中。我认为它们会被用于某些元类恶作剧。

在调试器中查找后,我发现People._meta.local_fields中列出了某种类型的Field对象。但是,这些不仅仅是简单的描述,可以copy.copy()用于描述另一个模型。例如,它们包含引用.model的{​​{1}}属性。

如何根据现有模型的字段为新模型创建字段描述?

2 个答案:

答案 0 :(得分:6)

从调试器和源代码中探索:所有Django模型都使用/db/models/base.py中定义的ModelBase元类。对于模型类定义中的每个字段,ModelBase的{​​{1}}方法将调用字段的.add_to_class方法。

.contribute_to_class/db/models/fields/__init__.py中定义,它负责将字段定义与特定模型相关联。通过添加Field.contribute_to_class属性并使用模型类定义中使用的名称调用.model方法来修改字段。这反过来又会添加.set_attributes_from_name.attname属性,并在必要时设置.column.name

当我检查新定义的.verbose_name的{​​{1}}属性并将其与已与模型关联的__dict__的属性进行比较时,我也看到这些是唯一的区别:

  • CharField属性对于每个实例都是唯一的。
  • 新实例上不存在CharField.creation_counter.attrname属性。
  • 新实例上的.column.model属性为.name

似乎无法区分手动指定给构造函数的.verbose_name / None属性和自动生成的属性。您需要选择始终重置它们,忽略任何手动指定的值,或从不清除它们,这将导致它们始终忽略它们在新模型中给出的任何新名称。我想使用与原始字段相同的名称,因此我不打算触摸它们。

知道存在哪些差异,我使用.name克隆现有实例,然后应用这些更改使其行为像新实例。

.verbose_name

鉴于此功能,我使用以下内容创建新模型:

copy.copy()

有效!

答案 1 :(得分:1)

解决方案

存在一种用于复制字段Field.clone()的方式-解构字段的方法将删除任何模型相关的引用:

    def clone(self):
        """
        Uses deconstruct() to clone a new copy of this Field.
        Will not preserve any class attachments/attribute names.
        """
        name, path, args, kwargs = self.deconstruct()
        return self.__class__(*args, **kwargs)

因此,您可以使用以下util来复制字段,以确保不会意外影响从中复制模型的源字段:

def get_field(model, name, **kwargs):
    field = model._meta.get_field(name)
    field_copy = field.clone()
    field_copy.__dict__.update(kwargs)
    return field_copy

还可以传递一些常规的kwarg,例如verbose_name等:

def get_field_as_nullable(*args, **kwargs):
    return get_field(*args, null=True, blank=True, **kwargs)

不适用于模型定义内的m2m字段。(模型定义上的m2m.clone()引发AppRegistryNotReady: Models aren't loaded yet

为什么要用它而不是抽象模型?

嗯,要视情况而定。有时您不需要继承,但精算字段复制。什么时候?例如:

我有一个用户模型和一个模型,该模型代表一个用于用户数据更新的应用程序(用于用户数据更新请求的文档):

class User(models.Model):
    first_name = ...
    last_name = ...
    email = ...
    phone_number = ...
    birth_address = ...
    sex = ...
    age = ...
    representative = ...
    identity_document = ...


class UserDataUpdateApplication(models.Model):
    # This application must ONLY update these fields.
    # These fiends must be absolute copies from User model fields.
    user_first_name = ...
    user_last_name = ...
    user_email = ...
    user_phone_number = ...

因此,由于某些其他非用户逻辑扩展模型想要的事实,我不应将用户模型中的字段重复到抽象类具有完全相同的字段。为什么?因为它与用户模型不是直接直接相关-用户模型不应该在乎它的依赖关系(不包括您要扩展用户模型的情况) ),因此不应将其分开,原因是其他一些具有自己非用户相关逻辑的模型希望具有完全相同的字段。

相反,您可以执行以下操作:

class UserDataUpdateApplication(models.Model):
    # This application must ONLY update these fields.
    user_first_name = get_field(User, 'first_name')
    user_last_name =  get_field(User, 'last_name')
    user_email =  get_field(User, 'user_email')
    user_phone_number =  get_field(User, 'phone_number')

您还将使som util能够“动态”生成一些abc类,以避免代码重复:

class UserDataUpdateApplication(
    generate_abc_for_model(
        User,
        fields=['first_name', 'last_name', 'email', 'phone_number'],
        prefix_fields_with='user_'),
    models.Model,
):
    pass