Skip to content

Commit abd1587

Browse files
authored
Merge pull request #54 from sliverc/query_by_active_lang
Add support for localized query lookups
2 parents 60fc79e + ff83683 commit abd1587

File tree

5 files changed

+149
-0
lines changed

5 files changed

+149
-0
lines changed

README.rst

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

349350
.. code-block:: python
350351

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