Django Admin Integration

In order to be able to edit the translations via the django.contrib.admin application you need to register a special admin class for the translated models. The admin class must derive from modeltranslation.admin.TranslationAdmin which does some funky patching on all your models registered for translation. Taken the news example the most simple case would look like:

from django.contrib import admin
from news.models import News
from modeltranslation.admin import TranslationAdmin

class NewsAdmin(TranslationAdmin):
    pass

admin.site.register(News, NewsAdmin)

Tweaks Applied to the Admin

formfield_for_dbfield

The TranslationBaseModelAdmin class, which TranslationAdmin and all inline related classes in modeltranslation derive from, implements a special method which is formfield_for_dbfield(self, db_field, **kwargs). This method does the following:

  1. Copies the widget of the original field to each of its translation fields.
  2. Checks if the original field was required and if so makes the default translation field required instead.

get_form/get_fieldsets/_declared_fieldsets

In addition the TranslationBaseModelAdmin class overrides get_form, get_fieldsets and _declared_fieldsets to make the options fields, exclude and fieldsets work in a transparent way. It basically does:

  1. Removes the original field from every admin form by adding it to exclude under the hood.
  2. Replaces the - now removed - orginal fields with their corresponding translation fields.

Taken the fieldsets option as an example, where the title field is registered for translation but not the news field:

class NewsAdmin(TranslationAdmin):
    fieldsets = [
        (u'News', {'fields': ('title', 'news',)})
    ]

In this case get_fieldsets will return a patched fieldset which contains the translation fields of title, but not the original field:

>>> a = NewsAdmin(NewsModel, site)
>>> a.get_fieldsets(request)
[(u'News', {'fields': ('title_de', 'title_en', 'news',)})]

TranslationAdmin in Combination with Other Admin Classes

If there already exists a custom admin class for a translated model and you don’t want or can’t edit that class directly there is another solution.

Taken a reusable blog app which defines a model Entry and a corresponding admin class called EntryAdmin. This app is not yours and you don’t want to touch it at all.

In the most common case you simply make use of Python’s support for multiple inheritance like this:

class MyTranslatedEntryAdmin(EntryAdmin, TranslationAdmin):
    pass

The class is then registered for the admin.site (not to be confused with modeltranslation’s translator). If EntryAdmin is already registered through the blog app, it has to be unregistered first:

admin.site.unregister(Entry)
admin.site.register(Entry, MyTranslatedEntryAdmin)

Admin Classes that Override formfield_for_dbfield

In a more complex setup the original EntryAdmin might override formfield_for_dbfield itself:

