1

I'm learning Django and I'm working in a webpage where I need to offer the user the possibility to log in to an external service. I can't simply use the traditional Django views system because otherwise, I would lose the connection with a simple refresh. For that reason, I thought about using Django Channels.

My problem now is how do I send the data to the consumer class? Using the consumers.py given in the tutorial, I would like to send data from a form submission to the connect function and then make the connection if the login to the external service is ok. Then, in that scenario, I could use the clientinstance and the methods from these external services.

So, in short, is it possible to send form data the consumer? And would this be ok, with respect to security in case of sensitive data?

from channels.generic.websocket import AsyncWebsocketConsumer
import json

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):

        ######
        ## login to external service
        ######

        #get login data from form submited when the websockted is initiated
        username = ...
        pass = ...

        self.client = Client(username, password)
        if  client:       
            await self.accept()

    # Receive message from room group
    async def chat_message(self, event):
        message = event['message']

        self.client.send(event['message'])

Update:

To clear the explanation: I can't save the user username and pass of the external service, and that I want to offer the user the possibility to use this [sms service](https://clxcommunications.github.io/sdk-xms-python/tutorial.html) with a text field and phone number.

So the problem is that even if I create a form and the username and password to log in (in the view) with

client = clx.xms.Client('myserviceplan', 'mytoken')

then in next request, I would lose the client instance. That's why I thought about Django Channels. But I'm not really sure if it is the best solution...

Miguel
  • 2,738
  • 3
  • 35
  • 51
  • You could just cache the credentials in the browser or as cookies. In any case, if you really want to use channels to achieve this, the client has to first connect via websocket to the server and you add him to a group made from his id or something unique to him. Then when he submits the form, you use the channel_layer like I did below to call some method (not connect) in the consumer that takes the credentials and creates the client if it does not exist. And then goes on to make the required requests to the service. That's a really weired way to go about it though – Ken4scholars Mar 13 '19 at 14:06
  • Ok, thanks for the advice! – Miguel Mar 13 '19 at 15:50

2 Answers2

3

This will help you.

consumers.py

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
import json

class EventConsumer(WebsocketConsumer):
    def connect(self):
        # self.room_name = self.scope['url_route']['kwargs']['room_name']
        # self.room_group_name = 'chat_%s' % self.room_name
        self.room_name = 'event'
        self.room_group_name = self.room_name+"_sharif"
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )
        print(self.room_group_name)
        self.accept()
        print("#######CONNECTED############")

    def disconnect(self, code):
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )
        print("DISCONNECED CODE: ",code)

    def receive(self, text_data=None, bytes_data=None):
        print(" MESSAGE RECEIVED")
        data = json.loads(text_data)
        message = data['message']
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,{
                "type": 'send_message_to_frontend',
                "message": message
            }
        )
    def send_message_to_frontend(self,event):
        print("EVENT TRIGERED")
        # Receive message from room group
        message = event['message']
        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))

then call the function outside/anywhere from your app like

def event_triger():
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        'event_sharif',
        {
            'type': 'send_message_to_frontend',
            'message': "event_trigered_from_views"
        }
    ) 
# here 'event_sharif' is your room_group_name as i defined before in consumer
# 'type' is like a command, for which method you wants to trigger in your consumer

for more you may take a look Send message using Django Channels from outside Consumer class

Community
  • 1
  • 1
sharif_42
  • 511
  • 4
  • 11
0

Generally, you can call a method in the consumer from an external code in the following way:

from channels.layers import get_channel_layer
channel_layer = get_channel_layer()

await self.channel_layer.send(
            '<channel_name>',
            {
                'type': '<method_name>',
            }
        )

But as you can see, this requires that you specify the channel name which you can only get after the client has connected. In other words, you should not try to call connect but some other method in the consumer. Also, your client should first connect to the websocket before you eventually access it. I don't understand your use case completey but I hope this gives you an idea

Ken4scholars
  • 6,076
  • 2
  • 21
  • 38
  • My main problem is that I need to send and keep alive an instance of an external service. To sum up, I want to create a web app with django that will allow the user to login to an external service such as this [clx sms service](https://clxcommunications.github.io/sdk-xms-python/tutorial.html). At some point, the user will fill a form with username and pass of this external service and be redirected to another page where he/she can use the service. But for that, I need to keep in save instance `client`alive. Any idea how can I do that? – Miguel Mar 13 '19 at 12:15
  • PS: `self.channel_layer.send()` won't work because the object to send can't is not serializable. – Miguel Mar 13 '19 at 12:21
  • I'm not exactly sure I understood you and how channels comes into play here. If the user must provide his login and password to the external service through your application, why not just store it and use it in subsequent requests to the service? What do you exactly mean by keeping the client alive? which client? It will be better to explain what you want to achieve and the current restrictions or issues involved step by step in the original post so it is understandable – Ken4scholars Mar 13 '19 at 13:31
  • Ok, sorry for the bad explanation. Perhaps an important detail: I was trying to avoid the need to save the user login and password (of the external service). That's is why I need to keep the instance (self.client) logged from one request to another. And that's why I thought about `Django Channels`. Not really sure if it is appropriate, though... – Miguel Mar 13 '19 at 13:39
  • just added an additional explanation. Please tell me if it is still confusing. Thanks! – Miguel Mar 13 '19 at 13:55