新的聊天消息通知 Django 频道 [英] New chat message notification Django Channels

查看:24
本文介绍了新的聊天消息通知 Django 频道的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经按照教程在我的 Django 应用程序中设置了 Django Channels 2.1.2,现在需要为新消息设置通知系统.我想以最简单的方式做到这一点.

我可以通过浏览器推送通知来做到这一点,但我不想那样做.我希望它像 Stack Overflow 一样,其中有一个红色数字代表新消息的实例.

这里的一个答案说

<块引用>

对于通知,您只需要两个模型:UserNotification.在connect 将范围设置为当前经过身份验证的用户.设置一个post_save 信号在您的 Notification 模型上触​​发消费者向通知对象的用户发送消息的方法.——

我正在努力思考这会是什么样子,我已经有一个 User 模型,但没有 Notification 模型.

聊天仅在 2 个用户之间进行,它不是聊天室,而是更多的聊天线程.这两个 html 模板是 inbox.htmlthread.html

感谢任何帮助!

我的 Django 频道代码如下!

consumers.py

class ChatConsumer(AsyncConsumer):async def websocket_connect(self, event):打印('连接',事件)other_user = self.scope['url_route']['kwargs']['username']me = self.scope['user']#print(other_user, 我)thread_obj = await self.get_thread(me, other_user)self.thread_obj = thread_objchat_room = f"thread_{thread_obj.id}"self.chat_room = chat_room# 下面创建聊天室等待 self.channel_layer.group_add(聊天室,self.channel_name)等待 self.send({类型":websocket.accept"})async def websocket_receive(self, event):# 当收到来自 websocket 的消息时打印(接收",事件)message_type = event.get('type', None) #检查消息类型,采取相应措施如果 message_type == "notification_read":# 更新通知模型中的通知读取状态标志.通知 = Notification.object.get(id=notification_id)notification.notification_read = True通知.save() #提交到数据库打印(通知已读")front_text = event.get('text', None)如果 front_text 不是 None:Loaded_dict_data = json.loads(front_text)msg = loaded_dict_data.get('message')用户 = self.scope['用户']用户名 = '默认'如果 user.is_authenticated:用户名 = 用户.用户名我的回应 = {'消息':消息,'用户名':用户名,}等待 self.create_chat_message(user, msg)# 广播要发送的消息事件,群发层# 为所有群组(chat_room)触发chat_message功能等待 self.channel_layer.group_send(self.chat_room,{'type': 'chat_message','文本':json.dumps(myResponse)})# chat_method 是我们自定义的方法名异步 def chat_message(self, event):# 发送实际消息等待 self.send({'type': 'websocket.send','文本':事件['文本']})async def websocket_disconnect(self, event):# 当socket断开时打印('断开连接',事件)@database_sync_to_asyncdef get_thread(self, user, other_username):返回 Thread.objects.get_or_new(user, other_username)[0]@database_sync_to_asyncdef create_chat_message(self, me, msg):thread_obj = self.thread_obj返回 ChatMessage.objects.create(thread=thread_obj, user=me, message=msg)

经理

class ThreadManager(models.Manager):def by_user(self, user):qlookup = Q(first=user) |Q(第二个=用户)qlookup2 = Q(first=user) &Q(第二个=用户)qs = self.get_queryset().filter(qlookup).exclude(qlookup2).distinct()返回 qs# 为2个用户抓取线程的方法def get_or_new(self, user, other_username): # get_or_create用户名 = 用户.用户名如果用户名 == 其他用户名:返回无,无# 看起来基于任一用户名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()如果 qs.count() == 1:返回 qs.first(), Falseelif qs.count() >1:返回 qs.order_by('timestamp').first(), False别的:类 = user.__class__尝试:user2 = Klass.objects.get(username=other_username)除了 Klass.DoesNotExist:用户 2 = 无如果用户 != 用户 2:obj = self.model(第一个=用户,第二=用户2)obj.save()返回对象,真返回无,假

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')更新 = models.DateTimeField(auto_now=True)时间戳 =models.DateTimeField(auto_now_add=True)对象 = 线程管理器()def __str__(self):返回 f'{self.id}'@财产def room_group_name(self):返回 f'chat_{self.id}'定义广播(自我,味精=无):如果 msg 不是 None:broadcast_msg_to_chat(msg, group_name=self.room_group_name, user='admin')返回真返回错误类 ChatMessage(models.Model):线程=models.ForeignKey(线程,空=真,空白=真,on_delete=models.SET_NULL)user = models.ForeignKey(settings.AUTH_USER_MODEL,verbose_name='sender', on_delete=models.CASCADE)消息 = 模型.TextField()时间戳 =models.DateTimeField(auto_now_add=True)def __str__(self):返回 f'{self.id}'类通知(模型.模型):Notification_user = models.ForeignKey(用户,on_delete=models.CASCADE)notification_chat = models.ForeignKey(ChatMessage, on_delete=models.CASCADE)notification_read = models.BooleanField(默认=False)def __str__(self):返回 f'{self.id}'

