Opened 5 weeks ago

Last modified 5 weeks ago

#36644 assigned New feature

Enable using an empty order_by() to disable implicit primary key ordering in first()

Reported by: Lily Owned by: Mridul
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Jake Howard Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

As discussed in https://github.com/django/new-features/issues/71, update the interaction between order_by and first to enable this behaviour:

# Existing
MyModel.objects.first()  # first, ordered by `pk`
MyModel.objects.order_by("pk").first()  # as above
MyModel.objects.order_by("name").first()  # first, ordered by `name`

# New
MyModel.objects.order_by().first()  # first, but in whatever order the database returns it

This will interact with default orderings (`Meta.ordering`), which will need some careful handling.

Change History (3)

comment:1 by Simon Charette, 5 weeks ago

Triage Stage: UnreviewedAccepted

comment:2 by Mridul, 5 weeks ago

Owner: set to Mridul
Status: newassigned

comment:3 by Jacob Walls, 5 weeks ago

Cc: Jake Howard added

For folks checking the forum thread, when Tom Carrick summarized the options discussed here, this was "option 2", to which I did not see specific objections afterward.


This will interact with ​default orderings (Meta.ordering), which will need some careful handling.

I think that looks like this:

  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index b404fd1875..4d1218ea89 100644
    a b class QuerySet(AltersData):  
    11351135
    11361136    def first(self):
    11371137        """Return the first object of a query or None if no match is found."""
     1138        # RemovedInDjango70Warning: replace with:
     1139        # if self.ordered or not self.query.default_ordering:
    11381140        if self.ordered:
    11391141            queryset = self
    11401142        else:
     1143            if not self.query.default_ordering:
     1144                warnings.warn(..., RemovedInDjango70Warning, skip_file_prefixes=django_file_prefixes())
    11411145            self._check_ordering_first_last_queryset_aggregation(method="first")
    11421146            queryset = self.order_by("pk")
    11431147        for obj in queryset[:1]:
    class QuerySet(AltersData):  
    11481152
    11491153    def last(self):
    11501154        """Return the last object of a query or None if no match is found."""
     1155        # RemovedInDjango70Warning: replace with:
     1156        # if self.ordered or not self.query.default_ordering:
    11511157        if self.ordered:
    11521158            queryset = self.reverse()
    11531159        else:
     1160            if not self.query.default_ordering:
     1161                warnings.warn(..., RemovedInDjango70Warning, skip_file_prefixes=django_file_prefixes())
    11541162            self._check_ordering_first_last_queryset_aggregation(method="last")
    11551163            queryset = self.order_by("-pk")
    11561164        for obj in queryset[:1]:
  • tests/queries/test_qs_combinators.py

    diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py
    index e329d0c4f0..79cfb78dd7 100644
    a b class QuerySetSetOperationTests(TestCase):  
    416416        base_qs = Author.objects.order_by()
    417417        qs1 = base_qs.filter(name="a1")
    418418        qs2 = base_qs.filter(name="a2")
    419         self.assertEqual(qs1.union(qs2).first(), a1)
     419        self.assertEqual(qs1.union(qs2).order_by("pk").first(), a1)
    420420
    421421    def test_union_multiple_models_with_values_list_and_order(self):
    422422        reserved_name = ReservedName.objects.create(name="rn1", order=0)

I think this will need a deprecation (notice the edited test). We will be enforcing some more explicitness for users who must to clear an ordering for the sake of doing a union but then need to add it back to retain the prior behavior of first(), but I think that trade-off is worth it.

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