Disclaimer: I can wipe out the database anytime. So while answering this, please don’t care about migrations and stuff.
Imagine me having a model with multiple values:
class Compound(models.Model):
color = models.CharField(max_length=20, blank=True, default="")
brand = models.CharField(max_length=200, blank=True, default="")
temperature = models.FloatField(null=True, blank=True)
melting_temp = models.FloatField(null=True, blank=True)
# more (~20) especially numeric values as model fields
Now I want to add a comment to be stored for every value of that model. For example I want to add a comment “measured in winter” to the temperature model field.
What is the best approach to do that?
My brainstorming came up with:
- By hand add 20 more model fields like
temperature_comment = ...but that sounds not very DRY - Add one big json field which stores every comment. But how do I create a Form with such a json field? Because I want to separate each input field for related value. I would probably have to use javascript which I would want to avoid.
- Add a model called
Valuefor every value and connect them toCompoundviaOneToOneFields. But how do I then create a Form forCompound? Because I want to create aCompoundutilizing one form. I do not want to create everyValueon its own. Also it is not as easy as before, to access and play around with the values inside theCompoundmodel.
I guess this is a fairly abstract question for a usecase that comes up quite often. I do not know why I did not find resources on how to accomplish that.
Advertisement
Answer
The Pythonic way is to use a metaclass:
class CommentModelMeta(models.base.ModelBase):
def __new__(mcs, name, bases, attrs, **kwargs):
for attr_name, attr in list(attrs.items()):
if isinstance(attr, models.Field):
attrs[f'{attr_name}_comment'] = models.CharField(max_length=20, blank=True, default="")
return super().__new__(mcs, name, bases, attrs, **kwargs)
class Compound(models.Model, metaclass=CommentModelMeta):
color = models.CharField(max_length=20, blank=True, default="")
...
If you need code completion, you can use type hints:
class Compound(models.Model, metaclass=CommentModelMeta):
color = models.CharField(max_length=20, blank=True, default="")
brand = models.CharField(max_length=200, blank=True, default="")
temperature = models.FloatField(null=True, blank=True)
melting_temp = models.FloatField(null=True, blank=True)
# more (~20) especially numeric values as model fields
color_comment: models.CharField
temperature_comment: models.CharField