diff --git a/django_mongodb_backend/fields/embedded_model.py b/django_mongodb_backend/fields/embedded_model.py index 95163236..3c51bd11 100644 --- a/django_mongodb_backend/fields/embedded_model.py +++ b/django_mongodb_backend/fields/embedded_model.py @@ -155,6 +155,16 @@ def formfield(self, **kwargs): } ) + def contribute_to_class(self, cls, name): + super().contribute_to_class(cls, name) + for field in self.embedded_model._meta.local_fields: + embedded_field_name = f"{name}.{field.name}" + field = field.clone() + field.unique = False + field.db_index = False + field.name = embedded_field_name + models.Field.contribute_to_class(field, cls, embedded_field_name, private_only=False) + class KeyTransform(Transform): def __init__(self, key_name, ref_field, *args, **kwargs): diff --git a/tests/schema_/test_embedded_model.py b/tests/schema_/test_embedded_model.py index c6c92603..0ef64fcc 100644 --- a/tests/schema_/test_embedded_model.py +++ b/tests/schema_/test_embedded_model.py @@ -519,6 +519,168 @@ class Meta: self.assertTableNotExists(Author) +class EmbeddedModelsTopLevelIndexTest(TestMixin, TransactionTestCase): + @isolate_apps("schema_") + def test_unique_together(self): + """Meta.unique_together defined at the top-level for embedded fields.""" + + class Address(EmbeddedModel): + unique_together_one = models.CharField(max_length=10) + unique_together_two = models.CharField(max_length=10) + + class Meta: + app_label = "schema_" + + class Author(EmbeddedModel): + address = EmbeddedModelField(Address) + unique_together_three = models.CharField(max_length=10) + unique_together_four = models.CharField(max_length=10) + + class Meta: + app_label = "schema_" + + class Book(models.Model): + author = EmbeddedModelField(Author) + + class Meta: + app_label = "schema_" + unique_together = [ + ("author.unique_together_three", "author.unique_together_four"), + ( + "author.address.unique_together_one", + "author.address.unique_together_two", + ), + ] + + with connection.schema_editor() as editor: + editor.create_model(Book) + self.assertTableExists(Book) + # Embedded uniques are created from top-level definition. + self.assertEqual( + self.get_constraints_for_columns( + Book, ["author.unique_together_three", "author.unique_together_four"] + ), + [ + "schema__book_author.unique_together_three_author.unique_together_four_09a570b8_uniq" + ], + ) + self.assertEqual( + self.get_constraints_for_columns( + Book, + ["author.address.unique_together_one", "author.address.unique_together_two"], + ), + [ + "schema__book_author.address.unique_together_one_author.address.unique_together_two_2c2d1477_uniq" + ], + ) + editor.delete_model(Book) + self.assertTableNotExists(Book) + + @isolate_apps("schema_") + def test_add_remove_field_indexes(self): + """AddField/RemoveField + EmbeddedModelField + Meta.indexes at top-level.""" + + class Address(EmbeddedModel): + indexed_one = models.CharField(max_length=10) + + class Meta: + app_label = "schema_" + + class Author(EmbeddedModel): + address = EmbeddedModelField(Address) + indexed_two = models.CharField(max_length=10) + + class Meta: + app_label = "schema_" + + class Book(models.Model): + author = EmbeddedModelField(Author) + + class Meta: + app_label = "schema_" + indexes = [ + models.Index(fields=["author.indexed_two"]), + models.Index(fields=["author.address.indexed_one"]), + ] + + new_field = EmbeddedModelField(Author) + new_field.set_attributes_from_name("author") + + with connection.schema_editor() as editor: + # Create the table and add the field. + editor.create_model(Book) + editor.add_field(Book, new_field) + # Embedded indexes are created. + self.assertEqual( + self.get_constraints_for_columns(Book, ["author.indexed_two"]), + ["schema__boo_author._333c90_idx"], + ) + self.assertEqual( + self.get_constraints_for_columns( + Book, + ["author.address.indexed_one"], + ), + ["schema__boo_author._f54386_idx"], + ) + editor.delete_model(Book) + self.assertTableNotExists(Book) + + @isolate_apps("schema_") + def test_add_remove_field_constraints(self): + """AddField/RemoveField + EmbeddedModelField + Meta.constraints at top-level.""" + + class Address(EmbeddedModel): + unique_constraint_one = models.CharField(max_length=10) + + class Meta: + app_label = "schema_" + + class Author(EmbeddedModel): + address = EmbeddedModelField(Address) + unique_constraint_two = models.CharField(max_length=10) + + class Meta: + app_label = "schema_" + + class Book(models.Model): + author = EmbeddedModelField(Author) + + class Meta: + app_label = "schema_" + constraints = [ + models.UniqueConstraint( + fields=["author.unique_constraint_two"], + name="unique_two", + ), + models.UniqueConstraint( + fields=["author.address.unique_constraint_one"], + name="unique_one", + ), + ] + + new_field = EmbeddedModelField(Author) + new_field.set_attributes_from_name("author") + + with connection.schema_editor() as editor: + # Create the table and add the field. + editor.create_model(Book) + editor.add_field(Book, new_field) + # Embedded constraints are created. + self.assertEqual( + self.get_constraints_for_columns(Book, ["author.unique_constraint_two"]), + ["unique_two"], + ) + self.assertEqual( + self.get_constraints_for_columns( + Book, + ["author.address.unique_constraint_one"], + ), + ["unique_one"], + ) + editor.delete_model(Book) + self.assertTableNotExists(Book) + + class EmbeddedModelsIgnoredTests(TestMixin, TransactionTestCase): def test_embedded_not_created(self): """create_model() and delete_model() ignore EmbeddedModel."""