mirror of https://github.com/django/django.git
Fixed #24687 -- Added select_related() validation for nested non-relational fields.
The removed test was added in the original select_related() validation
patch (45d4e43d2d
), but there doesn't
seem to be any reason for it.
Thanks Claude Paroz for help and review.
This commit is contained in:
parent
5171f56fae
commit
67732a9b18
|
@ -10,6 +10,7 @@ from django.contrib.gis.db.models.sql import (
|
||||||
from django.contrib.gis.geometry.backend import Geometry
|
from django.contrib.gis.geometry.backend import Geometry
|
||||||
from django.contrib.gis.measure import Area, Distance
|
from django.contrib.gis.measure import Area, Distance
|
||||||
from django.db import connections
|
from django.db import connections
|
||||||
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.db.models.fields import Field
|
from django.db.models.fields import Field
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
@ -675,9 +676,10 @@ class GeoQuerySet(QuerySet):
|
||||||
if geo_field not in opts.fields:
|
if geo_field not in opts.fields:
|
||||||
# Is this operation going to be on a related geographic field?
|
# Is this operation going to be on a related geographic field?
|
||||||
# If so, it'll have to be added to the select related information
|
# If so, it'll have to be added to the select related information
|
||||||
# (e.g., if 'location__point' was given as the field name).
|
# (e.g., if 'location__point' was given as the field name, then
|
||||||
|
# chop the non-relational field and add select_related('location')).
|
||||||
# Note: the operation really is defined as "must add select related!"
|
# Note: the operation really is defined as "must add select related!"
|
||||||
self.query.add_select_related([field_name])
|
self.query.add_select_related([field_name.rsplit(LOOKUP_SEP, 1)[0]])
|
||||||
# Call pre_sql_setup() so that compiler.select gets populated.
|
# Call pre_sql_setup() so that compiler.select gets populated.
|
||||||
compiler.pre_sql_setup()
|
compiler.pre_sql_setup()
|
||||||
for col, _, _ in compiler.select:
|
for col, _, _ in compiler.select:
|
||||||
|
|
|
@ -673,7 +673,7 @@ class SQLCompiler(object):
|
||||||
if not f.is_relation:
|
if not f.is_relation:
|
||||||
# If a non-related field is used like a relation,
|
# If a non-related field is used like a relation,
|
||||||
# or if a single non-relational field is given.
|
# or if a single non-relational field is given.
|
||||||
if next or (cur_depth == 1 and f.name in requested):
|
if next or f.name in requested:
|
||||||
raise FieldError(
|
raise FieldError(
|
||||||
"Non-relational field given in select_related: '%s'. "
|
"Non-relational field given in select_related: '%s'. "
|
||||||
"Choices are: %s" % (
|
"Choices are: %s" % (
|
||||||
|
|
|
@ -217,6 +217,23 @@ Database backend API
|
||||||
|
|
||||||
* ...
|
* ...
|
||||||
|
|
||||||
|
``select_related()`` prohibits non-relational fields for nested relations
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Django 1.8 added validation for non-relational fields in ``select_related()``::
|
||||||
|
|
||||||
|
>>> Book.objects.select_related('title')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
FieldError: Non-relational field given in select_related: 'title'
|
||||||
|
|
||||||
|
But it didn't prohibit nested non-relation fields as it does now::
|
||||||
|
|
||||||
|
>>> Book.objects.select_related('author__name')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
FieldError: Non-relational field given in select_related: 'name'
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ class ChildAdmin(admin.ModelAdmin):
|
||||||
list_filter = ['parent', 'age']
|
list_filter = ['parent', 'age']
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super(ChildAdmin, self).get_queryset(request).select_related("parent__name")
|
return super(ChildAdmin, self).get_queryset(request).select_related("parent")
|
||||||
|
|
||||||
|
|
||||||
class CustomPaginationAdmin(ChildAdmin):
|
class CustomPaginationAdmin(ChildAdmin):
|
||||||
|
|
|
@ -75,9 +75,7 @@ class ChangeListTests(TestCase):
|
||||||
request, Child,
|
request, Child,
|
||||||
*get_changelist_args(m, list_select_related=m.get_list_select_related(request))
|
*get_changelist_args(m, list_select_related=m.get_list_select_related(request))
|
||||||
)
|
)
|
||||||
self.assertEqual(cl.queryset.query.select_related, {
|
self.assertEqual(cl.queryset.query.select_related, {'parent': {}})
|
||||||
'parent': {'name': {}}
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_select_related_as_tuple(self):
|
def test_select_related_as_tuple(self):
|
||||||
ia = InvitationAdmin(Invitation, custom_site)
|
ia = InvitationAdmin(Invitation, custom_site)
|
||||||
|
|
|
@ -62,6 +62,9 @@ class RelatedGeoModelTest(TestCase):
|
||||||
qs = list(City.objects.filter(name=name).transform(srid, field_name='location__point'))
|
qs = list(City.objects.filter(name=name).transform(srid, field_name='location__point'))
|
||||||
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
|
check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
|
||||||
|
|
||||||
|
# Relations more than one level deep can be queried.
|
||||||
|
self.assertEqual(list(Parcel.objects.transform(srid, field_name='city__location__point')), [])
|
||||||
|
|
||||||
@skipUnlessDBFeature("supports_extent_aggr")
|
@skipUnlessDBFeature("supports_extent_aggr")
|
||||||
def test_related_extent_aggregate(self):
|
def test_related_extent_aggregate(self):
|
||||||
"Testing the `Extent` aggregate on related geographic models."
|
"Testing the `Extent` aggregate on related geographic models."
|
||||||
|
|
|
@ -134,12 +134,6 @@ class SelectRelatedTests(TestCase):
|
||||||
orders = [o.genus.family.order.name for o in world]
|
orders = [o.genus.family.order.name for o in world]
|
||||||
self.assertEqual(orders, ['Agaricales'])
|
self.assertEqual(orders, ['Agaricales'])
|
||||||
|
|
||||||
def test_single_related_field(self):
|
|
||||||
with self.assertNumQueries(1):
|
|
||||||
species = Species.objects.select_related('genus__name')
|
|
||||||
names = [s.genus.name for s in species]
|
|
||||||
self.assertEqual(sorted(names), ['Amanita', 'Drosophila', 'Homo', 'Pisum'])
|
|
||||||
|
|
||||||
def test_field_traversal(self):
|
def test_field_traversal(self):
|
||||||
with self.assertNumQueries(1):
|
with self.assertNumQueries(1):
|
||||||
s = (Species.objects.all()
|
s = (Species.objects.all()
|
||||||
|
@ -206,6 +200,10 @@ class SelectRelatedValidationTests(SimpleTestCase):
|
||||||
with self.assertRaisesMessage(FieldError, self.non_relational_error % ('name', '(none)')):
|
with self.assertRaisesMessage(FieldError, self.non_relational_error % ('name', '(none)')):
|
||||||
list(Domain.objects.select_related('name'))
|
list(Domain.objects.select_related('name'))
|
||||||
|
|
||||||
|
def test_non_relational_field_nested(self):
|
||||||
|
with self.assertRaisesMessage(FieldError, self.non_relational_error % ('name', 'family')):
|
||||||
|
list(Species.objects.select_related('genus__name'))
|
||||||
|
|
||||||
def test_many_to_many_field(self):
|
def test_many_to_many_field(self):
|
||||||
with self.assertRaisesMessage(FieldError, self.invalid_error % ('toppings', '(none)')):
|
with self.assertRaisesMessage(FieldError, self.invalid_error % ('toppings', '(none)')):
|
||||||
list(Pizza.objects.select_related('toppings'))
|
list(Pizza.objects.select_related('toppings'))
|
||||||
|
|
Loading…
Reference in New Issue