View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -10,6 +10,7 @@
from django.db.models.fields.proxy import OrderWrt
from django.db.models.loading import get_models, app_cache_ready
from django.utils import six
from django.utils.functional import cached_property
from django.utils.datastructures import SortedDict
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
from django.utils.translation import activate, deactivate_all, get_language, string_concat
@@ -173,6 +174,22 @@ def add_field(self, field):
if hasattr(self, '_field_cache'):
del self._field_cache
del self._field_name_cache
# The fields, concrete_fields and local_concrete_fields are
# implemented as cached properties for performance reasons.
# The attrs will not exists if the cached property isn't
# accessed yet, hence the try-excepts.
try:
del self.fields
except AttributeError:
pass
try:
del self.concrete_fields
except AttributeError:
pass
try:
del self.local_concrete_fields
except AttributeError:
pass
if hasattr(self, '_name_map'):
del self._name_map
@@ -245,7 +262,8 @@ def _swapped(self):
return None
swapped = property(_swapped)
def _fields(self):
@cached_property
def fields(self):
"""
The getter for self.fields. This returns the list of field objects
available to this model (including through parent models).
@@ -258,7 +276,14 @@ def _fields(self):
except AttributeError:
self._fill_fields_cache()
return self._field_name_cache
fields = property(_fields)
@cached_property
def concrete_fields(self):
return [f for f in self.fields if f.column is not None]
@cached_property
def local_concrete_fields(self):
return [f for f in self.local_fields if f.column is not None]
def get_fields_with_model(self):
"""
@@ -272,6 +297,10 @@ def get_fields_with_model(self):
self._fill_fields_cache()
return self._field_cache
def get_concrete_fields_with_model(self):
return [(field, model) for field, model in self.get_fields_with_model() if
field.column is not None]
def _fill_fields_cache(self):
cache = []
for parent in self.parents:
@@ -377,6 +406,9 @@ def init_name_map(self):
cache[f.name] = (f, model, True, True)
for f, model in self.get_fields_with_model():
cache[f.name] = (f, model, True, False)
for f in self.virtual_fields:
if hasattr(f, 'related'):
cache[f.name] = (f.related, None if f.model == self.model else f.model, True, False)
if app_cache_ready():
self._name_map = cache
return cache
@@ -432,7 +464,7 @@ def _fill_related_objects_cache(self):
for klass in get_models(include_auto_created=True, only_installed=False):
if not klass._meta.swapped:
for f in klass._meta.local_fields:
if f.rel and not isinstance(f.rel.to, six.string_types):
if f.rel and not isinstance(f.rel.to, six.string_types) and f.generate_reverse_relation:
if self == f.rel.to._meta:
cache[f.related] = None
proxy_cache[f.related] = None
View
@@ -261,13 +261,13 @@ def iterator(self):
only_load = self.query.get_loaded_field_names()
if not fill_cache:
fields = self.model._meta.fields
fields = self.model._meta.concrete_fields
load_fields = []
# If only/defer clauses have been specified,
# build the list of fields that are to be loaded.
if only_load:
for field, model in self.model._meta.get_fields_with_model():
for field, model in self.model._meta.get_concrete_fields_with_model():
if model is None:
model = self.model
try:
@@ -280,7 +280,7 @@ def iterator(self):
load_fields.append(field.name)
index_start = len(extra_select)
aggregate_start = index_start + len(load_fields or self.model._meta.fields)
aggregate_start = index_start + len(load_fields or self.model._meta.concrete_fields)
skip = None
if load_fields and not fill_cache:
@@ -312,7 +312,11 @@ def iterator(self):
if skip:
obj = model_cls(**dict(zip(init_list, row_data)))
else:
obj = model(*row_data)
try:
obj = model(*row_data)
except IndexError:
import ipdb; ipdb.set_trace()
pass
# Store the source database of the object
obj._state.db = db
@@ -962,7 +966,7 @@ def _setup_aggregate_query(self, aggregates):
"""
opts = self.model._meta
if self.query.group_by is None:
field_names = [f.attname for f in opts.fields]
field_names = [f.attname for f in opts.concrete_fields]
self.query.add_fields(field_names, False)
self.query.set_group_by()
@@ -1055,7 +1059,7 @@ def _setup_query(self):
else:
# Default to all fields.
self.extra_names = None
self.field_names = [f.attname for f in self.model._meta.fields]
self.field_names = [f.attname for f in self.model._meta.concrete_fields]
self.aggregate_names = None
self.query.select = []
@@ -1266,7 +1270,7 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
skip = set()
init_list = []
# Build the list of fields that *haven't* been requested
for field, model in klass._meta.get_fields_with_model():
for field, model in klass._meta.get_concrete_fields_with_model():
if field.name not in load_fields:
skip.add(field.attname)
elif from_parent and issubclass(from_parent, model.__class__):
@@ -1285,22 +1289,22 @@ def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None,
else:
# Load all fields on klass
field_count = len(klass._meta.fields)
field_count = len(klass._meta.concrete_fields)
# Check if we need to skip some parent fields.
if from_parent and len(klass._meta.local_fields) != len(klass._meta.fields):
if from_parent and len(klass._meta.local_concrete_fields) != len(klass._meta.concrete_fields):
# Only load those fields which haven't been already loaded into
# 'from_parent'.
non_seen_models = [p for p in klass._meta.get_parent_list()
if not issubclass(from_parent, p)]
# Load local fields, too...
non_seen_models.append(klass)
field_names = [f.attname for f in klass._meta.fields
field_names = [f.attname for f in klass._meta.concrete_fields
if f.model in non_seen_models]
field_count = len(field_names)
# Try to avoid populating field_names variable for perfomance reasons.
# If field_names variable is set, we use **kwargs based model init
# which is slower than normal init.
if field_count == len(klass._meta.fields):
if field_count == len(klass._meta.concrete_fields):
field_names = ()
restricted = requested is not None
View
@@ -7,7 +7,7 @@
# describe the relation in Model terms (model Options and Fields for both
# sides of the relation. The join_field is the field backing the relation.
PathInfo = namedtuple('PathInfo',
'from_field to_field from_opts to_opts join_field '
'from_opts to_opts target_fields join_field '
'm2m direct')
class RelatedObject(object):
View
@@ -2,10 +2,9 @@
from django.conf import settings
from django.core.exceptions import FieldError
from django.db import transaction
from django.db.backends.util import truncate_name
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import select_related_descend
from django.db.models.query_utils import select_related_descend, QueryWrapper
from django.db.models.sql.constants import (SINGLE, MULTI, ORDER_DIR,
GET_ITERATOR_CHUNK_SIZE, SelectInfo)
from django.db.models.sql.datastructures import EmptyResultSet
@@ -33,7 +32,7 @@ def pre_sql_setup(self):
# cleaned. We are not using a clone() of the query here.
"""
if not self.query.tables:
self.query.join((None, self.query.model._meta.db_table, None, None))
self.query.join((None, self.query.model._meta.db_table, None))
if (not self.query.select and self.query.default_cols and not
self.query.included_inherited_models):
self.query.setup_inherited_models()
@@ -273,7 +272,7 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
# be used by local fields.
seen_models = {None: start_alias}
for field, model in opts.get_fields_with_model():
for field, model in opts.get_concrete_fields_with_model():
if from_parent and model is not None and issubclass(from_parent, model):
# Avoid loading data for already loaded parents.
continue
@@ -314,9 +313,10 @@ def get_distinct(self):
for name in self.query.distinct_fields:
parts = name.split(LOOKUP_SEP)
field, col, alias, _, _ = self._setup_joins(parts, opts, None)
col, alias = self._final_join_removal(col, alias)
result.append("%s.%s" % (qn(alias), qn2(col)))
field, cols, alias, _, _ = self._setup_joins(parts, opts, None)
cols, alias = self._final_join_removal(cols, alias)
for col in cols:
result.append("%s.%s" % (qn(alias), qn2(col)))
return result
@@ -387,15 +387,16 @@ def get_ordering(self):
elif get_order_dir(field)[0] not in self.query.extra_select:
# 'col' is of the form 'field' or 'field1__field2' or
# '-field1__field2__field', etc.
for table, col, order in self.find_ordering_name(field,
for table, cols, order in self.find_ordering_name(field,
self.query.model._meta, default_order=asc):
if (table, col) not in processed_pairs:
elt = '%s.%s' % (qn(table), qn2(col))
processed_pairs.add((table, col))
if distinct and elt not in select_aliases:
ordering_aliases.append(elt)
result.append('%s %s' % (elt, order))
group_by.append((elt, []))
for col in cols:
if (table, col) not in processed_pairs:
elt = '%s.%s' % (qn(table), qn2(col))
processed_pairs.add((table, col))
if distinct and elt not in select_aliases:
ordering_aliases.append(elt)
result.append('%s %s' % (elt, order))
group_by.append((elt, []))
else:
elt = qn2(col)
if distinct and col not in select_aliases:
@@ -414,7 +415,7 @@ def find_ordering_name(self, name, opts, alias=None, default_order='ASC',
"""
name, order = get_order_dir(name, default_order)
pieces = name.split(LOOKUP_SEP)
field, col, alias, joins, opts = self._setup_joins(pieces, opts, alias)
field, cols, alias, joins, opts = self._setup_joins(pieces, opts, alias)
# If we get to this point and the field is a relation to another model,
# append the default ordering for that model.
@@ -432,8 +433,8 @@ def find_ordering_name(self, name, opts, alias=None, default_order='ASC',
results.extend(self.find_ordering_name(item, opts, alias,
order, already_seen))
return results
col, alias = self._final_join_removal(col, alias)
return [(alias, col, order)]
cols, alias = self._final_join_removal(cols, alias)
return [(alias, cols, order)]
def _setup_joins(self, pieces, opts, alias):
"""
@@ -446,13 +447,13 @@ def _setup_joins(self, pieces, opts, alias):
"""
if not alias:
alias = self.query.get_initial_alias()
field, target, opts, joins, _ = self.query.setup_joins(
field, targets, opts, joins, _ = self.query.setup_joins(
pieces, opts, alias)
# We will later on need to promote those joins that were added to the
# query afresh above.
joins_to_promote = [j for j in joins if self.query.alias_refcount[j] < 2]
alias = joins[-1]
col = target.column
cols = [target.column for target in targets]
if not field.rel:
# To avoid inadvertent trimming of a necessary alias, use the
# refcount to show that we are referencing a non-relation field on
@@ -463,9 +464,9 @@ def _setup_joins(self, pieces, opts, alias):
# Ordering or distinct must not affect the returned set, and INNER
# JOINS for nullable fields could do this.
self.query.promote_joins(joins_to_promote)
return field, col, alias, joins, opts
return field, cols, alias, joins, opts
def _final_join_removal(self, col, alias):
def _final_join_removal(self, cols, alias):
"""
A helper method for get_distinct and get_ordering. This method will
trim extra not-needed joins from the tail of the join chain.
@@ -477,12 +478,14 @@ def _final_join_removal(self, col, alias):
if alias:
while 1:
join = self.query.alias_map[alias]
if col != join.rhs_join_col:
lhs_cols, rhs_cols = zip(*[(lhs_col, rhs_col) for lhs_col, rhs_col in join.join_cols])
if set(cols) != set(rhs_cols):
break
cols = [lhs_cols[rhs_cols.index(col)] for col in cols]
self.query.unref_alias(alias)
alias = join.lhs_alias
col = join.lhs_join_col
return col, alias
return cols, alias
def get_from_clause(self):
"""
@@ -504,22 +507,30 @@ def get_from_clause(self):
if not self.query.alias_refcount[alias]:
continue
try:
name, alias, join_type, lhs, lhs_col, col, _, join_field = self.query.alias_map[alias]
name, alias, join_type, lhs, join_cols, _, join_field = self.query.alias_map[alias]
except KeyError:
# Extra tables can end up in self.tables, but not in the
# alias_map if they aren't in a join. That's OK. We skip them.
continue
alias_str = (alias != name and ' %s' % alias or '')
if join_type and not first:
if join_field and hasattr(join_field, 'get_extra_join_sql'):
extra_cond, extra_params = join_field.get_extra_join_sql(
self.connection, qn, lhs, alias)
extra_cond = join_field.get_extra_restriction(
self.query.where_class, alias, lhs)
if extra_cond:
extra_sql, extra_params = extra_cond.as_sql(
qn, self.connection)
extra_sql = 'AND (%s)' % extra_sql
from_params.extend(extra_params)
else:
extra_cond = ""
result.append('%s %s%s ON (%s.%s = %s.%s%s)' %
(join_type, qn(name), alias_str, qn(lhs),
qn2(lhs_col), qn(alias), qn2(col), extra_cond))
extra_sql = ""
result.append('%s %s%s ON ('
% (join_type, qn(name), alias_str))
for index, (lhs_col, rhs_col) in enumerate(join_cols):
if index != 0:
result.append(' AND ')
result.append('%s.%s = %s.%s' %
(qn(lhs), qn2(lhs_col), qn(alias), qn2(rhs_col)))
result.append('%s)' % extra_sql)
else:
connector = not first and ', ' or ''
result.append('%s%s%s' % (connector, qn(name), alias_str))
@@ -545,7 +556,7 @@ def get_grouping(self, having_group_by, ordering_group_by):
select_cols = self.query.select + self.query.related_select_cols
# Just the column, not the fields.
select_cols = [s[0] for s in select_cols]
if (len(self.query.model._meta.fields) == len(self.query.select)
if (len(self.query.model._meta.concrete_fields) == len(self.query.select)
and self.connection.features.allows_group_by_pk):
self.query.group_by = [
(self.query.model._meta.db_table, self.query.model._meta.pk.column)
@@ -623,14 +634,13 @@ def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
table = f.rel.to._meta.db_table
promote = nullable or f.null
alias = self.query.join_parent_model(opts, model, root_alias, {})
alias = self.query.join((alias, table, f.column,
f.rel.get_related_field().column),
join_cols = f.get_joining_columns()
alias = self.query.join((alias, table, join_cols),
outer_if_first=promote, join_field=f)
columns, aliases = self.get_default_columns(start_alias=alias,
opts=f.rel.to._meta, as_pairs=True)
self.query.related_select_cols.extend(
SelectInfo(col, field) for col, field in zip(columns, f.rel.to._meta.fields))
SelectInfo(col, field) for col, field in zip(columns, f.rel.to._meta.concrete_fields))
if restricted:
next = requested.get(f.name, {})
else:
@@ -653,7 +663,7 @@ def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
alias = self.query.join_parent_model(opts, f.rel.to, root_alias, {})
table = model._meta.db_table
alias = self.query.join(
(alias, table, f.rel.get_related_field().column, f.column),
(alias, table, f.get_joining_columns(reverse_join=True)),
outer_if_first=True, join_field=f
)
from_parent = (opts.model if issubclass(model, opts.model)
@@ -662,7 +672,7 @@ def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
opts=model._meta, as_pairs=True, from_parent=from_parent)
self.query.related_select_cols.extend(
SelectInfo(col, field) for col, field
in zip(columns, model._meta.fields))
in zip(columns, model._meta.concrete_fields))
next = requested.get(f.related_query_name(), {})
# Use True here because we are looking at the _reverse_ side of
# the relation, which is always nullable.
@@ -706,7 +716,7 @@ def results_iter(self):
if self.query.select:
fields = [f.field for f in self.query.select]
else:
fields = self.query.model._meta.fields
fields = self.query.model._meta.concrete_fields
fields = fields + [f.field for f in self.query.related_select_cols]
# If the field was deferred, exclude it from being passed
@@ -776,6 +786,22 @@ def execute_sql(self, result_type=MULTI):
return list(result)
return result
def as_subquery_condition(self, alias, columns):
qn = self.quote_name_unless_alias
qn2 = self.connection.ops.quote_name
if len(columns) == 1:
sql, params = self.as_sql()
return '%s.%s IN (%s)' % (qn(alias), qn2(columns[0]), sql), params
for index, select_col in enumerate(self.query.select):
lhs = '%s.%s' % (qn(select_col.col[0]), qn2(select_col.col[1]))
rhs = '%s.%s' % (qn(alias), qn2(columns[index]))
self.query.where.add(
QueryWrapper('%s = %s' % (lhs, rhs), []), 'AND')
sql, params = self.as_sql()
return 'EXISTS (%s)' % sql, params
class SQLInsertCompiler(SQLCompiler):
def placeholder(self, field, val):
View
@@ -25,7 +25,7 @@
# dictionary in the Query class).
JoinInfo = namedtuple('JoinInfo',
'table_name rhs_alias join_type lhs_alias '
'lhs_join_col rhs_join_col nullable join_field')
'join_cols nullable join_field')
# Pairs of column clauses to select, and (possibly None) field for the clause.
SelectInfo = namedtuple('SelectInfo', 'col field')
View
@@ -55,13 +55,14 @@ def prepare_leaf(self, node, query, allow_joins):
self.cols.append((node, query.aggregate_select[node.name]))
else:
try:
field, source, opts, join_list, path = query.setup_joins(
field, sources, opts, join_list, path = query.setup_joins(
field_list, query.get_meta(),
query.get_initial_alias(), self.reuse)
target, _, join_list = query.trim_joins(source, join_list, path)
targets, _, join_list = query.trim_joins(sources, join_list, path)
if self.reuse is not None:
self.reuse.update(join_list)
self.cols.append((node, (join_list[-1], target.column)))
for t in targets:
self.cols.append((node, (join_list[-1], t.column)))
except FieldDoesNotExist:
raise FieldError("Cannot resolve keyword %r into field. "
"Choices are: %s" % (self.name,
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -382,3 +382,28 @@ def relabeled_clone(self, change_map):
new.__class__ = self.__class__
new.alias, new.col, new.field = change_map[self.alias], self.col, self.field
return new
class SubqueryConstraint(object):
def __init__(self, alias, columns, targets, query_object):
self.alias = alias
self.columns = columns
self.targets = targets
self.query_object = query_object
def as_sql(self, qn, connection):
query = self.query_object
# QuerySet was sent
if hasattr(query, 'values'):
# as_sql should throw if we are using a
# connection on another database
query._as_sql(connection=connection)
query = query.values(*self.targets).query
query_compiler = query.get_compiler(connection=connection)
return query_compiler.as_subquery_condition(self.alias, self.columns)
def relabeled_clone(self, relabels):
return self.__class__(
relabels.get(self.alias, self.alias),
self.columns, self.query_object)
View
@@ -110,7 +110,7 @@ def model_to_dict(instance, fields=None, exclude=None):
from django.db.models.fields.related import ManyToManyField
opts = instance._meta
data = {}
for f in opts.fields + opts.many_to_many:
for f in opts.concrete_fields + opts.many_to_many:
if not f.editable:
continue
if fields and not f.name in fields:
@@ -149,7 +149,7 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
field_list = []
ignored = []
opts = model._meta
for f in sorted(opts.fields + opts.many_to_many):
for f in sorted(opts.concrete_fields + opts.many_to_many):
if not f.editable:
continue
if fields is not None and not f.name in fields: