mirror of
https://github.com/Hopiu/django-modeltranslation.git
synced 2026-05-23 19:55:51 +00:00
Use app-level translation files in favour of a single project-level one. Adds an autoregister feature similiar to the one provided by Django's admin. A new setting MODELTRANSLATION_TRANSLATION_FILES keeps backwards compatibility with older versions. This is basically a merge from django-modeltranslation-wrapper with a few changes regarding how registration is triggered. See documentation for details. Resolves issues 58 and 71 (thanks to Jacek Tomaszewski, the author of modeltranslation-wrapper).
This commit is contained in:
parent
e85f776aa8
commit
4579d831e5
6 changed files with 139 additions and 76 deletions
|
|
@ -1,6 +1,14 @@
|
|||
v0.4.0-alpha1
|
||||
=============
|
||||
|
||||
CHANGED: Use app-level translation files in favour of a single project-level
|
||||
one. Adds an autoregister feature similiar to the one provided by
|
||||
Django's admin. A new setting MODELTRANSLATION_TRANSLATION_FILES keeps
|
||||
backwards compatibility with older versions. This is basically a merge
|
||||
from django-modeltranslation-wrapper with a few changes regarding how
|
||||
registration is triggered. See documentation for details.
|
||||
(thanks to Jacek Tomaszewski, the author of modeltranslation-wrapper,
|
||||
resolves issues 58 and 71)
|
||||
CHANGED: Moved tests to separate folder and added tests for TranslationAdmin.
|
||||
To run the tests the settings provided in model.tests.modeltranslation
|
||||
have to be used (settings.LANGUAGES override doesn't work for
|
||||
|
|
|
|||
|
|
@ -41,15 +41,17 @@ 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 the ``settings.py``.
|
||||
2. Configure your ``LANGUAGES`` in ``settings.py``.
|
||||
|
||||
3. Create a ``translation.py`` in your project directory and register
|
||||
3. Create a ``translation.py`` in your app directory and register
|
||||
``TranslationOptions`` for every model you want to translate.
|
||||
|
||||
4. Configure the ``MODELTRANSLATION_TRANSLATION_REGISTRY`` variable in your
|
||||
4. Configure the ``MODELTRANSLATION_TRANSLATION_FILES`` variable in your
|
||||
``settings.py``.
|
||||
|
||||
5. Sync the database using ``manage.py syncdb`` (note that this only applies
|
||||
5. Add ``translator.autoregister()`` to your project's ``urls.py``.
|
||||
|
||||
6. 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.
|
||||
|
|
@ -107,17 +109,26 @@ 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_REGISTRY**
|
||||
**settings.MODELTRANSLATION_TRANSLATION_FILES**
|
||||
|
||||
In order to be able to import the project's ``translation.py`` registration
|
||||
file the ``MODELTRANSLATION_TRANSLATION_REGISTRY`` must be set to a value in
|
||||
the form ``<PROJECT_MODULE>.translation``. E.g. if your project is located in a
|
||||
folder named ``myproject`` the ``MODELTRANSLATION_TRANSLATION_REGISTRY`` must
|
||||
be set like this:
|
||||
*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:
|
||||
|
||||
::
|
||||
|
||||
MODELTRANSLATION_TRANSLATION_REGISTRY = "myproject.translation"
|
||||
('<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**
|
||||
|
||||
|
|
@ -152,13 +163,13 @@ option class containg the fields to translate is registered with the
|
|||
Registering models and their fields for translation requires the following
|
||||
steps:
|
||||
|
||||
1. Create a ``translation.py`` in your project directory.
|
||||
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
|
||||
project directory thereby triggering the registration of the translation
|
||||
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
|
||||
|
|
@ -167,10 +178,6 @@ and it must provide a ``fields`` attribute storing the list of fieldnames. The
|
|||
option class must be registered with the
|
||||
``modeltranslation.translator.translator`` instance.
|
||||
|
||||
.. note:: In contrast to the Django admin application which looks for
|
||||
``admin.py`` files in the project **and** application directories,
|
||||
the modeltranslation app looks only for one ``translation.py`` file in the project directory.
|
||||
|
||||
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:
|
||||
|
|
@ -205,6 +212,27 @@ translation will have been added some auto-magical fields. The next section
|
|||
explains how things are working under the hood.
|
||||
|
||||
|
||||
Autoregister translation files
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Similiar to Django's admin, modeltranslation provides a mechanism to
|
||||
automatically register your translation files. It has to be hooked into your
|
||||
project's urls.py.
|
||||
|
||||
::
|
||||
|
||||
from modeltranslation import translator
|
||||
translator.autodiscover()
|
||||
|
||||
# ...
|
||||
# Must be in in front of:
|
||||
|
||||
from django.contrib import admin
|
||||
admin.autodiscover()
|
||||
|
||||
.. note:: This step is basically optional if you prefer to register your models
|
||||
programmatically.
|
||||
|
||||
|
||||
Changes automatically applied to the model class
|
||||
------------------------------------------------
|
||||
After registering the ``News`` model for transaltion an SQL dump of the
|
||||
|
|
@ -359,21 +387,54 @@ patching on all your models registered for translation:
|
|||
|
||||
Tweaks applied to the admin
|
||||
---------------------------
|
||||
The ``TranslationAdmin`` class does only implement one special method which is
|
||||
``def formfield_for_dbfield(self, db_field, **kwargs)``. This method does the
|
||||
following:
|
||||
|
||||
1. Removes the original field from every admin form by setting it
|
||||
``editable=False``.
|
||||
2. Copies the widget of the original field to each of it's translation fields.
|
||||
3. Checks if the - now removed - original field was required and if so makes
|
||||
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
|
||||
---------------------------------------------------------
|
||||
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.
|
||||
|
||||
|
|
@ -387,7 +448,8 @@ inheritance like this:
|
|||
class MyTranslatedNewsAdmin(NewsAdmin, TranslationAdmin):
|
||||
pass
|
||||
|
||||
In a more complex setup the NewsAdmin might define its own class:
|
||||
In a more complex setup the NewsAdmin itself might override
|
||||
formfield_for_dbfield:
|
||||
|
||||
::
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from modeltranslation.settings import TRANSLATION_REGISTRY
|
||||
from modeltranslation.translator import translator
|
||||
|
||||
# Every model registered with the modeltranslation.translator.translator is
|
||||
# patched to contain additional localized versions for every field specified
|
||||
# in the model's translation options.
|
||||
|
||||
# Import the project's global "translation.py" which registers model classes
|
||||
# and their translation options with the translator object.
|
||||
try:
|
||||
__import__(TRANSLATION_REGISTRY, {}, {}, [''])
|
||||
except ImportError:
|
||||
sys.stderr.write("modeltranslation: Can't import module '%s'.\n"
|
||||
"(If the module exists, it's causing an ImportError "
|
||||
"somehow.)\n" % TRANSLATION_REGISTRY)
|
||||
# For some reason ImportErrors raised in translation.py or in modules that
|
||||
# are included from there become swallowed. Work around this problem by
|
||||
# printing the traceback explicitly.
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# After importing all translation modules, all translation classes are
|
||||
# registered with the translator.
|
||||
if settings.DEBUG:
|
||||
try:
|
||||
if sys.argv[1] in ('runserver', 'runserver_plus'):
|
||||
translated_model_names = ', '.join(
|
||||
t.__name__ for t in translator._registry.keys())
|
||||
print('modeltranslation: Registered %d models for '
|
||||
'translation (%s).' % (len(translator._registry),
|
||||
translated_model_names))
|
||||
except IndexError:
|
||||
pass
|
||||
|
|
@ -6,17 +6,17 @@ from django.conf import settings
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
if hasattr(settings, 'MODELTRANSLATION_TRANSLATION_REGISTRY'):
|
||||
TRANSLATION_REGISTRY = getattr(
|
||||
settings, 'MODELTRANSLATION_TRANSLATION_REGISTRY', None)
|
||||
elif hasattr(settings, 'TRANSLATION_REGISTRY'):
|
||||
warn('The setting TRANSLATION_REGISTRY is deprecated, use '
|
||||
'MODELTRANSLATION_TRANSLATION_REGISTRY instead.', DeprecationWarning)
|
||||
TRANSLATION_REGISTRY = getattr(settings, 'TRANSLATION_REGISTRY', None)
|
||||
else:
|
||||
TRANSLATION_FILES = tuple(
|
||||
getattr(settings, 'MODELTRANSLATION_TRANSLATION_FILES', ()))
|
||||
TRANSLATION_REGISTRY = getattr(
|
||||
settings, 'MODELTRANSLATION_TRANSLATION_REGISTRY', None)
|
||||
if TRANSLATION_REGISTRY:
|
||||
TRANSLATION_FILES += (TRANSLATION_REGISTRY,)
|
||||
warn('The setting MODELTRANSLATION_TRANSLATION_REGISTRY is deprecated, '
|
||||
'use MODELTRANSLATION_TRANSLATION_FILES instead.', DeprecationWarning)
|
||||
if not TRANSLATION_FILES:
|
||||
raise ImproperlyConfigured(
|
||||
"You haven't set the MODELTRANSLATION_TRANSLATION_REGISTRY "
|
||||
"setting yet.")
|
||||
"You haven't set the MODELTRANSLATION_TRANSLATION_FILES setting yet.")
|
||||
|
||||
AVAILABLE_LANGUAGES = [l[0] for l in settings.LANGUAGES]
|
||||
DEFAULT_LANGUAGE = getattr(settings, 'MODELTRANSLATION_DEFAULT_LANGUAGE', None)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
"""
|
||||
Tests have to be run with modeltranslation.tests.settings:
|
||||
./manage.py test --settings=modeltranslation.tests.settings modeltranslation
|
||||
|
||||
TODO: Merge autoregister tests from django-modeltranslation-wrapper.
|
||||
"""
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
|
|
|||
|
|
@ -232,5 +232,35 @@ class Translator(object):
|
|||
'translation' % model.__name__)
|
||||
|
||||
|
||||
def autodiscover():
|
||||
"""
|
||||
Auto-discover INSTALLED_APPS translation.py modules and fail silently when
|
||||
not present. This forces an import on them to register.
|
||||
Also import explicit modules.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.utils.importlib import import_module
|
||||
from django.utils.module_loading import module_has_submodule
|
||||
from modeltranslation.settings import TRANSLATION_FILES
|
||||
|
||||
project_translations = TRANSLATION_FILES
|
||||
|
||||
for app in settings.INSTALLED_APPS:
|
||||
mod = import_module(app)
|
||||
# Attempt to import the app's translation module.
|
||||
module = '%s.translation' % app
|
||||
try:
|
||||
import_module(module)
|
||||
except:
|
||||
# Decide whether to bubble up this error. If the app just
|
||||
# doesn't have an translation module, we can ignore the error
|
||||
# attempting to import it, otherwise we want it to bubble up.
|
||||
if module_has_submodule(mod, 'translation'):
|
||||
raise
|
||||
|
||||
for module in project_translations:
|
||||
import_module(module)
|
||||
|
||||
|
||||
# This global object represents the singleton translator object
|
||||
translator = Translator()
|
||||
|
|
|
|||
Loading…
Reference in a new issue