I have a few models that have pairs of fields named like: text
and rendered_text
.
The former one(s) can contain LaTeX formulas, and what I’m doing is I’m saving those formulas already rendered (via server-side mathjax) as svgs in the field(s) whose name begins with rendered_
. This makes things way easier on the frontend. Since I have several models that implement this behavior and on differently named fields, I tried to abstract away this behavior and I thought I could use an abstract model for this. This is what I came up with.
class TexRenderable(models.Model):
class Meta:
abstract = True
def _get_renderable_field_pairs(self):
ret = []
for field in self._meta.get_fields():
split_field_name = field.name.split("_", maxsplit=1)
if split_field_name[0] == "rendered": # this is a target field
target_field = field.name
# look for a field with the same name minus the `rendered_` prefix
source_field = self._meta.get_field(split_field_name[1]) # target field will contain rendered text from this field
ret.append(
(source_field, target_field),
)
return ret
def save(self, *args, **kwargs):
creating = self.pk is None
renderable_field_pairs = self._get_renderable_field_pairs()
for (source, target) in renderable_field_pairs:
# only re-render if the field's value has changed
value_changed = creating or getattr(
type(self).objects.get(pk=self.pk), source
) != getattr(self, source)
if value_changed:
rendered_content = get_rendered_tex(getattr(self, source))
setattr(self, target, rendered_content)
return super(TexRenderable, self).save(*args, **kwargs)
I’d like to hear your thoughts on this approach.
First of all, is this a good use case for abstract model inheritance? I was thinking maybe a mixin would be more appropriate given there are no fields defined inside of the base model class, but I don’t have much experience with mixins in python so I’m not sure.
Secondly, how does the actual implementation of the method look? There are a few things that feel hacky to me, like calling type(self)
to get the model class. I haven’t worked with complex inheritance-related stuff in python, so I don’t (currently) know any better.
Bonus question:
Say I want a concrete model to inherit from TexRenderable
as well as this other abstract model:
class AbstractItem(models.Model):
course = models.ForeignKey(Course)
topic = models.ForeignKey(Topic)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.topic not in self.course.topics.all():
raise ValidationError("Chosen topic doesn't belong to chosen course")
return super(Item, self).save(*args, **kwargs)
this simply adds two foreign keys and does a check inside of save
before actually saving. What would be the correct order to put them inside of the parentheses when declaring the child class? Does it even matter, or will the behavior of save
be the same regardless? Will this even work, or will I get an error when calling save
from a child instance? I know I could just try it out but this is a novel topic for me, so I really want to grasp how multiple inheritance works in python. I’ll do my research on the side too–if you feel this part is off-topic, just ignore it.