Django 通道和信号的问题

问题描述 投票:0回答:1

我有一个 Vue 脚本,可以从 Django 检索通知,并且应该在徽章中显示和更新通知计数。我使用Django通道,Django版本是4.2.8,通道版本4,uvicorn 0.26和websockets 12.0。为了更新通知计数,我使用 Django 信号,因此当从管理员或其他来源添加新通知时,会触发 post_save 事件,并且应该调用 Consumers.py 中的 update_notification_count 方法。在浏览器中一切正常,但是当我从 Django 管理员添加新通知时,前端不会更新,也就是说,即使触发事件 post_save,也不会调用 update_notification_count 。这是代码。

首先是我的配置:

# settings.py
CHANNEL_LAYERS = {
        'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer',
        'CONFIG': {
            'capacity': 1000,
            'expiry': 60,
        },
    }
}

现在asgi.py

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from notifapi.routing import websocket_urlpatterns

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproj.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": URLRouter(websocket_urlpatterns),
})

signals.py 文件就是这样编码的

from django.dispatch import receiver
from django.db.models.signals import post_save
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from .models import NotifyModel as Notification
from .consumers import NotificationConsumer


@receiver(post_save, sender=Notification)
def notification_created(sender, instance, created, **kwargs):
    try:
        if created:
            print(f"A new notification was created {instance.message}")
            channel_layer = get_channel_layer()
            async_to_sync(channel_layer.group_send)(
                "public_room",
                {
                    "type": "update_notification_count",
                    "message": instance.message
                }
            )
    except Exception as e:
        print(f"Error in group_send: {e}")

现在消费者.py

from channels.generic.websocket import AsyncWebsocketConsumer
from django.apps import apps
from django.core.serializers import serialize
from asgiref.sync import sync_to_async
import json
import logging

class NotificationConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        # Allow all connections
        await self.channel_layer.group_add("public_room", self.channel_name)
        await self.accept()
        await self.update_notification_count()

    
    async def update_notification_count(self, event=None):
        print("update_notification_count method called with event:", event)

        NotifyModel = apps.get_model('notifapi', 'NotifyModel')
        notifications = await sync_to_async(list)(NotifyModel.objects.all().values('is_read'))
        messages = await sync_to_async(list)(NotifyModel.objects.all().values('message'))
        # Get the notification count asynchronously using a custom utility method
        notification_count = len(notifications)
        
        
        #print(f"Notification count is {notification_count}")
        # Extracting is_read values from notifications
        is_read_values = [notification['is_read'] for notification in notifications]
        messages_values = [notification['message'] for notification in messages]
        #print("Am I here?")
        print(f"Messages values are: {messages_values}")
        await self.send(text_data=json.dumps({
            "type": "notification.update",
            "count": notification_count,
            "is_read_values": is_read_values,
            "messages_values": messages_values
        }))

        
    async def disconnect(self, close_code):
        print("Consumer disconnected")
        # Remove the channel from the public_room group when the WebSocket connection is closed
        await self.channel_layer.group_discard(
            "public_room",
            self.channel_name
        )


    async def receive(self, text_data):
        # Handle incoming messages (if any)
        data = json.loads(text_data)
        if data['type'] == 'update.notification.count':
            await self.update_notification_count()

最后是Vue脚本(仅供参考,因为问题取决于signals.py,而不是Vue脚本):

// App.vue
<template>
  <div id="app">
    <div id="wrapper">
      <NotificationBell />
    </div>
  </div>
</template>

<script>
import NotificationBell from './components/NotificationBell.vue';

export default {
  components: {
    NotificationBell,
  },
};
</script>

还有NotificationBell.vue

<template>
    <div class="fancy-container">
        <a href="#" class="position-relative">
            <i class="fa fa-bell _gray" style="font-size:24px"></i>
            <span class="my-text btnLnk">Visits</span>
            <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger _reduced">
                {{ notificationCount }}
            </span>
        </a>
    </div>
</template>
<script>
//import Axios from 'axios';

