1

I am using JQuery/JS with Django Channels WebSockets to configure real-time notifications for chat messages.

When a user has any Notification objects with nofication_read=False, they are rendered out in the navbar with the code below.

{{ notification|length }} displays the number of Notification objects with nofication_read=False.

navbar.html

<li id="notification_li" class="nav-item">
  <a class="nav-link" href="#" id="notificationLink">
    <span id="notification_id{{notification_id}}">{{ notification|length }}</span>
  <div id="notificationContainer">
    <div id="notificationTitle">Notifications</div>
    <div id="notificationsBody" class="notifications">
      {% for notifications in notification|slice:"0:10" %}
        <span id="notification-{{notification.id}}">
          {{ notifications.notification_chat.message }}
          via {{ notifications.notification_chat.user }}
          at {{ notifications.notification_chat.timestamp }}
        </span>
      {% endfor %}

I know this isn't realtime at the moment.

But the problem is that when the user refreshes the page and clicks on the red icon span id=notification_id to display their list of unread notifications, the data is sent through the websocket (JSON.stringify) as "type": "notification_read" which calls the if message_type == "notification_read": command in consumers.y which marks them as nofication_read=True in the database.

When the user refreshes the page, the red icon is no longer there and when they try to click on the background link, it's unclickable with the error in the browser console

Uncaught ReferenceError: notification_id is not defined

As soon as they get another notification and the page is refreshed, it obviously becomes clickable again.

Any time after that initial click, users should still be able to click and see the list of read notifications.

consumers.py

class ChatConsumer(AsyncConsumer):
    ...
    async def websocket_receive(self, event):
        message_type = json.loads(event.get('text','{}')).get('type')
        if message_type == "notification_read":
            user = self.scope['user']
            username = user.username if user.is_authenticated else 'default'
            # Update the notification read status flag in Notification model.
            notification = Notification.objects.filter(notification_user=user).update(notification_read=True)
            print("notification read")
            return 

front_text = event.get('text', None)
        if front_text is not None:
            loaded_dict_data = json.loads(front_text)
            msg =  loaded_dict_data.get('message')
            user = self.scope['user']
            username = user.username if user.is_authenticated else 'default'
            notification_id = 'default'
            myResponse = {
                'message': msg,
                'username': username,
                'notification': notification_id,
            }
   ...
        await self.create_chat_message(user, msg)
        other_user = self.scope['url_route']['kwargs']['username']
        other_user = User.objects.get(username=other_user)
        await self.create_notification(other_user, msg)
       ...
@database_sync_to_async
def create_chat_message(self, me, msg):
    thread_obj = self.thread_obj
    return ChatMessage.objects.create(thread=thread_obj, user=me, message=msg)

@database_sync_to_async
def create_notification(self, other_user, msg):
    last_chat = ChatMessage.objects.latest('id')
    created_notification = Notification.objects.create(notification_user=other_user, notification_chat=last_chat)
    return created_notification

context_processors.py

def notification(request):
    if request.user.is_authenticated:
        notification = Notification.objects.filter(notification_user=request.user, notification_read=False)
        return {
        'notification':notification,
    return Notification.objects.none()

base.html

 <script>
    $(document).ready(function() {
     $("#notificationLink").click(function() {
        var data = {
          "type": "notification_read",
          "username": username,
          "notification_id": notification_id,
        }
        socket.send(JSON.stringify(data));

        $("#notificationContainer").fadeToggle(300);
        $("#notification_id").fadeOut("slow");
        return false;
      });
...
var incomingMsg = $('.incoming_msg')

socket.onmessage = function(e) {
      console.log("message", e)
      var chatDataMsg = JSON.parse(e.data)
      incomingMsg.append('<li>' + chatDataMsg.message + ' from ' + chatDataMsg.username + '</li>')
    }
Brown Bear
  • 19,655
  • 10
  • 58
  • 76
Trilla
  • 943
  • 2
  • 15
  • 31
  • 1
    Hi, before `"notification_id": notification_id` you should define the `notification_id`, but in your case you need do it by loop here example https://stackoverflow.com/questions/16191727/jquery-for-each-all-elements-having-similar-id – Brown Bear May 03 '19 at 14:23
  • where are `username` and `notification_id` defined that you reference in your `data` var inside the click handler? – dirkgroten May 03 '19 at 14:30
  • @dirkgroten I've updated to show more of my `consumers.py` but yes you are right they are not defined in base.html? – Trilla May 03 '19 at 14:35
  • you just have a javascript error, so the python code is not really relevant here. Just your template and javascript. Yes, the vars `username` and `notification_id` are not defined in javascript (not sure about `username`, since it's not raising the error). – dirkgroten May 03 '19 at 14:42
  • @dirkgroten sorry yes I forgot I have `` in my template – Trilla May 03 '19 at 14:45
  • @dirkgroten but have now defined it as per @bearbrown advice as `var username = '{{ request.user.username }}'` above `var data` – Trilla May 03 '19 at 14:53

1 Answers1

2

before "notification_id": notification_id you should define the notification_id, but in your case you need do it by loop, you can try:

 $("#notificationLink").click(function() {

   $('span[id^="notification-"]').each(function(){
        var notification_id = $(this).attr('id').substring(13)
        var data = {
          "type": "notification_read",
          "username": username,
          "notification_id": notification_id,
        }
        socket.send(JSON.stringify(data));
    });

    $("#notificationContainer").fadeToggle(300);
    $("#notification_id").fadeOut("slow");
    return false;
  })

i hope in your template you already have something like:

var username = '{{ request.user.username }}'

by your current code on the backend you don't need the notification_id, so you can simple remove it:

 $("#notificationLink").click(function() {
    var data = {
      "type": "notification_read",
      "username": username,
    }
    socket.send(JSON.stringify(data));

    $("#notificationContainer").fadeToggle(300);
    $("#notification_id").fadeOut("slow");
    return false;
  });
Brown Bear
  • 19,655
  • 10
  • 58
  • 76