Reputation: 111
I am creating an abstract model for my django app, SrdObject
. One of the characteristics of my model is that it has a pair of fields that, taken together, must be unique: 'name' and the foreign key 'module'.
Here is an example of what I had
class SrdObject(models.Model):
name = models.CharField(max_length=50)
slug_name = models.SlugField(max_length=75, unique=True)
active = models.BooleanField(default=True)
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='%(class)s', blank=False, null=False, default='default')
class Meta:
unique_together = ['name', 'module']
ordering = ['name']
abstract = True
This seemed to be working ok, but the unique_together
attribute has been marked as deprecated by django (See here), so I changed it to this
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', 'module'], name='unique-in-module')
]
ordering = ['name']
abstract = True
This doesn't work because the name field must be unique, and since this is an abstract class, the constraint is repeated over several models.
I also tried
models.UniqueConstraint(fields=['name', 'module'], name='{}-unique-in-module'.format(model_name))
But obviously this falls into scoping problems, so I tried a decorator method
def add_unique_in_module_constraint(cls):
cls._meta.constraints = [
models.UniqueConstraint(fields=['name', 'module'], name='unique-in-module')
]
return cls
@add_unique_in_module_constraint
class SrdObject(models.Model):
class Meta:
ordering = ['name']
abstract = True
But this didn't seem to do anything.
So how do I create a models.UniqueConstraint in abstract model if every constraint needs a unique name attribute?
Upvotes: 11
Views: 4091
Reputation: 483
LATEST UPDATE
Since 3rd version you finally can do that by specifying interpolation:
Changed in Django 3.0:
Interpolation of '%(app_label)s' and '%(class)s' was added.
Example:
UniqueConstraint(fields=['room', 'date'], name='%(app_label)s_unique_booking')
OLD (Django < 3.0)
You can't do that, same problem for me, so sad...
Source: django docs
Constraints in abstract base classes
You must always specify a unique name for the constraint. As such, you cannot normally specify a constraint on an abstract base class, since the Meta.constraints option is inherited by subclasses, with exactly the same values for the attributes (including name) each time. Instead, specify the constraints option on subclasses directly, providing a unique name for each constraint.
Upvotes: 12
Reputation: 653
I took this model setup:
class Module(models.Model):
pass
class SrdObject(models.Model):
name = models.CharField(max_length=50)
slug_name = models.SlugField(max_length=75, unique=True)
active = models.BooleanField(default=True)
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='%(class)s', blank=False, null=False, default='default')
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', 'module'], name='unique-in-module')
]
ordering = ['name']
abstract = True
class SrdObjectA(SrdObject):
pass
class SrdObjectB(SrdObject):
pass
And then ran these tests:
class TestSrdObject(TestCase):
@classmethod
def setUpTestData(cls):
cls.module = Module.objects.create()
SrdObjectA.objects.create(name='A', module=cls.module)
def test_unique_applies_to_same_model(self):
with self.assertRaises(IntegrityError):
SrdObjectA.objects.create(name='A', module=self.module)
def test_unique_does_not_apply_to_different_model(self):
self.assertTrue(SrdObjectB.objects.create(name='A', module=self.module))
And they pass. Perhaps I'm still missing the problem you're running into?
Upvotes: 0