title: New chat message notification Django Channels
body: <p>I've got <code>Django Channels 2.1.2</code> set up in my Django app by following a tutorial and now need to set up a notification system for new messages. I want to do this in the simplest way possible.</p>
  
  <p>I can do it via browser push notifications, but I don't want to do it like that. I want it to be like Stack Overflow, where there is a red number representing the instance of a new message.</p>
  
  <p>One answer on here said</p>
  
  <blockquote>
    <p>For notifications you only need two models: <code>User</code> and <code>Notification</code>. On
    connect set the scope to the currently authenticated user. Set up a
    <code>post_save</code> signal on your <code>Notification</code> model to trigger a consumer
    method to message the notification object's user. –</p>
  </blockquote>
  
  <p>I am struggling to wrap my head around what this would look like, I already have a <code>User</code> model but no <code>Notification</code> one.</p>
  
  <p>The chat is between only 2 users, it is not a chat room but more of a chat thread. The 2 html templates are <code>inbox.html</code> and <code>thread.html</code></p>
  
  <p>Appreciate any help!</p>
  
  <p>My Django Channels code is below!</p>
  
  <p>consumers.py</p>
  
  <pre><code>class ChatConsumer(AsyncConsumer):
      async def websocket_connect(self, event):
          print('connected', event)
  
          other_user = self.scope['url_route']['kwargs']['username']
          me = self.scope['user']
          #print(other_user, me)
          thread_obj = await self.get_thread(me, other_user)
          self.thread_obj = thread_obj
          chat_room = f"thread_{thread_obj.id}"
          self.chat_room = chat_room
          # below creates the chatroom
          await self.channel_layer.group_add(
              chat_room,
              self.channel_name
          )
  
          await self.send({
              "type": "websocket.accept"
          })
  
      async def websocket_receive(self, event):
          # when a message is recieved from the websocket
          print("receive", event)
  
          message_type = event.get('type', None)  #check message type, act accordingly
          if message_type == "notification_read":
              # Update the notification read status flag in Notification model.
              notification = Notification.object.get(id=notification_id)
              notification.notification_read = True
              notification.save()  #commit to DB
              print("notification read")
  
          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 = 'default'
              if user.is_authenticated:
                  username = user.username
              myResponse = {
                  'message': msg,
                  'username': username,
              }
              await self.create_chat_message(user, msg)
  
              # broadcasts the message event to be sent, the group send layer
              # triggers the chat_message function for all of the group (chat_room)
              await self.channel_layer.group_send(
                  self.chat_room,
                  {
                      'type': 'chat_message',
                      'text': json.dumps(myResponse)
                  }
              )
      # chat_method is a custom method name that we made
      async def chat_message(self, event):
          # sends the actual message
          await self.send({
                  'type': 'websocket.send',
                  'text': event['text']
          })
  
      async def websocket_disconnect(self, event):
          # when the socket disconnects
          print('disconnected', event)
  
      @database_sync_to_async
      def get_thread(self, user, other_username):
          return Thread.objects.get_or_new(user, other_username)[0]
  
      @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)
  </code></pre>
  
  <p>manager</p>
  
  <pre><code>class ThreadManager(models.Manager):
      def by_user(self, user):
          qlookup = Q(first=user) | Q(second=user)
          qlookup2 = Q(first=user) &amp; Q(second=user)
          qs = self.get_queryset().filter(qlookup).exclude(qlookup2).distinct()
          return qs
  
      # method to grab the thread for the 2 users
      def get_or_new(self, user, other_username): # get_or_create
          username = user.username
          if username == other_username:
              return None, None
          # looks based off of either username
          qlookup1 = Q(first__username=username) &amp; Q(second__username=other_username)
          qlookup2 = Q(first__username=other_username) &amp; Q(second__username=username)
          qs = self.get_queryset().filter(qlookup1 | qlookup2).distinct()
          if qs.count() == 1:
              return qs.first(), False
          elif qs.count() &gt; 1:
              return qs.order_by('timestamp').first(), False
          else:
              Klass = user.__class__
              try:
                  user2 = Klass.objects.get(username=other_username)
              except Klass.DoesNotExist:
                  user2 = None
              if user != user2:
                  obj = self.model(
                          first=user,
                          second=user2
                      )
                  obj.save()
                  return obj, True
              return None, False
  </code></pre>
  
  <p>models.py</p>
  
  <pre><code>class Thread(models.Model):
      first        = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_first')
      second       = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='chat_thread_second')
      updated      = models.DateTimeField(auto_now=True)
      timestamp    = models.DateTimeField(auto_now_add=True)
  
      objects      = ThreadManager()
  
      def __str__(self):
          return f'{self.id}'
  
      @property
      def room_group_name(self):
          return f'chat_{self.id}'
  
      def broadcast(self, msg=None):
          if msg is not None:
              broadcast_msg_to_chat(msg, group_name=self.room_group_name, user='admin')
              return True
          return False
  
  class ChatMessage(models.Model):
      thread      = models.ForeignKey(Thread, null=True, blank=True, on_delete=models.SET_NULL)
      user        = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name='sender', on_delete=models.CASCADE)
      message     = models.TextField()
      timestamp   = models.DateTimeField(auto_now_add=True)
  
      def __str__(self):
          return f'{self.id}'
  
  class Notification(models.Model):
      notification_user = models.ForeignKey(User, on_delete=models.CASCADE)
      notification_chat = models.ForeignKey(ChatMessage, on_delete=models.CASCADE)
      notification_read = models.BooleanField(default=False)
  
      def __str__(self):
          return f'{self.id}'
  </code></pre>
  
  <p>views.py</p>
  
  <pre><code>class InboxView(LoginRequiredMixin, ListView):
      template_name = 'chat/inbox.html'
      context_object_name = 'threads'
      def get_queryset(self):
          return Thread.objects.by_user(self.request.user).exclude(chatmessage__isnull=True).order_by('timestamp')
          # by_user(self.request.user)
  
  class ThreadView(LoginRequiredMixin, FormMixin, DetailView):
      template_name = 'chat/thread.html'
      form_class = ComposeForm
      success_url = '#'
  
      def get_queryset(self):
          return Thread.objects.by_user(self.request.user)
  
      def get_object(self):
          other_username  = self.kwargs.get("username")
          obj, created    = Thread.objects.get_or_new(self.request.user, other_username)
          if obj == None:
              raise Http404
          return obj
  
      def get_context_data(self, **kwargs):
          context = super().get_context_data(**kwargs)
          context['form'] = self.get_form()
          return context
  
      def post(self, request, *args, **kwargs):
          if not request.user.is_authenticated:
              return HttpResponseForbidden()
          self.object = self.get_object()
          form = self.get_form()
          if form.is_valid():
              return self.form_valid(form)
          else:
              return self.form_invalid(form)
  
      def form_valid(self, form):
          thread = self.get_object()
          user = self.request.user
          message = form.cleaned_data.get("message")
          ChatMessage.objects.create(user=user, thread=thread, message=message)
          return super().form_valid(form)
  </code></pre>
  
  <p>thread.html</p>
  
  <pre><code>{% block head %}
  &lt;title&gt;Chat&lt;/title&gt;
  &lt;script src="{% static '/channels/js/websocketbridge.js' %}" type="text/javascript"&gt;&lt;/script&gt;
  {% endblock %}
  
  
      {% block content %}
  &lt;script&gt;
    $(#notification-element).on("click", function(){
        data = {"type":"notification_read", "username": username, "notification_id": notification_id};
        socket.send(JSON.stringify(data));
    });
  &lt;/script&gt;
  
  &lt;!-- back to inbox button with notification example --&gt;
          &lt;a class="btn btn-light" id="notification_id" href="{% url 'chat:inbox' %}"&gt;Back to Inbox&lt;/a&gt;
  
      &lt;div class="msg_history"&gt;
            {% for chat in object.chatmessage_set.all %}
            {% if chat.user == user %}
            &lt;div class="outgoing_msg"&gt;
              &lt;div class="outgoing_msg_img"&gt; &lt;img src="{{ chat.user.profile.image.url }}"&gt; &lt;/div&gt;
              &lt;div class="sent_msg"&gt;
                &lt;p&gt;{{ chat.message }}&lt;/p&gt;
                &lt;span class="time_date"&gt; {{ chat.timestamp }}&lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            {% else %}
            &lt;div class="incoming_msg"&gt;
              &lt;div class="incoming_msg_img"&gt; &lt;img src="{{ chat.user.profile.image.url }}"&gt; &lt;/div&gt;
              &lt;div class="received_msg"&gt;
                &lt;div class="received_withd_msg"&gt;
                  &lt;p&gt;{{ chat.message }}&lt;/p&gt;
                  &lt;span class="time_date"&gt; {{ chat.timestamp }}&lt;/span&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            {% endif %}
            {% endfor %}
          &lt;/div&gt;
          &lt;div class="type_msg"&gt;
            &lt;div class="input_msg_write"&gt;
              &lt;!-- text input / write message form --&gt;
              &lt;form id='form' method='POST'&gt;
                {% csrf_token %}
                &lt;input type='hidden' id='myUsername' value='{{ user.username }}' /&gt;
                {{ form.as_p }}
                &lt;center&gt;&lt;button type="submit" class='btn btn-success disabled' value="Send"&gt;Send&lt;/button&gt;&lt;/center&gt;
              &lt;/form&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  {% endblock %}
  
  {% block script %}
  
  &lt;script src='https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.js'&gt;&lt;/script&gt;
  
  &lt;script&gt;
    // websocket scripts - client side*
    var loc = window.location
    var formData = $("#form")
    var msgInput = $("#id_message")
    var chatHolder = $('#chat-items')
    var me = $('#myUsername').val()
  
    var wsStart = 'ws://'
    if (loc.protocol == 'https:') {
      wsStart = 'wss://'
    }
    var endpoint = wsStart + loc.host + loc.pathname
    var socket = new ReconnectingWebSocket(endpoint)
  
    // below is the message I am receiving
    socket.onmessage = function(e) {
      console.log("message", e)
      var data = JSON.parse(event.data);
      // Find the notification icon/button/whatever and show a red dot, add the notification_id to element as id or data attribute.
      var chatDataMsg = JSON.parse(e.data)
      chatHolder.append('&lt;li&gt;' + chatDataMsg.message + ' from ' + chatDataMsg.username + '&lt;/li&gt;')
    }
    // below is the message I am sending
    socket.onopen = function(e) {
      console.log("open", e)
      formData.submit(function(event) {
        event.preventDefault()
        var msgText = msgInput.val()
  
        var finalData = {
          'message': msgText
        }
        socket.send(JSON.stringify(finalData))
        formData[0].reset()
      })
    }
    socket.onerror = function(e) {
      console.log("error", e)
    }
    socket.onclose = function(e) {
      console.log("close", e)
    }
  &lt;/script&gt;
  
  &lt;script&gt;
    document.addEventListener('DOMContentLoaded', function() {
      const webSocketBridge = new channels.WebSocketBridge();
      webSocketBridge.connect('/ws');
      webSocketBridge.listen(function(action, stream) {
        console.log("RESPONSE:", action);
      })
      document.ws = webSocketBridge; /* for debugging */
    })
  &lt;/script&gt;
  
  {% endblock %}
  </code></pre>