Skip to content

Commit 9c4da75

Browse files
author
Oliver Sauder
committed
Add support for localized query look ups
1 parent 88e2d29 commit 9c4da75

File tree

5 files changed

+147
-0
lines changed

5 files changed

+147
-0
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ Experimental feature
348348
^^^^^^^^^^^^^^^^^^^^
349349
Enables the following experimental features:
350350
* ``LocalizedField`` will return ``None`` instead of an empty ``LocalizedValue`` if there is no database value.
351+
* ``LocalizedField`` lookups will lookup by currently active language instead of HStoreField
351352

352353
.. code-block:: python
353354

localized_fields/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
default_app_config = 'localized_fields.apps.LocalizedFieldsConfig'

localized_fields/apps.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1+
import inspect
2+
13
from django.apps import AppConfig
4+
from django.conf import settings
5+
6+
from . import lookups
7+
from .fields import LocalizedField
8+
from .lookups import LocalizedLookupMixin
29

310

411
class LocalizedFieldsConfig(AppConfig):
512
name = 'localized_fields'
13+
14+
def ready(self):
15+
if getattr(settings, 'LOCALIZED_FIELDS_EXPERIMENTAL', False):
16+
for _, clazz in inspect.getmembers(lookups):
17+
if not inspect.isclass(clazz) or clazz is LocalizedLookupMixin:
18+
continue
19+
20+
if issubclass(clazz, LocalizedLookupMixin):
21+
LocalizedField.register_lookup(clazz)

localized_fields/lookups.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from django.conf import settings
2+
from django.contrib.postgres.fields.hstore import KeyTransform
3+
from django.contrib.postgres.lookups import (SearchLookup, TrigramSimilar,
4+
Unaccent,)
5+
from django.db.models.lookups import (Contains, EndsWith, Exact, IContains,
6+
IEndsWith, IExact, In, IRegex, IsNull,
7+
IStartsWith, Regex, StartsWith,)
8+
from django.utils import translation
9+
10+
11+
class LocalizedLookupMixin():
12+
def process_lhs(self, qn, connection):
13+
language = translation.get_language() or settings.LANGUAGE_CODE
14+
self.lhs = KeyTransform(language, self.lhs)
15+
return super().process_lhs(qn, connection)
16+
17+
def get_prep_lookup(self):
18+
return str(self.rhs)
19+
20+
21+
class LocalizedSearchLookup(LocalizedLookupMixin, SearchLookup):
22+
pass
23+
24+
25+
class LocalizedUnaccent(LocalizedLookupMixin, Unaccent):
26+
pass
27+
28+
29+
class LocalizedTrigramSimilair(LocalizedLookupMixin, TrigramSimilar):
30+
pass
31+
32+
33+
class LocalizedExact(LocalizedLookupMixin, Exact):
34+
pass
35+
36+
37+
class LocalizedIExact(LocalizedLookupMixin, IExact):
38+
pass
39+
40+
41+
class LocalizedIn(LocalizedLookupMixin, In):
42+
pass
43+
44+
45+
class LocalizedContains(LocalizedLookupMixin, Contains):
46+
pass
47+
48+
49+
class LocalizedIContains(LocalizedLookupMixin, IContains):
50+
pass
51+
52+
53+
class LocalizedStartsWith(LocalizedLookupMixin, StartsWith):
54+
pass
55+
56+
57+
class LocalizedIStartsWith(LocalizedLookupMixin, IStartsWith):
58+
pass
59+
60+
61+
class LocalizedEndsWith(LocalizedLookupMixin, EndsWith):
62+
pass
63+
64+
65+
class LocalizedIEndsWith(LocalizedLookupMixin, IEndsWith):
66+
pass
67+
68+
69+
class LocalizedIsNullWith(LocalizedLookupMixin, IsNull):
70+
pass
71+
72+
73+
class LocalizedRegexWith(LocalizedLookupMixin, Regex):
74+
pass
75+
76+
77+
class LocalizedIRegexWith(LocalizedLookupMixin, IRegex):
78+
pass

tests/test_lookups.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from django.apps import apps
2+
from django.conf import settings
3+
from django.test import TestCase, override_settings
4+
from django.utils import translation
5+
6+
from localized_fields.fields import LocalizedField
7+
from localized_fields.value import LocalizedValue
8+
9+
from .fake_model import get_fake_model
10+
11+
12+
@override_settings(LOCALIZED_FIELDS_EXPERIMENTAL=True)
13+
class LocalizedLookupsTestCase(TestCase):
14+
"""Tests whether localized lookups properly work with."""
15+
TestModel1 = None
16+
17+
@classmethod
18+
def setUpClass(cls):
19+
"""Creates the test model in the database."""
20+
21+
super(LocalizedLookupsTestCase, cls).setUpClass()
22+
23+
# reload app as setting has changed
24+
config = apps.get_app_config('localized_fields')
25+
config.ready()
26+
27+
cls.TestModel = get_fake_model(
28+
{
29+
'text': LocalizedField(),
30+
}
31+
)
32+
33+
def test_localized_lookup(self):
34+
"""Tests whether localized lookup properly works."""
35+
36+
self.TestModel.objects.create(
37+
text=LocalizedValue(dict(en='text_en', ro='text_ro', nl='text_nl')),
38+
)
39+
40+
# assert that it properly lookups the currently active language
41+
for lang_code, _ in settings.LANGUAGES:
42+
translation.activate(lang_code)
43+
assert self.TestModel.objects.filter(text='text_' + lang_code).exists()
44+
45+
# ensure that the default language is used in case no
46+
# language is active at all
47+
translation.deactivate_all()
48+
assert self.TestModel.objects.filter(text='text_en').exists()
49+
50+
# ensure that hstore lookups still work
51+
assert self.TestModel.objects.filter(text__ro='text_ro').exists()

0 commit comments

Comments
 (0)