diff --git a/chat/consumers.py b/chat/consumers.py index 75afd75..c356089 100644 --- a/chat/consumers.py +++ b/chat/consumers.py @@ -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']}) diff --git a/chat/models.py b/chat/models.py index 0e4b078..913d8b8 100644 --- a/chat/models.py +++ b/chat/models.py @@ -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") diff --git a/chat/static/chat.js b/chat/static/chat.js index 8e51aec..2265508 100644 --- a/chat/static/chat.js +++ b/chat/static/chat.js @@ -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']) diff --git a/chat/templates/chat/chat.html b/chat/templates/chat/chat.html index 860147b..b3d293a 100644 --- a/chat/templates/chat/chat.html +++ b/chat/templates/chat/chat.html @@ -35,31 +35,20 @@
- +
{% endblock %}