mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 09:42:10 +01:00 
			
		
		
		
	@@ -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']})
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 
 | 
			
		||||
@@ -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'])
 | 
			
		||||
 
 | 
			
		||||
@@ -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 %}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user