# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['garnett', 'garnett.ext', 'garnett.serializers', 'garnett.templatetags']

package_data = \
{'': ['*'], 'garnett': ['templates/admin/*', 'templates/garnett/*']}

install_requires = \
['django>=3.1.0,<3.2.0', 'langcodes>=2.1,<2.2', 'language-data>=1.0.1,<1.1.0']

setup_kwargs = {
    'name': 'django-garnett',
    'version': '0.4.3',
    'description': 'Simple translatable Django fields',
    'long_description': '# django-garnett\n\nDjango Garnett is a field level translation library that allows you to store strings in multiple languages for fields in Django - with minimal changes to your models and without having to rewrite your code.\n\nWant a demo? https://django-garnett.herokuapp.com/\n\n<a href="https://www.aristotlemetadata.com"\n    style="text-decoration: none">\nMade with <i style="color:#d63384">♥</i> by\n<img src="https://brand.aristotlemetadata.com/images/svgs/thick.svg" height=20> Aristotle Metadata\n</a>\n\nIn summary it allows you to do this:\n\n<table>\n<tr>\n    <td>\n        <tt>models.py</tt>\n    <td>\n        You can do this!\n</tr>\n<tr>\n<td>\nBy changing your models from this...\n\n```python\nclass Greeting(models.model):\n    text = CharField(max_length=150)\n    target = models.CharField()\n    def __str__(self):\n        return f"{self.greeting}, {self.target}"\n\n```\n\nto this...\n\n```python\n# Import garnett\nfrom garnett.fields import Translated\n\nclass Greeting(models.model):\n    # Convert greeting to a translatable field\n    text = Translated(CharField(max_length=150))\n    target = models.CharField()\n    def __str__(self):\n        return f"{self.greeting} {self.target}"\n```\n</td>\n<td>\n\n```python\nfrom garnett.context import set_field_language\ngreeting = Greeting(text="Hello", target="World")\n\nwith set_field_language("en"):\n    greeting.text = "Hello"\nwith set_field_language("fr"):\n    greeting.text = "Bonjour"\n\ngreeting.save()\ngreeting.refresh_from_db()\n\nwith set_field_language("en"):\n    print(greeting.text)\n    print(greeting)\n# >>> "Hello"\n# >>> "Hello World"\n\nwith set_field_language("fr"):\n    print(greeting.text)\n    print(greeting)\n# >>> "Bonjour"\n# >>> "Bonjour World!"\n\nwith set_field_language("en"):\n    print(greeting.text)\n    print(greeting)\n# >>> "Hello"\n# >>> "Hello World"\n    Greeting.objects.filter(title="Hello").exists()\n# >>> True\n    Greeting.objects.filter(title="Bonjour").exists()\n# >>> False\n    Greeting.objects.filter(title__fr="Bonjour").exists()\n# >>> True!!\n\n# Assuming that GARNETT_DEFAULT_TRANSLATABLE_LANGUAGE="en"\n# Or a middleware has set the language context\nprint(greeting.text)\n# >>> Hello\nprint(greeting)\n# >>> Hello World!\n\n```\n\n</td>\n</table>\n\nTested on:\n  - Django 3.1+\n  - Postgres, SQLite, MariaDB\n  - Python 3.7+\n\nTested with the following libraries:\n  - [Django Rest Framework](https://www.django-rest-framework.org/)\n  - [django-reversion](https://github.com/etianen/django-reversion) & [django-reversion-compare](https://github.com/jedie/django-reversion-compare)\n  - [django-filters](https://github.com/carltongibson/django-filter)\n\nPros:\n* Battletested in production - [Aristotle Metadata](https://www.aristotlemetadata.com) built, support and uses this library for 2 separate products, served to government and enterprise clients!\n* Fetching all translations for a model requires a single query\n* Translations are stored in a single database field with the model\n* All translations act like a regular field, so `Model.field_name = "some string"` and `print(Model.field_name)` work as you would expect\n* Includes a configurable middleware that can set the current language context based on users cookies, query string or HTTP headers\n* Works nicely with Django Rest Framework - translatable fields can be set as strings or as json dictionaries\n* Works nicely with Django `F()` and `Q()` objects within the ORM - and when it doesn\'t we have a language aware `LangF()` replacement you can use.\n\nCons:\n* You need to alter the models, so you can\'t make third-party libraries translatable.\n* It doesn\'t work on `queryset.values_list` - but we have a workaround below.\n\n## Why write a new Django field translator?\n\nA few reasons:\n* Most existing django field translation libraries are static, and add separate database columns or extra tables per translation.\n* Other libraries may not be compatible with common django libraries, like django-rest-framework.\n* We had a huge codebase that we wanted to upgrade to be multilingual - so we needed a library that could be added in without requiring a rewriting every access to fields on models, and only required minor tweaks.\n\nNote: Field language is different to the django display language. Django can be set up to translate your pages based on the users browser and serve them with a user interface in their preferred language.\n\nGarnett *does not* use the browser language by design - a user with a French browser may want the user interface in French, but want to see content in English or French based on their needs.\n\n\n# How to install\n\n1. Add `django-garnett` to your dependencies. eg. `pip install django-garnett`\n2. Convert your chosen field using the `Translated` function\n\n    * For example: `title = fields.Translated(models.CharField(*args))`\n\n3. Add `GARNETT_TRANSLATABLE_LANGUAGES` (a callable or list of [language codes][term-language-code]) to your django settings.\n    > Note: At the moment there is no way to allow "a user to enter any language".\n4. Add `GARNETT_DEFAULT_TRANSLATABLE_LANGUAGE` (a callable or single [language code][term-language-code]) to your settings.\n5. Re-run `django makemigrations` & `django migrate` for any apps you\'ve updated.\n6. Thats mostly it.\n\nYou can also add a few optional value adds:\n\n7. (Optional) Add a garnett middleware to take care of field language handling:\n\n    * You want to capture the garnett language in a context variable available in views use: `garnett.middleware.TranslationContextMiddleware`\n\n    * You want to capture the garnett language in a context variable available in views, and want to raise a 404 if the user requests an invalid language use: `garnett.middleware.TranslationContextNotFoundMiddleware`\n\n    * (Future addition) You want to capture the garnett language in a context variable available in views, and want to redirect to the default language if the user requests an invalid language use: `garnett.middleware.TranslationContextRedirectDefaultMiddleware`\n\n    * If you want to cache the current language in session storage use `garnett.middleware.TranslationCacheMiddleware` after one of the above middleware (this is useful with the session selector mentioned below)\n\n8. (Optional) Add the `garnett` app to your `INSTALLED_APPS` to use garnett\'s template_tags. If this is installed before `django.contrib.admin` it also include a language switcher in the Django Admin Site.\n\n9. (Optional) Add a template processor:\n\n    * Install `garnett.context_processors.languages` this will add `garnett_languages` (a list of available `Language`s) and `garnett_current_language` (the currently selected language).\n\n10. (Optional) Add a custom translation fallback:\n\n    By default, if a language isn\'t available for a field, Garnett will show a mesage like:\n    > No translation of this field available in English\n\n    You can override this either by creating a custom fallback method:\n    ```\n    Translated(CharField(max_length=150), fallback=my_fallback_method))\n    ```\n    Where `my_fallback_method` takes a dictionary of language codes and corresponding strings, and returns the necessary text.\n\n    Additionally, you can customise how django outputs text in templates by creating a new\n    `TranslationStr` class, and overriding the [`__html__` method][dunder-html].\n\n\n\n## `Language` vs language\n\nDjango Garnett uses the python `langcodes` library to determine more information about the languages being used - including the full name and local name of the language being used. This is stored as a `Language` object.\n\n\n## Django Settings options:\n\n* `GARNETT_DEFAULT_TRANSLATABLE_LANGUAGE`\n    * Stores the default language to be used for reading and writing fields if no language is set in a context manager or by a request.\n    * By default it is \'en-AU\' the [language code][term-language-code] for \'Strayan, the native tongue of inhabitants of \'Straya (or more commonly known as Australia). \n    * This can also be a callable that returns list of language codes. Combined with storing user settings in something like (django-solo)[https://github.com/lazybird/django-solo] users can dynamically add or change their language settings.\n    * default: `\'en-AU\'`\n* `GARNETT_TRANSLATABLE_LANGUAGES`:\n    * Stores a list of [language codes][term-language-code] that users can use to save against TranslatableFields.\n    * This can also be a callable that returns list of language codes. Combined with storing user settings in something like (django-solo)[https://github.com/lazybird/django-solo] users can dynamically add or change their language settings.\n    * default `[GARNETT_DEFAULT_TRANSLATABLE_LANGUAGE]`\n* `GARNETT_REQUEST_LANGUAGE_SELECTORS`:\n    * A list of string modules that determines the order of options used to determine the language selected by the user. The first selector found is used for the language for the request, if none are found the DEFAULT_LANGUAGE is used. These can any of the following in any order:\n        * `garnett.selector.query`: Checks the `GARNETT_QUERY_PARAMETER_NAME` for a language to display\n        * `garnett.selector.cookie`: Checks for a cookie called `GARNETT_LANGUAGE_CODE` for a language to display.\n            Note: you cannot change this cookie name.\n        * `garnett.selector.session`: Checks for a session key `GARNETT_LANGUAGE_CODE` for a language to display.\n            Note: you cannot change this key name.\n        * `garnett.selector.header`: Checks for a HTTP Header called `X-Garnett-Language-Code` for a language to display.\n            Note: you cannot change this Header name.\n        * `garnett.selector.browser`: Uses Django\'s `get_language` function to get the users browser/UI language [as determined by Django][django-how].\n    * For example, if you only want to check headers and cookies in that order, set this to `[\'garnett.selectors.header\', \'garnett.selectors.cookie\']`.\n    * default: `[\'garnett.selectors.header\', \'garnett.selectors.query\', \'garnett.selectors.cookie\']`\n* `GARNETT_QUERY_PARAMETER_NAME`:\n    * The query parameter used to determine the language requested by a user during a HTTP request.\n    * default: `glang`\n\nAdvanced Settings (you probably don\'t need to adjust these)\n* `GARNETT_TRANSLATABLE_FIELDS_PROPERTY_NAME`:\n    * Garnett adds a property to all models that returns a list of all TranslatableFields. By default, this is \'translatable_fields\', but you can customise it here if you want.\n    * default: `translatable_fields`\n* `GARNETT_TRANSLATIONS_PROPERTY_NAME`:\n    * Garnett adds a property to all models that returns a dictionary of all translations of all TranslatableFields. By default, this is \'translations\', but you can customise it here if you want.\n    * default: `translations`\n\n# Using Garnett\n\nIf you did everything above correctly, garnett should for the most part "just work".\n\n## Switching the active language\n\nGarnett comes with a handy context manager that can be used to specify the current language. In any place where you want to manually control the current language, wrap your code in `set_field_language` and garnett will correctly store the language. This can be nested, or you can change the language for a context multiple times before saving.\n\n```python\nfrom garnett.context import set_field_language\ngreeting = Greeting(text="Hello", target="World")\n\nwith set_field_language("en"):\n    greeting.text = "Hello"\nwith set_field_language("fr"):\n    greeting.text = "Bonjour"\n\ngreeting.save()\n```\n\n## Using Garnett with `values_list`\n\nThis is one of the areas that garnett _doesn\'t_ work immediately, but there is a solution.\n\nIn the places you are using values lists, wrap any translated field in an L-expression and the values list will return correctly. For example:\n\n```python\nfrom garnett.expressions import L\nBook.objects.values_list(L("title"))\n```\n\n## Using Garnett with Django-Rest-Framework\n\nAs `TranslationField`s are based on JSONField, by default Django-Rest-Framework renders these as a JSONField, which may not be ideal.\n\nYou can get around this by using the `TranslatableSerializerMixin` _as the first mixin_, which adds the necessary hooks to your serializer. This will mean class changes, but you won\'t need to update or override every field.\n\nFor example:\n\n```\nfrom rest_framework import serializers\nfrom library_app import models\nfrom garnett.ext.drf import TranslatableSerializerMixin\n\n\nclass BookSerializer(TranslatableSerializerMixin, serializers.ModelSerializer):\n    class Meta:\n        model = models.Book\n        fields = "__all__"\n```\n\nThis will allow you to set the value for a translatable as either a string for the active langauge, or by setting a dictionary that has all languages to be saved (note: this will override the existing language set).\nFor example:\n\nTo override just the active language: \n\n    curl -X PATCH ... -d "{  \\"title\\": \\"Hello\\"}"\n\nTo specifically override a single language (for example, Klingon): \n\n    curl -X PATCH ...  -H  "X-Garnett-Language-Code: tlh" -d "{  \\"title\\": \\"Hello\\"}"\n\nTo override all languages:\n\n    curl -X PATCH ... -d "{  \\"title\\": {\\"en\\": \\"Hello\\", \\"fr\\": \\"Bonjour\\"}}"\n\n## Using Garnett with django-reversion and django-reversion-compare\n\nThere are a few minor tweaks required to get Garnett to operate properly with\ndjango-reversion and django-reversion-compare based on how they serialise and display data.\n\nThis is because Garnett does not use the same \'field.attname\' and \'field.name\' which means serialization in Django will not work correctly.\n\nTo get django-reversion to work you will need to use a translation-aware serialiser and apply a patch to ensure that django-reversion-compare can show the right information.\n\nAn example json translation-aware serializer is included with Garnett and this can be applied with the following two settings in `settings.py`:\n\n```\n# In settings.py\n\nGARNETT_PATCH_REVERSION_COMPARE = True\nSERIALIZATION_MODULES = {"json": "garnett.serializers.json"}\n```\n\nTranslatedFields will list the history and changes in json, but it does do comparisons correctly.\n\n## Why call it Garnett?\n\n* Libraries need a good name.\n* Searching for "Famous Translators" will tell you about [Constance Garnett](https://en.wikipedia.org/wiki/Constance_Garnett).\n* Searching for "Django Garnett" showed there was no python library with this name.\n* It did however talk about [Garnet Clark](https://en.wikipedia.org/wiki/Garnet_Clark) (also spelled Garnett), a jazz pianist who played with Django Reinhart - the namesake of the Django Web Framework.\n* Voila - a nice name\n\n## Warnings\n\n* `contains` acts like `icontains` on SQLite only when doing a contains query\n it does a case insensitive search. I don\'t know why - https://www.youtube.com/watch?v=PgGNWRtceag\n* Due to how django sets admin form fields you will not get the admin specific widgets like\n  `AdminTextAreaWidget` on translated fields in the django admin site by default. They can however\n  be specified explicitly on the corresponding admin model form.\n\n## Want to help maintain this library?\n\nThere is a `/dev/` directory with a docker-compose stack you can ues to bring up a database and clean development environment.\n\n## Want other options?\n\nThere are a few good options for adding translatable strings to Django that may meet other use cases. We\'ve included a few other options here, their strengths and why we didn\'t go with them.\n\n* [django-modeltranslation](https://github.com/deschler/django-modeltranslation)\n  \n   **Pros:** this library lets you apply translations to external apps, without altering their models.\n   \n   **Cons:** each translation adds an extra column, which means languages are specified in code, and can\'t be altered by users later.\n* [django-translated-fields](https://github.com/matthiask/django-translated-fields)\n  \n   **Pros:** uses a great context processor for switching lanugages (which is where we got the idea for ours).\n   \n   **Cons:** Languages are specified in django-settings, and can\'t be altered by users later.\n* [django-parler](https://github.com/django-parler/django-parler)\n  \n   **Pros:** Django admin site support.\n   \n   **Cons:** Languages are stored in a separate table and can\'t be altered by users later. Translated fields are specified in model meta, away from the fields definition which makes complex lookups harder.\n\n\n\n[term-language-code]: https://docs.djangoproject.com/en/3.1/topics/i18n/#term-language-code\n[django-how]: https://docs.djangoproject.com/en/3.1/topics/i18n/translation/#how-django-discovers-language-preference\n[dunder-html]: https://code.djangoproject.com/ticket/7261',
    'author': 'Aristotle Metadata Enterprises',
    'author_email': None,
    'maintainer': None,
    'maintainer_email': None,
    'url': None,
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
