Send messages

Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
Emmy D'Anello 2024-04-27 12:59:50 +02:00
parent f3a4a99b78
commit 4a78e80399
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
4 changed files with 147 additions and 29 deletions

View File

@ -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']})

View File

@ -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")

View File

@ -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'])

View File

@ -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 %}