Don't double key transform in lookups

In Django 4.2, `process_lhs()` is called for IsNull lookups,
which is how this bug came to appear. It was already there
and could be reproduced on Django 3.2.

The issue is when `LocalizedRef` is combined with a localized
lookup. The lookup is unaware of the existing localized ref
and adds another transform on top. This results in the
expression becoming:

```
field->'en'->'en'
```
This commit is contained in:
Swen Kooij 2024-07-01 16:34:57 +03:00 committed by Swen Kooij
parent 25259b8469
commit e6527fff4c
2 changed files with 38 additions and 5 deletions

View File

@ -23,6 +23,7 @@ from django.db.models.lookups import (
StartsWith, StartsWith,
) )
from django.utils import translation from django.utils import translation
from psqlextra.expressions import HStoreColumn
from .fields import LocalizedField from .fields import LocalizedField
@ -37,14 +38,33 @@ except ImportError:
class LocalizedLookupMixin: class LocalizedLookupMixin:
def process_lhs(self, qn, connection): def process_lhs(self, qn, connection):
if isinstance(self.lhs, Col): # If the LHS is already a reference to a specific hstore key, there
# is nothing to be done since it already references as specific language.
if isinstance(self.lhs, HStoreColumn) or isinstance(
self.lhs, KeyTransform
):
return super().process_lhs(qn, connection)
# If this is something custom expression, we don't really know how to
# handle that, so we better do nothing.
if not isinstance(self.lhs, Col):
return super().process_lhs(qn, connection)
# Select the key for the current language. We do this so that
#
# myfield__<lookup>=
#
# Is converted into:
#
# myfield__<lookup>__<current language>=
language = translation.get_language() or settings.LANGUAGE_CODE language = translation.get_language() or settings.LANGUAGE_CODE
self.lhs = KeyTransform(language, self.lhs) self.lhs = KeyTransform(language, self.lhs)
return super().process_lhs(qn, connection) return super().process_lhs(qn, connection)
def get_prep_lookup(self): def get_prep_lookup(self):
# Django 4.0 removed the ability for isnull fields to be something other than a bool # Django 4.0 removed the ability for isnull fields to be something
# We should NOT convert them to strings # other than a bool We should NOT convert them to strings
if isinstance(self.rhs, bool): if isinstance(self.rhs, bool):
return self.rhs return self.rhs
return str(self.rhs) return str(self.rhs)

View File

@ -3,6 +3,7 @@ from django.conf import settings
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.utils import translation from django.utils import translation
from localized_fields.expressions import LocalizedRef
from localized_fields.fields import LocalizedField from localized_fields.fields import LocalizedField
from localized_fields.value import LocalizedValue from localized_fields.value import LocalizedValue
@ -49,6 +50,18 @@ class LocalizedLookupsTestCase(TestCase):
# ensure that hstore lookups still work # ensure that hstore lookups still work
assert self.TestModel.objects.filter(text__ro="text_ro").exists() assert self.TestModel.objects.filter(text__ro="text_ro").exists()
def test_localized_lookup_specific_isnull(self):
self.TestModel.objects.create(
text=LocalizedValue(dict(en="text_en", ro="text_ro", nl=None))
)
translation.activate("nl")
assert (
self.TestModel.objects.annotate(text_localized=LocalizedRef("text"))
.filter(text_localized__isnull=True)
.exists()
)
class LocalizedRefLookupsTestCase(TestCase): class LocalizedRefLookupsTestCase(TestCase):
"""Tests whether ref lookups properly work with.""" """Tests whether ref lookups properly work with."""