export default {
    data() {
        return {
            notifications: [],
            webSocket: null
        };
    },
    computed: {
        notificationCount() {
        // Calculate the notification count based on the current state of notifications
        return this.notifications.filter(notification => !notification.fields.is_read).length;
        }
    },
    mounted() {
        this.establishWebSocketConnection();
    },
    methods: {
        async establishWebSocketConnection() {
            this.webSocket = new WebSocket('ws://127.0.0.1:8001/websocket/ws/notifications/', 'echo-protocol');
            
            this.webSocket.onopen = () => {
                console.log('WebSocket connection established!');
                this.updateNotificationCount();
            };
            
            this.webSocket.onmessage = (event) => {
                console.log("Message received:", event.data);
                const message = JSON.parse(event.data);
                console.log("Received type:", message.type);  // Log the type field to identify the message type
                if(message.type === 'notification.update'){
                    this.notifications = message.is_read_values.map(is_read => ({ fields: { is_read } }));
                }else{
                    console.log("Notification count was not updated!")
                }

            };

            this.webSocket.onclose = () => {
                console.log('WebSocket connection closed.');
            // implement reconnect logic if desired
            };
        },
        updateNotificationCount() {
            console.log('updateNotificationCount called!');
            // Send a message to the WebSocket server to request updated notification count
            this.webSocket.send(JSON.stringify({
                "type": "update.notification.count"  // Define a custom type to trigger the count update
            }));

        },
    },
};
</script>

我完全知道所有模型信息都可以通过consumers.py中的单个数据库命中来检索,当我解决问题时我将重构它。

问题确实在于,当我从 Django 管理员添加新通知时,会显示 signal.py 中 notification_created 函数内的打印内容

print(f"A new notification was created {instance.message}")

但是代码在这里

async_to_sync(channel_layer.group_send)(
                "public_room",
                {
                    "type": "update_notification_count",
                    "message": instance.message
                }
            )

永远不会被打电话。我可以在 Gunicorn 控制台和浏览器控制台中看到,只有在浏览器中按 F5 后,信息才会更新。

信号没有影响,无法调用update_notification_count,我不知道为什么,可能是因为信号没有被设计为调用consumers.py文件中的异步方法,我根本不知道原因,但是我需要一种机制来在添加新通知时推送新通知,以便更新计数而无需刷新浏览器。

我没有 Django 通道和 Web 套接字的经验。

有人可以帮我吗?

django vue.js websocket channel uvicorn
1个回答
0
投票

由于没有人回答这个问题,我自己找到了解决方案。问题是消费者运行在 WSGI 服务器中,或者至少运行在端口 8000 上。但是 Uvicorn,我在开发中使用它,因为我不知道 Daphne 是官方的开发解决方案(Daphne 不再包含在其中) Django Channels 自版本 4) 起,在端口 8001 上运行。不同的服务器和不同的端口。当 Signals.py 中的代码尝试调用消费者中的异步函数时

@receiver(post_save, sender=Notification)
def notification_created(sender, instance, created, **kwargs):
    
    if created:
        print(f"A new notification was created {instance.message}")
        channel_layer = get_channel_layer()
        print(channel_layer)
        try:
            async_to_sync(channel_layer.group_send)(
                'public_room',
                {
                    "type":"update_notification_count",
                    "message":instance.message
                }
            )

即使使用async_to_sinc,它仍然无法与监听另一个端口的另一个服务器通信。它只能通过从客户端直接调用 websocket 来工作,因此它只能在浏览器刷新时工作。

这个配置

CHANNEL_LAYERS = {
        'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer',
        'CONFIG': {
            'capacity': 1000,
            'expiry': 60,
        },
    }
}

仅适用于在开发模式下运行时

python manage.py runserver

并在 wsgi 和 asgi 的开发模式下使用 Daphne,它将与 Daphne 运行在相同的端口 8000 上。这在生产模式下使用 Uvicorn 是行不通的。 InMemoryChannelLayer 的设计初衷并不是为了处理不同端口上的不同服务器。它仅设计为在同一端口上使用 Daphne 运行。但文档没有提到所有这些细节,我必须自己弄清楚这一切。

为此,我需要 Redis。 Redis 不是通道层。它是一个在内存中运行的键值数据库。但是channels_redis.core.RedisChannelLayer通道层,必须安装

pip install channels-redis

它还将安装一个依赖项 redis,它将使用 Redis 进行通信并在不同服务器之间中断消息。在 Windows 中,我必须安装 Memurai,它是在 Windows 上运行的 Redis 克隆,以便测试这一切。现在一切都很完美。

所以教训是:

如果您只想在开发模式下使用通道层,特别是如果您在 Windows 上进行开发,则使用

CHANNEL_LAYERS = {
        'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer',
        'CONFIG': {
            'capacity': 1000,
            'expiry': 60,
        },
    }
}

添加这个

#WSGI_APPLICATION = 'myproj.wsgi.application'
ASGI_APPLICATION = 'myproj.asgi.application'

相应配置asgi.py并安装Daphne以与开发服务器一起运行。

如果您想在生产中运行 Django Channels,或者您想测试或模拟生产环境,即使是在 Windows 中,那么您必须安装 Memurai 并遵循上述相同的说明。

所以我希望能帮助其他人解决我的解决方案。当你不知道如何继续时,这是一个非常棘手的问题,因为即使是人工智能工具也完全无法找出解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.