access django channels' consumer class variables from outside - django

class ExampleConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.id = 1
self.foo = 'bar'
await self.accept()
Is it possible to get all existing instances of ExampleConsumer, filter them by id and get foo value? Somewhere in a django view

You can get all instance with gc.get_objects so:
import gc
class ExampleConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.id = 1
self.foo = 'bar'
await self.accept()
def get_inc(cls):
return [obj for obj in gc.get_objects() if isinstance(obj, cls)]
for i in get_inc(ExampleConsumer):
print(i.foo)

Related

Get Session Data in Django Consumer

I want to get session data in consumers.py, this session data will be used to filter data. The code like this.
queryset = Mail.objects.filter(status='session_data')
serializer_class = PostSerializer
permissions = permissions.AllowAny
async def connect(self, **kwargs):
await self.model_change.subscribe()
await super().connect()
#model_observer(Mail)
async def model_change(self, message, observer=None, **kwargs):
await self.send_json(message)
#model_change.serializer
def model_serialize(self, instance, action, **kwargs):
return dict(data=PostSerializer(instance=instance).data, action=action.value)
How can i get it ?

can't use setUpTestData variable in setUp

i define this line of code in
but i got self.user not define user
How can I use the variables defined in setUp func it?
class TestSample(TestCase):
#classmethod
def setUpTestData(cls):
super().setUpTestData()
business = BusinessFactory()
cls.user = business.main_owner
def setUp(self):
self.api_client = APIClient()
self.client, self.token, self.user = self.client_login(
self.api_client,
phone=self.user
)

Django Channels Websocket group name

