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

时间:2014-11-24 19:39:49

标签: python django python-2.7 metaclass

我有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))

我真的不知道为什么会这样,但至少我现在已经开始工作了。

3 个答案:

答案 0 :(得分:5)

之所以出现这种情况,是因为您在prm_class_attrs.copy()循环中没有for,因此__modules__键会在第一次迭代时从dict中弹出

至于为什么这不起作用:

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

......这与Django的models.Model有一个元类的事实有关。但是这是一个 Python metaclass ,它定制了模型类的创建,与Django模型的Meta内部类没有任何关系(它只提供了'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)

答案 1 :(得分:2)

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

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

答案 2 :(得分:0)

对于仍然想知道如何执行此操作的任何人,我从@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