mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2024-12-25 06:22:22 +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" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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 %}
|
{% 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
|
# Copyright (C) 2023 by Animath
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# 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 django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from participation.models import Tournament
|
||||||
|
|
||||||
class DisplayView(TemplateView):
|
|
||||||
|
class DisplayView(LoginRequiredMixin, TemplateView):
|
||||||
template_name = 'draw/index.html'
|
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
|
crispy-bootstrap5~=0.7
|
||||||
Django>=4.1,<5.0
|
Django>=4.1,<5.0
|
||||||
django-cas-server~=2.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
|
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
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
import draw.routing
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
|
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
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'daphne',
|
||||||
|
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
@ -51,6 +53,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.forms',
|
'django.forms',
|
||||||
|
|
||||||
|
'channels',
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'crispy_bootstrap5',
|
'crispy_bootstrap5',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
@ -111,6 +114,7 @@ TEMPLATES = [
|
|||||||
|
|
||||||
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
||||||
|
|
||||||
|
ASGI_APPLICATION = 'tfjm.asgi.application'
|
||||||
WSGI_APPLICATION = 'tfjm.wsgi.application'
|
WSGI_APPLICATION = 'tfjm.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
@ -249,6 +253,13 @@ FORBIDDEN_TRIGRAMS = [
|
|||||||
"SEX",
|
"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
|
if os.getenv("TFJM_STAGE", "dev") == "prod": # pragma: no cover
|
||||||
from .settings_prod import * # noqa: F401,F403
|
from .settings_prod import * # noqa: F401,F403
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user