Sorry for bad English :(
I want to create notification system per user. User has own group/room and every notification go to specific user's notification room. Every time when user connect to websocket, user creates the same id of user. self.scope["user"]. Therefore only user_notification_1 group name was created. How it is possible to create group name depending on user?
I use
application = ProtocolTypeRouter(
{
"websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
}
)
Code:
import json
from channels.generic.websocket import WebsocketConsumer
class NotificationConsumer(WebsocketConsumer):
async def connect(self):
self.user = self.scope["user"]
if self.user.is_authenticated:
self.room_group_name = f"user_notification_{self.user.id}"
else:
self.room_group_name = "anonymous"
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
async def receive(self, text_data=None, bytes_data=None):
self.user = self.scope["user"]
if self.user.is_authenticated:
await self.send(
text_data=json.dumps({"message": "pong"})
)
else:
await self.send(
text_data=json.dumps({"type": "error", "code": "UserNotAuthenticated"})
)
async def new_chat_message(self, event):
await self.send(text_data=json.dumps(event.get("data")))
async def connect_successful(self, event):
await self.send(text_data=json.dumps(event.get("data")))
UPD My problem was solved when I noticed that I disabled REST_SESSION_LOGIN. I use dj-rest-auth

Django Channels middleware use ORM

I have troubles checking the user token inside of middleware. I'm getting token from cookies and then I need to query database to check if this token exists and belongs to user that made a request.
routing.py
from channels.routing import ProtocolTypeRouter, URLRouter
import game.routing
from authentication.utils import TokenAuthMiddlewareStack
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': TokenAuthMiddlewareStack(
URLRouter(
game.routing.websocket_urlpatterns
)
),
})
middleware.py
from rest_framework.authentication import TokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_auth.models import TokenModel
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
...
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
close_old_connections()
headers = dict(scope['headers'])
if b'Authorization' in headers[b'cookie']:
try:
cookie_str = headers[b'cookie'].decode('utf-8')
try: # no cookie Authorization=Token in the request
token_str = [x for x in cookie_str.split(';') if re.search(' Authorization=Token', x)][0].strip()
except IndexError:
scope['user'] = AnonymousUser()
return self.inner(scope)
token_name, token_key = token_str.replace('Authorization=', '').split()
if token_name == 'Token':
token = TokenModel.objects.get(key=token_key)
scope['user'] = token.user
except TokenModel.DoesNotExist:
scope['user'] = AnonymousUser()
return self.inner(scope)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
And this gives me
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
I also tried the following approaches
async def __call__(self, scope):
...
if token_name == 'Token':
token = await self.get_token(token_key)
scope['user'] = token.user
...
# approach 1
#sync_to_async
def get_token(self, token_key):
return TokenModel.objects.get(key=token_key)
# approach 2
#database_sync_to_async
def get_token(self, token_key):
return TokenModel.objects.get(key=token_key)
Those approaches give the following error
[Failure instance: Traceback: <class 'TypeError'>: 'coroutine' object is not callable
/Users/nikitatonkoshkur/Documents/work/svoya_igra/venv/lib/python3.8/site-packages/autobahn/websocket/protocol.py:2847:processHandshake
/Users/nikitatonkoshkur/Documents/work/svoya_igra/venv/lib/python3.8/site-packages/txaio/tx.py:366:as_future
/Users/nikitatonkoshkur/Documents/work/svoya_igra/venv/lib/python3.8/site-packages/twisted/internet/defer.py:151:maybeDeferred
/Users/nikitatonkoshkur/Documents/work/svoya_igra/venv/lib/python3.8/site-packages/daphne/ws_protocol.py:72:onConnect
--- <exception caught here> ---
/Users/nikitatonkoshkur/Documents/work/svoya_igra/venv/lib/python3.8/site-packages/twisted/internet/defer.py:151:maybeDeferred
/Users/nikitatonkoshkur/Documents/work/svoya_igra/venv/lib/python3.8/site-packages/daphne/server.py:206:create_application
]```
I am not sure if it will work or not,but you can try
First write the get_token function outside the class.
# approach 1
#sync_to_async
def get_token(self, token_key):
return TokenModel.objects.get(key=token_key)
then in your async function write get_token() instead of self.get_token()
async def __call__(self, scope):
...
if token_name == 'Token':
token = await get_token(token_key)
scope['user'] = token.user
...
Having looked at the source code of django-channels, especially at how SessionMiddleware works and uses django ORM, I ended up writing my TokenMiddleware in a similar fashion.
class TokenAuthMiddlewareInstance:
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, scope, middleware):
self.middleware = middleware
self.scope = dict(scope)
self.inner = self.middleware.inner
async def __call__(self, receive, send):
close_old_connections()
headers = dict(self.scope.get('headers', {}))
if b'cookie' in headers:
cookie_dict = parse_cookie(headers[b'cookie'].decode("ascii"))
token = cookie_dict.get('Authorization', '')
token_name, token_key = token.replace('Authorization=', '').split()
if token_name == 'Token':
self.scope['user'] = await self.get_user_from_token(token_key)
inner = self.inner(self.scope)
return await inner(receive, send)
inner = self.inner(self.scope)
self.scope['user'] = AnonymousUser()
return await inner(receive, send)
#staticmethod
#database_sync_to_async
def get_user_from_token(token_key):
try:
return TokenModel.objects.select_related('user').get(key=token_key).user
except TokenModel.DoesNotExist:
return AnonymousUser()
class TokenAuthMiddleware:
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
return TokenAuthMiddlewareInstance(scope, self)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))

Send notification on post_save signal in django

I have a model called deposit and I am trying to send real time notification when a new row is added in table. Is it possible to do so using django-channels ?
You can use the save method of your Django model, like so:
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
class Info(models.Model):
# your model fields
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
super(Info, self).save(force_insert, force_update, using, update_fields)
# send info to channel
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
'infochannel',
{
'type': 'infochannel.message',
'device_id': str(self.device_id)
}
)
And in the consumer:
from channels.generic.websocket import AsyncWebsocketConsumer
class DataConsumer(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.group_name = ''
async def connect(self):
# we are using one fixed group
self.group_name = 'infochannel'
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard('infochannel', self.channel_name)
async def infochannel_message(self, event):
# Send message to websocket group
await self.send(text_data=event['device_id'])
Where device_id is a field on my model, and of course you also have to set up routing, redis_channels, and so on.
You can simply add a signal for a post_save
#receiver(post_save, sender=Deposit)
def signal_deposit_save(sender, instance, created, **kwargs):
if created: # This means it is a new row
# Send notification using django-channels