diff --git a/README.rst b/README.rst index cb6afa8..827d8c6 100644 --- a/README.rst +++ b/README.rst @@ -348,6 +348,7 @@ 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 diff --git a/localized_fields/__init__.py b/localized_fields/__init__.py index e69de29..45ff2ec 100644 --- a/localized_fields/__init__.py +++ b/localized_fields/__init__.py @@ -0,0 +1 @@ +default_app_config = 'localized_fields.apps.LocalizedFieldsConfig' diff --git a/localized_fields/apps.py b/localized_fields/apps.py index f394096..7dec5df 100644 --- a/localized_fields/apps.py +++ b/localized_fields/apps.py @@ -1,5 +1,21 @@ +import inspect + from django.apps import AppConfig +from django.conf import settings + +from . import lookups +from .fields import LocalizedField +from .lookups import LocalizedLookupMixin class LocalizedFieldsConfig(AppConfig): name = 'localized_fields' + + def ready(self): + if getattr(settings, 'LOCALIZED_FIELDS_EXPERIMENTAL', False): + for _, clazz in inspect.getmembers(lookups): + if not inspect.isclass(clazz) or clazz is LocalizedLookupMixin: + continue + + if issubclass(clazz, LocalizedLookupMixin): + LocalizedField.register_lookup(clazz) diff --git a/localized_fields/lookups.py b/localized_fields/lookups.py new file mode 100644 index 0000000..2875976 --- /dev/null +++ b/localized_fields/lookups.py @@ -0,0 +1,80 @@ +from django.conf import settings +from django.contrib.postgres.fields.hstore import KeyTransform +from django.contrib.postgres.lookups import (SearchLookup, TrigramSimilar, + Unaccent) +from django.db.models.expressions import Col +from django.db.models.lookups import (Contains, EndsWith, Exact, IContains, + IEndsWith, IExact, In, IRegex, IsNull, + IStartsWith, Regex, StartsWith) +from django.utils import translation + + +class LocalizedLookupMixin(): + def process_lhs(self, qn, connection): + if isinstance(self.lhs, Col): + language = translation.get_language() or settings.LANGUAGE_CODE + self.lhs = KeyTransform(language, self.lhs) + return super().process_lhs(qn, connection) + + def get_prep_lookup(self): + return str(self.rhs) + + +class LocalizedSearchLookup(LocalizedLookupMixin, SearchLookup): + pass + + +class LocalizedUnaccent(LocalizedLookupMixin, Unaccent): + pass + + +class LocalizedTrigramSimilair(LocalizedLookupMixin, TrigramSimilar): + pass + + +class LocalizedExact(LocalizedLookupMixin, Exact): + pass + + +class LocalizedIExact(LocalizedLookupMixin, IExact): + pass + + +class LocalizedIn(LocalizedLookupMixin, In): + pass + + +class LocalizedContains(LocalizedLookupMixin, Contains): + pass + + +class LocalizedIContains(LocalizedLookupMixin, IContains): + pass + + +class LocalizedStartsWith(LocalizedLookupMixin, StartsWith): + pass + + +class LocalizedIStartsWith(LocalizedLookupMixin, IStartsWith): + pass + + +class LocalizedEndsWith(LocalizedLookupMixin, EndsWith): + pass + + +class LocalizedIEndsWith(LocalizedLookupMixin, IEndsWith): + pass + + +class LocalizedIsNullWith(LocalizedLookupMixin, IsNull): + pass + + +class LocalizedRegexWith(LocalizedLookupMixin, Regex): + pass + + +class LocalizedIRegexWith(LocalizedLookupMixin, IRegex): + pass diff --git a/tests/test_lookups.py b/tests/test_lookups.py new file mode 100644 index 0000000..056f6b4 --- /dev/null +++ b/tests/test_lookups.py @@ -0,0 +1,51 @@ +from django.apps import apps +from django.conf import settings +from django.test import TestCase, override_settings +from django.utils import translation + +from localized_fields.fields import LocalizedField +from localized_fields.value import LocalizedValue + +from .fake_model import get_fake_model + + +@override_settings(LOCALIZED_FIELDS_EXPERIMENTAL=True) +class LocalizedLookupsTestCase(TestCase): + """Tests whether localized lookups properly work with.""" + TestModel1 = None + + @classmethod + def setUpClass(cls): + """Creates the test model in the database.""" + + super(LocalizedLookupsTestCase, cls).setUpClass() + + # reload app as setting has changed + config = apps.get_app_config('localized_fields') + config.ready() + + cls.TestModel = get_fake_model( + { + 'text': LocalizedField(), + } + ) + + def test_localized_lookup(self): + """Tests whether localized lookup properly works.""" + + self.TestModel.objects.create( + text=LocalizedValue(dict(en='text_en', ro='text_ro', nl='text_nl')), + ) + + # assert that it properly lookups the currently active language + for lang_code, _ in settings.LANGUAGES: + translation.activate(lang_code) + assert self.TestModel.objects.filter(text='text_' + lang_code).exists() + + # ensure that the default language is used in case no + # language is active at all + translation.deactivate_all() + assert self.TestModel.objects.filter(text='text_en').exists() + + # ensure that hstore lookups still work + assert self.TestModel.objects.filter(text__ro='text_ro').exists()