From ea9a0857d4922fab1f9146f3a7828b67281edc89 Mon Sep 17 00:00:00 2001 From: Selwin Ong Date: Tue, 21 May 2013 18:35:12 +0300 Subject: [PATCH] Fixed #19326 -- Added first() and last() methods to QuerySet --- django/db/models/manager.py | 6 ++++++ django/db/models/query.py | 20 +++++++++++++++++ docs/ref/models/querysets.txt | 30 ++++++++++++++++++++++++++ docs/releases/1.6.txt | 5 +++++ tests/get_earliest_or_latest/tests.py | 31 +++++++++++++++++++++++++++ 5 files changed, 92 insertions(+) diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 43a8264f113..a1aa79f8098 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -186,6 +186,12 @@ class Manager(six.with_metaclass(RenameManagerMethods)): def latest(self, *args, **kwargs): return self.get_queryset().latest(*args, **kwargs) + def first(self): + return self.get_queryset().first() + + def last(self): + return self.get_queryset().last() + def order_by(self, *args, **kwargs): return self.get_queryset().order_by(*args, **kwargs) diff --git a/django/db/models/query.py b/django/db/models/query.py index 4313d044eef..f2015f57a8c 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -498,6 +498,26 @@ class QuerySet(object): def latest(self, field_name=None): return self._earliest_or_latest(field_name=field_name, direction="-") + def first(self): + """ + Returns the first object of a query, returns None if no match is found. + """ + qs = self if self.ordered else self.order_by('pk') + try: + return qs[0] + except IndexError: + return None + + def last(self): + """ + Returns the last object of a query, returns None if no match is found. + """ + qs = self.reverse() if self.ordered else self.order_by('-pk') + try: + return qs[0] + except IndexError: + return None + def in_bulk(self, id_list): """ Returns a dictionary mapping each of the given IDs to the object with diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 2dec00afc18..b9a40374407 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1585,6 +1585,36 @@ earliest Works otherwise like :meth:`~django.db.models.query.QuerySet.latest` except the direction is changed. +first +~~~~~ +.. method:: first() + +.. versionadded:: 1.6 + +Returns the first object matched by the queryset, or ``None`` if there +is no matching object. If the ``QuerySet`` has no ordering defined, then the +queryset is automatically ordered by the primary key. + +Example:: + + p = Article.objects.order_by('title', 'pub_date').first() + +Note that ``first()`` is a convenience method, the following code sample is +equivalent to the above example:: + + try: + p = Article.objects.order_by('title', 'pub_date')[0] + except IndexError: + p = None + +last +~~~~ +.. method:: last() + +.. versionadded:: 1.6 + +Works like :meth:`first()` except the ordering is reversed. + aggregate ~~~~~~~~~ diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index a417e81f621..29b4569d4e4 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -253,6 +253,11 @@ Minor features allow users to specify the primary keys of objects they want to dump. This option can only be used with one model. +* Added ``QuerySet`` methods :meth:`~django.db.models.query.QuerySet.first` + and :meth:`~django.db.models.query.QuerySet.last` which are convenience + methods returning the first or last object matching the filters. Returns + ``None`` if there are no objects matching. + Backwards incompatible changes in 1.6 ===================================== diff --git a/tests/get_earliest_or_latest/tests.py b/tests/get_earliest_or_latest/tests.py index 6317a0974ce..8d16af95870 100644 --- a/tests/get_earliest_or_latest/tests.py +++ b/tests/get_earliest_or_latest/tests.py @@ -121,3 +121,34 @@ class EarliestOrLatestTests(TestCase): p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3)) self.assertRaises(AssertionError, Person.objects.latest) self.assertEqual(Person.objects.latest("birthday"), p2) + + def test_first(self): + p1 = Person.objects.create(name="Bob", birthday=datetime(1950, 1, 1)) + p2 = Person.objects.create(name="Alice", birthday=datetime(1961, 2, 3)) + self.assertEqual( + Person.objects.first(), p1) + self.assertEqual( + Person.objects.order_by('name').first(), p2) + self.assertEqual( + Person.objects.filter(birthday__lte=datetime(1955, 1, 1)).first(), + p1) + self.assertIs( + Person.objects.filter(birthday__lte=datetime(1940, 1, 1)).first(), + None) + + def test_last(self): + p1 = Person.objects.create( + name="Alice", birthday=datetime(1950, 1, 1)) + p2 = Person.objects.create( + name="Bob", birthday=datetime(1960, 2, 3)) + # Note: by default PK ordering. + self.assertEqual( + Person.objects.last(), p2) + self.assertEqual( + Person.objects.order_by('-name').last(), p1) + self.assertEqual( + Person.objects.filter(birthday__lte=datetime(1955, 1, 1)).last(), + p1) + self.assertIs( + Person.objects.filter(birthday__lte=datetime(1940, 1, 1)).last(), + None)