使用 `type` 动态创建 Django 模型

Posted

技术标签:

【中文标题】使用 `type` 动态创建 Django 模型【英文标题】:Dynamically creating Django models with `type` 【发布时间】:2015-01-22 15:16:09 【问题描述】:

我有 20 多个 mysql 表,prm_aprm_b,...具有相同的基本结构但名称不同,我想将它们与 Django 模型类相关联,而无需手动编写每一个。因此,怀着雄心壮志,我​​想尝试将type() 用作类工厂:

以下作品:

def get_model_meta_class(prm_name):
    class Meta:
        app_label = 'myapp'
    setattr(Meta, 'db_table', 'prm_%s' % prm_name)
    return Meta

prm_class_attrs = 
    'foo': models.ForeignKey(Foo),
    'val': models.FloatField(),
    'err': models.FloatField(blank=True, null=True),
    'source': models.ForeignKey(Source),
    '__module__': __name__,


###
prm_a_attrs = prm_class_attrs.copy()
prm_a_attrs['Meta'] = get_model_meta_class('a')
Prm_a = type('Prm_a', (models.Model,), prm_a_attrs)

prm_b_attrs = prm_class_attrs.copy()
prm_b_attrs['Meta'] = get_model_meta_class('b')
Prm_b = type('Prm_b', (models.Model,), prm_b_attrs)
###

但如果我尝试按如下方式生成模型类:

###
prms = ['a', 'b']
for prm_name in prms:
    prm_class_name = 'Prm_%s' % prm_name
    prm_class = type(prm_class_name, (models.Model,), prm_class_attrs)
    setattr(prm_class, 'Meta', get_model_meta_class(prm_name))
    globals()[prm_class_name] = prm_class
###

我在type() 行上得到一个奇怪的异常(假设__module__ 实际上在prm_class_attrs 字典中):

File ".../models.py", line 168, in <module>
    prm_class = type(prm_class_name, (models.Model,), prm_class_attrs)
  File ".../lib/python2.7/site-packages/django/db/models/base.py", line 79, in __new__
    module = attrs.pop('__module__')
KeyError: u'__module__' 

所以我有两个问题:我的第二种方法有什么问题,这甚至是创建我的类模型的正确方法吗?

好的 - 感谢@Anentropic,我看到我的prm_class_attrs 字典中的项目在创建类时被 Python 弹出。我现在让它工作了,但前提是我这样做:

attrs = prm_class_attrs.copy()
attrs['Meta'] = get_model_meta_class(prm_name)
prm_class = type(prm_class_name, (models.Model,), attrs)

如果我将 Meta 类设置为属性,则不会

setattr(prm_class, 'Meta', get_model_meta_class(prm_name))

我真的不知道为什么会这样,但至少我现在可以正常工作了。

【问题讨论】:

因为您没有在 for 循环中执行 prm_class_attrs.copy(),所以 __modules__ 在第一次迭代中从字典中弹出 是的 - 就是这样。谢谢! 我不知道你必须提供__module__!这个问题真的很有帮助! 【参考方案1】:

对于仍然想知道如何做到这一点的任何人,我从 @Anentropic 那里得到了天才的答案,并对其进行了一些修改。

我还将 db 表名更改为更像 django 用来命名模型表的名称(“appname_lowercaseclassname”),方法是从类名中剥离所有非字母字符并将生成的字符串转换为小写。

我在 Django 2.2.6 上测试过

def generate_model(class_name):

    clean_name_for_table = ''.join(filter(str.isalpha, class_name)).lower() # Could also use regex to remove all non alphabetic characters.
    db_table_name = f"__package___clean_name_for_table"
    prm_class = type(
        class_name,
        (BaseClass,),
        
            # this will get merged with the attrs from GeneratedModelBase.Meta
            'Meta': type("Meta",(),'db_table':db_table_name),
            '__module__': __name__,
        
    )
    globals()[class_name] = prm_class
    return prm_class

【讨论】:

【参考方案2】:

直接原因是因为您没有在 for 循环中执行 prm_class_attrs.copy(),所以 __modules__ 键在第一次迭代时从字典中弹出

至于为什么这不起作用:

setattr(prm_class, 'Meta', get_model_meta_class(prm_name))

...这与 Django 的 models.Model 有一个元类有关。但这是一个 Python metaclass 自定义模型类的创建,与 Django 模型的 Meta 内部类无关(它只是提供有关模型的“元”信息)。

事实上,尽管在models.py 中定义类时看起来是这样,但生成的类没有Meta 属性:

class MyModel(models.Model):
    class Meta:
        verbose_name = 'WTF'

>>> MyModel.Meta
AttributeError: type object 'MyModel' has no attribute 'Meta'

(您可以直接访问Meta类,但别名为MyModel._meta

您在models.py 中定义的模型实际上更像是模型类的模板,而不是实际的模型类。这就是为什么当您访问模型实例上的字段属性时,您会获得该字段的 ,而不是字段对象本身。

Django model inheritance 可以简化你正在做的事情:

class GeneratedModelBase(models.Model):
    class Meta:
        abstract = True
        app_label = 'myapp'

    foo = models.ForeignKey(Foo)
    val = models.FloatField()
    err = models.FloatField(blank=True, null=True)
    source = models.ForeignKey(Source)

def generate_model(suffix):
    prm_class_name = 'Prm_%s' % prm_name
    prm_class = type(
        prm_class_name,
        (GeneratedModelBase,),
        
            # this will get merged with the attrs from GeneratedModelBase.Meta
            'Meta': 'db_table', 'prm_%s' % prm_name,
            '__module__': __name__,
        
    )
    globals()[prm_class_name] = prm_class
    return prm_class

prms = ['a', 'b']
for prm_name in prms:
    generate_model(prm_name)

【讨论】:

我一直在寻找如何做到这一点的简明具体示例。谢谢!【参考方案3】:

您可以使用./manage.py inspectdb 这将为您在settings.py 中指向的数据库打印一个 python 模型文件 Documentation

编辑 对于动态模型,请尝试django-mutant 或查看this link

【讨论】:

我发现如果数据库是静态且固定的,这将起作用,但如果我想添加一个表,我希望代码能够即时生成相应的模型。 @xnx 你肯定要写一堆其他代码才能在你的应用程序中使用表格吗?

以上是关于使用 `type` 动态创建 Django 模型的主要内容,如果未能解决你的问题,请参考以下文章

Django使用不完整的模型创建一个有效的ModelForm,以在表单验证后手动添加字段

11.21

1121课堂小结

Django:如何动态创建模型仅用于测试

Django在运行时创建动态模型,需要重启服务器才能反映,为啥?

在 Django 框架中使用动态模型