28
28
from django .db .models .constants import LOOKUP_SEP
29
29
from django .db .models .constraints import CheckConstraint , UniqueConstraint
30
30
from django .db .models .deletion import CASCADE , Collector
31
+ from django .db .models .expressions import RawSQL
31
32
from django .db .models .fields .related import (
32
33
ForeignObjectRel ,
33
34
OneToOneField ,
@@ -1189,6 +1190,16 @@ def _get_next_or_previous_in_order(self, is_next):
1189
1190
setattr (self , cachename , obj )
1190
1191
return getattr (self , cachename )
1191
1192
1193
+ def _get_field_value_map (self , meta , exclude = None ):
1194
+ if exclude is None :
1195
+ exclude = set ()
1196
+ meta = meta or self ._meta
1197
+ return {
1198
+ field .name : Value (getattr (self , field .attname ), field )
1199
+ for field in meta .local_concrete_fields
1200
+ if field .name not in exclude
1201
+ }
1202
+
1192
1203
def prepare_database_save (self , field ):
1193
1204
if self .pk is None :
1194
1205
raise ValueError (
@@ -1221,7 +1232,7 @@ def validate_unique(self, exclude=None):
1221
1232
if errors :
1222
1233
raise ValidationError (errors )
1223
1234
1224
- def _get_unique_checks (self , exclude = None ):
1235
+ def _get_unique_checks (self , exclude = None , include_meta_constraints = False ):
1225
1236
"""
1226
1237
Return a list of checks to perform. Since validate_unique() could be
1227
1238
called from a ModelForm, some fields may have been excluded; we can't
@@ -1234,13 +1245,15 @@ def _get_unique_checks(self, exclude=None):
1234
1245
unique_checks = []
1235
1246
1236
1247
unique_togethers = [(self .__class__ , self ._meta .unique_together )]
1237
- constraints = [(self .__class__ , self ._meta .total_unique_constraints )]
1248
+ constraints = []
1249
+ if include_meta_constraints :
1250
+ constraints = [(self .__class__ , self ._meta .total_unique_constraints )]
1238
1251
for parent_class in self ._meta .get_parent_list ():
1239
1252
if parent_class ._meta .unique_together :
1240
1253
unique_togethers .append (
1241
1254
(parent_class , parent_class ._meta .unique_together )
1242
1255
)
1243
- if parent_class ._meta .total_unique_constraints :
1256
+ if include_meta_constraints and parent_class ._meta .total_unique_constraints :
1244
1257
constraints .append (
1245
1258
(parent_class , parent_class ._meta .total_unique_constraints )
1246
1259
)
@@ -1251,10 +1264,11 @@ def _get_unique_checks(self, exclude=None):
1251
1264
# Add the check if the field isn't excluded.
1252
1265
unique_checks .append ((model_class , tuple (check )))
1253
1266
1254
- for model_class , model_constraints in constraints :
1255
- for constraint in model_constraints :
1256
- if not any (name in exclude for name in constraint .fields ):
1257
- unique_checks .append ((model_class , constraint .fields ))
1267
+ if include_meta_constraints :
1268
+ for model_class , model_constraints in constraints :
1269
+ for constraint in model_constraints :
1270
+ if not any (name in exclude for name in constraint .fields ):
1271
+ unique_checks .append ((model_class , constraint .fields ))
1258
1272
1259
1273
# These are checks for the unique_for_<date/year/month>.
1260
1274
date_checks = []
@@ -1410,10 +1424,35 @@ def unique_error_message(self, model_class, unique_check):
1410
1424
params = params ,
1411
1425
)
1412
1426
1413
- def full_clean (self , exclude = None , validate_unique = True ):
1427
+ def get_constraints (self ):
1428
+ constraints = [(self .__class__ , self ._meta .constraints )]
1429
+ for parent_class in self ._meta .get_parent_list ():
1430
+ if parent_class ._meta .constraints :
1431
+ constraints .append ((parent_class , parent_class ._meta .constraints ))
1432
+ return constraints
1433
+
1434
+ def validate_constraints (self , exclude = None ):
1435
+ constraints = self .get_constraints ()
1436
+ using = router .db_for_write (self .__class__ , instance = self )
1437
+
1438
+ errors = {}
1439
+ for model_class , model_constraints in constraints :
1440
+ for constraint in model_constraints :
1441
+ try :
1442
+ constraint .validate (model_class , self , exclude = exclude , using = using )
1443
+ except ValidationError as e :
1444
+ if e .code == "unique" and len (constraint .fields ) == 1 :
1445
+ errors .setdefault (constraint .fields [0 ], []).append (e )
1446
+ else :
1447
+ errors = e .update_error_dict (errors )
1448
+ if errors :
1449
+ raise ValidationError (errors )
1450
+
1451
+ def full_clean (self , exclude = None , validate_unique = True , validate_constraints = True ):
1414
1452
"""
1415
- Call clean_fields(), clean(), and validate_unique() on the model.
1416
- Raise a ValidationError for any errors that occur.
1453
+ Call clean_fields(), clean(), validate_unique(), and
1454
+ validate_constraints() on the model. Raise a ValidationError for any
1455
+ errors that occur.
1417
1456
"""
1418
1457
errors = {}
1419
1458
if exclude is None :
@@ -1443,6 +1482,16 @@ def full_clean(self, exclude=None, validate_unique=True):
1443
1482
except ValidationError as e :
1444
1483
errors = e .update_error_dict (errors )
1445
1484
1485
+ # Run constraints checks, but only for fields that passed validation.
1486
+ if validate_constraints :
1487
+ for name in errors :
1488
+ if name != NON_FIELD_ERRORS and name not in exclude :
1489
+ exclude .add (name )
1490
+ try :
1491
+ self .validate_constraints (exclude = exclude )
1492
+ except ValidationError as e :
1493
+ errors = e .update_error_dict (errors )
1494
+
1446
1495
if errors :
1447
1496
raise ValidationError (errors )
1448
1497
@@ -2339,8 +2388,28 @@ def _check_constraints(cls, databases):
2339
2388
connection .features .supports_table_check_constraints
2340
2389
or "supports_table_check_constraints"
2341
2390
not in cls ._meta .required_db_features
2342
- ) and isinstance (constraint .check , Q ):
2343
- references .update (cls ._get_expr_references (constraint .check ))
2391
+ ):
2392
+ if isinstance (constraint .check , Q ):
2393
+ references .update (
2394
+ cls ._get_expr_references (constraint .check )
2395
+ )
2396
+ if any (
2397
+ isinstance (expr , RawSQL )
2398
+ for expr in constraint .check .flatten ()
2399
+ ):
2400
+ errors .append (
2401
+ checks .Warning (
2402
+ f"Check constraint { constraint .name !r} contains "
2403
+ f"RawSQL() expression and won't be validated "
2404
+ f"during the model full_clean()." ,
2405
+ hint = (
2406
+ "Silence this warning if you don't care about "
2407
+ "it."
2408
+ ),
2409
+ obj = cls ,
2410
+ id = "models.W045" ,
2411
+ ),
2412
+ )
2344
2413
for field_name , * lookups in references :
2345
2414
# pk is an alias that won't be found by opts.get_field.
2346
2415
if field_name != "pk" :
0 commit comments