mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-04-25 11:42:54 +03:00
Add sphinx based docs
This commit is contained in:
parent
7bf0311306
commit
cdf1831d35
1
docs/.gitignore
vendored
Normal file
1
docs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build/
|
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line, and also
|
||||||
|
# from the environment for the first two.
|
||||||
|
SPHINXOPTS ?=
|
||||||
|
SPHINXBUILD ?= sphinx-build
|
||||||
|
SOURCEDIR = source
|
||||||
|
BUILDDIR = build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
pushd %~dp0
|
||||||
|
|
||||||
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
|
if "%SPHINXBUILD%" == "" (
|
||||||
|
set SPHINXBUILD=sphinx-build
|
||||||
|
)
|
||||||
|
set SOURCEDIR=source
|
||||||
|
set BUILDDIR=build
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
%SPHINXBUILD% >NUL 2>NUL
|
||||||
|
if errorlevel 9009 (
|
||||||
|
echo.
|
||||||
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||||
|
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||||
|
echo.may add the Sphinx directory to PATH.
|
||||||
|
echo.
|
||||||
|
echo.If you don't have Sphinx installed, grab it from
|
||||||
|
echo.http://sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:help
|
||||||
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
|
||||||
|
:end
|
||||||
|
popd
|
BIN
docs/source/_static/django_admin_widget.png
Normal file
BIN
docs/source/_static/django_admin_widget.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
10
docs/source/conf.py
Normal file
10
docs/source/conf.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import sphinx_rtd_theme
|
||||||
|
|
||||||
|
project = 'django-localized-fields'
|
||||||
|
copyright = '2019, Sector Labs'
|
||||||
|
author = 'Sector Labs'
|
||||||
|
extensions = ["sphinx_rtd_theme"]
|
||||||
|
templates_path = ['_templates']
|
||||||
|
exclude_patterns = []
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
html_static_path = ['_static']
|
100
docs/source/constraints.rst
Normal file
100
docs/source/constraints.rst
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
.. _unique_together: https://docs.djangoproject.com/en/2.2/ref/models/options/#unique-together
|
||||||
|
|
||||||
|
Constraints
|
||||||
|
===========
|
||||||
|
|
||||||
|
All constraints are enforced by PostgreSQL. Constraints can be applied to all fields documented in :ref:`Fields <fields>`.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Don't forget to run ``python manage.py makemigrations`` after modifying constraints.
|
||||||
|
|
||||||
|
Required/optional
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
|
||||||
|
* Default language required and all other languages optional:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField()
|
||||||
|
|
||||||
|
|
||||||
|
* All languages are optional and the field itself can be ``None``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(blank=True, null=True, required=False)
|
||||||
|
|
||||||
|
* All languages are optional but the field itself cannot be ``None``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(blank=False, null=False, required=False)
|
||||||
|
|
||||||
|
|
||||||
|
* Make specific languages required:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(blank=False, null=False, required=['en', 'ro'])
|
||||||
|
|
||||||
|
|
||||||
|
* Make all languages required:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(blank=False, null=False, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
Uniqueness
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Uniqueness is enforced by PostgreSQL by creating unique indexes on hstore keys. Keep this is mind when setting up unique constraints. If you already have a unique constraint in place, you do not have to add an additional index as uniqueness is enforced by creating an index.
|
||||||
|
|
||||||
|
|
||||||
|
* Enforce uniqueness for one or more languages:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(uniqueness=['en', 'ro'])
|
||||||
|
|
||||||
|
|
||||||
|
* Enforce uniqueness for all languages:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.util import get_language_codes
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(uniqueness=get_language_codes())
|
||||||
|
|
||||||
|
|
||||||
|
* Enforce uniqueness for one or more languages together:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(uniqueness=[('en', 'ro')])
|
||||||
|
|
||||||
|
This is similar to Django's `unique_together`_.
|
||||||
|
|
||||||
|
|
||||||
|
* Enforce uniqueness for all languages together:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.util import get_language_codes
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
title = LocalizedField(uniqueness=[(*get_language_codes())])
|
19
docs/source/django_admin.rst
Normal file
19
docs/source/django_admin.rst
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Django Admin
|
||||||
|
------------
|
||||||
|
|
||||||
|
To display ``LocalizedFields`` as a tab bar in Django Admin; inherit your admin model class from ``LocalizedFieldsAdminMixin``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from myapp.models import MyLocalizedModel
|
||||||
|
|
||||||
|
from localized_fields.admin import LocalizedFieldsAdminMixin
|
||||||
|
|
||||||
|
class MyLocalizedModelAdmin(LocalizedFieldsAdminMixin, admin.ModelAdmin):
|
||||||
|
"""Any admin options you need go here"""
|
||||||
|
|
||||||
|
admin.site.register(MyLocalizedModel, MyLocalizedModelAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: _static/django_admin_widget.png
|
99
docs/source/fields.rst
Normal file
99
docs/source/fields.rst
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
.. _django.db.models.CharField: https://docs.djangoproject.com/en/2.2/ref/models/fields/#charfield
|
||||||
|
.. _django.db.models.TextField: https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.TextField
|
||||||
|
.. _django.db.models.IntegerField: https://docs.djangoproject.com/en/2.2/ref/models/fields/#integerfield
|
||||||
|
.. _django.db.models.FileField: https://docs.djangoproject.com/en/2.2/ref/models/fields/#filefield
|
||||||
|
|
||||||
|
.. _fields:
|
||||||
|
|
||||||
|
Localized fields
|
||||||
|
================
|
||||||
|
|
||||||
|
LocalizedField
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Base localized fields. Stores content as strings of arbitrary lengths.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.fields import LocalizedField
|
||||||
|
|
||||||
|
|
||||||
|
LocalizedCharField
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Localized version of `django.db.models.CharField`_.
|
||||||
|
|
||||||
|
Use this for single-line text content. Uses ``<input type="text" />`` when rendered as a widget.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.fields import LocalizedCharField
|
||||||
|
|
||||||
|
Follows the same convention as `django.db.models.CharField`_ to store empty strings for "no data" and not NULL.
|
||||||
|
|
||||||
|
|
||||||
|
LocalizedTextField
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Localized version of `django.db.models.TextField`_.
|
||||||
|
|
||||||
|
Use this for multi-line text content.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.fields import LocalizedTextField
|
||||||
|
|
||||||
|
Follows the same convention as `django.db.models.TextField`_ to store empty strings for "no data" and not NULL.
|
||||||
|
|
||||||
|
|
||||||
|
LocalizedFileField
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Localized version of `django.db.models.FileField`_.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.fields import LocalizedFileField
|
||||||
|
|
||||||
|
def my_directory_path(instance, filename, lang):
|
||||||
|
# file will be uploaded to MEDIA_ROOT/<lang>/<id>_<filename>
|
||||||
|
return '{0}/{0}_{1}'.format(lang, instance.id, filename)
|
||||||
|
|
||||||
|
class MyModel(models.Model):
|
||||||
|
file1 = LocalizedFileField(upload_to='uploads/{lang}/')
|
||||||
|
file2 = LocalizedFileField(upload_to=my_directory_path)
|
||||||
|
|
||||||
|
|
||||||
|
The ``upload_to`` supports the ``{lang}}`` placeholder for string formatting or as function argument (in case if upload_to is a callable).
|
||||||
|
|
||||||
|
In a template, you can access the files for different languages:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
{# For current active language: #}
|
||||||
|
|
||||||
|
{{ model.file.url }} {# output file url #}
|
||||||
|
{{ model.file.name }} {# output file name #}
|
||||||
|
|
||||||
|
{# Or get it in a specific language: #}
|
||||||
|
|
||||||
|
{{ model.file.ro.url }} {# output file url for romanian language #}
|
||||||
|
{{ model.file.ro.name }} {# output file name for romanian language #}
|
||||||
|
|
||||||
|
To get the file instance for the current language:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
model.file.localized()
|
||||||
|
|
||||||
|
|
||||||
|
LocalizedIntegerField
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Localized version of `django.db.models.IntegerField`_.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.fields import LocalizedIntegerField
|
||||||
|
|
||||||
|
Although the underlying PostgreSQL data type for ``LocalizedField`` is hstore (which only stores strings). ``LocalizedIntegerField`` takes care of making sure that input values are integers and casts the values back to integers when querying them.
|
44
docs/source/filtering.rst
Normal file
44
docs/source/filtering.rst
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
Filtering localized content
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
All examples below assume a model declared like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.models import LocalizedModel
|
||||||
|
from localized_fields.fields import LocalizedField
|
||||||
|
|
||||||
|
|
||||||
|
class MyModel(LocalizedModel):
|
||||||
|
title = LocalizedField()
|
||||||
|
|
||||||
|
|
||||||
|
Active language
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from django.utils import translation
|
||||||
|
|
||||||
|
# filter in english
|
||||||
|
translation.activate("en")
|
||||||
|
MyModel.objects.filter(title="test")
|
||||||
|
|
||||||
|
# filter in dutch
|
||||||
|
translation.activate("nl")
|
||||||
|
MyModel.objects.filter(title="test")
|
||||||
|
|
||||||
|
|
||||||
|
Specific language
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
MyModel.objects.filter(title__en="test")
|
||||||
|
MyModel.objects.filter(title__nl="test")
|
||||||
|
|
||||||
|
# do it dynamically, where the language code is a var
|
||||||
|
lang_code = "nl"
|
||||||
|
MyModel.objects.filter(**{"title_%s" % lang_code: "test"})
|
22
docs/source/index.rst
Normal file
22
docs/source/index.rst
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Welcome
|
||||||
|
=======
|
||||||
|
|
||||||
|
``django-localized-fields`` is a Django library that provides fields to store localized content (content in various languages) in a PostgreSQL database. It does this by utilizing the PostgreSQL ``hstore`` type, which is available in Django as ``HStoreField`` since Django 1.10.
|
||||||
|
|
||||||
|
This package requires Python 3.7 or newer, Django 2.0 or newer and PostgreSQL 10 or newer.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Overview
|
||||||
|
|
||||||
|
installation
|
||||||
|
quick_start
|
||||||
|
constraints
|
||||||
|
fields
|
||||||
|
saving
|
||||||
|
querying
|
||||||
|
filtering
|
||||||
|
localized_value
|
||||||
|
django_admin
|
||||||
|
settings
|
||||||
|
releases
|
73
docs/source/installation.rst
Normal file
73
docs/source/installation.rst
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
.. _installation:
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
1. Install the package from PyPi:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ pip install django-localized-fields
|
||||||
|
|
||||||
|
2. Add ``django.contrib.postgres``, ``psqlextra`` and ``localized_fields`` to your ``INSTALLED_APPS``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
...
|
||||||
|
"django.contrib.postgres",
|
||||||
|
"psqlextra",
|
||||||
|
"localized_fields",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
3. Set the database engine to ``psqlextra.backend``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
...
|
||||||
|
"ENGINE": "psqlextra.backend",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Already using a custom back-end? Set ``POSTGRES_EXTRA_DB_BACKEND_BASE`` to your custom back-end. See django-postgres-extra's documentation for more details: `Using a custom database back-end <https://django-postgres-extra.readthedocs.io/db-engine/#using-a-custom-database-back-end>`_.
|
||||||
|
|
||||||
|
4. Set ``LANGUAGES`` and ``LANGUAGE_CODE``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en" # default language
|
||||||
|
LANGUAGES = (
|
||||||
|
("en", "English"), # default language
|
||||||
|
("ar", "Arabic"),
|
||||||
|
("ro", "Romanian"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Make sure that the language specified in ``LANGUAGE_CODE`` is the first language in the ``LANGUAGES`` list. Django and many third party packages assume that the default language is the first one in the list.
|
||||||
|
|
||||||
|
5. Apply migrations to enable the HStore extension:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ python manage.py migrate
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Migrations might fail to be applied if the PostgreSQL user applying the migration is not a super user. Enabling/creating extensions requires superuser permission. Not a superuser? Ask your database administrator to create the ``hstore`` extension on your PostgreSQL server manually using the following statement:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
CREATE EXTENSION IF NOT EXISTS hstore;
|
||||||
|
|
||||||
|
Then, fake apply the migration to tell Django that the migration was applied already:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python manage.py migrate localized_fields --fake
|
79
docs/source/localized_value.rst
Normal file
79
docs/source/localized_value.rst
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
Working with localized values
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
All examples below assume a model declared like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.models import LocalizedModel
|
||||||
|
from localized_fields.fields import LocalizedField
|
||||||
|
|
||||||
|
|
||||||
|
class MyModel(LocalizedModel):
|
||||||
|
title = LocalizedField()
|
||||||
|
|
||||||
|
Localized content is represented by ``localized_fields.value.LocalizedValue``. Which is essentially a dictionary where the key is the language and the value the content in the respective language.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.value import LocalizedValue
|
||||||
|
|
||||||
|
obj = MyModel.objects.first()
|
||||||
|
assert isistance(obj.title, LocalizedValue) # True
|
||||||
|
|
||||||
|
|
||||||
|
With fallback
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
Configure :ref:`LOCALIZED_FIELDS_FALLBACKS <LOCALIZED_FIELDS_FALLBACKS>` to control the fallback behaviour.
|
||||||
|
|
||||||
|
|
||||||
|
Active language
|
||||||
|
***************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# gets content in Arabic, falls back to next language
|
||||||
|
# if not availble
|
||||||
|
translation.activate('ar')
|
||||||
|
obj.title.translate()
|
||||||
|
|
||||||
|
# alternative: cast to string
|
||||||
|
title_ar = str(obj.title)
|
||||||
|
|
||||||
|
|
||||||
|
Specific language
|
||||||
|
*****************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# gets content in Arabic, falls back to next language
|
||||||
|
# if not availble
|
||||||
|
obj.title.translate('ar')
|
||||||
|
|
||||||
|
|
||||||
|
Without fallback
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Specific language
|
||||||
|
*****************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# gets content in Dutch, None if not available
|
||||||
|
# no fallback to secondary languages here!
|
||||||
|
obj.title.nl
|
||||||
|
|
||||||
|
|
||||||
|
Specific language dynamically
|
||||||
|
*****************************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# gets content in Dutch, None if not available
|
||||||
|
# no fallback to secondary languages here!
|
||||||
|
obj.title.get('nl')
|
59
docs/source/querying.rst
Normal file
59
docs/source/querying.rst
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
Querying localized content
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
All examples below assume a model declared like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.models import LocalizedModel
|
||||||
|
from localized_fields.fields import LocalizedField
|
||||||
|
|
||||||
|
|
||||||
|
class MyModel(LocalizedModel):
|
||||||
|
title = LocalizedField()
|
||||||
|
|
||||||
|
|
||||||
|
Active language
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Only need a value in a specific language? Use the ``LocalizedRef`` expression to query a value in the currently active language:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.expressions import LocalizedRef
|
||||||
|
|
||||||
|
MyModel.objects.create(title=dict(en="Hello", nl="Hallo"))
|
||||||
|
|
||||||
|
translation.activate("nl")
|
||||||
|
english_title = (
|
||||||
|
MyModel
|
||||||
|
.objects
|
||||||
|
.annotate(title=LocalizedRef("title"))
|
||||||
|
.values_list("title", flat=True)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
print(english_title) # prints "Hallo"
|
||||||
|
|
||||||
|
|
||||||
|
Specific language
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.expressions import LocalizedRef
|
||||||
|
|
||||||
|
result = (
|
||||||
|
MyModel
|
||||||
|
.objects
|
||||||
|
.values(
|
||||||
|
'title__en',
|
||||||
|
'title__nl',
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
print(result['title__en'])
|
||||||
|
print(result['title__nl'])
|
140
docs/source/quick_start.rst
Normal file
140
docs/source/quick_start.rst
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
.. _django.utils.translation.override: https://docs.djangoproject.com/en/2.2/ref/utils/#django.utils.translation.override
|
||||||
|
.. _django.db.models.TextField: https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.TextField
|
||||||
|
.. _LANGUAGES: https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-LANGUAGE_CODE
|
||||||
|
.. _LANGUAGE_CODE: https://docs.djangoproject.com/en/2.2/ref/settings/#languages
|
||||||
|
|
||||||
|
Quick start
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This assumes you have followed the :ref:`Installation guide <installation>`.
|
||||||
|
|
||||||
|
``django-localized-fields`` provides various model field types to store content in multiple languages. The most basic of them all is ``LocalizedField`` which stores text of arbitrary length (like `django.db.models.TextField`_).
|
||||||
|
|
||||||
|
Declaring a model
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.models import LocalizedModel
|
||||||
|
from localized_fields.fields import LocalizedField
|
||||||
|
|
||||||
|
|
||||||
|
class MyModel(LocalizedModel):
|
||||||
|
title = LocalizedField()
|
||||||
|
|
||||||
|
|
||||||
|
This creates a model with one localized field. Inside the ``LocalizedField``, strings can be stored in multiple languages. There are more fields like this for different data types (integers, images etc). ``LocalizedField`` is the most basic of them all.
|
||||||
|
|
||||||
|
Saving localized content
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
You can now save text in ``MyModel.title`` in all languages you defined in the `LANGUAGES`_ setting. A short example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
newobj = MyModel()
|
||||||
|
newobj.title.en = "Hello"
|
||||||
|
newobj.title.ar = "مرحبا"
|
||||||
|
newobj.title.nl = "Hallo"
|
||||||
|
newobj.save()
|
||||||
|
|
||||||
|
There are various other ways of saving localized content. For example, all fields can be set at once by assigning a ``dict``:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
newobj = MyModel()
|
||||||
|
newobj.title = dict(en="Hello", ar="مرحبا", nl="Hallo")
|
||||||
|
newobj.save()
|
||||||
|
|
||||||
|
This also works when using the ``create`` function:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
newobj = MyModel.objects.create(title=dict(en="Hello", ar="مرحبا", nl="Hallo"))
|
||||||
|
|
||||||
|
Need to set the content dynamically? Use the ``set`` function:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
newobj = MyModel()
|
||||||
|
newobj.title.set("en", "Hello")
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Localized field values (``localized_fields.value.LocalizedValue``) act like dictionaries. In fact, ``LocalizedValue`` extends ``dict``. Anything that works on a ``dict`` works on ``LocalizedValue``.
|
||||||
|
|
||||||
|
Retrieving localized content
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
When querying, the currently active language is taken into account. If there is no active language set, the default language is returned (set by the `LANGUAGE_CODE`_ setting).
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from django.utils import translation
|
||||||
|
|
||||||
|
obj = MyModel.objects.first()
|
||||||
|
|
||||||
|
print(obj.title) # prints "Hello"
|
||||||
|
|
||||||
|
translation.activate("ar")
|
||||||
|
print(obj.title) # prints "مرحبا"
|
||||||
|
str(obj.title) # same as printing, forces translation to active language
|
||||||
|
|
||||||
|
translation.activate("nl")
|
||||||
|
print(obj.title) # prints "Hallo"
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Use `django.utils.translation.override`_ to change the language for just a block of code rather than setting the language globally:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from django.utils import translation
|
||||||
|
|
||||||
|
with translation.override("nl"):
|
||||||
|
print(obj.title) # prints "Hallo"
|
||||||
|
|
||||||
|
|
||||||
|
Fallback
|
||||||
|
********
|
||||||
|
|
||||||
|
If there is no content for the currently active language, a fallback kicks in where the content will be returned in the next language. The fallback order is controlled by the order set in the `LANGUAGES`_ setting.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel.objects.create(dict(en="Hallo", ar="مرحبا"))
|
||||||
|
|
||||||
|
translation.activate("nl")
|
||||||
|
print(obj.title) # prints "مرحبا" because there"s no content in NL
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
Use the :ref:`LOCALIZED_FIELDS_FALLBACKS <LOCALIZED_FIELDS_FALLBACKS>` setting to control the fallback behaviour.
|
||||||
|
|
||||||
|
|
||||||
|
Cast to str
|
||||||
|
***********
|
||||||
|
|
||||||
|
Want to get the value in the currently active language without casting to ``str``? (For null-able fields for example). Use the ``.translate()`` function:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel.objects.create(dict(en="Hallo", ar="مرحبا"))
|
||||||
|
|
||||||
|
str(obj.title) == obj.title.translate() # True
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
``str(..)`` is guarenteed to return a string. If the value is ``None``, ``str(..)`` returns an empty string. ``translate()`` would return ``None``. This is because Python forces the ``__str__`` function to return a string.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel.objects.create(dict(en="Hallo"))
|
||||||
|
|
||||||
|
translation.activate('nl')
|
||||||
|
|
||||||
|
str(obj.title) # ""
|
||||||
|
obj.title.translate() # None
|
17
docs/source/releases.rst
Normal file
17
docs/source/releases.rst
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Releases
|
||||||
|
========
|
||||||
|
|
||||||
|
v6.0
|
||||||
|
----
|
||||||
|
|
||||||
|
Breaking changes
|
||||||
|
****************
|
||||||
|
|
||||||
|
* Removes support for Python 3.6 and earlier.
|
||||||
|
* Removes support for PostgreSQL 9.6 and earlier.
|
||||||
|
* Sets ``LOCALIZED_FIELDS_EXPERIMENTAL`` to ``True`` by default.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
*********
|
||||||
|
|
||||||
|
* Fixes a bug where ``LocalizedIntegerField`` could not be used in ``order_by``.
|
82
docs/source/saving.rst
Normal file
82
docs/source/saving.rst
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
Saving localized content
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
All examples below assume a model declared like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from localized_fields.models import LocalizedModel
|
||||||
|
from localized_fields.fields import LocalizedField
|
||||||
|
|
||||||
|
|
||||||
|
class MyModel(LocalizedModel):
|
||||||
|
title = LocalizedField()
|
||||||
|
|
||||||
|
|
||||||
|
Individual assignment
|
||||||
|
*********************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel()
|
||||||
|
obj.title.en = 'Hello'
|
||||||
|
obj.title.nl = 'Hallo'
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
Individual dynamic assignment
|
||||||
|
*****************************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel()
|
||||||
|
obj.title.set('en', 'Hello')
|
||||||
|
obj.title.set('nl', 'Hallo')
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
|
Multiple assignment
|
||||||
|
*******************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel()
|
||||||
|
obj.title = dict(en='Hello', nl='Hallo')
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
obj = MyModel(title=dict(en='Hello', nl='Hallo'))
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
obj = MyModel.objects.create(title=dict(en='Hello', nl='Hallo'))
|
||||||
|
|
||||||
|
|
||||||
|
Default language assignment
|
||||||
|
***************************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel()
|
||||||
|
obj.title = 'Hello' # assumes value is in default language
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
obj = MyModel(title='Hello') # assumes value is in default language
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
obj = MyModel.objects.create(title='title') # assumes value is in default language
|
||||||
|
|
||||||
|
|
||||||
|
Array assignment
|
||||||
|
****************
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
obj = MyModel()
|
||||||
|
obj.title = ['Hello', 'Hallo'] # order according to LANGUAGES
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
obj = MyModel(title=['Hello', 'Hallo']) # order according to LANGUAGES
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
obj = MyModel.objects.create(title=['Hello', 'Hallo']) # order according to LANGUAGES
|
39
docs/source/settings.rst
Normal file
39
docs/source/settings.rst
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
.. _LANGUAGES: https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-LANGUAGE_CODE
|
||||||
|
.. _LANGUAGE_CODE: https://docs.djangoproject.com/en/2.2/ref/settings/#languages
|
||||||
|
|
||||||
|
Settings
|
||||||
|
========
|
||||||
|
|
||||||
|
.. LOCALIZED_FIELDS_EXPERIMENTAL:
|
||||||
|
|
||||||
|
* ``LOCALIZED_FIELDS_EXPERIMENTAL``
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Disabled in v5.x and earlier. Enabled by default since v6.0.
|
||||||
|
|
||||||
|
When enabled:
|
||||||
|
|
||||||
|
* ``LocalizedField`` will return ``None`` instead of an empty ``LocalizedValue`` if there is no database value.
|
||||||
|
* ``LocalizedField`` lookups will by the currently active language instead of an exact match by dict.
|
||||||
|
|
||||||
|
|
||||||
|
.. _LOCALIZED_FIELDS_FALLBACKS:
|
||||||
|
|
||||||
|
* ``LOCALIZED_FIELDS_FALLBACKS``
|
||||||
|
|
||||||
|
List of language codes which define the order in which fallbacks should happen. If a value is not available in a specific language, we'll try to pick the value in the next language in the list.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If this setting is not configured, the default behaviour is to fall back to the value in the **default language**. It is recommended to configure this setting to get predictible fallback behaviour that suits your use case.
|
||||||
|
|
||||||
|
Use the same language codes as you used for configuring the `LANGUAGES`_ and `LANGUAGE_CODE`_ setting.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
LOCALIZED_FIELDS_FALLBACKS = {
|
||||||
|
"en": ["nl", "ar"], # if trying to get EN, but not available, try NL and then AR
|
||||||
|
"nl": ["en", "ar"], # if trying to get NL, but not available, try EN and then AR
|
||||||
|
"ar": ["en", "nl"], # if trying to get AR, but not available, try EN and then NL
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user