Skip to content

Commit 2d30fdb

Browse files
committed
INTPYTHON-765 Fix crash loading embedded models with missing fields that use database converters
1 parent 3de47f7 commit 2d30fdb

File tree

6 files changed

+53
-4
lines changed

6 files changed

+53
-4
lines changed

django_mongodb_backend/operations.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ def convert_embeddedmodelfield_value(self, value, expression, connection):
184184
if value is not None:
185185
# Apply database converters to each field of the embedded model.
186186
for field in expression.output_field.embedded_model._meta.fields:
187+
if field.attname not in value:
188+
continue
187189
field_expr = Expression(output_field=field)
188190
converters = connection.ops.get_db_converters(
189191
field_expr
@@ -204,6 +206,8 @@ def convert_polymorphicembeddedmodelfield_value(self, value, expression, connect
204206
model_class = expression.output_field._get_model_from_label(value["_label"])
205207
# Apply database converters to each field of the embedded model.
206208
for field in model_class._meta.fields:
209+
if field.attname not in value:
210+
continue
207211
field_expr = Expression(output_field=field)
208212
converters = connection.ops.get_db_converters(
209213
field_expr

docs/releases/5.2.x.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ New features
1515
Bug fixes
1616
---------
1717

18-
- ...
18+
- Fixed a ``KeyError`` crash when loading models with ``EmbeddedModel`` fields
19+
that use a database converter, if the field isn't present in the data (e.g.
20+
data not written by Django, or after a field was added to an existing
21+
``EmbeddedModel``).
1922

2023
Deprecated features
2124
-------------------

tests/model_fields_/test_embedded_model.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from datetime import timedelta
33

44
from django.core.exceptions import FieldDoesNotExist, ValidationError
5-
from django.db import models
5+
from django.db import connection, models
66
from django.db.models import (
77
Exists,
88
ExpressionWrapper,
@@ -107,6 +107,16 @@ def test_pre_save(self):
107107
self.assertEqual(obj.data.auto_now_add, auto_now_add)
108108
self.assertGreater(obj.data.auto_now, auto_now_two)
109109

110+
def test_missing_field_in_data(self):
111+
"""
112+
Loading a model with an EmbeddedModelField that has a missing subfield
113+
(e.g. data not written by Django) that uses a database converter (in
114+
this case, integer is an IntegerField) doesn't crash.
115+
"""
116+
Holder.objects.create(data=Data(integer=5))
117+
connection.database.model_fields__holder.update_many({}, {"$unset": {"data.integer": ""}})
118+
self.assertIsNone(Holder.objects.first().data.integer)
119+
110120

111121
class QueryingTests(TestCase):
112122
@classmethod

tests/model_fields_/test_embedded_model_array.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ def test_save_load_null(self):
6262
movie = Movie.objects.get(title="Lion King")
6363
self.assertIsNone(movie.reviews)
6464

65+
def test_missing_field_in_data(self):
66+
"""
67+
Loading a model with an EmbeddedModelArrayField that has a missing
68+
subfield (e.g. data not written by Django) that uses a database
69+
converter (in this case, rating is an IntegerField) doesn't crash.
70+
"""
71+
Movie.objects.create(title="Lion King", reviews=[Review(title="The best", rating=10)])
72+
connection.database.model_fields__movie.update_many(
73+
{}, {"$unset": {"reviews.$[].rating": ""}}
74+
)
75+
self.assertIsNone(Movie.objects.first().reviews[0].rating)
76+
6577

6678
class QueryingTests(TestCase):
6779
@classmethod

tests/model_fields_/test_polymorphic_embedded_model.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from decimal import Decimal
33

44
from django.core.exceptions import FieldDoesNotExist, ValidationError
5-
from django.db import models
5+
from django.db import connection, models
66
from django.test import SimpleTestCase, TestCase
77
from django.test.utils import isolate_apps
88

@@ -91,6 +91,16 @@ def test_pre_save(self):
9191
# simultaneously.
9292
self.assertAlmostEqual(updated_at, created_at, delta=timedelta(microseconds=1000))
9393

94+
def test_missing_field_in_data(self):
95+
"""
96+
Loading a model with a PolymorphicEmbeddedModelField that has a missing
97+
subfield (e.g. data not written by Django) that uses a database
98+
converter (in this case, weight is a DecimalField) doesn't crash.
99+
"""
100+
Person.objects.create(pet=Cat(name="Pheobe", weight="3.5"))
101+
connection.database.model_fields__person.update_many({}, {"$unset": {"pet.weight": ""}})
102+
self.assertIsNone(Person.objects.first().pet.weight)
103+
94104

95105
class QueryingTests(TestCase):
96106
@classmethod

tests/model_fields_/test_polymorphic_embedded_model_array.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from decimal import Decimal
22

33
from django.core.exceptions import FieldDoesNotExist
4-
from django.db import models
4+
from django.db import connection, models
55
from django.test import SimpleTestCase, TestCase
66
from django.test.utils import isolate_apps
77

@@ -62,6 +62,16 @@ def test_save_load_null(self):
6262
owner = Owner.objects.get(name="Bob")
6363
self.assertIsNone(owner.pets)
6464

65+
def test_missing_field_in_data(self):
66+
"""
67+
Loading a model with a PolymorphicEmbeddedModelArrayField that has a
68+
missing subfield (e.g. data not written by Django) that uses a database
69+
converter (in this case, weight is a DecimalField) doesn't crash.
70+
"""
71+
Owner.objects.create(name="Bob", pets=[Cat(name="Phoebe", weight="3.5")])
72+
connection.database.model_fields__owner.update_many({}, {"$unset": {"pets.$[].weight": ""}})
73+
self.assertIsNone(Owner.objects.first().pets[0].weight)
74+
6575

6676
class QueryingTests(TestCase):
6777
@classmethod

0 commit comments

Comments
 (0)