Fixed #19173 -- Made EmptyQuerySet a marker class only
The guarantee that no queries will be made when accessing results is done by new EmptyWhere class which is used for query.where and having. Thanks to Simon Charette for reviewing and valuable suggestions.
This commit is contained in:
parent
a843539af2
commit
a2396a4c8f
|
@ -473,8 +473,8 @@ class AnonymousUser(object):
|
||||||
is_staff = False
|
is_staff = False
|
||||||
is_active = False
|
is_active = False
|
||||||
is_superuser = False
|
is_superuser = False
|
||||||
_groups = EmptyManager()
|
_groups = EmptyManager(Group)
|
||||||
_user_permissions = EmptyManager()
|
_user_permissions = EmptyManager(Permission)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import copy
|
import copy
|
||||||
from django.db import router
|
from django.db import router
|
||||||
from django.db.models.query import QuerySet, EmptyQuerySet, insert_query, RawQuerySet
|
from django.db.models.query import QuerySet, insert_query, RawQuerySet
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.db.models.fields import FieldDoesNotExist
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ class Manager(object):
|
||||||
#######################
|
#######################
|
||||||
|
|
||||||
def get_empty_query_set(self):
|
def get_empty_query_set(self):
|
||||||
return EmptyQuerySet(self.model, using=self._db)
|
return QuerySet(self.model, using=self._db).none()
|
||||||
|
|
||||||
def get_query_set(self):
|
def get_query_set(self):
|
||||||
"""Returns a new QuerySet object. Subclasses can override this method
|
"""Returns a new QuerySet object. Subclasses can override this method
|
||||||
|
@ -258,5 +258,9 @@ class SwappedManagerDescriptor(object):
|
||||||
|
|
||||||
|
|
||||||
class EmptyManager(Manager):
|
class EmptyManager(Manager):
|
||||||
|
def __init__(self, model):
|
||||||
|
super(EmptyManager, self).__init__()
|
||||||
|
self.model = model
|
||||||
|
|
||||||
def get_query_set(self):
|
def get_query_set(self):
|
||||||
return self.get_empty_query_set()
|
return self.get_empty_query_set()
|
||||||
|
|
|
@ -35,7 +35,6 @@ class QuerySet(object):
|
||||||
"""
|
"""
|
||||||
def __init__(self, model=None, query=None, using=None):
|
def __init__(self, model=None, query=None, using=None):
|
||||||
self.model = model
|
self.model = model
|
||||||
# EmptyQuerySet instantiates QuerySet with model as None
|
|
||||||
self._db = using
|
self._db = using
|
||||||
self.query = query or sql.Query(self.model)
|
self.query = query or sql.Query(self.model)
|
||||||
self._result_cache = None
|
self._result_cache = None
|
||||||
|
@ -217,7 +216,9 @@ class QuerySet(object):
|
||||||
def __and__(self, other):
|
def __and__(self, other):
|
||||||
self._merge_sanity_check(other)
|
self._merge_sanity_check(other)
|
||||||
if isinstance(other, EmptyQuerySet):
|
if isinstance(other, EmptyQuerySet):
|
||||||
return other._clone()
|
return other
|
||||||
|
if isinstance(self, EmptyQuerySet):
|
||||||
|
return self
|
||||||
combined = self._clone()
|
combined = self._clone()
|
||||||
combined._merge_known_related_objects(other)
|
combined._merge_known_related_objects(other)
|
||||||
combined.query.combine(other.query, sql.AND)
|
combined.query.combine(other.query, sql.AND)
|
||||||
|
@ -225,9 +226,11 @@ class QuerySet(object):
|
||||||
|
|
||||||
def __or__(self, other):
|
def __or__(self, other):
|
||||||
self._merge_sanity_check(other)
|
self._merge_sanity_check(other)
|
||||||
combined = self._clone()
|
if isinstance(self, EmptyQuerySet):
|
||||||
|
return other
|
||||||
if isinstance(other, EmptyQuerySet):
|
if isinstance(other, EmptyQuerySet):
|
||||||
return combined
|
return self
|
||||||
|
combined = self._clone()
|
||||||
combined._merge_known_related_objects(other)
|
combined._merge_known_related_objects(other)
|
||||||
combined.query.combine(other.query, sql.OR)
|
combined.query.combine(other.query, sql.OR)
|
||||||
return combined
|
return combined
|
||||||
|
@ -632,7 +635,9 @@ class QuerySet(object):
|
||||||
"""
|
"""
|
||||||
Returns an empty QuerySet.
|
Returns an empty QuerySet.
|
||||||
"""
|
"""
|
||||||
return self._clone(klass=EmptyQuerySet)
|
clone = self._clone()
|
||||||
|
clone.query.set_empty()
|
||||||
|
return clone
|
||||||
|
|
||||||
##################################################################
|
##################################################################
|
||||||
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
|
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
|
||||||
|
@ -981,6 +986,18 @@ class QuerySet(object):
|
||||||
# empty" result.
|
# empty" result.
|
||||||
value_annotation = True
|
value_annotation = True
|
||||||
|
|
||||||
|
class InstanceCheckMeta(type):
|
||||||
|
def __instancecheck__(self, instance):
|
||||||
|
return instance.query.is_empty()
|
||||||
|
|
||||||
|
class EmptyQuerySet(six.with_metaclass(InstanceCheckMeta), object):
|
||||||
|
"""
|
||||||
|
Marker class usable for checking if a queryset is empty by .none():
|
||||||
|
isinstance(qs.none(), EmptyQuerySet) -> True
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
raise TypeError("EmptyQuerySet can't be instantiated")
|
||||||
|
|
||||||
class ValuesQuerySet(QuerySet):
|
class ValuesQuerySet(QuerySet):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -1180,138 +1197,6 @@ class DateQuerySet(QuerySet):
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
class EmptyQuerySet(QuerySet):
|
|
||||||
def __init__(self, model=None, query=None, using=None):
|
|
||||||
super(EmptyQuerySet, self).__init__(model, query, using)
|
|
||||||
self._result_cache = []
|
|
||||||
|
|
||||||
def __and__(self, other):
|
|
||||||
return self._clone()
|
|
||||||
|
|
||||||
def __or__(self, other):
|
|
||||||
return other._clone()
|
|
||||||
|
|
||||||
def count(self):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _clone(self, klass=None, setup=False, **kwargs):
|
|
||||||
c = super(EmptyQuerySet, self)._clone(klass, setup=setup, **kwargs)
|
|
||||||
c._result_cache = []
|
|
||||||
return c
|
|
||||||
|
|
||||||
def iterator(self):
|
|
||||||
# This slightly odd construction is because we need an empty generator
|
|
||||||
# (it raises StopIteration immediately).
|
|
||||||
yield next(iter([]))
|
|
||||||
|
|
||||||
def all(self):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def filter(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def exclude(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def complex_filter(self, filter_obj):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def select_related(self, *fields, **kwargs):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def annotate(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def order_by(self, *field_names):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def distinct(self, fields=None):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def extra(self, select=None, where=None, params=None, tables=None,
|
|
||||||
order_by=None, select_params=None):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
assert self.query.can_filter(), \
|
|
||||||
"Cannot change a query once a slice has been taken"
|
|
||||||
return self
|
|
||||||
|
|
||||||
def reverse(self):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def defer(self, *fields):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def only(self, *fields):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def update(self, **kwargs):
|
|
||||||
"""
|
|
||||||
Don't update anything.
|
|
||||||
"""
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def aggregate(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Return a dict mapping the aggregate names to None
|
|
||||||
"""
|
|
||||||
for arg in args:
|
|
||||||
kwargs[arg.default_alias] = arg
|
|
||||||
return dict([(key, None) for key in kwargs])
|
|
||||||
|
|
||||||
def values(self, *fields):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def values_list(self, *fields, **kwargs):
|
|
||||||
"""
|
|
||||||
Always returns EmptyQuerySet.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
# EmptyQuerySet is always an empty result in where-clauses (and similar
|
|
||||||
# situations).
|
|
||||||
value_annotation = False
|
|
||||||
|
|
||||||
def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
|
def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
|
||||||
only_load=None, from_parent=None):
|
only_load=None, from_parent=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -25,7 +25,7 @@ from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
|
||||||
from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
|
from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
|
||||||
from django.db.models.sql.expressions import SQLEvaluator
|
from django.db.models.sql.expressions import SQLEvaluator
|
||||||
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
|
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
|
||||||
ExtraWhere, AND, OR)
|
ExtraWhere, AND, OR, EmptyWhere)
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
|
|
||||||
__all__ = ['Query', 'RawQuery']
|
__all__ = ['Query', 'RawQuery']
|
||||||
|
@ -1511,6 +1511,13 @@ class Query(object):
|
||||||
self.add_filter(('%s__isnull' % trimmed_prefix, False), negate=True,
|
self.add_filter(('%s__isnull' % trimmed_prefix, False), negate=True,
|
||||||
can_reuse=can_reuse)
|
can_reuse=can_reuse)
|
||||||
|
|
||||||
|
def set_empty(self):
|
||||||
|
self.where = EmptyWhere()
|
||||||
|
self.having = EmptyWhere()
|
||||||
|
|
||||||
|
def is_empty(self):
|
||||||
|
return isinstance(self.where, EmptyWhere) or isinstance(self.having, EmptyWhere)
|
||||||
|
|
||||||
def set_limits(self, low=None, high=None):
|
def set_limits(self, low=None, high=None):
|
||||||
"""
|
"""
|
||||||
Adjusts the limits on the rows retrieved. We use low/high to set these,
|
Adjusts the limits on the rows retrieved. We use low/high to set these,
|
||||||
|
|
|
@ -272,6 +272,14 @@ class WhereNode(tree.Node):
|
||||||
if hasattr(child[3], 'relabel_aliases'):
|
if hasattr(child[3], 'relabel_aliases'):
|
||||||
child[3].relabel_aliases(change_map)
|
child[3].relabel_aliases(change_map)
|
||||||
|
|
||||||
|
class EmptyWhere(WhereNode):
|
||||||
|
|
||||||
|
def add(self, data, connector):
|
||||||
|
return
|
||||||
|
|
||||||
|
def as_sql(self, qn=None, connection=None):
|
||||||
|
raise EmptyResultSet
|
||||||
|
|
||||||
class EverythingNode(object):
|
class EverythingNode(object):
|
||||||
"""
|
"""
|
||||||
A node that matches everything.
|
A node that matches everything.
|
||||||
|
|
|
@ -593,15 +593,17 @@ none
|
||||||
|
|
||||||
.. method:: none()
|
.. method:: none()
|
||||||
|
|
||||||
Returns an ``EmptyQuerySet`` — a ``QuerySet`` subclass that always evaluates to
|
Calling none() will create a queryset that never returns any objects and no
|
||||||
an empty list. This can be used in cases where you know that you should return
|
query will be executed when accessing the results. A qs.none() queryset
|
||||||
an empty result set and your caller is expecting a ``QuerySet`` object (instead
|
is an instance of ``EmptyQuerySet``.
|
||||||
of returning an empty list, for example.)
|
|
||||||
|
|
||||||
Examples::
|
Examples::
|
||||||
|
|
||||||
>>> Entry.objects.none()
|
>>> Entry.objects.none()
|
||||||
[]
|
[]
|
||||||
|
>>> from django.db.models.query import EmptyQuerySet
|
||||||
|
>>> isinstance(Entry.objects.none(), EmptyQuerySet)
|
||||||
|
True
|
||||||
|
|
||||||
all
|
all
|
||||||
~~~
|
~~~
|
||||||
|
|
|
@ -31,6 +31,11 @@ Minor features
|
||||||
Backwards incompatible changes in 1.6
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
* The ``django.db.models.query.EmptyQuerySet`` can't be instantiated any more -
|
||||||
|
it is only usable as a marker class for checking if
|
||||||
|
:meth:`~django.db.models.query.QuerySet.none` has been called:
|
||||||
|
``isinstance(qs.none(), EmptyQuerySet)``
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
In addition to the changes outlined in this section, be sure to review the
|
In addition to the changes outlined in this section, be sure to review the
|
||||||
|
|
|
@ -4,6 +4,7 @@ from datetime import datetime
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||||
from django.db.models.fields import Field, FieldDoesNotExist
|
from django.db.models.fields import Field, FieldDoesNotExist
|
||||||
|
from django.db.models.query import EmptyQuerySet
|
||||||
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
|
@ -639,3 +640,9 @@ class ModelTest(TestCase):
|
||||||
Article.objects.bulk_create([Article(headline=lazy, pub_date=datetime.now())])
|
Article.objects.bulk_create([Article(headline=lazy, pub_date=datetime.now())])
|
||||||
article = Article.objects.get()
|
article = Article.objects.get()
|
||||||
self.assertEqual(article.headline, notlazy)
|
self.assertEqual(article.headline, notlazy)
|
||||||
|
|
||||||
|
def test_emptyqs(self):
|
||||||
|
# Can't be instantiated
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
EmptyQuerySet()
|
||||||
|
self.assertTrue(isinstance(Article.objects.none(), EmptyQuerySet))
|
||||||
|
|
|
@ -53,7 +53,7 @@ class GetObjectOr404Tests(TestCase):
|
||||||
get_object_or_404, Author.objects.all()
|
get_object_or_404, Author.objects.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Using an EmptyQuerySet raises a Http404 error.
|
# Using an empty QuerySet raises a Http404 error.
|
||||||
self.assertRaises(Http404,
|
self.assertRaises(Http404,
|
||||||
get_object_or_404, Article.objects.none(), title__contains="Run"
|
get_object_or_404, Article.objects.none(), title__contains="Run"
|
||||||
)
|
)
|
||||||
|
|
|
@ -436,7 +436,7 @@ class LookupTests(TestCase):
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_none(self):
|
def test_none(self):
|
||||||
# none() returns an EmptyQuerySet that behaves like any other QuerySet object
|
# none() returns a QuerySet that behaves like any other QuerySet object
|
||||||
self.assertQuerysetEqual(Article.objects.none(), [])
|
self.assertQuerysetEqual(Article.objects.none(), [])
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Article.objects.none().filter(headline__startswith='Article'), [])
|
Article.objects.none().filter(headline__startswith='Article'), [])
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django.conf import settings
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db import DatabaseError, connection, connections, DEFAULT_DB_ALIAS
|
from django.db import DatabaseError, connection, connections, DEFAULT_DB_ALIAS
|
||||||
from django.db.models import Count, F, Q
|
from django.db.models import Count, F, Q
|
||||||
from django.db.models.query import ITER_CHUNK_SIZE, EmptyQuerySet
|
from django.db.models.query import ITER_CHUNK_SIZE
|
||||||
from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode
|
from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode
|
||||||
from django.db.models.sql.datastructures import EmptyResultSet
|
from django.db.models.sql.datastructures import EmptyResultSet
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
|
@ -663,31 +663,32 @@ class Queries1Tests(BaseQuerysetTest):
|
||||||
Item.objects.filter(created__in=[self.time1, self.time2]),
|
Item.objects.filter(created__in=[self.time1, self.time2]),
|
||||||
['<Item: one>', '<Item: two>']
|
['<Item: one>', '<Item: two>']
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ticket7235(self):
|
def test_ticket7235(self):
|
||||||
# An EmptyQuerySet should not raise exceptions if it is filtered.
|
# An EmptyQuerySet should not raise exceptions if it is filtered.
|
||||||
q = EmptyQuerySet()
|
Eaten.objects.create(meal='m')
|
||||||
self.assertQuerysetEqual(q.all(), [])
|
q = Eaten.objects.none()
|
||||||
self.assertQuerysetEqual(q.filter(x=10), [])
|
with self.assertNumQueries(0):
|
||||||
self.assertQuerysetEqual(q.exclude(y=3), [])
|
self.assertQuerysetEqual(q.all(), [])
|
||||||
self.assertQuerysetEqual(q.complex_filter({'pk': 1}), [])
|
self.assertQuerysetEqual(q.filter(meal='m'), [])
|
||||||
self.assertQuerysetEqual(q.select_related('spam', 'eggs'), [])
|
self.assertQuerysetEqual(q.exclude(meal='m'), [])
|
||||||
self.assertQuerysetEqual(q.annotate(Count('eggs')), [])
|
self.assertQuerysetEqual(q.complex_filter({'pk': 1}), [])
|
||||||
self.assertQuerysetEqual(q.order_by('-pub_date', 'headline'), [])
|
self.assertQuerysetEqual(q.select_related('food'), [])
|
||||||
self.assertQuerysetEqual(q.distinct(), [])
|
self.assertQuerysetEqual(q.annotate(Count('food')), [])
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(q.order_by('meal', 'food'), [])
|
||||||
q.extra(select={'is_recent': "pub_date > '2006-01-01'"}),
|
self.assertQuerysetEqual(q.distinct(), [])
|
||||||
[]
|
self.assertQuerysetEqual(
|
||||||
)
|
q.extra(select={'foo': "1"}),
|
||||||
q.query.low_mark = 1
|
[]
|
||||||
self.assertRaisesMessage(
|
)
|
||||||
AssertionError,
|
q.query.low_mark = 1
|
||||||
'Cannot change a query once a slice has been taken',
|
self.assertRaisesMessage(
|
||||||
q.extra, select={'is_recent': "pub_date > '2006-01-01'"}
|
AssertionError,
|
||||||
)
|
'Cannot change a query once a slice has been taken',
|
||||||
self.assertQuerysetEqual(q.reverse(), [])
|
q.extra, select={'foo': "1"}
|
||||||
self.assertQuerysetEqual(q.defer('spam', 'eggs'), [])
|
)
|
||||||
self.assertQuerysetEqual(q.only('spam', 'eggs'), [])
|
self.assertQuerysetEqual(q.reverse(), [])
|
||||||
|
self.assertQuerysetEqual(q.defer('meal'), [])
|
||||||
|
self.assertQuerysetEqual(q.only('meal'), [])
|
||||||
|
|
||||||
def test_ticket7791(self):
|
def test_ticket7791(self):
|
||||||
# There were "issues" when ordering and distinct-ing on fields related
|
# There were "issues" when ordering and distinct-ing on fields related
|
||||||
|
@ -1935,8 +1936,8 @@ class CloneTests(TestCase):
|
||||||
|
|
||||||
class EmptyQuerySetTests(TestCase):
|
class EmptyQuerySetTests(TestCase):
|
||||||
def test_emptyqueryset_values(self):
|
def test_emptyqueryset_values(self):
|
||||||
# #14366 -- Calling .values() on an EmptyQuerySet and then cloning that
|
# #14366 -- Calling .values() on an empty QuerySet and then cloning
|
||||||
# should not cause an error"
|
# that should not cause an error
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Number.objects.none().values('num').order_by('num'), []
|
Number.objects.none().values('num').order_by('num'), []
|
||||||
)
|
)
|
||||||
|
@ -1952,9 +1953,9 @@ class EmptyQuerySetTests(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ticket_19151(self):
|
def test_ticket_19151(self):
|
||||||
# #19151 -- Calling .values() or .values_list() on an EmptyQuerySet
|
# #19151 -- Calling .values() or .values_list() on an empty QuerySet
|
||||||
# should return EmptyQuerySet and not cause an error.
|
# should return an empty QuerySet and not cause an error.
|
||||||
q = EmptyQuerySet()
|
q = Author.objects.none()
|
||||||
self.assertQuerysetEqual(q.values(), [])
|
self.assertQuerysetEqual(q.values(), [])
|
||||||
self.assertQuerysetEqual(q.values_list(), [])
|
self.assertQuerysetEqual(q.values_list(), [])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue