Reputation: 1171
In Django 2.2, you can get rid of unique_together
in favor of a constraints
list with a UniqueConstraint
class that can be handy because it has a condition
arg.
MyObject has three foreign keys, one is optional.
class MyObject(ModelBase):
parent=... # mandatory
source=... # mandatory
subsource=... # optional
class Meta:
constraints = [
models.UniqueConstraint(
fields=["parent", "source", "subsource"],
name="unique_subsource"
),
models.UniqueConstraint(
fields=["parent", "source"],
condition=models.Q(subsource=None),
name="unique_source",
),
]
Imagine I try to create successively the following objects and the expected validation:
parent source subsource valid?
1. 1. 1. yes
1. 1. 2. yes
1. 1. - yes
1. 1. - no
1. 1. 3. yes
1. 2. - yes
...
So I wrote two tests:
def test_unique1(self):
""" unique parent/source/subsource """
parent = ParentFactory()
source = SourceFactory()
subsource = SubsourceFactory()
MyObjectFactory(parent=parent, source=source, subsource=subsource)
myobj = MyObjectFactory.build(parent=parent, source=source, subsource=subsource)
self.should_raise_validation_error(myobj)
def test_unique2(self):
""" unique parent/source """
parent = ParentFactory()
source = SourceFactory()
subsource = SubsourceFactory()
MyObjectFactory(parent=parent, source=source)
myobj = MyObjectFactory.build(parent=parent, source=source)
self.should_raise_validation_error(myobj)
But the latter does not raise any validation error:
.....F.
======================================================================
FAIL: test_unique2
unique parent/source/subsource
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/sebastiennicolaidis/dev/python/scientcap/macrovars/tests/models/test_dsflink.py", line 50, in test_unique2
self.should_raise_validation_error(dsflink)
File "/Users/sebastiennicolaidis/dev/python/scientcap/shared/tests/base_model_test.py", line 9, in should_raise_validation_error
instance.full_clean()
AssertionError: ValidationError not raised
----------------------------------------------------------------------
Ran 7 tests in 0.141s
BEGIN;
--
-- Change Meta options on dataset
--
--
-- Change Meta options on dsforecast
--
--
-- Alter unique_together for dsflink (0 constraint(s))
--
ALTER TABLE "macrovars_dsflink" DROP CONSTRAINT "macrovars_dsflink_parent_id_source_id_73901780_uniq";
--
-- Create constraint unique_subsource on model dsflink
--
CREATE UNIQUE INDEX "unique_subsource" ON "macrovars_dsflink" ("parent_id", "source_id", "subsource_id") WHERE "subsource_id" IS NOT NULL;
--
-- Create constraint unique_source on model dsflink
--
CREATE UNIQUE INDEX "unique_source" ON "macrovars_dsflink" ("parent_id", "source_id") WHERE "subsource_id" IS NULL;
COMMIT;
Upvotes: 0
Views: 98
Reputation: 2130
I think the solution would be to create both the constraint with condition.
class MyObject(ModelBase):
parent=... # mandatory
source=... # mandatory
subsource=... # optional
class Meta:
constraints = [
models.UniqueConstraint(
fields=["parent", "source", "subsource"],
condition=models.Q(subsource__isnull=False), # <- filter subsource is null
name="unique_subsource"
),
models.UniqueConstraint(
fields=["parent", "source"],
condition=models.Q(subsource__isnull=True),
name="unique_source",
),
]
Upvotes: 2