How to change field Model attributes when inheriting from abstract models
In my code, I use a lot of abstract Model classes. When I inherit from them, I often need to change some of the attributes of specific fields. I am trying to implement a solution where those attributes are defined as class variables which can then be overridden upon instantiation.
Here is a simple example of what I am trying to do:
Let's say we have an abstract model class as such:
class AbstractThing(models.Model):
MAX_LENGTH = 1000
IS_UNIQUE = True
name = models.CharField(max_length=MAX_LENGTH, unique=IS_UNIQUE)
class Meta:
abstract = True
What I am hoping to do is this:
class RealThing(AbstractThing):
IS_UNIQUE = False
MAX_LENGTH = 200
However it does not work. The resulting migration file looks like this:
migrations.CreateModel(
name='RealThing',
fields=[
('id', models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID')),
('name', models.CharField(
max_length=1000,
unique=True)
),
],
options={
'abstract': False,
},
),
Is this just not possible or am I doing something incorrect? Any ideas would be really appreciated.
models.CharField(max_length=MAX_LENGTH, unique=IS_UNIQUE)
is evaluated at the time of class definition, meaning that MAX_LENGTH
and IS_UNIQUE
are resolved once and won't dynamically update when subclassing.
To fix it, just override the field in the subclass.
class RealThing(AbstractThing):
IS_UNIQUE = False
MAX_LENGTH = 200
name = models.CharField(max_length=MAX_LENGTH, unique=IS_UNIQUE) # override it
You could probably work with a metaclass, like:
class AbstractMeta(ModelBase):
def __new__(cls, name, bases, attrs):
print((name, bases, attrs))
attrs['name'] = models.CharField(
max_length=attrs.get('MAX_LENGTH', 1000),
unique=attrs.get('IS_UNIQUE', True),
)
return super().__new__(cls, name, bases, attrs)
class AbstractThing(models.Model, metaclass=AbstractMeta):
class Meta:
abstract = True
class Foo(AbstractThing):
MAX_LENGTH = 100
class Bar(AbstractThing):
MAX_LENGTH = 200
I'm however not sure that this is a good idea. It contains some customization, and probably the logic should run before the .__init_subclass__(…)
method [python-doc].