Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
@slurms slurms authored akaariai committed
View
3  django/db/models/manager.py
@@ -172,6 +172,9 @@ def in_bulk(self, *args, **kwargs):
def iterator(self, *args, **kwargs):
return self.get_query_set().iterator(*args, **kwargs)
+ def earliest(self, *args, **kwargs):
+ return self.get_query_set().earliest(*args, **kwargs)
+
def latest(self, *args, **kwargs):
return self.get_query_set().latest(*args, **kwargs)
View
22 django/db/models/query.py
@@ -29,6 +29,7 @@
# Pull into this namespace for backwards compatibility.
EmptyResultSet = sql.EmptyResultSet
+
class QuerySet(object):
"""
Represents a lazy database lookup for a set of objects.
@@ -487,21 +488,28 @@ def get_or_create(self, **kwargs):
# Re-raise the IntegrityError with its original traceback.
six.reraise(*exc_info)
- def latest(self, field_name=None):
+ def _earliest_or_latest(self, field_name=None, direction="-"):
"""
- Returns the latest object, according to the model's 'get_latest_by'
- option or optional given field_name.
+ Returns the latest object, according to the model's
+ 'get_latest_by' option or optional given field_name.
"""
- latest_by = field_name or self.model._meta.get_latest_by
- assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
+ order_by = field_name or getattr(self.model._meta, 'get_latest_by')
+ assert bool(order_by), "earliest() and latest() require either a "\
+ "field_name parameter or 'get_latest_by' in the model"
assert self.query.can_filter(), \
- "Cannot change a query once a slice has been taken."
+ "Cannot change a query once a slice has been taken."
obj = self._clone()
obj.query.set_limits(high=1)
obj.query.clear_ordering()
- obj.query.add_ordering('-%s' % latest_by)
+ obj.query.add_ordering('%s%s' % (direction, order_by))
return obj.get()
+ def earliest(self, field_name=None):
+ return self._earliest_or_latest(field_name=field_name, direction="")
+
+ def latest(self, field_name=None):
+ return self._earliest_or_latest(field_name=field_name, direction="-")
+
def in_bulk(self, id_list):
"""
Returns a dictionary mapping each of the given IDs to the object with
View
3  docs/ref/models/options.txt
@@ -86,7 +86,8 @@ Django quotes column and table names behind the scenes.
The name of an orderable field in the model, typically a :class:`DateField`,
:class:`DateTimeField`, or :class:`IntegerField`. This specifies the default
field to use in your model :class:`Manager`'s
- :meth:`~django.db.models.query.QuerySet.latest` method.
+ :meth:`~django.db.models.query.QuerySet.latest` and
+ :meth:`~django.db.models.query.QuerySet.earliest` methods.
Example::
View
21 docs/ref/models/querysets.txt
@@ -1477,14 +1477,23 @@ This example returns the latest ``Entry`` in the table, according to the
If your model's :ref:`Meta <meta-options>` specifies
:attr:`~django.db.models.Options.get_latest_by`, you can leave off the
-``field_name`` argument to ``latest()``. Django will use the field specified
-in :attr:`~django.db.models.Options.get_latest_by` by default.
+``field_name`` argument to ``earliest()`` or ``latest()``. Django will use the
+field specified in :attr:`~django.db.models.Options.get_latest_by` by default.
-Like :meth:`get()`, ``latest()`` raises
-:exc:`~django.core.exceptions.DoesNotExist` if there is no object with the given
-parameters.
+Like :meth:`get()`, ``earliest()`` and ``latest()`` raise
+:exc:`~django.core.exceptions.DoesNotExist` if there is no object with the
+given parameters.
+
+Note that ``earliest()`` and ``latest()`` exist purely for convenience and
+readability.
+
+earliest
+~~~~~~~~
+
+.. method:: earliest(field_name=None)
-Note ``latest()`` exists purely for convenience and readability.
+Works otherwise like :meth:`~django.db.models.query.QuerySet.latest` except
+the direction is changed.
aggregate
~~~~~~~~~
View
3  docs/releases/1.6.txt
@@ -28,6 +28,9 @@ Minor features
undefined if the given ``QuerySet`` isn't ordered and there are more than
one ordered values to compare against.
+* Added :meth:`~django.db.models.query.QuerySet.earliest` for symmetry with
+ :meth:`~django.db.models.query.QuerySet.latest`.
+
Backwards incompatible changes in 1.6
=====================================
View
0  tests/modeltests/get_latest/__init__.py → ...ltests/get_earliest_or_latest/__init__.py
File renamed without changes
View
8 tests/modeltests/get_latest/models.py → ...deltests/get_earliest_or_latest/models.py
@@ -9,10 +9,8 @@
"""
from django.db import models
-from django.utils.encoding import python_2_unicode_compatible
-@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
@@ -20,15 +18,15 @@ class Article(models.Model):
class Meta:
get_latest_by = 'pub_date'
- def __str__(self):
+ def __unicode__(self):
@charettes Collaborator

Won't this break Python 3?

@akaariai Collaborator

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.

@charettes Collaborator

Ahhh right, no assertQuerySetEqual to stumble upon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
return self.headline
-@python_2_unicode_compatible
+
class Person(models.Model):
name = models.CharField(max_length=30)
birthday = models.DateField()
# Note that this model doesn't have "get_latest_by" set.
- def __str__(self):
+ def __unicode__(self):
return self.name
View
123 tests/modeltests/get_earliest_or_latest/tests.py
@@ -0,0 +1,123 @@
+from __future__ import absolute_import
+
+from datetime import datetime
+
+from django.test import TestCase
+
+from .models import Article, Person
+
+
+class EarliestOrLatestTests(TestCase):
+ """Tests for the earliest() and latest() objects methods"""
+
+ def tearDown(self):
+ """Makes sure Article has a get_latest_by"""
+ if not Article._meta.get_latest_by:
+ Article._meta.get_latest_by = 'pub_date'
+
+ def test_earliest(self):
+ # Because no Articles exist yet, earliest() raises ArticleDoesNotExist.
+ self.assertRaises(Article.DoesNotExist, Article.objects.earliest)
+
+ a1 = Article.objects.create(
+ headline="Article 1", pub_date=datetime(2005, 7, 26),
+ expire_date=datetime(2005, 9, 1)
+ )
+ a2 = Article.objects.create(
+ headline="Article 2", pub_date=datetime(2005, 7, 27),
+ expire_date=datetime(2005, 7, 28)
+ )
+ a3 = Article.objects.create(
+ headline="Article 3", pub_date=datetime(2005, 7, 28),
+ expire_date=datetime(2005, 8, 27)
+ )
+ a4 = Article.objects.create(
+ headline="Article 4", pub_date=datetime(2005, 7, 28),
+ expire_date=datetime(2005, 7, 30)
+ )
+
+ # Get the earliest Article.
+ self.assertEqual(Article.objects.earliest(), a1)
+ # Get the earliest Article that matches certain filters.
+ self.assertEqual(
+ Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).earliest(),
+ a2
+ )
+
+ # Pass a custom field name to earliest() to change the field that's used
+ # to determine the earliest object.
+ self.assertEqual(Article.objects.earliest('expire_date'), a2)
+ self.assertEqual(Article.objects.filter(
+ pub_date__gt=datetime(2005, 7, 26)).earliest('expire_date'), a2)
+
+ # Ensure that earliest() overrides any other ordering specified on the
+ # query. Refs #11283.
+ self.assertEqual(Article.objects.order_by('id').earliest(), a1)
+
+ # Ensure that error is raised if the user forgot to add a get_latest_by
+ # in the Model.Meta
+ Article.objects.model._meta.get_latest_by = None
+ self.assertRaisesMessage(
+ AssertionError,
+ "earliest() and latest() require either a field_name parameter or "
+ "'get_latest_by' in the model",
+ lambda: Article.objects.earliest(),
+ )
+
+ def test_latest(self):
+ # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
+ self.assertRaises(Article.DoesNotExist, Article.objects.latest)
+
+ a1 = Article.objects.create(
+ headline="Article 1", pub_date=datetime(2005, 7, 26),
+ expire_date=datetime(2005, 9, 1)
+ )
+ a2 = Article.objects.create(
+ headline="Article 2", pub_date=datetime(2005, 7, 27),
+ expire_date=datetime(2005, 7, 28)
+ )
+ a3 = Article.objects.create(
+ headline="Article 3", pub_date=datetime(2005, 7, 27),
+ expire_date=datetime(2005, 8, 27)
+ )
+ a4 = Article.objects.create(
+ headline="Article 4", pub_date=datetime(2005, 7, 28),
+ expire_date=datetime(2005, 7, 30)
+ )
+
+ # Get the latest Article.
+ self.assertEqual(Article.objects.latest(), a4)
+ # Get the latest Article that matches certain filters.
+ self.assertEqual(
+ Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(),
+ a1
+ )
+
+ # Pass a custom field name to latest() to change the field that's used
+ # to determine the latest object.
+ self.assertEqual(Article.objects.latest('expire_date'), a1)
+ self.assertEqual(
+ Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'),
+ a3,
+ )
+
+ # Ensure that latest() overrides any other ordering specified on the query. Refs #11283.
+ self.assertEqual(Article.objects.order_by('id').latest(), a4)
+
+ # Ensure that error is raised if the user forgot to add a get_latest_by
+ # in the Model.Meta
+ Article.objects.model._meta.get_latest_by = None
+ self.assertRaisesMessage(
+ AssertionError,
+ "earliest() and latest() require either a field_name parameter or "
+ "'get_latest_by' in the model",
+ lambda: Article.objects.latest(),
+ )
+
+ def test_latest_manual(self):
+ # You can still use latest() with a model that doesn't have
+ # "get_latest_by" set -- just pass in the field name manually.
+ p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
+ p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
+ self.assertRaises(AssertionError, Person.objects.latest)
+ self.assertEqual(Person.objects.latest("birthday"), p2)
View
58 tests/modeltests/get_latest/tests.py
@@ -1,58 +0,0 @@
-from __future__ import absolute_import
-
-from datetime import datetime
-
-from django.test import TestCase
-
-from .models import Article, Person
-
-
-class LatestTests(TestCase):
- def test_latest(self):
- # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
- self.assertRaises(Article.DoesNotExist, Article.objects.latest)
-
- a1 = Article.objects.create(
- headline="Article 1", pub_date=datetime(2005, 7, 26),
- expire_date=datetime(2005, 9, 1)
- )
- a2 = Article.objects.create(
- headline="Article 2", pub_date=datetime(2005, 7, 27),
- expire_date=datetime(2005, 7, 28)
- )
- a3 = Article.objects.create(
- headline="Article 3", pub_date=datetime(2005, 7, 27),
- expire_date=datetime(2005, 8, 27)
- )
- a4 = Article.objects.create(
- headline="Article 4", pub_date=datetime(2005, 7, 28),
- expire_date=datetime(2005, 7, 30)
- )
-
- # Get the latest Article.
- self.assertEqual(Article.objects.latest(), a4)
- # Get the latest Article that matches certain filters.
- self.assertEqual(
- Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(),
- a1
- )
-
- # Pass a custom field name to latest() to change the field that's used
- # to determine the latest object.
- self.assertEqual(Article.objects.latest('expire_date'), a1)
- self.assertEqual(
- Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'),
- a3,
- )
-
- # Ensure that latest() overrides any other ordering specified on the query. Refs #11283.
- self.assertEqual(Article.objects.order_by('id').latest(), a4)
-
- def test_latest_manual(self):
- # You can still use latest() with a model that doesn't have
- # "get_latest_by" set -- just pass in the field name manually.
- p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
- p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
- self.assertRaises(AssertionError, Person.objects.latest)
-
- self.assertEqual(Person.objects.latest("birthday"), p2)
Please sign in to comment.
Something went wrong with that request. Please try again.