diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 2846f2bb3..7fcc91fe0 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -60,6 +60,7 @@ Contributors * Abdeldjalil Hezouat ``@Abdeldjalil-H`` * Andrea Magistà ``@vlakius`` * Daniel Szucs ``@Quasar6X`` +* Rui Catarino ``@ruitcatarino`` Special Thanks ============== diff --git a/tests/test_model_methods.py b/tests/test_model_methods.py index 2e9e49be5..02bce5cd4 100644 --- a/tests/test_model_methods.py +++ b/tests/test_model_methods.py @@ -210,6 +210,10 @@ async def test_first(self): mdl = await self.cls.first() self.assertEqual(self.mdl.id, mdl.id) + async def test_last(self): + mdl = await self.cls.last() + self.assertEqual(self.mdl.id, mdl.id) + async def test_latest(self): mdl = await self.cls.latest("name") self.assertEqual(self.mdl.id, mdl.id) diff --git a/tests/test_queryset.py b/tests/test_queryset.py index 8db89f202..ef734aca2 100644 --- a/tests/test_queryset.py +++ b/tests/test_queryset.py @@ -272,6 +272,35 @@ async def test_first(self): None, ) + async def test_last(self): + self.assertEqual( + (await IntFields.all().order_by("intnum").filter(intnum__gte=40).last()).intnum, 97 + ) + self.assertEqual( + (await IntFields.all().order_by("intnum").filter(intnum__gte=40).last().values())[ + "intnum" + ], + 97, + ) + self.assertEqual( + (await IntFields.all().order_by("intnum").filter(intnum__gte=40).last().values_list())[ + 1 + ], + 97, + ) + + self.assertEqual( + await IntFields.all().order_by("intnum").filter(intnum__gte=400).last(), None + ) + self.assertEqual( + await IntFields.all().order_by("intnum").filter(intnum__gte=400).last().values(), None + ) + self.assertEqual( + await IntFields.all().order_by("intnum").filter(intnum__gte=400).last().values_list(), + None, + ) + self.assertEqual((await IntFields.all().filter(intnum__gte=40).last()).intnum, 97) + async def test_latest(self): self.assertEqual((await IntFields.all().latest("intnum")).intnum, 97) self.assertEqual( diff --git a/tortoise/models.py b/tortoise/models.py index 6f30f98fb..f027fba98 100644 --- a/tortoise/models.py +++ b/tortoise/models.py @@ -1247,6 +1247,13 @@ def first(cls, using_db: Optional[BaseDBAsyncClient] = None) -> QuerySetSingle[O """ return cls._db_queryset(using_db).first() + @classmethod + def last(cls, using_db: Optional[BaseDBAsyncClient] = None) -> QuerySetSingle[Optional[Self]]: + """ + Generates a QuerySet that returns the last record. + """ + return cls._db_queryset(using_db).last() + @classmethod def filter(cls, *args: Q, **kwargs: Any) -> QuerySet[Self]: """ diff --git a/tortoise/queryset.py b/tortoise/queryset.py index 26ade3861..54de93a0f 100644 --- a/tortoise/queryset.py +++ b/tortoise/queryset.py @@ -789,6 +789,29 @@ def first(self) -> QuerySetSingle[Optional[MODEL]]: queryset._single = True return queryset # type: ignore + def last(self) -> QuerySetSingle[Optional[MODEL]]: + """ + Limit queryset to one object and return the last object instead of list. + """ + queryset = self._clone() + + if queryset._orderings: + new_ordering = [ + (field, Order.desc if order_type == Order.asc else Order.asc) + for field, order_type in queryset._orderings + ] + elif pk := self.model._meta.pk: + new_ordering = [(pk.model_field_name, Order.desc)] + else: + raise FieldError( + f"QuerySet has no ordering and model {self.model.__name__} has no pk defined" + ) + + queryset._orderings = new_ordering + queryset._limit = 1 + queryset._single = True + return queryset # type: ignore + def get(self, *args: Q, **kwargs: Any) -> QuerySetSingle[MODEL]: """ Fetch exactly one object matching the parameters.