I’m trying to dynamically generate a new Model, based on fields from an existing Model. Both are defined in /apps/main/models.py. The existing model looks something like this:
from django.db import models
class People(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
height = models.IntegerField()
I have a list containing the names of fields that I would like to copy:
target_fields = ["name", "age"]
I want to generate a new model the has all of the Fields named in target_fields, but in this case they should be indexed (db_index = True).
I originally hoped that I would just be able to iterate over the class properties of People and use copy.copy to copy the field descriptions that are defined on it. Like this:
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)
I wasn’t sure if copy.copy()ing Fields would work, but I didn’t get far enough to find out: the fields listed in the class definition don’t aren’t actually included as properties on the class object. I assume they’re used for some metaclass shenanigans instead.
After poking around in the debugger, I found some type of Field objects listed in People._meta.local_fields. However, these aren’t just simple description that can be copy.copy()ed and used to describe another model. For example, they include a .model property referring to People.
How can I create a field description for a new model based on a field of an existing model?
From poking around in the debugger and the source: all Django models use the
ModelBasemetaclass defined in/db/models/base.py. For each field in a model’s class definition,ModelBase‘s.add_to_classmethod will call the field’s.contribute_to_classmethod.Field.contribute_to_classis defined in/db/models/fields/__init__.pyand it is what’s responsible for associating a field definition with a particular model. The field is modified by adding the.modelproperty and by calling the.set_attributes_from_namemethod with the name used in the model’s class definition. This in turn adds adds the.attnameand.columnproperties and sets.nameand.verbose_nameif necessary.When I inspect the
__dict__property of a newly-definedCharFieldand compare it with that of aCharFieldthat was already associated with a model, I also see that these are the only differences:.creation_counterproperty is unique for each instance..attrname,.columnand.modelproperties do not exist on the new instance..nameand.verbose_nameproperties isNoneon the new instance.It doesn’t seem possible to distinguish between
.name/.verbose_nameproperties that were manually specified to the constructor and ones that were automatically generated. You’ll need to chose either to always reset them, ignoring any manually-specified values, or never clear them, which would cause them to always ignore any new name they were given in the new model. I want to use the same name as the original fields, so I am not going to touch them.Knowing what differences exist, I am using
copy.copy()to clone the existing instance, then apply these changes to make it behave like a new instance.Given this function, I create the new Model with the following:
It works!