Skip to content

Commit 0b9428c

Browse files
committed
[Bugfix]: Define new column and model instance per result
1 parent 64b1c10 commit 0b9428c

File tree

2 files changed

+56
-31
lines changed

2 files changed

+56
-31
lines changed

django_mongodb_backend/queryset.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,37 +53,36 @@ def __iter__(self):
5353
compiler = connection.ops.compiler("SQLCompiler")(query, connection, db)
5454
query_iterator = iter(query)
5555
try:
56-
# Get the columns from the first result.
57-
try:
58-
first_result = next(query_iterator)
59-
except StopIteration:
60-
# No results.
61-
return
62-
self.queryset.columns = list(first_result.keys())
63-
# Reset the iterator to include the first item.
64-
query_iterator = self._make_result(chain([first_result], query_iterator))
65-
(
66-
model_init_names,
67-
model_init_pos,
68-
annotation_fields,
69-
) = self.queryset.resolve_model_init_order()
70-
model_cls = self.queryset.model
71-
if model_cls._meta.pk.attname not in model_init_names:
72-
raise FieldDoesNotExist("Raw query must include the primary key")
73-
fields = [self.queryset.model_fields.get(c) for c in self.queryset.columns]
74-
converters = compiler.get_converters(
75-
[f.get_col(f.model._meta.db_table) if f else None for f in fields]
76-
)
77-
if converters:
78-
query_iterator = compiler.apply_converters(query_iterator, converters)
79-
for values in query_iterator:
56+
# Get the columns for each result
57+
for result in query_iterator:
8058
# Associate fields to values
81-
model_init_values = [values[pos] for pos in model_init_pos]
82-
instance = model_cls.from_db(db, model_init_names, model_init_values)
83-
if annotation_fields:
84-
for column, pos in annotation_fields:
85-
setattr(instance, column, values[pos])
86-
yield instance
59+
self.queryset.columns = list(result.keys())
60+
# Use the new columns to define the new model_init_order.
61+
(
62+
model_init_names,
63+
model_init_pos,
64+
annotation_fields,
65+
) = self.queryset.resolve_model_init_order()
66+
model_cls = self.queryset.model
67+
if model_cls._meta.pk.attname not in model_init_names:
68+
raise FieldDoesNotExist("Raw query must include the primary key")
69+
fields = [self.queryset.model_fields.get(c) for c in self.queryset.columns]
70+
converters = compiler.get_converters(
71+
[f.get_col(f.model._meta.db_table) if f else None for f in fields]
72+
)
73+
# Make an iterator from the singular result
74+
result_iter = self._make_result([result])
75+
if converters:
76+
result_iter = compiler.apply_converters(result_iter, converters)
77+
78+
# Iterate once to generate a model object based solely on the result
79+
for values in result_iter:
80+
model_init_values = [values[pos] for pos in model_init_pos]
81+
instance = model_cls.from_db(db, model_init_names, model_init_values)
82+
if annotation_fields:
83+
for column, pos in annotation_fields:
84+
setattr(instance, column, values[pos])
85+
yield instance
8786
finally:
8887
query.cursor.close()
8988

@@ -93,4 +92,5 @@ def _make_result(self, query):
9392
of __iter__().
9493
"""
9594
for result in query:
96-
yield tuple(result.values())
95+
# Create a tuple of values strictly from the outlined result columns
96+
yield tuple(result.get(key, None) for key in self.queryset.columns)

tests/raw_query_/test_raw_aggregate.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from datetime import date
66

7+
from django.db import connection
78
from django.core.exceptions import FieldDoesNotExist
89
from django.test import TestCase
910

@@ -170,6 +171,30 @@ def test_order_handler(self):
170171
authors = Author.objects.all()
171172
self.assertSuccessfulRawQuery(Author, query, authors)
172173

174+
def test_different_ordered_in_database(self):
175+
"""Documents in MongoDB are not required to maintain key order as
176+
a means to improve write efficiency. Documents can be returned
177+
to Django out of order. This can lead to incorrect information being placed
178+
in a RawQueryset object.
179+
"""
180+
database = connection["<TEST_DATABASE>"].database
181+
raw_insert = Author(first_name="Out of", last_name="Order", dob=date(1950, 9, 20))
182+
try:
183+
# Insert a document into the database in reverse
184+
database[Author._meta.db_table].insert_one(
185+
{
186+
field.name: getattr(field.name, raw_insert)
187+
for field in reversed(Author._meta.get_fields())
188+
}
189+
)
190+
query = []
191+
authors = Author.objects.all()
192+
self.assertSuccessfulRawQuery(Author, query, authors)
193+
finally:
194+
database[Author._meta.db_table].delete_one(
195+
{"first_name": raw_insert.first_name, "last_name": raw_insert.last_name}
196+
)
197+
173198
def test_query_representation(self):
174199
"""Test representation of raw query."""
175200
query = [{"$match": {"last_name": "foo"}}]

0 commit comments

Comments
 (0)