| 1 |
from django.conf import settings |
|---|
| 2 |
from django.db import backend, connection, transaction |
|---|
| 3 |
from django.db.models.fields import DateField, FieldDoesNotExist |
|---|
| 4 |
from django.db.models import signals, loading |
|---|
| 5 |
from django.dispatch import dispatcher |
|---|
| 6 |
from django.utils.datastructures import SortedDict |
|---|
| 7 |
from django.utils.encoding import smart_unicode |
|---|
| 8 |
from django.contrib.contenttypes import generic |
|---|
| 9 |
import datetime |
|---|
| 10 |
import operator |
|---|
| 11 |
import re |
|---|
| 12 |
|
|---|
| 13 |
try: |
|---|
| 14 |
set |
|---|
| 15 |
except NameError: |
|---|
| 16 |
from sets import Set as set # Python 2.3 fallback |
|---|
| 17 |
|
|---|
| 18 |
# The string constant used to separate query parts |
|---|
| 19 |
LOOKUP_SEPARATOR = '__' |
|---|
| 20 |
|
|---|
| 21 |
# The list of valid query types |
|---|
| 22 |
QUERY_TERMS = ( |
|---|
| 23 |
'exact', 'iexact', 'contains', 'icontains', |
|---|
| 24 |
'gt', 'gte', 'lt', 'lte', 'in', |
|---|
| 25 |
'startswith', 'istartswith', 'endswith', 'iendswith', |
|---|
| 26 |
'range', 'year', 'month', 'day', 'isnull', 'search', |
|---|
| 27 |
'regex', 'iregex', |
|---|
| 28 |
) |
|---|
| 29 |
|
|---|
| 30 |
# Size of each "chunk" for get_iterator calls. |
|---|
| 31 |
# Larger values are slightly faster at the expense of more storage space. |
|---|
| 32 |
GET_ITERATOR_CHUNK_SIZE = 100 |
|---|
| 33 |
|
|---|
| 34 |
class EmptyResultSet(Exception): |
|---|
| 35 |
pass |
|---|
| 36 |
|
|---|
| 37 |
#################### |
|---|
| 38 |
# HELPER FUNCTIONS # |
|---|
| 39 |
#################### |
|---|
| 40 |
|
|---|
| 41 |
# Django currently supports two forms of ordering. |
|---|
| 42 |
# Form 1 (deprecated) example: |
|---|
| 43 |
# order_by=(('pub_date', 'DESC'), ('headline', 'ASC'), (None, 'RANDOM')) |
|---|
| 44 |
# Form 2 (new-style) example: |
|---|
| 45 |
# order_by=('-pub_date', 'headline', '?') |
|---|
| 46 |
# Form 1 is deprecated and will no longer be supported for Django's first |
|---|
| 47 |
# official release. The following code converts from Form 1 to Form 2. |
|---|
| 48 |
|
|---|
| 49 |
LEGACY_ORDERING_MAPPING = {'ASC': '_', 'DESC': '-_', 'RANDOM': '?'} |
|---|
| 50 |
|
|---|
| 51 |
def handle_legacy_orderlist(order_list): |
|---|
| 52 |
if not order_list or isinstance(order_list[0], basestring): |
|---|
| 53 |
return order_list |
|---|
| 54 |
else: |
|---|
| 55 |
import warnings |
|---|
| 56 |
new_order_list = [LEGACY_ORDERING_MAPPING[j.upper()].replace('_', smart_unicode(i)) for i, j in order_list] |
|---|
| 57 |
warnings.warn("%r ordering syntax is deprecated. Use %r instead." % (order_list, new_order_list), DeprecationWarning) |
|---|
| 58 |
return new_order_list |
|---|
| 59 |
|
|---|
| 60 |
def orderfield2column(f, opts): |
|---|
| 61 |
try: |
|---|
| 62 |
return opts.get_field(f, False).column |
|---|
| 63 |
except FieldDoesNotExist: |
|---|
| 64 |
return f |
|---|
| 65 |
|
|---|
| 66 |
def orderlist2sql(order_list, opts, prefix=''): |
|---|
| 67 |
if prefix.endswith('.'): |
|---|
| 68 |
prefix = backend.quote_name(prefix[:-1]) + '.' |
|---|
| 69 |
output = [] |
|---|
| 70 |
for f in handle_legacy_orderlist(order_list): |
|---|
| 71 |
if f.startswith('-'): |
|---|
| 72 |
output.append('%s%s DESC' % (prefix, backend.quote_name(orderfield2column(f[1:], opts)))) |
|---|
| 73 |
elif f == '?': |
|---|
| 74 |
output.append(backend.get_random_function_sql()) |
|---|
| 75 |
else: |
|---|
| 76 |
output.append('%s%s ASC' % (prefix, backend.quote_name(orderfield2column(f, opts)))) |
|---|
| 77 |
return ', '.join(output) |
|---|
| 78 |
|
|---|
| 79 |
def quote_only_if_word(word): |
|---|
| 80 |
if re.search('\W', word): # Don't quote if there are spaces or non-word chars. |
|---|
| 81 |
return word |
|---|
| 82 |
else: |
|---|
| 83 |
return backend.quote_name(word) |
|---|
| 84 |
|
|---|
| 85 |
class _QuerySet(object): |
|---|
| 86 |
"Represents a lazy database lookup for a set of objects" |
|---|
| 87 |
def __init__(self, model=None): |
|---|
| 88 |
self.model = model |
|---|
| 89 |
self._filters = Q() |
|---|
| 90 |
self._order_by = None # Ordering, e.g. ('date', '-name'). If None, use model's ordering. |
|---|
| 91 |
self._select_related = False # Whether to fill cache for related objects. |
|---|
| 92 |
self._max_related_depth = 0 # Maximum "depth" for select_related |
|---|
| 93 |
self._distinct = False # Whether the query should use SELECT DISTINCT. |
|---|
| 94 |
self._select = {} # Dictionary of attname -> SQL. |
|---|
| 95 |
self._where = [] # List of extra WHERE clauses to use. |
|---|
| 96 |
self._params = [] # List of params to use for extra WHERE clauses. |
|---|
| 97 |
self._tables = [] # List of extra tables to use. |
|---|
| 98 |
self._offset = None # OFFSET clause. |
|---|
| 99 |
self._limit = None # LIMIT clause. |
|---|
| 100 |
self._result_cache = None |
|---|
| 101 |
|
|---|
| 102 |
######################## |
|---|
| 103 |
# PYTHON MAGIC METHODS # |
|---|
| 104 |
######################## |
|---|
| 105 |
|
|---|
| 106 |
def __repr__(self): |
|---|
| 107 |
return repr(self._get_data()) |
|---|
| 108 |
|
|---|
| 109 |
def __len__(self): |
|---|
| 110 |
return len(self._get_data()) |
|---|
| 111 |
|
|---|
| 112 |
def __iter__(self): |
|---|
| 113 |
return iter(self._get_data()) |
|---|
| 114 |
|
|---|
| 115 |
def __getitem__(self, k): |
|---|
| 116 |
"Retrieve an item or slice from the set of results." |
|---|
| 117 |
if not isinstance(k, (slice, int)): |
|---|
| 118 |
raise TypeError |
|---|
| 119 |
assert (not isinstance(k, slice) and (k >= 0)) \ |
|---|
| 120 |
or (isinstance(k, slice) and (k.start is None or k.start >= 0) and (k.stop is None or k.stop >= 0)), \ |
|---|
| 121 |
"Negative indexing is not supported." |
|---|
| 122 |
if self._result_cache is None: |
|---|
| 123 |
if isinstance(k, slice): |
|---|
| 124 |
# Offset: |
|---|
| 125 |
if self._offset is None: |
|---|
| 126 |
offset = k.start |
|---|
| 127 |
elif k.start is None: |
|---|
| 128 |
offset = self._offset |
|---|
| 129 |
else: |
|---|
| 130 |
offset = self._offset + k.start |
|---|
| 131 |
# Now adjust offset to the bounds of any existing limit: |
|---|
| 132 |
if self._limit is not None and k.start is not None: |
|---|
| 133 |
limit = self._limit - k.start |
|---|
| 134 |
else: |
|---|
| 135 |
limit = self._limit |
|---|
| 136 |
|
|---|
| 137 |
# Limit: |
|---|
| 138 |
if k.stop is not None and k.start is not None: |
|---|
| 139 |
if limit is None: |
|---|
| 140 |
limit = k.stop - k.start |
|---|
| 141 |
else: |
|---|
| 142 |
limit = min((k.stop - k.start), limit) |
|---|
| 143 |
else: |
|---|
| 144 |
if limit is None: |
|---|
| 145 |
limit = k.stop |
|---|
| 146 |
else: |
|---|
| 147 |
if k.stop is not None: |
|---|
| 148 |
limit = min(k.stop, limit) |
|---|
| 149 |
|
|---|
| 150 |
if k.step is None: |
|---|
| 151 |
return self._clone(_offset=offset, _limit=limit) |
|---|
| 152 |
else: |
|---|
| 153 |
return list(self._clone(_offset=offset, _limit=limit))[::k.step] |
|---|
| 154 |
else: |
|---|
| 155 |
try: |
|---|
| 156 |
return list(self._clone(_offset=k, _limit=1))[0] |
|---|
| 157 |
except self.model.DoesNotExist, e: |
|---|
| 158 |
raise IndexError, e.args |
|---|
| 159 |
else: |
|---|
| 160 |
return self._result_cache[k] |
|---|
| 161 |
|
|---|
| 162 |
def __and__(self, other): |
|---|
| 163 |
combined = self._combine(other) |
|---|
| 164 |
combined._filters = self._filters & other._filters |
|---|
| 165 |
return combined |
|---|
| 166 |
|
|---|
| 167 |
def __or__(self, other): |
|---|
| 168 |
combined = self._combine(other) |
|---|
| 169 |
combined._filters = self._filters | other._filters |
|---|
| 170 |
return combined |
|---|
| 171 |
|
|---|
| 172 |
#################################### |
|---|
| 173 |
# METHODS THAT DO DATABASE QUERIES # |
|---|
| 174 |
#################################### |
|---|
| 175 |
|
|---|
| 176 |
def iterator(self): |
|---|
| 177 |
"Performs the SELECT database lookup of this QuerySet." |
|---|
| 178 |
try: |
|---|
| 179 |
select, sql, params = self._get_sql_clause() |
|---|
| 180 |
except EmptyResultSet: |
|---|
| 181 |
raise StopIteration |
|---|
| 182 |
|
|---|
| 183 |
# self._select is a dictionary, and dictionaries' key order is |
|---|
| 184 |
# undefined, so we convert it to a list of tuples. |
|---|
| 185 |
extra_select = self._select.items() |
|---|
| 186 |
|
|---|
| 187 |
cursor = connection.cursor() |
|---|
| 188 |
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) |
|---|
| 189 |
|
|---|
| 190 |
fill_cache = self._select_related |
|---|
| 191 |
fields = self.model._meta.fields |
|---|
| 192 |
index_end = len(fields) |
|---|
| 193 |
has_resolve_columns = hasattr(self, 'resolve_columns') |
|---|
| 194 |
while 1: |
|---|
| 195 |
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) |
|---|
| 196 |
if not rows: |
|---|
| 197 |
raise StopIteration |
|---|
| 198 |
for row in rows: |
|---|
| 199 |
if has_resolve_columns: |
|---|
| 200 |
row = self.resolve_columns(row, fields) |
|---|
| 201 |
if fill_cache: |
|---|
| 202 |
obj, index_end = get_cached_row(klass=self.model, row=row, |
|---|
| 203 |
index_start=0, max_depth=self._max_related_depth) |
|---|
| 204 |
else: |
|---|
| 205 |
obj = self.model(*row[:index_end]) |
|---|
| 206 |
for i, k in enumerate(extra_select): |
|---|
| 207 |
setattr(obj, k[0], row[index_end+i]) |
|---|
| 208 |
yield obj |
|---|
| 209 |
|
|---|
| 210 |
def count(self): |
|---|
| 211 |
""" |
|---|
| 212 |
Performs a SELECT COUNT() and returns the number of records as an |
|---|
| 213 |
integer. |
|---|
| 214 |
|
|---|
| 215 |
If the queryset is already cached (i.e. self._result_cache is set) this |
|---|
| 216 |
simply returns the length of the cached results set to avoid multiple |
|---|
| 217 |
SELECT COUNT(*) calls. |
|---|
| 218 |
""" |
|---|
| 219 |
if self._result_cache is not None: |
|---|
| 220 |
return len(self._result_cache) |
|---|
| 221 |
|
|---|
| 222 |
counter = self._clone() |
|---|
| 223 |
counter._order_by = () |
|---|
| 224 |
counter._select_related = False |
|---|
| 225 |
|
|---|
| 226 |
offset = counter._offset |
|---|
| 227 |
limit = counter._limit |
|---|
| 228 |
counter._offset = None |
|---|
| 229 |
counter._limit = None |
|---|
| 230 |
|
|---|
| 231 |
try: |
|---|
| 232 |
select, sql, params = counter._get_sql_clause() |
|---|
| 233 |
except EmptyResultSet: |
|---|
| 234 |
return 0 |
|---|
| 235 |
|
|---|
| 236 |
cursor = connection.cursor() |
|---|
| 237 |
if self._distinct: |
|---|
| 238 |
id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), |
|---|
| 239 |
backend.quote_name(self.model._meta.pk.column)) |
|---|
| 240 |
cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params) |
|---|
| 241 |
else: |
|---|
| 242 |
cursor.execute("SELECT COUNT(*)" + sql, params) |
|---|
| 243 |
count = cursor.fetchone()[0] |
|---|
| 244 |
|
|---|
| 245 |
# Apply any offset and limit constraints manually, since using LIMIT or |
|---|
| 246 |
# OFFSET in SQL doesn't change the output of COUNT. |
|---|
| 247 |
if offset: |
|---|
| 248 |
count = max(0, count - offset) |
|---|
| 249 |
if limit: |
|---|
| 250 |
count = min(limit, count) |
|---|
| 251 |
|
|---|
| 252 |
return count |
|---|
| 253 |
|
|---|
| 254 |
def get(self, *args, **kwargs): |
|---|
| 255 |
"Performs the SELECT and returns a single object matching the given keyword arguments." |
|---|
| 256 |
clone = self.filter(*args, **kwargs) |
|---|
| 257 |
# clean up SQL by removing unneeded ORDER BY |
|---|
| 258 |
if not clone._order_by: |
|---|
| 259 |
clone._order_by = () |
|---|
| 260 |
obj_list = list(clone) |
|---|
| 261 |
if len(obj_list) < 1: |
|---|
| 262 |
raise self.model.DoesNotExist, "%s matching query does not exist." % self.model._meta.object_name |
|---|
| 263 |
assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) |
|---|
| 264 |
return obj_list[0] |
|---|
| 265 |
|
|---|
| 266 |
def create(self, **kwargs): |
|---|
| 267 |
""" |
|---|
| 268 |
Create a new object with the given kwargs, saving it to the database |
|---|
| 269 |
and returning the created object. |
|---|
| 270 |
""" |
|---|
| 271 |
obj = self.model(**kwargs) |
|---|
| 272 |
obj.save() |
|---|
| 273 |
return obj |
|---|
| 274 |
|
|---|
| 275 |
def get_or_create(self, **kwargs): |
|---|
| 276 |
""" |
|---|
| 277 |
Looks up an object with the given kwargs, creating one if necessary. |
|---|
| 278 |
Returns a tuple of (object, created), where created is a boolean |
|---|
| 279 |
specifying whether an object was created. |
|---|
| 280 |
""" |
|---|
| 281 |
assert len(kwargs), 'get_or_create() must be passed at least one keyword argument' |
|---|
| 282 |
defaults = kwargs.pop('defaults', {}) |
|---|
| 283 |
try: |
|---|
| 284 |
return self.get(**kwargs), False |
|---|
| 285 |
except self.model.DoesNotExist: |
|---|
| 286 |
params = dict([(k, v) for k, v in kwargs.items() if '__' not in k]) |
|---|
| 287 |
params.update(defaults) |
|---|
| 288 |
obj = self.model(**params) |
|---|
| 289 |
obj.save() |
|---|
| 290 |
return obj, True |
|---|
| 291 |
|
|---|
| 292 |
def latest(self, field_name=None): |
|---|
| 293 |
""" |
|---|
| 294 |
Returns the latest object, according to the model's 'get_latest_by' |
|---|
| 295 |
option or optional given field_name. |
|---|
| 296 |
""" |
|---|
| 297 |
latest_by = field_name or self.model._meta.get_latest_by |
|---|
| 298 |
assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model" |
|---|
| 299 |
assert self._limit is None and self._offset is None, \ |
|---|
| 300 |
"Cannot change a query once a slice has been taken." |
|---|
| 301 |
return self._clone(_limit=1, _order_by=('-'+latest_by,)).get() |
|---|
| 302 |
|
|---|
| 303 |
def in_bulk(self, id_list): |
|---|
| 304 |
""" |
|---|
| 305 |
Returns a dictionary mapping each of the given IDs to the object with |
|---|
| 306 |
that ID. |
|---|
| 307 |
""" |
|---|
| 308 |
assert self._limit is None and self._offset is None, \ |
|---|
| 309 |
"Cannot use 'limit' or 'offset' with in_bulk" |
|---|
| 310 |
assert isinstance(id_list, (tuple, list)), "in_bulk() must be provided with a list of IDs." |
|---|
| 311 |
id_list = list(id_list) |
|---|
| 312 |
if id_list == []: |
|---|
| 313 |
return {} |
|---|
| 314 |
qs = self._clone() |
|---|
| 315 |
qs._where.append("%s.%s IN (%s)" % (backend.quote_name(self.model._meta.db_table), backend.quote_name(self.model._meta.pk.column), ",".join(['%s'] * len(id_list)))) |
|---|
| 316 |
qs._params.extend(id_list) |
|---|
| 317 |
return dict([(obj._get_pk_val(), obj) for obj in qs.iterator()]) |
|---|
| 318 |
|
|---|
| 319 |
def delete(self): |
|---|
| 320 |
""" |
|---|
| 321 |
Deletes the records in the current QuerySet. |
|---|
| 322 |
""" |
|---|
| 323 |
assert self._limit is None and self._offset is None, \ |
|---|
| 324 |
"Cannot use 'limit' or 'offset' with delete." |
|---|
| 325 |
|
|---|
| 326 |
del_query = self._clone() |
|---|
| 327 |
|
|---|
| 328 |
# disable non-supported fields |
|---|
| 329 |
del_query._select_related = False |
|---|
| 330 |
del_query._order_by = [] |
|---|
| 331 |
|
|---|
| 332 |
# Delete objects in chunks to prevent an the list of |
|---|
| 333 |
# related objects from becoming too long |
|---|
| 334 |
more_objects = True |
|---|
| 335 |
while more_objects: |
|---|
| 336 |
# Collect all the objects to be deleted in this chunk, and all the objects |
|---|
| 337 |
# that are related to the objects that are to be deleted |
|---|
| 338 |
seen_objs = SortedDict() |
|---|
| 339 |
more_objects = False |
|---|
| 340 |
for object in del_query[0:GET_ITERATOR_CHUNK_SIZE]: |
|---|
| 341 |
more_objects = True |
|---|
| 342 |
object._collect_sub_objects(seen_objs) |
|---|
| 343 |
|
|---|
| 344 |
# If one or more objects were found, delete them. |
|---|
| 345 |
# Otherwise, stop looping. |
|---|
| 346 |
if more_objects: |
|---|
| 347 |
delete_objects(seen_objs) |
|---|
| 348 |
|
|---|
| 349 |
# Clear the result cache, in case this QuerySet gets reused. |
|---|
| 350 |
self._result_cache = None |
|---|
| 351 |
delete.alters_data = True |
|---|
| 352 |
|
|---|
| 353 |
################################################## |
|---|
| 354 |
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # |
|---|
| 355 |
################################################## |
|---|
| 356 |
|
|---|
| 357 |
def values(self, *fields): |
|---|
| 358 |
return self._clone(klass=ValuesQuerySet, _fields=fields) |
|---|
| 359 |
|
|---|
| 360 |
def dates(self, field_name, kind, order='ASC'): |
|---|
| 361 |
""" |
|---|
| 362 |
Returns a list of datetime objects representing all available dates |
|---|
| 363 |
for the given field_name, scoped to 'kind'. |
|---|
| 364 |
""" |
|---|
| 365 |
assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'." |
|---|
| 366 |
assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'." |
|---|
| 367 |
# Let the FieldDoesNotExist exception propagate. |
|---|
| 368 |
field = self.model._meta.get_field(field_name, many_to_many=False) |
|---|
| 369 |
assert isinstance(field, DateField), "%r isn't a DateField." % field_name |
|---|
| 370 |
return self._clone(klass=DateQuerySet, _field=field, _kind=kind, _order=order) |
|---|
| 371 |
|
|---|
| 372 |
################################################################## |
|---|
| 373 |
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET # |
|---|
| 374 |
################################################################## |
|---|
| 375 |
|
|---|
| 376 |
def filter(self, *args, **kwargs): |
|---|
| 377 |
"Returns a new QuerySet instance with the args ANDed to the existing set." |
|---|
| 378 |
return self._filter_or_exclude(None, *args, **kwargs) |
|---|
| 379 |
|
|---|
| 380 |
def exclude(self, *args, **kwargs): |
|---|
| 381 |
"Returns a new QuerySet instance with NOT (args) ANDed to the existing set." |
|---|
| 382 |
return self._filter_or_exclude(QNot, *args, **kwargs) |
|---|
| 383 |
|
|---|
| 384 |
def _filter_or_exclude(self, mapper, *args, **kwargs): |
|---|
| 385 |
# mapper is a callable used to transform Q objects, |
|---|
| 386 |
# or None for identity transform |
|---|
| 387 |
if mapper is None: |
|---|
| 388 |
mapper = lambda x: x |
|---|
| 389 |
if len(args) > 0 or len(kwargs) > 0: |
|---|
| 390 |
assert self._limit is None and self._offset is None, \ |
|---|
| 391 |
"Cannot filter a query once a slice has been taken." |
|---|
| 392 |
|
|---|
| 393 |
clone = self._clone() |
|---|
| 394 |
if len(kwargs) > 0: |
|---|
| 395 |
clone._filters = clone._filters & mapper(Q(**kwargs)) |
|---|
| 396 |
if len(args) > 0: |
|---|
| 397 |
clone._filters = clone._filters & reduce(operator.and_, map(mapper, args)) |
|---|
| 398 |
return clone |
|---|
| 399 |
|
|---|
| 400 |
def complex_filter(self, filter_obj): |
|---|
| 401 |
"""Returns a new QuerySet instance with filter_obj added to the filters. |
|---|
| 402 |
filter_obj can be a Q object (has 'get_sql' method) or a dictionary of |
|---|
| 403 |
keyword lookup arguments.""" |
|---|
| 404 |
# This exists to support framework features such as 'limit_choices_to', |
|---|
| 405 |
# and usually it will be more natural to use other methods. |
|---|
| 406 |
if hasattr(filter_obj, 'get_sql'): |
|---|
| 407 |
return self._filter_or_exclude(None, filter_obj) |
|---|
| 408 |
else: |
|---|
| 409 |
return self._filter_or_exclude(None, **filter_obj) |
|---|
| 410 |
|
|---|
| 411 |
def select_related(self, true_or_false=True, depth=0): |
|---|
| 412 |
"Returns a new QuerySet instance with '_select_related' modified." |
|---|
| 413 |
return self._clone(_select_related=true_or_false, _max_related_depth=depth) |
|---|
| 414 |
|
|---|
| 415 |
def order_by(self, *field_names): |
|---|
| 416 |
"Returns a new QuerySet instance with the ordering changed." |
|---|
| 417 |
assert self._limit is None and self._offset is None, \ |
|---|
| 418 |
"Cannot reorder a query once a slice has been taken." |
|---|
| 419 |
return self._clone(_order_by=field_names) |
|---|
| 420 |
|
|---|
| 421 |
def distinct(self, true_or_false=True): |
|---|
| 422 |
"Returns a new QuerySet instance with '_distinct' modified." |
|---|
| 423 |
return self._clone(_distinct=true_or_false) |
|---|
| 424 |
|
|---|
| 425 |
def extra(self, select=None, where=None, params=None, tables=None): |
|---|
| 426 |
assert self._limit is None and self._offset is None, \ |
|---|
| 427 |
"Cannot change a query once a slice has been taken" |
|---|
| 428 |
clone = self._clone() |
|---|
| 429 |
if select: clone._select.update(select) |
|---|
| 430 |
if where: clone._where.extend(where) |
|---|
| 431 |
if params: clone._params.extend(params) |
|---|
| 432 |
if tables: clone._tables.extend(tables) |
|---|
| 433 |
return clone |
|---|
| 434 |
|
|---|
| 435 |
################### |
|---|
| 436 |
# PRIVATE METHODS # |
|---|
| 437 |
################### |
|---|
| 438 |
|
|---|
| 439 |
def _clone(self, klass=None, **kwargs): |
|---|
| 440 |
if klass is None: |
|---|
| 441 |
klass = self.__class__ |
|---|
| 442 |
c = klass() |
|---|
| 443 |
c.model = self.model |
|---|
| 444 |
c._filters = self._filters |
|---|
| 445 |
c._order_by = self._order_by |
|---|
| 446 |
c._select_related = self._select_related |
|---|
| 447 |
c._max_related_depth = self._max_related_depth |
|---|
| 448 |
c._distinct = self._distinct |
|---|
| 449 |
c._select = self._select.copy() |
|---|
| 450 |
c._where = self._where[:] |
|---|
| 451 |
c._params = self._params[:] |
|---|
| 452 |
c._tables = self._tables[:] |
|---|
| 453 |
c._offset = self._offset |
|---|
| 454 |
c._limit = self._limit |
|---|
| 455 |
c.__dict__.update(kwargs) |
|---|
| 456 |
return c |
|---|
| 457 |
|
|---|
| 458 |
def _combine(self, other): |
|---|
| 459 |
assert self._limit is None and self._offset is None \ |
|---|
| 460 |
and other._limit is None and other._offset is None, \ |
|---|
| 461 |
"Cannot combine queries once a slice has been taken." |
|---|
| 462 |
assert self._distinct == other._distinct, \ |
|---|
| 463 |
"Cannot combine a unique query with a non-unique query" |
|---|
| 464 |
# use 'other's order by |
|---|
| 465 |
# (so that A.filter(args1) & A.filter(args2) does the same as |
|---|
| 466 |
# A.filter(args1).filter(args2) |
|---|
| 467 |
combined = other._clone() |
|---|
| 468 |
if self._select: combined._select.update(self._select) |
|---|
| 469 |
if self._where: combined._where.extend(self._where) |
|---|
| 470 |
if self._params: combined._params.extend(self._params) |
|---|
| 471 |
if self._tables: combined._tables.extend(self._tables) |
|---|
| 472 |
# If 'self' is ordered and 'other' isn't, propagate 'self's ordering |
|---|
| 473 |
if (self._order_by is not None and len(self._order_by) > 0) and \ |
|---|
| 474 |
(combined._order_by is None or len(combined._order_by) == 0): |
|---|
| 475 |
combined._order_by = self._order_by |
|---|
| 476 |
return combined |
|---|
| 477 |
|
|---|
| 478 |
def _get_data(self): |
|---|
| 479 |
if self._result_cache is None: |
|---|
| 480 |
self._result_cache = list(self.iterator()) |
|---|
| 481 |
return self._result_cache |
|---|
| 482 |
|
|---|
| 483 |
def _get_sql_clause(self): |
|---|
| 484 |
opts = self.model._meta |
|---|
| 485 |
|
|---|
| 486 |
# Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. |
|---|
| 487 |
select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields] |
|---|
| 488 |
tables = [quote_only_if_word(t) for t in self._tables] |
|---|
| 489 |
joins = SortedDict() |
|---|
| 490 |
where = self._where[:] |
|---|
| 491 |
params = self._params[:] |
|---|
| 492 |
|
|---|
| 493 |
# Convert self._filters into SQL. |
|---|
| 494 |
joins2, where2, params2 = self._filters.get_sql(opts) |
|---|
| 495 |
joins.update(joins2) |
|---|
| 496 |
where.extend(where2) |
|---|
| 497 |
params.extend(params2) |
|---|
| 498 |
|
|---|
| 499 |
# Add additional tables and WHERE clauses based on select_related. |
|---|
| 500 |
if self._select_related: |
|---|
| 501 |
fill_table_cache(opts, select, tables, where, |
|---|
| 502 |
old_prefix=opts.db_table, |
|---|
| 503 |
cache_tables_seen=[opts.db_table], |
|---|
| 504 |
max_depth=self._max_related_depth) |
|---|
| 505 |
|
|---|
| 506 |
# Add any additional SELECTs. |
|---|
| 507 |
if self._select: |
|---|
| 508 |
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) |
|---|
| 509 |
|
|---|
| 510 |
# Start composing the body of the SQL statement. |
|---|
| 511 |
sql = [" FROM", backend.quote_name(opts.db_table)] |
|---|
| 512 |
|
|---|
| 513 |
# Compose the join dictionary into SQL describing the joins. |
|---|
| 514 |
if joins: |
|---|
| 515 |
sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition) |
|---|
| 516 |
for (alias, (table, join_type, condition)) in joins.items()])) |
|---|
| 517 |
|
|---|
| 518 |
# Compose the tables clause into SQL. |
|---|
| 519 |
if tables: |
|---|
| 520 |
sql.append(", " + ", ".join(tables)) |
|---|
| 521 |
|
|---|
| 522 |
# Compose the where clause into SQL. |
|---|
| 523 |
if where: |
|---|
| 524 |
sql.append(where and "WHERE " + " AND ".join(where)) |
|---|
| 525 |
|
|---|
| 526 |
# ORDER BY clause |
|---|
| 527 |
order_by = [] |
|---|
| 528 |
if self._order_by is not None: |
|---|
| 529 |
ordering_to_use = self._order_by |
|---|
| 530 |
else: |
|---|
| 531 |
ordering_to_use = opts.ordering |
|---|
| 532 |
for f in handle_legacy_orderlist(ordering_to_use): |
|---|
| 533 |
if f == '?': # Special case. |
|---|
| 534 |
order_by.append(backend.get_random_function_sql()) |
|---|
| 535 |
else: |
|---|
| 536 |
if f.startswith('-'): |
|---|
| 537 |
col_name = f[1:] |
|---|
| 538 |
order = "DESC" |
|---|
| 539 |
else: |
|---|
| 540 |
col_name = f |
|---|
| 541 |
order = "ASC" |
|---|
| 542 |
if "." in col_name: |
|---|
| 543 |
table_prefix, col_name = col_name.split('.', 1) |
|---|
| 544 |
table_prefix = backend.quote_name(table_prefix) + '.' |
|---|
| 545 |
else: |
|---|
| 546 |
# Use the database table as a column prefix if it wasn't given, |
|---|
| 547 |
# and if the requested column isn't a custom SELECT. |
|---|
| 548 |
if "." not in col_name and col_name not in (self._select or ()): |
|---|
| 549 |
table_prefix = backend.quote_name(opts.db_table) + '.' |
|---|
| 550 |
else: |
|---|
| 551 |
table_prefix = '' |
|---|
| 552 |
order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order)) |
|---|
| 553 |
if order_by: |
|---|
| 554 |
sql.append("ORDER BY " + ", ".join(order_by)) |
|---|
| 555 |
|
|---|
| 556 |
# LIMIT and OFFSET clauses |
|---|
| 557 |
if self._limit is not None: |
|---|
| 558 |
sql.append("%s " % backend.get_limit_offset_sql(self._limit, self._offset)) |
|---|
| 559 |
else: |
|---|
| 560 |
assert self._offset is None, "'offset' is not allowed without 'limit'" |
|---|
| 561 |
|
|---|
| 562 |
return select, " ".join(sql), params |
|---|
| 563 |
|
|---|
| 564 |
# Use the backend's QuerySet class if it defines one, otherwise use _QuerySet. |
|---|
| 565 |
if hasattr(backend, 'get_query_set_class'): |
|---|
| 566 |
QuerySet = backend.get_query_set_class(_QuerySet) |
|---|
| 567 |
else: |
|---|
| 568 |
QuerySet = _QuerySet |
|---|
| 569 |
|
|---|
| 570 |
class ValuesQuerySet(QuerySet): |
|---|
| 571 |
def __init__(self, *args, **kwargs): |
|---|
| 572 |
super(ValuesQuerySet, self).__init__(*args, **kwargs) |
|---|
| 573 |
# select_related isn't supported in values(). |
|---|
| 574 |
self._select_related = False |
|---|
| 575 |
|
|---|
| 576 |
def iterator(self): |
|---|
| 577 |
try: |
|---|
| 578 |
select, sql, params = self._get_sql_clause() |
|---|
| 579 |
except EmptyResultSet: |
|---|
| 580 |
raise StopIteration |
|---|
| 581 |
|
|---|
| 582 |
# self._fields is a list of field names to fetch. |
|---|
| 583 |
if self._fields: |
|---|
| 584 |
if not self._select: |
|---|
| 585 |
fields = [self.model._meta.get_field(f, many_to_many=False) for f in self._fields] |
|---|
| 586 |
else: |
|---|
| 587 |
fields = [] |
|---|
| 588 |
for f in self._fields: |
|---|
| 589 |
if f in [field.name for field in self.model._meta.fields]: |
|---|
| 590 |
fields.append(self.model._meta.get_field(f, many_to_many=False)) |
|---|
| 591 |
elif not self._select.has_key( f ): |
|---|
| 592 |
raise FieldDoesNotExist, '%s has no field named %r' % ( self.model._meta.object_name, f ) |
|---|
| 593 |
|
|---|
| 594 |
field_names = self._fields |
|---|
| 595 |
else: # Default to all fields. |
|---|
| 596 |
fields = self.model._meta.fields |
|---|
| 597 |
field_names = [f.attname for f in fields] |
|---|
| 598 |
|
|---|
| 599 |
columns = [f.column for f in fields] |
|---|
| 600 |
select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] |
|---|
| 601 |
# Add any additional SELECTs. |
|---|
| 602 |
if self._select: |
|---|
| 603 |
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) |
|---|
| 604 |
|
|---|
| 605 |
cursor = connection.cursor() |
|---|
| 606 |
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) |
|---|
| 607 |
|
|---|
| 608 |
has_resolve_columns = hasattr(self, 'resolve_columns') |
|---|
| 609 |
while 1: |
|---|
| 610 |
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) |
|---|
| 611 |
if not rows: |
|---|
| 612 |
raise StopIteration |
|---|
| 613 |
for row in rows: |
|---|
| 614 |
if has_resolve_columns: |
|---|
| 615 |
row = self.resolve_columns(row, fields) |
|---|
| 616 |
yield dict(zip(field_names, row)) |
|---|
| 617 |
|
|---|
| 618 |
def _clone(self, klass=None, **kwargs): |
|---|
| 619 |
c = super(ValuesQuerySet, self)._clone(klass, **kwargs) |
|---|
| 620 |
c._fields = self._fields[:] |
|---|
| 621 |
return c |
|---|
| 622 |
|
|---|
| 623 |
class DateQuerySet(QuerySet): |
|---|
| 624 |
def iterator(self): |
|---|
| 625 |
from django.db.backends.util import typecast_timestamp |
|---|
| 626 |
from django.db.models.fields import DateTimeField |
|---|
| 627 |
self._order_by = () # Clear this because it'll mess things up otherwise. |
|---|
| 628 |
if self._field.null: |
|---|
| 629 |
self._where.append('%s.%s IS NOT NULL' % \ |
|---|
| 630 |
(backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) |
|---|
| 631 |
try: |
|---|
| 632 |
select, sql, params = self._get_sql_clause() |
|---|
| 633 |
except EmptyResultSet: |
|---|
| 634 |
raise StopIteration |
|---|
| 635 |
|
|---|
| 636 |
table_name = backend.quote_name(self.model._meta.db_table) |
|---|
| 637 |
field_name = backend.quote_name(self._field.column) |
|---|
| 638 |
|
|---|
| 639 |
if backend.allows_group_by_ordinal: |
|---|
| 640 |
group_by = '1' |
|---|
| 641 |
else: |
|---|
| 642 |
group_by = backend.get_date_trunc_sql(self._kind, |
|---|
| 643 |
'%s.%s' % (table_name, field_name)) |
|---|
| 644 |
|
|---|
| 645 |
sql = 'SELECT %s %s GROUP BY %s ORDER BY 1 %s' % \ |
|---|
| 646 |
(backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), |
|---|
| 647 |
backend.quote_name(self._field.column))), sql, group_by, self._order) |
|---|
| 648 |
cursor = connection.cursor() |
|---|
| 649 |
cursor.execute(sql, params) |
|---|
| 650 |
|
|---|
| 651 |
has_resolve_columns = hasattr(self, 'resolve_columns') |
|---|
| 652 |
needs_datetime_string_cast = backend.needs_datetime_string_cast |
|---|
| 653 |
dates = [] |
|---|
| 654 |
# It would be better to use self._field here instead of DateTimeField(), |
|---|
| 655 |
# but in Oracle that will result in a list of datetime.date instead of |
|---|
| 656 |
# datetime.datetime. |
|---|
| 657 |
fields = [DateTimeField()] |
|---|
| 658 |
while 1: |
|---|
| 659 |
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) |
|---|
| 660 |
if not rows: |
|---|
| 661 |
return dates |
|---|
| 662 |
for row in rows: |
|---|
| 663 |
date = row[0] |
|---|
| 664 |
if has_resolve_columns: |
|---|
| 665 |
date = self.resolve_columns([date], fields)[0] |
|---|
| 666 |
elif needs_datetime_string_cast: |
|---|
| 667 |
date = typecast_timestamp(str(date)) |
|---|
| 668 |
dates.append(date) |
|---|
| 669 |
|
|---|
| 670 |
def _clone(self, klass=None, **kwargs): |
|---|
| 671 |
c = super(DateQuerySet, self)._clone(klass, **kwargs) |
|---|
| 672 |
c._field = self._field |
|---|
| 673 |
c._kind = self._kind |
|---|
| 674 |
c._order = self._order |
|---|
| 675 |
return c |
|---|
| 676 |
|
|---|
| 677 |
class EmptyQuerySet(QuerySet): |
|---|
| 678 |
def __init__(self, model=None): |
|---|
| 679 |
super(EmptyQuerySet, self).__init__(model) |
|---|
| 680 |
self._result_cache = [] |
|---|
| 681 |
|
|---|
| 682 |
def count(self): |
|---|
| 683 |
return 0 |
|---|
| 684 |
|
|---|
| 685 |
def delete(self): |
|---|
| 686 |
pass |
|---|
| 687 |
|
|---|
| 688 |
def _clone(self, klass=None, **kwargs): |
|---|
| 689 |
c = super(EmptyQuerySet, self)._clone(klass, **kwargs) |
|---|
| 690 |
c._result_cache = [] |
|---|
| 691 |
return c |
|---|
| 692 |
|
|---|
| 693 |
def _get_sql_clause(self): |
|---|
| 694 |
raise EmptyResultSet |
|---|
| 695 |
|
|---|
| 696 |
class QOperator(object): |
|---|
| 697 |
"Base class for QAnd and QOr" |
|---|
| 698 |
def __init__(self, *args): |
|---|
| 699 |
self.args = args |
|---|
| 700 |
|
|---|
| 701 |
def get_sql(self, opts): |
|---|
| 702 |
joins, where, params = SortedDict(), [], [] |
|---|
| 703 |
for val in self.args: |
|---|
| 704 |
try: |
|---|
| 705 |
joins2, where2, params2 = val.get_sql(opts) |
|---|
| 706 |
joins.update(joins2) |
|---|
| 707 |
where.extend(where2) |
|---|
| 708 |
params.extend(params2) |
|---|
| 709 |
except EmptyResultSet: |
|---|
| 710 |
if not isinstance(self, QOr): |
|---|
| 711 |
raise EmptyResultSet |
|---|
| 712 |
if where: |
|---|
| 713 |
return joins, ['(%s)' % self.operator.join(where)], params |
|---|
| 714 |
return joins, [], params |
|---|
| 715 |
|
|---|
| 716 |
class QAnd(QOperator): |
|---|
| 717 |
"Encapsulates a combined query that uses 'AND'." |
|---|
| 718 |
operator = ' AND ' |
|---|
| 719 |
def __or__(self, other): |
|---|
| 720 |
return QOr(self, other) |
|---|
| 721 |
|
|---|
| 722 |
def __and__(self, other): |
|---|
| 723 |
if isinstance(other, QAnd): |
|---|
| 724 |
return QAnd(*(self.args+other.args)) |
|---|
| 725 |
elif isinstance(other, (Q, QOr)): |
|---|
| 726 |
return QAnd(*(self.args+(other,))) |
|---|
| 727 |
else: |
|---|
| 728 |
raise TypeError, other |
|---|
| 729 |
|
|---|
| 730 |
class QOr(QOperator): |
|---|
| 731 |
"Encapsulates a combined query that uses 'OR'." |
|---|
| 732 |
operator = ' OR ' |
|---|
| 733 |
def __and__(self, other): |
|---|
| 734 |
return QAnd(self, other) |
|---|
| 735 |
|
|---|
| 736 |
def __or__(self, other): |
|---|
| 737 |
if isinstance(other, QOr): |
|---|
| 738 |
return QOr(*(self.args+other.args)) |
|---|
| 739 |
elif isinstance(other, (Q, QAnd)): |
|---|
| 740 |
return QOr(*(self.args+(other,))) |
|---|
| 741 |
else: |
|---|
| 742 |
raise TypeError, other |
|---|
| 743 |
|
|---|
| 744 |
class Q(object): |
|---|
| 745 |
"Encapsulates queries as objects that can be combined logically." |
|---|
| 746 |
def __init__(self, **kwargs): |
|---|
| 747 |
self.kwargs = kwargs |
|---|
| 748 |
|
|---|
| 749 |
def __and__(self, other): |
|---|
| 750 |
return QAnd(self, other) |
|---|
| 751 |
|
|---|
| 752 |
def __or__(self, other): |
|---|
| 753 |
return QOr(self, other) |
|---|
| 754 |
|
|---|
| 755 |
def get_sql(self, opts): |
|---|
| 756 |
return parse_lookup(self.kwargs.items(), opts) |
|---|
| 757 |
|
|---|
| 758 |
class QNot(Q): |
|---|
| 759 |
"Encapsulates NOT (...) queries as objects" |
|---|
| 760 |
def __init__(self, q): |
|---|
| 761 |
"Creates a negation of the q object passed in." |
|---|
| 762 |
self.q = q |
|---|
| 763 |
|
|---|
| 764 |
def get_sql(self, opts): |
|---|
| 765 |
try: |
|---|
| 766 |
joins, where, params = self.q.get_sql(opts) |
|---|
| 767 |
where2 = ['(NOT (%s))' % " AND ".join(where)] |
|---|
| 768 |
except EmptyResultSet: |
|---|
| 769 |
return SortedDict(), [], [] |
|---|
| 770 |
return joins, where2, params |
|---|
| 771 |
|
|---|
| 772 |
def get_where_clause(lookup_type, table_prefix, field_name, value): |
|---|
| 773 |
if table_prefix.endswith('.'): |
|---|
| 774 |
table_prefix = backend.quote_name(table_prefix[:-1])+'.' |
|---|
| 775 |
field_name = backend.quote_name(field_name) |
|---|
| 776 |
if type(value) == datetime.datetime and backend.get_datetime_cast_sql(): |
|---|
| 777 |
cast_sql = backend.get_datetime_cast_sql() |
|---|
| 778 |
else: |
|---|
| 779 |
cast_sql = '%s' |
|---|
| 780 |
if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and backend.needs_upper_for_iops: |
|---|
| 781 |
format = 'UPPER(%s%s) %s' |
|---|
| 782 |
else: |
|---|
| 783 |
format = '%s%s %s' |
|---|
| 784 |
try: |
|---|
| 785 |
return format % (table_prefix, field_name, |
|---|
| 786 |
backend.OPERATOR_MAPPING[lookup_type] % cast_sql) |
|---|
| 787 |
except KeyError: |
|---|
| 788 |
pass |
|---|
| 789 |
if lookup_type == 'in': |
|---|
| 790 |
in_string = ','.join(['%s' for id in value]) |
|---|
| 791 |
if in_string: |
|---|
| 792 |
return '%s%s IN (%s)' % (table_prefix, field_name, in_string) |
|---|
| 793 |
else: |
|---|
| 794 |
raise EmptyResultSet |
|---|
| 795 |
elif lookup_type in ('range', 'year'): |
|---|
| 796 |
return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) |
|---|
| 797 |
elif lookup_type in ('month', 'day'): |
|---|
| 798 |
return "%s = %%s" % backend.get_date_extract_sql(lookup_type, table_prefix + field_name) |
|---|
| 799 |
elif lookup_type == 'isnull': |
|---|
| 800 |
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) |
|---|
| 801 |
elif lookup_type == 'search': |
|---|
| 802 |
return backend.get_fulltext_search_sql(table_prefix + field_name) |
|---|
| 803 |
elif lookup_type in ('regex', 'iregex'): |
|---|
| 804 |
if settings.DATABASE_ENGINE == 'oracle': |
|---|
| 805 |
if lookup_type == 'regex': |
|---|
| 806 |
match_option = 'c' |
|---|
| 807 |
else: |
|---|
| 808 |
match_option = 'i' |
|---|
| 809 |
return "REGEXP_LIKE(%s%s, %s, '%s')" % (table_prefix, field_name, cast_sql, match_option) |
|---|
| 810 |
else: |
|---|
| 811 |
raise NotImplementedError |
|---|
| 812 |
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) |
|---|
| 813 |
|
|---|
| 814 |
def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0): |
|---|
| 815 |
"""Helper function that recursively returns an object with cache filled""" |
|---|
| 816 |
|
|---|
| 817 |
# If we've got a max_depth set and we've exceeded that depth, bail now. |
|---|
| 818 |
if max_depth and cur_depth > max_depth: |
|---|
| 819 |
return None |
|---|
| 820 |
|
|---|
| 821 |
index_end = index_start + len(klass._meta.fields) |
|---|
| 822 |
obj = klass(*row[index_start:index_end]) |
|---|
| 823 |
for f in klass._meta.fields: |
|---|
| 824 |
if f.rel and not f.null: |
|---|
| 825 |
cached_row = get_cached_row(f.rel.to, row, index_end, max_depth, cur_depth+1) |
|---|
| 826 |
if cached_row: |
|---|
| 827 |
rel_obj, index_end = cached_row |
|---|
| 828 |
setattr(obj, f.get_cache_name(), rel_obj) |
|---|
| 829 |
return obj, index_end |
|---|
| 830 |
|
|---|
| 831 |
def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen, max_depth=0, cur_depth=0): |
|---|
| 832 |
""" |
|---|
| 833 |
Helper function that recursively populates the select, tables and where (in |
|---|
| 834 |
place) for select_related queries. |
|---|
| 835 |
""" |
|---|
| 836 |
|
|---|
| 837 |
# If we've got a max_depth set and we've exceeded that depth, bail now. |
|---|
| 838 |
if max_depth and cur_depth > max_depth: |
|---|
| 839 |
return None |
|---|
| 840 |
|
|---|
| 841 |
qn = backend.quote_name |
|---|
| 842 |
for f in opts.fields: |
|---|
| 843 |
if f.rel and not f.null: |
|---|
| 844 |
db_table = f.rel.to._meta.db_table |
|---|
| 845 |
if db_table not in cache_tables_seen: |
|---|
| 846 |
tables.append(qn(db_table)) |
|---|
| 847 |
else: # The table was already seen, so give it a table alias. |
|---|
| 848 |
new_prefix = '%s%s' % (db_table, len(cache_tables_seen)) |
|---|
| 849 |
tables.append('%s %s' % (qn(db_table), qn(new_prefix))) |
|---|
| 850 |
db_table = new_prefix |
|---|
| 851 |
cache_tables_seen.append(db_table) |
|---|
| 852 |
where.append('%s.%s = %s.%s' % \ |
|---|
| 853 |
(qn(old_prefix), qn(f.column), qn(db_table), qn(f.rel.get_related_field().column))) |
|---|
| 854 |
select.extend(['%s.%s' % (qn(db_table), qn(f2.column)) for f2 in f.rel.to._meta.fields]) |
|---|
| 855 |
fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen, max_depth, cur_depth+1) |
|---|
| 856 |
|
|---|
| 857 |
def parse_lookup(kwarg_items, opts): |
|---|
| 858 |
# Helper function that handles converting API kwargs |
|---|
| 859 |
# (e.g. "name__exact": "tom") to SQL. |
|---|
| 860 |
# Returns a tuple of (joins, where, params). |
|---|
| 861 |
|
|---|
| 862 |
# 'joins' is a sorted dictionary describing the tables that must be joined |
|---|
| 863 |
# to complete the query. The dictionary is sorted because creation order |
|---|
| 864 |
# is significant; it is a dictionary to ensure uniqueness of alias names. |
|---|
| 865 |
# |
|---|
| 866 |
# Each key-value pair follows the form |
|---|
| 867 |
# alias: (table, join_type, condition) |
|---|
| 868 |
# where |
|---|
| 869 |
# alias is the AS alias for the joined table |
|---|
| 870 |
# table is the actual table name to be joined |
|---|
| 871 |
# join_type is the type of join (INNER JOIN, LEFT OUTER JOIN, etc) |
|---|
| 872 |
# condition is the where-like statement over which narrows the join. |
|---|
| 873 |
# alias will be derived from the lookup list name. |
|---|
| 874 |
# |
|---|
| 875 |
# At present, this method only every returns INNER JOINs; the option is |
|---|
| 876 |
# there for others to implement custom Q()s, etc that return other join |
|---|
| 877 |
# types. |
|---|
| 878 |
joins, where, params = SortedDict(), [], [] |
|---|
| 879 |
|
|---|
| 880 |
for kwarg, value in kwarg_items: |
|---|
| 881 |
path = kwarg.split(LOOKUP_SEPARATOR) |
|---|
| 882 |
# Extract the last elements of the kwarg. |
|---|
| 883 |
# The very-last is the lookup_type (equals, like, etc). |
|---|
| 884 |
# The second-last is the table column on which the lookup_type is |
|---|
| 885 |
# to be performed. If this name is 'pk', it will be substituted with |
|---|
| 886 |
# the name of the primary key. |
|---|
| 887 |
# If there is only one part, or the last part is not a query |
|---|
| 888 |
# term, assume that the query is an __exact |
|---|
| 889 |
lookup_type = path.pop() |
|---|
| 890 |
if lookup_type == 'pk': |
|---|
| 891 |
lookup_type = 'exact' |
|---|
| 892 |
path.append(None) |
|---|
| 893 |
elif len(path) == 0 or lookup_type not in QUERY_TERMS: |
|---|
| 894 |
path.append(lookup_type) |
|---|
| 895 |
lookup_type = 'exact' |
|---|
| 896 |
|
|---|
| 897 |
if len(path) < 1: |
|---|
| 898 |
raise TypeError, "Cannot parse keyword query %r" % kwarg |
|---|
| 899 |
|
|---|
| 900 |
if value is None: |
|---|
| 901 |
# Interpret '__exact=None' as the sql '= NULL'; otherwise, reject |
|---|
| 902 |
# all uses of None as a query value. |
|---|
| 903 |
if lookup_type != 'exact': |
|---|
| 904 |
raise ValueError, "Cannot use None as a query value" |
|---|
| 905 |
elif callable(value): |
|---|
| 906 |
value = value() |
|---|
| 907 |
|
|---|
| 908 |
joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None) |
|---|
| 909 |
joins.update(joins2) |
|---|
| 910 |
where.extend(where2) |
|---|
| 911 |
params.extend(params2) |
|---|
| 912 |
return joins, where, params |
|---|
| 913 |
|
|---|
| 914 |
class FieldFound(Exception): |
|---|
| 915 |
"Exception used to short circuit field-finding operations." |
|---|
| 916 |
pass |
|---|
| 917 |
|
|---|
| 918 |
def find_field(name, field_list, related_query): |
|---|
| 919 |
""" |
|---|
| 920 |
Finds a field with a specific name in a list of field instances. |
|---|
| 921 |
Returns None if there are no matches, or several matches. |
|---|
| 922 |
""" |
|---|
| 923 |
if related_query: |
|---|
| 924 |
matches = [f for f in field_list if f.field.related_query_name() == name] |
|---|
| 925 |
else: |
|---|
| 926 |
matches = [f for f in field_list if f.name == name] |
|---|
| 927 |
if len(matches) != 1: |
|---|
| 928 |
return None |
|---|
| 929 |
return matches[0] |
|---|
| 930 |
|
|---|
| 931 |
def field_choices(field_list, related_query): |
|---|
| 932 |
if related_query: |
|---|
| 933 |
choices = [f.field.related_query_name() for f in field_list] |
|---|
| 934 |
else: |
|---|
| 935 |
choices = [f.name for f in field_list] |
|---|
| 936 |
return choices |
|---|
| 937 |
|
|---|
| 938 |
def lookup_inner(path, lookup_type, value, opts, table, column): |
|---|
| 939 |
qn = backend.quote_name |
|---|
| 940 |
joins, where, params = SortedDict(), [], [] |
|---|
| 941 |
current_opts = opts |
|---|
| 942 |
current_table = table |
|---|
| 943 |
current_column = column |
|---|
| 944 |
intermediate_table = None |
|---|
| 945 |
join_required = False |
|---|
| 946 |
|
|---|
| 947 |
name = path.pop(0) |
|---|
| 948 |
# Has the primary key been requested? If so, expand it out |
|---|
| 949 |
# to be the name of the current class' primary key |
|---|
| 950 |
if name is None or name == 'pk': |
|---|
| 951 |
name = current_opts.pk.name |
|---|
| 952 |
|
|---|
| 953 |
# Try to find the name in the fields associated with the current class |
|---|
| 954 |
try: |
|---|
| 955 |
# Does the name belong to a defined many-to-many field? |
|---|
| 956 |
field = find_field(name, current_opts.many_to_many, False) |
|---|
| 957 |
if field: |
|---|
| 958 |
new_table = current_table + '__' + name |
|---|
| 959 |
new_opts = field.rel.to._meta |
|---|
| 960 |
new_column = new_opts.pk.column |
|---|
| 961 |
|
|---|
| 962 |
# Need to create an intermediate table join over the m2m table |
|---|
| 963 |
# This process hijacks current_table/column to point to the |
|---|
| 964 |
# intermediate table. |
|---|
| 965 |
current_table = "m2m_" + new_table |
|---|
| 966 |
intermediate_table = field.m2m_db_table() |
|---|
| 967 |
join_column = field.m2m_reverse_name() |
|---|
| 968 |
intermediate_column = field.m2m_column_name() |
|---|
| 969 |
|
|---|
| 970 |
raise FieldFound |
|---|
| 971 |
|
|---|
| 972 |
# Does the name belong to a reverse defined many-to-many field? |
|---|
| 973 |
field = find_field(name, current_opts.get_all_related_many_to_many_objects(), True) |
|---|
| 974 |
if field: |
|---|
| 975 |
new_table = current_table + '__' + name |
|---|
| 976 |
new_opts = field.opts |
|---|
| 977 |
new_column = new_opts.pk.column |
|---|
| 978 |
|
|---|
| 979 |
# Need to create an intermediate table join over the m2m table. |
|---|
| 980 |
# This process hijacks current_table/column to point to the |
|---|
| 981 |
# intermediate table. |
|---|
| 982 |
current_table = "m2m_" + new_table |
|---|
| 983 |
intermediate_table = field.field.m2m_db_table() |
|---|
| 984 |
join_column = field.field.m2m_column_name() |
|---|
| 985 |
intermediate_column = field.field.m2m_reverse_name() |
|---|
| 986 |
|
|---|
| 987 |
raise FieldFound |
|---|
| 988 |
|
|---|
| 989 |
# Does the name belong to a one-to-many field? |
|---|
| 990 |
field = find_field(name, current_opts.get_all_related_objects(), True) |
|---|
| 991 |
if field: |
|---|
| 992 |
new_table = table + '__' + name |
|---|
| 993 |
new_opts = field.opts |
|---|
| 994 |
new_column = field.field.column |
|---|
| 995 |
join_column = opts.pk.column |
|---|
| 996 |
|
|---|
| 997 |
# 1-N fields MUST be joined, regardless of any other conditions. |
|---|
| 998 |
join_required = True |
|---|
| 999 |
|
|---|
| 1000 |
raise FieldFound |
|---|
| 1001 |
|
|---|
| 1002 |
# Does the name belong to a one-to-one, many-to-one, or regular field? |
|---|
| 1003 |
field = find_field(name, current_opts.fields, False) |
|---|
| 1004 |
if field: |
|---|
| 1005 |
if field.rel: # One-to-One/Many-to-one field |
|---|
| 1006 |
new_table = current_table + '__' + name |
|---|
| 1007 |
new_opts = field.rel.to._meta |
|---|
| 1008 |
new_column = new_opts.pk.column |
|---|
| 1009 |
join_column = field.column |
|---|
| 1010 |
raise FieldFound |
|---|
| 1011 |
elif path: |
|---|
| 1012 |
# For regular fields, if there are still items on the path, |
|---|
| 1013 |
# an error has been made. We munge "name" so that the error |
|---|
| 1014 |
# properly identifies the cause of the problem. |
|---|
| 1015 |
name += LOOKUP_SEPARATOR + path[0] |
|---|
| 1016 |
else: |
|---|
| 1017 |
raise FieldFound |
|---|
| 1018 |
|
|---|
| 1019 |
except FieldFound: # Match found, loop has been shortcut. |
|---|
| 1020 |
pass |
|---|
| 1021 |
else: # No match found. |
|---|
| 1022 |
choices = field_choices(current_opts.many_to_many, False) + \ |
|---|
| 1023 |
field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \ |
|---|
| 1024 |
field_choices(current_opts.get_all_related_objects(), True) + \ |
|---|
| 1025 |
field_choices(current_opts.fields, False) |
|---|
| 1026 |
raise TypeError, "Cannot resolve keyword '%s' into field. Choices are: %s" % (name, ", ".join(choices)) |
|---|
| 1027 |
|
|---|
| 1028 |
# Check whether an intermediate join is required between current_table |
|---|
| 1029 |
# and new_table. |
|---|
| 1030 |
if intermediate_table: |
|---|
| 1031 |
joins[qn(current_table)] = ( |
|---|
| 1032 |
qn(intermediate_table), "LEFT OUTER JOIN", |
|---|
| 1033 |
"%s.%s = %s.%s" % (qn(table), qn(current_opts.pk.column), qn(current_table), qn(intermediate_column)) |
|---|
| 1034 |
) |
|---|
| 1035 |
|
|---|
| 1036 |
if path: |
|---|
| 1037 |
# There are elements left in the path. More joins are required. |
|---|
| 1038 |
if len(path) == 1 and path[0] in (new_opts.pk.name, None) \ |
|---|
| 1039 |
and lookup_type in ('exact', 'isnull') and not join_required: |
|---|
| 1040 |
# If the next and final name query is for a primary key, |
|---|
| 1041 |
# and the search is for isnull/exact, then the current |
|---|
| 1042 |
# (for N-1) or intermediate (for N-N) table can be used |
|---|
| 1043 |
# for the search. No need to join an extra table just |
|---|
| 1044 |
# to check the primary key. |
|---|
| 1045 |
new_table = current_table |
|---|
| 1046 |
else: |
|---|
| 1047 |
# There are 1 or more name queries pending, and we have ruled out |
|---|
| 1048 |
# any shortcuts; therefore, a join is required. |
|---|
| 1049 |
joins[qn(new_table)] = ( |
|---|
| 1050 |
qn(new_opts.db_table), "INNER JOIN", |
|---|
| 1051 |
"%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column)) |
|---|
| 1052 |
) |
|---|
| 1053 |
# If we have made the join, we don't need to tell subsequent |
|---|
| 1054 |
# recursive calls about the column name we joined on. |
|---|
| 1055 |
join_column = None |
|---|
| 1056 |
|
|---|
| 1057 |
# There are name queries remaining. Recurse deeper. |
|---|
| 1058 |
joins2, where2, params2 = lookup_inner(path, lookup_type, value, new_opts, new_table, join_column) |
|---|
| 1059 |
|
|---|
| 1060 |
joins.update(joins2) |
|---|
| 1061 |
where.extend(where2) |
|---|
| 1062 |
params.extend(params2) |
|---|
| 1063 |
else: |
|---|
| 1064 |
# No elements left in path. Current element is the element on which |
|---|
| 1065 |
# the search is being performed. |
|---|
| 1066 |
|
|---|
| 1067 |
if join_required: |
|---|
| 1068 |
# Last query term is a RelatedObject |
|---|
| 1069 |
if field.field.rel.multiple: |
|---|
| 1070 |
# RelatedObject is from a 1-N relation. |
|---|
| 1071 |
# Join is required; query operates on joined table. |
|---|
| 1072 |
column = new_opts.pk.name |
|---|
| 1073 |
joins[qn(new_table)] = ( |
|---|
| 1074 |
qn(new_opts.db_table), "INNER JOIN", |
|---|
| 1075 |
"%s.%s = %s.%s" % (qn(current_table), qn(join_column), qn(new_table), qn(new_column)) |
|---|
| 1076 |
) |
|---|
| 1077 |
current_table = new_table |
|---|
| 1078 |
else: |
|---|
| 1079 |
# RelatedObject is from a 1-1 relation, |
|---|
| 1080 |
# No need to join; get the pk value from the related object, |
|---|
| 1081 |
# and compare using that. |
|---|
| 1082 |
column = current_opts.pk.name |
|---|
| 1083 |
elif intermediate_table: |
|---|
| 1084 |
# Last query term is a related object from an N-N relation. |
|---|
| 1085 |
# Join from intermediate table is sufficient. |
|---|
| 1086 |
column = join_column |
|---|
| 1087 |
elif name == current_opts.pk.name and lookup_type in ('exact', 'isnull') and current_column: |
|---|
| 1088 |
# Last query term is for a primary key. If previous iterations |
|---|
| 1089 |
# introduced a current/intermediate table that can be used to |
|---|
| 1090 |
# optimize the query, then use that table and column name. |
|---|
| 1091 |
column = current_column |
|---|
| 1092 |
else: |
|---|
| 1093 |
# Last query term was a normal field. |
|---|
| 1094 |
column = field.column |
|---|
| 1095 |
|
|---|
| 1096 |
where.append(get_where_clause(lookup_type, current_table + '.', column, value)) |
|---|
| 1097 |
params.extend(field.get_db_prep_lookup(lookup_type, value)) |
|---|
| 1098 |
|
|---|
| 1099 |
return joins, where, params |
|---|
| 1100 |
|
|---|
| 1101 |
def delete_objects(seen_objs): |
|---|
| 1102 |
"Iterate through a list of seen classes, and remove any instances that are referred to" |
|---|
| 1103 |
qn = backend.quote_name |
|---|
| 1104 |
ordered_classes = seen_objs.keys() |
|---|
| 1105 |
ordered_classes.reverse() |
|---|
| 1106 |
|
|---|
| 1107 |
cursor = connection.cursor() |
|---|
| 1108 |
|
|---|
| 1109 |
for cls in ordered_classes: |
|---|
| 1110 |
seen_objs[cls] = seen_objs[cls].items() |
|---|
| 1111 |
seen_objs[cls].sort() |
|---|
| 1112 |
|
|---|
| 1113 |
# Pre notify all instances to be deleted |
|---|
| 1114 |
for pk_val, instance in seen_objs[cls]: |
|---|
| 1115 |
dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance) |
|---|
| 1116 |
|
|---|
| 1117 |
pk_list = [pk for pk,instance in seen_objs[cls]] |
|---|
| 1118 |
for related in cls._meta.get_all_related_many_to_many_objects(): |
|---|
| 1119 |
if not isinstance(related.field, generic.GenericRelation): |
|---|
| 1120 |
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
|---|
| 1121 |
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ |
|---|
| 1122 |
(qn(related.field.m2m_db_table()), |
|---|
| 1123 |
qn(related.field.m2m_reverse_name()), |
|---|
| 1124 |
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), |
|---|
| 1125 |
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) |
|---|
| 1126 |
for f in cls._meta.many_to_many: |
|---|
| 1127 |
if isinstance(f, generic.GenericRelation): |
|---|
| 1128 |
from django.contrib.contenttypes.models import ContentType |
|---|
| 1129 |
query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column |
|---|
| 1130 |
args_extra = [ContentType.objects.get_for_model(cls).id] |
|---|
| 1131 |
else: |
|---|
| 1132 |
query_extra = '' |
|---|
| 1133 |
args_extra = [] |
|---|
| 1134 |
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
|---|
| 1135 |
cursor.execute(("DELETE FROM %s WHERE %s IN (%s)" % \ |
|---|
| 1136 |
(qn(f.m2m_db_table()), qn(f.m2m_column_name()), |
|---|
| 1137 |
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]]))) + query_extra, |
|---|
| 1138 |
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra) |
|---|
| 1139 |
for field in cls._meta.fields: |
|---|
| 1140 |
if field.rel and field.null and field.rel.to in seen_objs: |
|---|
| 1141 |
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
|---|
| 1142 |
cursor.execute("UPDATE %s SET %s=NULL WHERE %s IN (%s)" % \ |
|---|
| 1143 |
(qn(cls._meta.db_table), qn(field.column), qn(cls._meta.pk.column), |
|---|
| 1144 |
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), |
|---|
| 1145 |
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) |
|---|
| 1146 |
|
|---|
| 1147 |
# Now delete the actual data |
|---|
| 1148 |
for cls in ordered_classes: |
|---|
| 1149 |
seen_objs[cls].reverse() |
|---|
| 1150 |
pk_list = [pk for pk,instance in seen_objs[cls]] |
|---|
| 1151 |
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
|---|
| 1152 |
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ |
|---|
| 1153 |
(qn(cls._meta.db_table), qn(cls._meta.pk.column), |
|---|
| 1154 |
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), |
|---|
| 1155 |
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) |
|---|
| 1156 |
|
|---|
| 1157 |
# Last cleanup; set NULLs where there once was a reference to the object, |
|---|
| 1158 |
# NULL the primary key of the found objects, and perform post-notification. |
|---|
| 1159 |
for pk_val, instance in seen_objs[cls]: |
|---|
| 1160 |
for field in cls._meta.fields: |
|---|
| 1161 |
if field.rel and field.null and field.rel.to in seen_objs: |
|---|
| 1162 |
setattr(instance, field.attname, None) |
|---|
| 1163 |
|
|---|
| 1164 |
setattr(instance, cls._meta.pk.attname, None) |
|---|
| 1165 |
dispatcher.send(signal=signals.post_delete, sender=cls, instance=instance) |
|---|
| 1166 |
|
|---|
| 1167 |
transaction.commit_unless_managed() |
|---|