Making existing fields translatable

The following guide explains how to make existing fields translatable, and migrate the data from the old fields to translated fields.

django-parler stores translated fields in a separate model, so it can store multiple versions (translations) of the same field. To make existing fields translatable, 3 migration steps are needed:

  1. Create the translation table, keep the existing columns
  2. Copy the data from the original table to the translation table.
  3. Remove the fields from the original model.

The following sections explain this in detail:

Step 1: Create the translation table

Say we have the following model:

class MyModel(models.Model):
    name = models.CharField(max_length=123)

First create the translatable fields:

class MyModel(TranslatableModel):
    name = models.CharField(max_length=123)

    translations = TranslatedFields(
          name=models.CharField(max_length=123),
    )

Now create the migration:

manage.py makemigrations myapp --name "add_translation_model"

Step 2: Copy the data

Within the data migration, copy the existing data:

Create an empty migration:

manage.py makemigrations --empty myapp --name "migrate_translatable_fields"

And use it to move the data:

from django.db import migrations
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist


def forwards_func(apps, schema_editor):
    MyModel = apps.get_model('myapp', 'MyModel')
    MyModelTranslation = apps.get_model('myapp', 'MyModelTranslation')

    for object in MyModel.objects.all():
        MyModelTranslation.objects.create(
            master_id=object.pk,
            language_code=settings.LANGUAGE_CODE,
            name=object.name
        )

def backwards_func(apps, schema_editor):
    MyModel = apps.get_model('myapp', 'MyModel')
    MyModelTranslation = apps.get_model('myapp', 'MyModelTranslation')

    for object in MyModel.objects.all():
        translation = _get_translation(object, MyModelTranslation)
        object.name = translation.name
        object.save()   # Note this only calls Model.save()

def _get_translation(object, MyModelTranslation):
    translations = MyModelTranslation.objects.filter(master_id=object.pk)
    try:
        # Try default translation
        return translations.get(language_code=settings.LANGUAGE_CODE)
    except ObjectDoesNotExist:
        try:
            # Try default language
            return translations.get(language_code=settings.PARLER_DEFAULT_LANGUAGE_CODE)
        except ObjectDoesNotExist:
            # Maybe the object was translated only in a specific language?
            # Hope there is a single translation
            return translations.get()


class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(forwards_func, backwards_func),
    ]

Note

Be careful which language is used to migrate the existing data. In this example, the backwards_func() logic is extremely defensive not to loose translated data.

Step 3: Remove the old fields

Remove the old field from the original model. The example model now looks like:

class MyModel(TranslatableModel):
    translations = TranslatedFields(
        name=models.CharField(max_length=123),
    )

Create the database migration, it will simply remove the original field:

manage.py makemigrations myapp --name "remove_untranslated_fields"

Updating code

The project code should be updated. For example:

Deployment

To have a smooth deployment, it’s recommended to only run the first 2 migrations - which create columns and move the data. Removing the old fields should be done after reloading the WSGI instance.