我有一个 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 套接字的经验。
有人可以帮我吗?
由于没有人回答这个问题,我自己找到了解决方案。问题是消费者运行在 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 并遵循上述相同的说明。
所以我希望能帮助其他人解决我的解决方案。当你不知道如何继续时,这是一个非常棘手的问题,因为即使是人工智能工具也完全无法找出解决方案。