web-dev-qa-db-fra.com

Envoi d'un message à un seul utilisateur via Django-channels

J'ai essayé Django-channels y compris la lecture de la documentation et les exemples.

Je souhaite pouvoir envoyer à un seul utilisateur un message déclenché par l'enregistrement d'une nouvelle instance dans une base de données.

Mon cas d'utilisation consiste à créer une nouvelle notification (via une tâche de céleri) et, une fois la notification enregistrée, à envoyer cette notification à un seul utilisateur.

Cela ressemble à cela est possible (à partir de Django-channels docs )

... la partie cruciale est que vous pouvez exécuter du code (et donc envoyer sur des canaux ) en réponse à tout événement - et cela inclut ceux que vous avez créés. Vous pouvez déclencher des sauvegardes de modèle, d'autres messages entrants ou À partir de chemins de code dans des vues et des formulaires.

Cependant, en lisant plus avant la documentation et en jouant avec les exemples Django-channels , je ne vois pas comment je pourrais le faire. Les exemples de liaison de données et de liveblog illustrent l'envoi à un groupe, mais je ne vois pas comment envoyer à un seul utilisateur.

Toute suggestion serait très appréciée.

9
lukeaus

Développer la réponse de @ Flip en créant un groupe pour cet utilisateur particulier. 

Dans votre fonction python dans votre fonction ws_connect, vous pouvez ajouter cet utilisateur dans un groupe juste pour eux:

consommateurs.py

from channels.auth import channel_session_user_from_http,
from channels import Group

@channel_session_user_from_http
def ws_connect(message):
    if user.is_authenticated:
        Group("user-{}".format(user.id)).add(message.reply_channel)

Pour envoyer à cet utilisateur un message à partir de votre code python:

my view.py

import json
from channels import Group

def foo(user):
    if user.is_authenticated:
        Group("user-{}".format(user.id)).send({
            "text": json.dumps({
            "foo": 'bar'
        })
    })

S'ils sont connectés, ils recevront le message. Si l'utilisateur n'est pas connecté à un websocket, il échouera en silence.

Vous devrez également vous assurer que vous ne connectez qu'un utilisateur au groupe de chaque utilisateur, sinon plusieurs utilisateurs pourraient recevoir le message que vous aviez destiné pour un utilisateur spécifique.

Regardez des exemples de canaux Django, en particulier multichat pour savoir comment implémenter le routage, créer la connexion websocket côté client et configurer Django_channels. 

Assurez-vous également de consulter la la documentation sur les canaux Django .

14
lukeaus

Peu de mise à jour, car les groupes fonctionnent différemment avec les canaux 2 et les canaux 1. Il n’existe plus de classe de groupe, comme indiqué par ici .

La nouvelle API des groupes est documentée ici . Voir aussi ici .

Ce qui fonctionne pour moi c'est:

# Required for channel communication
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync


def send_channel_message(group_name, message):
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        '{}'.format(group_name),
        {
            'type': 'channel_message',
            'message': message
        }
    )

N'oubliez pas de définir une méthode pour gérer le type de message dans le consommateur!

    # Receive message from the group
    def channel_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))
6
user42488

La meilleure approche consiste à créer le groupe pour cet utilisateur particulier. Lorsque ws_connect vous pouvez ajouter cet utilisateur dans Group("%s" % <user>).add(message.reply_channel)

Remarque: Mon URL Websocket est ws://127.0.0.1:8000/<user>

3
Raja Simon

Dans Canaux 2 , vous pouvez enregistrer self.channel_name dans une méthode db on connect qui correspond à un hachage spécifique pour chaque utilisateur. Documentation ici

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

class Consumer(AsyncJsonWebsocketConsumer):
    async def connect(self):
        self.room_group_name = 'room'

        if self.scope["user"].is_anonymous:
            # Reject the connection
            await self.close()
        else:
            # Accept the connection
            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name
            )

            await self.accept()

        print( self.channel_name )

La dernière ligne retourne quelque chose comme specific.WxuYsxLK!owndoeYTkLBw

Ce hachage spécifique que vous pouvez enregistrer dans la table de l'utilisateur.

0
Karl Zillner

Juste pour prolonger la réponse de @ luke_aus, si vous travaillez avec ResourceBindings, vous pouvez également le faire de sorte que seuls les utilisateurs "possédant" un objet récupèrent les mises à jour pour ceux-ci:

Comme @luke_aus answer, nous enregistrons l'utilisateur dans son propre groupe où nous pouvons publier des actions (update, create), etc. qui ne devraient être visibles que par cet utilisateur:

from channels.auth import channel_session_user_from_http,
from channels import Group

@channel_session_user_from_http
def ws_connect(message):
    Group("user-%s" % message.user).add(message.reply_channel)

Nous pouvons maintenant modifier la liaison correspondante afin qu'elle ne publie les modifications que si l'objet lié appartient à cet utilisateur, en supposant un modèle comme celui-ci:

class SomeUserOwnedObject(models.Model):
    owner = models.ForeignKey(User)

Nous pouvons maintenant lier ce modèle à notre groupe d'utilisateurs et toutes les actions (mise à jour, création, etc.) ne seront publiées que sur cet utilisateur:

class SomeUserOwnedObjectBinding(ResourceBinding):
    # your binding might look like this:
    model = SomeUserOwnedObject
    stream = 'someuserownedobject'
    serializer_class = SomeUserOwnedObjectSerializer
    queryset = SomeUserOwnedObject.objects.all()

    # here's the magic to only publish to this user's group
    @classmethod
    def group_names(cls, instance, action):
        # note that this will also override all other model bindings
        # like `someuserownedobject-update` `someuserownedobject-create` etc
        return ['user-%s' % instance.owner.pk]
0
devsnd