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
This commit is contained in:
Jacob Kaplan-Moss 2009-10-24 00:28:39 +00:00
parent 9f70783b14
commit b79702b2de
6 changed files with 31 additions and 28 deletions

View File

@ -3,11 +3,6 @@ import types
import sys import sys
import os import os
from itertools import izip 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. import django.db.models.manager # Imported to register signal handler.
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
from django.db.models.fields import AutoField, FieldDoesNotExist 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.utils.encoding import smart_str, force_unicode, smart_unicode
from django.conf import settings from django.conf import settings
class ModelBase(type): class ModelBase(type):
""" """
Metaclass for all models. Metaclass for all models.
@ -236,7 +230,6 @@ class ModelBase(type):
signals.class_prepared.send(sender=cls) signals.class_prepared.send(sender=cls)
class Model(object): class Model(object):
__metaclass__ = ModelBase __metaclass__ = ModelBase
_deferred = False _deferred = False
@ -467,7 +460,7 @@ class Model(object):
if pk_set: if pk_set:
# Determine whether a record with the primary key already exists. # Determine whether a record with the primary key already exists.
if (force_update or (not force_insert and 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. # It does already exist, so do an UPDATE.
if force_update or non_pks: 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] values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]

View File

@ -1,5 +1,4 @@
import copy import copy
from django.db.models.query import QuerySet, EmptyQuerySet, insert_query from django.db.models.query import QuerySet, EmptyQuerySet, insert_query
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
@ -173,6 +172,9 @@ class Manager(object):
def only(self, *args, **kwargs): def only(self, *args, **kwargs):
return self.get_query_set().only(*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): def _insert(self, values, **kwargs):
return insert_query(self.model, values, **kwargs) return insert_query(self.model, values, **kwargs)

View File

@ -2,20 +2,13 @@
The main QuerySet implementation. This provides the public API for the ORM. 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 copy import deepcopy
from django.db import connection, transaction, IntegrityError from django.db import connection, transaction, IntegrityError
from django.db.models.aggregates import Aggregate from django.db.models.aggregates import Aggregate
from django.db.models.fields import DateField 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.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
from django.db.models import signals, sql from django.db.models import signals, sql
# Used to control how many objects are worked with at once in some cases (e.g. # Used to control how many objects are worked with at once in some cases (e.g.
# when deleting objects). # when deleting objects).
CHUNK_SIZE = 100 CHUNK_SIZE = 100
@ -444,6 +437,11 @@ class QuerySet(object):
return query.execute_sql(None) return query.execute_sql(None)
_update.alters_data = True _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 # # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
################################################## ##################################################

View File

@ -8,7 +8,6 @@ all about the internals of models in order to get the information it needs.
""" """
from copy import deepcopy from copy import deepcopy
from django.utils.tree import Node from django.utils.tree import Node
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
@ -24,11 +23,6 @@ from django.core.exceptions import FieldError
from datastructures import EmptyResultSet, Empty, MultiJoin from datastructures import EmptyResultSet, Empty, MultiJoin
from constants import * from constants import *
try:
set
except NameError:
from sets import Set as set # Python 2.3 fallback
__all__ = ['Query', 'BaseQuery'] __all__ = ['Query', 'BaseQuery']
class BaseQuery(object): class BaseQuery(object):
@ -384,6 +378,15 @@ class BaseQuery(object):
return number 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): def as_sql(self, with_limits=True, with_col_aliases=False):
""" """
Creates the SQL for this query. Returns the SQL string and list of Creates the SQL for this query. Returns the SQL string and list of

View File

@ -319,9 +319,7 @@ class BaseModelForm(BaseForm):
if self.instance.pk is not None: if self.instance.pk is not None:
qs = qs.exclude(pk=self.instance.pk) qs = qs.exclude(pk=self.instance.pk)
# This cute trick with extra/values is the most efficient way to if qs.exists():
# tell if a particular query returns any results.
if qs.extra(select={'a': 1}).values('a').order_by():
if len(unique_check) == 1: if len(unique_check) == 1:
self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)]) self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)])
else: else:
@ -354,9 +352,7 @@ class BaseModelForm(BaseForm):
if self.instance.pk is not None: if self.instance.pk is not None:
qs = qs.exclude(pk=self.instance.pk) qs = qs.exclude(pk=self.instance.pk)
# This cute trick with extra/values is the most efficient way to if qs.exists():
# tell if a particular query returns any results.
if qs.extra(select={'a': 1}).values('a').order_by():
self._errors[field] = ErrorList([ self._errors[field] = ErrorList([
self.date_error_message(lookup_type, field, unique_for) self.date_error_message(lookup_type, field, unique_for)
]) ])

View File

@ -1114,6 +1114,17 @@ Aggregation <topics-db-aggregation>`.
.. _field-lookups: .. _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 Field lookups
------------- -------------