Django Parler
Django Parler (current version 2.3) provides simple Django model translations without nasty hacks, featuring nice admin integration. It enables storing multiple language versions of model fields in a separate table. The library is actively maintained with updates for Django compatibility and new features regularly released.
Common errors
-
TypeError: Translatable model <class '__fake__.YourModel'> does not appear to inherit from TranslatableModel
cause Often occurs during migrations when converting an existing model to be translatable, if the migration file is not manually corrected to ensure the generated translation model inherits properly.fixWhen performing `makemigrations` for an existing model becoming translatable, manually edit the generated migration file. In the `migrations.CreateModel` operation for the *translation* model, change the `bases` argument from `parler.models.TranslatedFieldsModelMixin` to `parler.models.TranslatableModel`. -
AttributeError: 'NoneType' object has no attribute 'get_all_fields'
cause This error can occur during data migration when converting existing models to use `django-parler` if the migration steps are not followed precisely, particularly when attempting to copy data before the translation model is fully established or when the ORM's data migration tries to access incorrect attributes.fixEnsure you follow the recommended three-step manual migration process (Create, Copy, Remove) for existing models. This error is typically bypassed by using `migrations.RunSQL` for data copying instead of ORM-based `RunPython` operations that might trigger this issue. -
Translated fields or language tabs are not showing in the Django admin site after migration/setup.
cause This can happen due to incorrect `INSTALLED_APPS` configuration, missing `TranslatableAdmin` registration, issues with `PARLER_LANGUAGES` settings, or a bug in `django-parler`'s admin JavaScript for certain versions.fix1. Verify `'parler'` is in `INSTALLED_APPS`. 2. Ensure your model's admin class inherits from `parler.admin.TranslatableAdmin`. 3. Check your `PARLER_LANGUAGES` settings in `settings.py` are correct and `LANGUAGE_CODE` and `LANGUAGES` are defined. 4. If using version 2.3, be aware of a known issue regarding missing admin JavaScript (`parler.js`), and check for updates or workarounds. -
django.db.utils.DataError: value too long for type character varying(...)
cause This error occurs during data migration when copying existing field data to the new translation table if the original field's content exceeds the `max_length` defined for the corresponding translated field.fixEnsure that the `max_length` defined for the fields within `TranslatedFields` is sufficient to accommodate the existing data from the original (non-translated) fields. You may need to increase the `max_length` in your `TranslatedFields` definition before running the data migration, or truncate existing data if appropriate.
Warnings
- breaking When migrating existing models to be translatable, directly running `makemigrations` and `migrate` can lead to various `TypeError`, `AttributeError`, or `DataError` exceptions. This is because `django-parler` stores translations in a separate table, and the ORM struggles with data migration for this change.
- gotcha Querying translatable fields (e.g., using `.translated()` or `.active_translations()`) cannot be chained with multiple `.filter()` calls for translatable fields due to ORM restrictions. Doing so might lead to incorrect or incomplete results. Also, `.active_translations()` often returns duplicate objects if `distinct()` is not used.
- gotcha Enforcing unique constraints on translated fields (e.g., a unique product `slug` across all languages) requires explicit configuration. Simply adding `unique=True` to `CharField` inside `TranslatedFields` will enforce uniqueness across *all* translations, not per language.
- gotcha Accessing a translated field when a translation is missing for the current language, and no fallbacks are defined or available, will raise a `TranslationDoesNotExist` exception (which inherits from `AttributeError`).
- breaking There are reports of the admin JavaScript (parler.js) missing in version 2.3, which can break the functionality of `TranslatableAdmin` and prevent the display of language tabs in the Django admin interface.
Install
-
pip install django-parler
Imports
- TranslatableModel
from parler.models import TranslatableModel
- TranslatedFields
from parler.models import TranslatedFields
- TranslatableAdmin
from parler.admin import TranslatableAdmin
- TranslatedField
from parler.fields import TranslatedField
Quickstart
# settings.py
INSTALLED_APPS = [
# ...
'parler',
# Your app
'myapp',
]
LANGUAGE_CODE = 'en'
LANGUAGES = (
('en', 'English'),
('fr', 'French'),
('es', 'Spanish'),
)
PARLER_LANGUAGES = {
None: (
{'code': 'en'},
{'code': 'fr'},
{'code': 'es'},
),
'default': {
'fallbacks': ['en'],
'hide_untranslated': False,
}
}
# myapp/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
from parler.models import TranslatableModel, TranslatedFields
class Category(TranslatableModel):
translations = TranslatedFields(
name=models.CharField(_("Name"), max_length=200, unique=True)
)
# Add a non-translatable field for demonstration
is_active = models.BooleanField(default=True)
def __str__(self):
return self.safe_translation_getter('name', any_language=True)
# myapp/admin.py
from django.contrib import admin
from parler.admin import TranslatableAdmin
from .models import Category
@admin.register(Category)
class CategoryAdmin(TranslatableAdmin):
list_display = ('name', 'is_active',)
# Prepopulated fields (if you had a slug for example)
# prepopulated_fields = {'slug': ('name',)}
# Example usage in a shell or view
from django.utils import translation
# Create a category
cat = Category(is_active=True)
cat.set_current_language('en')
cat.name = "Electronics"
cat.save()
cat.set_current_language('fr')
cat.name = "Électronique"
cat.save()
# Accessing translations
with translation.override('en'):
print(f"English Name: {cat.name}") # Output: English Name: Electronics
with translation.override('fr'):
print(f"French Name: {cat.name}") # Output: French Name: Électronique
# Querying translated fields
# All categories with name 'Electronics' in any language
electronics_en = Category.objects.translated(name='Electronics').first()
print(f"Found in EN: {electronics_en.name}")
# Categories with active translations for current language or fallbacks
active_cats = Category.objects.active_translations().filter(is_active=True)
for c in active_cats:
print(f"Active Category ({c.get_current_language()}): {c.name}")