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:
Anssi Kääriäinen 2012-10-24 00:04:37 +03:00
parent a843539af2
commit a2396a4c8f
11 changed files with 96 additions and 177 deletions

View file

@ -35,7 +35,6 @@ class QuerySet(object):
"""
def __init__(self, model=None, query=None, using=None):
self.model = model
# EmptyQuerySet instantiates QuerySet with model as None
self._db = using
self.query = query or sql.Query(self.model)
self._result_cache = None
@ -217,7 +216,9 @@ class QuerySet(object):
def __and__(self, other):
self._merge_sanity_check(other)
if isinstance(other, EmptyQuerySet):
return other._clone()
return other
if isinstance(self, EmptyQuerySet):
return self
combined = self._clone()
combined._merge_known_related_objects(other)
combined.query.combine(other.query, sql.AND)
@ -225,9 +226,11 @@ class QuerySet(object):
def __or__(self, other):
self._merge_sanity_check(other)
combined = self._clone()
if isinstance(self, EmptyQuerySet):
return other
if isinstance(other, EmptyQuerySet):
return combined
return self
combined = self._clone()
combined._merge_known_related_objects(other)
combined.query.combine(other.query, sql.OR)
return combined
@ -632,7 +635,9 @@ class QuerySet(object):
"""
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 #
@ -981,6 +986,18 @@ class QuerySet(object):
# empty" result.
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):
def __init__(self, *args, **kwargs):
@ -1180,138 +1197,6 @@ class DateQuerySet(QuerySet):
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,
only_load=None, from_parent=None):
"""