Search code examples
pythondjangodjango-modelsdjango-signals

Model field value does not get updated m2m_changed(Django)


I have been searching for an answer for hours, however every answer I found does not work. Also trying to find a bug on my own didn't bring me any results.

I have created a receiver function which should update model's total_likes attribute(based on number of users_like attribute) every time user click on the like button of the specific image. (This is a part of 'Django by Example` book). But the field's value stays always the same and is equal to default value of 0. Even if I try to assign the value to the field manually, in the django's shell, it does not change(code example in 'Update' section).

Can someone please have a look at the code and point me in the right directions if I am doing something wrong?

I am using Django 1.9.

# models.py
class Image(models.Model):
    ...
    users_like = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                    related_name='images_liked',
                                    blank=True)
    total_likes = models.PositiveIntegerField(default=5)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
            super(Image, self).save(*args, **kwargs)

# signals.py
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from .models import Image

@receiver(m2m_changed, sender=Image.users_like.through)
def users_like_changed(sender, instance, **kwargs):
    instance.total_likes = instance.users_like.count()
    instance.save()

# apps.py
from django.apps import AppConfig

class ImagesConfig(AppConfig):
    name = 'images'
    verbose_name = 'Image bookmarks'

    def ready(self):
        # import signal handlers
        import images.signals

# __init__.py
default_app_config = 'images.apps.ImagesConfig'

Update:

When I run code below from django shell, this does change the total_likes value, but it looks like it do just temporary:

from images.models import Image
for image in Image.objects.all():
    print(image.total_likes)
    image.total_likes = image.users_like.count()
    print(image.total_likes)
    image.save()
    print(image.total_likes)

Output from code above:

0 #initial/default value of 0
3 #current number of users who like the picture
3

Because when I run the for loop code again, to see the results(or even check the field value in admin interface) i still get the initial/default value of 0.

Can someone see the problem why the field does not get updated?


Solution

  • Ok, so the problem was with the custom save() method on the model class.

    I needed to call the save() method of the parent class like this:

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        super(Image, self).save(*args, **kwargs)
    

    and it made it work.