Skip to content

Commit 9cb1451

Browse files
committed
add clashing field name validation
1 parent 3beb915 commit 9cb1451

File tree

4 files changed

+97
-5
lines changed

4 files changed

+97
-5
lines changed

django_mongodb_backend/fields/polymorphic_embedded_model.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from django.core import checks
55
from django.core.exceptions import FieldDoesNotExist, ValidationError
6-
from django.db import models
6+
from django.db import connections, models
77
from django.db.models.fields.related import lazy_related_operation
88
from django.db.models.lookups import Transform
99

@@ -30,6 +30,7 @@ def check(self, **kwargs):
3030
from ..models import EmbeddedModel
3131

3232
errors = super().check(**kwargs)
33+
embedded_fields = {}
3334
for model in self.embedded_models:
3435
if not issubclass(model, EmbeddedModel):
3536
return [
@@ -52,6 +53,23 @@ def check(self, **kwargs):
5253
id="django_mongodb_backend.embedded_model.E001",
5354
)
5455
)
56+
field_name = field.name
57+
if existing_field := embedded_fields.get(field.name):
58+
connection = _get_mongodb_connection()
59+
if existing_field.db_type(connection) != field.db_type(connection):
60+
errors.append(
61+
checks.Warning(
62+
f"Embedded models {existing_field.model._meta.label} "
63+
f"and {field.model._meta.label} both have field "
64+
f"'{field_name}' of different type.",
65+
obj=self,
66+
id="django_mongodb_backend.embedded_model.E003",
67+
hint="It may be impossible to query both fields.",
68+
)
69+
)
70+
71+
else:
72+
embedded_fields[field_name] = field
5573
return errors
5674

5775
def deconstruct(self):
@@ -219,3 +237,10 @@ def __init__(self, key_name, ref_field):
219237

220238
def __call__(self, *args, **kwargs):
221239
return KeyTransform(self.key_name, self.ref_field, *args, **kwargs)
240+
241+
242+
def _get_mongodb_connection():
243+
for alias in connections:
244+
if connections[alias].vendor == "mongodb":
245+
return connections[alias]
246+
return None

docs/source/ref/models/fields.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ These indexes use 0-based indexing.
321321

322322
.. versionadded:: 5.2.0b2
323323

324-
Stores a model of type ``embedded_models``.
324+
Stores a model of one of the types in ``embedded_models``.
325325

326326
.. attribute:: embedded_models
327327

@@ -337,7 +337,8 @@ These indexes use 0-based indexing.
337337
.. admonition:: Migrations support is limited
338338

339339
:djadmin:`makemigrations` does not yet detect changes to embedded models,
340-
nor does it create indexes or constraints for embedded models.
340+
nor does it create indexes or constraints for embedded models referenced
341+
by ``PolymorphicEmbeddedModelField``.
341342

342343
.. admonition:: Forms are not supported
343344

docs/source/topics/embedded-models.rst

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ Represented in BSON, the person structures looks like this:
246246
pet: { name: 'Pheobe', purrs: true, _label: 'myapp.Cat' }
247247
}
248248
249-
The ``_label`` field contains the model's
250-
:attr:`~django.db.models.Options.label`.
249+
The ``_label`` field tracks the model's :attr:`~django.db.models.Options.label`
250+
so that the model can be initialized properly.
251251

252252
Querying ``PolymorphicEmbeddedModelField``
253253
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -262,3 +262,31 @@ You can also filter on fields that aren't shared among the embedded models. For
262262
example, if you filter on ``barks``, you'll only get back people with dogs::
263263

264264
>>> Person.objects.filter(pet__barks=True)
265+
266+
Clashing field names
267+
~~~~~~~~~~~~~~~~~~~~
268+
269+
Be careful not to use embedded models with clashing field names of different
270+
types. For example::
271+
272+
from django.db import models
273+
274+
from django_mongodb_backend.fields import PolymorphicEmbeddedModelField
275+
from django_mongodb_backend.models import EmbeddedModel
276+
277+
class Target1(EmbeddedModel):
278+
number = models.IntegerField()
279+
280+
class Target2(EmbeddedModel):
281+
number = models.DecimalField(max_digits=4, decimal_places=2)
282+
283+
class Example(models.Model):
284+
target = PolymorphicEmbeddedModelField([Target1, Target2])
285+
286+
In this case, it will be impossible to query the ``number`` field properly
287+
since Django won't know whether to prepare the lookup value as an integer or as
288+
a decimal.
289+
290+
Additionally, querying into nested embedded model fields with the same name
291+
isn't well supported: the first model in ``embedded_models`` is the one that
292+
will be used for lookups.

tests/model_fields_/test_polymorphic_embedded_model.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,41 @@ class MyModel(models.Model):
216216
msg,
217217
"Embedded models must be a subclass of django_mongodb_backend.models.EmbeddedModel.",
218218
)
219+
220+
def test_clashing_fields(self):
221+
class Target1(EmbeddedModel):
222+
clash = models.DecimalField(max_digits=4, decimal_places=2)
223+
224+
class Target2(EmbeddedModel):
225+
clash = models.CharField(max_length=255)
226+
227+
class MyModel(models.Model):
228+
field = PolymorphicEmbeddedModelField([Target1, Target2])
229+
230+
errors = MyModel().check()
231+
self.assertEqual(len(errors), 1)
232+
self.assertEqual(errors[0].id, "django_mongodb_backend.embedded_model.E003")
233+
self.assertEqual(
234+
errors[0].msg,
235+
"Embedded models model_fields_.Target1 and model_fields_.Target2 "
236+
"both have field 'clash' of different type.",
237+
)
238+
self.assertEqual(
239+
errors[0].hint,
240+
"It may be impossible to query both fields.",
241+
)
242+
243+
def test_clashing_fields_of_same_type(self):
244+
"""Fields of different type don't clash if they use the same db_type."""
245+
246+
class Target1(EmbeddedModel):
247+
clash = models.TextField()
248+
249+
class Target2(EmbeddedModel):
250+
clash = models.CharField(max_length=255)
251+
252+
class MyModel(models.Model):
253+
field = PolymorphicEmbeddedModelField([Target1, Target2])
254+
255+
errors = MyModel().check()
256+
self.assertEqual(len(errors), 0)

0 commit comments

Comments
 (0)