11from django .db .backends .base .schema import BaseDatabaseSchemaEditor
2- from django .db .models import Index
2+ from django .db .models import Index , UniqueConstraint
33from pymongo .operations import IndexModel
44
55from .query import wrap_database_errors
@@ -17,18 +17,23 @@ def create_model(self, model):
1717
1818 def _create_model_indexes (self , model ):
1919 """
20- Create all indexes (field indexes, index_together, Meta.indexes) for
21- the specified model.
20+ Create all indexes (field indexes & uniques , index_together,
21+ Meta.constraints, Meta.indexes) for the specified model.
2222 """
2323 if not model ._meta .managed or model ._meta .proxy or model ._meta .swapped :
2424 return
25- # Field indexes
25+ # Field indexes and uniques
2626 for field in model ._meta .local_fields :
2727 if self ._field_should_be_indexed (model , field ):
2828 self ._add_field_index (model , field )
29+ elif self ._field_should_have_unique (field ):
30+ self ._add_field_unique (model , field )
2931 # Meta.index_together (RemovedInDjango51Warning)
3032 for field_names in model ._meta .index_together :
3133 self ._add_composed_index (model , field_names )
34+ # Meta.constraints
35+ for constraint in model ._meta .constraints :
36+ self .add_constraint (model , constraint )
3237 # Meta.indexes
3338 for index in model ._meta .indexes :
3439 self .add_index (model , index )
@@ -50,9 +55,11 @@ def add_field(self, model, field):
5055 self .connection .database [model ._meta .db_table ].update_many (
5156 {}, [{"$set" : {column : self .effective_default (field )}}]
5257 )
53- # Add an index, if required.
58+ # Add an index or unique , if required.
5459 if self ._field_should_be_indexed (model , field ):
5560 self ._add_field_index (model , field )
61+ elif self ._field_should_have_unique (field ):
62+ self ._add_field_unique (model , field )
5663
5764 def _alter_field (
5865 self ,
@@ -102,6 +109,8 @@ def remove_field(self, model, field):
102109 self .connection .database [model ._meta .db_table ].update_many ({}, {"$unset" : {column : "" }})
103110 if self ._field_should_be_indexed (model , field ):
104111 self ._remove_field_index (model , field )
112+ elif self ._field_should_have_unique (field ):
113+ self ._remove_field_unique (model , field )
105114
106115 def alter_index_together (self , model , old_index_together , new_index_together ):
107116 olds = {tuple (fields ) for fields in old_index_together }
@@ -116,9 +125,19 @@ def alter_index_together(self, model, old_index_together, new_index_together):
116125 def alter_unique_together (self , model , old_unique_together , new_unique_together ):
117126 pass
118127
119- def add_index (self , model , index , field = None ):
128+ def add_index (self , model , index , field = None , unique = False ):
120129 if index .contains_expressions :
121130 return
131+ kwargs = {}
132+ if unique :
133+ filter_expression = {}
134+ if field :
135+ filter_expression [field .column ] = {"$type" : field .db_type (self .connection )}
136+ else :
137+ for field_name , _ in index .fields_orders :
138+ field_ = model ._meta .get_field (field_name )
139+ filter_expression [field_ .column ] = {"$type" : field_ .db_type (self .connection )}
140+ kwargs = {"partialFilterExpression" : filter_expression , "unique" : True }
122141 index_orders = (
123142 [(field .column , 1 )]
124143 if field
@@ -130,6 +149,7 @@ def add_index(self, model, index, field=None):
130149 idx = IndexModel (
131150 index_orders ,
132151 name = index .name ,
152+ ** kwargs ,
133153 )
134154 self .connection .database [model ._meta .db_table ].create_indexes ([idx ])
135155
@@ -196,13 +216,56 @@ def _remove_field_index(self, model, field):
196216 # is to look at its name (refs #28053).
197217 collection .drop_index (index_name )
198218
199- def add_constraint (self , model , constraint ):
200- pass
219+ def add_constraint (self , model , constraint , field = None ):
220+ if isinstance (constraint , UniqueConstraint ) and self ._unique_supported (
221+ condition = constraint .condition ,
222+ deferrable = constraint .deferrable ,
223+ include = constraint .include ,
224+ expressions = constraint .expressions ,
225+ nulls_distinct = constraint .nulls_distinct ,
226+ ):
227+ idx = Index (fields = constraint .fields , name = constraint .name )
228+ self .add_index (model , idx , field = field , unique = True )
229+
230+ def _add_field_unique (self , model , field ):
231+ constraint = UniqueConstraint (
232+ fields = [field .name ], name = f"{ model ._meta .db_table } _{ field .column } _key"
233+ )
234+ self .add_constraint (model , constraint , field = field )
201235
202236 def remove_constraint (self , model , constraint ):
203- pass
237+ if isinstance (constraint , UniqueConstraint ) and self ._unique_supported (
238+ condition = constraint .condition ,
239+ deferrable = constraint .deferrable ,
240+ include = constraint .include ,
241+ expressions = constraint .expressions ,
242+ nulls_distinct = constraint .nulls_distinct ,
243+ ):
244+ idx = Index (fields = constraint .fields , name = constraint .name )
245+ self .remove_index (model , idx )
246+
247+ def _remove_field_unique (self , model , field , strict = True ):
248+ # Find the unique constraint for this field
249+ meta_constraint_names = {constraint .name for constraint in model ._meta .constraints }
250+ constraint_names = self ._constraint_names (
251+ model ,
252+ [field .column ],
253+ unique = True ,
254+ primary_key = False ,
255+ exclude = meta_constraint_names ,
256+ )
257+ if strict and len (constraint_names ) != 1 :
258+ raise ValueError (
259+ f"Found wrong number ({ len (constraint_names )} ) of unique "
260+ f"constraints for { model ._meta .db_table } .{ field .column } "
261+ )
262+ for constraint_name in constraint_names :
263+ self .connection .database [model ._meta .db_table ].drop_index (constraint_name )
204264
205265 def alter_db_table (self , model , old_db_table , new_db_table ):
206266 if old_db_table == new_db_table :
207267 return
208268 self .connection .database [old_db_table ].rename (new_db_table )
269+
270+ def _field_should_have_unique (self , field ):
271+ return field .unique and field .column != "_id"
0 commit comments