From 36aefae0bbdba156474b117f27b86a846b8ff15f Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 19:34:49 -0400 Subject: [PATCH 01/12] Added Documentation RST files in Docs dir --- docs/AUTHORS.rst | 20 +++ docs/CHANGELOG.rst | 77 +++++++++++ docs/Configuration.rst | 213 ++++++++++++++++++++++++++++ docs/Installation.rst | 117 ++++++++++++++++ docs/Introduction.rst | 52 +++++++ docs/Multilingual Manager.rst | 252 ++++++++++++++++++++++++++++++++++ docs/Registering Models.rst | 214 +++++++++++++++++++++++++++++ docs/Usage.rst | 105 ++++++++++++++ docs/index.rst | 21 +++ 9 files changed, 1071 insertions(+) create mode 100644 docs/AUTHORS.rst create mode 100644 docs/CHANGELOG.rst create mode 100644 docs/Configuration.rst create mode 100644 docs/Installation.rst create mode 100644 docs/Introduction.rst create mode 100644 docs/Multilingual Manager.rst create mode 100644 docs/Registering Models.rst create mode 100644 docs/Usage.rst create mode 100644 docs/index.rst diff --git a/docs/AUTHORS.rst b/docs/AUTHORS.rst new file mode 100644 index 0000000..56dc5e3 --- /dev/null +++ b/docs/AUTHORS.rst @@ -0,0 +1,20 @@ +InfoPortugal, S.A. - https://github.com/infoportugal + +Authors +======= + +Core Committers +--------------- + +* Diogo Marques +* Rui Martins +* Eduardo Nogueira + +Contributors +------------ + +* Django-modeltranslation_ +* Django-linguo_ + +.. _django-modeltranslation: https://github.com/deschler/django-modeltranslation +.. _django-linguo: https://github.com/zmathew/django-linguo \ No newline at end of file diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst new file mode 100644 index 0000000..2b550fd --- /dev/null +++ b/docs/CHANGELOG.rst @@ -0,0 +1,77 @@ +----------------------------------- +Change Log +----------------------------------- + +v0.2.2: +- Added duplicate content buttons to translated StreamFieldPanels; + +v0.2.1: +- Fixed missing templatetags folder on pypi package; + +v0.2: +- Support for StreamFields; +- No more django-modeltranslation dependency; + +v0.1.5 + +- Fixed required fields related bug + +v0.1.4 + +- Support for wagtailsearch and wagtailsnippets + +v0.1.3 + +- Support for translated inline panels #8: https://github.com/infoportugal/wagtail-modeltranslation/issues/8 + +v0.1.2 + +- Fixed Problem updating field with null value #6: https://github.com/infoportugal/wagtail-modeltranslation/issues/6 + +v0.1.1 + +- Fixed url_path issue caused by a browser with language different from settings.LANGUAGE_CODE + +v0.1 + +- Minor release working but lacks full test coverage. +- Last version had required fields validation problems, now fixed. + +v0.0.9 + +- Fixed issue that causes duplicated translation fields, preventing auto-slug from working properly. + +v0.0.8 + +- Fixed issue related to deleting a non existing key on PAGE_EDIT_HANDLER dict + +v0.0.7 + +- Fixed set_url_path() translatable model method + +v0.0.6 + +- Fixed js issue related to auto-generating slugs + +v0.0.5 + +- Now using django-modeltranslation 0.9.1; +- Fixed problem related to slug field fallbacks; + +v0.0.4 + +** IMPORTANT: ** make sure that TranslationMixin comes before Page class on model inheritance + +- Fix enhancement #1: url_path translation field +- Now includes a template tag that returns current page url to corresponding translated url +- New management command to update url_path translation fields - **set\_translation\_url\_paths** + +v0.0.3 + +- New methods; +- Now supports required fields; +- Fixed issue related to browser locale; + +v0.0.2 + +- Prepopulated fields now works for translated fields (title and slug) \ No newline at end of file diff --git a/docs/Configuration.rst b/docs/Configuration.rst new file mode 100644 index 0000000..9a77051 --- /dev/null +++ b/docs/Configuration.rst @@ -0,0 +1,213 @@ +********************************* +Configuration of Modeltranslation +********************************* + + +Modeltranslation also has some advanced settings to customize its behaviour. + +.. _settings-modeltranslation_default_language: + + +Default language +---------------- + +``MODELTRANSLATION_DEFAULT_LANGUAGE`` + +Default: ``None`` + +To override the default language as described in :ref:`Configuration settings`, you can define a language in +``MODELTRANSLATION_DEFAULT_LANGUAGE``. Note that the value has to be in ``settings.LANGUAGES``, otherwise an +ImproperlyConfigured exception will be raised. + +Example:: + + MODELTRANSLATION_DEFAULT_LANGUAGE = 'pt' + + + +Default languages +----------------- + +``MODELTRANSLATION_LANGUAGES`` + +Default: same as LANGUAGES + +Allows to set the languages the content will be translated into. If not set, by default all languages listed in LANGUAGES +will be used. + +Example:: + + LANGUAGES = ( + ('pt', 'Portuguese'), + ('es', 'Spanish'), + ('fr', 'French'), + ) + + MODELTRANSLATION_LANGUAGES = ('pt', 'es') + +.. note:: + This setting may become useful if your users will be producing content for a restricted set of languages, while your + application is translated into a greater number of locales. + + +.. _MODELTRANSLATION_FALLBACK_LANGUAGES: + + +Fallback languages +------------------ + +``MODELTRANSLATION_FALLBACK_LANGUAGES`` + +Default: ``(DEFAULT_LANGUAGE)`` + +By default modeltranslation will fallback to the computed value of the DEFAULT_LANGUAGE. This is either the first language +found in the LANGUAGES setting or the value defined through MODELTRANSLATION_DEFAULT_LANGUAGE which acts as an override. + +This setting allows for a more fine grained tuning of the fallback behaviour by taking additional languages into account. +The language order is defined as a tuple or list of language codes. + +Example:: + + MODELTRANSLATION_FALLBACK_LANGUAGES = ('pt', 'es') + +Using a dict syntax it is also possible to define fallbacks by language. A default key is required in this case to define +the default behaviour of unlisted languages. + +Example:: + + MODELTRANSLATION_FALLBACK_LANGUAGES = {'default': ('pt', 'es'), 'fr': ('pt',)} + +.. note:: + Each language has to be in the LANGUAGES setting, otherwise an ``Improperly Configured`` exception is raised. + + +.. _settings-modeltranslation_prepopulate_language: + + +Prepopulate language +-------------------- + +``MODELTRANSLATION_PREPOPULATE_LANGUAGE`` + +Default: ``current active language`` + +By default modeltranslation will use the current request language for prepopulating admin fields specified in the +``prepopulated_fields`` admin property. This is often used to automatically fill slug fields. + +This setting allows you to pin this functionality to a specific language. + +Example:: + + MODELTRANSLATION_PREPOPULATE_LANGUAGE = 'fr' + +.. note:: + The language has to be in the LANGUAGES setting, otherwise an ImproperlyConfigured exception is raised. + + +Translation files +----------------- + +``MODELTRANSLATION_TRANSLATION_FILES`` + +Default: ``()`` (empty tuple) + +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. + +The setting ``MODELTRANSLATION_TRANSLATION_FILES`` is provided to extend the modules that are taken into account. + +Syntax:: + + MODELTRANSLATION_TRANSLATION_FILES = ( + '.translation', + '.translation', + ) + +Example:: + + MODELTRANSLATION_TRANSLATION_FILES = ( + 'foo.translation', + 'projects.translation', + ) + + +Custom fields +------------- + +``MODELTRANSLATION_CUSTOM_FIELDS`` + +Default: ``()`` (empty tuple) + + +Modeltranslation supports the fields listed in the `Matrix of supported_fields`. In most cases subclasses of the supported +fields will work fine, too. Unsupported fields will throw an ``Improperly Configured`` exception. + +The list of supported fields can be extended by defining a tuple of field names in your ``settings file``. + +Example:: + + MODELTRANSLATION_CUSTOM_FIELDS = ('MyField', 'MyOtherField',) + +.. warning:: + This just prevents modeltranslation from throwing an ``Improperly Configured`` exception. Any unsupported 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. + + +.. _settings-modeltranslation_auto_populate: + + +Auto populate +------------- + +``MODELTRANSLATION_AUTO_POPULATE`` + +Default: ``False`` + +This setting controls if the `multilingual_manager` should automatically populate language field values in its ``create`` +and ``get_or_create`` method, and in model constructors, so that these two blocks of statements can be considered equivalent:: + + foo.objects.populate(True).create(title='-- no translation yet --') + with auto_populate(True): + q = foo(title='-- no translation yet --') + + # same effect with MODELTRANSLATION_AUTO_POPULATE == True: + + foo.objects.create(title='-- no translation yet --') + q = foo(title='-- no translation yet --') + + +Debug +----- + +``MODELTRANSLATION_DEBUG`` + + +Default: ``False`` + +Used for modeltranslation related debug output. Currently setting it to ``False`` will just prevent Django's development +server from printing the ``Registered xx models for translation`` message to stdout. + + +Fallbacks +--------- + +``MODELTRANSLATION_ENABLE_FALLBACKS`` + +Default: ``True`` + +Controls if fallback (both language and value) will occur. + + +.. _settings-modeltranslation_loaddata_retain_locale: + + +Retain locale +------------- + +``MODELTRANSLATION_LOADDATA_RETAIN_LOCALE`` + +Default: ``True`` + +Control if the ``loaddata`` command should leave the settings-defined locale alone. Setting it to ``False`` will result in +previous behaviour of loaddata: inserting fixtures to database under en-us locale. diff --git a/docs/Installation.rst b/docs/Installation.rst new file mode 100644 index 0000000..c8ce71c --- /dev/null +++ b/docs/Installation.rst @@ -0,0 +1,117 @@ +.. _installation: + +************ +Installation +************ + +Requirements +============ + +* Django >= 1.7 +* Wagtail >= 1.0 + + + +Installing using Pip +-------------------- + +.. code-block:: console + + $ pip install wagtail-modeltranslation >= 0.2.2 + + +Installing using the source +--------------------------- + +* From github: **git clone** https://github.com/infoportugal/wagtail-modeltranslation.git + + * Copy wagtail_modeltranslation folder in project tree + + OR + +* Download ZIP file on Github.com from infoportugal/wagtail-modeltranslation + + * Unzip and copy wagtail_modeltranslation folder in project tree + + +Setup +===== + +To setup the application please follow these steps: + +1. In the settings/base.py file: + + - Add wagtail_modeltranslation to the INSTALLED_APPS + + .. code-block:: console + + INSTALLED_APPS = ( + ... + 'wagtail_modeltranslation', + ) + + + - Add django.middleware.locale.LocaleMiddleware to MIDDLEWARE_CLASSES. + + + .. code-block:: console + + MIDDLEWARE_CLASSES = ( + ... + + 'django.middleware.locale.LocaleMiddleware', + ) + + + - Set ``USE_I18N = True`` + +.. _language_settings: + + - Configure your LANGUAGES. + + The LANGUAGES variable must contain all languages you will use for translation. The first language is treated as the + *default language*. + + Modeltranslation uses the list of languages to add localized fields to the models registered for translation. + For example, to use the languages Portuguese, Spanish and French in your project, set the LANGUAGES variable like this + (where ``pt`` is the default language). + + .. code-block:: console + + LANGUAGES = ( + ('pt', u'Portugese'), + ('es', u'Spanish'), + ('fr', u'French'), + ) + +.. warning:: + + When the LANGUAGES setting isn't present in ``settings/base.py`` (and neither is ``MODELTRANSLATION_LANGUAGES``), it defaults to Django's global LANGUAGES setting instead, and there are quite a few languages in the default! + + +2. Create a ``translation.py`` in your app directory and register ``TranslationOptions`` for every model you want to translate. + +.. code-block:: console + + from .models import foo + from wagtail_modeltranslation.translator import TranslationOptions + from wagtail_modeltranslation.decorators import register + + @register(foo) + class FooTR(TranslationOptions): + fields = ( + 'body', + ) + +3. Add ``TranslationMixin`` to your translatable model:. + +.. code-block::console + # .models + ... + from wagtail_modeltranslation.models import TranslationMixin + + class FooModel(TranslationMixin, Page): + body = StreamField(...) + + +4. Run ``python manage.py makemigrations`` followed by ``python manage.py migrate``. This will add extra fields in the database. \ No newline at end of file diff --git a/docs/Introduction.rst b/docs/Introduction.rst new file mode 100644 index 0000000..8f3034f --- /dev/null +++ b/docs/Introduction.rst @@ -0,0 +1,52 @@ +************* +Introduction +************* + +Creating multilingual sites +=========================== + +**I18n** + +Django and Wagtail CMS have implemented Internationalisation (I18n) in their frameworks. Hooks are provided for translating +strings such as literals. Furthermore, **locale language files** are included. This is where the translated text of the +frameworks is stored. + +When writing your own apps, it is recommended that you use Il18n. If you need guidance, you can read the `Django Internalization +Documentation `_. + +**Wagtail-modeltranslation** + +Another important component in the translation equation is the content stored in database fields. This is where +wagtail-modeltranslation comes into play. + +Wagtail-modeltranslation is a fork of django-modeltranslation designed to define the fields that need to be translated. +In Wagtail, translation fields are displayed and edited together on the same page in the Wagtail admin interface. Translated +fields can be used in your templates and as you would use any other field. + + +Some of the advantages of wagtail-modeltranslation +-------------------------------------------------- + +* The same template is used for multiple languages +* The document tree is simpler with no need to have a separate branch for each language +* Languages can be added without changing existing models or views +* Translation fields are stored in the same table (no expensive joins) +* Can handle more than just text fields +* Wagtail admin integration +* Flexible fallbacks, auto-population and more! +* Default Page model has translatable fields by default +* StreamFields are supported +* Easy to implement + + +Examples used in this document +------------------------------ +We will be using a fictitious model ``foo`` in the coding examples. + + +Wagtail-modeltranslation and Modeltranslation +--------------------------------------------- +This document is for the most part an adaptation of the django-modeltranslation documentation, so we will refer to +``wagtail-modeltranslation`` when the material discussed is specific to Wagtail CMS and ``modeltranslation`` when it +is applicable to both wagtail-modeltranslation and django-modeltranslaton. You don't need to distinguish between the +two since wagtail-modeltranslation includes all the functionalities of django-modeltranslation. \ No newline at end of file diff --git a/docs/Multilingual Manager.rst b/docs/Multilingual Manager.rst new file mode 100644 index 0000000..daa9c32 --- /dev/null +++ b/docs/Multilingual Manager.rst @@ -0,0 +1,252 @@ +******************** +Multilingual Manager +******************** + +Every model registered for translation is patched so that all its managers become subclasses of ``MultilingualManager`` (of course, if a custom manager is defined on the model, its functions is retained). ``MultilingualManager`` simplifies language-aware queries, especially on third-party apps, by rewriting query field names. + +Every model manager is patched, not only ``objects`` but also managers inherited from abstract base classes. + +For example:: + + # Assuming the current language is "pt", + # these queries returns the same objects + Foo1 = Foo.objects.filter(introduction__contains='Welcome') + Foo2 = Foo.objects.filter(introduction_pt__contains='Welcome') + + assert Foo1 == Foo2 + + +It works this way: the translation field name is used, it is changed into the current language field name, based on the current language. Any language-suffixed fields are left untouched, so ``title_es`` wouldn't change, no matter what the current language is. + +Rewriting of field names works with operators (like ``__in``, ``__ge``) as well as with +relationship spanning. Moreover, it is also handled on ``Q`` and ``F`` expressions. + +These manager methods perform rewriting: + +- ``filter()``, ``exclude()``, ``get()`` +- ``order_by()`` +- ``update()`` +- ``only()``, ``defer()`` +- ``values()``, ``values_list()``, with :ref:`fallback ` mechanism +- ``dates()`` +- ``select_related()`` +- ``create()``, with optional auto-population_ feature + +In order not to introduce differences between ``X.objects.create(...)`` and ``X(...)``, model +constructor is also patched and performs rewriting of field names prior to regular initialization. + +If one wants to turn rewriting of field names off, this can be easily achieved with +``rewrite(mode)`` method. ``mode`` is a boolean specifying whether rewriting should be applied. +It can be changed several times inside a query. So ``X.objects.rewrite(False)`` turns rewriting off. + +``MultilingualManager`` offers one additional method: ``raw_values``. It returns actual values from +the database, without field names rewriting. Useful for checking translated field database value. + +Auto-population +--------------- + +There is special manager method ``populate mode `` which can trigger ``create()`` or +``get_or_create()`` to populate all translation language fields with values from translated original ones. It can be very convenient when working with many languages. So:: + + x = Foo.objects.populate(True).create(title='bar') + +is equivalent of:: + + x = Foo.objects.create(title_pt='bar', title_es='bar', title_fr='bar') ## title_?? for every language + + +Moreover, some fields can be explicitly assigned different values:: + + x = Foo.objects.populate(True).create(title='-- no translation yet --', title_es='hay traducción todavía') + +It will result in ``title_es == 'hay traducción todavía'`` and other ``title_?? == '-- no translation yet --'``. + +There is another way of altering the current population status, an ``auto_populate`` context +manager:: + + from modeltranslation.utils import auto_populate + + with auto_populate(True): + x = Foo.objects.create(title='bar') + +Auto-population takes place also in model constructor, what is extremely useful when loading +non-translated fixtures. Just remember to use the context manager:: + + with auto_populate(): # True can be ommited + call_command('loaddata', 'fixture.json') # Some fixture loading + + z = Foo(title='bar') + print z.title_pt, z.title_es, z.title_fr # prints 'bar bar bar' + +There is a more convenient way than calling ``populate`` manager method or entering +``auto_populate`` manager context all the time: :ref:`settings-modeltranslation_auto_populate` +setting. It controls the default population behaviour. + + +Auto-population modes +--------------------- + +There are four different population modes: + +``False`` + [set by default] + + Auto-population turned off + +``True`` or ``'all'`` + [default argument to population altering methods] + + Auto-population turned on, copying translated field value to all other languages + (unless a translation field value is provided) + +``'default'`` + Auto-population turned on, copying translated field value to default language field + (unless its value is provided) + +``'required'`` + Acts like ``'default'``, but copy value only if the original field is non-nullable + + +.. _fallback: + +Falling back +------------ + +Modeltranslation provides a mechanism to control behaviour of data access in case of empty +translation values. This mechanism affects field access, as well as ``values()`` and ``values_list()`` manager methods. + +Here is an example: a writer of some news hasn't specified a French title and content, but only the Spanish and Portuguese ones. Then if a French visitor is viewing the site, we would rather show +him English title/content of the news than having empty strings displayed. This is called *fallback*. :: + + news.title_en = 'English title' + news.title_fr = '' + print news.title + # If current active language is French, it should display the title_de field value (''). + # But if fallback is enabled, it would display 'English title' instead. + + # Similarly for manager + news.save() + print News.objects.filter(pk=news.pk).values_list('title', flat=True)[0] + # As above: if current active language is French and fallback to English is enabled, + # it would display 'English title'. + +There are several ways of controlling fallback, described below. + +.. _fallback_lang: + +Fallback languages +------------------ + +:ref:`modeltranslation_fallback_languages` setting allows to set the order of *fallback +languages*. By default that's the ``DEFAULT_LANGUAGE``. + +For example, setting :: + + MODELTRANSLATION_FALLBACK_LANGUAGES = ('en', 'es') + +means: if current active language field value is unset, try English value. If it is also unset, +try Portuguese, and so on - until some language yields a non-empty value of the field. + +There is also an option to define a fallback by language, using dict syntax:: + + MODELTRANSLATION_FALLBACK_LANGUAGES = { + 'default': ('pt', 'es', 'en'), + 'fr': ('es',), + 'uk': ('fr',) + } + +The ``default`` key is required and its value denote languages which are always tried at the end. +With such a setting: + +- for `uk` the order of fallback languages is: ``('ru', 'en', 'de', 'fr')`` +- for `fr` the order of fallback languages is: ``('de', 'en')`` - Note, that `fr` obviously is not + a fallback, since its active language and `de` would be tried before `en` +- for `en` and `de` the fallback order is ``('de', 'fr')`` and ``('en', 'fr')``, respectively +- for any other language the order of fallback languages is just ``('en', 'de', 'fr')`` + +What is more, fallback languages order can be overridden per model, using ``TranslationOptions``:: + + class NewsTranslationOptions(TranslationOptions): + fields = ('title', 'text',) + fallback_languages = {'default': ('fa', 'km')} # use Persian and Khmer as fallback for News + +Dict syntax is only allowed there. + +Even more, all fallbacks may be switched on or off for just some exceptional block of code using:: + + from modeltranslation.utils import fallbacks + + with fallbacks(False): + # Work with values for the active language only + +.. _fallback_val: + +Fallback values +--------------- + +But what if current language and all fallback languages yield no field value? Then modeltranslation +will use the field's *fallback value*, if one was defined. + +Fallback values are defined in ``TranslationOptions``, for example:: + + class NewsTranslationOptions(TranslationOptions): + fields = ('title', 'text',) + fallback_values = _('-- sorry, no translation provided --') + +In this case, if title is missing in active language and any of fallback languages, news title +will be ``'-- sorry, no translation provided --'`` (maybe translated, since gettext is used). +Empty text will be handled in same way. + +Fallback values can be also customized per model field:: + + class NewsTranslationOptions(TranslationOptions): + fields = ('title', 'text',) + fallback_values = { + 'title': _('-- sorry, this news was not translated --'), + 'text': _('-- please contact our translator (translator@example.com) --') + } + +If current language and all fallback languages yield no field value, and no fallback values are +defined, then modeltranslation will use the field's default value. + +.. _fallback_undef: + +Fallback undefined +------------------ + +Another question is what do we consider "no value", on what value should we fall back to other +translations? For text fields the empty string can usually be considered as the undefined value, +but other fields may have different concepts of empty or missing values. + +Modeltranslation defaults to using the field's default value as the undefined value (the empty +string for non-nullable ``CharFields``). This requires calling ``get_default`` for every field +access, which in some cases may be expensive. + +If you'd like to fall back on a different value or your default is expensive to calculate, provide +a custom undefined value (for a field or model):: + + class NewsTranslationOptions(TranslationOptions): + fields = ('title', 'text',) + fallback_undefined = { + 'title': 'no title', + 'text': None + } + +The State of the original field +------------------------------- + +As defined by the :ref:`rules`, accessing the original field is guaranteed to +work on the associated translation field of the current language. This applies +to both, read and write operations. + +The actual field value (which *can* still be accessed through instance.__dict__['original_field_name']``) however has to +be considered **undetermined** once the field has been registered for translation. Attempts to keep the value in sync with +either the default or current language's field value has raised a boatload of unpredictable side effects in older versions +of modeltranslation. + +.. warning:: + Do not rely on the underlying value of the *original field* in any way! + +.. todo:: + Perhaps outline effects this might have on the ``update_translation_field`` + management command. \ No newline at end of file diff --git a/docs/Registering Models.rst b/docs/Registering Models.rst new file mode 100644 index 0000000..0d8ad4d --- /dev/null +++ b/docs/Registering Models.rst @@ -0,0 +1,214 @@ +.. _Registering Models: + +Registering models for translation +================================== + +``Modeltranslation`` can translate model fields of any model class. + +In wagtail-modeltranslation a **TranslationMixin** is used with the Page model: + +.. code-block::console + + from wagtail_modeltranslation.models import TranslationMixin + + class FooModel(TranslationMixin, Page): + body = StreamField(...) + + +Registering models and their fields used for translation requires the following steps: + +1. Create **translation.py** in your app directory. +2. Define the models you want to use, import wagtail-modeltranslation's **TranslationOptions** and the **register** decorator +3. Create a translation option class for every model you want to translate and precede the class with the **@register** decorator. + +The wagtail-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 model fields are needed for translation. The class must derive from +**wagtail_modeltranslation.translator.TranslationOptions** and it must provide a **field** attribute storing the list of +field names. The option class must be registered with the **wagtail_modeltranslation.decorators.register** instance. + +To illustrate this let's have a look at a simple example using a **Foo** model. The example only contains an **introduction** +and a **body** field. + +Instead of a **Foo** model, this could be any Wagtail model class: + +.. code-block:: console + + from .models import Foo + from wagtail_modeltranslation.translator import TranslationOptions + from wagtail_modeltranslation.decorators import register + + @register(Foo) + class FooTR(TranslationOptions): + fields = ( + 'introduction', + 'body', + ) + +In the above example, the **introduction** and **body** language fields will be be added for each language defined in +LANGUAGES in the settings file ,**base.py**, when the database is updated with **./manage.py makemigrations** and +**./manage.py migrate.** + + +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 **Foo** model for translation a SQL dump of the Foo app will look like this: + +.. code-block:: console + + $ ./manage.py sqlall news + BEGIN; + CREATE TABLE `news_Foo` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `introduction` varchar(255) NOT NULL, + `introduction_pt` varchar(255) NULL, + `introduction_es` varchar(255) NULL, + `introduction_fr` varchar(255) NULL, + `body` varchar(255) NOT NULL, + `body_pt` varchar(255) NULL, + `body_es` varchar(255) NULL, + `body_fr` varchar(255) NULL, + ) + ; + CREATE INDEX `news_Foo_page_id` ON `news_Foo` (`page_id`); + COMMIT; + +Note the ``introduction_pt``, ``introduction_es``, ``introduction_fr``, ``body_pt``, ``body_es`` and ``body_fr`` fields +which are not declared in the original ``Foo`` model class 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. + + +.. _register-precautions: + +Precautions regarding registration approach +------------------------------------------- + +Be aware that registration approach (as opposed to base-class approach) to models translation has a few caveats, though +(despite many pros). + +First important thing to note is the fact that translatable models are being patched - that means their fields list is not +final until the modeltranslation code executes. In normal circumstances it shouldn't affect anything - as long as +``models.py`` contain only models' related code. + +For example: consider a project where a ``ModelForm`` is declared in ``models.py`` just after its model. When the file is +executed, the form gets prepared - but it will be frozen with old fields list (without translation fields). That's because the +``ModelForm`` will be created before modeltranslation would add new fields to the model (``ModelForm`` gathers fields info at +class creation time, not instantiation time). Proper solution is to define the form in ``forms.py``, which wouldn't be imported +alongside with **models.py** (and rather imported from views file or urlconf). + +Generally, for seamless integration with modeltranslation (and as sensible design anyway), the models.py`` should contain + only bare models and model related logic. + +.. _db-fields: + +Committing fields to database +----------------------------- + +.. _migrations: + +Modeltranslation supports the migration system introduced by Django 1.7. Besides the normal workflow as described in Django's +`Migration Docs `__, you should do a migration whenever one of the following changes have been made to your project: + +- Added or removed a language through ``settings.LANGUAGES`` or ``settings.MODELTRANSLATION LANGUAGES``. +- Registered or unregistered a field through ``TranslationOptions``. + +It doesn't matter if you are starting a fresh project or change an existing one, it's always: + +1. ``python manage.py makemigration`` to create a new migration with + the added or removed fields. + +2. ``python manage.py migrate`` to apply the changes. + + +.. _required_langs: + +Required fields +--------------- + +By default, all translation fields are optional (not required). This can be changed using a special attribute on +``TranslationOptions``:: + + class NewsTranslationOptions(TranslationOptions): + fields = ('title', 'text',) + required_languages = ('en', 'de') + +It's quite self-explanatory: for German and English, all translation fields are required. For other +languages - optional. + +A more fine-grained control is available:: + + class NewsTranslationOptions(TranslationOptions): + fields = ('title', 'text',) + required_languages = {'de': ('title', 'text'), 'default': ('title',)} + +For German, all fields (both ``title`` and ``text``) are required; for all other languages - only +**title** is required. The **'default'** is optional. + +.. note:: + Requirement is enforced by ``blank=False``. Please remember that it will trigger validation only + in modelforms and admin (as always in Django). Manual model validation can be performed via + the ``full_clean()`` model method. + + The required fields are still ``null=True``, though. + + +.. _supported_field_matrix: + +Matrix of supported fields +-------------------------- + +While the main purpose of modeltranslation is to translate text-like fields, translating other fields can be useful in +several situations. The table lists all model fields available in Django and Wagtail and gives an overview about their +current support status. + +=============================== =============== + Model Field Implemented +=============================== =============== +**AutoField** |n| +**BigIntegerField** |i| +**BooleanField** |y| +**CharField** |y| +**CommaSeparatedIntegerField** |y| +**DateField** |y| +**DateTimeField** |y| +**DecimalField** |y| +**EmailField** |i| +**FileField** |y| +**FilePathField** |i| +**FloatField** |y| +**ImageField** |y| +**IntegerField** |y| +**IPAddressField** |y| +**GenericIPAddressField** |y| +**NullBooleanField** |y| +**PositiveIntegerField** |i| +**PositiveSmallIntegerField** |i| +**SlugField** |i| +**SmallIntegerField** |i| +**StreamField** |y| +**TextField** |y| +**TimeField** |y| +**URLField** |i| +**ForeignKey** |y| +**OneToOneField** |y| +**ManyToManyField** |n| +=============================== =============== + +.. |y| replace:: Yes +.. |i| replace:: Yes\* +.. |n| replace:: No +.. |u| replace:: ? + +\* Implicitly supported (as subclass of a supported field) diff --git a/docs/Usage.rst b/docs/Usage.rst new file mode 100644 index 0000000..60031c6 --- /dev/null +++ b/docs/Usage.rst @@ -0,0 +1,105 @@ + +Accessing translated fields +=========================== + +Modeltranslation changes the behaviour of the translated fields. To explain this consider the Foo +example from the :ref:`Registering Models` chapter again. The original ``Foo`` model: + +.. code-block:: console + + class Foo(TranslationMixin, Page): + introduction = models.CharField(max_length=255) + body = RichtextField(blank=True) + +Now that it is registered with wagtail-modeltranslation, the model looks like this - note the additional fields automatically added by the app: + +.. code-block:: console + + class Foo(TranslationMixin, Page): + introduction = models.CharField(max_length=255) # original/translated field + introduction_pt = models.CharField(null=True, blank=True, max_length=255) # default translation field + introduction_es = models.CharField(null=True, blank=True, max_length=255) # translation field + introduction_fr = models.CharField(null=True, blank=True, max_length=255) # translation field + body = RichTextField(blank=True) # original/translated field + body_pt = RichTextField(null=True, blank=True) # default translation field + body_es = RichTextField(null=True, blank=True) # translation field + body_fr = RichTextField(null=True, blank=True) # translation field + +The example above assumes that the default language is ``pt``, therefore the ``introduction_pt`` and ``body_pt`` fields are marked as the *default translation fields*. If the default language were ``fr``, then ``introduction_fr`` and ``body_fr`` would be the *default translation fields*. + +.. warning:: + + The ``title`` field is inherited from the Page model; if you try to create a field called ``title`` in one of your ``models`` you will get a warning messaqe. You can include the ``title field`` in ``translation.py`` and in the ``content_panels`` since it is inherited. + +.. code-block:: console + + # .models + HomePage.content_panels = [ + FieldPanel('title', classname="full title"), + FieldPanel('introduction', classname="full"), + FieldPanel('body', classname="full"), + InlinePanel(HomePage, 'carousel_items', label="Carousel items"), + InlinePanel(HomePage, 'related_links', label="Related links"), + ] + + +.. code-block:: console + + # translation.py + from .models import Foo + from wagtail_modeltranslation.translator import TranslationOptions + from wagtail_modeltranslation.decorators import register + + @register(Foo) + class FooTR(TranslationOptions): + fields = ( + 'title', + 'introduction', + 'body', + ) + +.. _rules: + +Rules for Translated Field Access +--------------------------------- + +The following rules apply to setting and getting the value of the original and the translation fields: + +**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 updates the value in the associated + current language translation field. + +**Rule 3** + + If both fields - the original and the current language translation field - + are updated at the same time, the current language translation field wins. + + .. note:: This can only happen in the model's constructor or + ``objects.create``. There is no other situation which can be considered + *changing several fields at the same time*. + + +Examples of translated field access +------------------------------------ + +Because the whole point of using the wagtail-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** refers to the Django ``set_language`` view and the corresponding ``get_lang`` +function. + +Assuming the current language is ``pt`` in the Foo example above, the translated ``introduction`` field will return the value from the ``introduction_pt`` field:: + + # Assuming the current language is "pt" + n = News.objects.all()[0] + t = n.introduction # returns the Portuguese translation + + # Assuming the current language is "pt" + t = n.introduction # returns the Portuguese 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. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..d4f4802 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,21 @@ +.. Wagtail ModelTranslation Documentation documentation master file, created by + sphinx-quickstart on Tue Aug 18 11:42:59 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Wagtail Modeltranslation +------------------------ + +Contents: + +.. toctree:: + :maxdepth: 2 + + Introduction + Installation + Configuration + Registering Models + Usage + Multilingual Manager + AUTHORS + CHANGELOG From a94d9b849d68a21ccbc6035ffb83f375bf241e06 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 19:48:18 -0400 Subject: [PATCH 02/12] fixed link for Contributers --- AUTHORS.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 2a8e54c..17171ef 100755 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -13,5 +13,9 @@ Core Committers Contributors ------------ -.. _django-modeltranslation: https://github.com/deschler/django-modeltranslation -.. _django-linguo: https://github.com/zmathew/django-linguo +* `Django-modeltranslation`_ +* `Django-lingua`_ + + +.. _Django-modeltranslation: https://github.com/deschler/django-modeltranslation +.. _Django-linguo: https://github.com/zmathew/django-linguo From 7e7800419281ed05dfc176d1ec27c4ffe88cbdf8 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 19:51:48 -0400 Subject: [PATCH 03/12] fixed link for Contributers --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 17171ef..bf71ae1 100755 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -14,7 +14,7 @@ Contributors ------------ * `Django-modeltranslation`_ -* `Django-lingua`_ +* `Django-linguo`_ .. _Django-modeltranslation: https://github.com/deschler/django-modeltranslation From 91923859bad0bda6cbd356fe57e2d001a28369c9 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 22:25:01 -0400 Subject: [PATCH 04/12] Fixed code not displayed step 4 --- docs/Installation.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Installation.rst b/docs/Installation.rst index c8ce71c..c8a681e 100644 --- a/docs/Installation.rst +++ b/docs/Installation.rst @@ -105,7 +105,8 @@ To setup the application please follow these steps: 3. Add ``TranslationMixin`` to your translatable model:. -.. code-block::console +.. code-block:: console + # .models ... from wagtail_modeltranslation.models import TranslationMixin @@ -114,4 +115,4 @@ To setup the application please follow these steps: body = StreamField(...) -4. Run ``python manage.py makemigrations`` followed by ``python manage.py migrate``. This will add extra fields in the database. \ No newline at end of file +4. Run ``python manage.py makemigrations`` followed by ``python manage.py migrate``. This will add extra fields in the database. From 80163f73d9b2d7e42e5c95226d16ee7997f4f5a3 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 22:28:13 -0400 Subject: [PATCH 05/12] Fixed code not displayed step 3 --- docs/Installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Installation.rst b/docs/Installation.rst index c8a681e..2d22f62 100644 --- a/docs/Installation.rst +++ b/docs/Installation.rst @@ -107,7 +107,7 @@ To setup the application please follow these steps: .. code-block:: console - # .models + # .models foo ... from wagtail_modeltranslation.models import TranslationMixin From ad9d2cbe29f26125df4c8cef3539df13c21b8820 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 23:06:28 -0400 Subject: [PATCH 06/12] Renamed Config file to Advanced Settings --- ...Configuration.rst => Advanced Settings.rst} | 18 +++++++++--------- docs/index.rst | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) rename docs/{Configuration.rst => Advanced Settings.rst} (93%) diff --git a/docs/Configuration.rst b/docs/Advanced Settings.rst similarity index 93% rename from docs/Configuration.rst rename to docs/Advanced Settings.rst index 9a77051..89bb520 100644 --- a/docs/Configuration.rst +++ b/docs/Advanced Settings.rst @@ -1,6 +1,6 @@ -********************************* -Configuration of Modeltranslation -********************************* +***************** +Advanced Settings +***************** Modeltranslation also has some advanced settings to customize its behaviour. @@ -17,7 +17,7 @@ Default: ``None`` To override the default language as described in :ref:`Configuration settings`, you can define a language in ``MODELTRANSLATION_DEFAULT_LANGUAGE``. Note that the value has to be in ``settings.LANGUAGES``, otherwise an -ImproperlyConfigured exception will be raised. +``ImproperlyConfigured`` exception will be raised. Example:: @@ -75,7 +75,7 @@ the default behaviour of unlisted languages. Example:: - MODELTRANSLATION_FALLBACK_LANGUAGES = {'default': ('pt', 'es'), 'fr': ('pt',)} + MODELTRANSLATION_FALLBACK_LANGUAGES = {'default': ('pt', 'es'), 'fr': ('es',)} .. note:: Each language has to be in the LANGUAGES setting, otherwise an ``Improperly Configured`` exception is raised. @@ -89,7 +89,7 @@ Prepopulate language ``MODELTRANSLATION_PREPOPULATE_LANGUAGE`` -Default: ``current active language`` +Default: ``the current active language`` By default modeltranslation will use the current request language for prepopulating admin fields specified in the ``prepopulated_fields`` admin property. This is often used to automatically fill slug fields. @@ -101,7 +101,7 @@ Example:: MODELTRANSLATION_PREPOPULATE_LANGUAGE = 'fr' .. note:: - The language has to be in the LANGUAGES setting, otherwise an ImproperlyConfigured exception is raised. + The language has to be in the ``LANGUAGES`` setting, otherwise an ``ImproperlyConfigured`` exception is raised. Translation files @@ -126,7 +126,7 @@ Syntax:: Example:: MODELTRANSLATION_TRANSLATION_FILES = ( - 'foo.translation', + 'news.translation', 'projects.translation', ) @@ -139,7 +139,7 @@ Custom fields Default: ``()`` (empty tuple) -Modeltranslation supports the fields listed in the `Matrix of supported_fields`. In most cases subclasses of the supported +Modeltranslation supports the fields listed in the `Matrix of supported_fields`_. In most cases subclasses of the supported fields will work fine, too. Unsupported fields will throw an ``Improperly Configured`` exception. The list of supported fields can be extended by defining a tuple of field names in your ``settings file``. diff --git a/docs/index.rst b/docs/index.rst index d4f4802..44b9591 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,7 +13,7 @@ Contents: Introduction Installation - Configuration + Advanced Settings Registering Models Usage Multilingual Manager From e0b9053292b1aaa33afd91c4d94a4191134f12b6 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 23:45:42 -0400 Subject: [PATCH 07/12] Corrections in examples, language choices, etc. --- docs/Advanced Settings.rst | 2 +- docs/Installation.rst | 2 +- docs/Registering Models.rst | 16 ++++++++-------- docs/Usage.rst | 10 ++++------ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/Advanced Settings.rst b/docs/Advanced Settings.rst index 89bb520..2c233e0 100644 --- a/docs/Advanced Settings.rst +++ b/docs/Advanced Settings.rst @@ -3,7 +3,7 @@ Advanced Settings ***************** -Modeltranslation also has some advanced settings to customize its behaviour. +Modeltranslation has some advanced settings to customize its behaviour. .. _settings-modeltranslation_default_language: diff --git a/docs/Installation.rst b/docs/Installation.rst index 2d22f62..3dc7054 100644 --- a/docs/Installation.rst +++ b/docs/Installation.rst @@ -89,7 +89,7 @@ To setup the application please follow these steps: When the LANGUAGES setting isn't present in ``settings/base.py`` (and neither is ``MODELTRANSLATION_LANGUAGES``), it defaults to Django's global LANGUAGES setting instead, and there are quite a few languages in the default! -2. Create a ``translation.py`` in your app directory and register ``TranslationOptions`` for every model you want to translate. +2. Create a ``translation.py`` file in your app directory and register ``TranslationOptions`` for every model you want to translate. .. code-block:: console diff --git a/docs/Registering Models.rst b/docs/Registering Models.rst index 0d8ad4d..c97ee90 100644 --- a/docs/Registering Models.rst +++ b/docs/Registering Models.rst @@ -141,20 +141,20 @@ By default, all translation fields are optional (not required). This can be chan ``TranslationOptions``:: class NewsTranslationOptions(TranslationOptions): - fields = ('title', 'text',) - required_languages = ('en', 'de') + fields = ('introduction', 'body',) + required_languages = ('pt', 'es') -It's quite self-explanatory: for German and English, all translation fields are required. For other -languages - optional. +It's quite self-explanatory: for Portuguese and Spanish, the ``introduction`` and ``body`` translation fields are required. For other +languages, they are optional. A more fine-grained control is available:: class NewsTranslationOptions(TranslationOptions): - fields = ('title', 'text',) - required_languages = {'de': ('title', 'text'), 'default': ('title',)} + fields = ('introduction', 'body',) + required_languages = {'pt': ('introduction', 'body'), 'default': ('introduction',)} -For German, all fields (both ``title`` and ``text``) are required; for all other languages - only -**title** is required. The **'default'** is optional. +For Portuguese, all fields (both ``introduction`` and ``body``) are required; for all other languages, only +``introduction`` is required. The ``default`` is optional. .. note:: Requirement is enforced by ``blank=False``. Please remember that it will trigger validation only diff --git a/docs/Usage.rst b/docs/Usage.rst index 60031c6..fb469b2 100644 --- a/docs/Usage.rst +++ b/docs/Usage.rst @@ -7,7 +7,7 @@ example from the :ref:`Registering Models` chapter again. The original ``Foo`` m .. code-block:: console - class Foo(TranslationMixin, Page): + class FooModel(TranslationMixin, Page): introduction = models.CharField(max_length=255) body = RichtextField(blank=True) @@ -15,7 +15,7 @@ Now that it is registered with wagtail-modeltranslation, the model looks like th .. code-block:: console - class Foo(TranslationMixin, Page): + class FooModel(TranslationMixin, Page): introduction = models.CharField(max_length=255) # original/translated field introduction_pt = models.CharField(null=True, blank=True, max_length=255) # default translation field introduction_es = models.CharField(null=True, blank=True, max_length=255) # translation field @@ -33,13 +33,11 @@ The example above assumes that the default language is ``pt``, therefore the ``i .. code-block:: console - # .models - HomePage.content_panels = [ + # Indicate fields to include in Wagtail admin panel(s) + FooModel.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('introduction', classname="full"), FieldPanel('body', classname="full"), - InlinePanel(HomePage, 'carousel_items', label="Carousel items"), - InlinePanel(HomePage, 'related_links', label="Related links"), ] From 6faddbffb46ac2bab748bbc8b54ae433721a955c Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 23:51:45 -0400 Subject: [PATCH 08/12] Added presentation text to index page --- docs/index.rst | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 44b9591..00b3eaf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,8 +3,42 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. +======================== Wagtail Modeltranslation ------------------------- +======================== + +This app is based on django-modeltranslation: https://github.com/deschler/django-modeltranslation + +It's an alternative approach for i18n support on Wagtail CMS websites. + +The modeltranslation application is used to translate dynamic content of +existing Wagtail 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 add translations to existing or new projects and is +fully integrated into the Wagtail admin UI. + +The advantage of a registration approach is the ability to add translations to +models on a per-app basis. You can use the same app in different projects, +whether or not they use translations, and without touching the original +model class. + + +.. image:: https://github.com/infoportugal/wagtail-modeltranslation/blob/master/screenshot.png?raw=true + :target: https://github.com/infoportugal/wagtail-modeltranslation/blob/master/screenshot.png?raw=true + + +Features +======== + +- Add translations without changing existing models or views +- Translation fields are stored in the same table (no expensive joins) +- Supports inherited models (abstract and multi-table inheritance) +- Handle more than just text fields +- Wagtail admin integration +- Flexible fallbacks, auto-population and more! +- Default Page model fields has translatable fields by default +- StreamFields are now supported! + Contents: From e534716573c3768487eccc5096db7567b0f8b549 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 23:54:57 -0400 Subject: [PATCH 09/12] Improved Index file --- docs/index.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 00b3eaf..212efb7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,8 +39,9 @@ Features - Default Page model fields has translatable fields by default - StreamFields are now supported! - -Contents: +======== +Contents +======== .. toctree:: :maxdepth: 2 From 86a3a01270ac76920f796a7724af482819ed8cc6 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 23:56:44 -0400 Subject: [PATCH 10/12] Improved Index file --- docs/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 212efb7..2133bdc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,6 +39,8 @@ Features - Default Page model fields has translatable fields by default - StreamFields are now supported! + + ======== Contents ======== From 9a6c0f4345a71130a01caeb814a23e56a144d923 Mon Sep 17 00:00:00 2001 From: agelinas Date: Thu, 20 Aug 2015 23:57:30 -0400 Subject: [PATCH 11/12] Improved Index file --- docs/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 2133bdc..153c030 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,7 +41,6 @@ Features -======== Contents ======== From 30043527708999c74d1e28d3bbdac1616b89bb9e Mon Sep 17 00:00:00 2001 From: agelinas Date: Fri, 21 Aug 2015 00:01:14 -0400 Subject: [PATCH 12/12] Added link to readthedocs --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ffd6792..1c05088 100755 --- a/README.rst +++ b/README.rst @@ -97,4 +97,4 @@ https://github.com/infoportugal/wagtail-modeltranslation Documentation ------------- -Coming soon +http://wagtail-modeltranslation-docs.readthedocs.org/en/latest/#