summaryrefslogtreecommitdiff
path: root/django/db/models
diff options
context:
space:
mode:
Diffstat (limited to 'django/db/models')
-rw-r--r--django/db/models/base.py23
-rw-r--r--django/db/models/constraints.py29
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.