Author: carljm
Date: 2011-01-24 21:39:38 -0600 (Mon, 24 Jan 2011)
New Revision: 15304

Modified:
   django/branches/releases/1.2.X/django/db/models/fields/related.py
   django/branches/releases/1.2.X/django/db/models/sql/query.py
   django/branches/releases/1.2.X/django/forms/models.py
   django/branches/releases/1.2.X/tests/modeltests/custom_pk/tests.py
   django/branches/releases/1.2.X/tests/regressiontests/queries/models.py
   django/branches/releases/1.2.X/tests/regressiontests/queries/tests.py
Log:
[1.2.X] Fixed #11319 - Added lookup support for ForeignKey.to_field. Also 
reverted no-longer-needed model formsets workaround for lack of such support 
from r10756. Thanks Russell and Alex for review.

Backport of r15303 from trunk.

Modified: django/branches/releases/1.2.X/django/db/models/fields/related.py
===================================================================
--- django/branches/releases/1.2.X/django/db/models/fields/related.py   
2011-01-25 03:14:28 UTC (rev 15303)
+++ django/branches/releases/1.2.X/django/db/models/fields/related.py   
2011-01-25 03:39:38 UTC (rev 15304)
@@ -176,9 +176,20 @@
         # the primary key may itself be an object - so we need to keep drilling
         # down until we hit a value that can be used for a comparison.
         v = value
+
+        # In the case of an FK to 'self', this check allows to_field to be used
+        # for both forwards and reverse lookups across the FK. (For normal FKs,
+        # it's only relevant for forward lookups).
+        if isinstance(v, self.rel.to):
+            field_name = getattr(self.rel, "field_name", None)
+        else:
+            field_name = None
         try:
             while True:
-                v = getattr(v, v._meta.pk.name)
+                if field_name is None:
+                    field_name = v._meta.pk.name
+                v = getattr(v, field_name)
+                field_name = None
         except AttributeError:
             pass
         except exceptions.ObjectDoesNotExist:

Modified: django/branches/releases/1.2.X/django/db/models/sql/query.py
===================================================================
--- django/branches/releases/1.2.X/django/db/models/sql/query.py        
2011-01-25 03:14:28 UTC (rev 15303)
+++ django/branches/releases/1.2.X/django/db/models/sql/query.py        
2011-01-25 03:39:38 UTC (rev 15304)
@@ -1365,7 +1365,12 @@
                         table = opts.db_table
                         from_col = local_field.column
                         to_col = field.column
-                        target = opts.pk
+                        # In case of a recursive FK, use the to_field for
+                        # reverse lookups as well
+                        if orig_field.model is local_field.model:
+                            target = opts.get_field(field.rel.field_name)
+                        else:
+                            target = opts.pk
                         orig_opts._join_cache[name] = (table, from_col, to_col,
                                 opts, target)
 

Modified: django/branches/releases/1.2.X/django/forms/models.py
===================================================================
--- django/branches/releases/1.2.X/django/forms/models.py       2011-01-25 
03:14:28 UTC (rev 15303)
+++ django/branches/releases/1.2.X/django/forms/models.py       2011-01-25 
03:39:38 UTC (rev 15304)
@@ -690,13 +690,9 @@
         self.save_as_new = save_as_new
         # is there a better way to get the object descriptor?
         self.rel_name = RelatedObject(self.fk.rel.to, self.model, 
self.fk).get_accessor_name()
-        if self.fk.rel.field_name == self.fk.rel.to._meta.pk.name:
-            backlink_value = self.instance
-        else:
-            backlink_value = getattr(self.instance, self.fk.rel.field_name)
         if queryset is None:
             queryset = self.model._default_manager
-        qs = queryset.filter(**{self.fk.name: backlink_value})
+        qs = queryset.filter(**{self.fk.name: self.instance})
         super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix,
                                                 queryset=qs)
 

Modified: django/branches/releases/1.2.X/tests/modeltests/custom_pk/tests.py
===================================================================
--- django/branches/releases/1.2.X/tests/modeltests/custom_pk/tests.py  
2011-01-25 03:14:28 UTC (rev 15303)
+++ django/branches/releases/1.2.X/tests/modeltests/custom_pk/tests.py  
2011-01-25 03:39:38 UTC (rev 15304)
@@ -158,11 +158,9 @@
         new_bar = Bar.objects.create()
         new_foo = Foo.objects.create(bar=new_bar)
 
-        # FIXME: This still doesn't work, but will require some changes in
-        # get_db_prep_lookup to fix it.
-        # f = Foo.objects.get(bar=new_bar.pk)
-        # self.assertEqual(f, new_foo)
-        # self.assertEqual(f.bar, new_bar)
+        f = Foo.objects.get(bar=new_bar.pk)
+        self.assertEqual(f, new_foo)
+        self.assertEqual(f.bar, new_bar)
 
         f = Foo.objects.get(bar=new_bar)
         self.assertEqual(f, new_foo),

Modified: django/branches/releases/1.2.X/tests/regressiontests/queries/models.py
===================================================================
--- django/branches/releases/1.2.X/tests/regressiontests/queries/models.py      
2011-01-25 03:14:28 UTC (rev 15303)
+++ django/branches/releases/1.2.X/tests/regressiontests/queries/models.py      
2011-01-25 03:39:38 UTC (rev 15304)
@@ -274,3 +274,23 @@
 class Article(models.Model):
     name = models.CharField(max_length=20)
     created = models.DateTimeField()
+
+class Food(models.Model):
+    name = models.CharField(max_length=20, unique=True)
+
+    def __unicode__(self):
+        return self.name
+
+class Eaten(models.Model):
+    food = models.ForeignKey(Food, to_field="name")
+    meal = models.CharField(max_length=20)
+
+    def __unicode__(self):
+        return u"%s at %s" % (self.food, self.meal)
+
+class Node(models.Model):
+    num = models.IntegerField(unique=True)
+    parent = models.ForeignKey("self", to_field="num", null=True)
+
+    def __unicode__(self):
+        return u"%s" % self.num

Modified: django/branches/releases/1.2.X/tests/regressiontests/queries/tests.py
===================================================================
--- django/branches/releases/1.2.X/tests/regressiontests/queries/tests.py       
2011-01-25 03:14:28 UTC (rev 15303)
+++ django/branches/releases/1.2.X/tests/regressiontests/queries/tests.py       
2011-01-25 03:39:38 UTC (rev 15304)
@@ -14,7 +14,7 @@
 from models import (Annotation, Article, Author, Celebrity, Child, Cover, 
Detail,
     DumbCategory, ExtraInfo, Fan, Item, LeafA, LoopX, LoopZ, ManagedModel,
     Member, NamedCategory, Note, Number, Plaything, PointerA, Ranking, Related,
-    Report, ReservedName, Tag, TvChef, Valid, X)
+    Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten, Node)
 
 
 class BaseQuerysetTest(TestCase):
@@ -1506,6 +1506,67 @@
         )
 
 
+class ToFieldTests(TestCase):
+    def test_in_query(self):
+        apple = Food.objects.create(name="apple")
+        pear = Food.objects.create(name="pear")
+        lunch = Eaten.objects.create(food=apple, meal="lunch")
+        dinner = Eaten.objects.create(food=pear, meal="dinner")
+
+        self.assertEqual(
+            set(Eaten.objects.filter(food__in=[apple, pear])),
+            set([lunch, dinner]),
+        )
+
+    def test_reverse_in(self):
+        apple = Food.objects.create(name="apple")
+        pear = Food.objects.create(name="pear")
+        lunch_apple = Eaten.objects.create(food=apple, meal="lunch")
+        lunch_pear = Eaten.objects.create(food=pear, meal="dinner")
+
+        self.assertEqual(
+            set(Food.objects.filter(eaten__in=[lunch_apple, lunch_pear])),
+            set([apple, pear])
+        )
+
+    def test_single_object(self):
+        apple = Food.objects.create(name="apple")
+        lunch = Eaten.objects.create(food=apple, meal="lunch")
+        dinner = Eaten.objects.create(food=apple, meal="dinner")
+
+        self.assertEqual(
+            set(Eaten.objects.filter(food=apple)),
+            set([lunch, dinner])
+        )
+
+    def test_single_object_reverse(self):
+        apple = Food.objects.create(name="apple")
+        lunch = Eaten.objects.create(food=apple, meal="lunch")
+
+        self.assertEqual(
+            set(Food.objects.filter(eaten=lunch)),
+            set([apple])
+        )
+
+    def test_recursive_fk(self):
+        node1 = Node.objects.create(num=42)
+        node2 = Node.objects.create(num=1, parent=node1)
+
+        self.assertEqual(
+            list(Node.objects.filter(parent=node1)),
+            [node2]
+        )
+
+    def test_recursive_fk_reverse(self):
+        node1 = Node.objects.create(num=42)
+        node2 = Node.objects.create(num=1, parent=node1)
+
+        self.assertEqual(
+            list(Node.objects.filter(node=node2)),
+            [node1]
+        )
+
+
 # In Python 2.6 beta releases, exceptions raised in __len__ are swallowed
 # (Python issue 1242657), so these cases return an empty list, rather than
 # raising an exception. Not a lot we can do about that, unfortunately, due to

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to