Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #17813 -- Added a .earliest() method to QuerySet

Thanks a lot to everybody participating in developing this feature.
The patch was developed by multiple people, at least Trac aliases
tonnzor, jimmysong, Fandekasp and slurms.

Stylistic changes added by committer.
  • Loading branch information...
commit fe54377dae1357a7f102d72614a13f0ef8b2dbdf 1 parent 37718eb
Nick Sandford authored January 12, 2013 akaariai committed January 20, 2013
3  django/db/models/manager.py
@@ -172,6 +172,9 @@ def in_bulk(self, *args, **kwargs):
172 172
     def iterator(self, *args, **kwargs):
173 173
         return self.get_query_set().iterator(*args, **kwargs)
174 174
 
  175
+    def earliest(self, *args, **kwargs):
  176
+        return self.get_query_set().earliest(*args, **kwargs)
  177
+
175 178
     def latest(self, *args, **kwargs):
176 179
         return self.get_query_set().latest(*args, **kwargs)
177 180
 
22  django/db/models/query.py
@@ -29,6 +29,7 @@
29 29
 # Pull into this namespace for backwards compatibility.
30 30
 EmptyResultSet = sql.EmptyResultSet
31 31
 
  32
+
32 33
 class QuerySet(object):
33 34
     """
34 35
     Represents a lazy database lookup for a set of objects.
@@ -487,21 +488,28 @@ def get_or_create(self, **kwargs):
487 488
                     # Re-raise the IntegrityError with its original traceback.
488 489
                     six.reraise(*exc_info)
489 490
 
490  
-    def latest(self, field_name=None):
  491
+    def _earliest_or_latest(self, field_name=None, direction="-"):
491 492
         """
492  
-        Returns the latest object, according to the model's 'get_latest_by'
493  
-        option or optional given field_name.
  493
+        Returns the latest object, according to the model's
  494
+        'get_latest_by' option or optional given field_name.
494 495
         """
495  
-        latest_by = field_name or self.model._meta.get_latest_by
496  
-        assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
  496
+        order_by = field_name or getattr(self.model._meta, 'get_latest_by')
  497
+        assert bool(order_by), "earliest() and latest() require either a "\
  498
+            "field_name parameter or 'get_latest_by' in the model"
497 499
         assert self.query.can_filter(), \
498  
-                "Cannot change a query once a slice has been taken."
  500
+            "Cannot change a query once a slice has been taken."
499 501
         obj = self._clone()
500 502
         obj.query.set_limits(high=1)
501 503
         obj.query.clear_ordering()
502  
-        obj.query.add_ordering('-%s' % latest_by)
  504
+        obj.query.add_ordering('%s%s' % (direction, order_by))
503 505
         return obj.get()
504 506
 
  507
+    def earliest(self, field_name=None):
  508
+        return self._earliest_or_latest(field_name=field_name, direction="")
  509
+
  510
+    def latest(self, field_name=None):
  511
+        return self._earliest_or_latest(field_name=field_name, direction="-")
  512
+
505 513
     def in_bulk(self, id_list):
506 514
         """
507 515
         Returns a dictionary mapping each of the given IDs to the object with
3  docs/ref/models/options.txt
@@ -86,7 +86,8 @@ Django quotes column and table names behind the scenes.
86 86
     The name of an orderable field in the model, typically a :class:`DateField`,
87 87
     :class:`DateTimeField`, or :class:`IntegerField`. This specifies the default
88 88
     field to use in your model :class:`Manager`'s
89  
-    :meth:`~django.db.models.query.QuerySet.latest` method.
  89
+    :meth:`~django.db.models.query.QuerySet.latest` and
  90
+    :meth:`~django.db.models.query.QuerySet.earliest` methods.
90 91
 
91 92
     Example::
92 93
 
21  docs/ref/models/querysets.txt
@@ -1477,14 +1477,23 @@ This example returns the latest ``Entry`` in the table, according to the
1477 1477
 
