#34745 closed Bug (invalid)
Arbitrary rhs of lookup gives "Object has no attribute 'contains_aggregate'"
Reported by: | Jacob Walls | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
This example overrides JSONField.get_prep_value
to return some arbitrary SQL. Later, a lookup raises an AttributeError
.
It also includes two commented out workarounds. I don't know if this usage is supported, but I bisected the behavior change to f42ccdd835e5b3f0914b5e6f87621c648136ea36, which no longer uses getattr()
to silence AttributeError
for right-hand sides. I found this upgrading a 3.2 project to 4.0.
If this usage isn't supported, what is the recommended interface to implement when writing a custom as_sql()
method?
models.py:
from django.db import models from django.db.models.fields.json import JSONField from django.db.models.sql.where import NothingNode class ArbitrarySQL: # class ArbitrarySQL(NothingNode): # contains_aggregate = False def as_sql(self, compiler, connection): return "'{}'", () class MyJSONField(JSONField): def get_prep_value(self, value): return ArbitrarySQL() class MyModel(models.Model): config = MyJSONField()
Gives:
>>> from repro_app.models import MyModel >>> MyModel.objects.filter(config={}) Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/jwalls/django/django/db/models/query.py", line 373, in __repr__ data = list(self[: REPR_OUTPUT_SIZE + 1]) File "/Users/jwalls/django/django/db/models/query.py", line 397, in __iter__ self._fetch_all() File "/Users/jwalls/django/django/db/models/query.py", line 1897, in _fetch_all self._result_cache = list(self._iterable_class(self)) File "/Users/jwalls/django/django/db/models/query.py", line 90, in __iter__ results = compiler.execute_sql( File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 1549, in execute_sql sql, params = self.as_sql() File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 736, in as_sql extra_select, order_by, group_by = self.pre_sql_setup( File "/Users/jwalls/django/django/db/models/sql/compiler.py", line 86, in pre_sql_setup self.where, self.having, self.qualify = self.query.where.split_having_qualify( File "/Users/jwalls/django/django/db/models/sql/where.py", line 46, in split_having_qualify if not self.contains_aggregate and not self.contains_over_clause: File "/Users/jwalls/django/django/utils/functional.py", line 47, in __get__ res = instance.__dict__[self.name] = self.func(instance) File "/Users/jwalls/django/django/db/models/sql/where.py", line 249, in contains_aggregate return self._contains_aggregate(self) File "/Users/jwalls/django/django/db/models/sql/where.py", line 244, in _contains_aggregate return any(cls._contains_aggregate(c) for c in obj.children) File "/Users/jwalls/django/django/db/models/sql/where.py", line 244, in <genexpr> return any(cls._contains_aggregate(c) for c in obj.children) File "/Users/jwalls/django/django/db/models/sql/where.py", line 245, in _contains_aggregate return obj.contains_aggregate File "/Users/jwalls/django/django/utils/functional.py", line 47, in __get__ res = instance.__dict__[self.name] = self.func(instance) File "/Users/jwalls/django/django/db/models/expressions.py", line 242, in contains_aggregate return any( File "/Users/jwalls/django/django/db/models/expressions.py", line 243, in <genexpr> expr and expr.contains_aggregate for expr in self.get_source_expressions() AttributeError: 'ArbitrarySQL' object has no attribute 'contains_aggregate'
I don't think we offer any guarantees that an object that implements the compilable protocol (
as_sql(compiler, connection) -> tuple[str, tuple]
) is enough to be passed around the ORM. Not against it but it warrants a larger discussion and would likely require the introduction of typing to the ORM to enforce properly through continuous integration.If you want to return arbitrary SQL I'd suggest using RawSQL directly or subclass it as it implements most of the implicit attributes the ORM expects.