Copying a description of a Django field from an existing model to a new one

I am 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 the fields that I would like to copy:

 target_fields = ["name", "age"] 

I want to create a new model that has all the fields named in target_fields , but in this case they should be indexed ( db_index = True ).

I initially hoped that I could just iterate over the properties of the People class and use copy.copy to copy the field descriptions 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 was not sure that copy.copy() ing Fields would work, but I did not go far enough to find out: the fields listed in the class definition are not really included as properties of the class object, I assume they are used for some machinists of the metaclass.

After scrolling through the debugger, I found some types of Field objects listed in People._meta.local_fields . However, this is not just a description, which can be copy.copy() ed and used to describe another model. For example, they include the .model property related to People .

How to create a field description for a new model based on a field of an existing model?

+4
source share
1 answer

From pushing in the debugger and source: all Django models use the ModelBase metaclass defined in /db/models/base.py . For each field in the definition of the model class, the ModelBase method .add_to_class will call the field method .contribute_to_class .

Field.contribute_to_class defined in /db/models/fields/__init__.py , and this is responsible for mapping the field definition to a specific model. The field is changed by adding the .model property and calling the .set_attributes_from_name method with the name used in the model class definition. This, in turn, adds the .attname and .column and sets .name and .verbose_name if necessary.

When I check the __dict__ property of the newly defined CharField and compare it with the CharField that was already associated with the model, I also see that these are the only differences:

  • The .creation_counter property .creation_counter unique to each instance.
  • The .attrname , .column and .model do not exist in the new instance.
  • The .name and .verbose_name are None in the new instance.

It is not possible to distinguish between the .name / .verbose_name properties that were manually specified for the constructor and those that were automatically generated. You need to choose either always reset them, ignoring any manually set values, or never clear them, which will lead to the fact that they will always ignore any new name that they received in the new model. I want to use the same name as the source fields, so I will not touch them.

Knowing what the differences are, I use copy.copy() to clone an existing instance, and then apply these changes to make it behave like a new instance.

 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 

Given this function, I am creating a new model with the following:

 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) 

It works!

+6
source

All Articles