diff options
Diffstat (limited to 'django/db/models')
| -rw-r--r-- | django/db/models/base.py | 23 | ||||
| -rw-r--r-- | django/db/models/constraints.py | 29 |
2 files changed, 48 insertions, 4 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py index 0711ec0d61..3e9b847b37 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -2442,6 +2442,29 @@ class Model(AltersData, metaclass=ModelBase): id="models.W044", ) ) + if not ( + connection.features.supports_nulls_distinct_unique_constraints + or ( + "supports_nulls_distinct_unique_constraints" + in cls._meta.required_db_features + ) + ) and any( + isinstance(constraint, UniqueConstraint) + and constraint.nulls_distinct is not None + for constraint in cls._meta.constraints + ): + errors.append( + checks.Warning( + "%s does not support unique constraints with " + "nulls distinct." % connection.display_name, + hint=( + "A constraint won't be created. Silence this " + "warning if you don't care about it." + ), + obj=cls, + id="models.W047", + ) + ) fields = set( chain.from_iterable( (*constraint.fields, *constraint.include) diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 0df0782b6f..e5136f89f5 100644 --- a/django/db/models/constraints.py +++ b/django/db/models/constraints.py @@ -186,6 +186,7 @@ class UniqueConstraint(BaseConstraint): deferrable=None, include=None, opclasses=(), + nulls_distinct=None, violation_error_code=None, violation_error_message=None, ): @@ -223,6 +224,8 @@ class UniqueConstraint(BaseConstraint): raise ValueError("UniqueConstraint.include must be a list or tuple.") if not isinstance(opclasses, (list, tuple)): raise ValueError("UniqueConstraint.opclasses must be a list or tuple.") + if not isinstance(nulls_distinct, (NoneType, bool)): + raise ValueError("UniqueConstraint.nulls_distinct must be a bool.") if opclasses and len(fields) != len(opclasses): raise ValueError( "UniqueConstraint.fields and UniqueConstraint.opclasses must " @@ -233,6 +236,7 @@ class UniqueConstraint(BaseConstraint): self.deferrable = deferrable self.include = tuple(include) if include else () self.opclasses = opclasses + self.nulls_distinct = nulls_distinct self.expressions = tuple( F(expression) if isinstance(expression, str) else expression for expression in expressions @@ -284,6 +288,7 @@ class UniqueConstraint(BaseConstraint): include=include, opclasses=self.opclasses, expressions=expressions, + nulls_distinct=self.nulls_distinct, ) def create_sql(self, model, schema_editor): @@ -302,6 +307,7 @@ class UniqueConstraint(BaseConstraint): include=include, opclasses=self.opclasses, expressions=expressions, + nulls_distinct=self.nulls_distinct, ) def remove_sql(self, model, schema_editor): @@ -318,10 +324,11 @@ class UniqueConstraint(BaseConstraint): include=include, opclasses=self.opclasses, expressions=expressions, + nulls_distinct=self.nulls_distinct, ) def __repr__(self): - return "<%s:%s%s%s%s%s%s%s%s%s>" % ( + return "<%s:%s%s%s%s%s%s%s%s%s%s>" % ( self.__class__.__qualname__, "" if not self.fields else " fields=%s" % repr(self.fields), "" if not self.expressions else " expressions=%s" % repr(self.expressions), @@ -332,6 +339,11 @@ class UniqueConstraint(BaseConstraint): "" if not self.opclasses else " opclasses=%s" % repr(self.opclasses), ( "" + if self.nulls_distinct is None + else " nulls_distinct=%r" % self.nulls_distinct + ), + ( + "" if self.violation_error_code is None else " violation_error_code=%r" % self.violation_error_code ), @@ -353,6 +365,7 @@ class UniqueConstraint(BaseConstraint): and self.include == other.include and self.opclasses == other.opclasses and self.expressions == other.expressions + and self.nulls_distinct is other.nulls_distinct and self.violation_error_code == other.violation_error_code and self.violation_error_message == other.violation_error_message ) @@ -370,6 +383,8 @@ class UniqueConstraint(BaseConstraint): kwargs["include"] = self.include if self.opclasses: kwargs["opclasses"] = self.opclasses + if self.nulls_distinct is not None: + kwargs["nulls_distinct"] = self.nulls_distinct return path, self.expressions, kwargs def validate(self, model, instance, exclude=None, using=DEFAULT_DB_ALIAS): @@ -381,9 +396,15 @@ class UniqueConstraint(BaseConstraint): return field = model._meta.get_field(field_name) lookup_value = getattr(instance, field.attname) - if lookup_value is None or ( - lookup_value == "" - and connections[using].features.interprets_empty_strings_as_nulls + if ( + self.nulls_distinct is not False + and lookup_value is None + or ( + lookup_value == "" + and connections[ + using + ].features.interprets_empty_strings_as_nulls + ) ): # A composite constraint containing NULL value cannot cause # a violation since NULL != NULL in SQL. |
