我已按照教程在Django应用中设置了Django Channels 2.1.2
,现在需要为新消息设置通知系统。我想以最简单的方式做到这一点。
我可以通过浏览器推送通知来执行此操作,但是我不想那样做。我希望它就像Stack Overflow,其中有一个红色的数字代表新消息的实例。
这里有一个答案
对于通知,您仅需要两个模型:
User
和Notification
。上 connect将范围设置为当前已认证的用户。设置一个 在您的post_save
模型上发出Notification
信号来触发消费者 向通知对象的用户发送消息的方法。 –
我正在竭尽全力寻找自己的模样,我已经有一个User
模型,但没有Notification
模型。
聊天仅在2个用户之间进行,它不是聊天室,而是更多的聊天线程。 2个html模板是inbox.html
和thread.html
感谢任何帮助!
下面是我的Django频道代码!
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)
经理
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 %}
答案 0 :(得分:3)
实现通知系统的一种简单方法是:
如果要显示新消息,请在网络套接字上收到消息后立即使用JS操作HTML。每当与元素进行交互时,这意味着用户已经阅读了通知,就可以使用websocket将消息发送回服务器。
您的Notification
可以拥有ForeignKeys
的用户名和消息以及BooleanField
的读取状态。每当您向用户发送消息时,都应在消息后附加notification_id,
#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
}
...
在客户端,
// 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));
});
您可以根据需要将单个/所有未读通知标记为已读。
我为一个培训项目做过类似的事情,您可以检查一下是否有想法。 Github link.
答案 1 :(得分:0)
我无法将其标记为重复项,因为上面有赏金。但是解决方案是,您需要两个以上的模型。根据{{3}}的帖子,您的models.py
应该看起来像这样:
class MessageThread(models.Model):
title = models.CharField()
clients = models.ManyToManyField(User, blank=True)
class Message(models.Model):
date = models.DateField()
text = models.CharField()
thread = models.ForeignKey('messaging.MessageThread', on_delete=models.CASCADE)
sender = models.ForeignKey(User, on_delete=models.SET_NULL)
您的consumers.py
应该如下所示:
class ChatConsumer(WebSocketConsumer):
def connect(self):
if self.scope['user'].is_authenticated:
self.accept()
# add connection to existing groups
for thread in MessageThread.objects.filter(clients=self.scope['user']).values('id'):
async_to_sync(self.channel_layer.group_add)(thread.id, self.channel_name)
# store client channel name in the user session
self.scope['session']['channel_name'] = self.channel_name
self.scope['session'].save()
def disconnect(self, close_code):
# remove channel name from session
if self.scope['user'].is_authenticated:
if 'channel_name' in self.scope['session']:
del self.scope['session']['channel_name']
self.scope['session'].save()
async_to_sync(self.channel_layer.group_discard)(self.scope['user'].id, self.channel_name)