1478 1478
 If your model's :ref:`Meta <meta-options>` specifies
1479 1479
 :attr:`~django.db.models.Options.get_latest_by`, you can leave off the
1480  
-``field_name`` argument to ``latest()``. Django will use the field specified
1481  
-in :attr:`~django.db.models.Options.get_latest_by` by default.
  1480
+``field_name`` argument to ``earliest()`` or ``latest()``. Django will use the
  1481
+field specified in :attr:`~django.db.models.Options.get_latest_by` by default.
1482 1482
 
1483  
-Like :meth:`get()`, ``latest()`` raises
1484  
-:exc:`~django.core.exceptions.DoesNotExist` if there is no object with the given
1485  
-parameters.
  1483
+Like :meth:`get()`, ``earliest()`` and ``latest()`` raise
  1484
+:exc:`~django.core.exceptions.DoesNotExist` if there is no object with the
  1485
+given parameters.
  1486
+
  1487
+Note that ``earliest()`` and ``latest()`` exist purely for convenience and
  1488
+readability.
  1489
+
  1490
+earliest
  1491
+~~~~~~~~
  1492
+
  1493
+.. method:: earliest(field_name=None)
1486 1494
 
1487  
-Note ``latest()`` exists purely for convenience and readability.
  1495
+Works otherwise like :meth:`~django.db.models.query.QuerySet.latest` except
  1496
+the direction is changed.
1488 1497
 
1489 1498
 aggregate
1490 1499
 ~~~~~~~~~
3  docs/releases/1.6.txt
@@ -28,6 +28,9 @@ Minor features
28 28
   undefined if the given ``QuerySet`` isn't ordered and there are more than
29 29
   one ordered values to compare against.
30 30
 
  31
+* Added :meth:`~django.db.models.query.QuerySet.earliest` for symmetry with
  32
+  :meth:`~django.db.models.query.QuerySet.latest`.
  33
+
31 34
 Backwards incompatible changes in 1.6
32 35
 =====================================
33 36
 
