mirror of
				https://github.com/django/django.git
				synced 2025-11-03 21:25:09 +00:00 
			
		
		
		
	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
					
				
					 7 changed files with 31 additions and 13 deletions
				
			
		| 
						 | 
					@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue