Update to django channels 2

This commit is contained in:
Tom Hubrecht 2022-06-27 15:34:24 +02:00
parent 0065269af5
commit 0cc035d903
14 changed files with 129 additions and 73 deletions

View file

@ -1,8 +1,15 @@
"""
ASGI entrypoint. Configures Django and then runs the application
defined in the ASGI_APPLICATION setting.
"""
import os import os
from channels.asgi import get_channel_layer import django
from channels.routing import get_default_application
if "DJANGO_SETTINGS_MODULE" not in os.environ: if "DJANGO_SETTINGS_MODULE" not in os.environ:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings.local")
channel_layer = get_channel_layer() django.setup()
application = get_default_application()

View file

@ -1,3 +1,18 @@
from channels.routing import include from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
routing = [include("kfet.routing.routing", path=r"^/ws/k-fet")] from kfet.routing import KFRouter
application = ProtocolTypeRouter(
{
# WebSocket chat handler
"websocket": AuthMiddlewareStack(
URLRouter(
[
path("ws/k-fet", KFRouter),
]
)
),
}
)

View file

@ -109,6 +109,8 @@ MEDIA_URL = "/gestion/media/"
CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr") CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr")
ASGI_APPLICATION = "gestioasso.routing.application"
# --- # ---
# Auth-related stuff # Auth-related stuff
# --- # ---
@ -147,7 +149,7 @@ CACHES = {
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
"default": { "default": {
"BACKEND": "asgi_redis.RedisChannelLayer", "BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": { "CONFIG": {
"hosts": [ "hosts": [
( (
@ -160,11 +162,9 @@ CHANNEL_LAYERS = {
) )
] ]
}, },
"ROUTING": "gestioasso.routing.routing",
} }
} }
# --- # ---
# reCAPTCHA settings # reCAPTCHA settings
# https://github.com/praekelt/django-recaptcha # https://github.com/praekelt/django-recaptcha

View file

@ -47,8 +47,7 @@ CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
# Use the default in memory asgi backend for local development # Use the default in memory asgi backend for local development
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
"default": { "default": {
"BACKEND": "asgiref.inmemory.ChannelLayer", "BACKEND": "channels.layers.InMemoryChannelLayer",
"ROUTING": "gestioasso.routing.routing",
} }
} }

View file

@ -4,3 +4,6 @@ from .utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer): class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
groups = ["kfet.kpsul"] groups = ["kfet.kpsul"]
perms_connect = ["kfet.is_team"] perms_connect = ["kfet.is_team"]
async def kpsul(self, event):
await self.send_json(event)

View file

@ -12,13 +12,15 @@ class OpenKfetConsumer(PermConsumerMixin, DjangoJsonWebsocketConsumer):
""" """
def connection_groups(self, user, **kwargs): async def open_status(self, event):
"""Select which group the user should be connected.""" await self.send_json(event)
if kfet_is_team(user):
return ["kfet.open.team"]
return ["kfet.open.base"]
def connect(self, message, *args, **kwargs): async def connect(self):
"""Send current status on connect.""" """Send current status on connect."""
super().connect(message, *args, **kwargs) await super().connect()
self.send(kfet_open.export(message.user))
group = "team" if kfet_is_team(self.user) else "base"
await self.channel_layer.group_add(f"kfet.open.{group}", self.channel_name)
await self.send_json(kfet_open.export(self.user))

View file

