From b87bc152d11c556a9bb207b132c15f25d7f5f0a2 Mon Sep 17 00:00:00 2001 From: Dirk Eschler Date: Wed, 17 Oct 2012 15:02:22 +0200 Subject: [PATCH] Major refactoring of the documentation. --- .gitignore | 3 + AUTHORS.rst | 17 + AUTHORS.txt | 13 - README.rst | 19 +- docs/modeltranslation/admin.rst | 210 ++++++ docs/modeltranslation/authors.rst | 1 + docs/modeltranslation/caveats.rst | 52 ++ docs/modeltranslation/commands.rst | 40 ++ docs/modeltranslation/index.rst | 723 +-------------------- docs/modeltranslation/installation.rst | 154 +++++ docs/modeltranslation/readme.rst | 1 + docs/modeltranslation/registration.rst | 110 ++++ docs/modeltranslation/related_projects.rst | 55 ++ docs/modeltranslation/usage.rst | 83 +++ 14 files changed, 749 insertions(+), 732 deletions(-) create mode 100644 AUTHORS.rst delete mode 100644 AUTHORS.txt create mode 100644 docs/modeltranslation/admin.rst create mode 120000 docs/modeltranslation/authors.rst create mode 100644 docs/modeltranslation/caveats.rst create mode 100644 docs/modeltranslation/commands.rst create mode 100644 docs/modeltranslation/installation.rst create mode 120000 docs/modeltranslation/readme.rst create mode 100644 docs/modeltranslation/registration.rst create mode 100644 docs/modeltranslation/related_projects.rst create mode 100644 docs/modeltranslation/usage.rst diff --git a/.gitignore b/.gitignore index af60229..390048e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ pip-log.txt .coverage .tox nosetests.xml + +# Sphinx +_build diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..98a3369 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,17 @@ +Authors +------- + +* Peter Eschler +* Dirk Eschler + + +Contributors +------------ + +* Carl J. Meyer +* Jaap Roes +* Bojan Mihelac +* Sébastien Fievet +* Jacek Tomaszewski +* Bruno Tavares +* And many more... diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 88b9959..0000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,13 +0,0 @@ -AUTHORS -======= -Peter Eschler (http://www.nmy.de) -Dirk Eschler (http://www.nmy.de) - -CONTRIBUTORS -============ -Carl J. Meyer -Jaap Roes -Bojan Mihelac -Sébastien Fievet -Jacek Tomaszewski -Bruno Tavares diff --git a/README.rst b/README.rst index 0bc5896..e1a890e 100644 --- a/README.rst +++ b/README.rst @@ -23,15 +23,14 @@ Features - Supports inherited models +Project home +------------ +https://github.com/deschler/django-modeltranslation + Documentation -============= +------------- +https://readthedocs.org/projects/django-modeltranslation -* https://github.com/deschler/django-modeltranslation/blob/master/docs/modeltranslation/modeltranslation.txt - - -Getting Help -============ - -A mailing list is available at: - -* http://groups.google.com/group/django-modeltranslation +Mailing list +------------ +http://groups.google.com/group/django-modeltranslation diff --git a/docs/modeltranslation/admin.rst b/docs/modeltranslation/admin.rst new file mode 100644 index 0000000..265c957 --- /dev/null +++ b/docs/modeltranslation/admin.rst @@ -0,0 +1,210 @@ +Django admin 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: + +.. code-block:: python + + 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: + +.. code-block:: python + + 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: + +.. code-block:: python + + >>> 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: + +.. code-block:: python + + class MyTranslatedNewsAdmin(NewsAdmin, TranslationAdmin): + pass + +In a more complex setup the NewsAdmin itself might override +formfield_for_dbfield: + +.. code-block:: python + + 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: + +.. code-block:: python + + 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 +------- +.. versionadded:: 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 new model called ``Image``. It's +definition is left out for simplicity. Our ``News`` model inlines the new +model: + +.. code-block:: python + + 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_ + +.. code-block:: python + + 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 +------------------------------- +.. versionadded:: 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: + +.. code-block:: python + + class NewsAdmin(TranslationAdmin): + class Media: + js = ( + 'modeltranslation/js/force_jquery.js', + 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js', + 'modeltranslation/js/tabbed_translation_fields.js', + ) + css = { + 'screen': ('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. diff --git a/docs/modeltranslation/authors.rst b/docs/modeltranslation/authors.rst new file mode 120000 index 0000000..4968901 --- /dev/null +++ b/docs/modeltranslation/authors.rst @@ -0,0 +1 @@ +../../AUTHORS.rst \ No newline at end of file diff --git a/docs/modeltranslation/caveats.rst b/docs/modeltranslation/caveats.rst new file mode 100644 index 0000000..4f44a78 --- /dev/null +++ b/docs/modeltranslation/caveats.rst @@ -0,0 +1,52 @@ +Caveats +======= + +Consider the following example (assuming the default language is ``de``): + +.. code-block:: python + + >>> 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: + +.. code-block:: python + + >>> 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. diff --git a/docs/modeltranslation/commands.rst b/docs/modeltranslation/commands.rst new file mode 100644 index 0000000..671ec18 --- /dev/null +++ b/docs/modeltranslation/commands.rst @@ -0,0 +1,40 @@ +Management commands +=================== + +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: + +.. code-block:: console + + $ ./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. + + +The ``sync_translation_fields`` command +--------------------------------------- +.. versionadded:: 0.4 + +.. code-block:: console + + $ ./manage.py sync_translation_fields + +TODO diff --git a/docs/modeltranslation/index.rst b/docs/modeltranslation/index.rst index bb20704..5f03fdf 100644 --- a/docs/modeltranslation/index.rst +++ b/docs/modeltranslation/index.rst @@ -2,721 +2,26 @@ .. _ref-topics-modeltranslation: +.. include:: readme.rst + + +Table of content ================ -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 -- Dirk Eschler -- and many more contributors .. toctree:: :maxdepth: 2 + installation + registration + usage + admin + commands + caveats -Features -======== +.. toctree:: + :maxdepth: 1 -- Unlimited number of target languages -- Add translations without changing existing models -- Django admin support -- Supports inherited models + related_projects -Installation -============ - -:: - - pip install django-modeltranslation - - -Setup -===== - -To setup 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. 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`` ---------------------------------------- - -Required settings -***************** -The following variables have to be added to or edited in the project's -``settings.py``: - -``INSTALLED_APPS`` -^^^^^^^^^^^^^^^^^^ -Make sure that the ``modeltranslation`` app is listed in your -``INSTALLED_APPS`` variable: - -:: - - INSTALLED_APPS = ( - ... - 'modeltranslation', - .... - ) - -.. note:: Also make sure that the app can be found on a path contained in your - ``PYTHONPATH`` environment variable. - -``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 ``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. - -Advanced settings -***************** -Modeltranslation also has some advanced settings to customize its behaviour: - -``MODELTRANSLATION_DEFAULT_LANGUAGE`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. versionadded:: 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. - -``MODELTRANSLATION_TRANSLATION_FILES`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. versionadded:: 0.4 - -Modeltranslation uses an autoregister feature similiar to the one in Django's -admin. The autoregistration process will look for a ``translation.py`` -file in the root directory of each application that is in ``INSTALLED_APPS``. - -A setting ``MODELTRANSLATION_TRANSLATION_FILES`` is provided to limit or extend -the modules that are taken into account. It uses the following syntax: - -:: - - ('.translation', - '.translation',) - -.. note:: Modeltranslation up to version 0.3 used a single project wide - registration file which was defined through - ``MODELTRANSLATION_TRANSLATION_REGISTRY = '.translation'``. - For backwards compatibiliy the module defined through this setting is - automatically added to ``MODELTRANSLATION_TRANSLATION_FILES``. A - DeprecationWarning is issued in this case. - -``MODELTRANSLATION_CUSTOM_FIELDS`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. versionadded:: 0.3 - -``Modeltranslation`` officially supports ``CharField`` and ``TextField``. - -.. versionadded:: 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 (as well as ``FileField`` and ``ImageField`` as of version 0.4) -of any model class. For each model to translate a translation option class -containing 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 news app directory and -add the following: - -:: - - from modeltranslation.translator import translator, TranslationOptions - from 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 its 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 -------- -.. versionadded:: 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 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 -------------------------------- -.. versionadded:: 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.2/jquery-ui.min.js', - 'modeltranslation/js/tabbed_translation_fields.js', - ) - css = { - 'screen': ('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. - - -The ``sync_translation_fields`` command -======================================= -.. versionadded:: 0.4 - -TODO - - -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/ - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +.. include:: authors.rst diff --git a/docs/modeltranslation/installation.rst b/docs/modeltranslation/installation.rst new file mode 100644 index 0000000..b63c4f5 --- /dev/null +++ b/docs/modeltranslation/installation.rst @@ -0,0 +1,154 @@ +Installation +============ + +Using pip +--------- + +.. code-block:: console + + $ pip install django-modeltranslation + +Using the source +---------------- + +Get a source tarball from `github`_ or `pypi`_, unpack, then install with: + +.. code-block:: console + + $ python setup.py install + +.. note:: As an alternative, if you don't want to mess with any packaging tool, + unpack the tarball and copy/move the ``modeltranslation`` directory + somewhere into your ``PYTHONPATH``. + +.. _github: https://github.com/deschler/django-modeltranslation/downloads +.. _pypi: http://pypi.python.org/pypi/django-modeltranslation/ + + +Setup +===== + +To setup 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. 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`` +--------------------------------------- + +Required settings +***************** +The following variables have to be added to or edited in the project's +``settings.py``: + +``INSTALLED_APPS`` +^^^^^^^^^^^^^^^^^^ +Make sure that the ``modeltranslation`` app is listed in your +``INSTALLED_APPS`` variable: + +.. code-block:: python + + INSTALLED_APPS = ( + ... + 'modeltranslation', + .... + ) + +.. note:: Also make sure that the app can be found on a path contained in your + ``PYTHONPATH`` environment variable. + +``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 ``LANGUAGES`` variable like this (where +``de`` is the default language): + +.. code-block:: python + + 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. + +Advanced settings +***************** +Modeltranslation also has some advanced settings to customize its behaviour: + +``MODELTRANSLATION_DEFAULT_LANGUAGE`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. versionadded:: 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. + +``MODELTRANSLATION_TRANSLATION_FILES`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. versionadded:: 0.4 + +Modeltranslation uses an autoregister feature similiar to the one in Django's +admin. The autoregistration process will look for a ``translation.py`` +file in the root directory of each application that is in ``INSTALLED_APPS``. + +A setting ``MODELTRANSLATION_TRANSLATION_FILES`` is provided to limit or extend +the modules that are taken into account. It uses the following syntax: + +.. code-block:: python + + ('.translation', + '.translation',) + +.. note:: Modeltranslation up to version 0.3 used a single project wide + registration file which was defined through + ``MODELTRANSLATION_TRANSLATION_REGISTRY = '.translation'``. + For backwards compatibiliy the module defined through this setting is + automatically added to ``MODELTRANSLATION_TRANSLATION_FILES``. A + DeprecationWarning is issued in this case. + +``MODELTRANSLATION_CUSTOM_FIELDS`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. versionadded:: 0.3 + +``Modeltranslation`` officially supports ``CharField`` and ``TextField``. + +.. versionadded:: 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: + +.. code-block:: python + + 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. diff --git a/docs/modeltranslation/readme.rst b/docs/modeltranslation/readme.rst new file mode 120000 index 0000000..c768ff7 --- /dev/null +++ b/docs/modeltranslation/readme.rst @@ -0,0 +1 @@ +../../README.rst \ No newline at end of file diff --git a/docs/modeltranslation/registration.rst b/docs/modeltranslation/registration.rst new file mode 100644 index 0000000..f2edd0b --- /dev/null +++ b/docs/modeltranslation/registration.rst @@ -0,0 +1,110 @@ +Registering models and their fields for translation +=================================================== + +The ``modeltranslation`` app can translate ``CharField`` and ``TextField`` +based fields (as well as ``FileField`` and ``ImageField`` as of version 0.4) +of any model class. For each model to translate a translation option class +containing 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: + +.. code-block:: python + + 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 news app directory and +add the following: + +.. code-block:: python + + from modeltranslation.translator import translator, TranslationOptions + from 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 its 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: + +.. code-block:: console + + $ ./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. diff --git a/docs/modeltranslation/related_projects.rst b/docs/modeltranslation/related_projects.rst new file mode 100644 index 0000000..6e79b91 --- /dev/null +++ b/docs/modeltranslation/related_projects.rst @@ -0,0 +1,55 @@ +Related projects +================ + +.. note:: This list is horribly outdated and only covers apps that where + available when modeltranslation was initially developed. A more + complete list can be found at `djangopackages.com`_. + + +`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. + + +.. _djangopackages.com: http://www.djangopackages.com/grids/g/model-translation/ +.. _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/ diff --git a/docs/modeltranslation/usage.rst b/docs/modeltranslation/usage.rst new file mode 100644 index 0000000..b7da61d --- /dev/null +++ b/docs/modeltranslation/usage.rst @@ -0,0 +1,83 @@ +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: + +.. code-block:: python + + 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: + +.. code-block:: python + + 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: + +.. code-block:: python + + # 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.