views.py

class InboxView(LoginRequiredMixin, ListView):template_name = '聊天/收件箱.html'context_object_name = '线程'def get_queryset(self):返回 Thread.objects.by_user(self.request.user).exclude(chatmessage__isnull=True).order_by('timestamp')# by_user(self.request.user)类 ThreadView(LoginRequiredMixin, FormMixin, DetailView):template_name = 'chat/thread.html'form_class = ComposeForm成功网址 = '#'def get_queryset(self):返回 Thread.objects.by_user(self.request.user)def get_object(self):other_username = self.kwargs.get("用户名")obj, created = Thread.objects.get_or_new(self.request.user, other_username)如果 obj == 无:提高Http404返回对象def get_context_data(self, **kwargs):context = super().get_context_data(**kwargs)context['form'] = self.get_form()返回上下文def post(self, request, *args, **kwargs):如果不是 request.user.is_authenticated:返回 HttpResponseForbidden()self.object = self.get_object()form = self.get_form()如果 form.is_valid():返回 self.form_valid(form)别的:返回 self.form_invalid(form)def form_valid(self, form):线程 = self.get_object()用户 = self.request.usermessage = form.cleaned_data.get("message")ChatMessage.objects.create(用户=用户,线程=线程,消息=消息)返回 super().form_valid(form)

thread.html

{% 区块头 %}<title>聊天</title><script src="{% static '/channels/js/websocketbridge.js' %}" type="text/javascript"></script>{% 结束块 %}{% 块内容 %}<脚本>$(#notification-element).on("点击", function(){数据 = {类型":notification_read",用户名":用户名,notification_id":notification_id};socket.send(JSON.stringify(data));});<!-- 返回带有通知示例的收件箱按钮--><a class="btn btn-light" id="notification_id" href="{% url 'chat:inbox' %}">返回收件箱</a><div class="msg_history">{% 用于 object.chatmessage_set.all 中的聊天 %}{% if chat.user == user %}<div class="outgoing_msg"><div class="outgoing_msg_img"><img src="{{ chat.user.profile.image.url }}">

<div class="sent_msg"><p>{{ chat.message }}</p><span class="time_date">{{ chat.timestamp }}</span>

{% 别的 %}<div class="incoming_msg"><div class="incoming_msg_img"><img src="{{ chat.user.profile.image.url }}">

<div class="received_msg"><div class="received_withd_msg"><p>{{ chat.message }}</p><span class="time_date">{{ chat.timestamp }}</span>

{% 万一 %}{% 结束为 %}

<div class="type_msg"><div class="input_msg_write"><!-- 文字输入/写留言表单--><form id='form' method='POST'>{% csrf_token %}<input type='hidden' id='myUsername' value='{{ user.username }}'/>{{ form.as_p }}<center><button type="submit" class='btn btn-success disabled' value="Send">Send</button></center></表单>

{% 结束块 %}{% 块脚本 %}<script src='https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.js'></script><脚本>//websocket 脚本 - 客户端*var loc = window.locationvar formData = $("#form")var msgInput = $("#id_message")var chatHolder = $('#chat-items')var me = $('#myUsername').val()var wsStart = 'ws://'if (loc.protocol == 'https:') {wsStart = 'wss://'}var 端点 = wsStart + loc.host + loc.pathnamevar socket = new ReconnectingWebSocket(endpoint)//下面是我收到的消息socket.onmessage = 函数(e){console.log("message", e)var data = JSON.parse(event.data);//找到通知图标/按钮/任何东西并显示一个红点,将notification_id作为id或data属性添加到元素中.var chatDataMsg = JSON.parse(e.data)chatHolder.append('<li>' + chatDataMsg.message + ' from ' + chatDataMsg.username + '</li>')}//下面是我发送的消息socket.onopen = 函数(e){console.log("打开", e)formData.submit(函数(事件){event.preventDefault()var msgText = msgInput.val()var finalData = {'消息':msgText}socket.send(JSON.stringify(finalData))formData[0].reset()})}socket.onerror = 函数(e){console.log("错误", e)}socket.onclose = 函数(e){console.log("关闭", e)}<脚本>document.addEventListener('DOMContentLoaded', function() {const webSocketBridge = new channels.WebSocketBridge();webSocketBridge.connect('/ws');webSocketBridge.listen(函数(动作,流){console.log("RESPONSE:", action);})document.ws = webSocketBridge;/* 用于调试 */}){% 结束块 %}

解决方案

实现通知系统的一种简单方法可以是:

当你想显示一条新消息时,一旦你在 websocket 上收到消息,就使用 JS 操作 HTML.每当与元素进行交互时,这意味着用户已阅读通知,使用 websocket 将消息发送回服务器.

您的 Notification 可以有 ForeignKeys 给用户和消息以及用于读取状态的 BooleanField.每当你向用户发送消息时,你应该在消息中附加 notification_id,

#consumer.pyasync def websocket_receive(self, event):# 当收到来自 websocket 的消息时打印(接收",事件)message_type = event.get('type', None) #检查消息类型,采取相应措施如果 message_type == "notification_read":# 更新通知模型中的通知读取状态标志.通知 = Notification.object.get(id=notification_id)notification.notification_read = True通知.save() #提交到数据库打印(通知已读")front_text = event.get('text', None)如果 front_text 不是 None:Loaded_dict_data = json.loads(front_text)msg = loaded_dict_data.get('message')用户 = self.scope['用户']用户名 = '默认'如果 user.is_authenticated:用户名 = 用户.用户名我的回应 = {'消息':消息,'用户名':用户名,'notification': notification_id # 发送通知的唯一标识符}...

在客户端,

//thread.htmlsocket.onmessage = 函数(e){var data = JSON.parse(event.data);//找到通知图标/按钮/任何东西并显示一个红点,将notification_id作为id或data属性添加到元素中.}...$(#notification-element).on("点击", function(){数据 = {类型":notification_read",用户名":用户名,notification_id":notification_id};socket.send(JSON.stringify(data));});

您可以根据需要将单个/所有未读通知标记为已读.

我为一个培训项目做过类似的事情,你可以看看有什么想法.Github 链接.

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.

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.

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.

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)

        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)

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

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

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>
<script src="{% static '/channels/js/websocketbridge.js' %}" type="text/javascript"></script>
{% endblock %}


    {% block content %}
