#27536: order_by('pk') is not obeyed if superclass has default ordering
-------------------------------------+-------------------------------------
Reporter: Andrew Dodd | Owner: nobody
Type: Bug | Status: new
Component: Database layer | Version: 1.10
(models, ORM) |
Severity: Normal | Resolution:
Keywords: order_by ordering | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Andrew Dodd:
Old description:
> Using 'pk' as an order_by() target does not work if the superclass has a
> default ordering specified on its 'Meta' class.
>
> For example, with the following classes:
> {{{
> #!python
> class SuperWithoutOrdering(models.Model):
> name = models.CharField(max_length=20)
> extra = models.CharField(max_length=20)
>
> class SuperWithOrdering(models.Model):
> name = models.CharField(max_length=20)
> extra = models.CharField(max_length=20)
>
> class Meta:
> ordering = ('name', 'extra')
>
> class SubFromSuperWithoutOrdering(SuperWithoutOrdering):
> anything = models.CharField(max_length=20)
>
> class SubFromSuperWithOrdering(SuperWithOrdering):
> anything = models.CharField(max_length=20)
>
> def __str__(self):
> # so test output is readable
> return 'SubWithOrdering - name:{} extra:{} anything:{}'.format(
> self.name, self.extra, self.anything)
> }}}
>
> Then:
> {{{
> #!python
> SubFromSuperWithoutOrdering.objects.all().order_by('pk') # works,
> ordering by id
> SubFromSuperWithOrdering.object.all().order_by('pk') # does not work, it
> just orders by 'name' then 'extra'
> }}}
>
> I have two tests that demonstrate this.
>
> One that compares the 'ORDER BY' clause of the actual SQL queries:
> {{{
> #!python
> class TestOrderByClause(TestCase):
> def
> test_it_orders_by_pk_and_id_are_the_same_if_super_does_not_have_ordering(self):
> qs = SubFromSuperWithoutOrdering.objects.all()
> by_pk = str(qs.order_by('pk').query).split('ORDER BY')[1]
> by_id = str(qs.order_by('id').query).split('ORDER BY')[1]
> self.assertEquals(by_pk, by_id) # PASSES
>
> def
> test_it_orders_by_pk_and_id_are_the_same_if_super_does_have_ordering(self):
> qs = SubFromSuperWithOrdering.objects.all()
> by_pk = str(qs.order_by('pk').query).split('ORDER BY')[1]
> by_id = str(qs.order_by('id').query).split('ORDER BY')[1]
> self.assertEquals(by_pk, by_id) # FAILS
>
> # Alternative form?
> def test_order_by_clause_is_the_same_if_using_pk_or_id(self):
> for test_class in [SubFromSuperWithoutOrdering,
> SubFromSuperWithOrdering]:
> qs = test_class.objects.all()
> by_pk = str(qs.order_by('pk').query).split('ORDER BY')[1]
> by_id = str(qs.order_by('id').query).split('ORDER BY')[1]
> self.assertEquals(by_pk, by_id, 'Failed for
> {}'.format(test_class))
> }}}
>
> And one that compares the order of the items actually returned from
> executing the query:
>
> {{{
> #!python
> class TestOrderingOfQuerySetWhenSuperclassHasDefaultOrdering(TestCase):
> def setUp(self):
> def make_obj(name, extra, anything):
> return SubFromSuperWithOrdering.objects.create(name=name,
> extra=extra, anything=anything)
>
> self.zoot2 = make_obj( 'Zoot', 'ZZZ - Unknown', 'fourth by
> super ordering, first by pk')
> self.zoot1 = make_obj( 'Zoot', 'AAA - Handmaiden', 'third by
> super ordering, second by pk')
> self.arthur2 = make_obj('Arthur', 'ZZZ - Imposter', 'second by
> super ordering, third by pk')
> self.arthur1 = make_obj('Arthur', 'AAA - The King', 'first by
> super ordering, fourth by pk')
>
> def test_it_uses_superclass_ordering_by_default(self):
> expected = [self.arthur1, self.arthur2, self.zoot1, self.zoot2]
> actual = list(SubFromSuperWithOrdering.objects.all())
> self.assertEqual(expected, actual)
>
> def test_it_correctly_orders_with_id(self):
> expected = [self.zoot2, self.zoot1, self.arthur2, self.arthur1]
> actual =
> list(SubFromSuperWithOrdering.objects.all().order_by('id'))
> self.assertEqual(expected, actual)
>
> def test_it_correctly_orders_with_pk(self):
> expected = [self.zoot2, self.zoot1, self.arthur2, self.arthur1]
> actual =
> list(SubFromSuperWithOrdering.objects.all().order_by('pk'))
> self.assertEqual(expected, actual)
> }}}
>
> I'm not very sure where to go looking to fix this. The workaround is to
> specify the name of the field used as the PK instead of using 'pk'.
New description:
Using 'pk' as an order_by() target does not work if the superclass has a
default ordering specified on its 'Meta' class.
For example, with the following classes:
{{{
#!python
class SuperWithoutOrdering(models.Model):
name = models.CharField(max_length=200)
extra = models.CharField(max_length=200)
class SuperWithOrdering(models.Model):
name = models.CharField(max_length=200)
extra = models.CharField(max_length=200)
class Meta:
ordering = ('name', 'extra')
class SubFromSuperWithoutOrdering(SuperWithoutOrdering):
anything = models.CharField(max_length=200)
class SubFromSuperWithOrdering(SuperWithOrdering):
anything = models.CharField(max_length=200)
def __str__(self):
# so test output is readable
return 'SubWithOrdering - name:{} extra:{} anything:{}'.format(
self.name, self.extra, self.anything)
}}}
Then:
{{{
#!python
SubFromSuperWithoutOrdering.objects.all().order_by('pk') # works, ordering
by id
SubFromSuperWithOrdering.object.all().order_by('pk') # does not work, it
just orders by 'name' then 'extra'
}}}
I have two tests that demonstrate this.
One that compares the 'ORDER BY' clause of the actual SQL queries:
{{{
#!python
class TestOrderByClause(TestCase):
def
test_it_orders_by_pk_and_id_are_the_same_if_super_does_not_have_ordering(self):
qs = SubFromSuperWithoutOrdering.objects.all()
by_pk = str(qs.order_by('pk').query).split('ORDER BY')[1]
by_id = str(qs.order_by('id').query).split('ORDER BY')[1]
self.assertEquals(by_pk, by_id) # PASSES
def
test_it_orders_by_pk_and_id_are_the_same_if_super_does_have_ordering(self):
qs = SubFromSuperWithOrdering.objects.all()
by_pk = str(qs.order_by('pk').query).split('ORDER BY')[1]
by_id = str(qs.order_by('id').query).split('ORDER BY')[1]
self.assertEquals(by_pk, by_id) # FAILS
# Alternative form?
def test_order_by_clause_is_the_same_if_using_pk_or_id(self):
for test_class in [SubFromSuperWithoutOrdering,
SubFromSuperWithOrdering]:
qs = test_class.objects.all()
by_pk = str(qs.order_by('pk').query).split('ORDER BY')[1]
by_id = str(qs.order_by('id').query).split('ORDER BY')[1]
self.assertEquals(by_pk, by_id, 'Failed for
{}'.format(test_class))
}}}
And one that compares the order of the items actually returned from
executing the query:
{{{
#!python
class TestOrderingOfQuerySetWhenSuperclassHasDefaultOrdering(TestCase):
def setUp(self):
def make_obj(name, extra, anything):
return SubFromSuperWithOrdering.objects.create(name=name,
extra=extra, anything=anything)
self.zoot2 = make_obj( 'Zoot', 'ZZZ - Unknown', 'fourth by
super ordering, first by pk')
self.zoot1 = make_obj( 'Zoot', 'AAA - Handmaiden', 'third by
super ordering, second by pk')
self.arthur2 = make_obj('Arthur', 'ZZZ - Imposter', 'second by
super ordering, third by pk')
self.arthur1 = make_obj('Arthur', 'AAA - The King', 'first by
super ordering, fourth by pk')
def test_it_uses_superclass_ordering_by_default(self):
expected = [self.arthur1, self.arthur2, self.zoot1, self.zoot2]
actual = list(SubFromSuperWithOrdering.objects.all())
self.assertEqual(expected, actual)
def test_it_correctly_orders_with_id(self):
expected = [self.zoot2, self.zoot1, self.arthur2, self.arthur1]
actual =
list(SubFromSuperWithOrdering.objects.all().order_by('id'))
self.assertEqual(expected, actual)
def test_it_correctly_orders_with_pk(self):
expected = [self.zoot2, self.zoot1, self.arthur2, self.arthur1]
actual =
list(SubFromSuperWithOrdering.objects.all().order_by('pk'))
self.assertEqual(expected, actual)
}}}
I'm not very sure where to go looking to fix this. The workaround is to
specify the name of the field used as the PK instead of using 'pk'.
--
--
Ticket URL: <https://code.djangoproject.com/ticket/27536#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/068.9ad1bb3ef525d58a371245b160f56e8a%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.