Making an abstract base class for a Django database model to overwrite the save() function

I was recently faced with the challenge of needing to add translations to a hierarchal database setup in Django/Python. We wanted these translations to be stored in the database for multiple languages (for access through a CMS), where not every field on the model needed to be translated. These translations should be auto generated from when a database values changes.

Here is how I solved this with an abstract base class on a Django database model.

The original database models were defined as something like this:

import uuid
from django.db import models

class School(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)
    description = models.CharField()

class Professor(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)
    profile = models.CharField()
    school = models.ForeignKey("app_name.School", on_delete=models.PROTECT, related_name="professors")

We want to translate both the school’s name and description, as well as the professor’s profile.

Let’s add the definition for our Translatable base class first. Note that since we use abstract = True here, and are not defining new fields, no Django database migration will be created. We simply want to tap into the save() method for each inheriting class in order to auto generate rows in another table.

from django.db import models

class Translatable(models.Model):
    translatable_fields = []

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        # Loop through any translatable fields and insert translations
        for translatable_field in self.translatable_fields:
            # Use reflection to get the actual value we just saved
            translatable_field_value = getattr(self, translatable_field.attname)
            # ... do other things with this value

    class Meta:
        abstract = True

Now we can inherit from this Translatable model in both our School and Professor models like this:

import uuid
from django.db import models
from app_name.Translatable import Translatable


class School(models.Model, Translatable):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)
    description = models.CharField()

    translatable_fields = [name, description]

class Professor(models.Model, Translatable):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)
    profile = models.CharField()
    school = models.ForeignKey("app_name.School", on_delete=models.PROTECT, related_name="professors")

    translatable_fields = [profile]

This then enables us to tap into Django’s save() function to do whatever we want to do. We will get real-time values on saving.

For example, if we are saving a School object with values:

School(
    name="Jasper University",
    description="An example",
)

When Django’s save() method is fired, we’ll get both translatable_field_value = "Jasper University" and translatable_field_value = "An Example" as values inside of our abstract base classes loop. I then store these in another database table to be picked up in a separate process.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.