Opened 8 years ago

Last modified 8 years ago

#27536 closed Bug

order_by('pk') is not obeyed if superclass has default ordering — at Initial Version

Reported by: Andrew Dodd Owned by: nobody
Component: Database layer (models, ORM) Version: 1.10
Severity: Normal Keywords: order_by ordering
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

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:

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:

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:

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:

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'.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top