1

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?

UMR
  • 310
  • 4
  • 16

1 Answers1

1

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.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • After adding your code and run makemigrations and migrate again, the error `OperationalError: no such table: network_follow` keep showing up. – UMR Feb 24 '21 at 07:31
  • Migrations for 'network': network\migrations\0001_initial.py - Create model User - Create model Post - Create model Follow - Add field followers to user - Add field groups to user - Add field user_permissions to user - Create constraint follow_once on model follow - Create constraint not_follow_self on model follow – UMR Feb 24 '21 at 07:40
  • @NguyễnThếQuý: did you run the migrations on the database? – Willem Van Onsem Feb 24 '21 at 07:52
  • You mean `python manage.py migrate --run-syncdb` ? – UMR Feb 24 '21 at 08:02
  • Migrations and migrate does't rise any error, it only happens when I use `objects.add()` method – UMR Feb 24 '21 at 08:19
  • @NguyễnThếQuý: but the error indicates it did not construct the `Follow` model. Can you construct the migrations and tables from scratch, that will likely be the easiest way to do this. – Willem Van Onsem Feb 24 '21 at 08:24
  • 1
    needed to construct the table again to make it work – UMR Feb 24 '21 at 15:20