I have a model with a non-nullable CharField
and 2 x nullable CharField
:
class MyModel(models.Model):
name = models.CharField('Name', max_length=255, null=False)
title = models.CharField('Title', max_length=255, blank=True)
position = models.CharField('Position', max_length=255, blank=True)
I want to ensure that name
, title
, and position
are unique together, and so use a UniqueConstraint
:
def Meta:
constraints = [
models.UniqueConstraint(
fields=['name', 'title', 'position'],
name="unique_name_title_position"
),
]
However, if title
is None
then this constraint fails.
Looking into why, this is because you can insert NULL
values into columns with the UNIQUE
constraint because NULL
is the absence of a value, so it is never equal to other NULL
values and not considered a duplicate value. This means that it's possible to insert rows that appear to be duplicates if one of the values is NULL
.
What's the correct way to strictly enforce this uniqueness in Django?
CodePudding user response:
UniqueConstraint
are not enforced on Django level but rather directly on database.
PostgreSQL for instance allows multiple Null
records for unique while MSSQL does not
You can rather check if for each subset of columns with CheckConstraint
but as you will find out this is tedious as it requires each combination to be uniquely indexed.
Rather to avoid even whole this mess you can follow Django guide for CharField
as documented
Avoid using null on string-based fields such as CharField and TextField. If a string-based field has null=True, that means it has two possible values for “no data”: NULL, and the empty string. In most cases, it’s redundant to have two possible values for “no data;” the Django convention is to use the empty string, not NULL. One exception is when a CharField has both unique=True and blank=True set. In this situation, null=True is required to avoid unique constraint violations when saving multiple objects with blank values.
Empty string is checked for uniqueness
CodePudding user response:
You could pick values that don't ever occur for title
and position
and use an index like this:
CREATE UNIQUE INDEX ON mytable (
name,
coalesce(title, '#impossible#'),
coalesce(position, '#impossible#'),
);
That will replace the NULL values with something else, so that duplicates are prevented.