diff --git a/django/db/models/query.py b/django/db/models/query.py index d22a1581887..104c000325e 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1299,8 +1299,67 @@ class RawQuerySet(object): self.translations = translations or {} def __iter__(self): - for row in self.query: - yield self.transform_results(row) + # Mapping of attrnames to row column positions. Used for constructing + # the model using kwargs, needed when not all model's fields are present + # in the query. + model_init_field_names = {} + # A list of tuples of (column name, column position). Used for + # annotation fields. + annotation_fields = [] + + # Cache some things for performance reasons outside the loop. + db = self.db + compiler = connections[db].ops.compiler('SQLCompiler')(self.query, connections[db], db) + need_resolv_columns = hasattr(compiler, 'resolve_columns') + + # Find out which columns are model's fields, and which ones should be + # annotated to the model. + for pos, column in enumerate(self.columns): + if column in self.model_fields: + model_init_field_names[self.model_fields[column].attname] = pos + else: + annotation_fields.append((column, pos)) + + # Find out which model's fields are not present in the query. + skip = set() + for field in self.model._meta.fields: + if field.attname not in model_init_field_names: + skip.add(field.attname) + if skip: + if self.model._meta.pk.attname in skip: + raise InvalidQuery('Raw query must include the primary key') + model_cls = deferred_class_factory(self.model, skip) + else: + model_cls = self.model + # All model's fields are present in the query. So, it is possible + # to use *args based model instantation. For each field of the model, + # record the query column position matching that field. + model_init_field_pos = [] + for field in self.model._meta.fields: + model_init_field_pos.append(model_init_field_names[field.attname]) + if need_resolv_columns: + fields = [self.model_fields.get(c, None) for c in self.columns] + # Begin looping through the query values. + for values in self.query: + if need_resolv_columns: + values = compiler.resolve_columns(values, fields) + # Associate fields to values + if skip: + model_init_kwargs = {} + for attname, pos in model_init_field_names.iteritems(): + model_init_kwargs[attname] = values[pos] + instance = model_cls(**model_init_kwargs) + else: + model_init_args = [values[pos] for pos in model_init_field_pos] + instance = model_cls(*model_init_args) + if annotation_fields: + for column, pos in annotation_fields: + setattr(instance, column, values[pos]) + + instance._state.db = db + instance._state.adding = False + + yield instance def __repr__(self): return "" % (self.raw_query % self.params) @@ -1355,50 +1414,6 @@ class RawQuerySet(object): self._model_fields[converter(column)] = field return self._model_fields - def transform_results(self, values): - model_init_kwargs = {} - annotations = () - - # Perform database backend type resolution - connection = connections[self.db] - compiler = connection.ops.compiler('SQLCompiler')(self.query, connection, self.db) - if hasattr(compiler, 'resolve_columns'): - fields = [self.model_fields.get(c,None) for c in self.columns] - values = compiler.resolve_columns(values, fields) - - # Associate fields to values - for pos, value in enumerate(values): - column = self.columns[pos] - - # Separate properties from annotations - if column in self.model_fields.keys(): - model_init_kwargs[self.model_fields[column].attname] = value - else: - annotations += (column, value), - - # Construct model instance and apply annotations - skip = set() - for field in self.model._meta.fields: - if field.attname not in model_init_kwargs.keys(): - skip.add(field.attname) - - if skip: - if self.model._meta.pk.attname in skip: - raise InvalidQuery('Raw query must include the primary key') - model_cls = deferred_class_factory(self.model, skip) - else: - model_cls = self.model - - instance = model_cls(**model_init_kwargs) - - for field, value in annotations: - setattr(instance, field, value) - - instance._state.db = self.query.using - instance._state.adding = False - - return instance - def insert_query(model, values, return_id=False, raw_values=False, using=None): """ Inserts a new record for the given model. This provides an interface to