From 77926176b281b9c553c934e52acdd1c0377ea601 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 27 Apr 2022 11:24:43 +0200 Subject: [PATCH] Refs #33646 -- Added RawModelIterable. --- django/db/models/query.py | 80 ++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 8e9eb78cd4..80dc83a41c 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -140,6 +140,50 @@ class ModelIterable(BaseIterable): yield obj +class RawModelIterable(BaseIterable): + """ + Iterable that yields a model instance for each row from a raw queryset. + """ + + def __iter__(self): + # Cache some things for performance reasons outside the loop. + db = self.queryset.db + query = self.queryset.query + connection = connections[db] + compiler = connection.ops.compiler("SQLCompiler")(query, connection, db) + query_iterator = iter(query) + + try: + ( + model_init_names, + model_init_pos, + annotation_fields, + ) = self.queryset.resolve_model_init_order() + model_cls = self.queryset.model + if model_cls._meta.pk.attname not in model_init_names: + raise exceptions.FieldDoesNotExist( + "Raw query must include the primary key" + ) + fields = [self.queryset.model_fields.get(c) for c in self.queryset.columns] + converters = compiler.get_converters( + [f.get_col(f.model._meta.db_table) if f else None for f in fields] + ) + if converters: + query_iterator = compiler.apply_converters(query_iterator, converters) + for values in query_iterator: + # Associate fields to values + model_init_values = [values[pos] for pos in model_init_pos] + instance = model_cls.from_db(db, model_init_names, model_init_values) + if annotation_fields: + for column, pos in annotation_fields: + setattr(instance, column, values[pos]) + yield instance + finally: + # Done iterating the Query. If it has its own cursor, close it. + if hasattr(query, "cursor") and query.cursor: + query.cursor.close() + + class ValuesIterable(BaseIterable): """ Iterable returned by QuerySet.values() that yields a dict for each row. @@ -1994,41 +2038,7 @@ class RawQuerySet: return iter(self._result_cache) def iterator(self): - # Cache some things for performance reasons outside the loop. - db = self.db - connection = connections[db] - compiler = connection.ops.compiler("SQLCompiler")(self.query, connection, db) - query = iter(self.query) - - try: - ( - model_init_names, - model_init_pos, - annotation_fields, - ) = self.resolve_model_init_order() - if self.model._meta.pk.attname not in model_init_names: - raise exceptions.FieldDoesNotExist( - "Raw query must include the primary key" - ) - model_cls = self.model - fields = [self.model_fields.get(c) for c in self.columns] - converters = compiler.get_converters( - [f.get_col(f.model._meta.db_table) if f else None for f in fields] - ) - if converters: - query = compiler.apply_converters(query, converters) - for values in query: - # Associate fields to values - model_init_values = [values[pos] for pos in model_init_pos] - instance = model_cls.from_db(db, model_init_names, model_init_values) - if annotation_fields: - for column, pos in annotation_fields: - setattr(instance, column, values[pos]) - yield instance - finally: - # Done iterating the Query. If it has its own cursor, close it. - if hasattr(self.query, "cursor") and self.query.cursor: - self.query.cursor.close() + yield from RawModelIterable(self) def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.query)