diff --git a/django/db/models/base.py b/django/db/models/base.py index 7687eea598..e94c14f869 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1674,13 +1674,13 @@ class Model(six.with_metaclass(ModelBase)): def method_set_order(ordered_obj, self, id_list, using=None): if using is None: using = DEFAULT_DB_ALIAS - rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) - order_name = ordered_obj._meta.order_with_respect_to.name + order_wrt = ordered_obj._meta.order_with_respect_to + filter_args = order_wrt.get_forward_related_filter(self) # FIXME: It would be nice if there was an "update many" version of update # for situations like this. with transaction.atomic(using=using, savepoint=False): for i, j in enumerate(id_list): - ordered_obj.objects.filter(**{'pk': j, order_name: rel_val}).update(_order=i) + ordered_obj.objects.filter(pk=j, **filter_args).update(_order=i) def method_get_order(ordered_obj, self): diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 47b479c763..f64ca22b5d 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -314,6 +314,19 @@ class RelatedField(Field): else: self.do_related_class(other, cls) + def get_forward_related_filter(self, obj): + """ + Return the keyword arguments that when supplied to + self.model.object.filter(), would select all instances related through + this field to the remote obj. This is used to build the querysets + returned by related descriptors. obj is an instance of + self.related_field.model. + """ + return { + '%s__%s' % (self.name, rh_field.name): getattr(obj, rh_field.attname) + for _, rh_field in self.related_fields + } + @property def swappable_setting(self): """ diff --git a/docs/releases/1.8.7.txt b/docs/releases/1.8.7.txt index df0ff41159..b11ea9781a 100644 --- a/docs/releases/1.8.7.txt +++ b/docs/releases/1.8.7.txt @@ -42,3 +42,7 @@ Bugfixes * Fixed a duplicate query regression in 1.8 on proxied model deletion (:ticket:`25685`). + +* Fixed ``set_FOO_order()`` crash when the ``ForeignKey`` of a model with + ``order_with_respect_to`` references a model with a ``OneToOneField`` + primary key (:ticket:`25786`). diff --git a/tests/order_with_respect_to/models.py b/tests/order_with_respect_to/models.py index 18dbd08dbc..b655d29046 100644 --- a/tests/order_with_respect_to/models.py +++ b/tests/order_with_respect_to/models.py @@ -33,3 +33,19 @@ class Post(models.Model): def __str__(self): return self.title + + +# order_with_respect_to points to a model with a OneToOneField primary key. +class Entity(models.Model): + pass + + +class Dimension(models.Model): + entity = models.OneToOneField('Entity', primary_key=True) + + +class Component(models.Model): + dimension = models.ForeignKey('Dimension') + + class Meta: + order_with_respect_to = 'dimension' diff --git a/tests/order_with_respect_to/tests.py b/tests/order_with_respect_to/tests.py index 60181cca4b..82c323b93b 100644 --- a/tests/order_with_respect_to/tests.py +++ b/tests/order_with_respect_to/tests.py @@ -5,7 +5,7 @@ from operator import attrgetter from django.db import models from django.test import TestCase -from .models import Answer, Post, Question +from .models import Answer, Dimension, Entity, Post, Question class OrderWithRespectToTests(TestCase): @@ -103,3 +103,13 @@ class OrderWithRespectToTests2(TestCase): count += 1 self.assertEqual(count, 1) + + +class TestOrderWithRespectToOneToOnePK(TestCase): + def test_set_order(self): + e = Entity.objects.create() + d = Dimension.objects.create(entity=e) + c1 = d.component_set.create() + c2 = d.component_set.create() + d.set_component_order([c1.id, c2.id]) + self.assertQuerysetEqual(d.component_set.all(), [c1.id, c2.id], attrgetter('pk'))