0

I have a signal that looks like this:

@receiver([post_save, post_delete], sender=Following)
def increment_follow_count(instance, created=False, **kwargs):
    if created:
        instance.follower.following_count += 1
        instance.target.follower_count += 1
    else:
        instance.follower.following_count -= 1
        instance.target.follower_count -= 1

When a user follows another user, it works correctly. However, when that same user unfollows that user, only the person that the user followed (target) has their follower count decremented, but the user's following count is not decremented. Why is this behavior happening and how can I fix it?

Model:

class Following(models.Model):
    target = models.ForeignKey('User', related_name='followers', on_delete=models.CASCADE, null=True)
    follower = models.ForeignKey('User', related_name='targets', on_delete=models.CASCADE, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return '{} is followed by {}'.format(self.target, self.follower)

Code to follow/unfollow user

def follow_unfollow(follower, target):
    # Query to see if the following exists or not
    following = target.followers.filter(follower=follower)

    if following.exists():
        following.delete()
    else:
        target.followers.create(follower=follower)

    target.save()
    follower.save()
user2896120
  • 3,180
  • 4
  • 40
  • 100

1 Answers1

0

increment_follow_count signal contains increment logic but no save logic and saving is done is another follow_unfollow method?


First, increment is better to be atomic, to make sure no changes are lost.

Atomic increment can be achieved using F() expressions.

from django.db.models import F

@receiver([post_save, post_delete], sender=Following)
def increment_follow_count(instance, created=False, **kwargs):
    if created:
        User.objects.filter(
            pk=instance.follower_id
        ).update(
            following_count=F('following_count') + 1
        )
        User.objects.filter(
            pk=instance.target_id
        ).update(
            following_count=F('following_count') + 1
        )
    else:
        User.objects.filter(
            pk=instance.follower_id
        ).update(
            following_count=F('following_count') - 1
        )
        User.objects.filter(
            pk=instance.target_id
        ).update(
            following_count=F('following_count') - 1
        )

Here increment is not only atomic, but changes are immediately saved in the database in the same method.

Also, I would suggest to remove target.save() and follower.save() in follow_unfollow - as it overwrites instance in database with the values in memory, and this should not be the case, at least for following_count as its increment logic is in signal. If in follow_unfollow method some changes to fields, other than followng_count are done - then save() should be called with update_fields list to update only changed fields.


Regular += 1 takes current in-memory instance field value, i.e. count=5, increment (count=6), and later, when save is called, it is being saved as update count=6. And during this time value might have already changed in database many times (and update will set it to 6 regardless), especially under load / simultaneous actions.

With atomic increment logic is moved from python to database - and will increment actual value at the time transaction is made.

Oleg Russkin
  • 4,234
  • 1
  • 8
  • 20
  • I'm getting this error: `in increment_follow_count following_count=F('following_count') - 1 TypeError: 'int' object is not callable` – user2896120 Dec 25 '19 at 21:59
  • In a queryset (User.objects.filter().update(f_c=F('f_c')-1))? Seems like field is called / modified directly on instance, not in queryset. – Oleg Russkin Dec 25 '19 at 22:02
  • Ah it was an import issue, no errors no but this doesn't seem to update the following_count for some reason – user2896120 Dec 25 '19 at 22:09
  • Is `target.save(); follower.save()` removed from `follow_unfollow`? – Oleg Russkin Dec 26 '19 at 06:24
  • Is `target.save()` / `follower.save()` in other places while follow / unfollow action? Log of database SQL queries when user clicks follow / unfollow button would be helpful. – Oleg Russkin Dec 26 '19 at 09:01