Django model fields with dynamic names
Django models can be created with dynamic field names . Here is a simple Django model:
class Animal(models.Model):
name = models.CharField(max_length=32)
And here is the equivalent class built using type()
:
attrs = {
'name': models.CharField(max_length=32),
'__module__': 'myapp.models'
}
Animal = type("Animal", (models.Model,), attrs)
Any Django model that can be defined in the normal fashion can be made using type()
.
To run migrations:South has a reliable set of functions to handle schema and database migrations for Django projects. When used in development, South can suggest migrations but does not attempt to automatically apply them
from south.db import db
model_class = generate_my_model_class()
fields = [(f.name, f) for f in model_class._meta.local_fields]
table_name = model_class._meta.db_table
db.create_table(table_name, fields)
# some fields (eg GeoDjango) require additional SQL to be executed
db.execute_deferred_sql()
The cleanest way would probably be using add_to_class()
:
ModelOne.add_to_class(
'%s_title' % field_prefix,
models.CharField(max_length=255, blank=True, default='')
)
Still this can be considered "monkey-patching" with all its downsides like making the app more difficult to maintain, have code that is more difficult to understand etc... Bu if your use case makes it really necessary to do something like that it would probably be the best solution as add_to_class()
is some functionality provided from Django itself and has been stable for quite some time.
Try using a factory pattern to set up your different versions of AbstractModel
.
With this approach, you can more strictly control the way AbstractModel
is modified by way of the factory function dynamic_fieldname_model_factory
.
We're also not modifying ModelOne
or ModelTwo
after their definitions -- other solutions have pointed out that this helps avoid maintainability problems.
models.py:
from django.db import models
def dynamic_fieldname_model_factory(fields_prefix):
class AbstractModel(models.Model):
class Meta:
abstract = True
AbstractModel.add_to_class(
fields_prefix + '_title',
models.CharField(max_length=255, blank=True, default=''),
)
return AbstractModel
class ModelOne(dynamic_fieldname_model_factory('someprefix1')):
id = models.AutoField(primary_key=True)
class ModelTwo(dynamic_fieldname_model_factory('someprefix2')):
id = models.AutoField(primary_key=True)
Here is the migration generated by this code:
# Generated by Django 2.1.7 on 2019-03-07 19:53
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ModelOne',
fields=[
('someprefix1_title', models.CharField(blank=True, default='', max_length=255)),
('id', models.AutoField(primary_key=True, serialize=False)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='ModelTwo',
fields=[
('someprefix2_title', models.CharField(blank=True, default='', max_length=255)),
('id', models.AutoField(primary_key=True, serialize=False)),
],
options={
'abstract': False,
},
),
]