From cd7b54aab0c087ba6efd4a86ae0ecb6ed55d7e4b Mon Sep 17 00:00:00 2001
From: Russell Keith-Magee <russell@keith-magee.com>
Date: Sat, 1 Jul 2006 03:14:33 +0000
Subject: [PATCH] Fixes #2271 -- Added code to imply !__exact on any query
 argument that doesn't finish with a known query term.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@3248 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/db/models/query.py              | 17 +++++++++++++++--
 tests/modeltests/many_to_one/models.py |  8 ++++++++
 2 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/django/db/models/query.py b/django/db/models/query.py
index aa2138643e..8fbb942da2 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -10,8 +10,20 @@ import re
 if not hasattr(__builtins__, 'set'):
     from sets import Set as set
 
+# The string constant used to separate query parts
 LOOKUP_SEPARATOR = '__'
 
+# The list of valid query types 
+QUERY_TERMS=(
+    'exact', 'iexact',
+    'contains', 'icontains',
+    'gt', 'gte', 'lt', 'lte',
+    'in',
+    'startswith', 'istartswith', 'endswith', 'iendswith',
+    'range', 'year', 'month', 'day',
+    'isnull'
+)
+
 # Size of each "chunk" for get_iterator calls.
 # Larger values are slightly faster at the expense of more storage space.
 GET_ITERATOR_CHUNK_SIZE = 100
@@ -710,12 +722,13 @@ def parse_lookup(kwarg_items, opts):
             #     if we find "pk", make the clause "exact', and insert
             #     a dummy name of None, which we will replace when
             #     we know which table column to grab as the primary key.
-            # 2)  If there is only one part, assume it to be an __exact
+            # 2)  If there is only one part, or the last part is not a query
+            #     term, assume that the query is an __exact            
             clause = path.pop()
             if clause == 'pk':
                 clause = 'exact'
                 path.append(None)
-            elif len(path) == 0:
+            elif len(path) == 0 or clause not in QUERY_TERMS:
                 path.append(clause)
                 clause = 'exact'
 
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index b7d27e2ed3..d202975128 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -136,6 +136,10 @@ False
 >>> Article.objects.filter(reporter__first_name__exact='John')
 [<Article: John's second story>, <Article: This is a test>]
 
+# Check that implied __exact also works
+>>> Article.objects.filter(reporter__first_name='John')
+[<Article: John's second story>, <Article: This is a test>]
+
 # Query twice over the related field.
 >>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith')
 [<Article: John's second story>, <Article: This is a test>]
@@ -237,6 +241,10 @@ TypeError: Cannot resolve keyword 'reporter_id' into field
 >>> Reporter.objects.filter(article__reporter__exact=r).distinct()
 [<Reporter: John Smith>]
 
+# Check that implied __exact also works
+>>> Reporter.objects.filter(article__reporter=r).distinct()
+[<Reporter: John Smith>]
+
 # If you delete a reporter, his articles will be deleted.
 >>> Article.objects.all()
 [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>, <Article: This is a test>, <Article: This is a test>]