@ -1,5 +1,6 @@
from datetime import timedelta from datetime import timedelta
from channels.layers import get_channel_layer
from django.utils import timezone from django.utils import timezone
from ..decorators import kfet_is_team from ..decorators import kfet_is_team
@ -77,7 +78,7 @@ class OpenKfet(CachedMixin, object):
""" """
status = self.status() status = self.status()
base = {"status": status} base = {"status": status, "type": "open.status"}
restrict = { restrict = {
"admin_status": self.admin_status(status), "admin_status": self.admin_status(status),
"force_close": self.force_close, "force_close": self.force_close,
@ -95,13 +96,14 @@ class OpenKfet(CachedMixin, object):
base, team = self._export() base, team = self._export()
return team if kfet_is_team(user) else base return team if kfet_is_team(user) else base
def send_ws(self): async def send_ws(self):
"""Send internal state to websocket channels.""" """Send internal state to websocket channels."""
from .consumers import OpenKfetConsumer
base, team = self._export() base, team = self._export()
OpenKfetConsumer.group_send("kfet.open.base", base)
OpenKfetConsumer.group_send("kfet.open.team", team) channel_layer = get_channel_layer()
await channel_layer.group_send("kfet.open.base", base)
await channel_layer.group_send("kfet.open.team", team)
kfet_open = OpenKfet() kfet_open = OpenKfet()

View file

@ -1,5 +1,10 @@
from channels.routing import route_class from channels.routing import URLRouter
from django.urls import path
from . import consumers from .consumers import OpenKfetConsumer
routing = [route_class(consumers.OpenKfetConsumer)] OpenRouter = URLRouter(
[
path(r"", OpenKfetConsumer),
]
)

View file

@ -1,3 +1,4 @@
from asgiref.sync import async_to_sync
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
@ -18,7 +19,7 @@ def raw_open(request):
raise PermissionDenied raise PermissionDenied
raw_open = request.POST.get("raw_open") in TRUE_STR raw_open = request.POST.get("raw_open") in TRUE_STR
kfet_open.raw_open = raw_open kfet_open.raw_open = raw_open
kfet_open.send_ws() async_to_sync(kfet_open.send_ws)()
return HttpResponse() return HttpResponse()
@ -27,5 +28,5 @@ def raw_open(request):
def force_close(request): def force_close(request):
force_close = request.POST.get("force_close") in TRUE_STR force_close = request.POST.get("force_close") in TRUE_STR
kfet_open.force_close = force_close kfet_open.force_close = force_close
kfet_open.send_ws() async_to_sync(kfet_open.send_ws)()
return HttpResponse() return HttpResponse()

View file

@ -1,8 +1,13 @@
from channels.routing import include, route_class from channels.routing import URLRouter
from django.urls import path
from . import consumers from kfet.open.routing import OpenRouter
routing = [ from .consumers import KPsul
route_class(consumers.KPsul, path=r"^/k-psul/$"),
include("kfet.open.routing.routing", path=r"^/open"), KFRouter = URLRouter(
] [
path("k-psul/", KPsul),
path("open", OpenRouter),
]
)

View file

@ -1,8 +1,7 @@
import json import json
import math import math
from channels.channel import Group from channels.generic.websocket import AsyncJsonWebsocketConsumer
from channels.generic.websockets import JsonWebsocketConsumer
from django.core.cache import cache from django.core.cache import cache
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
@ -63,7 +62,7 @@ class CachedMixin:
# Consumers # Consumers
class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer): class DjangoJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
"""Custom Json Websocket Consumer. """Custom Json Websocket Consumer.
Encode to JSON with DjangoJSONEncoder. Encode to JSON with DjangoJSONEncoder.
@ -71,7 +70,7 @@ class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
""" """
@classmethod @classmethod
def encode_json(cls, content): async def encode_json(cls, content):
return json.dumps(content, cls=DjangoJSONEncoder) return json.dumps(content, cls=DjangoJSONEncoder)
@ -89,31 +88,35 @@ class PermConsumerMixin:
http_user = True # Enable message.user http_user = True # Enable message.user
perms_connect = [] perms_connect = []
def connect(self, message, **kwargs): async def connect(self):
"""Check permissions on connection.""" """Check permissions on connection."""
if message.user.has_perms(self.perms_connect): self.user = self.scope["user"]
super().connect(message, **kwargs)
if self.user.has_perms(self.perms_connect):
await super().connect()
else: else:
self.close() await self.close()
def raw_connect(self, message, **kwargs): # async def raw_connect(self, message, **kwargs):
# Same as original raw_connect method of JsonWebsocketConsumer # # Same as original raw_connect method of JsonWebsocketConsumer
# We add user to connection_groups call. # # We add user to connection_groups call.
groups = self.connection_groups(user=message.user, **kwargs) # groups = self.connection_groups(user=message.user, **kwargs)
for group in groups: # for group in groups:
Group(group, channel_layer=message.channel_layer).add(message.reply_channel) # await self.channel_layer.group_add(group, message.reply_channel)
self.connect(message, **kwargs) # # Group(group, channel_layer=message.channel_layer).add(message.reply_channel)
# self.connect(message, **kwargs)
def raw_disconnect(self, message, **kwargs): #
# Same as original raw_connect method of JsonWebsocketConsumer # async def raw_disconnect(self, message, **kwargs):
# We add user to connection_groups call. # # Same as original raw_connect method of JsonWebsocketConsumer
groups = self.connection_groups(user=message.user, **kwargs) # # We add user to connection_groups call.
for group in groups: # groups = self.connection_groups(user=message.user, **kwargs)
Group(group, channel_layer=message.channel_layer).discard( # for group in groups:
message.reply_channel # await self.channel_layer.group_discard(group, message.reply_channel)
) # # Group(group, channel_layer=message.channel_layer).discard(
self.disconnect(message, **kwargs) # # message.reply_channel
# # )
def connection_groups(self, user, **kwargs): # self.disconnect(message, **kwargs)
"""`message.user` is available as `user` arg. Original behavior.""" #
return super().connection_groups(user=user, **kwargs) # def connection_groups(self, user, **kwargs):
# """`message.user` is available as `user` arg. Original behavior."""
# return super().connection_groups(user=user, **kwargs)

View file

@ -6,6 +6,8 @@ from decimal import Decimal
from typing import List, Tuple from typing import List, Tuple
from urllib.parse import urlencode from urllib.parse import urlencode
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
@ -42,7 +44,7 @@ from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from gestioncof.models import CofProfile from gestioncof.models import CofProfile
from kfet import KFET_DELETED_TRIGRAMME, consumers from kfet import KFET_DELETED_TRIGRAMME
from kfet.auth.decorators import kfet_password_auth from kfet.auth.decorators import kfet_password_auth
from kfet.autocomplete import kfet_account_only_autocomplete, kfet_autocomplete from kfet.autocomplete import kfet_account_only_autocomplete, kfet_autocomplete
from kfet.config import kfet_config from kfet.config import kfet_config
@ -994,8 +996,14 @@ def kpsul_update_addcost(request):
kfet_config.set(addcost_for=account, addcost_amount=amount) kfet_config.set(addcost_for=account, addcost_amount=amount)
data = {"addcost": {"for": account and account.trigramme or None, "amount": amount}} data = {
consumers.KPsul.group_send("kfet.kpsul", data) "addcost": {"for": account and account.trigramme or None, "amount": amount},
"type": "kpsul",
}
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)("kfet.kpsul", data)
return JsonResponse(data) return JsonResponse(data)
@ -1179,7 +1187,7 @@ def kpsul_perform_operations(request):
) )
# Websocket data # Websocket data
websocket_data = {} websocket_data = {"type": "kpsul"}
websocket_data["groups"] = [ websocket_data["groups"] = [
{ {
"add": True, "add": True,
@ -1226,7 +1234,10 @@ def kpsul_perform_operations(request):
websocket_data["articles"].append( websocket_data["articles"].append(
{"id": article["id"], "stock": article["stock"]} {"id": article["id"], "stock": article["stock"]}
) )
consumers.KPsul.group_send("kfet.kpsul", websocket_data)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)("kfet.kpsul", websocket_data)
return JsonResponse(data) return JsonResponse(data)
@ -1421,7 +1432,7 @@ def cancel_operations(request):
articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk) articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk)
# Websocket data # Websocket data
websocket_data = {"checkouts": [], "articles": []} websocket_data = {"checkouts": [], "articles": [], "type": "kpsul"}
for checkout in checkouts: for checkout in checkouts:
websocket_data["checkouts"].append( websocket_data["checkouts"].append(
{"id": checkout["id"], "balance": checkout["balance"]} {"id": checkout["id"], "balance": checkout["balance"]}
@ -1430,7 +1441,10 @@ def cancel_operations(request):
websocket_data["articles"].append( websocket_data["articles"].append(
{"id": article["id"], "stock": article["stock"]} {"id": article["id"], "stock": article["stock"]}
) )
consumers.KPsul.group_send("kfet.kpsul", websocket_data)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)("kfet.kpsul", websocket_data)
data["canceled"] = list(opes) data["canceled"] = list(opes)
data["opegroups_to_update"] = list(opegroups) data["opegroups_to_update"] = list(opegroups)

View file

@ -6,7 +6,7 @@ psycopg2<2.8
# Redis # Redis
django-redis-cache==2.1.* django-redis-cache==2.1.*
redis~=2.10.6 redis~=2.10.6
asgi-redis==1.4.* channels-redis==2.4.*
# ASGI protocol and HTTP server # ASGI protocol and HTTP server
asgiref~=1.1.2 asgiref~=1.1.2

View file

@ -1,7 +1,7 @@
Django==2.2.* Django==2.2.*
Pillow==7.2.0 Pillow==7.2.0
authens==0.1b0 authens==0.1b0
channels==1.1.* channels==2.4.*
configparser==3.5.0 configparser==3.5.0
django-autocomplete-light==3.3.* django-autocomplete-light==3.3.*
django-bootstrap-form==3.3 django-bootstrap-form==3.3