Fixed #6785 -- Made QuerySet.get() fetch a limited number of rows.

Co-authored-by: Tim Graham <timograham@gmail.com>
Co-authored-by: Patryk Zawadzki <patrys@room-303.com>
Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
Amir Hadi 2019-04-13 14:33:05 +02:00 committed by Mariusz Felisiak
parent 7c3732a1b4
commit 330638b89f
2 changed files with 32 additions and 3 deletions

View File

@ -28,6 +28,9 @@ from django.utils import timezone
from django.utils.functional import cached_property, partition from django.utils.functional import cached_property, partition
from django.utils.version import get_version from django.utils.version import get_version
# The maximum number of results to fetch in a get() query.
MAX_GET_RESULTS = 21
# The maximum number of items to display in a QuerySet.__repr__ # The maximum number of items to display in a QuerySet.__repr__
REPR_OUTPUT_SIZE = 20 REPR_OUTPUT_SIZE = 20
@ -398,6 +401,10 @@ class QuerySet:
clone = self.filter(*args, **kwargs) clone = self.filter(*args, **kwargs)
if self.query.can_filter() and not self.query.distinct_fields: if self.query.can_filter() and not self.query.distinct_fields:
clone = clone.order_by() clone = clone.order_by()
limit = None
if not clone.query.select_for_update or connections[clone.db].features.supports_select_for_update_with_limit:
limit = MAX_GET_RESULTS
clone.query.set_limits(high=limit)
num = len(clone) num = len(clone)
if num == 1: if num == 1:
return clone._result_cache[0] return clone._result_cache[0]
@ -407,8 +414,10 @@ class QuerySet:
self.model._meta.object_name self.model._meta.object_name
) )
raise self.model.MultipleObjectsReturned( raise self.model.MultipleObjectsReturned(
"get() returned more than one %s -- it returned %s!" % 'get() returned more than one %s -- it returned %s!' % (
(self.model._meta.object_name, num) self.model._meta.object_name,
num if not limit or num < limit else 'more than %s' % (limit - 1),
)
) )
def create(self, **kwargs): def create(self, **kwargs):

View File

@ -4,7 +4,7 @@ from datetime import datetime, timedelta
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.db import DEFAULT_DB_ALIAS, DatabaseError, connections, models from django.db import DEFAULT_DB_ALIAS, DatabaseError, connections, models
from django.db.models.manager import BaseManager from django.db.models.manager import BaseManager
from django.db.models.query import EmptyQuerySet, QuerySet from django.db.models.query import MAX_GET_RESULTS, EmptyQuerySet, QuerySet
from django.test import ( from django.test import (
SimpleTestCase, TestCase, TransactionTestCase, skipUnlessDBFeature, SimpleTestCase, TestCase, TransactionTestCase, skipUnlessDBFeature,
) )
@ -387,6 +387,26 @@ class ModelTest(TestCase):
# Fields that weren't deleted aren't reloaded. # Fields that weren't deleted aren't reloaded.
self.assertEqual(article.pub_date, new_pub_date) self.assertEqual(article.pub_date, new_pub_date)
def test_multiple_objects_max_num_fetched(self):
max_results = MAX_GET_RESULTS - 1
Article.objects.bulk_create(
Article(headline='Area %s' % i, pub_date=datetime(2005, 7, 28))
for i in range(max_results)
)
self.assertRaisesMessage(
MultipleObjectsReturned,
'get() returned more than one Article -- it returned %d!' % max_results,
Article.objects.get,
headline__startswith='Area',
)
Article.objects.create(headline='Area %s' % max_results, pub_date=datetime(2005, 7, 28))
self.assertRaisesMessage(
MultipleObjectsReturned,
'get() returned more than one Article -- it returned more than %d!' % max_results,
Article.objects.get,
headline__startswith='Area',
)
class ModelLookupTest(TestCase): class ModelLookupTest(TestCase):
@classmethod @classmethod