#37057: UniqueConstraint incorrectly raises ValidationError on nullable fields
in
condition
-------------------------------------+-------------------------------------
Reporter: Drews | Type: Bug
Status: new | Component: Database
| layer (models, ORM)
Version: 6.0 | Severity: Normal
Keywords: UniqueConstraint | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
== Summary
When a `UniqueConstraint` has a `condition` that references a nullable
field (e.g., `condition=Q(cash_register_type=10)` on a nullable
`PositiveSmallIntegerField`), Django's `UniqueConstraint.validate()`
incorrectly reports a constraint violation if the instance being saved has
`cash_register_type=None` and another record matching the condition
already exists.
''Tested in Django==5.2.4 (with Postgres 17) and 6.0.4 (with Postgres
18)''
== Steps to Reproduce
{{{
from django.db import models
class Location(models.Model):
name = models.CharField(max_length=100)
class Device(models.Model):
class CashRegisterType(models.IntegerChoices):
MASTER = 10, "Master"
SLAVE = 20, "Slave"
name = models.CharField(max_length=100)
pos_location = models.ForeignKey(Location, on_delete=models.CASCADE)
cash_register_type = models.PositiveSmallIntegerField(
choices=CashRegisterType,
blank=True, null=True, default=None,
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["pos_location"],
condition=models.Q(cash_register_type=10),
name="unique_master_cash_register",
violation_error_message="Only one Master per
POSLocation.",
)
]
}}}
Create one Device with `cash_register_type` Master and creating another
one for the same Location with `cash_register_type=None` will raise
ValidationError
{{{
from myapp.models import Location, Device
loc = Location.objects.create(name="Store 1")
# 1. Create a Master -- works
master = Device.objects.create(
name="CashRegister A", pos_location=loc, cash_register_type=10
)
# 2. Create a second Device with cash_register_type=None
# while a Master already exists -- BUG
register_b = Device.objects.create(
name="Device B", pos_location=loc, cash_register_type=None
)
}}}
== Workaround
Add an explicit `isnull=False` check to the condition:
`condition=models.Q(cash_register_type=10) &
models.Q(cash_register_type__isnull=False)`
Although `10` should already be `isnull=False`.
--
Ticket URL: <https://code.djangoproject.com/ticket/37057>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/django-updates/0107019db4d0e696-d3c042ff-4afa-4cc3-838f-19b78656fd69-000000%40eu-central-1.amazonses.com.