summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Phalip <jphalip@gmail.com>2012-02-05 07:11:53 +0000
committerJulien Phalip <jphalip@gmail.com>2012-02-05 07:11:53 +0000
commitd02ba7f4ee7c448de28724369fc5dd9cf3e2538a (patch)
treee84ef86ed22de1094d2ca891de591918a96a6bee
parent9b762d170a9d895eadae270569c1dee6dbd976c5 (diff)
Fixed #11670 -- Prevented genuine model fields named 'year', 'month', 'gt', 'lt' etc. from being mistaken for lookup types in lookups across relations. Thanks to andy for the report, to jpwatts for the initial patch and to Anssi Kääriäinen and Alex Gaynor for the reviews.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17450 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/db/models/sql/query.py30
-rw-r--r--tests/modeltests/lookup/models.py22
-rw-r--r--tests/modeltests/lookup/tests.py82
3 files changed, 126 insertions, 8 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index ed2bc06d4f..a78df343a7 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -1061,11 +1061,31 @@ class Query(object):
if not parts:
raise FieldError("Cannot parse keyword query %r" % arg)
- # Work out the lookup type and remove it from 'parts', if necessary.
- if len(parts) == 1 or parts[-1] not in self.query_terms:
- lookup_type = 'exact'
- else:
- lookup_type = parts.pop()
+ # Work out the lookup type and remove it from the end of 'parts',
+ # if necessary.
+ lookup_type = 'exact' # Default lookup type
+ num_parts = len(parts)
+ if (len(parts) > 1 and parts[-1] in self.query_terms
+ and arg not in self.aggregates):
+ # Traverse the lookup query to distinguish related fields from
+ # lookup types.
+ lookup_model = self.model
+ for counter, field_name in enumerate(parts):
+ try:
+ lookup_field = lookup_model._meta.get_field(field_name)
+ except FieldDoesNotExist:
+ # Not a field. Bail out.
+ lookup_type = parts.pop()
+ break
+ # Unless we're at the end of the list of lookups, let's attempt
+ # to continue traversing relations.
+ if (counter + 1) < num_parts:
+ try:
+ lookup_model = lookup_field.rel.to
+ except AttributeError:
+ # Not a related field. Bail out.
+ lookup_type = parts.pop()
+ break
# By default, this is a WHERE clause. If an aggregate is referenced
# in the value, the filter will be promoted to a HAVING
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
index 7c264a40e4..bcdd3d7c68 100644
--- a/tests/modeltests/lookup/models.py
+++ b/tests/modeltests/lookup/models.py
@@ -27,3 +27,25 @@ class Tag(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name', )
+
+class Season(models.Model):
+ year = models.PositiveSmallIntegerField()
+ gt = models.IntegerField(null=True, blank=True)
+
+ def __unicode__(self):
+ return unicode(self.year)
+
+class Game(models.Model):
+ season = models.ForeignKey(Season, related_name='games')
+ home = models.CharField(max_length=100)
+ away = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return u"%s at %s" % (self.away, self.home)
+
+class Player(models.Model):
+ name = models.CharField(max_length=100)
+ games = models.ManyToManyField(Game, related_name='players')
+
+ def __unicode__(self):
+ return self.name \ No newline at end of file
diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py
index 9c2b0c6cea..3571e216be 100644
--- a/tests/modeltests/lookup/tests.py
+++ b/tests/modeltests/lookup/tests.py
@@ -1,4 +1,4 @@
-from __future__ import absolute_import
+from __future__ import absolute_import, with_statement
from datetime import datetime
from operator import attrgetter
@@ -6,12 +6,11 @@ from operator import attrgetter
from django.core.exceptions import FieldError
from django.test import TestCase, skipUnlessDBFeature
-from .models import Author, Article, Tag
+from .models import Author, Article, Tag, Game, Season, Player
class LookupTests(TestCase):
- #def setUp(self):
def setUp(self):
# Create a few Authors.
self.au1 = Author(name='Author 1')
@@ -610,3 +609,80 @@ class LookupTests(TestCase):
a16.save()
self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'b(.).*b\1'),
['<Article: barfoobaz>', '<Article: bazbaRFOO>', '<Article: foobarbaz>'])
+
+ def test_nonfield_lookups(self):
+ """
+ Ensure that a lookup query containing non-fields raises the proper
+ exception.
+ """
+ with self.assertRaises(FieldError):
+ Article.objects.filter(headline__blahblah=99)
+ with self.assertRaises(FieldError):
+ Article.objects.filter(headline__blahblah__exact=99)
+ with self.assertRaises(FieldError):
+ Article.objects.filter(blahblah=99)
+
+ def test_lookup_collision(self):
+ """
+ Ensure that genuine field names don't collide with built-in lookup
+ types ('year', 'gt', 'range', 'in' etc.).
+ Refs #11670.
+ """
+
+ # Here we're using 'gt' as a code number for the year, e.g. 111=>2009.
+ season_2009 = Season.objects.create(year=2009, gt=111)
+ season_2009.games.create(home="Houston Astros", away="St. Louis Cardinals")
+ season_2010 = Season.objects.create(year=2010, gt=222)
+ season_2010.games.create(home="Houston Astros", away="Chicago Cubs")
+ season_2010.games.create(home="Houston Astros", away="Milwaukee Brewers")
+ season_2010.games.create(home="Houston Astros", away="St. Louis Cardinals")
+ season_2011 = Season.objects.create(year=2011, gt=333)
+ season_2011.games.create(home="Houston Astros", away="St. Louis Cardinals")
+ season_2011.games.create(home="Houston Astros", away="Milwaukee Brewers")
+ hunter_pence = Player.objects.create(name="Hunter Pence")
+ hunter_pence.games = Game.objects.filter(season__year__in=[2009, 2010])
+ pudge = Player.objects.create(name="Ivan Rodriquez")
+ pudge.games = Game.objects.filter(season__year=2009)
+ pedro_feliz = Player.objects.create(name="Pedro Feliz")
+ pedro_feliz.games = Game.objects.filter(season__year__in=[2011])
+ johnson = Player.objects.create(name="Johnson")
+ johnson.games = Game.objects.filter(season__year__in=[2011])
+
+ # Games in 2010
+ self.assertEqual(Game.objects.filter(season__year=2010).count(), 3)
+ self.assertEqual(Game.objects.filter(season__year__exact=2010).count(), 3)
+ self.assertEqual(Game.objects.filter(season__gt=222).count(), 3)
+ self.assertEqual(Game.objects.filter(season__gt__exact=222).count(), 3)
+
+ # Games in 2011
+ self.assertEqual(Game.objects.filter(season__year=2011).count(), 2)
+ self.assertEqual(Game.objects.filter(season__year__exact=2011).count(), 2)
+ self.assertEqual(Game.objects.filter(season__gt=333).count(), 2)
+ self.assertEqual(Game.objects.filter(season__gt__exact=333).count(), 2)
+ self.assertEqual(Game.objects.filter(season__year__gt=2010).count(), 2)
+ self.assertEqual(Game.objects.filter(season__gt__gt=222).count(), 2)
+
+ # Games played in 2010 and 2011
+ self.assertEqual(Game.objects.filter(season__year__in=[2010, 2011]).count(), 5)
+ self.assertEqual(Game.objects.filter(season__year__gt=2009).count(), 5)
+ self.assertEqual(Game.objects.filter(season__gt__in=[222, 333]).count(), 5)
+ self.assertEqual(Game.objects.filter(season__gt__gt=111).count(), 5)
+
+ # Players who played in 2009
+ self.assertEqual(Player.objects.filter(games__season__year=2009).distinct().count(), 2)
+ self.assertEqual(Player.objects.filter(games__season__year__exact=2009).distinct().count(), 2)
+ self.assertEqual(Player.objects.filter(games__season__gt=111).distinct().count(), 2)
+ self.assertEqual(Player.objects.filter(games__season__gt__exact=111).distinct().count(), 2)
+
+ # Players who played in 2010
+ self.assertEqual(Player.objects.filter(games__season__year=2010).distinct().count(), 1)
+ self.assertEqual(Player.objects.filter(games__season__year__exact=2010).distinct().count(), 1)
+ self.assertEqual(Player.objects.filter(games__season__gt=222).distinct().count(), 1)
+ self.assertEqual(Player.objects.filter(games__season__gt__exact=222).distinct().count(), 1)
+
+ # Players who played in 2011
+ self.assertEqual(Player.objects.filter(games__season__year=2011).distinct().count(), 2)
+ self.assertEqual(Player.objects.filter(games__season__year__exact=2011).distinct().count(), 2)
+ self.assertEqual(Player.objects.filter(games__season__gt=333).distinct().count(), 2)
+ self.assertEqual(Player.objects.filter(games__season__year__gt=2010).distinct().count(), 2)
+ self.assertEqual(Player.objects.filter(games__season__gt__gt=222).distinct().count(), 2)