Skip to content

Commit 51fda6e

Browse files
committed
Inherit Meta.ordering from abstract base models
When a model defined its own ``Meta`` (e.g. to set ``table``) but did not set ``ordering``, the ordering declared on an abstract base model's ``Meta`` was lost, because ``ModelMeta.__new__`` reads ``Meta`` only from the class's own attributes. As a result queries on the subclass came back unordered. If the subclass's own ``Meta`` does not define ``ordering``, inherit the default ordering from the first base model that has one. A subclass that defines its own ``ordering`` (including an explicitly empty one) is unaffected. Closes #2046
1 parent af34316 commit 51fda6e

3 files changed

Lines changed: 44 additions & 0 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Fixed
2626
- ``QuerySet.count()`` now matches the limited query result for the LIMIT/OFFSET edge cases: it returns ``0`` (instead of a negative number) when ``offset()`` exceeds the total row count, and ``0`` (instead of the total) for ``limit(0)``. (#2208)
2727
- Field declarations on models now resolve to their concrete type (e.g. ``CharField[str]``) in Pyright/Pylance instead of ``Field[Unknown]``; the ``Field.__new__`` type-check stub now returns ``Self``. (#2216)
2828
- ``TransactionContext`` now returns a ``TransactionalDBClient`` instead of a raw database connection. This change gives the correct inferred type for the transaction context. (#2232)
29+
- A model with its own ``Meta`` now inherits ``Meta.ordering`` from its abstract base model when it does not define its own. (#2046)
2930

3031

3132
1.1.7

tests/test_inheritance.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,37 @@ async def test_basic(db):
1515
await model.save()
1616
assert model.created_at is not None
1717
assert model.modified_at is not None
18+
19+
20+
def test_abstract_meta_ordering_is_inherited():
21+
"""A subclass without its own ``Meta.ordering`` inherits the abstract
22+
base's ordering, while a subclass that defines its own keeps it and an
23+
explicitly empty ordering stays empty (#2046)."""
24+
from tortoise import fields
25+
from tortoise.models import Model
26+
27+
class OrderedBase(Model):
28+
value = fields.IntField()
29+
30+
class Meta:
31+
abstract = True
32+
ordering = ["-value"]
33+
34+
class InheritsOrdering(OrderedBase):
35+
class Meta:
36+
abstract = True
37+
38+
class OwnOrdering(OrderedBase):
39+
class Meta:
40+
abstract = True
41+
ordering = ["value"]
42+
43+
class EmptyOrdering(OrderedBase):
44+
class Meta:
45+
abstract = True
46+
ordering = []
47+
48+
assert OrderedBase._meta._default_ordering != ()
49+
assert InheritsOrdering._meta._default_ordering == OrderedBase._meta._default_ordering
50+
assert OwnOrdering._meta._default_ordering != OrderedBase._meta._default_ordering
51+
assert EmptyOrdering._meta._default_ordering == ()

tortoise/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,15 @@ def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> M
539539
pk_attr,
540540
)
541541

542+
# Inherit the default ordering from an abstract base model when the
543+
# subclass's own Meta does not define one (#2046).
544+
if not hasattr(meta_class, "ordering"):
545+
for base in bases:
546+
base_meta = getattr(base, "_meta", None)
547+
if base_meta is not None and base_meta._default_ordering:
548+
meta._default_ordering = base_meta._default_ordering
549+
break
550+
542551
new_class = super().__new__(cls, name, bases, attrs)
543552
for field in meta.fields_map.values():
544553
field.model = new_class # type: ignore

0 commit comments

Comments
 (0)