mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 03:02:14 +01:00 
			
		
		
		
	First play with websockets
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
		
							
								
								
									
										45
									
								
								draw/consumers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								draw/consumers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from asgiref.sync import sync_to_async
 | 
			
		||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
 | 
			
		||||
 | 
			
		||||
from participation.models import Tournament
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DrawConsumer(AsyncJsonWebsocketConsumer):
 | 
			
		||||
    async def connect(self):
 | 
			
		||||
        tournament_id = self.scope['url_route']['kwargs']['tournament_id']
 | 
			
		||||
        self.tournament = await sync_to_async(Tournament.objects.get)(pk=tournament_id)
 | 
			
		||||
 | 
			
		||||
        user = self.scope['user']
 | 
			
		||||
        reg = user.registration
 | 
			
		||||
        if reg.is_volunteer and not reg.is_admin and self.tournament not in reg.interesting_tournaments \
 | 
			
		||||
            or not reg.is_volunteer and reg.team.participation.tournament != self.tournament:
 | 
			
		||||
            # This user may not have access to the drawing session
 | 
			
		||||
            await self.close()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        await self.accept()
 | 
			
		||||
        await self.channel_layer.group_add(f"tournament-{self.tournament.id}", self.channel_name)
 | 
			
		||||
 | 
			
		||||
    async def disconnect(self, close_code):
 | 
			
		||||
        await self.channel_layer.group_discard(f"tournament-{self.tournament.id}", self.channel_name)
 | 
			
		||||
 | 
			
		||||
    async def receive_json(self, content, **kwargs):
 | 
			
		||||
        message = content["message"]
 | 
			
		||||
 | 
			
		||||
        print(self.scope)
 | 
			
		||||
 | 
			
		||||
        # TODO: Implement drawing system, instead of making a simple chatbot
 | 
			
		||||
        await self.channel_layer.group_send(
 | 
			
		||||
            f"tournament-{self.tournament.id}",
 | 
			
		||||
            {
 | 
			
		||||
                "type": "draw.message",
 | 
			
		||||
                "username": self.scope["user"].username,
 | 
			
		||||
                "message": message,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    async def draw_message(self, event):
 | 
			
		||||
        print(event)
 | 
			
		||||
        await self.send_json({"message": event['message']})
 | 
			
		||||
							
								
								
									
										7
									
								
								draw/routing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								draw/routing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
from django.urls import path
 | 
			
		||||
 | 
			
		||||
from . import consumers
 | 
			
		||||
 | 
			
		||||
websocket_urlpatterns = [
 | 
			
		||||
    path("ws/draw/<int:tournament_id>/", consumers.DrawConsumer.as_asgi()),
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										37
									
								
								draw/static/draw.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								draw/static/draw.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
const tournaments = JSON.parse(document.getElementById('tournaments_list').textContent)
 | 
			
		||||
const sockets = {}
 | 
			
		||||
 | 
			
		||||
for (let tournament of tournaments) {
 | 
			
		||||
    let socket = new WebSocket(
 | 
			
		||||
        'ws://' + window.location.host + '/ws/draw/' + tournament.id + '/'
 | 
			
		||||
    )
 | 
			
		||||
    sockets[tournament.id] = socket
 | 
			
		||||
 | 
			
		||||
    // TODO: For now, we only have a chatbot. Need to implementthe drawing interface
 | 
			
		||||
    socket.onmessage = function(e) {
 | 
			
		||||
        console.log(e.data)
 | 
			
		||||
        const data = JSON.parse(e.data)
 | 
			
		||||
        console.log(data)
 | 
			
		||||
        document.querySelector('#chat-log-' + tournament.id).value += (data.message + '\n')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    socket.onclose = function(e) {
 | 
			
		||||
        console.error('Chat socket closed unexpectedly')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    document.querySelector('#chat-message-' + tournament.id + '-input').focus();
 | 
			
		||||
    document.querySelector('#chat-message-' + tournament.id + '-input').onkeyup = function(e) {
 | 
			
		||||
        if (e.keyCode === 13) {  // enter, return
 | 
			
		||||
            document.querySelector('#chat-message-' + tournament.id + '-submit').click();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    document.querySelector('#chat-message-' + tournament.id + '-submit').onclick = function(e) {
 | 
			
		||||
        const messageInputDom = document.querySelector('#chat-message-' + tournament.id + '-input');
 | 
			
		||||
        const message = messageInputDom.value;
 | 
			
		||||
        socket.send(JSON.stringify({
 | 
			
		||||
            'message': message
 | 
			
		||||
        }));
 | 
			
		||||
        messageInputDom.value = '';
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,34 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load static %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
Hello world!
 | 
			
		||||
    <ul class="nav nav-tabs" id="tournaments-tab" role="tablist">
 | 
			
		||||
        {% for tournament in tournaments %}
 | 
			
		||||
            <li class="nav-item" role="presentation">
 | 
			
		||||
                <button class="nav-link{% if forloop.first %} active{% endif %}"
 | 
			
		||||
                        id="tab-{{ tournament.name|slugify }}" data-bs-toggle="tab"
 | 
			
		||||
                        data-bs-target="#tab-{{ tournament.name|slugify }}-pane" type="button" role="tab"
 | 
			
		||||
                        aria-controls="tab-{{ tournament.name|slugify }}-pane" aria-selected="true">
 | 
			
		||||
                    {{ tournament.name }}
 | 
			
		||||
                </button>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <div class="tab-content" id="tab-content">
 | 
			
		||||
    {% for tournament in tournaments %}
 | 
			
		||||
        <div class="tab-pane fade{% if forloop.first %} show active{% endif %}"
 | 
			
		||||
             id="tab-{{ tournament.name|slugify }}-pane" role="tabpanel"
 | 
			
		||||
             aria-labelledby="tab-{{ tournament.name|slugify }}" tabindex="0">
 | 
			
		||||
            {% include "draw/tournament_content.html" with tournament=tournament %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extrajavascript %}
 | 
			
		||||
    {{ tournaments|json_script:'tournaments_list' }}
 | 
			
		||||
 | 
			
		||||
    <script src="{% static 'draw.js' %}"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								draw/templates/draw/tournament_content.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								draw/templates/draw/tournament_content.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
{{ tournament.name }}
 | 
			
		||||
 | 
			
		||||
<!-- This is only a test -->
 | 
			
		||||
<textarea id="chat-log-{{ tournament.id }}"></textarea>
 | 
			
		||||
<input id="chat-message-{{ tournament.id }}-input">
 | 
			
		||||
<input id="chat-message-{{ tournament.id }}-submit" type="submit">
 | 
			
		||||
@@ -1,8 +1,27 @@
 | 
			
		||||
# Copyright (C) 2023 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.views.generic import TemplateView
 | 
			
		||||
 | 
			
		||||
from participation.models import Tournament
 | 
			
		||||
 | 
			
		||||
class DisplayView(TemplateView):
 | 
			
		||||
 | 
			
		||||
class DisplayView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
    template_name = 'draw/index.html'
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
        reg = self.request.user.registration
 | 
			
		||||
        if reg.is_admin:
 | 
			
		||||
            tournaments = Tournament.objects.all()
 | 
			
		||||
        elif reg.is_volunteer:
 | 
			
		||||
            tournaments = reg.interesting_tournaments
 | 
			
		||||
        else:
 | 
			
		||||
            tournaments = [reg.team.participation.tournament]
 | 
			
		||||
        context['tournaments'] = [{'id': t.id, 'name': t.name} for t in tournaments]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return context
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
channels[daphne]~=4.0.0
 | 
			
		||||
crispy-bootstrap5~=0.7
 | 
			
		||||
Django>=4.1,<5.0
 | 
			
		||||
django-cas-server~=2.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								tfjm/asgi.py
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								tfjm/asgi.py
									
									
									
									
									
								
							@@ -12,8 +12,20 @@ https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from channels.auth import AuthMiddlewareStack
 | 
			
		||||
from channels.routing import ProtocolTypeRouter, URLRouter
 | 
			
		||||
from channels.security.websocket import AllowedHostsOriginValidator
 | 
			
		||||
from django.core.asgi import get_asgi_application
 | 
			
		||||
 | 
			
		||||
import draw.routing
 | 
			
		||||
 | 
			
		||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
 | 
			
		||||
 | 
			
		||||
application = get_asgi_application()
 | 
			
		||||
application = ProtocolTypeRouter(
 | 
			
		||||
    {
 | 
			
		||||
        "http": get_asgi_application(),
 | 
			
		||||
        "websocket": AllowedHostsOriginValidator(
 | 
			
		||||
            AuthMiddlewareStack(URLRouter(draw.routing.websocket_urlpatterns))
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,8 @@ ALLOWED_HOSTS = ['*']
 | 
			
		||||
# Application definition
 | 
			
		||||
 | 
			
		||||
INSTALLED_APPS = [
 | 
			
		||||
    'daphne',
 | 
			
		||||
 | 
			
		||||
    'django.contrib.admin',
 | 
			
		||||
    'django.contrib.auth',
 | 
			
		||||
    'django.contrib.contenttypes',
 | 
			
		||||
@@ -51,6 +53,7 @@ INSTALLED_APPS = [
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    'django.forms',
 | 
			
		||||
 | 
			
		||||
    'channels',
 | 
			
		||||
    'crispy_forms',
 | 
			
		||||
    'crispy_bootstrap5',
 | 
			
		||||
    'django_filters',
 | 
			
		||||
@@ -111,6 +114,7 @@ TEMPLATES = [
 | 
			
		||||
 | 
			
		||||
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
 | 
			
		||||
 | 
			
		||||
ASGI_APPLICATION = 'tfjm.asgi.application'
 | 
			
		||||
WSGI_APPLICATION = 'tfjm.wsgi.application'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -249,6 +253,13 @@ FORBIDDEN_TRIGRAMS = [
 | 
			
		||||
    "SEX",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# TODO: Use a redis server in production
 | 
			
		||||
CHANNEL_LAYERS = {
 | 
			
		||||
    "default": {
 | 
			
		||||
        "BACKEND": "channels.layers.InMemoryChannelLayer"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if os.getenv("TFJM_STAGE", "dev") == "prod":  # pragma: no cover
 | 
			
		||||
    from .settings_prod import *    # noqa: F401,F403
 | 
			
		||||
else:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user