summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2015-10-09 21:03:04 -0400
committerTim Graham <timograham@gmail.com>2015-10-10 09:48:50 -0400
commit67732a9b183d2e84c85147b04fdf9499f4395ac6 (patch)
tree86ffa81542c884a7c38f71ff3443732139c65e4b
parent5171f56faeb9ec27153c7e7319c96d3fba774ea5 (diff)
Fixed #24687 -- Added select_related() validation for nested non-relational fields.
The removed test was added in the original select_related() validation patch (45d4e43d2d25b902e3821b612209afa951a8bcb8), but there doesn't seem to be any reason for it. Thanks Claude Paroz for help and review.
-rw-r--r--django/contrib/gis/db/models/query.py6
-rw-r--r--django/db/models/sql/compiler.py2
-rw-r--r--docs/releases/1.10.txt17
-rw-r--r--tests/admin_changelist/admin.py2
-rw-r--r--tests/admin_changelist/tests.py4
-rw-r--r--tests/gis_tests/relatedapp/tests.py3
-rw-r--r--tests/select_related/tests.py10
7 files changed, 31 insertions, 13 deletions
diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py
index 07d593de02..0375f4a7cb 100644
--- a/django/contrib/gis/db/models/query.py
+++ b/django/contrib/gis/db/models/query.py
@@ -10,6 +10,7 @@ from django.contrib.gis.db.models.sql import (
from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.measure import Area, Distance
from django.db import connections
+from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import RawSQL
from django.db.models.fields import Field
from django.db.models.query import QuerySet
@@ -675,9 +676,10 @@ class GeoQuerySet(QuerySet):
if geo_field not in opts.fields:
# Is this operation going to be on a related geographic field?
# 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!"
- 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.
compiler.pre_sql_setup()
for col, _, _ in compiler.select:
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 790f5f53f7..ea9b3e78b4 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -673,7 +673,7 @@ class SQLCompiler(object):
if not f.is_relation:
# If a non-related field is used like a relation,
# 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(
"Non-relational field given in select_related: '%s'. "
"Choices are: %s" % (
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index d72b0b627b..c720c65b80 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -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
~~~~~~~~~~~~~
diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py
index c32082f78a..4ca5ae587a 100644
--- a/tests/admin_changelist/admin.py
+++ b/tests/admin_changelist/admin.py
@@ -36,7 +36,7 @@ class ChildAdmin(admin.ModelAdmin):
list_filter = ['parent', 'age']
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):
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index caf9beeea0..e5a0ae2c2b 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -75,9 +75,7 @@ class ChangeListTests(TestCase):
request, Child,
*get_changelist_args(m, list_select_related=m.get_list_select_related(request))
)
- self.assertEqual(cl.queryset.query.select_related, {
- 'parent': {'name': {}}
- })
+ self.assertEqual(cl.queryset.query.select_related, {'parent': {}})
def test_select_related_as_tuple(self):
ia = InvitationAdmin(Invitation, custom_site)
diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py
index 14828455fb..d6950f7432 100644
--- a/tests/gis_tests/relatedapp/tests.py
+++ b/tests/gis_tests/relatedapp/tests.py
@@ -62,6 +62,9 @@ class RelatedGeoModelTest(TestCase):
qs = list(City.objects.filter(name=name).transform(srid, field_name='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")
def test_related_extent_aggregate(self):
"Testing the `Extent` aggregate on related geographic models."
diff --git a/tests/select_related/tests.py b/tests/select_related/tests.py
index b8acd586a6..93d96c9665 100644
--- a/tests/select_related/tests.py
+++ b/tests/select_related/tests.py
@@ -134,12 +134,6 @@ class SelectRelatedTests(TestCase):
orders = [o.genus.family.order.name for o in world]
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):
with self.assertNumQueries(1):
s = (Species.objects.all()
@@ -206,6 +200,10 @@ class SelectRelatedValidationTests(SimpleTestCase):
with self.assertRaisesMessage(FieldError, self.non_relational_error % ('name', '(none)')):
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):
with self.assertRaisesMessage(FieldError, self.invalid_error % ('toppings', '(none)')):
list(Pizza.objects.select_related('toppings'))