我正在尝试根据现有模型中的字段动态生成新模型。两者都在/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
中命名的所有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()
ing Fields是否可以工作,但我没有得到足够的结论:类定义中列出的字段实际上并没有作为属性包含在类对象中。我认为它们被用于一些元类恶作剧。
在调试器中查找后,我发现People._meta.local_fields
中列出了某种类型的Field对象。然而,这些不仅仅是简单的描述,可以用copy.copy()
ed并用于描述另一个模型。例如,它们包括.model
属性,指的是People
。
如何基于现有模型的字段为新模型创建字段描述?
从调试器和源代码中探索:所有Django模型都使用ModelBase
中定义的/db/models/base.py
元类。对于模型类定义中的每个字段,ModelBase
的.add_to_class
方法将调用字段的.contribute_to_class
方法。
Field.contribute_to_class
在/db/models/fields/__init__.py
中定义,它负责将字段定义与特定模型相关联。通过添加.model
属性并使用模型的类定义中使用的名称调用.set_attributes_from_name
方法来修改该字段。这反过来又增加了.attname
和.column
属性,并在必要时设置.name
和.verbose_name
。
当我检查新定义的__dict__
的CharField
属性并将其与已经与模型相关联的CharField
的属性进行比较时,我也看到这些是唯一的区别:
.creation_counter
属性对于每个实例都是唯一的。.attrname
,.column
和.model
属性。.name
和.verbose_name
属性是None
。似乎无法区分手动指定给构造函数的.name
/ .verbose_name
属性和自动生成的属性。您需要选择始终重置它们,忽略任何手动指定的值,或从不清除它们,这将导致它们始终忽略它们在新模型中给出的任何新名称。我想使用与原始字段相同的名称,所以我不打算触摸它们。
知道存在哪些差异,我使用copy.copy()
克隆现有实例,然后应用这些更改使其行为像新实例。
import copy
from django.db import models
def copy_field(f):
fp = copy.copy(f)
fp.creation_counter = models.Field.creation_counter
models.Field.creation_counter += 1
if hasattr(f, "model"):
del fp.attname
del fp.column
del fp.model
# you may set .name and .verbose_name to None here
return fp
鉴于此功能,我使用以下内容创建新模型:
target_field_name = "name"
target_field = People._meta.get_field_by_name(target_field_name)[0]
model_fields = {}
model_fields["value"] = copy_field(target_field)
model_fields["value"].db_index = True
model_fields["__module__"] = People.__module__
NewModel = type("People_index_" + field_name, (models.Model,), model_fields)
有用!
有复制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
也可以传递一些像verbose_name等常规的kwargs:
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 = ...
因此,我不应该从我的User模型到抽象类执行重复字段,因为其他一些非用户逻辑扩展模型想要具有完全相同的字段。为什么?因为它与用户模型没有直接关系 - 用户模型不应该关心它取决于它(不包括你想要扩展用户模型的情况),所以它不应该被分开,因为其他一些模型带有它自己的非用户相关逻辑希望具有完全相同的字段。
相反,你可以这样做:
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类“on fly”以避免代码重复:
class UserDataUpdateApplication(
generate_abc_for_model(
User,
fields=['first_name', 'last_name', 'email', 'phone_number'],
prefix_fields_with='user_'),
models.Model,
):
pass