0  tests/modeltests/get_latest/__init__.py → tests/modeltests/get_earliest_or_latest/__init__.py
File renamed without changes
8  tests/modeltests/get_latest/models.py → tests/modeltests/get_earliest_or_latest/models.py
@@ -9,10 +9,8 @@
9 9
 """
10 10
 
11 11
 from django.db import models
12  
-from django.utils.encoding import python_2_unicode_compatible
13 12
 
14 13
 
15  
-@python_2_unicode_compatible
16 14
 class Article(models.Model):
17 15
     headline = models.CharField(max_length=100)
18 16
     pub_date = models.DateField()
@@ -20,15 +18,15 @@ class Article(models.Model):
20 18
     class Meta:
21 19
         get_latest_by = 'pub_date'
22 20
 
23  
-    def __str__(self):
  21
+    def __unicode__(self):
24 22
         return self.headline
25 23
 
26  
-@python_2_unicode_compatible
  24
+
27 25
 class Person(models.Model):
28 26
     name = models.CharField(max_length=30)
29 27
     birthday = models.DateField()
30 28
 
31 29
     # Note that this model doesn't have "get_latest_by" set.
32 30
 
33  
-    def __str__(self):
  31
+    def __unicode__(self):
34 32
         return self.name
123  tests/modeltests/get_earliest_or_latest/tests.py
... ...
@@ -0,0 +1,123 @@
  1
+from __future__ import absolute_import
  2
+
  3
+from datetime import datetime
  4
+
  5
+from django.test import TestCase
  6
+
  7
+from .models import Article, Person
  8
+
  9
+
  10
+class EarliestOrLatestTests(TestCase):
  11
+    """Tests for the earliest() and latest() objects methods"""
  12
+
  13
+    def tearDown(self):
  14
+        """Makes sure Article has a get_latest_by"""
  15
+        if not Article._meta.get_latest_by:
  16
+            Article._meta.get_latest_by = 'pub_date'
  17
+
  18
+    def test_earliest(self):
  19
+        # Because no Articles exist yet, earliest() raises ArticleDoesNotExist.
  20
+        self.assertRaises(Article.DoesNotExist, Article.objects.earliest)
  21
+
  22
+        a1 = Article.objects.create(
  23
+            headline="Article 1", pub_date=datetime(2005, 7, 26),
  24
+            expire_date=datetime(2005, 9, 1)
  25
+        )
  26
+        a2 = Article.objects.create(
  27
+            headline="Article 2", pub_date=datetime(2005, 7, 27),
  28
+            expire_date=datetime(2005, 7, 28)
  29
+        )
  30
+        a3 = Article.objects.create(
  31
+            headline="Article 3", pub_date=datetime(2005, 7, 28),
  32
+            expire_date=datetime(2005, 8, 27)
  33
+        )
  34
+        a4 = Article.objects.create(
  35
+            headline="Article 4", pub_date=datetime(2005, 7, 28),
  36
+            expire_date=datetime(2005, 7, 30)
  37
+        )
  38
+
  39
+        # Get the earliest Article.
  40
+        self.assertEqual(Article.objects.earliest(), a1)
  41
+        # Get the earliest Article that matches certain filters.
  42
+        self.assertEqual(
  43
+            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).earliest(),
  44
+            a2
  45
+        )
  46
+
  47
+        # Pass a custom field name to earliest() to change the field that's used
  48
+        # to determine the earliest object.
  49
+        self.assertEqual(Article.objects.earliest('expire_date'), a2)
  50
+        self.assertEqual(Article.objects.filter(
  51
+            pub_date__gt=datetime(2005, 7, 26)).earliest('expire_date'), a2)
  52
+
  53
+        # Ensure that earliest() overrides any other ordering specified on the
  54
+        # query. Refs #11283.
  55
+        self.assertEqual(Article.objects.order_by('id').earliest(), a1)
  56
+
  57
+        # Ensure that error is raised if the user forgot to add a get_latest_by
  58
+        # in the Model.Meta
  59
+        Article.objects.model._meta.get_latest_by = None
  60
+        self.assertRaisesMessage(
  61
+            AssertionError,
  62
+            "earliest() and latest() require either a field_name parameter or "
  63
+            "'get_latest_by' in the model",
  64
+            lambda: Article.objects.earliest(),
  65
+        )
  66
+
  67
+    def test_latest(self):
  68
+        # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
  69
+        self.assertRaises(Article.DoesNotExist, Article.objects.latest)
  70
+
  71
+        a1 = Article.objects.create(
  72
+            headline="Article 1", pub_date=datetime(2005, 7, 26),
  73
+            expire_date=datetime(2005, 9, 1)
  74
+        )
  75
+        a2 = Article.objects.create(
  76
+            headline="Article 2", pub_date=datetime(2005, 7, 27),
  77
+            expire_date=datetime(2005, 7, 28)
  78
+        )
  79
+        a3 = Article.objects.create(
  80
+            headline="Article 3", pub_date=datetime(2005, 7, 27),
  81
+            expire_date=datetime(2005, 8, 27)
  82
+        )
  83
+        a4 = Article.objects.create(
  84
+            headline="Article 4", pub_date=datetime(2005, 7, 28),
  85
+            expire_date=datetime(2005, 7, 30)
  86
+        )
  87
+
  88
+        # Get the latest Article.
  89
+        self.assertEqual(Article.objects.latest(), a4)
  90
+        # Get the latest Article that matches certain filters.
  91
+        self.assertEqual(
  92
+            Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(),
  93
+            a1
  94
+        )
  95
+
  96
+        # Pass a custom field name to latest() to change the field that's used
  97
+        # to determine the latest object.
  98
+        self.assertEqual(Article.objects.latest('expire_date'), a1)
  99
+        self.assertEqual(
  100
+            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'),
  101
+            a3,
  102
+        )
  103
+
  104
+        # Ensure that latest() overrides any other ordering specified on the query. Refs #11283.
  105
+        self.assertEqual(Article.objects.order_by('id').latest(), a4)
  106
+
  107
+        # Ensure that error is raised if the user forgot to add a get_latest_by
  108
+        # in the Model.Meta
  109
+        Article.objects.model._meta.get_latest_by = None
  110
+        self.assertRaisesMessage(
  111
+            AssertionError,
  112
+            "earliest() and latest() require either a field_name parameter or "
  113
+            "'get_latest_by' in the model",
  114
+            lambda: Article.objects.latest(),
  115
+        )
  116
+
  117
+    def test_latest_manual(self):
  118
+        # You can still use latest() with a model that doesn't have
  119
+        # "get_latest_by" set -- just pass in the field name manually.
  120
+        p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
  121
+        p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
  122
+        self.assertRaises(AssertionError, Person.objects.latest)
  123
+        self.assertEqual(Person.objects.latest("birthday"), p2)
58  tests/modeltests/get_latest/tests.py
... ...
@@ -1,58 +0,0 @@
1  
-from __future__ import absolute_import
2  
-
3  
-from datetime import datetime
4  
-
5  
-from django.test import TestCase
6  
-
7  
-from .models import Article, Person
8  
-
9  
-
10  
-class LatestTests(TestCase):
11  
-    def test_latest(self):
12  
-        # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
13  
-        self.assertRaises(Article.DoesNotExist, Article.objects.latest)
14  
-
15  
-        a1 = Article.objects.create(
16  
-            headline="Article 1", pub_date=datetime(2005, 7, 26),
17  
-            expire_date=datetime(2005, 9, 1)
18  
-        )
19  
-        a2 = Article.objects.create(
20  
-            headline="Article 2", pub_date=datetime(2005, 7, 27),
21  
-            expire_date=datetime(2005, 7, 28)
22  
-        )
23  
-        a3 = Article.objects.create(
24  
-            headline="Article 3", pub_date=datetime(2005, 7, 27),
25  
-            expire_date=datetime(2005, 8, 27)
26  
-        )
27  
-        a4 = Article.objects.create(
28  
-            headline="Article 4", pub_date=datetime(2005, 7, 28),
29  
-            expire_date=datetime(2005, 7, 30)
30  
-        )
31  
-
32  
-        # Get the latest Article.
33  
-        self.assertEqual(Article.objects.latest(), a4)
34  
-        # Get the latest Article that matches certain filters.
35  
-        self.assertEqual(
36  
-            Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(),
37  
-            a1
38  
-        )
39  
-
40  
-        # Pass a custom field name to latest() to change the field that's used
41  
-        # to determine the latest object.
42  
-        self.assertEqual(Article.objects.latest('expire_date'), a1)
43  
-        self.assertEqual(
44  
-            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'),
45  
-            a3,
46  
-        )
47  
-
48  
-        # Ensure that latest() overrides any other ordering specified on the query. Refs #11283.
49  
-        self.assertEqual(Article.objects.order_by('id').latest(), a4)
50  
-
51  
-    def test_latest_manual(self):
52  
-        # You can still use latest() with a model that doesn't have
53  
-        # "get_latest_by" set -- just pass in the field name manually.
54  
-        p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
55  
-        p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
56  
-        self.assertRaises(AssertionError, Person.objects.latest)
57  
-
58  
-        self.assertEqual(Person.objects.latest("birthday"), p2)

0 notes on commit fe54377

Anssi Kääriäinen

How did I miss this one...

Will fix.

EDIT: Technically, no it wont break anything as the str isn't ever called, still a mistake of course. And that is the reason I missed this, this does run on py3 just fine.

Simon Charette

Ahhh right, no assertQuerySetEqual to stumble upon.

Please sign in to comment.
Something went wrong with that request. Please try again.