diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 6005874516..b60eed262a 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -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.db.models import signals from django.db.models.fields import FieldDoesNotExist @@ -41,12 +41,18 @@ class Manager(object): ####################### # PROXIES TO QUERYSET # ####################### + + def get_empty_query_set(self): + return EmptyQuerySet(self.model) def get_query_set(self): """Returns a new QuerySet object. Subclasses can override this method to easily customise the behaviour of the Manager. """ return QuerySet(self.model) + + def none(self): + return self.get_empty_query_set() def all(self): return self.get_query_set() diff --git a/django/db/models/query.py b/django/db/models/query.py index f42e8cfd42..cbfd09e8d7 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -25,6 +25,9 @@ QUERY_TERMS = ( # Larger values are slightly faster at the expense of more storage space. GET_ITERATOR_CHUNK_SIZE = 100 +class EmptyResultSet(Exception): + pass + #################### # HELPER FUNCTIONS # #################### @@ -168,7 +171,12 @@ class QuerySet(object): extra_select = self._select.items() 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) fill_cache = self._select_related index_end = len(self.model._meta.fields) @@ -192,7 +200,12 @@ class QuerySet(object): counter._offset = None counter._limit = None 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() if self._distinct: 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] 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] cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) while 1: @@ -545,7 +563,12 @@ class DateQuerySet(QuerySet): if self._field.null: self._where.append('%s.%s IS NOT NULL' % \ (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' % \ (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) @@ -562,6 +585,25 @@ class DateQuerySet(QuerySet): c._kind = self._kind c._order = self._order 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): "Base class for QAnd and QOr" @@ -571,10 +613,14 @@ class QOperator(object): def get_sql(self, opts): joins, where, params = SortedDict(), [], [] for val in self.args: - joins2, where2, params2 = val.get_sql(opts) - joins.update(joins2) - where.extend(where2) - params.extend(params2) + try: + joins2, where2, params2 = val.get_sql(opts) + joins.update(joins2) + where.extend(where2) + params.extend(params2) + except EmptyResultSet: + if not isinstance(self, QOr): + raise EmptyResultSet if where: return joins, ['(%s)' % self.operator.join(where)], params return joins, [], params @@ -628,8 +674,11 @@ class QNot(Q): self.q = q def get_sql(self, opts): - joins, where, params = self.q.get_sql(opts) - where2 = ['(NOT (%s))' % " AND ".join(where)] + try: + joins, where, params = self.q.get_sql(opts) + where2 = ['(NOT (%s))' % " AND ".join(where)] + except EmptyResultSet: + return SortedDict(), [], [] return joins, where2, params 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: return '%s%s IN (%s)' % (table_prefix, field_name, in_string) else: - # Most backends do not accept an empty string inside the IN - # 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' + raise EmptyResultSet elif lookup_type == 'range': return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) elif lookup_type in ('year', 'month', 'day'): diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py index 0b9ac05edb..b437109596 100644 --- a/django/oldforms/__init__.py +++ b/django/oldforms/__init__.py @@ -569,7 +569,7 @@ class NullBooleanField(SelectField): "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None" def __init__(self, field_name, is_required=False, validator_list=None): if validator_list is None: validator_list = [] - SelectField.__init__(self, field_name, choices=[('1', 'Unknown'), ('2', 'Yes'), ('3', 'No')], + SelectField.__init__(self, field_name, choices=[('1', _('Unknown')), ('2', _('Yes')), ('3', _('No'))], is_required=is_required, validator_list=validator_list) def render(self, data): diff --git a/docs/db-api.txt b/docs/db-api.txt index 0af2c773fb..99bb30054b 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -525,6 +525,21 @@ Examples:: [datetime.datetime(2005, 3, 20), datetime.datetime(2005, 2, 20)] >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day') [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()`` ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/generic_views.txt b/docs/generic_views.txt index 25635f35d8..932687d90b 100644 --- a/docs/generic_views.txt +++ b/docs/generic_views.txt @@ -99,7 +99,7 @@ which is a dictionary of the parameters captured in the URL. dictionary is callable, the generic view will call it just before rendering the template. (**This is new in the Django development version.**) - + **Example:** Given the following URL patterns:: @@ -205,11 +205,11 @@ If ``template_name`` isn't specified, this view will use the template ``/_archive.html`` by default, where: * ```` is your model's name in all lowercase. For a model - ``StaffMember``, that'd be ``staffmember``. + ``StaffMember``, that'd be ``staffmember``. * ```` is the right-most part of the full Python path to - your model's app. For example, if your model lives in - ``apps/blog/models.py``, that'd be ``blog``. + your model's app. For example, if your model lives in + ``apps/blog/models.py``, that'd be ``blog``. **Template context:** @@ -266,9 +266,9 @@ to ``True``. the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. The - view will append ``'_list'`` to the value of this parameter in - determining the variable's name. + to use in the template context. By default, this is ``'object'``. The + view will append ``'_list'`` to the value of this parameter in + determining the variable's name. * ``make_object_list``: A boolean specifying whether to retrieve the full list of objects for this year and pass those to the template. If ``True``, @@ -360,9 +360,9 @@ date in the *future* are not displayed unless you set ``allow_future`` to the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. The - view will append ``'_list'`` to the value of this parameter in - determining the variable's name. + to use in the template context. By default, this is ``'object'``. The + view will append ``'_list'`` to the value of this parameter in + determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_CONTENT_TYPE`` setting. @@ -441,9 +441,9 @@ in the *future* are not displayed unless you set ``allow_future`` to ``True``. the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. The - view will append ``'_list'`` to the value of this parameter in - determining the variable's name. + to use in the template context. By default, this is ``'object'``. The + view will append ``'_list'`` to the value of this parameter in + determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_CONTENT_TYPE`` setting. @@ -526,9 +526,9 @@ you set ``allow_future`` to ``True``. the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. The - view will append ``'_list'`` to the value of this parameter in - determining the variable's name. + to use in the template context. By default, this is ``'object'``. The + view will append ``'_list'`` to the value of this parameter in + determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_CONTENT_TYPE`` setting. @@ -638,7 +638,7 @@ future, the view will throw a 404 error by default, unless you set the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. + to use in the template context. By default, this is ``'object'``. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_CONTENT_TYPE`` setting. @@ -710,9 +710,9 @@ A page representing a list of objects. the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. The - view will append ``'_list'`` to the value of this parameter in - determining the variable's name. + to use in the template context. By default, this is ``'object'``. The + view will append ``'_list'`` to the value of this parameter in + determining the variable's name. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_CONTENT_TYPE`` setting. @@ -824,7 +824,7 @@ A page representing an individual object. the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. + to use in the template context. By default, this is ``'object'``. * ``mimetype``: The MIME type to use for the resulting document. Defaults to the value of the ``DEFAULT_CONTENT_TYPE`` setting. @@ -973,7 +973,7 @@ object. This uses the automatic manipulators that come with Django models. the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. + to use in the template context. By default, this is ``'object'``. **Template name:** @@ -1054,7 +1054,7 @@ contain a form that POSTs to the same URL. the view's template. See the `RequestContext docs`_. * ``template_object_name``: Designates the name of the template variable - to use in the template context. By default, this is ``'object'``. + to use in the template context. By default, this is ``'object'``. **Template name:** diff --git a/docs/model-api.txt b/docs/model-api.txt index 603c131662..33742220a3 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -1408,7 +1408,10 @@ This should be set to a list of field names that will be searched whenever somebody submits a search query in that text box. These fields should be some kind of text field, such as ``CharField`` or -``TextField``. +``TextField``. You can also perform a related lookup on a ``ForeignKey`` with +the lookup API "follow" notation:: + + search_fields = ['foreign_key__related_fieldname'] When somebody does a search in the admin search box, Django splits the search query into words and returns all objects that contain each of the words, case diff --git a/docs/settings.txt b/docs/settings.txt index 23fbb10d76..38a9e33226 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -557,6 +557,11 @@ Default: ``''`` (Empty string) URL that handles the media served from ``MEDIA_ROOT``. Example: ``"http://media.lawrence.com"`` +Note that this should have a trailing slash if it has a path component. + +Good: ``"http://www.example.com/static/"`` +Bad: ``"http://www.example.com/static"`` + MIDDLEWARE_CLASSES ------------------ diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt index 41d11d9e6e..69984cb47a 100644 --- a/docs/tutorial03.txt +++ b/docs/tutorial03.txt @@ -260,8 +260,7 @@ provides a shortcut. Here's the full ``index()`` view, rewritten:: latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list}) -Note that we no longer need to import ``loader``, ``Context`` or -``HttpResponse``. +Note that once we've done this in all these views, we no longer need to import ``loader``, ``Context`` and ``HttpResponse``. The ``render_to_response()`` function takes a template name as its first argument and a dictionary as its optional second argument. It returns an diff --git a/docs/url_dispatch.txt b/docs/url_dispatch.txt index 4991557859..8ad9cb0e73 100644 --- a/docs/url_dispatch.txt +++ b/docs/url_dispatch.txt @@ -393,7 +393,7 @@ to pass metadata and options to views. Passing extra options to ``include()`` -------------------------------------- -**New in the Django development version.** +**New in Django development version.** Similarly, you can pass extra options to ``include()``. When you pass extra options to ``include()``, *each* line in the included URLconf will be passed @@ -435,7 +435,7 @@ every view in the the included URLconf accepts the extra options you're passing. Passing callable objects instead of strings =========================================== -**New in the Django development version.** +**New in Django development version.** Some developers find it more natural to pass the actual Python function object rather than a string containing the path to its module. This alternative is diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 09c3aa7aa8..aa903d1a64 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -191,4 +191,19 @@ DoesNotExist: Article matching query does not exist. >>> Article.objects.filter(headline__contains='\\') [] +# 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=[]) +[, , , , , , , , , ] + """}