mirror of
https://github.com/SectorLabs/django-localized-fields.git
synced 2025-04-25 11:42:54 +03:00
Replace README with Markdown version
This commit is contained in:
parent
3bf7926c57
commit
e6ce2da161
59
README.md
Normal file
59
README.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
| | | |
|
||||||
|
|--------------------|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| :white_check_mark: | **Tests** | [](https://circleci.com/gh/SectorLabs/django-localized-fields/tree/master) |
|
||||||
|
| :memo: | **License** | [](http://doge.mit-license.org) |
|
||||||
|
| :package: | **PyPi** | [](https://pypi.python.org/pypi/django-localized-fields) |
|
||||||
|
| <img src="https://icon-library.net/images/django-icon/django-icon-0.jpg" width="22px" height="22px" align="center" /> | **Django Versions** | 2.0, 2.1, 2.2 |
|
||||||
|
| <img src="http://www.iconarchive.com/download/i73027/cornmanthe3rd/plex/Other-python.ico" width="22px" height="22px" align="center" /> | **Python Versions** | 3.7, 3.8 |
|
||||||
|
| :book: | **Documentation** | [Read The Docs](https://django-localized-fields.readthedocs.io) |
|
||||||
|
| :warning: | **Upgrade** | [Upgrade fom v5.x](https://django-localized-fields.readthedocs.io/releases)
|
||||||
|
| :checkered_flag: | **Installation** | [Installation Guide](https://django-localized-fields.readthedocs.io/#installation) |
|
||||||
|
|
||||||
|
`django-localized-fields` is an implementation of a field class for Django models that allows the field's value to be set in multiple languages. It does this by utilizing the ``hstore`` type (PostgreSQL specific), which is available as `models.HStoreField` since Django 1.10.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
:warning: **This README is for v6 which is currently under development. See the `v5.x` branch for v5.x.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Working with the code
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
* PostgreSQL 10 or newer.
|
||||||
|
* Django 2.0 or newer.
|
||||||
|
* Python 3.7 or newer.
|
||||||
|
|
||||||
|
### Getting started
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
λ git clone https://github.com/SectorLabs/django-localized-fields.git
|
||||||
|
|
||||||
|
2. Create a virtual environment:
|
||||||
|
|
||||||
|
λ cd django-localized-fields
|
||||||
|
λ virtualenv env
|
||||||
|
λ source env/bin/activate
|
||||||
|
|
||||||
|
3. Create a postgres user for use in tests (skip if your default user is a postgres superuser):
|
||||||
|
|
||||||
|
λ createuser --superuser psqlextra --pwprompt
|
||||||
|
λ export DATABASE_URL=postgres://psqlextra:<password>@localhost/psqlextra
|
||||||
|
|
||||||
|
Hint: if you're using virtualenvwrapper, you might find it beneficial to put
|
||||||
|
the ``export`` line in ``$VIRTUAL_ENV/bin/postactivate`` so that it's always
|
||||||
|
available when using this virtualenv.
|
||||||
|
|
||||||
|
4. Install the development/test dependencies:
|
||||||
|
|
||||||
|
λ pip install -r requirements/test.txt
|
||||||
|
λ pip install -r requirements/analysis.txt
|
||||||
|
|
||||||
|
5. Run the tests:
|
||||||
|
|
||||||
|
λ tox
|
||||||
|
|
||||||
|
7. Auto-format code, sort imports and auto-fix linting errors:
|
||||||
|
|
||||||
|
λ python setup.py fix
|
408
README.rst
408
README.rst
@ -1,408 +0,0 @@
|
|||||||
django-localized-fields
|
|
||||||
=======================
|
|
||||||
|
|
||||||
.. image:: https://circleci.com/gh/SectorLabs/django-localized-fields.svg?style=svg
|
|
||||||
:target: https://circleci.com/gh/SectorLabs/django-localized-fields
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/github/license/SectorLabs/django-localized-fields.svg
|
|
||||||
:target: https://github.com/SectorLabs/django-localized-fields/blob/master/LICENSE.md
|
|
||||||
|
|
||||||
.. image:: https://badge.fury.io/py/django-localized-fields.svg
|
|
||||||
:target: https://pypi.python.org/pypi/django-localized-fields
|
|
||||||
|
|
||||||
``django-localized-fields`` is an implementation of a field class for Django models that allows the field's value to be set in multiple languages. It does this by utilizing the ``hstore`` type (PostgreSQL specific), which is available as ``models.HStoreField`` since Django 1.10.
|
|
||||||
|
|
||||||
This package requires Python 3.7 or newer, Django 2.0 or newer and PostgreSQL 10 or newer.
|
|
||||||
|
|
||||||
Contributors
|
|
||||||
------------
|
|
||||||
|
|
||||||
* `seroy <https://github.com/seroy/>`_
|
|
||||||
* `umazalakain <https://github.com/umazalakain/>`_
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
1. Install the package from PyPi:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
$ pip install django-localized-fields
|
|
||||||
|
|
||||||
2. Add ``localized_fields`` and ``django.contrib.postgres`` to your ``INSTALLED_APPS``:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
...
|
|
||||||
|
|
||||||
'django.contrib.postgres',
|
|
||||||
'localized_fields.apps.LocalizedFieldsConfig'
|
|
||||||
]
|
|
||||||
|
|
||||||
3. Set the database engine to ``psqlextra.backend``:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
...
|
|
||||||
'ENGINE': 'psqlextra.backend'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
3. Set ``LANGUAGES` and `LANGUAGE_CODE`` in your settings:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en' # default language
|
|
||||||
LANGUAGES = (
|
|
||||||
('en', 'English'),
|
|
||||||
('nl', 'Dutch'),
|
|
||||||
('ro', 'Romanian')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
4. Apply migrations to enable the HStore extension:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
python manage.py migrate
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Preparation
|
|
||||||
^^^^^^^^^^^
|
|
||||||
Declare fields on your model as ``LocalizedField``:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from localized_fields.fields import LocalizedField
|
|
||||||
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField()
|
|
||||||
|
|
||||||
``django-localized-fields`` integrates with Django's i18n system, in order for certain languages to be available you have to correctly configure the ``LANGUAGES`` and ``LANGUAGE_CODE`` settings:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en' # default language
|
|
||||||
LANGUAGES = (
|
|
||||||
('en', 'English'),
|
|
||||||
('nl', 'Dutch'),
|
|
||||||
('ro', 'Romanian')
|
|
||||||
)
|
|
||||||
|
|
||||||
All the ``LocalizedField`` you define now will be available in the configured languages.
|
|
||||||
|
|
||||||
Basic usage
|
|
||||||
^^^^^^^^^^^
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
new = MyModel()
|
|
||||||
new.title.en = 'english title'
|
|
||||||
new.title.nl = 'dutch title'
|
|
||||||
new.title.ro = 'romanian title'
|
|
||||||
new.save()
|
|
||||||
|
|
||||||
By changing the active language you can control which language is presented:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from django.utils import translation
|
|
||||||
|
|
||||||
translation.activate('nl')
|
|
||||||
print(new.title) # prints 'dutch title'
|
|
||||||
|
|
||||||
translation.activate('en')
|
|
||||||
print(new.title) # prints 'english title'
|
|
||||||
|
|
||||||
Or get it in a specific language:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
print(new.title.get('en')) # prints 'english title'
|
|
||||||
print(new.title.get('ro')) # prints 'romanian title'
|
|
||||||
print(new.title.get()) # whatever language is the primary one
|
|
||||||
print(new.title.get('ar', 'haha')) # prints 'haha' if there is no value in arabic
|
|
||||||
|
|
||||||
You can also explicitly set a value in a certain language:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
new.title.set('en', 'other english title')
|
|
||||||
new.title.set('nl', 'other dutch title')
|
|
||||||
|
|
||||||
new.title.ro = 'other romanian title'
|
|
||||||
|
|
||||||
Constraints
|
|
||||||
^^^^^^^^^^^
|
|
||||||
|
|
||||||
**Required/Optional**
|
|
||||||
|
|
||||||
Constraints are enforced on a database level.
|
|
||||||
|
|
||||||
* Optional filling
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField(blank=True, null=True, required=False)
|
|
||||||
|
|
||||||
* Make translation required for any language
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField(blank=False, null=False, required=False)
|
|
||||||
|
|
||||||
* Make translation required for specific languages
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField(blank=False, null=False, required=['en', 'ro'])
|
|
||||||
|
|
||||||
* Make translation required for all languages
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField(blank=False, null=False, required=True)
|
|
||||||
|
|
||||||
* By default the primary language **required** and the others optional:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField()
|
|
||||||
|
|
||||||
**Uniqueness**
|
|
||||||
|
|
||||||
By default the values stored in a ``LocalizedField`` are *not unique*. You can enforce uniqueness for certain languages. This uniqueness constraint is enforced on a database level using a ``UNIQUE 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 ore more languages **together** (similar to Django's ``unique_together``):
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField(uniqueness=[('en', 'ro')])
|
|
||||||
|
|
||||||
* 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())])
|
|
||||||
|
|
||||||
|
|
||||||
Other fields
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
Besides ``LocalizedField``, there's also:
|
|
||||||
|
|
||||||
* ``LocalizedUniqueSlugField``
|
|
||||||
Successor of ``LocalizedAutoSlugField`` that fixes concurrency issues and enforces
|
|
||||||
uniqueness of slugs on a database level. Usage is the exact same:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from localized_fields.models import LocalizedModel
|
|
||||||
from localized_fields.fields import LocalizedField, LocalizedUniqueSlugField
|
|
||||||
|
|
||||||
class MyModel(LocalizedModel):
|
|
||||||
title = LocalizedField()
|
|
||||||
slug = LocalizedUniqueSlugField(populate_from='title')
|
|
||||||
|
|
||||||
``populate_from`` can be:
|
|
||||||
|
|
||||||
- The name of a field.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
slug = LocalizedUniqueSlugField(populate_from='name', include_time=True)
|
|
||||||
|
|
||||||
- A callable.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def generate_slug(instance):
|
|
||||||
return instance.title
|
|
||||||
|
|
||||||
slug = LocalizedUniqueSlugField(populate_from=generate_slug, include_time=True)
|
|
||||||
|
|
||||||
- A tuple of names of fields.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
slug = LocalizedUniqueSlugField(populate_from=('name', 'beer') include_time=True)
|
|
||||||
|
|
||||||
By setting the option ``include_time=True``
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
slug = LocalizedUniqueSlugField(populate_from='title', include_time=True)
|
|
||||||
|
|
||||||
You can instruct the field to include a part of the current time into
|
|
||||||
the resulting slug. This is useful if you're running into a lot of collisions.
|
|
||||||
|
|
||||||
* ``LocalizedBleachField``
|
|
||||||
Automatically bleaches the content of the field.
|
|
||||||
|
|
||||||
* django-bleach
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from localized_fields.fields import LocalizedField, LocalizedBleachField
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedField()
|
|
||||||
description = LocalizedBleachField()
|
|
||||||
|
|
||||||
* ``LocalizedIntegerField``
|
|
||||||
This is an experimental field type introduced in version 5.0 and is subject to change. It also has
|
|
||||||
some pretty major downsides due to the fact that values are stored as strings and are converted
|
|
||||||
back and forth.
|
|
||||||
|
|
||||||
Allows storing integers in multiple languages. This works exactly like ``LocalizedField`` except that
|
|
||||||
all values must be integers. Do note that values are stored as strings in your database because
|
|
||||||
the backing field type is ``hstore``, which only allows storing strings. The ``LocalizedIntegerField``
|
|
||||||
takes care of ensuring that all values are integers and converts the stored strings back to integers
|
|
||||||
when retrieving them from the database. Do not expect to be able to do queries such as:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
MyModel.objects.filter(score__en__gt=1)
|
|
||||||
|
|
||||||
|
|
||||||
* ``LocalizedCharField`` and ``LocalizedTextField``
|
|
||||||
These fields following the Django convention for string-based fields use the empty string as value for “no data”, not NULL.
|
|
||||||
``LocalizedCharField`` uses ``TextInput`` (``<input type="text">``) widget for render.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from localized_fields.fields import LocalizedCharField, LocalizedTextField
|
|
||||||
|
|
||||||
class MyModel(models.Model):
|
|
||||||
title = LocalizedCharField()
|
|
||||||
description = LocalizedTextField()
|
|
||||||
|
|
||||||
* ``LocalizedFileField``
|
|
||||||
A file-upload field
|
|
||||||
|
|
||||||
Parameter ``upload_to`` supports ``lang`` parameter for string formatting or as function argument (in case if ``upload_to`` is callable).
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
.. 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)
|
|
||||||
|
|
||||||
In template you can access to file attributes:
|
|
||||||
|
|
||||||
.. code-block:: django
|
|
||||||
|
|
||||||
{# 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 access to file instance for current active language use ``localized`` method:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
model.file.localized()
|
|
||||||
|
|
||||||
Experimental feature
|
|
||||||
^^^^^^^^^^^^^^^^^^^^
|
|
||||||
Enables the following experimental features:
|
|
||||||
* ``LocalizedField`` will return ``None`` instead of an empty ``LocalizedValue`` if there is no database value.
|
|
||||||
* ``LocalizedField`` lookups will lookup by currently active language instead of HStoreField
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
LOCALIZED_FIELDS_EXPERIMENTAL = True
|
|
||||||
|
|
||||||
|
|
||||||
Django Admin Integration
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
To enable widgets in the admin, you need to inherit 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:: ./images/admin-widget.png
|
|
||||||
:alt: The appearance of admin widget
|
|
||||||
|
|
||||||
|
|
||||||
Frequently asked questions (FAQ)
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
1. Does this package work with Python 2?
|
|
||||||
|
|
||||||
No. Only Python 3.7 or newer is supported. We're using type hints. These do not work well under older versions of Python.
|
|
||||||
|
|
||||||
2. With what Django versions does this package work?
|
|
||||||
|
|
||||||
Only Django 2.x or newer is supported. This is because we rely on Django's ``HStoreField`` and template-based widget rendering.
|
|
||||||
|
|
||||||
3. Does this package come with support for Django Admin?
|
|
||||||
|
|
||||||
Yes. Our custom fields come with a special form that will automatically be used in Django Admin if the field is of ``LocalizedField``.
|
|
||||||
|
|
||||||
4. Why should I pick this over any of the other translation packages out there?
|
|
||||||
|
|
||||||
You should pick whatever you feel comfortable with. This package stores translations in your database without having to have translation tables. It however only works on PostgreSQL.
|
|
||||||
|
|
||||||
5. I am using PostgreSQL <10, can I use this?
|
|
||||||
|
|
||||||
No. The ``hstore`` data type was introduced in PostgreSQL 9.6.
|
|
||||||
|
|
||||||
6. I am using this package. Can I give you some beer?
|
|
||||||
|
|
||||||
Yes! If you're ever in the area of Cluj-Napoca, Romania, swing by :)
|
|
2
setup.py
2
setup.py
@ -29,7 +29,7 @@ def create_command(text, commands):
|
|||||||
|
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "README.rst"), encoding="utf-8"
|
os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8"
|
||||||
) as readme:
|
) as readme:
|
||||||
README = readme.read()
|
README = readme.read()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user