Skip to content

Commit 5b5766c

Browse files
committed
implement SchemaEditor.add/remove_constraint()
And also creating uniques in create_model() and add_field().
1 parent 8520d2c commit 5b5766c

File tree

3 files changed

+56
-37
lines changed

3 files changed

+56
-37
lines changed

django_mongodb/features.py

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
1313
supports_collation_on_charfield = False
1414
supports_column_check_constraints = False
1515
supports_date_lookup_using_string = False
16+
supports_deferrable_unique_constraints = False
1617
supports_explaining_query_execution = True
1718
supports_expression_defaults = False
1819
supports_expression_indexes = False
@@ -22,6 +23,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
2223
# BSON Date type doesn't support microsecond precision.
2324
supports_microsecond_precision = False
2425
supports_paramstyle_pyformat = False
26+
# Not implemented.
27+
supports_partial_indexes = False
2528
supports_select_difference = False
2629
supports_select_intersection = False
2730
supports_sequence_reset = False
@@ -88,28 +91,17 @@ class DatabaseFeatures(BaseDatabaseFeatures):
8891
"schema.tests.SchemaTests.test_alter_auto_field_to_integer_field",
8992
"schema.tests.SchemaTests.test_alter_field_default_dropped",
9093
"schema.tests.SchemaTests.test_alter_field_fk_to_o2o",
91-
"schema.tests.SchemaTests.test_alter_field_o2o_keeps_unique",
9294
"schema.tests.SchemaTests.test_alter_field_o2o_to_fk",
93-
"schema.tests.SchemaTests.test_alter_int_pk_to_int_unique",
9495
"schema.tests.SchemaTests.test_alter_null_to_not_null",
9596
"schema.tests.SchemaTests.test_alter_primary_key_the_same_name",
9697
"schema.tests.SchemaTests.test_autofield_to_o2o",
9798
"schema.tests.SchemaTests.test_remove_constraints_capital_letters",
9899
# AlterField (unique)
99100
"schema.tests.SchemaTests.test_indexes",
100101
"schema.tests.SchemaTests.test_unique",
101-
"schema.tests.SchemaTests.test_unique_and_reverse_m2m",
102102
# alter_unique_together
103103
"migrations.test_operations.OperationTests.test_alter_unique_together",
104104
"schema.tests.SchemaTests.test_unique_together",
105-
# add/remove_constraint
106-
"introspection.tests.IntrospectionTests.test_get_constraints",
107-
"migrations.test_operations.OperationTests.test_add_partial_unique_constraint",
108-
"migrations.test_operations.OperationTests.test_create_model_with_partial_unique_constraint",
109-
"migrations.test_operations.OperationTests.test_remove_partial_unique_constraint",
110-
"schema.tests.SchemaTests.test_composed_constraint_with_fk",
111-
"schema.tests.SchemaTests.test_remove_ignored_unique_constraint_not_create_fk_index",
112-
"schema.tests.SchemaTests.test_unique_constraint",
113105
}
114106
# $bitAnd, #bitOr, and $bitXor are new in MongoDB 6.3.
115107
_django_test_expected_failures_bitwise = {
@@ -199,24 +191,6 @@ def django_test_expected_failures(self):
199191
"model_fields.test_autofield.SmallAutoFieldTests",
200192
"queries.tests.TestInvalidValuesRelation.test_invalid_values",
201193
},
202-
"MongoDB does not enforce UNIQUE constraints.": {
203-
"auth_tests.test_basic.BasicTestCase.test_unicode_username",
204-
"auth_tests.test_migrations.ProxyModelWithSameAppLabelTests.test_migrate_with_existing_target_permission",
205-
"constraints.tests.UniqueConstraintTests.test_database_constraint",
206-
"contenttypes_tests.test_operations.ContentTypeOperationsTests.test_content_type_rename_conflict",
207-
"contenttypes_tests.test_operations.ContentTypeOperationsTests.test_existing_content_type_rename",
208-
"custom_pk.tests.CustomPKTests.test_unique_pk",
209-
"force_insert_update.tests.ForceInsertInheritanceTests.test_force_insert_with_existing_grandparent",
210-
"get_or_create.tests.GetOrCreateTestsWithManualPKs.test_create_with_duplicate_primary_key",
211-
"get_or_create.tests.GetOrCreateTestsWithManualPKs.test_savepoint_rollback",
212-
"get_or_create.tests.GetOrCreateThroughManyToMany.test_something",
213-
"get_or_create.tests.UpdateOrCreateTests.test_manual_primary_key_test",
214-
"get_or_create.tests.UpdateOrCreateTestsWithManualPKs.test_create_with_duplicate_primary_key",
215-
"introspection.tests.IntrospectionTests.test_get_constraints_unique_indexes_orders",
216-
"model_fields.test_filefield.FileFieldTests.test_unique_when_same_filename",
217-
"one_to_one.tests.OneToOneTests.test_multiple_o2o",
218-
"queries.test_bulk_update.BulkUpdateTests.test_database_routing_batch_atomicity",
219-
},
220194
"MongoDB does not enforce PositiveIntegerField constraint.": {
221195
"model_fields.test_integerfield.PositiveIntegerFieldTests.test_negative_values",
222196
},

