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 django.contrib.auth.models import User
from participation.models import Team, Pool, Tournament
from registration.models import Registration
from .models import Channel
from .models import Channel, Message
class ChatConsumer(AsyncJsonWebsocketConsumer):
@ -34,6 +35,10 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
# Accept the connection
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:
"""
Called when the websocket got disconnected, for any reason.
@ -43,6 +48,10 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
# User is not authenticated
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):
"""
Called when the client sends us some data, parsed as JSON.
@ -51,6 +60,8 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
match content['type']:
case 'fetch_channels':
await self.fetch_channels()
case 'send_message':
await self.receive_message(content)
case unknown:
print("Unknown message type:", unknown)
@ -59,7 +70,6 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
read_channels = await Channel.get_accessible_channels(user, 'read')
write_channels = await Channel.get_accessible_channels(user, 'write')
print([channel async for channel in write_channels.all()])
message = {
'type': 'fetch_channels',
'channels': [
@ -73,3 +83,29 @@ class ChatConsumer(AsyncJsonWebsocketConsumer):
]
}
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
# SPDX-License-Identifier: GPL-3.0-or-later
from asgiref.sync import sync_to_async
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Q, QuerySet
from django.utils.text import format_lazy
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 tfjm.permissions import PermissionType
@ -141,9 +142,6 @@ class Channel(models.Model):
qs |= Channel.objects.filter(invited=user)
print(user)
print(qs.query)
return qs
class Meta:
@ -182,6 +180,65 @@ class Message(models.Model):
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:
verbose_name = _("message")
verbose_name_plural = _("messages")

View File

@ -37,6 +37,22 @@ function selectChannel(channel_id) {
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) {
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', () => {
/**
* Process the received data from the server.
@ -59,6 +92,9 @@ document.addEventListener('DOMContentLoaded', () => {
case 'fetch_channels':
setChannels(data['channels'])
break
case 'send_message':
receiveMessage(data)
break
default:
console.log(data)
console.error('Unknown message type:', data['type'])

View File

@ -35,31 +35,20 @@
</h3>
</div>
<div class="card-body overflow-y-scroll mw-100 h-100 flex-grow-0" id="chat-messages">
<ul class="list-group list-group-flush">
<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>
<ul class="list-group list-group-flush" id="message-list"></ul>
</div>
<div class="card-footer mt-auto">
<div class="input-group">
<label for="input-message" class="input-group-text">
<i class="fas fa-comment"></i>
</label>
<input type="text" class="form-control" id="input-message" placeholder="{% trans "Send message" %}" autocomplete="off">
<button class="input-group-text btn btn-success">
<i class="fas fa-paper-plane"></i>
</button>
</div>
<form onsubmit="event.preventDefault(); sendMessage()">
<div class="input-group">
<label for="input-message" class="input-group-text">
<i class="fas fa-comment"></i>
</label>
<input type="text" class="form-control" id="input-message" placeholder="{% trans "Send message" %}" autocomplete="off">
<button class="input-group-text btn btn-success" type="submit">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</form>
</div>
</div>
{% endblock %}