Dynamically creating Django models with `type`
I have 20 + MySQL-table prm_a
, prm_b
... with the same basic structure, but with different names, and I would like to relate them to the classes Django classes without writing each of them manually. So, feeling ambitious, I thought I would try to use type()
as a factory class:
The following works:
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)
###
But if I try to generate model classes like this:
###
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
###
I am getting a curious exception on the line type()
(given that __module__
there is, in fact, a dictionary 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__'
So I have two questions: what is wrong with my second approach, and is this even the correct way to create my class models?
OK - thanks to @Anentropic I can see the items in my dictionary are prm_class_attrs
popped by Python when classes are created. And now it works for me, but only if I do this:
attrs = prm_class_attrs.copy() attrs['Meta'] = get_model_meta_class(prm_name) prm_class = type(prm_class_name, (models.Model,), attrs)
not if I set the class Meta
as an attribute with
setattr(prm_class, 'Meta', get_model_meta_class(prm_name))
I really don't know why this is, but at least I'm working on it right now.
source to share
The exclusive reason is that you are not executing prm_class_attrs.copy()
in your loop for
, so the key is __modules__
popped out of the dict on the first iteration
How why doesn't it work:
setattr(prm_class, 'Meta', get_model_meta_class(prm_name))
... this is because Django models.Model
has a metaclass. But this is a Python meta-trick that customizes the creation of the model class and has nothing to do with Meta
Django's internal model class (which simply provides "meta" information about the model).
In fact, no matter what it looks like, when you define a class in models.py
, the resulting class has no attribute Meta
:
class MyModel(models.Model):
class Meta:
verbose_name = 'WTF'
>>> MyModel.Meta
AttributeError: type object 'MyModel' has no attribute 'Meta'
(You can access the class Meta
directly, but with an alias like MyModel._meta
)
The model you define in models.py
is indeed more of a template for the model class than the actual model class. This is why when you access a field attribute on a model instance, you get the value of that field, not the field object itself.
Django model inheritance can simplify what you do a bit:
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)
source to share
You can use ./manage.py inspectdb
This will print the python models file for the database you point to in your settings.py
Documentation
EDIT For dynamic models try django-mutant or visit this link
source to share