summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorVIZZARD-X <vigneshanandmay13@gmail.com>2026-01-10 02:26:37 +0530
committerJacob Walls <jacobtylerwalls@gmail.com>2026-02-13 16:58:36 -0500
commit08b4dfc5734f5d2fce685eabcd65385a6656db2f (patch)
tree468d1e7db12407c049e892181eb3ac3866b3ab05 /django
parent3dea5fed077e33c7d8bca4b5eeade5420cb05d27 (diff)
Fixed #36857 -- Added QuerySet.totally_ordered property.
Thanks Simon Charette for the idea.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/admin/views/main.py69
-rw-r--r--django/db/models/query.py76
2 files changed, 78 insertions, 67 deletions
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index cd40f14ce3..c510045db3 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -417,72 +417,9 @@ class ChangeList:
# Add the given query's ordering fields, if any.
ordering.extend(queryset.query.order_by)
- return self._get_deterministic_ordering(ordering)
-
- def _get_deterministic_ordering(self, ordering):
- """
- Ensure a deterministic order across all database backends. Search for a
- single field or unique together set of fields providing a total
- ordering. If these are missing, augment the ordering with a descendant
- primary key.
- """
- ordering = list(ordering)
- ordering_fields = set()
- total_ordering_fields = {"pk"} | {
- field.attname
- for field in self.lookup_opts.fields
- if field.unique and not field.null
- }
- for part in ordering:
- # Search for single field providing a total ordering.
- field_name = None
- if isinstance(part, str):
- field_name = part.lstrip("-")
- elif isinstance(part, F):
- field_name = part.name
- elif isinstance(part, OrderBy) and isinstance(part.expression, F):
- field_name = part.expression.name
- if field_name:
- # Normalize attname references by using get_field().
- try:
- field = self.lookup_opts.get_field(field_name)
- except FieldDoesNotExist:
- # Could be "?" for random ordering or a related field
- # lookup. Skip this part of introspection for now.
- continue
- # Ordering by a related field name orders by the referenced
- # model's ordering. Skip this part of introspection for now.
- if field.remote_field and field_name == field.name:
- continue
- if field.attname in total_ordering_fields:
- break
- ordering_fields.add(field.attname)
- else:
- # No single total ordering field, try unique_together and total
- # unique constraints.
- constraint_field_names = (
- *self.lookup_opts.unique_together,
- *(
- constraint.fields
- for constraint in self.lookup_opts.total_unique_constraints
- ),
- )
- for field_names in constraint_field_names:
- # Normalize attname references by using get_field().
- fields = [
- self.lookup_opts.get_field(field_name) for field_name in field_names
- ]
- # Composite unique constraints containing a nullable column
- # cannot ensure total ordering.
- if any(field.null for field in fields):
- continue
- if ordering_fields.issuperset(field.attname for field in fields):
- break
- else:
- # If no set of unique fields is present in the ordering, rely
- # on the primary key to provide total ordering.
- ordering.append("-pk")
- return ordering
+ if queryset.order_by(*ordering).totally_ordered:
+ return ordering
+ return ordering + ["-pk"]
def get_ordering_field_columns(self):
"""
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 76d0f449a6..cbe77caea9 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -26,7 +26,7 @@ from django.db import (
from django.db.models import AutoField, DateField, DateTimeField, Field, Max, sql
from django.db.models.constants import LOOKUP_SEP, OnConflict
from django.db.models.deletion import Collector
-from django.db.models.expressions import Case, DatabaseDefault, F, Value, When
+from django.db.models.expressions import Case, DatabaseDefault, F, OrderBy, Value, When
from django.db.models.fetch_modes import FETCH_ONE
from django.db.models.functions import Cast, Trunc
from django.db.models.query_utils import FilteredRelation, Q
@@ -1975,6 +1975,80 @@ class QuerySet(AltersData):
return False
@property
+ def totally_ordered(self):
+ """
+ Returns True if the QuerySet is ordered and the ordering is
+ deterministic. This requires that the ordering includes a field
+ (or set of fields) that is unique and non-nullable.
+
+ For queries involving a GROUP BY clause, the model's default
+ ordering is ignored. Ordering specified via .extra(order_by=...)
+ is also ignored.
+ """
+ if not self.ordered:
+ return False
+ ordering = self.query.order_by
+ if not ordering and self.query.default_ordering:
+ ordering = self.query.get_meta().ordering
+ if not ordering:
+ return False
+ opts = self.model._meta
+ pk_fields = {f.attname for f in opts.pk_fields}
+ ordering_fields = set()
+ for part in ordering:
+ # Search for single field providing a total ordering.
+ field_name = None
+ if isinstance(part, str):
+ field_name = part.lstrip("-")
+ elif isinstance(part, F):
+ field_name = part.name
+ elif isinstance(part, OrderBy) and isinstance(part.expression, F):
+ field_name = part.expression.name
+ if field_name:
+ if field_name == "pk":
+ return True
+ # Normalize attname references by using get_field().
+ try:
+ field = opts.get_field(field_name)
+ except exceptions.FieldDoesNotExist:
+ # Could be "?" for random ordering or a related field
+ # lookup. Skip this part of introspection for now.
+ continue
+ # Ordering by a related field name orders by the referenced
+ # model's ordering. Skip this part of introspection for now.
+ if field.remote_field and field_name == field.name:
+ continue
+ if field.attname in pk_fields and len(pk_fields) == 1:
+ return True
+ if field.unique and not field.null:
+ return True
+ ordering_fields.add(field.attname)
+
+ # Account for members of a CompositePrimaryKey.
+ if ordering_fields.issuperset(pk_fields):
+ return True
+ # No single total ordering field, try unique_together and total
+ # unique constraints.
+ constraint_field_names = (
+ *opts.unique_together,
+ *(constraint.fields for constraint in opts.total_unique_constraints),
+ )
+ for field_names in constraint_field_names:
+ # Normalize attname references by using get_field().
+ try:
+ fields = [opts.get_field(field_name) for field_name in field_names]
+ except exceptions.FieldDoesNotExist:
+ continue
+ # Composite unique constraints containing a nullable column
+ # cannot ensure total ordering.
+ if any(field.null for field in fields):
+ continue
+ if ordering_fields.issuperset(field.attname for field in fields):
+ return True
+
+ return False
+
+ @property
def db(self):
"""Return the database used if this query is executed now."""
if self._for_write: