Code

Ticket #3283: empty_queryset_2.patch

File empty_queryset_2.patch, 7.1 KB (added by medhat, 7 years ago)

this should replace the two previous patches, it contains both patches, documentation, and tests.

  • django/db/models/manager.py

     
    1 from django.db.models.query import QuerySet 
     1from django.db.models.query import QuerySet, EmptyQuerySet 
    22from django.dispatch import dispatcher 
    33from django.db.models import signals 
    44from django.db.models.fields import FieldDoesNotExist 
     
    4141    ####################### 
    4242    # PROXIES TO QUERYSET # 
    4343    ####################### 
     44     
     45    def get_empty_query_set(self): 
     46        return EmptyQuerySet(self.model) 
    4447 
    4548    def get_query_set(self): 
    4649        """Returns a new QuerySet object.  Subclasses can override this method 
    4750        to easily customise the behaviour of the Manager. 
    4851        """ 
    4952        return QuerySet(self.model) 
     53     
     54    def none(self): 
     55        return self.get_empty_query_set() 
    5056 
    5157    def all(self): 
    5258        return self.get_query_set() 
  • django/db/models/query.py

     
    2525# Larger values are slightly faster at the expense of more storage space. 
    2626GET_ITERATOR_CHUNK_SIZE = 100 
    2727 
     28class EmptyResultSet(Exception): 
     29    pass 
     30 
    2831#################### 
    2932# HELPER FUNCTIONS # 
    3033#################### 
     
    168171        extra_select = self._select.items() 
    169172 
    170173        cursor = connection.cursor() 
    171         select, sql, params = self._get_sql_clause() 
     174         
     175        try: 
     176            select, sql, params = self._get_sql_clause() 
     177        except EmptyResultSet: 
     178            raise StopIteration 
     179             
    172180        cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) 
    173181        fill_cache = self._select_related 
    174182        index_end = len(self.model._meta.fields) 
     
    192200        counter._offset = None 
    193201        counter._limit = None 
    194202        counter._select_related = False 
    195         select, sql, params = counter._get_sql_clause() 
     203         
     204        try: 
     205            select, sql, params = counter._get_sql_clause() 
     206        except EmptyResultSet: 
     207            return 0 
     208             
    196209        cursor = connection.cursor() 
    197210        if self._distinct: 
    198211            id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), 
     
    523536            field_names = [f.attname for f in self.model._meta.fields] 
    524537 
    525538        cursor = connection.cursor() 
    526         select, sql, params = self._get_sql_clause() 
     539         
     540        try: 
     541            select, sql, params = self._get_sql_clause() 
     542        except EmptyResultSet: 
     543            raise StopIteration 
     544         
    527545        select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] 
    528546        cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) 
    529547        while 1: 
     
    545563        if self._field.null: 
    546564            self._where.append('%s.%s IS NOT NULL' % \ 
    547565                (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) 
    548         select, sql, params = self._get_sql_clause() 
     566                 
     567        try: 
     568            select, sql, params = self._get_sql_clause() 
     569        except EmptyResultSet: 
     570            raise StopIteration 
     571         
    549572        sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ 
    550573            (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), 
    551574            backend.quote_name(self._field.column))), sql, self._order) 
     
    562585        c._kind = self._kind 
    563586        c._order = self._order 
    564587        return c 
     588     
     589class EmptyQuerySet(QuerySet): 
     590    def __init__(self, model=None): 
     591        super(EmptyQuerySet,self).__init__(model) 
     592        self._result_cache = [] 
     593         
     594    def iterator(self): 
     595        raise StopIteration 
     596         
     597    def count(self): 
     598        return 0 
     599         
     600    def delete(self): 
     601        pass 
    565602 
     603    def _clone(self, klass=None, **kwargs): 
     604        c = super(EmptyQuerySet, self)._clone(klass, **kwargs) 
     605        c._result_cache = [] 
     606        return c 
     607 
    566608class QOperator(object): 
    567609    "Base class for QAnd and QOr" 
    568610    def __init__(self, *args): 
     
    571613    def get_sql(self, opts): 
    572614        joins, where, params = SortedDict(), [], [] 
    573615        for val in self.args: 
    574             joins2, where2, params2 = val.get_sql(opts) 
    575             joins.update(joins2) 
    576             where.extend(where2) 
    577             params.extend(params2) 
     616            try: 
     617                joins2, where2, params2 = val.get_sql(opts) 
     618                joins.update(joins2) 
     619                where.extend(where2) 
     620                params.extend(params2) 
     621            except EmptyResultSet: 
     622                if not isinstance(self,QOr): 
     623                    raise EmptyResultSet 
    578624        if where: 
    579625            return joins, ['(%s)' % self.operator.join(where)], params 
    580626        return joins, [], params 
     
    645691        if in_string: 
    646692            return '%s%s IN (%s)' % (table_prefix, field_name, in_string) 
    647693        else: 
    648             # Most backends do not accept an empty string inside the IN 
    649             # expression, i.e. cannot do "WHERE ... IN ()".  Since there are 
    650             # also some backends that do not accept "WHERE false", we instead 
    651             # use an expression that always evaluates to False. 
    652             return '0=1' 
     694            raise EmptyResultSet 
    653695    elif lookup_type == 'range': 
    654696        return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) 
    655697    elif lookup_type in ('year', 'month', 'day'): 
  • docs/db-api.txt

     
    525525    [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)] 
    526526    >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day') 
    527527    [datetime.datetime(2005, 3, 20)] 
     528     
     529``none()`` 
     530~~~~~~~~~~ 
    528531 
     532Returns an ``EmptyQuerySet`` -- a ``QuerySet`` that always evaluates to  
     533an empty list. This can be used in cases where you know that you should 
     534return an empty result set and your caller is expecting a ``QuerySet`` 
     535object (instead of returning an empty list, for example.) 
     536 
     537Examples:: 
     538     
     539    >>> Entry.objects.none() 
     540    [] 
     541 
    529542``select_related()`` 
    530543~~~~~~~~~~~~~~~~~~~~ 
    531544 
  • tests/modeltests/lookup/models.py

     
    191191>>> Article.objects.filter(headline__contains='\\') 
    192192[<Article: Article with \ backslash>] 
    193193 
     194# none() returns an EmptyQuerySet that behaves like any other QuerySet object 
     195>>> Article.objects.none() 
     196[] 
     197>>> Article.objects.none().filter(headline__startswith='Article') 
     198[] 
     199>>> Article.objects.none().count() 
     2000 
     201 
     202# using __in with an empty list should return an empty query set 
     203>>> Article.objects.filter(id__in=[]) 
     204[] 
     205 
    194206"""}