Custom generic istruthy/isfalsy lookup doesn't work on Foreign Keys
In my Django project, I'm in need of a generic istruthy/isfalsy lookup that would work on any type of database field.
Example of models.py:
class MyModel(models.Model):
my_charfield = models.CharField(...)
my_decimalfield = models.DecimalField(...)
my_datefield = models.DateField(...)
my_boolfield = models.BooleanField(...)
my_fkfield = models.ForeignKey(...)
What I want to be able to do:
MyModel.objects.filter(my_charfield__isfalsy=True)
MyModel.objects.filter(my_decimalfield__isfalsy=True)
MyModel.objects.filter(my_datefield__isfalsy=True)
MyModel.objects.filter(my_boolfield__isfalsy=True)
MyModel.objects.filter(my_fkfield__isfalsy=True)
I have defined two custom lookups to do that:
from django.db.models import Lookup, BooleanField, ForeignKey, Field
from django.db.models.fields import IntegerField, DecimalField, FloatField
class IsTruthy(Lookup):
lookup_name = 'istruthy'
def as_sql(self, compiler, connection):
lhs, params = self.process_lhs(compiler, connection)
if isinstance(self.lhs.output_field, BooleanField):
return f"{lhs} = TRUE", params
if isinstance(self.lhs.output_field, IntegerField):
return f"{lhs} IS NOT NULL", params
if isinstance(self.lhs.output_field, DecimalField):
return f"{lhs} IS NOT NULL", params
if isinstance(self.lhs.output_field, FloatField):
return f"{lhs} IS NOT NULL", params
if isinstance(self.lhs.output_field, ForeignKey):
lhs = f"{lhs}_id"
return f"{lhs} IS NOT NULL", params
return f"{lhs} IS NOT NULL AND {lhs} <> ''", params
class IsFalsy(Lookup):
lookup_name = 'isfalsy'
def as_sql(self, compiler, connection):
lhs, params = self.process_lhs(compiler, connection)
if isinstance(self.lhs.output_field, BooleanField):
return f"{lhs} = FALSE", params
if isinstance(self.lhs.output_field, IntegerField):
return f"{lhs} IS NULL", params
if isinstance(self.lhs.output_field, DecimalField):
return f"{lhs} IS NULL", params
if isinstance(self.lhs.output_field, FloatField):
return f"{lhs} IS NULL", params
if isinstance(self.lhs.output_field, ForeignKey):
lhs = f"{lhs}_id"
return f"{lhs} IS NULL", params
return f"{lhs} IS NULL OR {lhs} = ''", params
Field.register_lookup(IsTruthy)
Field.register_lookup(IsFalsy)
These custom lookups work on all fields except ForeignKey. When used on a ForeignKey, the following error is triggered:
django.core.exceptions.FieldError: Unsupported lookup 'isfalsy' for ForeignKey or join on the field not permitted
I have tried different variations of this code to handle the ForeignKey fields, but nothing seems to work. I have also a lot of trouble debugging because pdb.set_trace() doesn't seem to do anything in as_sql() ; print statements don't show ; logging statements don't seem to work either.
(Side note: I know that my custom lookups provide the same result whether I use isfalsy=True or isfalsy=False. This behavior is fine in the context of my project)
I would be really grateful if anyone could help me find a way to handle FKs or at least help me find a way to debug!
Django version is 4.2
Thanks!
EDIT :
Full Traceback :
Traceback (most recent call last):
File "/home/luci/MyVENV/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3577, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-3-efef204fd6a5>", line 1, in <module>
MyModel.objects.filter(my_fkfield__isfalsy=True)
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/query.py", line 1436, in filter
return self._filter_or_exclude(False, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/query.py", line 1454, in _filter_or_exclude
clone._filter_or_exclude_inplace(negate, args, kwargs)
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/query.py", line 1461, in _filter_or_exclude_inplace
self._query.add_q(Q(*args, **kwargs))
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1545, in add_q
clause, _ = self._add_q(q_object, self.used_aliases)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1576, in _add_q
child_clause, needed_inner = self.build_filter(
^^^^^^^^^^^^^^^^^^
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1491, in build_filter
condition = self.build_lookup(lookups, col, value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1312, in build_lookup
lhs = self.try_transform(lhs, lookup_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/luci/MyVENV/lib/python3.11/site-packages/django/db/models/sql/query.py", line 1356, in try_transform
raise FieldError(
django.core.exceptions.FieldError: Unsupported lookup 'isfalsy' for ForeignKey or join on the field not permitted.
I had a similar problem try the following, it worked for me. Register this explicitly for models.ForeignKey
, something like this:
class CustomLookup(models.Lookup):
...
models.Field.register_lookup(CustomLookup)
# or more specific fields
models.ForeignKey.register_lookup(CustomLookup)