<script>
  $(#notification-element).on("click", function(){
      data = {"type":"notification_read", "username": username, "notification_id": notification_id};
      socket.send(JSON.stringify(data));
  });
</script>

<!-- back to inbox button with notification example -->
        <a class="btn btn-light" id="notification_id" href="{% url 'chat:inbox' %}">Back to Inbox</a>

    <div class="msg_history">
          {% for chat in object.chatmessage_set.all %}
          {% if chat.user == user %}
          <div class="outgoing_msg">
            <div class="outgoing_msg_img"> <img src="{{ chat.user.profile.image.url }}"> </div>
            <div class="sent_msg">
              <p>{{ chat.message }}</p>
              <span class="time_date"> {{ chat.timestamp }}</span>
            </div>
          </div>
          {% else %}
          <div class="incoming_msg">
            <div class="incoming_msg_img"> <img src="{{ chat.user.profile.image.url }}"> </div>
            <div class="received_msg">
              <div class="received_withd_msg">
                <p>{{ chat.message }}</p>
                <span class="time_date"> {{ chat.timestamp }}</span>
              </div>
            </div>
          </div>
          {% endif %}
          {% endfor %}
        </div>
        <div class="type_msg">
          <div class="input_msg_write">
            <!-- text input / write message form -->
            <form id='form' method='POST'>
              {% csrf_token %}
              <input type='hidden' id='myUsername' value='{{ user.username }}' />
              {{ form.as_p }}
              <center><button type="submit" class='btn btn-success disabled' value="Send">Send</button></center>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock %}

{% block script %}

<script src='https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.js'></script>

<script>
  // 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('<li>' + chatDataMsg.message + ' from ' + chatDataMsg.username + '</li>')
  }
  // 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)
  }
</script>

<script>
  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 */
  })
</script>

{% endblock %}

解决方案

One easy way to implement a notification system can be:

When you want to show a new message, manipulate HTML using JS as soon as you get a message on the websocket. And whenever the element has been interacted with, which means the user has read the notification, send a message back to server using the websocket.

Your Notification can have ForeignKeys to user and the message along with a BooleanField for read status. Whenever you are sending the message to the user, you should append the notification_id along the message,

#consumer.py
async def websocket_receive(self, event):
        # when a message is received 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,
                'notification': notification_id  # send a unique identifier for the notification
            }
            ...

On the client side,

// thread.html
socket.onmessage = function(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.
}
...

$(#notification-element).on("click", function(){
    data = {"type":"notification_read", "username": username, "notification_id": notification_id};
    socket.send(JSON.stringify(data));
});

You can mark individual/all unread notifications as read according to your need.

I did something similar for a training project, you can check that out for ideas. Github link.

这篇关于新的聊天消息通知 Django 频道的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
Python最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