1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

magic-removal: Massaged Manager and QuerySet. Still more work to do.

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@2159 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-01-30 02:07:10 +00:00
parent 06842af5ab
commit 502225ef86
3 changed files with 209 additions and 407 deletions

View File

@ -108,7 +108,6 @@ class ManyRelatedObjectsDescriptor(object):
# Dynamically create a class that subclasses the related # Dynamically create a class that subclasses the related
# model's default manager. # model's default manager.
manager = types.ClassType('RelatedManager', (self.related.model._default_manager.__class__,), {})() manager = types.ClassType('RelatedManager', (self.related.model._default_manager.__class__,), {})()
manager._use_cache = True
# Set core_filters on the new manager to limit it to the # Set core_filters on the new manager to limit it to the
# foreign-key relationship. # foreign-key relationship.

View File

@ -2,8 +2,6 @@ from django.db.models.fields import DateField
from django.utils.functional import curry from django.utils.functional import curry
from django.db import backend, connection from django.db import backend, connection
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.db.models.query import Q, fill_table_cache, get_cached_row # TODO - remove lots of these
from django.db.models.query import handle_legacy_orderlist, orderlist2sql, orderfield2column
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.db.models import signals from django.db.models import signals
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
@ -23,6 +21,47 @@ def ensure_default_manager(sender):
dispatcher.connect(ensure_default_manager, signal=signals.class_prepared) dispatcher.connect(ensure_default_manager, signal=signals.class_prepared)
# class OldSubmittedManager(QuerySet):
# def in_bulk(self, id_list, **kwargs):
# assert isinstance(id_list, list), "in_bulk() must be provided with a list of IDs."
# assert id_list != [], "in_bulk() cannot be passed an empty ID list."
# new_query = self # we have to do a copy later, so this is OK
# if kwargs:
# new_query = self.filter(**kwargs)
# new_query = new_query.extras(where=
# ["%s.%s IN (%s)" % (backend.quote_name(self.klass._meta.db_table),
# backend.quote_name(self.klass._meta.pk.column),
# ",".join(['%s'] * len(id_list)))],
# params=id_list)
# obj_list = list(new_query)
# return dict([(obj._get_pk_val(), obj) for obj in obj_list])
#
# def delete(self, **kwargs):
# # Remove the DELETE_ALL argument, if it exists.
# delete_all = kwargs.pop('DELETE_ALL', False)
#
# # Check for at least one query argument.
# if not kwargs and not delete_all:
# raise TypeError, "SAFETY MECHANISM: Specify DELETE_ALL=True if you actually want to delete all data."
#
# if kwargs:
# del_query = self.filter(**kwargs)
# else:
# del_query = self._clone()
# # disable non-supported fields
# del_query._select_related = False
# del_query._select = {}
# del_query._order_by = []
# del_query._offset = None
# del_query._limit = None
#
# opts = self.klass._meta
#
# # Perform the SQL delete
# cursor = connection.cursor()
# _, sql, params = del_query._get_sql_clause(False)
# cursor.execute("DELETE " + sql, params)
class Manager(QuerySet): class Manager(QuerySet):
# Tracks each time a Manager instance is created. Used to retain order. # Tracks each time a Manager instance is created. Used to retain order.
creation_counter = 0 creation_counter = 0
@ -34,85 +73,6 @@ class Manager(QuerySet):
Manager.creation_counter += 1 Manager.creation_counter += 1
self.klass = None self.klass = None
def _prepare(self):
pass
# TODO
#if self.klass._meta.get_latest_by:
# self.get_latest = self.__get_latest
#for f in self.klass._meta.fields:
# if isinstance(f, DateField):
# setattr(self, 'get_%s_list' % f.name, curry(self.__get_date_list, f))
def contribute_to_class(self, klass, name):
# TODO: Use weakref because of possible memory leak / circular reference.
self.klass = klass
dispatcher.connect(self._prepare, signal=signals.class_prepared, sender=klass)
setattr(klass, name, ManagerDescriptor(self))
if not hasattr(klass, '_default_manager') or self.creation_counter < klass._default_manager.creation_counter:
klass._default_manager = self
def get(self, **kwargs):
"""Gets a single object, using a new query. Keyword arguments are filters."""
obj_list = list(self.filter(**kwargs))
if len(obj_list) < 1:
raise self.klass.DoesNotExist, "%s does not exist for %s" % (self.klass._meta.object_name, kwargs)
assert len(obj_list) == 1, "get_object() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.klass._meta.object_name, len(obj_list), kwargs)
return obj_list[0]
def in_bulk(self, id_list, **kwargs):
assert isinstance(id_list, list), "in_bulk() must be provided with a list of IDs."
assert id_list != [], "in_bulk() cannot be passed an empty ID list."
new_query = self # we have to do a copy later, so this is OK
if kwargs:
new_query = self.filter(**kwargs)
new_query = new_query.extras(where=
["%s.%s IN (%s)" % (backend.quote_name(self.klass._meta.db_table),
backend.quote_name(self.klass._meta.pk.column),
",".join(['%s'] * len(id_list)))],
params=id_list)
obj_list = list(new_query)
return dict([(obj._get_pk_val(), obj) for obj in obj_list])
def delete(self, **kwargs):
# Remove the DELETE_ALL argument, if it exists.
delete_all = kwargs.pop('DELETE_ALL', False)
# Check for at least one query argument.
if not kwargs and not delete_all:
raise TypeError, "SAFETY MECHANISM: Specify DELETE_ALL=True if you actually want to delete all data."
if kwargs:
del_query = self.filter(**kwargs)
else:
del_query = self._clone()
# disable non-supported fields
del_query._select_related = False
del_query._select = {}
del_query._order_by = []
del_query._offset = None
del_query._limit = None
opts = self.klass._meta
# Perform the SQL delete
cursor = connection.cursor()
_, sql, params = del_query._get_sql_clause(False)
cursor.execute("DELETE " + sql, params)
class OldManager(object):
# Tracks each time a Manager instance is created. Used to retain order.
creation_counter = 0
# Dictionary of lookup parameters to apply to every _get_sql_clause().
core_filters = {}
def __init__(self):
# Increase the creation counter, and save our local copy.
self.creation_counter = Manager.creation_counter
Manager.creation_counter += 1
self.klass = None
def _prepare(self): def _prepare(self):
if self.klass._meta.get_latest_by: if self.klass._meta.get_latest_by:
self.get_latest = self.__get_latest self.get_latest = self.__get_latest
@ -128,107 +88,6 @@ class OldManager(object):
if not hasattr(klass, '_default_manager') or self.creation_counter < klass._default_manager.creation_counter: if not hasattr(klass, '_default_manager') or self.creation_counter < klass._default_manager.creation_counter:
klass._default_manager = self klass._default_manager = self
def _get_sql_clause(self, allow_joins, *args, **kwargs):
def quote_only_if_word(word):
if ' ' in word:
return word
else:
return backend.quote_name(word)
opts = self.klass._meta
# Apply core filters.
kwargs.update(self.core_filters)
# Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
tables = (kwargs.get('tables') and [quote_only_if_word(t) for t in kwargs['tables']] or [])
joins = SortedDict()
where = kwargs.get('where') and kwargs['where'][:] or []
params = kwargs.get('params') and kwargs['params'][:] or []
# Convert all the args into SQL.
table_count = 0
for arg in args:
# check that the provided argument is a Query (i.e., it has a get_sql method)
if not hasattr(arg, 'get_sql'):
raise TypeError, "'%s' is not a valid query argument" % str(arg)
tables2, joins2, where2, params2 = arg.get_sql(opts)
tables.extend(tables2)
joins.update(joins2)
where.extend(where2)
params.extend(params2)
# Convert the kwargs into SQL.
tables2, joins2, where2, params2 = parse_lookup(kwargs.items(), opts)
tables.extend(tables2)
joins.update(joins2)
where.extend(where2)
params.extend(params2)
# Add additional tables and WHERE clauses based on select_related.
if kwargs.get('select_related') is True:
fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
# Add any additional SELECTs passed in via kwargs.
if kwargs.get('select'):
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in kwargs['select']])
# Start composing the body of the SQL statement.
sql = [" FROM", backend.quote_name(opts.db_table)]
# Check if extra tables are allowed. If not, throw an error
if (tables or joins) and not allow_joins:
raise TypeError, "Joins are not allowed in this type of query"
# Compose the join dictionary into SQL describing the joins.
if joins:
sql.append(" ".join(["%s %s AS %s ON %s" % (join_type, table, alias, condition)
for (alias, (table, join_type, condition)) in joins.items()]))
# Compose the tables clause into SQL.
if tables:
sql.append(", " + ", ".join(tables))
# Compose the where clause into SQL.
if where:
sql.append(where and "WHERE " + " AND ".join(where))
# ORDER BY clause
order_by = []
for f in handle_legacy_orderlist(kwargs.get('order_by', opts.ordering)):
if f == '?': # Special case.
order_by.append(backend.get_random_function_sql())
else:
if f.startswith('-'):
col_name = f[1:]
order = "DESC"
else:
col_name = f
order = "ASC"
if "." in col_name:
table_prefix, col_name = col_name.split('.', 1)
table_prefix = backend.quote_name(table_prefix) + '.'
else:
# Use the database table as a column prefix if it wasn't given,
# and if the requested column isn't a custom SELECT.
if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]:
table_prefix = backend.quote_name(opts.db_table) + '.'
else:
table_prefix = ''
order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order))
if order_by:
sql.append("ORDER BY " + ", ".join(order_by))
# LIMIT and OFFSET clauses
if kwargs.get('limit') is not None:
sql.append("%s " % backend.get_limit_offset_sql(kwargs['limit'], kwargs.get('offset')))
else:
assert kwargs.get('offset') is None, "'offset' is not allowed without 'limit'"
return select, " ".join(sql), params
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
num_args = len(args) + len(kwargs) num_args = len(args) + len(kwargs)
@ -253,50 +112,7 @@ class OldManager(object):
_, sql, params = self._get_sql_clause(False, *args, **kwargs) _, sql, params = self._get_sql_clause(False, *args, **kwargs)
cursor.execute("DELETE " + sql, params) cursor.execute("DELETE " + sql, params)
def get_iterator(self, *args, **kwargs): def in_bulk(self, id_list, *args, **kwargs):
# kwargs['select'] is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples internally.
kwargs['select'] = kwargs.get('select', {}).items()
cursor = connection.cursor()
select, sql, params = self._get_sql_clause(True, *args, **kwargs)
cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = kwargs.get('select_related')
index_end = len(self.klass._meta.fields)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
raise StopIteration
for row in rows:
if fill_cache:
obj, index_end = get_cached_row(self.klass, row, 0)
else:
obj = self.klass(*row[:index_end])
for i, k in enumerate(kwargs['select']):
setattr(obj, k[0], row[index_end+i])
yield obj
def get_list(self, *args, **kwargs):
return list(self.get_iterator(*args, **kwargs))
def get_count(self, *args, **kwargs):
kwargs['order_by'] = []
kwargs['offset'] = None
kwargs['limit'] = None
kwargs['select_related'] = False
_, sql, params = self._get_sql_clause(True, *args, **kwargs)
cursor = connection.cursor()
cursor.execute("SELECT COUNT(*)" + sql, params)
return cursor.fetchone()[0]
def get_object(self, *args, **kwargs):
obj_list = self.get_list(*args, **kwargs)
if len(obj_list) < 1:
raise self.klass.DoesNotExist, "%s does not exist for %s" % (self.klass._meta.object_name, kwargs)
assert len(obj_list) == 1, "get_object() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.klass._meta.object_name, len(obj_list), kwargs)
return obj_list[0]
def get_in_bulk(self, id_list, *args, **kwargs):
assert isinstance(id_list, list), "get_in_bulk() must be provided with a list of IDs." assert isinstance(id_list, list), "get_in_bulk() must be provided with a list of IDs."
assert id_list != [], "get_in_bulk() cannot be passed an empty ID list." assert id_list != [], "get_in_bulk() cannot be passed an empty ID list."
kwargs['where'] = ["%s.%s IN (%s)" % (backend.quote_name(self.klass._meta.db_table), backend.quote_name(self.klass._meta.pk.column), ",".join(['%s'] * len(id_list)))] kwargs['where'] = ["%s.%s IN (%s)" % (backend.quote_name(self.klass._meta.db_table), backend.quote_name(self.klass._meta.pk.column), ",".join(['%s'] * len(id_list)))]
@ -357,9 +173,6 @@ class OldManager(object):
# objects -- MySQL returns the values as strings, instead. # objects -- MySQL returns the values as strings, instead.
return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
# DEBUG - to go back to old manager:
# Manager = OldManager
class ManagerDescriptor(object): class ManagerDescriptor(object):
def __init__(self, manager): def __init__(self, manager):
self.manager = manager self.manager = manager

