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:
parent
37718eb50b
commit
fe54377dae
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::
|
||||
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~
|
||||
|
|
|
@ -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
|
||||
=====================================
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue