Commit ca611958 authored by Artem Rizhov's avatar Artem Rizhov Committed by Tim Graham
Browse files

Fixed #23555 -- Avoided suppressing IndexError in QuerySet.first() and .last()

parent 9e2e4cb6
Loading
Loading
Loading
Loading
+8 −10
Original line number Diff line number Diff line
@@ -516,20 +516,18 @@ class QuerySet(object):
        """
        Returns the first object of a query, returns None if no match is found.
        """
        qs = self if self.ordered else self.order_by('pk')
        try:
            return qs[0]
        except IndexError:
        objects = list((self if self.ordered else self.order_by('pk'))[:1])
        if objects:
            return objects[0]
        return None

    def last(self):
        """
        Returns the last object of a query, returns None if no match is found.
        """
        qs = self.reverse() if self.ordered else self.order_by('-pk')
        try:
            return qs[0]
        except IndexError:
        objects = list((self.reverse() if self.ordered else self.order_by('-pk'))[:1])
        if objects:
            return objects[0]
        return None

    def in_bulk(self, id_list):
+15 −0
Original line number Diff line number Diff line
@@ -14,3 +14,18 @@ class Person(models.Model):
    name = models.CharField(max_length=30)
    birthday = models.DateField()
    # Note that this model doesn't have "get_latest_by" set.


# Ticket #23555 - model with an intentionally broken QuerySet.__iter__ method.

class IndexErrorQuerySet(models.QuerySet):
    """
    Emulates the case when some internal code raises an unexpected
    IndexError.
    """
    def __iter__(self):
        raise IndexError


class IndexErrorArticle(Article):
    objects = IndexErrorQuerySet.as_manager()
+25 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ from datetime import datetime

from django.test import TestCase

from .models import Article, Person
from .models import Article, Person, IndexErrorArticle


class EarliestOrLatestTests(TestCase):
@@ -122,6 +122,9 @@ class EarliestOrLatestTests(TestCase):
        self.assertRaises(AssertionError, Person.objects.latest)
        self.assertEqual(Person.objects.latest("birthday"), p2)


class TestFirstLast(TestCase):

    def test_first(self):
        p1 = Person.objects.create(name="Bob", birthday=datetime(1950, 1, 1))
        p2 = Person.objects.create(name="Alice", birthday=datetime(1961, 2, 3))
@@ -152,3 +155,24 @@ class EarliestOrLatestTests(TestCase):
        self.assertIs(
            Person.objects.filter(birthday__lte=datetime(1940, 1, 1)).last(),
            None)

    def test_index_error_not_suppressed(self):
        """
        #23555 -- Unexpected IndexError exceptions in QuerySet iteration
        shouldn't be suppressed.
        """
        def check():
            # We know that we've broken the __iter__ method, so the queryset
            # should always raise an exception.
            self.assertRaises(IndexError, lambda: IndexErrorArticle.objects.all()[0])
            self.assertRaises(IndexError, IndexErrorArticle.objects.all().first)
            self.assertRaises(IndexError, IndexErrorArticle.objects.all().last)

        check()

        # And it does not matter if there are any records in the DB.
        IndexErrorArticle.objects.create(
            headline="Article 1", pub_date=datetime(2005, 7, 26),
            expire_date=datetime(2005, 9, 1)
        )
        check()