django-modelsm2m

Prevent an entry in model from adding itself in many2many relationship


I want to prevent a user from following themselves in Django. The User model is defined like bellow:

class User(AbstractUser):
    followers = models.ManyToManyField(‘self’, related_name=“following”, symmetrical= False)

Changing save() method in the User model doesn’t help since add() method doesn’t call save(). The seems to be a solution using m2m_changed or UniqueConstraint but I don’t know how to implement them. How can I solve this problem?


Solution

  • A UniqueConstraint will not help since this will only enforce that you do not follow the same user twice.

    What you can do is construct a through=… model [Django-doc], and enforce that the follower and the followee are not the same:

    from django.conf import settings
    from django.core.exceptions import ValidationError
    from django.db import models
    from django.db.models import F, Q
    
    class User(AbstractUser):
        # …
        followers = models.ManyToManyField(
            'self',
            related_name='following',
            symmetrical=False,
            through='Follow',
            through_fields=('followee', 'follower'),
        )

    In the Follow model we then enforce that follower and followee can not be the same:

    class Follow(models.Model):
        followee = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE,
            related_name='followee_set'
        )
        follower = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE,
            related_name='following_set'
        )
    
        def clean(self, *args, **kwargs):
            if self.follower_id == self.followee_id:
                raise ValidationError('Can not follow self.')
            return super().clean(*args, **kwargs)
        
        class Meta:
            constraints = [
                models.UniqueConstraint(fields=['follower', 'followee'], name='follow_once'),
                models.CheckConstraint(check=~Q(follower=F('followee')), name='not_follow_self')
            ]

    Not all databases however enforce the CheckConstraint. Therefore it might be better to implement a clean method as well.