Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to Make existing fields translatable #263

Open
HappyMiner opened this issue Feb 28, 2020 · 10 comments · May be fixed by #332
Open

Unable to Make existing fields translatable #263

HappyMiner opened this issue Feb 28, 2020 · 10 comments · May be fixed by #332

Comments

@HappyMiner
Copy link

Hello,

I think I just found a problem in the documentation at: https://django-parler.readthedocs.io

I am trying to translate my model fields and when I try to follow the procedure described in the docs, all I get is:
raise TypeError("Translatable model {} does not appear to inherit from TranslatableModel".format(shared_model)) TypeError: Translatable model <class '__fake__.Product'> does not appear to inherit from TranslatableModel

When I try to follow the docs, Python complains that the fields that are in translations = TranslatedFields are duplicated, so the first part of the doc does not work. No idea how to continue at this point, as this is complete road block for me.

Please find below the relevant data classes where I attempt to perform the translations.

from django.db import models
from django.urls import reverse
from parler.models import TranslatableModel, TranslatedFields

class Category(TranslatableModel):

    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True, unique=True))

    class Meta:

        verbose_name = 'category'
        verbose_name_plural = 'categories'

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_list_by_category',
                       args=[self.slug])

class Product(TranslatableModel):

    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True),
        description = models.TextField(blank=True)
        )
    category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    # Commented for model translations.
    # class Meta:
    #
    #     ordering = ('name',)
    #     index_together = (('id', 'slug'),)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_detail', args=[self.id, self.slug])

Related admin classes:

from django.contrib import admin
from .models import Category, Product
from parler.admin import TranslatableAdmin

@admin.register(Category)
class CategoryAdmin(TranslatableAdmin):
    list_display = ['name', 'slug']

    def get_prepopulated_fields(self, request, obj=None):
        return {'slug': ('name',)}

@admin.register(Product)
class ProductAdmin(TranslatableAdmin):
    list_display = ['name', 'slug', 'price', 'available', 'created', 'updated']
    list_filter = ['available', 'created', 'updated']

    list_editable = ['price', 'available']

    def get_prepopulated_fields(self, request, obj=None):
        return {'slug': ('name',)}
(venv) 192:myshop Username$ python manage.py makemigrations shop --name "add_translation_model"
Traceback (most recent call last):
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/parler/models.py", line 942, in contribute_translations
    base = shared_model._parler_meta
AttributeError: type object 'Product' has no attribute '_parler_meta'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/base.py", line 328, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/base.py", line 369, in execute
    output = self.handle(*args, **options)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/commands/makemigrations.py", line 168, in handle
    migration_name=self.migration_name,
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/autodetector.py", line 43, in changes
    changes = self._detect_changes(convert_apps, graph)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/autodetector.py", line 128, in _detect_changes
    self.old_apps = self.from_state.concrete_apps
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 213, in concrete_apps
    self.apps = StateApps(self.real_apps, self.models, ignore_swappable=True)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 272, in __init__
    self.render_multiple([*models.values(), *self.real_models])
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 307, in render_multiple
    model.render(self)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 578, in render
    return type(self.name, bases, body)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/models/base.py", line 320, in __new__
    new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 338, in register_model
    self.do_pending_operations(model)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 424, in do_pending_operations
    function(model)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 403, in apply_next_model
    self.lazy_model_operation(next_function, *more_models)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 415, in lazy_model_operation
    apply_next_model(model_class)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 403, in apply_next_model
    self.lazy_model_operation(next_function, *more_models)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 389, in lazy_model_operation
    function()
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/models/fields/related.py", line 317, in resolve_related_class
    field.do_related_class(related, model)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/models/fields/related.py", line 388, in do_related_class
    self.contribute_to_related_class(other, self.remote_field)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/parler/fields.py", line 58, in contribute_to_related_class
    self.model.contribute_translations(cls)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/parler/models.py", line 944, in contribute_translations
    raise TypeError("Translatable model {} does not appear to inherit from TranslatableModel".format(shared_model))
TypeError: Translatable model <class '__fake__.Product'> does not appear to inherit from TranslatableModel
(venv) 192:myshop Username$ 
@HappyMiner
Copy link
Author

Just now I noticed that my projects migrations were apparently completely destroyed by "parler". I tried to go back on my changes and now I am completely stuck on a non existing migration. This thing is pretty much a disaster.

@milkomeda
Copy link

@HappyMiner please use latest django-parler>=2.0.1.

In addition you need to set bases manually on your migrations where these models were initially added/created.

Consider e.g. a file myapp/migrations/0001_initial.py

migrations.CreateModel(
    name='AttachmentType',
    fields=[
        ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    ],
    options={
        'verbose_name': 'Attachment type',
        'verbose_name_plural': 'Attachment types',
    },
    bases=(parler.models.TranslatableModelMixin, models.Model),
),
migrations.CreateModel(
    name='AttachmentTypeTranslation',
    fields=[
        ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
        ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
        ('name', models.CharField(max_length=500, unique=True, verbose_name='Name')),
        ('master', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='jobportal.AttachmentType')),
    ],
    options={
        'verbose_name': 'Attachment type Translation',
        'db_table': 'jobportal_attachmenttype_translation',
        'db_tablespace': '',
        'managed': True,
        'default_permissions': (),
    },
    bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
),

In particular if you changed your model e.g. AttachmentType later to be translatable and inheriting from TranslatableModel. You have to manually check the migrations for the bases attributes and add these if not present.

bases=(parler.models.TranslatableModelMixin, models.Model), for e.g. AttachmentType model

and

bases=(parler.models.TranslatedFieldsModelMixin, models.Model), for the automatically created AttachmentTypeTranslation model/table.

The documentation lacks for this information.

Feel free to contact me if you have any further questions.

@milkomeda
Copy link

@vdboor perhaps add some documentation in https://django-parler.readthedocs.io/en/stable/advanced/migrating.html about this issue to clarify things. Cause this issue pops up regulary.

@Abuba-oss
Copy link

I am having troubles with Django-Parler 2.0.1 after I had applied migration to translations to make existing fields translatable, it won't show Product fields in the admin site. I wonder whats the problem it's taking me days to get this problem resolved.
Screenshot (35)

this is my setting for translations in the models.py file, Django-Parler 2.0.1 won't show fields for Products in the admin site after I had synch migrations. I am currently using Django 3.0.3.

`from django.db import models
from django.urls import reverse
from parler.models import TranslatableModel, TranslatedFields

class Category(TranslatableModel):
translations = TranslatedFields(
name = models.CharField(max_length=200,
db_index=True),
slug = models.SlugField(max_length=200,
db_index=True,
unique=True)
)

class Meta:
    # ordering = ('name',)
    verbose_name = 'category'
    verbose_name_plural = 'categories'

def __str__(self):
    return self.name

def get_absolute_url(self):
        return reverse('shop:product_list_by_category',
                       args=[self.slug])

class Product(TranslatableModel):
translations = TranslatedFields(
name = models.CharField(max_length=200, db_index=True),
slug = models.SlugField(max_length=200, db_index=True),
description = models.TextField(blank=True)
)
category = models.ForeignKey(Category,
related_name='products',
on_delete=models.CASCADE)
image = models.ImageField(upload_to='products/%Y/%m/%d',
blank=True)

price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)

#class Meta:
#    ordering = ('name',)
#    index_together = (('id', 'slug'),)

def __str__(self):
    return self.name

def get_absolute_url(self):
        return reverse('shop:product_detail',
                       args=[self.id, self.slug])`

@Abuba-oss
Copy link

Hello,

I think I just found a problem in the documentation at: https://django-parler.readthedocs.io

I am trying to translate my model fields and when I try to follow the procedure described in the docs, all I get is:
raise TypeError("Translatable model {} does not appear to inherit from TranslatableModel".format(shared_model)) TypeError: Translatable model <class '__fake__.Product'> does not appear to inherit from TranslatableModel

When I try to follow the docs, Python complains that the fields that are in translations = TranslatedFields are duplicated, so the first part of the doc does not work. No idea how to continue at this point, as this is complete road block for me.

Please find below the relevant data classes where I attempt to perform the translations.

from django.db import models
from django.urls import reverse
from parler.models import TranslatableModel, TranslatedFields

class Category(TranslatableModel):

    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True, unique=True))

    class Meta:

        verbose_name = 'category'
        verbose_name_plural = 'categories'

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_list_by_category',
                       args=[self.slug])

class Product(TranslatableModel):

    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True),
        description = models.TextField(blank=True)
        )
    category = models.ForeignKey(Category, related_name='products', on_delete=models.CASCADE)
    image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    # Commented for model translations.
    # class Meta:
    #
    #     ordering = ('name',)
    #     index_together = (('id', 'slug'),)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_detail', args=[self.id, self.slug])

Related admin classes:

from django.contrib import admin
from .models import Category, Product
from parler.admin import TranslatableAdmin

@admin.register(Category)
class CategoryAdmin(TranslatableAdmin):
    list_display = ['name', 'slug']

    def get_prepopulated_fields(self, request, obj=None):
        return {'slug': ('name',)}

@admin.register(Product)
class ProductAdmin(TranslatableAdmin):
    list_display = ['name', 'slug', 'price', 'available', 'created', 'updated']
    list_filter = ['available', 'created', 'updated']

    list_editable = ['price', 'available']

    def get_prepopulated_fields(self, request, obj=None):
        return {'slug': ('name',)}
