简体   繁体   中英

Adding If statement to signals not working properly

I have set a Notification system for my Django project whenever a user likes or comments on a post the Author receives a notification about this activity.

I have read about the Signals in django: https://docs.djangoproject.com/en/3.1/topics/signals/#listening-to-signals

In the Post Model, I have added a num_likes which reflects the number of likes the post has received.

I am trying to add an option so that the Author of the Post can receive a Notification when the num_likes reaches a certain number. In my example, it is the First Like.

So, here is what I have tried but nothing happened.

Here is the models.py

class Post(models.Model):
    title = models.CharField(max_length=100, unique=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author')
    num_likes = models.IntegerField(default=0, verbose_name='No. of Likes')
    likes = models.ManyToManyField(User, related_name='liked', blank=True)

    def __str__(self):
        return self.title
#---------------------------My trial------------------------------------------
    def like_progress(sender, instance, *args, **kwargs):
        post = instance
        if post.num_likes == 1:
            notify = Notification(post=post, user=post.author, notification_type=3)
            notify.save()

# num_likes
post_save.connect(Post.like_progress, sender=Post)
#---------------------------My trial------------------------------------------

Here are the notifications model.py

class Notification(models.Model):
    NOTIFICATION_TYPES=((1,'Like'),(2,'Comment'),(3,'Admin'))

    post = models.ForeignKey('blog.Post', on_delete=models.CASCADE, related_name="noti_post", blank=True, null=True)
    sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="noti_from_user")
    user = models.ForeignKey(User, on_delete=models.CASCADE,related_name="noti_to_user")
    notification_type= models.IntegerField(choices=NOTIFICATION_TYPES)
    text_preview= models.CharField(max_length=90,blank=True)
    date=models.DateTimeField(auto_now=True)
    is_seen=models.BooleanField(default=False)

    def __str__(self):
        return self.notification_type

here are the notifications app views.py

def ShowNotifications(request, *args, **kwargs):
    user=request.user
    notifications= Notification.objects.filter(user=user).order_by('-date')
    Notification.objects.filter(user=user, is_seen=False).update(is_seen=True)

    template= loader.get_template('notifications/notifications.html')

    context = {
        'notifications': notifications,
    }

    return HttpResponse(template.render(context, request))

here is the url.py

app_name = 'notifications'

urlpatterns = [
    path('', ShowNotifications, name='show-notifications'),

here is the template:

<!-- Admin Notification -->
{% if notification.notification_type == 3 %}
Your First Like in this post
{% endif %}
<!-- Admin Notification -->

So to summarize:

I have a perfectly working Notification system I am just trying to add to it an option to notify the Author of a Post that he received a first like for example. What am I doing wrong in the above and how can I get this feature to work?

If there is anything vague or more information required please ask

You don't receive the notification because of these two lines in your view:

notifications= Notification.objects.filter(user=user).order_by('-date')
Notification.objects.filter(user=user, is_seen=False).update(is_seen=True)

Let me explain what happens in those lines.

  1. You filter the notifications but the filter ORM method is lazy, which means it will not be executed until the objects are needed, which in your case is in the template
  2. You then update the status of the notifications. Unlike filter, update is not lazy so the query is immediately executed and the notifications have status is_seen as True in DB.
  3. Django starts rendering your templates and executes the original filter to retrieve the notifications. At this point, you no longer have notifications with is_seen=False in the DB as you already updated them in step 2 to is_seen=True . So, the loop in the template that renders the notifications shows nothing as it could not find any unseen notifications in the DB.

To mitigate this, convert the notifications in step 1 to list, so that the query will be executed immediately. In this way, you are passing an actual list of objects to your template and not a lazy queryset.

This solves your problem:

notifications= list(Notification.objects.filter(user=user).order_by('-date'))

Converting it to list forces Django to immediately execute the query and return the objects

Define like_progress in a new file, signals.py, in the same directory as .models.Post instead of in the model. This will help avoid side effects.

In practice, signal handlers are usually defined in a signals submodule of the application they relate to.

Register the signal handler in the appConfig for the app which contains the Post model, I called the app "blog", so the module path is blog.apps.BlogConfig and add that module path to INSTALLED_APPS , instead of just the app name.

From the docs:

Signal receivers are connected in the ready() method of your application configuration class.

And check out more information on the ready method .

Your apps.py file in the blog app (whereever Post model lives) should look like so:

from django.apps import AppConfig
from django.db.models.signals import post_save


class BlogConfig(AppConfig):
    name = 'blog'

    def ready(self):
        from .models import Post
        from .signals import like_progress

        post_save.connect(like_progress, sender=Post)

Note that the imports need to happen in the ready method. There is also an option to specify the model import as a string.

Remember to also specify another user as a sender when you create the Notification. As in:

notify = Notification(post=post, user=post.author, sender=liker, notification_type=3)

And you might want to check out the docs on avoiding duplicate registration of signal handlers .

It's all in the docs you linked to, but it helped a lot to see what happened in the shell ( python manage.py shell ). At first it failed silently, but once the signal handler was moved into signals.py and registered in appConfig it started to show some results.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM