Skip to content

Commit f2a5963

Browse files
committed
Add support for dynamically joining ws groups
1 parent 791e377 commit f2a5963

File tree

4 files changed

+127
-15
lines changed

4 files changed

+127
-15
lines changed

lego/apps/comments/websockets.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
from lego.apps.comments.serializers.sockets import CommentSocketSerializer
66
from lego.apps.websockets.notifiers import notify_group
7+
from lego.apps.websockets.groups import group_for_content_model
78

89
if TYPE_CHECKING:
910
from lego.apps.comments.models import Comment
1011

1112

1213
def notify_comment(action_type: str, comment: Comment, **kwargs):
13-
group = "global"
14-
# group = group_for_content_target(comment.content_object)
14+
# group = "global"
15+
group = group_for_content_model(comment)
1516
serializer = CommentSocketSerializer(
1617
{
1718
"type": action_type,

lego/apps/websockets/constants.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
WS_STATUS_CONNECTED = "CONNECTED"
2+
WS_STATUS_CLOSED = "CLOSED"
3+
WS_STATUS_ERROR = "ERROR"
4+
5+
WS_GROUP_TYPES = [
6+
"global"
7+
"user",
8+
"event",
9+
"comment"
10+
]
11+
12+
"""
13+
Custom Websocket actions matching frontend A
14+
"""
15+
WS_GROUP_JOIN_BEGIN = "Websockets.GROUP_JOIN.BEGIN"
16+
WS_GROUP_JOIN_SUCCESS = "Websockets.GROUP_JOIN.SUCCESS"
17+
WS_GROUP_JOIN_FAILURE = "Websockets.GROUP_JOIN.FAILURE"
18+
19+
WS_GROUP_LEAVE_BEGIN = "Websockets.GROUP_LEAVE.BEGIN"
20+
WS_GROUP_LEAVE_SUCCESS = "Websockets.GROUP_LEAVE.SUCCESS"
21+
WS_GROUP_LEAVE_FAILURE = "Websockets.GROUP_LEAVE.FAILURE"

lego/apps/websockets/consumers.py

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,78 @@
11
from asgiref.sync import AsyncToSync
2-
from channels.generic.websocket import WebsocketConsumer
2+
from channels.generic.websocket import JsonWebsocketConsumer
33

44
from lego.apps.events.websockets import find_event_groups
5+
from lego.apps.websockets.groups import group_for_user, verify_group_access
56
from lego.apps.users.models import User
6-
from lego.apps.websockets.groups import group_for_user
7-
7+
from lego.apps.websockets import constants
88

99
def find_groups(user: User):
1010
return ["global", group_for_user(user.pk)] + find_event_groups(user)
1111

1212

13-
class GroupConsumer(WebsocketConsumer):
13+
class GroupConsumer(JsonWebsocketConsumer):
14+
"""
15+
Custom consumer for handling websocket groups.
16+
17+
Create own logic for tracking user groups as the WebsocketConsumer groups functionality
18+
does not have access to the user object.
19+
"""
20+
user_groups = set()
21+
user = None
22+
23+
def debug(self, message):
24+
if self.user:
25+
print(f"[{self.user.username.upper()}] {message}")
26+
else:
27+
print(f"[NO USER IN SCOPE] {message}")
28+
1429
def connect(self):
15-
self.accept()
16-
for group in find_groups(self.scope["user"]):
30+
user = self.scope["user"]
31+
for group in find_groups(user):
1732
AsyncToSync(self.channel_layer.group_add)(group, self.channel_name)
18-
19-
def disconnect(self, message):
20-
for group in find_groups(self.scope["user"]):
33+
self.user = user
34+
self.user_groups = set(find_groups(user))
35+
self.accept()
36+
37+
def disconnect(self, code):
38+
for group in self.user_groups:
2139
AsyncToSync(self.channel_layer.group_discard)(group, self.channel_name)
22-
40+
self.user_groups.clear()
41+
42+
def receive_json(self, content, **kwargs):
43+
type = content.get("type")
44+
payload = content.get("payload")
45+
46+
if type == constants.WS_GROUP_JOIN_BEGIN:
47+
group = payload.get("group")
48+
if self.user and verify_group_access(self.user, group):
49+
AsyncToSync(self.channel_layer.group_add)(group, self.channel_name)
50+
self.user_groups.add(group)
51+
self.send_message(constants.WS_GROUP_JOIN_SUCCESS, payload={ "group": group })
52+
else:
53+
self.send_message(constants.WS_GROUP_JOIN_FAILURE, payload={ "group": group })
54+
55+
if type == constants.WS_GROUP_LEAVE_BEGIN:
56+
group = payload.get("group")
57+
if group in self.groups:
58+
AsyncToSync(self.channel_layer.group_discard)(group, self.channel_name)
59+
self.user_groups.remove(group)
60+
self.send_message(constants.WS_GROUP_LEAVE_SUCCESS)
61+
62+
63+
def send_message(self, type: str, payload=None, meta=None):
64+
"""
65+
Send message on standardised format.
66+
"""
67+
content = {
68+
"type": type,
69+
"payload": payload,
70+
"meta": meta
71+
}
72+
self.send_json(content)
73+
2374
def notification_message(self, event):
2475
self.send(text_data=event["text"])
76+
77+
78+

lego/apps/websockets/groups.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from typing import TYPE_CHECKING
44

5+
from lego.apps.users.models import User
56
from lego.utils.content_types import instance_to_string
7+
from lego.utils.content_types import string_to_instance
8+
from lego.apps.permissions.constants import LIST
69

710
if TYPE_CHECKING:
811
from lego.apps.events.models import Event
@@ -16,6 +19,39 @@ def group_for_event(event: Event, has_registrations_access: bool) -> str:
1619
return f"event-{'full' if has_registrations_access else 'limited'}-{event.pk}"
1720

1821

19-
def group_for_content_target(content_target) -> str:
20-
content_target_string = instance_to_string(content_target)
21-
return f"comment-{content_target_string}"
22+
def group_for_content_model(model) -> str:
23+
modelname = model._meta.model_name
24+
content_target_string = instance_to_string(model.content_object)
25+
return f"{modelname}-{content_target_string}"
26+
27+
28+
def verify_group_access(user: User, group):
29+
if not group:
30+
return False
31+
32+
group_type, rest = group.split("-", 1)
33+
34+
if group_type == "comment":
35+
content_target = string_to_instance(rest)
36+
if user and content_target:
37+
return user.has_perm(LIST, content_target)
38+
39+
# if group_type == WS_GROUP_TYPE_USER:
40+
# user_id = rest
41+
# return user_id == str(user.pk)
42+
43+
# if group_type == WS_GROUP_TYPE_EVENT:
44+
# event_access, event_id = rest.split("-", 1)
45+
# """Not implemented"""
46+
47+
48+
return False
49+
50+
51+
def stringify_group(group):
52+
pass
53+
54+
# if content_target:
55+
# return f"{group.type}-{content_target}"
56+
57+
# return str(group.type)

0 commit comments

Comments
 (0)