(venv) 192:myshop Username$ python manage.py makemigrations shop --name "add_translation_model"
Traceback (most recent call last):
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/parler/models.py", line 942, in contribute_translations
    base = shared_model._parler_meta
AttributeError: type object 'Product' has no attribute '_parler_meta'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/base.py", line 328, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/base.py", line 369, in execute
    output = self.handle(*args, **options)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/core/management/commands/makemigrations.py", line 168, in handle
    migration_name=self.migration_name,
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/autodetector.py", line 43, in changes
    changes = self._detect_changes(convert_apps, graph)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/autodetector.py", line 128, in _detect_changes
    self.old_apps = self.from_state.concrete_apps
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 213, in concrete_apps
    self.apps = StateApps(self.real_apps, self.models, ignore_swappable=True)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 272, in __init__
    self.render_multiple([*models.values(), *self.real_models])
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 307, in render_multiple
    model.render(self)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 578, in render
    return type(self.name, bases, body)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/models/base.py", line 320, in __new__
    new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/migrations/state.py", line 338, in register_model
    self.do_pending_operations(model)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 424, in do_pending_operations
    function(model)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 403, in apply_next_model
    self.lazy_model_operation(next_function, *more_models)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 415, in lazy_model_operation
    apply_next_model(model_class)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 403, in apply_next_model
    self.lazy_model_operation(next_function, *more_models)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/apps/registry.py", line 389, in lazy_model_operation
    function()
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/models/fields/related.py", line 317, in resolve_related_class
    field.do_related_class(related, model)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/django/db/models/fields/related.py", line 388, in do_related_class
    self.contribute_to_related_class(other, self.remote_field)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/parler/fields.py", line 58, in contribute_to_related_class
    self.model.contribute_translations(cls)
  File "/Users/Username/Projects/Python/Django/Django2ByExample/myshop/venv/lib/python3.7/site-packages/parler/models.py", line 944, in contribute_translations
    raise TypeError("Translatable model {} does not appear to inherit from TranslatableModel".format(shared_model))
TypeError: Translatable model <class '__fake__.Product'> does not appear to inherit from TranslatableModel
(venv) 192:myshop Username$ 

I had the same problem and i had change the migrations field of the translation
from : bases=(parler.models.TranslatableModelMixin, models.Model),
to : bases=(parler.models.TranslatableModel, models.Model),
and it worked perfectly.... it should when you do it that way.

@alzohery
Copy link

alzohery commented May 6, 2021

Upgrade your django-parler to the latest version by:
"pip install django-parler == 2.2.0"

@alzohery
Copy link

alzohery commented May 6, 2021

(venv) D:\html\nd\myshop>python manage.py makemigrations shop --name "translations"
Migrations for 'shop':
shop\migrations\0002_translations.py
- Change Meta options on category
- Change Meta options on product
- Remove field name from category
- Remove field slug from category
- Alter index_together for product (0 constraint(s))
- Remove field description from product
- Remove field name from product
- Remove field slug from product
- Create model ProductTranslation
- Create model CategoryTranslation

@akikoskinen
Copy link

Looks like the documentation is talking about some future dream. The code explicitly mentions in comments that it isn't supported to change an existing field into a translated field.

akikoskinen added a commit to City-of-Helsinki/tunnistamo that referenced this issue Jan 25, 2022
Changing an existing non-translated field to a translated one requires a
multi-step process. The translated fields are first created with a
different temporary name. Then data is copied from the non-translated
fields to the translated fields. The old fields are removed and the
translated fields are renamed to have the same name as the original
fields had. All this resides in the single migration file in this
commit.

A few changes were required to an old migration file.
1) The bases needs to include TranslatableModelMixin in the migration
   where LoginMethod model is first introduced. This is required by
   Django-parler and how it works internally.
   django-parler/django-parler#263 (comment)
2) The non-null `name` field requires a default value so that migrating
   backwards works. The backward migration creates the non-null `name`
   column which fails unless the column also has a default value.
akikoskinen added a commit to City-of-Helsinki/tunnistamo that referenced this issue Jan 28, 2022
Changing an existing non-translated field to a translated one requires a
multi-step process. The translated fields are first created with a
different temporary name. Then data is copied from the non-translated
fields to the translated fields. The old fields are removed and the
translated fields are renamed to have the same name as the original
fields had. All this resides in the single migration file in this
commit.

A few changes were required to an old migration file.
1) The bases needs to include TranslatableModelMixin in the migration
   where LoginMethod model is first introduced. This is required by
   Django-parler and how it works internally.
   django-parler/django-parler#263 (comment)
2) The non-null `name` field requires a default value so that migrating
   backwards works. The backward migration creates the non-null `name`
   column which fails unless the column also has a default value.
@oldenboom
Copy link

I also ran into problems with making existing fields translatable. But I succeeded, having tried a lot. As I won't be alone, my story so far.