View File

@ -51,139 +51,153 @@ def orderlist2sql(order_list, opts, prefix=''):
output.append('%s%s ASC' % (prefix, backend.quote_name(orderfield2column(f, opts)))) output.append('%s%s ASC' % (prefix, backend.quote_name(orderfield2column(f, opts))))
return ', '.join(output) return ', '.join(output)
def quote_only_if_word(word):
if ' ' in word:
return word
else:
return backend.quote_name(word)
class QuerySet(object): class QuerySet(object):
"Represents a lazy database lookup for a set of objects" "Represents a lazy database lookup for a set of objects"
# Sub classes need to provide 'opts' member for this class # Subclasses need to provide 'self.klass' attribute for this class
# to be able to function. # to be able to function.
# Dictionary of lookup parameters to apply to every _get_sql_clause().
core_filters = {}
def __init__(self): def __init__(self):
self._filter = Q() self._filters = {} # Dictionary of lookup parameters, e.g. {'foo__gt': 3}
self._order_by = () self._order_by = () # Ordering, e.g. ('date', '-name')
self._select_related = False self._select_related = False # Whether to fill cache for related objects.
self._distinct = True self._distinct = False # Whether the query should use SELECT DISTINCT.
self._result_cache = None # self._result_cache = None
self._params = None self._select = None # Dictionary of attname -> SQL.
self._select = None self._where = None # List of extra WHERE clauses to use.
self._where = None self._params = None # List of params to use for extra WHERE clauses.
self._tables = None self._tables = None # List of extra tables to use.
self._offset = None self._offset = None # OFFSET clause
self._limit = None self._limit = None # LIMIT clause
self._use_cache = False # self._use_cache = False
def filter(self, **kwargs): ########################
"""Returns a new query instance with the query arguments # PYTHON MAGIC METHODS #
ANDed to the existing set""" ########################
clone = self._clone()
clone._filter = self._filter & Q(**kwargs)
return clone
def unique(self, true_or_false): # def __len__(self):
"""Returns a new query instance with the 'unique' qualifier modified""" # return len(list(self))
return self._clone(_distinct=true_or_false)
def order_by(self, *field_names): ###########################################
"""Returns a new query instance with the ordering changed.""" # PUBLIC METHODS THAT DO DATABASE QUERIES #
return self._clone(_order_by=field_names) ###########################################
def select_related(self, true_or_false): def __iter__(self):
"""Returns a new query instance with the 'related' qualifier modified""" "Performs the SELECT database lookup of this QuerySet."
return self._clone(_related=true_or_false) # self._select is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples.
extra_select = (self._select or {}).items()
cursor = connection.cursor()
select, sql, params = self._get_sql_clause(True)
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = self._select_related
index_end = len(self.klass._meta.fields)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
raise StopIteration
for row in rows:
if fill_cache:
obj, index_end = get_cached_row(self.klass, row, 0)
else:
obj = self.klass(*row[:index_end])
for i, k in enumerate(extra_select):
setattr(obj, k[0], row[index_end+i])
yield obj
def count(self): def count(self):
"Performs a SELECT COUNT() and returns the number of records as an integer."
counter = self._clone() counter = self._clone()
counter._order_by = [] counter._order_by = []
counter._offset = None
# TODO - do we change these or not? counter._limit = None
# e.g. if someone does objects[0:10].count()
# (which
#counter._offset = None
#counter._limit = None
counter._select_related = False counter._select_related = False
_, sql, params = counter._get_sql_clause(True) _, sql, params = counter._get_sql_clause(True)
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("SELECT COUNT(*)" + sql, params) cursor.execute("SELECT COUNT(*)" + sql, params)
return cursor.fetchone()[0] return cursor.fetchone()[0]
# Convenience function for subclasses def get(self, **kwargs):
def _set_core_filter(self, **kwargs): "Performs the SELECT and returns a single object matching the given keyword arguments."
"""Sets the filters that should always be applied to queries""" obj_list = list(self.filter(**kwargs))
self._filter = Q(**kwargs) if len(obj_list) < 1:
raise self.klass.DoesNotExist, "%s does not exist for %s" % (self.klass._meta.object_name, kwargs)
assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.klass._meta.object_name, len(obj_list), kwargs)
return obj_list[0]
def _clone(self, **kwargs): #############################################
"""Gets a clone of the object, with optional kwargs to alter the clone""" # PUBLIC METHODS THAT RETURN A NEW QUERYSET #
# Don't clone (even temporarily) the cache #############################################
_result_cache_save = self._result_cache
self._result_cache = None def filter(self, **kwargs):
# Must ensure we get fully deep copies of all the query objects "Returns a new QuerySet instance with the args ANDed to the existing set."
clone = copy.deepcopy(self) clone = self._clone()
# apply changes to clone clone._filters.update(**kwargs)
clone.__dict__.update(kwargs)
# restore cache
self._result_cache = _result_cache_save
return clone return clone
def _ensure_compatible(self, other): def select_related(self, true_or_false=True):
if self._distinct != other._distinct: "Returns a new QuerySet instance with '_select_related' modified."
raise ValueException, "Can't combine a unique query with a non-unique query" return self._clone(_select_related=true_or_false)
def _combine(self, other): def order_by(self, *field_names):
self._ensure_compatible(other) "Returns a new QuerySet instance with the ordering changed."
# get a deepcopy of 'other's order by return self._clone(_order_by=field_names)
# (so that A.filter(args1) & A.filter(args2) does the same as
# A.filter(args1).filter(args2)
combined = other._clone()
# If 'self' is ordered and 'other' isn't, propagate 'self's ordering
if len(self._order_by) > 0 and len(combined._order_by == 0):
combined._order_by = copy.deepcopy(self._order_by)
return combined
def extras(self, params=None, select=None, where=None, tables=None): def distinct(self, true_or_false=True):
return self._clone(_params=params, _select=select, _where=where, _tables=tables) "Returns a new QuerySet instance with '_distinct' modified."
return self._clone(_distinct=true_or_false)
def __and__(self, other): ###################
combined = self._combine(other) # PRIVATE METHODS #
combined._filter = self._filter & other._filter ###################
return combined
def __or__(self, other): def _clone(self, **kwargs):
combined = self._combine(other) c = QuerySet()
combined._filter = self._filter | other._filter c._filters = self._filters.copy()
return combined c._order_by = self._order_by
c._select_related = self._select_related
c._distinct = self._distinct
c._select = self._select
c._offset = self._offset
c._limit = self._limit
return c
# TODO - allow_joins - do we need it?
def _get_sql_clause(self, allow_joins): def _get_sql_clause(self, allow_joins):
def quote_only_if_word(word):
if ' ' in word:
return word
else:
return backend.quote_name(word)
# This is defined by sub-classes
# TODO - define a better accessor
opts = self.klass._meta opts = self.klass._meta
# Apply core filters.
self._filters.update(self.core_filters)
# Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields] select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields]
tables = [quote_only_if_word(t) for t in (self._tables or [])] tables = [quote_only_if_word(t) for t in (self._tables or [])]
joins = SortedDict() joins = SortedDict()
where = self._where or [] where = self._where or []
params = self._params or [] params = self._params or []
# Convert the Q object into SQL. # Convert self._filters into SQL.
tables2, joins2, where2, params2 = self._filter.get_sql(opts) tables2, joins2, where2, params2 = parse_lookup(self._filters.items(), opts)
tables.extend(tables2) tables.extend(tables2)
joins.update(joins2) joins.update(joins2)
where.extend(where2) where.extend(where2)
params.extend(params2) params.extend(params2)
# Add additional tables and WHERE clauses based on select_related. # Add additional tables and WHERE clauses based on select_related.
if self._select_related is True: if self._select_related:
fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table]) fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
# Add any additional SELECTs. # Add any additional SELECTs.
if self._select: if self._select:
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select ]) select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select])
# Start composing the body of the SQL statement. # Start composing the body of the SQL statement.
sql = [" FROM", backend.quote_name(opts.db_table)] sql = [" FROM", backend.quote_name(opts.db_table)]
@ -207,7 +221,7 @@ class QuerySet(object):
# ORDER BY clause # ORDER BY clause
order_by = [] order_by = []
for f in handle_legacy_orderlist(self._order_by): for f in handle_legacy_orderlist(self._order_by or opts.ordering):
if f == '?': # Special case. if f == '?': # Special case.
order_by.append(backend.get_random_function_sql()) order_by.append(backend.get_random_function_sql())
else: else:
@ -223,7 +237,7 @@ class QuerySet(object):
else: else:
# Use the database table as a column prefix if it wasn't given, # Use the database table as a column prefix if it wasn't given,
# and if the requested column isn't a custom SELECT. # and if the requested column isn't a custom SELECT.
if "." not in col_name and col_name not in [k[0] for k in (self._select or []) ]: if "." not in col_name and col_name not in [k[0] for k in (self._select or ())]:
table_prefix = backend.quote_name(opts.db_table) + '.' table_prefix = backend.quote_name(opts.db_table) + '.'
else: else:
table_prefix = '' table_prefix = ''
@ -239,80 +253,72 @@ class QuerySet(object):
return select, " ".join(sql), params return select, " ".join(sql), params
def _fetch_data(self): # class QuerySet(object):
if self._use_cache: # def _ensure_compatible(self, other):
if self._result_cache is None: # if self._distinct != other._distinct:
self._result_cache = list(self.get_iterator()) # raise ValueException, "Can't combine a unique query with a non-unique query"
return self._result_cache #
else: # def _combine(self, other):
return list(self.get_iterator()) # self._ensure_compatible(other)
# # get a deepcopy of 'other's order by
def __iter__(self): # # (so that A.filter(args1) & A.filter(args2) does the same as
"""Gets an iterator for the data""" # # A.filter(args1).filter(args2)
# Fetch the data or use get_iterator? If not, we can't # combined = other._clone()
# do sequence operations - or doing so will require re-fetching # # If 'self' is ordered and 'other' isn't, propagate 'self's ordering
# Also, lots of things in current template system break if we # if len(self._order_by) > 0 and len(combined._order_by == 0):
# don't get it all. # combined._order_by = copy.deepcopy(self._order_by)
return iter(self._fetch_data()) # return combined
#
def __len__(self): # def extras(self, params=None, select=None, where=None, tables=None):
return len(self._fetch_data()) # return self._clone(_params=params, _select=select, _where=where, _tables=tables)
#
def __getitem__(self, k): # def __and__(self, other):
"""Retrieve an item or slice from the set of results""" # combined = self._combine(other)
# getitem can't return query instances, because .filter() # combined._filter = self._filter & other._filter
# and .order_by() methods on the result would break badly. # return combined
# This means we don't have to worry about arithmetic with #
# self._limit or self._offset - they will both be None # def __or__(self, other):
# at this point # combined = self._combine(other)
if isinstance(k, slice): # combined._filter = self._filter | other._filter
# Get a new query if we haven't already got data from db # return combined
if self._result_cache is None: #
# slice.stop and slice.start # def _fetch_data(self):
clone = self._clone(_offset=k.start, _limit=k.stop) # if self._use_cache:
return list(clone)[::k.step] # if self._result_cache is None:
# TODO - we are throwing away this retrieved data. # self._result_cache = list(self.get_iterator())
# We could cache it if we had some kind of sparse # return self._result_cache
# list structure we could put it in. # else:
else: # return list(self.get_iterator())
return self._result_cache[k] #
# def __getitem__(self, k):
else: # """Retrieve an item or slice from the set of results"""
# TODO: possibly use a new query which just gets one item # # getitem can't return query instances, because .filter()
# if we haven't already got them all? # # and .order_by() methods on the result would break badly.
return self._fetch_data()[k] # # This means we don't have to worry about arithmetic with
# # self._limit or self._offset - they will both be None
def get_iterator(self): # # at this point
# self._select is a dictionary, and dictionaries' key order is # if isinstance(k, slice):
# undefined, so we convert it to a list of tuples. # # Get a new query if we haven't already got data from db
_extra_select = (self._select or {}).items() # if self._result_cache is None:
# # slice.stop and slice.start
cursor = connection.cursor() # clone = self._clone(_offset=k.start, _limit=k.stop)
select, sql, params = self._get_sql_clause(True) # return list(clone)[::k.step]
cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) # # TODO - we are throwing away this retrieved data.
fill_cache = self._select_related # # We could cache it if we had some kind of sparse
index_end = len(self.klass._meta.fields) # # list structure we could put it in.
while 1: # else:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) # return self._result_cache[k]
if not rows: #
raise StopIteration # else:
for row in rows: # # TODO: possibly use a new query which just gets one item
if fill_cache: # # if we haven't already got them all?
obj, index_end = get_cached_row(self.klass, row, 0) # return self._fetch_data()[k]
else:
obj = self.klass(*row[:index_end])
for i, k in enumerate(_extra_select):
setattr(obj, k[0], row[index_end+i])
yield obj
class QOperator: class QOperator:
"Base class for QAnd and QOr" "Base class for QAnd and QOr"
def __init__(self, *args): def __init__(self, *args):
self.args = args self.args = args
def __repr__(self):
return '(%s)' % self.operator.join([repr(el) for el in self.args])
def get_sql(self, opts): def get_sql(self, opts):
tables, joins, where, params = [], {}, [], [] tables, joins, where, params = [], {}, [], []
for val in self.args: for val in self.args:
@ -327,10 +333,7 @@ class QAnd(QOperator):
"Encapsulates a combined query that uses 'AND'." "Encapsulates a combined query that uses 'AND'."
operator = ' AND ' operator = ' AND '
def __or__(self, other): def __or__(self, other):
if isinstance(other, (QAnd, QOr, Q)): return QOr(self, other)
return QOr(self, other)
else:
raise TypeError, other
def __and__(self, other): def __and__(self, other):
if isinstance(other, QAnd): if isinstance(other, QAnd):
@ -344,10 +347,7 @@ class QOr(QOperator):
"Encapsulates a combined query that uses 'OR'." "Encapsulates a combined query that uses 'OR'."
operator = ' OR ' operator = ' OR '
def __and__(self, other): def __and__(self, other):
if isinstance(other, (QAnd, QOr, Q)): return QAnd(self, other)
return QAnd(self, other)
else:
raise TypeError, other
def __or__(self, other): def __or__(self, other):
if isinstance(other, QOr): if isinstance(other, QOr):
@ -362,20 +362,11 @@ class Q:
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.kwargs = kwargs self.kwargs = kwargs
def __repr__(self):
return 'Q%r' % self.kwargs
def __and__(self, other): def __and__(self, other):
if isinstance(other, (Q, QAnd, QOr)): return QAnd(self, other)
return QAnd(self, other)
else:
raise TypeError, other
def __or__(self, other): def __or__(self, other):
if isinstance(other, (Q, QAnd, QOr)): return QOr(self, other)
return QOr(self, other)
else:
raise TypeError, other
def get_sql(self, opts): def get_sql(self, opts):
return parse_lookup(self.kwargs.items(), opts) return parse_lookup(self.kwargs.items(), opts)
@ -493,7 +484,6 @@ def find_field(name, field_list, use_accessor=False):
Finds a field with a specific name in a list of field instances. Finds a field with a specific name in a list of field instances.
Returns None if there are no matches, or several matches. Returns None if there are no matches, or several matches.
""" """
if use_accessor: if use_accessor:
matches = [f for f in field_list if f.OLD_get_accessor_name() == name] matches = [f for f in field_list if f.OLD_get_accessor_name() == name]
else: else: