Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed QuerySet.raw_aggregate() when some documents don't contain all model fields #275

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions django_mongodb_backend/queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,37 +53,36 @@ def __iter__(self):
compiler = connection.ops.compiler("SQLCompiler")(query, connection, db)
query_iterator = iter(query)
try:
# Get the columns from the first result.
try:
first_result = next(query_iterator)
except StopIteration:
# No results.
return
self.queryset.columns = list(first_result.keys())
# Reset the iterator to include the first item.
query_iterator = self._make_result(chain([first_result], query_iterator))
(
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 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:
# Get the columns for each result
for result 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
self.queryset.columns = list(result.keys())
# Use the new columns to define the new model_init_order.
(
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 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]
)
# Make an iterator from the singular result
result_iter = self._make_result([result])
if converters:
result_iter = compiler.apply_converters(result_iter, converters)

# Iterate once to generate a model object based solely on the result
for values in result_iter:
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:
query.cursor.close()

Expand All @@ -93,4 +92,5 @@ def _make_result(self, query):
of __iter__().
"""
for result in query:
yield tuple(result.values())
# Create a tuple of values strictly from the outlined result columns
yield tuple(result.get(key, None) for key in self.queryset.columns)
25 changes: 25 additions & 0 deletions tests/raw_query_/test_raw_aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from datetime import date

from django.db import connection
from django.core.exceptions import FieldDoesNotExist
from django.test import TestCase

Expand Down Expand Up @@ -170,6 +171,30 @@ def test_order_handler(self):
authors = Author.objects.all()
self.assertSuccessfulRawQuery(Author, query, authors)

def test_different_ordered_in_database(self):
"""Documents in MongoDB are not required to maintain key order as
a means to improve write efficiency. Documents can be returned
to Django out of order. This can lead to incorrect information being placed
in a RawQueryset object.
"""
database = connection["<TEST_DATABASE>"].database
raw_insert = Author(first_name="Out of", last_name="Order", dob=date(1950, 9, 20))
try:
# Insert a document into the database in reverse
database[Author._meta.db_table].insert_one(
{
field.name: getattr(field.name, raw_insert)
for field in reversed(Author._meta.get_fields())
}
)
query = []
authors = Author.objects.all()
self.assertSuccessfulRawQuery(Author, query, authors)
finally:
database[Author._meta.db_table].delete_one(
{"first_name": raw_insert.first_name, "last_name": raw_insert.last_name}
)

def test_query_representation(self):
"""Test representation of raw query."""
query = [{"$match": {"last_name": "foo"}}]
Expand Down
Loading