I'm on Django 4.0.4 with django-parler 2.3 with a very basic class:

class Plan(models.Model):
    title = models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True)
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name=_('Price per year'))

The table does contain data. I've followed the documentation at migration. So I changed my class to:

class Plan(TranslatableModel):
    title = models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True)
    translations = TranslatedFields(
        title=models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True),
    )
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name=_('Price per year'))

At first I got the error

**TypeError: Translatable model <class 'fake.Plan'> does not appear to inherit from TranslatableModel

I fixed that by following some suggestion here. Just added to the migration file

bases=(parler.models.TranslatableModel, models.Model),

But then I got the error

TypeError: The model 'Plan' already has a field named 'title'

I suspected the migration system to be the culprit here as the second title attribute should be part of PlanTranslation, not of Plan. I changed the name of the attribute to the_title:

class Plan(TranslatableModel):
    title = models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True)
    translations = TranslatedFields(
        the_title=models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True),
    )
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name=_('Price per year'))

Ran makemigrations again and then edited the migration file to rename the_title to title again before running the actual migration. The migration system didn't like that.

Deleted the migration, and edited the model to:

class Plan(TranslatableModel):
    translations = TranslatedFields(
        title=models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True),
    )
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name=_('Price per year'))

Created the migration file and edited. Removed the RemoveField part and tried to run the migration. The migration system didn't like that either.

So, back to the the_title version with:

class Plan(TranslatableModel):
    title = models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True)
    translations = TranslatedFields(
        the_title=models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True),
    )
    price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name=_('Price per year'))

Created the migration with makemigrations and ran it with migrate. No issues here. Then created an empty migration to rename the_title to title in PlanTranslation:

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

Before running th emigration, I included an operation into this migration file:

        migrations.RenameField(
            model_name = 'PlanTranslation',
            old_name = 'the_title',
            new_name = 'title',
        ),

The migration system sure isn't stupid. It didn't buy this and complained with

TypeError: The model 'Plan' already has a field named 'title'

Deleted the migration file and added another empty migration file as per the instructions

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

Inserted the functions into the empty migration. I modified the forwards_func and backwards_func to reflect the proper model names and proper field names. Note: I do have both the attributes title and the_title.

...

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

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

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

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

And inserted the code to run the functions into the operations list:

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

Then I ran the migration with

manage.py migrate myapp

Verified if the data got copied to the PlanTranslation table. It was.

Now I needed to have the_title renamed to title and get rid of the title field in Plan. Just rename the title field in the model first and run migrations:

class Plan(TranslatableModel):
    old_title = models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True)
    translations = TranslatedFields(
        the_title=models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True),
    )

Edit the models again, rename the_title to title and remove the attribute old_title and run migrations:

class Plan(TranslatableModel):
    translations = TranslatedFields(
        title=models.CharField(max_length=40, unique=True, verbose_name=_('Name'), db_index=True),
    )

This worked for me. I have the system up & running now. Edited the admin.py as per the documentation and it sure looks great in the admin. Tested the functionality and database contents: everything ok.

@eduumach
Copy link

I came to this solution in my:

This was the migration generated by django:

class Migration(migrations.Migration):

    dependencies = [
        ('maps_base', '0002_test_provider_date'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='test',
            name='name',
        ),
        migrations.CreateModel(
            name='LayerTranslation',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
                ('name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Name')),
                ('master', parler.fields.TranslationsForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='maps_base.Layer')),
            ],
            options={
                'verbose_name': 'Test Translation',
                'db_table': 'test_translation',
                'db_tablespace': '',
                'managed': True,
                'default_permissions': (),
                'unique_together': {('language_code', 'master')},
            },
            bases=(parler.models.TranslatableModel, models.Model),
        ),
    ]

Then I edited to do a data migration:

class Migration(migrations.Migration):

    dependencies = [
        ('maps_base', '0002_test_provider_date'),
    ]

    operations = [
        migrations.CreateModel(
            name='LayerTranslation',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
                ('name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Name')),
                ('master', parler.fields.TranslationsForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='maps_base.Layer')),
            ],
            options={
                'verbose_name': 'Test Translation',
                'db_table': 'test_translation',
                'db_tablespace': '',
                'managed': True,
                'default_permissions': (),
                'unique_together': {('language_code', 'master')},
            },
            bases=(parler.models.TranslatableModel, models.Model),
        ),
        migrations.RunSQL("INSERT INTO test_translation(language_code, name , master_id) SELECT 'pt-br' ,name, id FROM test;"),
        migrations.RemoveField(
            model_name='test',
            name='name',
        ),
    ]

I put the deletes to be done after creating the translation table and before deleting I copied the data with:

migrations.RunSQL("INSERT INTO (language_code, <Yours_parameters>, master_id) SELECT '<your_language>' , <Yours_parameters>, id FROM ;"),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants