Ticket #17813: t17813.diff

File t17813.diff, 17.0 KB (added by Adrien Lemaire, 12 years ago)

Added new get_first_by Objects field. More tests. Doc updated

  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 53b62df..b3d5c3b 100644
    a b class ModelBase(type):  
    7171                # method resolution order).
    7272                if not hasattr(meta, 'ordering'):
    7373                    new_class._meta.ordering = base_meta.ordering
     74                if not hasattr(meta, 'get_first_by'):
     75                    new_class._meta.get_first_by = base_meta.get_first_by
    7476                if not hasattr(meta, 'get_latest_by'):
    7577                    new_class._meta.get_latest_by = base_meta.get_latest_by
    7678
  • django/db/models/manager.py

    diff --git a/django/db/models/manager.py b/django/db/models/manager.py
    index e1bbf6e..3e05f82 100644
    a b class Manager(object):  
    160160    def iterator(self, *args, **kwargs):
    161161        return self.get_query_set().iterator(*args, **kwargs)
    162162
     163    def first(self, *args, **kwargs):
     164        return self.get_query_set().first(*args, **kwargs)
     165
    163166    def latest(self, *args, **kwargs):
    164167        return self.get_query_set().latest(*args, **kwargs)
    165168
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    index 44f8891..5c06ee8 100644
    a b from django.utils.datastructures import SortedDict  
    1515get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
    1616
    1717DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
    18                  'unique_together', 'permissions', 'get_latest_by',
     18                 'unique_together', 'permissions', 'get_first_by', 'get_latest_by',
    1919                 'order_with_respect_to', 'app_label', 'db_tablespace',
    2020                 'abstract', 'managed', 'proxy', 'auto_created')
    2121
    class Options(object):  
    3030        self.unique_together =  []
    3131        self.permissions =  []
    3232        self.object_name, self.app_label = None, app_label
     33        self.get_first_by = None
    3334        self.get_latest_by = None
    3435        self.order_with_respect_to = None
    3536        self.db_tablespace = settings.DEFAULT_TABLESPACE
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index 44acadf..030bbe5 100644
    a b class QuerySet(object):  
    458458                    # Re-raise the IntegrityError with its original traceback.
    459459                    raise exc_info[1], None, exc_info[2]
    460460
    461     def latest(self, field_name=None):
     461    def _first_or_latest(self, field_name=None, direction="-",
     462            get_by='get_latest_by'):
    462463        """
    463         Returns the latest object, according to the model's 'get_latest_by'
    464         option or optional given field_name.
     464        Returns the latest object, according to the model's 'get_first_by' or
     465        'get_latest_by' option or optional given field_name.
    465466        """
    466         latest_by = field_name or self.model._meta.get_latest_by
    467         assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model"
     467        order_by = field_name or getattr(self.model._meta, get_by)
     468        assert bool(order_by), "_first_or_latest() requires either a "\
     469            "field_name parameter or '%s' in the model" % get_by
    468470        assert self.query.can_filter(), \
    469471                "Cannot change a query once a slice has been taken."
    470472        obj = self._clone()
    471473        obj.query.set_limits(high=1)
    472474        obj.query.clear_ordering()
    473         obj.query.add_ordering('-%s' % latest_by)
     475        obj.query.add_ordering('%s%s' % (direction, order_by))     
    474476        return obj.get()
    475477
     478    def first(self, field_name=None):
     479        return self._first_or_latest(field_name=field_name, direction="",
     480            get_by='get_first_by')
     481
     482    def latest(self, field_name=None):
     483        return self._first_or_latest(field_name=field_name, direction="-")
     484
    476485    def in_bulk(self, id_list):
    477486        """
    478487        Returns a dictionary mapping each of the given IDs to the object with
  • docs/ref/models/options.txt

    diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
    index 6ca3d3b..6dde942 100644
    a b Django quotes column and table names behind the scenes.  
    7878    setting, if set. If the backend doesn't support tablespaces, this option is
    7979    ignored.
    8080
     81
     82``get_first_by``
     83-----------------
     84
     85.. attribute:: Options.get_first_by
     86
     87    The name of a :class:`DateField` or :class:`DateTimeField` in the model.
     88    This specifies the default field to use in your model :class:`Manager`'s
     89    :class:`~QuerySet.first` method.
     90
     91    Example::
     92
     93        get_first_by = "order_date"
     94
     95    See the docs for :meth:`~django.db.models.query.QuerySet.first` for more.
     96
     97
    8198``get_latest_by``
    8299-----------------
    83100
  • docs/ref/models/querysets.txt

    diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
    index e25bea0..6963824 100644
    a b This example returns the latest ``Entry`` in the table, according to the  
    14651465    Entry.objects.latest('pub_date')
    14661466
    14671467If your model's :ref:`Meta <meta-options>` specifies
    1468 :attr:`~django.db.models.Options.get_latest_by`, you can leave off the
    1469 ``field_name`` argument to ``latest()``. Django will use the field specified
    1470 in :attr:`~django.db.models.Options.get_latest_by` by default.
     1468:attr:`~django.db.models.Options.get_first_by`, you can leave off the
     1469``field_name`` argument to ``first()``. Django will use the field specified
     1470in :attr:`~django.db.models.Options.get_first_by` by default.
    14711471
    1472 Like :meth:`get()`, ``latest()`` raises
    1473 :exc:`~django.core.exceptions.DoesNotExist` if there is no object with the given
    1474 parameters.
     1472Same goes for :attr:`~django.db.models.Options.get_first_by` and ``latest()``.
     1473
     1474Like :meth:`get()`, ``first()`` and ``latest()`` raise
     1475:exc:`~django.core.exceptions.DoesNotExist` if there is no object with the
     1476given parameters.
    14751477
    1476 Note ``latest()`` exists purely for convenience and readability.
     1478Note that ``first()`` and ``latest()`` exist purely for convenience and
     1479readability.
    14771480
    14781481aggregate
    14791482~~~~~~~~~
  • docs/topics/db/models.txt

    diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
    index 65b2d59..87ddb2c 100644
    a b right).  
    991991
    992992So a child model does not have access to its parent's :ref:`Meta
    993993<meta-options>` class. However, there are a few limited cases where the child
    994 inherits behavior from the parent: if the child does not specify an
    995 :attr:`~django.db.models.Options.ordering` attribute or a
    996 :attr:`~django.db.models.Options.get_latest_by` attribute, it will inherit
    997 these from its parent.
     994inherits behavior from the parent: if the child does not specify an attribute
     995:attr:`~django.db.models.Options.ordering`,
     996:attr:`~django.db.models.Options.get_first_by`, or
     997:attr:`~django.db.models.Options.get_latest_by`,
     998, it will inherit these from its parent.
    998999
    9991000If the parent has an ordering and you don't want the child to have any natural
    10001001ordering, you can explicitly disable it::
  • new file tests/modeltests/get_first_or_latest/models.py

    diff --git a/tests/modeltests/get_first_or_latest/__init__.py b/tests/modeltests/get_first_or_latest/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/modeltests/get_first_or_latest/models.py b/tests/modeltests/get_first_or_latest/models.py
    new file mode 100644
    index 0000000..ede24c2
    - +  
     1"""
     28. get_latest_by
     3
     4Models can have a ``get_latest_by`` attribute, which should be set to the name
     5of a ``DateField`` or ``DateTimeField``. If ``get_latest_by`` exists, the
     6model's manager will get a ``latest()`` method, which will return the latest
     7object in the database according to that field. "Latest" means "having the date
     8farthest into the future."
     9"""
     10
     11from django.db import models
     12
     13
     14class Article(models.Model):
     15    headline = models.CharField(max_length=100)
     16    pub_date = models.DateField()
     17    expire_date = models.DateField()
     18    class Meta:
     19        get_first_by = 'pub_date'
     20        get_latest_by = 'pub_date'
     21
     22    def __unicode__(self):
     23        return self.headline
     24
     25
     26class Person(models.Model):
     27    name = models.CharField(max_length=30)
     28    birthday = models.DateField()
     29
     30    # Note that this model doesn't have "get_latest_by" set.
     31
     32    def __unicode__(self):
     33        return self.name
  • new file tests/modeltests/get_first_or_latest/tests.py

    diff --git a/tests/modeltests/get_first_or_latest/tests.py b/tests/modeltests/get_first_or_latest/tests.py
    new file mode 100644
    index 0000000..d46fe23
    - +  
     1from __future__ import absolute_import
     2
     3from datetime import datetime
     4
     5from django.test import TestCase
     6
     7from .models import Article, Person
     8
     9
     10class FirstOrLatestTests(TestCase):
     11    """Tests for the first() and latest() objects methods"""
     12
     13    def test_first(self):
     14        # Because no Articles exist yet, first() raises ArticleDoesNotExist.
     15        self.assertRaises(Article.DoesNotExist, Article.objects.first)
     16
     17        a1 = Article.objects.create(
     18            headline="Article 1", pub_date=datetime(2005, 7, 26),
     19            expire_date=datetime(2005, 9, 1)
     20        )
     21        a2 = Article.objects.create(
     22            headline="Article 2", pub_date=datetime(2005, 7, 27),
     23            expire_date=datetime(2005, 7, 28)
     24        )
     25        a3 = Article.objects.create(
     26            headline="Article 3", pub_date=datetime(2005, 7, 28),
     27            expire_date=datetime(2005, 8, 27)
     28        )
     29        a4 = Article.objects.create(
     30            headline="Article 4", pub_date=datetime(2005, 7, 28),
     31            expire_date=datetime(2005, 7, 30)
     32        )
     33
     34        # Get the first Article.
     35        self.assertEqual(Article.objects.first(), a1)
     36        # Get the first Article that matches certain filters.
     37        self.assertEqual(
     38            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).first(),
     39            a2
     40        )
     41
     42        # Pass a custom field name to first() to change the field that's used
     43        # to determine the first object.
     44        self.assertEqual(Article.objects.first('expire_date'), a2)
     45        self.assertEqual(Article.objects.filter(
     46            pub_date__gt=datetime(2005, 7, 26)).first('expire_date'), a2)
     47
     48        # Ensure that first() overrides any other ordering specified on the
     49        # query. Refs #11283.
     50        self.assertEqual(Article.objects.order_by('id').first(), a1)
     51
     52        # Ensure that error is raised if the user forgot to add a get_first_by
     53        # in the Model.Meta
     54        Article.objects.model._meta.get_first_by = None
     55        self.assertRaisesMessage(
     56            AssertionError,
     57            "_first_or_latest() requires either a field_name parameter or "\
     58                "'get_first_by' in the model",
     59            lambda: Article.objects.first(),
     60        )
     61
     62
     63    def test_latest(self):
     64        # Because no Articles exist yet, latest() raises ArticleDoesNotExist.
     65        self.assertRaises(Article.DoesNotExist, Article.objects.latest)
     66
     67        a1 = Article.objects.create(
     68            headline="Article 1", pub_date=datetime(2005, 7, 26),
     69            expire_date=datetime(2005, 9, 1)
     70        )
     71        a2 = Article.objects.create(
     72            headline="Article 2", pub_date=datetime(2005, 7, 27),
     73            expire_date=datetime(2005, 7, 28)
     74        )
     75        a3 = Article.objects.create(
     76            headline="Article 3", pub_date=datetime(2005, 7, 27),
     77            expire_date=datetime(2005, 8, 27)
     78        )
     79        a4 = Article.objects.create(
     80            headline="Article 4", pub_date=datetime(2005, 7, 28),
     81            expire_date=datetime(2005, 7, 30)
     82        )
     83
     84        # Get the latest Article.
     85        self.assertEqual(Article.objects.latest(), a4)
     86        # Get the latest Article that matches certain filters.
     87        self.assertEqual(
     88            Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(),
     89            a1
     90        )
     91
     92        # Pass a custom field name to latest() to change the field that's used
     93        # to determine the latest object.
     94        self.assertEqual(Article.objects.latest('expire_date'), a1)
     95        self.assertEqual(
     96            Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'),
     97            a3,
     98        )
     99
     100        # Ensure that latest() overrides any other ordering specified on the query. Refs #11283.
     101        self.assertEqual(Article.objects.order_by('id').latest(), a4)
     102
     103        # Ensure that error is raised if the user forgot to add a get_latest_by
     104        # in the Model.Meta
     105        Article.objects.model._meta.get_latest_by = None
     106        self.assertRaisesMessage(
     107            AssertionError,
     108            "_first_or_latest() requires either a field_name parameter or "\
     109                "'get_latest_by' in the model",
     110            lambda: Article.objects.latest(),
     111        )
     112
     113
     114    def test_latest_manual(self):
     115        # You can still use latest() with a model that doesn't have
     116        # "get_latest_by" set -- just pass in the field name manually.
     117        p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
     118        p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
     119        self.assertRaises(AssertionError, Person.objects.latest)
     120
     121        self.assertEqual(Person.objects.latest("birthday"), p2)
  • deleted file tests/modeltests/get_latest/models.py

    diff --git a/tests/modeltests/get_latest/__init__.py b/tests/modeltests/get_latest/__init__.py
    deleted file mode 100644
    index e69de29..0000000
    diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
    deleted file mode 100644
    index d8a690f..0000000
    + -  
    1 """
    2 8. get_latest_by
    3 
    4 Models can have a ``get_latest_by`` attribute, which should be set to the name
    5 of a ``DateField`` or ``DateTimeField``. If ``get_latest_by`` exists, the
    6 model's manager will get a ``latest()`` method, which will return the latest
    7 object in the database according to that field. "Latest" means "having the date
    8 farthest into the future."
    9 """
    10 
    11 from django.db import models
    12 
    13 
    14 class Article(models.Model):
    15     headline = models.CharField(max_length=100)
    16     pub_date = models.DateField()
    17     expire_date = models.DateField()
    18     class Meta:
    19         get_latest_by = 'pub_date'
    20 
    21     def __unicode__(self):
    22         return self.headline
    23 
    24 class Person(models.Model):
    25     name = models.CharField(max_length=30)
    26     birthday = models.DateField()
    27 
    28     # Note that this model doesn't have "get_latest_by" set.
    29 
    30     def __unicode__(self):
    31         return self.name
  • deleted file tests/modeltests/get_latest/tests.py

    diff --git a/tests/modeltests/get_latest/tests.py b/tests/modeltests/get_latest/tests.py
    deleted file mode 100644
    index 948af60..0000000
    + -  
    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)
Back to Top