mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2024-12-24 03:02:23 +00:00
First play with websockets
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
This commit is contained in:
parent
30efff0d9d
commit
b9ce4c737c
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:
|
||||
|
Loading…
Reference in New Issue
Block a user