#StackBounty: #python #django #django-models #django-views #django-channels New chat message notification Django Channels

Bounty: 200

I’ve got Django Channels 2.1.2 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 (ideally using native Django features if possible…etc Django templating).

I’ve looked around the internet but there isn’t a clear tutorial/explanation anywhere for doing this with Django Channels.

One answer on here said

For notifications you only need two models: User and Notification. On
connect set the scope to the currently authenticated user. Set up a
post_save signal on your Notification model to trigger a consumer
method to message the notification object’s user. –

I am struggling to wrap my head around what this would look like, I already have a User model but no Notification one.

Would I have a signals.py in my chat app which looked something like

@receiver(post_save, sender=User)
def create_notification(sender, instance, created, **kwargs):
    if created:
        Notification.objects.create(user=instance)

What would the front end look like? This is obviously all in-app and not via emails.

Would I save any instances of the Notification model (generated by post_save signals.py) to my database and then render out that figure using Django templating in the top corner of the webpage for example?

The chat is between only 2 users, it is not a chat room but more of a chat thread. The 2 html templates are inbox.html and thread.html

Appreciate any help!

My Django Channels code is below!

consumers.py

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)
        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)

manager

class ThreadManager(models.Manager):
    def by_user(self, user):
        qlookup = Q(first=user) | Q(second=user)
        qlookup2 = Q(first=user) & 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) & Q(second__username=other_username)
        qlookup2 = Q(first__username=other_username) & Q(second__username=username)
        qs = self.get_queryset().filter(qlookup1 | qlookup2).distinct()
        if qs.count() == 1:
            return qs.first(), False
        elif qs.count() > 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

models.py

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}'

views.py

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)

thread.html

{% block head %}
<title>Chat</title>
http://%%20static%20'/channels/js/websocketbridge.js'%20%
{% endblock %}


    {% block content %}
    
{% for chat in object.chatmessage_set.all %} {% if chat.user == user %}
sunil
{{ chat.message }} {{ chat.timestamp }}
</div> {% else %}
sunil
{{ chat.message }} {{ chat.timestamp }}
</div> </div> {% endif %} {% endfor %} </div>
{% csrf_token %} {{ form.as_p }}
</div> </div> </div> </div> </div> {% endblock %} {% block script %} https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.js // 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 chatDataMsg = JSON.parse(e.data) chatHolder.append('
  • ' + chatDataMsg.message + ' from ' + chatDataMsg.username + '
  • ') } // 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) } 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 */ }) {% endblock %}


    Get this bounty!!!

    Leave a Reply

    This site uses Akismet to reduce spam. Learn how your comment data is processed.