class EntryAdmin(model.Admin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        # does some funky stuff with the formfield here

Unfortunately the first example won’t work anymore because Python can only execute one of the formfield_for_dbfield methods. Since both admin classes implement this method Python must make a decision and it chooses the first class EntryAdmin. The functionality from TranslationAdmin will not be executed and translation in the admin will not work for this class.

But don’t panic, here’s a solution:

class MyTranslatedEntryAdmin(EntryAdmin, TranslationAdmin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(MyTranslatedEntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        self.patch_translation_field(db_field, field, **kwargs)
        return field

This implements the formfield_for_dbfield such that both functionalities will be executed. The first line calls the superclass method which in this case will be the one of EntryAdmin because it is the first class inherited from. The TranslationAdmin capsulates its functionality in the patch_translation_field method and the formfield_for_dbfield implementation of the TranslationAdmin class simply calls it. You can copy this behaviour by calling it from a custom admin class and that’s done in the example above. After that the field is fully patched for translation and finally returned.

Admin Inlines

New in version 0.2.

Support for tabular and stacked inlines, common and generic ones.

A translated inline must derive from one of the following classes:

  • modeltranslation.admin.TranslationTabularInline
  • modeltranslation.admin.TranslationStackedInline
  • modeltranslation.admin.TranslationGenericTabularInline
  • modeltranslation.admin.TranslationGenericStackedInline

Just like TranslationAdmin these classes implement a special method formfield_for_dbfield which does all the patching.

For our example we assume that there is a new model called Image. The definition is left out for simplicity. Our News model inlines the new model:

from django.contrib import admin
from news.models import Image, News
from modeltranslation.admin import TranslationTabularInline

class ImageInline(TranslationTabularInline):
    model = Image

class NewsAdmin(admin.ModelAdmin):
    list_display = ('title',)
    inlines = [ImageInline,]

admin.site.register(News, NewsAdmin)

Note

In this example only the Image model is registered in translation.py. It’s not a requirement that NewsAdmin derives from TranslationAdmin in order to inline a model which is registered for translation.

Complex Example with Admin Inlines

In this more complex example we assume that the News and Image models are registered in translation.py. The News model has an own custom admin class called NewsAdmin and the Image model an own generic stacked inline class called ImageInline. Furthermore we assume that NewsAdmin overrides formfield_for_dbfield itself and the admin class is already registered through the news app.

Note

The example uses the technique described in TranslationAdmin in combination with other admin classes.

Bringing it all together our code might look like this:

from django.contrib import admin
from news.admin import ImageInline
from news.models import Image, News
from modeltranslation.admin import TranslationAdmin, TranslationGenericStackedInline

class TranslatedImageInline(ImageInline, TranslationGenericStackedInline):
    model = Image

class TranslatedNewsAdmin(NewsAdmin, TranslationAdmin):
    inlines = [TranslatedImageInline,]

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(TranslatedNewsAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        self.patch_translation_field(db_field, field, **kwargs)
        return field

admin.site.unregister(News)
admin.site.register(News, NewsAdmin)

Using Tabbed Translation Fields

New in version 0.3.

Modeltranslation supports separation of translation fields via jquery-ui tabs. The proposed way to include it is through the inner Media class of a TranslationAdmin class like this:

class NewsAdmin(TranslationAdmin):
    class Media:
        js = (
            'modeltranslation/js/force_jquery.js',
            'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.24/jquery-ui.min.js',
            'modeltranslation/js/tabbed_translation_fields.js',
        )
        css = {
            'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
        }

Note

Here we stick to the jquery library shipped with Django. The force_jquery.js script is necessary when using Django’s built-in django.jQuery object. Otherwise the normal jQuery object won’t be available to the included (non-namespaced) jquery-ui library.

Standard jquery-ui theming can be used to customize the look of tabs, the provided css file is supposed to work well with a default Django admin.

As an alternative, if want to use a more recent version of jquery, you can do so by including this in your Media class instead:

class NewsAdmin(TranslationAdmin):
    class Media:
        js = (
            'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
            'http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js',
            'modeltranslation/js/tabbed_translation_fields.js',
        )
        css = {
            'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
        }

Tabbed Translation Fields Admin Classes

New in version 0.7.

To ease the inclusion of the required static files for tabbed translation fields, the following admin classes are provided:

  • TabbedDjangoJqueryTranslationAdmin (aliased to TabbedTranslationAdmin)
  • TabbedExternalJqueryTranslationAdmin

Rather than inheriting from TranslationAdmin, simply subclass one of these classes like this:

class NewsAdmin(TabbedTranslationAdmin):
    pass

TranslationAdmin Options

TranslationAdmin.group_fieldsets

New in version 0.6.

When this option is activated untranslated and translation fields are grouped into separate fieldsets. The first fieldset contains the untranslated fields, followed by a fieldset for each translation field. The translation field fieldsets use the original field’s verbose_name as a label.

Activating the option is a simple way to reduce the visual clutter one might experience when mixing these different types of fields.

The group_fieldsets option expects a boolean. By default fields are not grouped into fieldsets (group_fieldsets = False).

A few simple policies are applied:

  • A fieldsets option takes precedence over the group_fieldsets option.
  • Other default ModelAdmin options like exclude are respected.
class NewsAdmin(TranslationAdmin):
    group_fieldsets = True

Formfields with None-checkbox

There is the special widget which allow to choose whether empty field value should be stores as empty string or None (see None-checkbox widget). In TranslationAdmin some fields can use this widget regardless of their empty_values setting:

class NewsAdmin(TranslationAdmin):
    both_empty_values_fields = ('title', 'text')