Send messages
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
parent
f3a4a99b78
commit
4a78e80399
|
@ -3,9 +3,10 @@
|
||||||
|
|
||||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from participation.models import Team, Pool, Tournament
|
||||||
from registration.models import Registration
|
from registration.models import Registration
|
||||||
|
|
||||||
from .models import Channel
|
from .models import Channel, Message
|
||||||
|
|
||||||
|
|
||||||
class ChatConsumer(AsyncJsonWebsocketConsumer):
|
class ChatConsumer(AsyncJsonWebsocketConsumer):
|
||||||
|
@ -34,6 +35,10 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
|
||||||
# Accept the connection
|
# Accept the connection
|
||||||
await self.accept()
|
await self.accept()
|
||||||
|
|
||||||
|
channels = await Channel.get_accessible_channels(user, 'read')
|
||||||
|
async for channel in channels.all():
|
||||||
|
await self.channel_layer.group_add(f"chat-{channel.id}", self.channel_name)
|
||||||
|
|
||||||
async def disconnect(self, close_code) -> None:
|
async def disconnect(self, close_code) -> None:
|
||||||
"""
|
"""
|
||||||
Called when the websocket got disconnected, for any reason.
|
Called when the websocket got disconnected, for any reason.
|
||||||
|
@ -43,6 +48,10 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
|
||||||
# User is not authenticated
|
# User is not authenticated
|
||||||
return
|
return
|
||||||
|
|
||||||
|
channels = await Channel.get_accessible_channels(self.scope['user'], 'read')
|
||||||
|
async for channel in channels.all():
|
||||||
|
await self.channel_layer.group_discard(f"chat-{channel.id}", self.channel_name)
|
||||||
|
|
||||||
async def receive_json(self, content, **kwargs):
|
async def receive_json(self, content, **kwargs):
|
||||||
"""
|
"""
|
||||||
Called when the client sends us some data, parsed as JSON.
|
Called when the client sends us some data, parsed as JSON.
|
||||||
|
@ -51,6 +60,8 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
|
||||||
match content['type']:
|
match content['type']:
|
||||||
case 'fetch_channels':
|
case 'fetch_channels':
|
||||||
await self.fetch_channels()
|
await self.fetch_channels()
|
||||||
|
case 'send_message':
|
||||||
|
await self.receive_message(content)
|
||||||
case unknown:
|
case unknown:
|
||||||
print("Unknown message type:", unknown)
|
print("Unknown message type:", unknown)
|
||||||
|
|
||||||
|
@ -59,7 +70,6 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
|
||||||
|
|
||||||
read_channels = await Channel.get_accessible_channels(user, 'read')
|
read_channels = await Channel.get_accessible_channels(user, 'read')
|
||||||
write_channels = await Channel.get_accessible_channels(user, 'write')
|
write_channels = await Channel.get_accessible_channels(user, 'write')
|
||||||
print([channel async for channel in write_channels.all()])
|
|
||||||
message = {
|
message = {
|
||||||
'type': 'fetch_channels',
|
'type': 'fetch_channels',
|
||||||
'channels': [
|
'channels': [
|
||||||
|
@ -73,3 +83,29 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
await self.send_json(message)
|
await self.send_json(message)
|
||||||
|
|
||||||
|
async def receive_message(self, message: dict) -> None:
|
||||||
|
user = self.scope['user']
|
||||||
|
channel = await Channel.objects.prefetch_related('tournament__pools__juries', 'pool', 'team', 'invited') \
|
||||||
|
.aget(id=message['channel_id'])
|
||||||
|
write_channels = await Channel.get_accessible_channels(user, 'write')
|
||||||
|
if not await write_channels.acontains(channel):
|
||||||
|
return
|
||||||
|
|
||||||
|
message = await Message.objects.acreate(
|
||||||
|
author=user,
|
||||||
|
channel=channel,
|
||||||
|
content=message['content'],
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.channel_layer.group_send(f'chat-{channel.id}', {
|
||||||
|
'type': 'chat.send_message',
|
||||||
|
'id': message.id,
|
||||||
|
'timestamp': message.created_at.isoformat(),
|
||||||
|
'author': await message.aget_author_name(),
|
||||||
|
'content': message.content,
|
||||||
|
})
|
||||||
|
|
||||||
|
async def chat_send_message(self, message) -> None:
|
||||||
|
await self.send_json({'type': 'send_message', 'id': message['id'], 'timestamp': message['timestamp'],
|
||||||
|
'author': message['author'], 'content': message['content']})
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
# Copyright (C) 2024 by Animath
|
# Copyright (C) 2024 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, QuerySet
|
from django.db.models import Q, QuerySet
|
||||||
from django.utils.text import format_lazy
|
from django.utils.text import format_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from participation.models import Tournament
|
from participation.models import Pool, Team, Tournament
|
||||||
from registration.models import ParticipantRegistration, Registration, VolunteerRegistration
|
from registration.models import ParticipantRegistration, Registration, VolunteerRegistration
|
||||||
from tfjm.permissions import PermissionType
|
from tfjm.permissions import PermissionType
|
||||||
|
|
||||||
|
@ -141,9 +142,6 @@ class Channel(models.Model):
|
||||||
|
|
||||||
qs |= Channel.objects.filter(invited=user)
|
qs |= Channel.objects.filter(invited=user)
|
||||||
|
|
||||||
print(user)
|
|
||||||
print(qs.query)
|
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -182,6 +180,65 @@ class Message(models.Model):
|
||||||
verbose_name=_("content"),
|
verbose_name=_("content"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_author_name(self):
|
||||||
|
registration = self.author.registration
|
||||||
|
|
||||||
|
author_name = f"{self.author.first_name} {self.author.last_name}"
|
||||||
|
if registration.is_volunteer:
|
||||||
|
if registration.is_admin:
|
||||||
|
author_name += " (CNO)"
|
||||||
|
|
||||||
|
if self.channel.pool:
|
||||||
|
if registration == self.channel.pool.jury_president:
|
||||||
|
author_name += " (P. jury)"
|
||||||
|
elif registration in self.channel.pool.juries.all():
|
||||||
|
author_name += " (Juré⋅e)"
|
||||||
|
elif registration in self.channel.pool.tournament.organizers.all():
|
||||||
|
author_name += " (CRO)"
|
||||||
|
else:
|
||||||
|
author_name += " (Bénévole)"
|
||||||
|
elif self.channel.tournament:
|
||||||
|
if registration in self.channel.tournament.organizers.all():
|
||||||
|
author_name += " (CRO)"
|
||||||
|
elif any([registration.id == pool.jury_president
|
||||||
|
for pool in self.channel.tournament.pools.all()]):
|
||||||
|
pools = ", ".join([pool.short_name
|
||||||
|
for pool in self.channel.tournament.pools.all()
|
||||||
|
if pool.jury_president == registration])
|
||||||
|
author_name += f" (P. jury {pools})"
|
||||||
|
elif any([pool.juries.contains(registration)
|
||||||
|
for pool in self.channel.tournament.pools.all()]):
|
||||||
|
pools = ", ".join([pool.short_name
|
||||||
|
for pool in self.channel.tournament.pools.all()
|
||||||
|
if pool.juries.acontains(registration)])
|
||||||
|
author_name += f" (Juré⋅e {pools})"
|
||||||
|
else:
|
||||||
|
author_name += " (Bénévole)"
|
||||||
|
else:
|
||||||
|
if registration.organized_tournaments.exists():
|
||||||
|
tournaments = ", ".join([tournament.name
|
||||||
|
for tournament in registration.organized_tournaments.all()])
|
||||||
|
author_name += f" (CRO {tournaments})"
|
||||||
|
if Pool.objects.filter(jury_president=registration).exists():
|
||||||
|
tournaments = Tournament.objects.filter(pools__jury_president=registration).distinct()
|
||||||
|
tournaments = ", ".join([tournament.name for tournament in tournaments])
|
||||||
|
author_name += f" (P. jury {tournaments})"
|
||||||
|
elif registration.jury_in.exists():
|
||||||
|
tournaments = Tournament.objects.filter(pools__juries=registration).distinct()
|
||||||
|
tournaments = ", ".join([tournament.name for tournament in tournaments])
|
||||||
|
author_name += f" (Juré⋅e {tournaments})"
|
||||||
|
else:
|
||||||
|
if registration.team_id:
|
||||||
|
team = Team.objects.get(id=registration.team_id)
|
||||||
|
author_name += f" ({team.trigram})"
|
||||||
|
else:
|
||||||
|
author_name += " (sans équipe)"
|
||||||
|
|
||||||
|
return author_name
|
||||||
|
|
||||||
|
async def aget_author_name(self):
|
||||||
|
return await sync_to_async(self.get_author_name)()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("message")
|
verbose_name = _("message")
|
||||||
verbose_name_plural = _("messages")
|
verbose_name_plural = _("messages")
|
||||||
|
|
|
@ -37,6 +37,22 @@ function selectChannel(channel_id) {
|
||||||
messageInput.disabled = !channel['write_access']
|
messageInput.disabled = !channel['write_access']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
let messageInput = document.getElementById('input-message')
|
||||||
|
let message = messageInput.value
|
||||||
|
messageInput.value = ''
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
'type': 'send_message',
|
||||||
|
'channel_id': selected_channel_id,
|
||||||
|
'content': message,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
function setChannels(new_channels) {
|
function setChannels(new_channels) {
|
||||||
channels = {}
|
channels = {}
|
||||||
for (let channel of new_channels) {
|
for (let channel of new_channels) {
|
||||||
|
@ -48,6 +64,23 @@ function setChannels(new_channels) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function receiveMessage(message) {
|
||||||
|
let messageList = document.getElementById('message-list')
|
||||||
|
|
||||||
|
let messageElement = document.createElement('li')
|
||||||
|
messageElement.classList.add('list-group-item')
|
||||||
|
messageList.appendChild(messageElement)
|
||||||
|
|
||||||
|
let authorDiv = document.createElement('div')
|
||||||
|
authorDiv.classList.add('text-muted', 'fw-bold')
|
||||||
|
authorDiv.innerText = message['author']
|
||||||
|
messageElement.appendChild(authorDiv)
|
||||||
|
|
||||||
|
let contentDiv = document.createElement('div')
|
||||||
|
contentDiv.innerText = message['content']
|
||||||
|
messageElement.appendChild(contentDiv)
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
/**
|
/**
|
||||||
* Process the received data from the server.
|
* Process the received data from the server.
|
||||||
|
@ -59,6 +92,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
case 'fetch_channels':
|
case 'fetch_channels':
|
||||||
setChannels(data['channels'])
|
setChannels(data['channels'])
|
||||||
break
|
break
|
||||||
|
case 'send_message':
|
||||||
|
receiveMessage(data)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
console.log(data)
|
console.log(data)
|
||||||
console.error('Unknown message type:', data['type'])
|
console.error('Unknown message type:', data['type'])
|
||||||
|
|
|
@ -35,31 +35,20 @@
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body overflow-y-scroll mw-100 h-100 flex-grow-0" id="chat-messages">
|
<div class="card-body overflow-y-scroll mw-100 h-100 flex-grow-0" id="chat-messages">
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush" id="message-list"></ul>
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="fw-bold">Emmy D'Anello (CNO)</div>
|
|
||||||
Message 1
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="fw-bold">Emmy D'Anello (CNO)</div>
|
|
||||||
Message 2
|
|
||||||
</li>
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="fw-bold">Emmy D'Anello (CNO)</div>
|
|
||||||
Message 3
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer mt-auto">
|
<div class="card-footer mt-auto">
|
||||||
<div class="input-group">
|
<form onsubmit="event.preventDefault(); sendMessage()">
|
||||||
<label for="input-message" class="input-group-text">
|
<div class="input-group">
|
||||||
<i class="fas fa-comment"></i>
|
<label for="input-message" class="input-group-text">
|
||||||
</label>
|
<i class="fas fa-comment"></i>
|
||||||
<input type="text" class="form-control" id="input-message" placeholder="{% trans "Send message…" %}" autocomplete="off">
|
</label>
|
||||||
<button class="input-group-text btn btn-success">
|
<input type="text" class="form-control" id="input-message" placeholder="{% trans "Send message…" %}" autocomplete="off">
|
||||||
<i class="fas fa-paper-plane"></i>
|
<button class="input-group-text btn btn-success" type="submit">
|
||||||
</button>
|
<i class="fas fa-paper-plane"></i>
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue