Revision: 169
Author: eschler
Date: Fri Oct 12 06:19:56 2012
Log: Created docs for 0.4 release from current trunk.
http://code.google.com/p/django-modeltranslation/source/detail?r=169
Added:
/trunk/docs/modeltranslation/modeltranslation-0.4.txt
=======================================
--- /dev/null
+++ /trunk/docs/modeltranslation/modeltranslation-0.4.txt Fri Oct 12
06:19:56 2012
@@ -0,0 +1,687 @@
+.. _ref-topics-modeltranslation:
+
+================
+Modeltranslation
+================
+
+The modeltranslation application can be used to translate dynamic content
of
+existing models to an arbitrary number of languages without having to
change
+the original model classes. It uses a registration approach (comparable to
+Django's admin app) to be able to add translations to existing or new
projects
+and is fully integrated into the Django admin backend.
+
+The advantage of a registration approach is the ability to add
translations to
+models on a per-project basis. You can use the same app in different
projects,
+may they use translations or not, and you never have to touch the original
+model class.
+
+*Authors*
+
+- Peter Eschler <
pesc...@googlemail.com> (
http://www.nmy.de)
+- Dirk Eschler <
esc...@gmail.com> (
http://www.nmy.de)
+
+.. contents::
+
+
+Features
+========
+
+- Unlimited number of target languages
+- Add translations without changing existing models
+- Django admin support
+- Supports inherited models
+
+
+Installation
+============
+
+To install the application please follow these steps. Each step is
described
+in detail in the following sections:
+
+1. Add the ``modeltranslation`` app to the ``INSTALLED_APPS`` variable of
your
+ project's ``settings.py``.
+
+2. Configure your ``LANGUAGES`` in ``settings.py``.
+
+3. Create a ``translation.py`` in your app directory and register
+ ``TranslationOptions`` for every model you want to translate.
+
+4. Configure the ``MODELTRANSLATION_TRANSLATION_FILES`` variable in your
+ ``settings.py``.
+
+5. Sync the database using ``manage.py syncdb`` (note that this only
applies
+ if the models registered in the ``translations.py`` did not have been
+ synced to the database before. If they did - read further down what to
do
+ in that case.
+
+
+Configure the project's ``settings.py``
+---------------------------------------
+The following variables have to be added to or edited in the project's
+``settings.py``:
+
+**settings.INSTALLED_APPS**
+
+Make sure that the ``modeltranslation`` app is listed in your
+``INSTALLED_APPS`` variable:
+
+::
+
+ INSTALLED_APPS = (
+ ...
+ 'modeltranslation',
+ ....
+ )
+
+Also make sure that the app can be found on a path contained in your
+``PYTHONPATH`` environment variable.
+
+**settings.LANGUAGES**
+
+The LANGUAGES variable must contain all languages used for translation. The
+first language is treated as the *default language*.
+
+The modeltranslation application uses the list of languages to add
localized
+fields to the models registered for translation. To use the languages
``de``
+and ``en`` in your project, set the settings.LANGUAGES variable like this
+(where ``de`` is the default language):
+
+::
+
+ gettext = lambda s: s
+ LANGUAGES = (
+ ('de', gettext('German')),
+ ('en', gettext('English')),
+ )
+
+Note that the ``gettext`` lambda function is not a feature of the
+modeltranslation app, but rather required for Django to be able to
+(statically) translate the verbose names of the languages using the
standard
+``i18n`` solution.
+
+**settings.MODELTRANSLATION_DEFAULT_LANGUAGE**
+
+*New in 0.3*
+
+To override the default language as described in settings.LANGUAGES, define
+``MODELTRANSLATION_DEFAULT_LANGUAGE``. Note that the value has to be in
+settings.LANGUAGES, otherwise an exception will be raised.
+
+**settings.MODELTRANSLATION_TRANSLATION_FILES**
+
+*New in 0.4*
+
+In order to be able to import the ``translation.py`` registration files of
your
+apps, ``MODELTRANSLATION_TRANSLATION_FILES`` must be set to a value in the
+form:
+
+::
+
+ ('<APP1_MODULE>.translation',
+ '<APP2_MODULE>.translation',)
+
+.. note:: Modeltranslation up to version 0.3 used a single project wide
+ registration file which was defined through
+ ``MODELTRANSLATION_TRANSLATION_REGISTRY
= '<PROJECT_MODULE>.translation'``.
+ For backwards compatibiliy the module defined through this
setting is
+ automatically added to ``MODELTRANSLATION_TRANSLATION_FILES``. A
+ DeprecationWarning is issued in this case.
+
+
+**settings.MODELTRANSLATION_CUSTOM_FIELDS**
+
+*New in 0.3*
+
+``Modeltranslation`` officially supports ``CharField`` and ``TextField``.
+
+*New in 0.4*
+
+Support for ``FileField`` and ``ImageField``.
+
+In most cases subclasses of the supported fields will work fine, too. Other
+fields aren't supported and will throw an ``ImproperlyConfigured``
exception.
+
+The list of supported fields can be extended. Just define a tuple of field
+names in your settings.py like this:
+
+::
+
+ MODELTRANSLATION_CUSTOM_FIELDS = ('MyField', 'MyOtherField',)
+
+.. note:: This just prevents ``modeltranslation`` from throwing an
+ ``ImproperlyConfigured`` exception. Any non text-like field will
most
+ likely fail in one way or another. The feature is considered
+ experimental and might be replaced by a more sophisticated
mechanism
+ in future versions.
+
+
+Registering models and their fields for translation
+---------------------------------------------------
+The ``modeltranslation`` app can translate ``CharField`` and ``TextField``
+based fields of any model class. For each model to translate a translation
+option class containg the fields to translate is registered with the
+``modeltranslation`` app.
+
+Registering models and their fields for translation requires the following
+steps:
+
+1. Create a ``translation.py`` in your app directory.
+2. Create a translation option class for every model to translate.
+3. Register the model and the translation option class at the
+ ``modeltranslation.translator.translator``
+
+The ``modeltranslation`` application reads the ``translation.py`` file in
your
+app directory thereby triggering the registration of the translation
+options found in the file.
+
+A translation option is a class that declares which fields of a model to
+translate. The class must derive from ``modeltranslation.ModelTranslation``
+and it must provide a ``fields`` attribute storing the list of fieldnames.
The
+option class must be registered with the
+``modeltranslation.translator.translator`` instance.
+
+To illustrate this let's have a look at a simple example using a ``News``
+model. The news in this example only contains a ``title`` and a ``text``
field.
+Instead of a news, this could be any Django model class:
+
+::
+
+ class News(models.Model):
+ title = models.CharField(max_length=255)
+ text = models.TextField()
+
+In order to tell the ``modeltranslation`` app to translate the ``title``
and
+``text`` field, create a ``translation.py`` file in your project directory
and
+add the following:
+
+::
+
+ from modeltranslation.translator import translator, TranslationOptions
+ from some.news.models import News
+
+ class NewsTranslationOptions(TranslationOptions):
+ fields = ('title', 'text',)
+
+ translator.register(News, NewsTranslationOptions)
+
+Note that this does not require to change the ``News`` model in any way,
it's
+only imported. The ``NewsTranslationOptions`` derives from
+``TranslationOptions`` and provides the ``fields`` attribute. Finally the
model
+and it's translation options are registered at the ``translator`` object.
+
+At this point you are mostly done and the model classes registered for
+translation will have been added some auto-magical fields. The next section
+explains how things are working under the hood.
+
+
+Changes automatically applied to the model class
+------------------------------------------------
+After registering the ``News`` model for translation an SQL dump of the
+News app will look like this:
+
+::
+
+ $ ./manage.py sqlall news
+ BEGIN;
+ CREATE TABLE `news_news` (
+ `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
+ `title` varchar(255) NOT NULL,
+ `title_de` varchar(255) NULL,
+ `title_en` varchar(255) NULL,
+ `text` longtext NULL,
+ `text_de` longtext NULL,
+ `text_en` longtext NULL,
+ )
+ ;
+ ALTER TABLE `news_news` ADD CONSTRAINT page_id_refs_id_3edd1f0d
FOREIGN KEY (`page_id`) REFERENCES `page_page` (`id`);
+ CREATE INDEX `news_news_page_id` ON `news_news` (`page_id`);
+ COMMIT;
+
+Note the ``title_de``, ``title_en``, ``text_de`` and ``text_en`` fields
which
+are not declared in the original News model class but rather have been
added by
+the modeltranslation app. These are called *translation fields*. There
will be
+one for every language in your project's ``settings.py``.
+
+The name of these additional fields is build using the original name of the
+translated field and appending one of the language identifiers found in the
+``settings.LANGUAGES``.
+
+As these fields are added to the registered model class as fully valid
Django
+model fields, they will appear in the db schema for the model although it
has
+not been specified on the model explicitly.
+
+.. _set_language:
http://docs.djangoproject.com/en/dev/topics/i18n/#the-set-language-redirect-view
+
+If you are starting a fresh project and have considered your translation
needs
+in the beginning then simply sync your database and you are ready to use
+the translated models.
+
+In case you are translating an existing project and your models have
already
+been synced to the database you will need to alter the tables in your
database
+and add these additional translation fields. Note that all added fields are
+declared ``null=True`` not matter if the original field is required. In
other
+words - all translations are optional. To populate the default translation
+fields added by the ``modeltranslation`` application you can use the
+``update_translation_fields`` command below. See the `The
+update_translation_fields command` section for more infos on this.
+
+
+Accessing translated and translation fields
+===========================================
+The ``modeltranslation`` app changes the behaviour of the translated
fields. To
+explain this consider the News example again. The original ``News`` model
+looked like this:
+
+::
+
+ class News(models.Model):
+ title = models.CharField(max_length=255)
+ text = models.TextField()
+
+Now that it is registered with the ``modeltranslation`` app the model looks
+like this - note the additional fields automatically added by the app::
+
+ class News(models.Model):
+ title = models.CharField(max_length=255) # original/translated
field
+ title_de = models.CharField(null=True, blank=True,
max_length=255) # default translation field
+ title_en = models.CharField(null=True, blank=True,
max_length=255) # translation field
+ text = models.TextField() # original/translated field
+ text_de = models.TextField(null=True, blank=True) # default
translation field
+ text_en = models.TextField(null=True, blank=True) # translation
field
+
+The example above assumes that the default language is ``de``, therefore
the
+``title_de`` and ``text_de`` fields are marked as the *default translation
+fields*. If the default language is ``en``, the ``title_en`` and
``text_en``
+fields would be the *default translation fields*.
+
+
+Rules for translated field access
+---------------------------------
+So now when it comes to setting and getting the value of the original and
the
+translation fields the following rules apply:
+
+**Rule 1**
+
+Reading the value from the original field returns the value translated to
the
+*current language*.
+
+**Rule 2**
+
+Assigning a value to the original field also updates the value in the
+associated default translation field.
+
+**Rule 3**
+
+Assigning a value to the default translation field also updates the
original
+field - note that the value of the original field will not be updated
until the
+model instance is saved.
+
+**Rule 4**
+
+If both fields - the original and the default translation field - are
updated
+at the same time, the default translation field wins.
+
+
+Examples for translated field access
+------------------------------------
+Because the whole point of using the ``modeltranslation`` app is
translating
+dynamic content, the fields marked for translation are somehow special
when it
+comes to accessing them. The value returned by a translated field is
depending
+on the current language setting. "Language setting" is referring to the
Django
+`set_language`_ view and the corresponding ``get_lang`` function.
+
+Assuming the current language is ``de`` in the News example from above, the
+translated ``title`` field will return the value from the ``title_de``
field:
+
+::
+
+ # Assuming the current language is "de"
+ n = News.objects.all()[0]
+ t = n.title # returns german translation
+
+ # Assuming the current language is "en"
+ t = n.title # returns english translation
+
+This feature is implemented using Python descriptors making it happen
without
+the need to touch the original model classes in any way. The descriptor
uses
+the ``django.utils.i18n.get_language`` function to determine the current
+language.
+
+
+Django admin backend integration
+================================
+In order to be able to edit the translations via the admin backend 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:
+
+::
+
+ from django.contrib import admin
+ from modeltranslation.admin import TranslationAdmin
+
+ class NewsAdmin(TranslationAdmin):
+ list_display = ('title',)
+
+ 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 ``def formfield_for_dbfield(self, db_field, **kwargs)``.
This
+method does the following:
+
+1. Copies the widget of the original field to each of it's translation
fields.
+2. Checks if the original field was required and if so makes
+ the default translation field required instead.
+
+
+get_form and get_fieldsets
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+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:
+
+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 the News example let's say there is a ``NewsAdmin`` class defined by
the
+News app itself. This app is not yours or 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 MyTranslatedNewsAdmin(NewsAdmin, TranslationAdmin):
+ pass
+
+In a more complex setup the NewsAdmin itself might override
+formfield_for_dbfield:
+
+::
+
+ class NewsAdmin(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
class
+implement this method Python must make a decision and it chooses the first
+class ``NewsAdmin``. 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 MyTranslatedNewsAdmin(NewsAdmin, TranslationAdmin):
+ def formfield_for_dbfield(self, db_field, **kwargs):
+ field = super(MyTranslatedNewsAdmin,
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 ``NewsAdmin`` because it is the first class inherited
from.
+The ``TranslationAdmin`` capsulates all it's functionality in the
+``patch_translation_field(db_field, field, **kwargs)`` 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.
+
+
+Inlines
+-------
+*New in 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
+`def formfield_for_dbfield(self, db_field, **kwargs)` which does all the
+patching.
+
+For our example we assume that there is new model called Image. It's
+definition is left out for simplicity. Our News model inlines the new
model:
+
+::
+
+ from django.contrib import admin
+ 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.
+
+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
and
+the Image model an own generic stacked inline class. It uses the technique
+described in `TranslationAdmin in combination with other admin classes`__.:
+
+__ translationadmin_in_combination_with_other_admin_classes_
+
+::
+
+ from django.contrib import admin
+ from modeltranslation.admin import TranslationAdmin,
TranslationGenericStackedInline
+
+ class TranslatedImageInline(ImageInline,
TranslationGenericStackedInline):
+ model = Image
+
+ class TranslatedNewsAdmin(NewsAdmin, TranslationAdmin):
+ 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
+
+ inlines = [TranslatedImageInline,]
+
+ admin.site.register(News, NewsAdmin)
+
+
+Using tabbed translation fields
+-------------------------------
+*New in 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 = (
+ '/static/modeltranslation/js/force_jquery.js',
+ '
http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js',
+ '/static/modeltranslation/js/tabbed_translation_fields.js',
+ )
+ css = {
+ 'screen':
('/static/modeltranslation/css/tabbed_translation_fields.css',),
+ }
+
+The `force_jquery.js` script is necessary when using Django's built-in
+`django.jQuery` object. This and the static urls used are just an example
and
+might have to be adopted to your setup of serving static files. 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.
+
+
+The ``update_translation_fields`` command
+=========================================
+In case the modeltranslation app was installed on an existing project and
you
+have specified to translate fields of models which are already synced to
the
+database, you have to update your database schema manually.
+
+Unfortunately the newly added translation fields on the model will be empty
+then, and your templates will show the translated value of the fields (see
+Rule 1 below) which will be empty in this case. To correctly initialize the
+default translation field you can use the ``update_translation_fields``
+command:
+
+::
+
+ manage.py update_translation_fields
+
+Taken the News example from above this command will copy the value from the
+news object's ``title`` field to the default translation field
``title_de``.
+It only does so if the default translation field is empty otherwise nothing
+is copied.
+
+.. note:: The command will examine your ``settings.LANGUAGES`` variable
and the
+ first language declared there will be used as the default
language.
+
+All translated models (as specified in the project's ``translation.py``
will be
+populated with initial data.
+
+
+Caveats
+=======
+Consider the following example (assuming the default language is ``de``):
+
+::
+
+ >>> n = News.objects.create(title="foo")
+ >>> n.title
+ 'foo'
+ >>> n.title_de
+ >>>
+
+Because the original field ``title`` was specified in the constructor it is
+directly passed into the instance's ``__dict__`` and the descriptor which
+normally updates the associated default translation field (``title_de``)
is not
+called. Therefor the call to ``n.title_de`` returns an empty value.
+
+Now assign the title, which triggers the descriptor and the default
translation
+field is updated:
+
+::
+
+ >>> n.title = 'foo'
+ >>> n.title_de
+ 'foo'
+ >>>
+
+
+Accessing translated fields outside views
+-----------------------------------------
+Since the ``modeltranslation`` mechanism relies on the current language as
it
+is returned by the ``get_language`` function care must be taken when
accessing
+translated fields outside a view function.
+
+Within a view function the language is set by Django based on a flexible
model
+described at `How Django discovers language preference`_ which is normally
used
+only by Django's static translation system.
+
+.. _How Django discovers language preference:
http://docs.djangoproject.com/en/dev/topics/i18n/#id2
+
+When a translated field is accessed in a view function or in a template, it
+uses the ``django.utils.translation.get_language`` function to determine
the
+current language and return the appropriate value.
+
+Outside a view (or a template), i.e. in normal Python code, a call to the
+``get_language`` function still returns a value, but it might not what you
+expect. Since no request is involved, Django's machinery for discovering
the
+user's preferred language is not activated. *todo: explain more*
+
+The unittests in ``tests.py`` use the
``django.utils.translation.trans_real``
+functions to activate and deactive a specific language outside a view
function.
+
+
+Related projects
+================
+
+`django-multilingual`_
+----------------------
+
+ A library providing support for multilingual content in Django models.
+
+It is not possible to reuse existing models without modifying them.
+
+
+`django-multilingual-model`_
+----------------------------
+A much simpler version of the above `django-multilingual`.
+It works very similiar to the `django-multilingual` approach.
+
+
+`transdb`_
+----------
+
+ Django's field that stores labels in more than one language in
database.
+
+This approach uses a specialized ``Field`` class, which means one has to
change
+existing models.
+
+
+`i18ndynamic`_
+--------------
+This approach is not developed any more.
+
+
+`django-pluggable-model-i18n`_
+------------------------------
+
+ This app utilizes a new approach to multilingual models based on the
same
+ concept the new admin interface uses. A translation for an existing
model
+ can be added by registering a translation class for that model.
+
+This is more or less what ``modeltranslation`` does, unfortunately it is
far
+from being finished.
+
+.. _django-multilingual:
http://code.google.com/p/django-multilingual/
+.. _django-multilingual-model:
http://code.google.com/p/django-multilingual-model/
+.. _django-transdb:
http://code.google.com/p/transdb/
+.. _i18ndynamic:
http://code.google.com/p/i18ndynamic/
+.. _django-pluggable-model-i18n:
http://code.google.com/p/django-pluggable-model-i18n/