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.
This commit is contained in:
Nick Sandford 2013-01-12 16:37:19 +08:00 committed by Anssi Kääriäinen
parent 37718eb50b
commit fe54377dae
9 changed files with 164 additions and 77 deletions

View File

@ -172,6 +172,9 @@ class Manager(object):
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 File

@ -29,6 +29,7 @@ REPR_OUTPUT_SIZE = 20
# 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 @@ class QuerySet(object):
# 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 File

@ -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 File

@ -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 ``latest()`` exists purely for convenience and readability.
Note that ``earliest()`` and ``latest()`` exist purely for convenience and
readability.
earliest
~~~~~~~~
.. method:: earliest(field_name=None)
Works otherwise like :meth:`~django.db.models.query.QuerySet.latest` except
the direction is changed.
aggregate
~~~~~~~~~

View File

@ -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 File

@ -9,10 +9,8 @@ farthest into the future."
"""
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):
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 File

@ -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 File

@ -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)