#36924: FieldError when using selected_related on ForeignObject together with
defer
-------------------------------------+-------------------------------------
Reporter: Markus Holtermann | Type:
| Uncategorized
Status: new | Component: Database
| layer (models, ORM)
Version: 5.2 | Severity: Normal
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
A queryset `Model1.objects.select_related("data",
"model2").defer("data__field")` where `model2` is a `ForeignObject` (not
`ForeignKey`!), results in `django.core.exceptions.FieldError: Field
Model1.model2 cannot be both deferred and traversed using select_related
at the same time.`
{{{#!python
# models.py
from django.db import models
class JSONFieldNullable(models.Model):
json_field = models.JSONField(blank=True, null=True)
class Meta:
required_db_features = {"supports_json_field"}
class Model2(models.Model):
code = models.BigIntegerField(
primary_key=True, serialize=False, verbose_name="Code",
db_column="id"
)
class Model1(models.Model):
data = models.ForeignKey(JSONFieldNullable, on_delete=models.CASCADE)
model2_code = models.CharField(max_length=10)
model2 = models.ForeignObject(
Model2,
from_fields=["model2_code"],
to_fields=["code"],
on_delete=models.DO_NOTHING,
related_name="+",
)
# tests.py
from django.test import TestCase
from .models import JSONFieldNullable, Model1, Model2
class Tests(TestCase):
@classmethod
def setUpTestData(cls):
data = JSONFieldNullable.objects.create(json_field={"a": "b"})
model2 = Model2.objects.create(code=123)
Model1.objects.create(data=data, model2_code="123")
def test1(self):
# 1. SELECT ... FROM queries_model1
# INNER JOIN queries_jsonfieldnullable
# INNER JOIN queries_model2
with self.assertNumQueries(1):
queried = [
(
x.id,
x.model2_code,
x.model2,
x.model2.code,
x.data.id,
x.data.json_field,
)
for x in Model1.objects.select_related("data", "model2")
]
def test2(self):
# 1. SELECT ... FROM queries_model1
# INNER JOIN queries_jsonfieldnullable
# INNER JOIN queries_model2
# 2. SELECT ... FROM queries_jsonfieldnullable
# WHERE id = ...
with self.assertNumQueries(2):
queried = [
(
x.id,
x.model2_code,
x.model2,
x.model2.code,
x.data.id,
x.data.json_field,
)
for x in Model1.objects.select_related("data",
"model2").defer(
"data__json_field"
)
]
def test3(self):
# 1. SELECT ... FROM queries_model1
# INNER JOIN queries_jsonfieldnullable
# 2. SELECT ... FROM queries_model2
# WHERE id = ...
# 3. SELECT ... FROM queries_jsonfieldnullable
# WHERE id = ...
with self.assertNumQueries(3):
queried = [
(
x.id,
x.model2_code,
x.model2,
x.model2.code,
x.data.id,
x.data.json_field,
)
for x in
Model1.objects.select_related("data").defer("data__json_field")
]
# Traceback
======================================================================
ERROR: test2 (queries.test_foreignobject_select_related.Tests.test2)
----------------------------------------------------------------------
Traceback (most recent call last):
File
"/home/markus/Coding/django/tests/queries/test_foreignobject_select_related.py",
line 46, in test2
for x in Model1.objects.select_related("data", "model2").defer(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
"data__json_field"
^^^^^^^^^^^^^^^^^^
)
^
File "/home/markus/Coding/django/django/db/models/query.py", line 386,
in __iter__
self._fetch_all()
~~~~~~~~~~~~~~~^^
File "/home/markus/Coding/django/django/db/models/query.py", line 1954,
in _fetch_all
self._result_cache = list(self._iterable_class(self))
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/markus/Coding/django/django/db/models/query.py", line 93, in
__iter__
results = compiler.execute_sql(
chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
)
File "/home/markus/Coding/django/django/db/models/sql/compiler.py", line
1610, in execute_sql
sql, params = self.as_sql()
~~~~~~~~~~~^^
File "/home/markus/Coding/django/django/db/models/sql/compiler.py", line
766, in as_sql
extra_select, order_by, group_by = self.pre_sql_setup(
~~~~~~~~~~~~~~~~~~^
with_col_aliases=with_col_aliases or bool(combinator),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/markus/Coding/django/django/db/models/sql/compiler.py", line
85, in pre_sql_setup
self.setup_query(with_col_aliases=with_col_aliases)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/markus/Coding/django/django/db/models/sql/compiler.py", line
74, in setup_query
self.select, self.klass_info, self.annotation_col_map =
self.get_select(
~~~~~~~~~~~~~~~^
with_col_aliases=with_col_aliases,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/markus/Coding/django/django/db/models/sql/compiler.py", line
299, in get_select
related_klass_infos = self.get_related_selections(select, select_mask)
File "/home/markus/Coding/django/django/db/models/sql/compiler.py", line
1241, in get_related_selections
if not select_related_descend(f, restricted, requested, select_mask):
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/markus/Coding/django/django/db/models/query_utils.py", line
438, in select_related_descend
raise FieldError(
...<2 lines>...
)
django.core.exceptions.FieldError: Field Model1.model2 cannot be both
deferred and traversed using select_related at the same time.
}}}
I understand that `ForeignObject` is private API (in terms of the
deprecation policy per the [https://docs.djangoproject.com/en/5.2/topics
/composite-primary-key/#composite-primary-keys-and-relations comment in
the composite primary keys docs]), but it seems to me the given tests
should still pass.
--
Ticket URL: <https://code.djangoproject.com/ticket/36924>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/django-updates/0107019c56d238fe-1faa2740-b391-4d38-b8b1-10422dc74214-000000%40eu-central-1.amazonses.com.