Code

Ticket #17813: t17813.2.diff

File t17813.2.diff, 17.2 KB (added by Fandekasp, 2 years ago)

new diff with first renamed into earliest

Line 
1diff --git a/django/db/models/base.py b/django/db/models/base.py
2index 53b62df..0996695 100644
3--- a/django/db/models/base.py
4+++ b/django/db/models/base.py
5@@ -71,6 +71,8 @@ class ModelBase(type):
6                 # method resolution order).
7                 if not hasattr(meta, 'ordering'):
8                     new_class._meta.ordering = base_meta.ordering
9+                if not hasattr(meta, 'get_earliest_by'):
10+                    new_class._meta.get_earliest_by = base_meta.get_earliest_by
11                 if not hasattr(meta, 'get_latest_by'):
12                     new_class._meta.get_latest_by = base_meta.get_latest_by
13 
14diff --git a/django/db/models/manager.py b/django/db/models/manager.py
15index e1bbf6e..cc00fed 100644
16--- a/django/db/models/manager.py
17+++ b/django/db/models/manager.py
18@@ -160,6 +160,9 @@ class Manager(object):
19     def iterator(self, *args, **kwargs):
20         return self.get_query_set().iterator(*args, **kwargs)
21 
22+    def earliest(self, *args, **kwargs):
23+        return self.get_query_set().earliest(*args, **kwargs)
24+
25     def latest(self, *args, **kwargs):
26         return self.get_query_set().latest(*args, **kwargs)
27 
28diff --git a/django/db/models/options.py b/django/db/models/options.py
29index 44f8891..b88aee0 100644
30--- a/django/db/models/options.py
31+++ b/django/db/models/options.py
32@@ -15,7 +15,7 @@ from django.utils.datastructures import SortedDict
33 get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
34 
35 DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
36-                 'unique_together', 'permissions', 'get_latest_by',
37+                 'unique_together', 'permissions', 'get_earliest_by', 'get_latest_by',
38                  'order_with_respect_to', 'app_label', 'db_tablespace',
39                  'abstract', 'managed', 'proxy', 'auto_created')
40 
41@@ -30,6 +30,7 @@ class Options(object):
42         self.unique_together =  []
43         self.permissions =  []
44         self.object_name, self.app_label = None, app_label
45+        self.get_earliest_by = None
46         self.get_latest_by = None
47         self.order_with_respect_to = None
48         self.db_tablespace = settings.DEFAULT_TABLESPACE
49diff --git a/django/db/models/query.py b/django/db/models/query.py
50index 44acadf..55ebc06 100644
51--- a/django/db/models/query.py
52+++ b/django/db/models/query.py
53@@ -458,21 +458,30 @@ class QuerySet(object):
54                     # Re-raise the IntegrityError with its original traceback.
55                     raise exc_info[1], None, exc_info[2]
56 
57-    def latest(self, field_name=None):
58+    def _earliest_or_latest(self, field_name=None, direction="-",
59+            get_by='get_latest_by'):
60         """
61-        Returns the latest object, according to the model's 'get_latest_by'
62-        option or optional given field_name.
63+        Returns the latest object, according to the model's 'get_earliest_by' or
64+        'get_latest_by' option or optional given field_name.
65         """
66-        latest_by = field_name or self.model._meta.get_latest_by
67-        assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
68+        order_by = field_name or getattr(self.model._meta, get_by)
69+        assert bool(order_by), "_earliest_or_latest() requires either a "\
70+            "field_name parameter or '%s' in the model" % get_by
71         assert self.query.can_filter(), \
72                 "Cannot change a query once a slice has been taken."
73         obj = self._clone()
74         obj.query.set_limits(high=1)
75         obj.query.clear_ordering()
76-        obj.query.add_ordering('-%s' % latest_by)
77+        obj.query.add_ordering('%s%s' % (direction, order_by))     
78         return obj.get()
79 
80+    def earliest(self, field_name=None):
81+        return self._earliest_or_latest(field_name=field_name, direction="",
82+            get_by='get_earliest_by')
83+
84+    def latest(self, field_name=None):
85+        return self._earliest_or_latest(field_name=field_name, direction="-")
86+
87     def in_bulk(self, id_list):
88         """
89         Returns a dictionary mapping each of the given IDs to the object with
90diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
91index 6ca3d3b..fe6e1d2 100644
92--- a/docs/ref/models/options.txt
93+++ b/docs/ref/models/options.txt
94@@ -78,6 +78,23 @@ Django quotes column and table names behind the scenes.
95     setting, if set. If the backend doesn't support tablespaces, this option is
96     ignored.
97 
98+
99+``get_earliest_by``
100+-----------------
101+
102+.. attribute:: Options.get_earliest_by
103+
104+    The name of a :class:`DateField` or :class:`DateTimeField` in the model.
105+    This specifies the default field to use in your model :class:`Manager`'s
106+    :class:`~QuerySet.earliest` method.
107+
108+    Example::
109+
110+        get_earliest_by = "order_date"
111+
112+    See the docs for :meth:`~django.db.models.query.QuerySet.earliest` for more.
113+
114+
115 ``get_latest_by``
116 -----------------
117 
118diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
119index e25bea0..2e67695 100644
120--- a/docs/ref/models/querysets.txt
121+++ b/docs/ref/models/querysets.txt
122@@ -1465,15 +1465,18 @@ This example returns the latest ``Entry`` in the table, according to the
123     Entry.objects.latest('pub_date')
124 
125 If your model's :ref:`Meta <meta-options>` specifies
126-:attr:`~django.db.models.Options.get_latest_by`, you can leave off the
127-``field_name`` argument to ``latest()``. Django will use the field specified
128-in :attr:`~django.db.models.Options.get_latest_by` by default.
129+:attr:`~django.db.models.Options.get_earliest_by`, you can leave off the
130+``field_name`` argument to ``earliest()``. Django will use the field specified
131+in :attr:`~django.db.models.Options.get_earliest_by` by default.
132 
133-Like :meth:`get()`, ``latest()`` raises
134-:exc:`~django.core.exceptions.DoesNotExist` if there is no object with the given
135-parameters.
136+Same goes for :attr:`~django.db.models.Options.get_earliest_by` and ``latest()``.
137+
138+Like :meth:`get()`, ``earliest()`` and ``latest()`` raise
139+:exc:`~django.core.exceptions.DoesNotExist` if there is no object with the
140+given parameters.
141 
142-Note ``latest()`` exists purely for convenience and readability.
143+Note that ``earliest()`` and ``latest()`` exist purely for convenience and
144+readability.
145 
146 aggregate
147 ~~~~~~~~~
148diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
149index 65b2d59..36019fb 100644
150--- a/docs/topics/db/models.txt
151+++ b/docs/topics/db/models.txt
152@@ -991,10 +991,11 @@ right).
153 
154 So a child model does not have access to its parent's :ref:`Meta
155 <meta-options>` class. However, there are a few limited cases where the child
156-inherits behavior from the parent: if the child does not specify an
157-:attr:`~django.db.models.Options.ordering` attribute or a
158-:attr:`~django.db.models.Options.get_latest_by` attribute, it will inherit
159-these from its parent.
160+inherits behavior from the parent: if the child does not specify an attribute
161+:attr:`~django.db.models.Options.ordering`,
162+:attr:`~django.db.models.Options.get_earliest_by`, or
163+:attr:`~django.db.models.Options.get_latest_by`,
164+, it will inherit these from its parent.
165 
166 If the parent has an ordering and you don't want the child to have any natural
167 ordering, you can explicitly disable it::
168diff --git a/tests/modeltests/get_earliest_or_latest/__init__.py b/tests/modeltests/get_earliest_or_latest/__init__.py
169new file mode 100644
170index 0000000..e69de29
171diff --git a/tests/modeltests/get_earliest_or_latest/models.py b/tests/modeltests/get_earliest_or_latest/models.py
172new file mode 100644
173index 0000000..72e5781
174--- /dev/null
175+++ b/tests/modeltests/get_earliest_or_latest/models.py
176@@ -0,0 +1,33 @@
177+"""
178+8. get_latest_by
179+
180+Models can have a ``get_latest_by`` attribute, which should be set to the name
181+of a ``DateField`` or ``DateTimeField``. If ``get_latest_by`` exists, the
182+model's manager will get a ``latest()`` method, which will return the latest
183+object in the database according to that field. "Latest" means "having the date
184+farthest into the future."
185+"""
186+
187+from django.db import models
188+
189+
190+class Article(models.Model):
191+    headline = models.CharField(max_length=100)
192+    pub_date = models.DateField()
193+    expire_date = models.DateField()
194+    class Meta:
195+        get_earliest_by = 'pub_date'
196+        get_latest_by = 'pub_date'
197+
198+    def __unicode__(self):
199+        return self.headline
200+
201+
202+class Person(models.Model):
203+    name = models.CharField(max_length=30)
204+    birthday = models.DateField()
205+
206+    # Note that this model doesn't have "get_latest_by" set.
207+
208+    def __unicode__(self):
209+        return self.name
210diff --git a/tests/modeltests/get_earliest_or_latest/tests.py b/tests/modeltests/get_earliest_or_latest/tests.py
211new file mode 100644
212index 0000000..a78af2a
213--- /dev/null
214+++ b/tests/modeltests/get_earliest_or_latest/tests.py
215@@ -0,0 +1,121 @@
216+from __future__ import absolute_import
217+
218+from datetime import datetime
219+
220+from django.test import TestCase
221+
222+from .models import Article, Person
223+
224+
225+class EarliestOrLatestTests(TestCase):
226+    """Tests for the earliest() and latest() objects methods"""
227+
228+    def test_earliest(self):
229+        # Because no Articles exist yet, earliest() raises ArticleDoesNotExist.
230+        self.assertRaises(Article.DoesNotExist, Article.objects.earliest)
231+
232+        a1 = Article.objects.create(
233+            headline="Article 1", pub_date=datetime(2005, 7, 26),
234+            expire_date=datetime(2005, 9, 1)
235+        )
236+        a2 = Article.objects.create(
237+            headline="Article 2", pub_date=datetime(2005, 7, 27),
238+            expire_date=datetime(2005, 7, 28)
239+        )
240+        a3 = Article.objects.create(
241+            headline="Article 3", pub_date=datetime(2005, 7, 28),
242+            expire_date=datetime(2005, 8, 27)
243+        )
244+        a4 = Article.objects.create(
245+            headline="Article 4", pub_date=datetime(2005, 7, 28),
246+            expire_date=datetime(2005, 7, 30)
247+        )
248+
249+        # Get the earliest Article.
250+        self.assertEqual(Article.objects.earliest(), a1)
251+        # Get the earliest Article that matches certain filters.
252+        self.assertEqual(
253+            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).earliest(),
254+            a2
255+        )
256+
257+        # Pass a custom field name to earliest() to change the field that's used
258+        # to determine the earliest object.
259+        self.assertEqual(Article.objects.earliest('expire_date'), a2)
260+        self.assertEqual(Article.objects.filter(
261+            pub_date__gt=datetime(2005, 7, 26)).earliest('expire_date'), a2)
262+
263+        # Ensure that earliest() overrides any other ordering specified on the
264+        # query. Refs #11283.
265+        self.assertEqual(Article.objects.order_by('id').earliest(), a1)
266+
267+        # Ensure that error is raised if the user forgot to add a get_earliest_by
268+        # in the Model.Meta
269+        Article.objects.model._meta.get_earliest_by = None
270+        self.assertRaisesMessage(
271+            AssertionError,
272+            "_earliest_or_latest() requires either a field_name parameter or "\
273+                "'get_earliest_by' in the model",
274+            lambda: Article.objects.earliest(),
275+        )
276+
277+
278+    def test_latest(self):
279+        # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
280+        self.assertRaises(Article.DoesNotExist, Article.objects.latest)
281+
282+        a1 = Article.objects.create(
283+            headline="Article 1", pub_date=datetime(2005, 7, 26),
284+            expire_date=datetime(2005, 9, 1)
285+        )
286+        a2 = Article.objects.create(
287+            headline="Article 2", pub_date=datetime(2005, 7, 27),
288+            expire_date=datetime(2005, 7, 28)
289+        )
290+        a3 = Article.objects.create(
291+            headline="Article 3", pub_date=datetime(2005, 7, 27),
292+            expire_date=datetime(2005, 8, 27)
293+        )
294+        a4 = Article.objects.create(
295+            headline="Article 4", pub_date=datetime(2005, 7, 28),
296+            expire_date=datetime(2005, 7, 30)
297+        )
298+
299+        # Get the latest Article.
300+        self.assertEqual(Article.objects.latest(), a4)
301+        # Get the latest Article that matches certain filters.
302+        self.assertEqual(
303+            Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(),
304+            a1
305+        )
306+
307+        # Pass a custom field name to latest() to change the field that's used
308+        # to determine the latest object.
309+        self.assertEqual(Article.objects.latest('expire_date'), a1)
310+        self.assertEqual(
311+            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'),
312+            a3,
313+        )
314+
315+        # Ensure that latest() overrides any other ordering specified on the query. Refs #11283.
316+        self.assertEqual(Article.objects.order_by('id').latest(), a4)
317+
318+        # Ensure that error is raised if the user forgot to add a get_latest_by
319+        # in the Model.Meta
320+        Article.objects.model._meta.get_latest_by = None
321+        self.assertRaisesMessage(
322+            AssertionError,
323+            "_earliest_or_latest() requires either a field_name parameter or "\
324+                "'get_latest_by' in the model",
325+            lambda: Article.objects.latest(),
326+        )
327+
328+
329+    def test_latest_manual(self):
330+        # You can still use latest() with a model that doesn't have
331+        # "get_latest_by" set -- just pass in the field name manually.
332+        p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
333+        p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
334+        self.assertRaises(AssertionError, Person.objects.latest)
335+
336+        self.assertEqual(Person.objects.latest("birthday"), p2)
337diff --git a/tests/modeltests/get_latest/__init__.py b/tests/modeltests/get_latest/__init__.py
338deleted file mode 100644
339index e69de29..0000000
340diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
341deleted file mode 100644
342index d8a690f..0000000
343--- a/tests/modeltests/get_latest/models.py
344+++ /dev/null
345@@ -1,31 +0,0 @@
346-"""
347-8. get_latest_by
348-
349-Models can have a ``get_latest_by`` attribute, which should be set to the name
350-of a ``DateField`` or ``DateTimeField``. If ``get_latest_by`` exists, the
351-model's manager will get a ``latest()`` method, which will return the latest
352-object in the database according to that field. "Latest" means "having the date
353-farthest into the future."
354-"""
355-
356-from django.db import models
357-
358-
359-class Article(models.Model):
360-    headline = models.CharField(max_length=100)
361-    pub_date = models.DateField()
362-    expire_date = models.DateField()
363-    class Meta:
364-        get_latest_by = 'pub_date'
365-
366-    def __unicode__(self):
367-        return self.headline
368-
369-class Person(models.Model):
370-    name = models.CharField(max_length=30)
371-    birthday = models.DateField()
372-
373-    # Note that this model doesn't have "get_latest_by" set.
374-
375-    def __unicode__(self):
376-        return self.name
377diff --git a/tests/modeltests/get_latest/tests.py b/tests/modeltests/get_latest/tests.py
378deleted file mode 100644
379index 948af60..0000000
380--- a/tests/modeltests/get_latest/tests.py
381+++ /dev/null
382@@ -1,58 +0,0 @@
383-from __future__ import absolute_import
384-
385-from datetime import datetime
386-
387-from django.test import TestCase
388-
389-from .models import Article, Person
390-
391-
392-class LatestTests(TestCase):
393-    def test_latest(self):
394-        # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
395-        self.assertRaises(Article.DoesNotExist, Article.objects.latest)
396-
397-        a1 = Article.objects.create(
398-            headline="Article 1", pub_date=datetime(2005, 7, 26),
399-            expire_date=datetime(2005, 9, 1)
400-        )
401-        a2 = Article.objects.create(
402-            headline="Article 2", pub_date=datetime(2005, 7, 27),
403-            expire_date=datetime(2005, 7, 28)
404-        )
405-        a3 = Article.objects.create(
406-            headline="Article 3", pub_date=datetime(2005, 7, 27),
407-            expire_date=datetime(2005, 8, 27)
408-        )
409-        a4 = Article.objects.create(
410-            headline="Article 4", pub_date=datetime(2005, 7, 28),
411-            expire_date=datetime(2005, 7, 30)
412-        )
413-
414-        # Get the latest Article.
415-        self.assertEqual(Article.objects.latest(), a4)
416-        # Get the latest Article that matches certain filters.
417-        self.assertEqual(
418-            Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(),
419-            a1
420-        )
421-
422-        # Pass a custom field name to latest() to change the field that's used
423-        # to determine the latest object.
424-        self.assertEqual(Article.objects.latest('expire_date'), a1)
425-        self.assertEqual(
426-            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'),
427-            a3,
428-        )
429-
430-        # Ensure that latest() overrides any other ordering specified on the query. Refs #11283.
431-        self.assertEqual(Article.objects.order_by('id').latest(), a4)
432-
433-    def test_latest_manual(self):
434-        # You can still use latest() with a model that doesn't have
435-        # "get_latest_by" set -- just pass in the field name manually.
436-        p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
437-        p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
438-        self.assertRaises(AssertionError, Person.objects.latest)
439-
440-        self.assertEqual(Person.objects.latest("birthday"), p2)