How to change the behaviour of unique true in django model?

Question:

Here I am not deleting the model objects from database. I am just changing the is_deleted status to True while delete. But while doing this unique=True gives error for the deleted objects so how can I handle this ?

I want to exclude is_deleted=True objects from unique True.

class MyModel(models.Model):
    name = models.CharField(max_length=20, unique=True)
    is_deleted = models.BooleanField(default=False)


#views
class MyViewSet(ModelViewSet):

    serializer_class = MySerializer
    queryset = MyModel.objects.all()

    def destroy(self, request, *args, **kwargs):
        object = self.get_object()
        object.is_deleted = True
        object.save()
        return Response(status=status.HTTP_204_NO_CONTENT)
Asked By: D_P

||

Answers:

You can use a UniqueConstraint [Django docs] with a condition instead of the unique kwarg on the field. Although there is a caveat that validation (by forms etc.) will not be done automatically for a unique constraint with a condition and you will need to do that yourself.

from django.db.models import Q


class MyModel(models.Model):
    name = models.CharField(max_length=20)
    is_deleted = models.BooleanField(default=False)
    
    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['name'], condition=Q(is_deleted=False), name='unique_undeleted_name')
        ]

Note: Since Django 4.1 validation is automatically performed for all constraints using the Model.validate_constraints method and hence the above mentioned caveat doesn’t apply.

Answered By: Abdul Aziz Barkat

Since , you can work with Django’s constraint API, you can then specify a UniqueConstraint [Django-doc] that has a condition:

class MyModel(models.Model):
    #            no unique=True ↓
    name = models.CharField(max_length=20)
    is_deleted = models.BooleanField(default=False)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['name'],
                name='unique_name_not_deleted',
                condition=Q(is_deleted=False)
            )
        ]

It is of course the database that enforces this, and thus some databases might not have implemented that feature.

Answered By: Willem Van Onsem

You may use following also:

class Meta:
       unique_together = ("name", "is_deleted")
Answered By: Dips