File: migrating.rst

package info (click to toggle)
python-django-parler 2.3-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,032 kB
  • sloc: python: 4,293; makefile: 164; sh: 6
file content (138 lines) | stat: -rw-r--r-- 4,476 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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:

* Replace ``filter(field_name)`` with ``.translated(field_name)`` or ``filter(translations__field_name)``.
* Make sure there is one filter on the translated fields, see :ref:`orm-restrictions`.
* Update the ``ordering`` and ``order_by()`` code. See :ref:`ordering`.
* Update the admin ``search_fields`` and ``prepopulated_fields``. See :ref:`admin-compat`.


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.