From b79702b2deec4ca3c625e5bffe46fa976c3c4e5f Mon Sep 17 00:00:00 2001
From: Jacob Kaplan-Moss <jacob@jacobian.org>
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 <topics-db-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
 -------------