7

I've got a Django model like this:

class Process(Place):
    isRunning = models.BooleanField(default=True)
    name      = models.CharField(max_length=20)

I'd like to enforce that the name field is unique when isRunning is true.

Is this constraint possible in in Django models?


This might be a duplicate of this question, but it doesn't have an accepted answer, and Django has developed a lot since it was asked.
Community
  • 1
  • 1
ajwood
  • 18,227
  • 15
  • 61
  • 104
  • you can specify [`unique_together` fields](https://docs.djangoproject.com/en/1.8/ref/models/options/#unique-together) but that will also enforce that `name` is unique among records where `isRunning` is `false` (i.e. you have two groups of unique names) ...otherwise you can do custom [model validation](https://docs.djangoproject.com/en/1.8/ref/models/instances/#validating-objects) though bear in mind it is only called automatically when saving a ModelForm and in Django admin, not when doing `instance.save()` – Anentropic Aug 20 '15 at 17:15

3 Answers3

25

If your database supports it you could set up a partial unique index.

A partial index is an index built over a subset of a table; the subset is defined by a conditional expression (called the predicate of the partial index). The index contains entries only for those table rows that satisfy the predicate.

Below version 2.2 there's no special Django support for this, but you can set it up in a data migration (see here for more details).

In your case it would look something like:

operations = [
    migrations.RunSQL("CREATE UNIQUE INDEX running_name ON app_process(isRunning, name)
                       WHERE isRunning"),
]

Starting with version 2.2 you can simply declare the partial unique index in your model:

from django.db.models import Q, UniqueConstraint

class Process(Place):
    ...
    class Meta:
        constraints = [UniqueConstraint(fields=["name"], condition=Q(isRunning=True))]
Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
1

A unique field implies that a unique index is build in database for that field.

Now since your field is dependent on a field from the same model you have an option to validate this when your model is being saved.

You can override model save() method like:

def save(self, *args, **kwargs):
    #check if isRunning is true or not    
    super(Model, self).save(*args, **kwargs)
Ajay Gupta
  • 1,285
  • 8
  • 22
  • 1
    This is nice and simple. I'd rather keep my nose out of the SQL, so I'll go this route. – ajwood Aug 21 '15 at 15:33
  • 5
    @ajwood: Just be aware that if you rely on overriding `save()` you'll be limited to those methods that use it. In particular, you won't be able to use `.update()` and `.bulk_create()`. The advantage of having the constraint in the database is that you know it will be enforced no matter what. – Kevin Christopher Henry Aug 21 '15 at 19:46
  • 3
    This should not be the accepterad answer on this as it isn't enforcing on the DB level (see the higher voted answer below). It might seem like a good idea at the start to override various methods like this, but it will hurt at some point, so if there is another way to solve it it's usually better :) – BjornW Jan 24 '20 at 09:43
0

You could add an extra field such as stopped_at to record the time a Process is killed.

And create an unique constraint:

unique_together = ('name', 'stopped_at')

stopped_at could be set to None or some constant value for new processes.

This constraint will make sure that the names are unique among running processes. And stopped processes could have same names as their stopped_at are always different (hopefully or you could add other information to this tricky field to guarantee uniqueness for stopped processes).

Lewis Z
  • 498
  • 7
  • 16