django_mongodb/query.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
from django.db.models.sql.constants import INNER
1010
from django.db.models.sql.datastructures import Join
1111
from django.db.models.sql.where import AND, OR, XOR, NothingNode, WhereNode
12-
from pymongo.errors import DuplicateKeyError, PyMongoError
12+
from pymongo.errors import BulkWriteError, DuplicateKeyError, PyMongoError
1313

1414

1515
def wrap_database_errors(func):
1616
@wraps(func)
1717
def wrapper(*args, **kwargs):
1818
try:
1919
return func(*args, **kwargs)
20+
except BulkWriteError as e:
21+
if "E11000 duplicate key error" in str(e):
22+
raise IntegrityError from e
2023
except DuplicateKeyError as e:
2124
raise IntegrityError from e
2225
except PyMongoError as e:

django_mongodb/schema.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
2-
from django.db.models import Index
2+
from django.db.models import Index, UniqueConstraint
33
from pymongo.operations import IndexModel
44

55

@@ -14,8 +14,8 @@ def create_model(self, model):
1414

1515
def _create_model_indexes(self, model):
1616
"""
17-
Create all indexes (field indexes, index_together, Meta.indexes) for
18-
the specified model.
17+
Create all indexes (field indexes & uniques, index_together,
18+
Meta.constraints, Meta.indexes) for the specified model.
1919
"""
2020
if not model._meta.managed or model._meta.proxy or model._meta.swapped:
2121
return
@@ -25,11 +25,21 @@ def _create_model_indexes(self, model):
2525
index = Index(fields=[field.name])
2626
index.name = self._create_index_name(model._meta.db_table, [field.column])
2727
self.add_index(model, index)
28+
# Field uniques
29+
for field in model._meta.local_fields:
30+
if field.unique and field.column != "_id":
31+
constraint = UniqueConstraint(
32+
fields=[field.name], name=f"{model._meta.db_table}_{field.column}_key"
33+
)
34+
self.add_constraint(model, constraint)
2835
# Meta.index_together (RemovedInDjango51Warning)
2936
for field_names in model._meta.index_together:
3037
index = Index(fields=field_names)
3138
index.set_name_with_model(model)
3239
self.add_index(model, index)
40+
# Meta.constraints
41+
for constraint in model._meta.constraints:
42+
self.add_constraint(model, constraint)
3343
# Meta.indexes
3444
for index in model._meta.indexes:
3545
self.add_index(model, index)
@@ -56,6 +66,11 @@ def add_field(self, model, field):
5666
index = Index(fields=[field.name])
5767
index.name = self._create_index_name(model._meta.db_table, [field.column])
5868
self.add_index(model, index, field=field)
69+
if field.unique and field.column != "_id":
70+
constraint = UniqueConstraint(
71+
fields=[field.name], name=f"{model._meta.db_table}_{field.column}_key"
72+
)
73+
self.add_constraint(model, constraint, field=field)
5974

6075
def _alter_field(
6176
self,
@@ -148,9 +163,19 @@ def alter_index_together(self, model, old_index_together, new_index_together):
148163
def alter_unique_together(self, model, old_unique_together, new_unique_together):
149164
pass
150165

151-
def add_index(self, model, index, field=None):
166+
def add_index(self, model, index, field=None, unique=False):
152167
if index.contains_expressions:
153168
return
169+
kwargs = {}
170+
if unique:
171+
filter_expression = {}
172+
if field:
173+
filter_expression[field.column] = {"$type": field.db_type(self.connection)}
174+
else:
175+
for field_name, _ in index.fields_orders:
176+
field_ = model._meta.get_field(field_name)
177+
filter_expression[field_.column] = {"$type": field_.db_type(self.connection)}
178+
kwargs = {"partialFilterExpression": filter_expression, "unique": True}
154179
index_orders = (
155180
[(field.column, 1)]
156181
if field
@@ -162,6 +187,7 @@ def add_index(self, model, index, field=None):
162187
idx = IndexModel(
163188
index_orders,
164189
name=index.name,
190+
**kwargs,
165191
)
166192
self.connection.database[model._meta.db_table].create_indexes([idx])
167193

@@ -170,11 +196,27 @@ def remove_index(self, model, index):
170196
return
171197
self.connection.database[model._meta.db_table].drop_index(index.name)
172198

173-
def add_constraint(self, model, constraint):
174-
pass
199+
def add_constraint(self, model, constraint, field=None):
200+
if isinstance(constraint, UniqueConstraint) and self._unique_supported(
201+
condition=constraint.condition,
202+
deferrable=constraint.deferrable,
203+
include=constraint.include,
204+
expressions=constraint.expressions,
205+
nulls_distinct=constraint.nulls_distinct,
206+
):
207+
idx = Index(fields=constraint.fields, name=constraint.name)
208+
self.add_index(model, idx, field=field, unique=True)
175209

176210
def remove_constraint(self, model, constraint):
177-
pass
211+
if isinstance(constraint, UniqueConstraint) and self._unique_supported(
212+
condition=constraint.condition,
213+
deferrable=constraint.deferrable,
214+
include=constraint.include,
215+
expressions=constraint.expressions,
216+
nulls_distinct=constraint.nulls_distinct,
217+
):
218+
idx = Index(fields=constraint.fields, name=constraint.name)
219+
self.remove_index(model, idx)
178220

179221
def alter_db_table(self, model, old_db_table, new_db_table):
180222
if old_db_table == new_db_table:

0 commit comments

Comments
 (0)