mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #3283 -- Added support for empty QuerySets via none() method. Thanks for the patch, medhat
git-svn-id: http://code.djangoproject.com/svn/django/trunk@4394 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -1,4 +1,4 @@ | |||||||
| from django.db.models.query import QuerySet | from django.db.models.query import QuerySet, EmptyQuerySet | ||||||
| from django.dispatch import dispatcher | from django.dispatch import dispatcher | ||||||
| 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 | ||||||
| @@ -42,12 +42,18 @@ class Manager(object): | |||||||
|     # PROXIES TO QUERYSET # |     # PROXIES TO QUERYSET # | ||||||
|     ####################### |     ####################### | ||||||
|      |      | ||||||
|  |     def get_empty_query_set(self): | ||||||
|  |         return EmptyQuerySet(self.model) | ||||||
|  |  | ||||||
|     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 | ||||||
|         to easily customise the behaviour of the Manager. |         to easily customise the behaviour of the Manager. | ||||||
|         """ |         """ | ||||||
|         return QuerySet(self.model) |         return QuerySet(self.model) | ||||||
|      |      | ||||||
|  |     def none(self): | ||||||
|  |         return self.get_empty_query_set() | ||||||
|  |  | ||||||
|     def all(self): |     def all(self): | ||||||
|         return self.get_query_set() |         return self.get_query_set() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,6 +25,9 @@ QUERY_TERMS = ( | |||||||
| # Larger values are slightly faster at the expense of more storage space. | # Larger values are slightly faster at the expense of more storage space. | ||||||
| GET_ITERATOR_CHUNK_SIZE = 100 | GET_ITERATOR_CHUNK_SIZE = 100 | ||||||
|  |  | ||||||
|  | class EmptyResultSet(Exception): | ||||||
|  |     pass | ||||||
|  |  | ||||||
| #################### | #################### | ||||||
| # HELPER FUNCTIONS # | # HELPER FUNCTIONS # | ||||||
| #################### | #################### | ||||||
| @@ -168,7 +171,12 @@ class QuerySet(object): | |||||||
|         extra_select = self._select.items() |         extra_select = self._select.items() | ||||||
|  |  | ||||||
|         cursor = connection.cursor() |         cursor = connection.cursor() | ||||||
|         select, sql, params = self._get_sql_clause() |          | ||||||
|  |         try: | ||||||
|  |             select, sql, params = self._get_sql_clause() | ||||||
|  |         except EmptyResultSet: | ||||||
|  |             raise StopIteration | ||||||
|  |              | ||||||
|         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) |         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) | ||||||
|         fill_cache = self._select_related |         fill_cache = self._select_related | ||||||
|         index_end = len(self.model._meta.fields) |         index_end = len(self.model._meta.fields) | ||||||
| @@ -192,7 +200,12 @@ class QuerySet(object): | |||||||
|         counter._offset = None |         counter._offset = None | ||||||
|         counter._limit = None |         counter._limit = None | ||||||
|         counter._select_related = False |         counter._select_related = False | ||||||
|         select, sql, params = counter._get_sql_clause() |          | ||||||
|  |         try: | ||||||
|  |             select, sql, params = counter._get_sql_clause() | ||||||
|  |         except EmptyResultSet: | ||||||
|  |             return 0 | ||||||
|  |              | ||||||
|         cursor = connection.cursor() |         cursor = connection.cursor() | ||||||
|         if self._distinct: |         if self._distinct: | ||||||
|             id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), |             id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), | ||||||
| @@ -523,7 +536,12 @@ class ValuesQuerySet(QuerySet): | |||||||
|             field_names = [f.attname for f in self.model._meta.fields] |             field_names = [f.attname for f in self.model._meta.fields] | ||||||
|  |  | ||||||
|         cursor = connection.cursor() |         cursor = connection.cursor() | ||||||
|         select, sql, params = self._get_sql_clause() |          | ||||||
|  |         try: | ||||||
|  |             select, sql, params = self._get_sql_clause() | ||||||
|  |         except EmptyResultSet: | ||||||
|  |             raise StopIteration | ||||||
|  |          | ||||||
|         select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] |         select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] | ||||||
|         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) |         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) | ||||||
|         while 1: |         while 1: | ||||||
| @@ -545,7 +563,12 @@ class DateQuerySet(QuerySet): | |||||||
|         if self._field.null: |         if self._field.null: | ||||||
|             self._where.append('%s.%s IS NOT NULL' % \ |             self._where.append('%s.%s IS NOT NULL' % \ | ||||||
|                 (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) |                 (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) | ||||||
|         select, sql, params = self._get_sql_clause() |                  | ||||||
|  |         try: | ||||||
|  |             select, sql, params = self._get_sql_clause() | ||||||
|  |         except EmptyResultSet: | ||||||
|  |             raise StopIteration | ||||||
|  |          | ||||||
|         sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ |         sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ | ||||||
|             (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), |             (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), | ||||||
|             backend.quote_name(self._field.column))), sql, self._order) |             backend.quote_name(self._field.column))), sql, self._order) | ||||||
| @@ -563,6 +586,25 @@ class DateQuerySet(QuerySet): | |||||||
|         c._order = self._order |         c._order = self._order | ||||||
|         return c |         return c | ||||||
|      |      | ||||||
|  | class EmptyQuerySet(QuerySet): | ||||||
|  |     def __init__(self, model=None): | ||||||
|  |         super(EmptyQuerySet, self).__init__(model) | ||||||
|  |         self._result_cache = [] | ||||||
|  |          | ||||||
|  |     def iterator(self): | ||||||
|  |         raise StopIteration | ||||||
|  |          | ||||||
|  |     def count(self): | ||||||
|  |         return 0 | ||||||
|  |          | ||||||
|  |     def delete(self): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def _clone(self, klass=None, **kwargs): | ||||||
|  |         c = super(EmptyQuerySet, self)._clone(klass, **kwargs) | ||||||
|  |         c._result_cache = [] | ||||||
|  |         return c | ||||||
|  |  | ||||||
| class QOperator(object): | class QOperator(object): | ||||||
|     "Base class for QAnd and QOr" |     "Base class for QAnd and QOr" | ||||||
|     def __init__(self, *args): |     def __init__(self, *args): | ||||||
| @@ -571,10 +613,14 @@ class QOperator(object): | |||||||
|     def get_sql(self, opts): |     def get_sql(self, opts): | ||||||
|         joins, where, params = SortedDict(), [], [] |         joins, where, params = SortedDict(), [], [] | ||||||
|         for val in self.args: |         for val in self.args: | ||||||
|             joins2, where2, params2 = val.get_sql(opts) |             try: | ||||||
|             joins.update(joins2) |                 joins2, where2, params2 = val.get_sql(opts) | ||||||
|             where.extend(where2) |                 joins.update(joins2) | ||||||
|             params.extend(params2) |                 where.extend(where2) | ||||||
|  |                 params.extend(params2) | ||||||
|  |             except EmptyResultSet: | ||||||
|  |                 if not isinstance(self, QOr): | ||||||
|  |                     raise EmptyResultSet | ||||||
|         if where: |         if where: | ||||||
|             return joins, ['(%s)' % self.operator.join(where)], params |             return joins, ['(%s)' % self.operator.join(where)], params | ||||||
|         return joins, [], params |         return joins, [], params | ||||||
| @@ -628,8 +674,11 @@ class QNot(Q): | |||||||
|         self.q = q |         self.q = q | ||||||
|  |  | ||||||
|     def get_sql(self, opts): |     def get_sql(self, opts): | ||||||
|         joins, where, params = self.q.get_sql(opts) |         try: | ||||||
|         where2 = ['(NOT (%s))' % " AND ".join(where)] |             joins, where, params = self.q.get_sql(opts) | ||||||
|  |             where2 = ['(NOT (%s))' % " AND ".join(where)] | ||||||
|  |         except EmptyResultSet: | ||||||
|  |             return SortedDict(), [], [] | ||||||
|         return joins, where2, params |         return joins, where2, params | ||||||
|  |  | ||||||
| def get_where_clause(lookup_type, table_prefix, field_name, value): | def get_where_clause(lookup_type, table_prefix, field_name, value): | ||||||
| @@ -645,11 +694,7 @@ def get_where_clause(lookup_type, table_prefix, field_name, value): | |||||||
|         if in_string: |         if in_string: | ||||||
|             return '%s%s IN (%s)' % (table_prefix, field_name, in_string) |             return '%s%s IN (%s)' % (table_prefix, field_name, in_string) | ||||||
|         else: |         else: | ||||||
|             # Most backends do not accept an empty string inside the IN |             raise EmptyResultSet | ||||||
|             # expression, i.e. cannot do "WHERE ... IN ()".  Since there are |  | ||||||
|             # also some backends that do not accept "WHERE false", we instead |  | ||||||
|             # use an expression that always evaluates to False. |  | ||||||
|             return '0=1' |  | ||||||
|     elif lookup_type == 'range': |     elif lookup_type == 'range': | ||||||
|         return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) |         return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) | ||||||
|     elif lookup_type in ('year', 'month', 'day'): |     elif lookup_type in ('year', 'month', 'day'): | ||||||
|   | |||||||
| @@ -526,6 +526,21 @@ Examples:: | |||||||
|     >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day') |     >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day') | ||||||
|     [datetime.datetime(2005, 3, 20)] |     [datetime.datetime(2005, 3, 20)] | ||||||
|      |      | ||||||
|  | ``none()`` | ||||||
|  | ~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | **New in Django development version** | ||||||
|  |  | ||||||
|  | Returns an ``EmptyQuerySet`` -- a ``QuerySet`` that always evaluates to  | ||||||
|  | an empty list. This can be used in cases where you know that you should | ||||||
|  | return an empty result set and your caller is expecting a ``QuerySet`` | ||||||
|  | object (instead of returning an empty list, for example.) | ||||||
|  |  | ||||||
|  | Examples:: | ||||||
|  |      | ||||||
|  |     >>> Entry.objects.none() | ||||||
|  |     [] | ||||||
|  |  | ||||||
| ``select_related()`` | ``select_related()`` | ||||||
| ~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -191,4 +191,19 @@ DoesNotExist: Article matching query does not exist. | |||||||
| >>> Article.objects.filter(headline__contains='\\') | >>> Article.objects.filter(headline__contains='\\') | ||||||
| [<Article: Article with \ backslash>] | [<Article: Article with \ backslash>] | ||||||
|  |  | ||||||
|  | # none() returns an EmptyQuerySet that behaves like any other QuerySet object | ||||||
|  | >>> Article.objects.none() | ||||||
|  | [] | ||||||
|  | >>> Article.objects.none().filter(headline__startswith='Article') | ||||||
|  | [] | ||||||
|  | >>> Article.objects.none().count() | ||||||
|  | 0 | ||||||
|  |  | ||||||
|  | # using __in with an empty list should return an empty query set | ||||||
|  | >>> Article.objects.filter(id__in=[]) | ||||||
|  | [] | ||||||
|  |  | ||||||
|  | >>> Article.objects.exclude(id__in=[]) | ||||||
|  | [<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>] | ||||||
|  |  | ||||||
| """} | """} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user