From b79702b2deec4ca3c625e5bffe46fa976c3c4e5f Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sat, 24 Oct 2009 00:28:39 +0000 Subject: [PATCH] Fixed #11402: added a `QuerySet.exists()` method. Thanks, Alex Gaynor. git-svn-id: http://code.djangoproject.com/svn/django/trunk@11646 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/base.py | 9 +-------- django/db/models/manager.py | 4 +++- django/db/models/query.py | 12 +++++------- django/db/models/sql/query.py | 15 +++++++++------ django/forms/models.py | 8 ++------ docs/ref/models/querysets.txt | 11 +++++++++++ 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index a5c99865a6..1e081ae92e 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -3,11 +3,6 @@ import types import sys import os from itertools import izip -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback. - import django.db.models.manager # Imported to register signal handler. from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError from django.db.models.fields import AutoField, FieldDoesNotExist @@ -22,7 +17,6 @@ from django.utils.functional import curry from django.utils.encoding import smart_str, force_unicode, smart_unicode from django.conf import settings - class ModelBase(type): """ Metaclass for all models. @@ -236,7 +230,6 @@ class ModelBase(type): signals.class_prepared.send(sender=cls) - class Model(object): __metaclass__ = ModelBase _deferred = False @@ -467,7 +460,7 @@ class Model(object): if pk_set: # Determine whether a record with the primary key already exists. if (force_update or (not force_insert and - manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())): + manager.filter(pk=pk_val).exists())): # It does already exist, so do an UPDATE. if force_update or non_pks: values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks] diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 52612d8f64..af48fea705 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -1,5 +1,4 @@ import copy - from django.db.models.query import QuerySet, EmptyQuerySet, insert_query from django.db.models import signals from django.db.models.fields import FieldDoesNotExist @@ -173,6 +172,9 @@ class Manager(object): def only(self, *args, **kwargs): return self.get_query_set().only(*args, **kwargs) + def exists(self, *args, **kwargs): + return self.get_query_ste().exists(*args, **kwargs) + def _insert(self, values, **kwargs): return insert_query(self.model, values, **kwargs) diff --git a/django/db/models/query.py b/django/db/models/query.py index 46a86fc03c..d6d290584e 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -2,20 +2,13 @@ The main QuerySet implementation. This provides the public API for the ORM. """ -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback - from copy import deepcopy - from django.db import connection, transaction, IntegrityError from django.db.models.aggregates import Aggregate from django.db.models.fields import DateField from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory from django.db.models import signals, sql - # Used to control how many objects are worked with at once in some cases (e.g. # when deleting objects). CHUNK_SIZE = 100 @@ -444,6 +437,11 @@ class QuerySet(object): return query.execute_sql(None) _update.alters_data = True + def exists(self): + if self._result_cache is None: + return self.query.has_results() + return bool(self._result_cache) + ################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 23f99e41ad..136c97f639 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -8,7 +8,6 @@ all about the internals of models in order to get the information it needs. """ from copy import deepcopy - from django.utils.tree import Node from django.utils.datastructures import SortedDict from django.utils.encoding import force_unicode @@ -24,11 +23,6 @@ from django.core.exceptions import FieldError from datastructures import EmptyResultSet, Empty, MultiJoin from constants import * -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback - __all__ = ['Query', 'BaseQuery'] class BaseQuery(object): @@ -384,6 +378,15 @@ class BaseQuery(object): return number + def has_results(self): + q = self.clone() + q.add_extra({'a': 1}, None, None, None, None, None) + q.add_fields(()) + q.set_extra_mask(('a',)) + q.set_aggregate_mask(()) + q.clear_ordering() + return bool(q.execute_sql()) + def as_sql(self, with_limits=True, with_col_aliases=False): """ Creates the SQL for this query. Returns the SQL string and list of diff --git a/django/forms/models.py b/django/forms/models.py index cc43612bf5..37fce6824c 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -319,9 +319,7 @@ class BaseModelForm(BaseForm): if self.instance.pk is not None: qs = qs.exclude(pk=self.instance.pk) - # This cute trick with extra/values is the most efficient way to - # tell if a particular query returns any results. - if qs.extra(select={'a': 1}).values('a').order_by(): + if qs.exists(): if len(unique_check) == 1: self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)]) else: @@ -354,9 +352,7 @@ class BaseModelForm(BaseForm): if self.instance.pk is not None: qs = qs.exclude(pk=self.instance.pk) - # This cute trick with extra/values is the most efficient way to - # tell if a particular query returns any results. - if qs.extra(select={'a': 1}).values('a').order_by(): + if qs.exists(): self._errors[field] = ErrorList([ self.date_error_message(lookup_type, field, unique_for) ]) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index efd7c549b8..e685472cca 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1114,6 +1114,17 @@ Aggregation `. .. _field-lookups: +``exists()`` +~~~~~~~~~~~~ + +.. versionadded:: 1.2 + +Returns ``True`` if the :class:`QuerySet` contains any results, and ``False`` +if not. This tries to perform the query in the simplest and fastest way +possible, but it *does* execute nearly the same query. This means that calling +:meth:`QuerySet.exists()` is faster that ``bool(some_query_set)``, but not by +a large degree. + Field lookups -------------