Package: django-redis-pubsub
asyncronous subscription distrobution for django (with websocket support!!!!).PublishableModelYou’ll first need to create some publishable models.# models.pyclassCorrespondence(PublishableModel):PUBLISH_ON_CREATE=FalsePUBLISH_ON_UPDATE=Falseparticipants=models.ManyToManyField(settings.AUTH_USER_MODEL,related_name="correspondences")defsave(self,*args,**kwargs):super(Correspondence,self).save(*args,**kwargs)# add subscribe all the users to the channelchannel=self.channelforsubscriberinself.participants:channel.subscribe(subscriber)classMessage(PublishableModel):PUBLISH_ON_CREATE=TruePUBLISH_ON_UPDATE=Falsecorrespondence=models.ForeignKey("Correspondence",related_name="messages")author=models.ForeignKey(settings.AUTH_USER_MODEL,related_name="sent_messages")body=models.TextField()defsave(self,*args,**kwargs):ifnothasattr(self,"channel"):self.channel=self.correspondence.channelsuper(Message,self).save(*args,**kwargs)# views.pydefsend_message(request,correspondence,*args,**kwargs):message=Message.objects.create(correspondence_id=correspondence,author=request.user,body=request.POST["body"]returnrender_to_response(request,"messages.html",{"message":message})# websockets.py@websocket_pubsub(authenticate=True)defread_messages(ws,params,user,manager):subscription=user.subscriptions.get(channel__name="something:unique".format(user.username))reader=subscription.get_reader(manager=manager)@reader.callbackdefsend_message_alert(channel_name,model):alert=json.dumps({"message":"new message from{0}".format(model.author.get_full_name()))ws.send_str(alert)returnTruelistener=yield fromreader.listen()yield fromlistenerIn the above example, a client who has established a websocket connection to the handler inwebsockets.pywill receive alerts as long as the websocket connection remains open. When another client sends a POST request to the send_message view inviews.pythe message will be published and received by theread_messages.send_message_alertcallback where further processing/serialization can occur.WebsocketsIf you choose to useredis_pubsub.contrib.websocketsthere are additional packages that you will need to install:$ pip install aiohttp aiohttp_wsgiWebsocket handlers belong in module in your application by the name ofwebsockets.py. This module should export ahandlerconf, which is a list of the names of the handlers in the module@websocket("/")# this handler will be at http://yourapp.com/defmyhandler(ws,params,**kwargs):...handlerconf=["myhandler",]Websocket requests are handled with the excellentaiohttppackage which takes care of the encoding/decoding, handshake, and cleanup of a websocket session. Handlers for websocket requests are coroutines decorated with either theredis_pubsub.contrib.websockets.websocketorredis_pubsub.contrib.websockets.websocket_pubsubwrappers. These wrappers handle converting your handler to a coroutine and passing arguments to your handler. A simple handler that echo’s a message back to the client would look like this@websocket("/echo")defecho(ws,params,**kwargs):message=yield fromws.receive()ws.send_str(message.data)The former example shows a websocket handler that waits for a message from a connected client, echo’s the message back to the client and closes the connection.Websocket AuthenticationIf you choose to use authenticated websockets you will need to either installdjangorestframeworkand use therest_framework.authtoken.models.Tokenobject as your authentication method or simply userest_framework_jwtto distribute and challenge JTW’s provided by your client. to configure authentication with one of these methods (or your own token authentication method) add the module path to the REDIS_PUBSUB config:REDIS_PUBSUB = {
    "tokenauth_method": "redis_pubsub.auth.authjwt_method",  # defaults to "redis_pubsub.auth.authtoken_method"
}If you do decide to roll your owntokenauth_method, this method must accept a single argument (the token string) and return eitherNoneif the token is not valid or an instance ofAUTH_USER_MODELif the token is valid.Websocket PubsubYou can access the Pubsub methods provided byredis_pubsubin your websocket handlers by decorating your handler with theredis.pubsub.contrib.websockets.websocket_pubsubwrapper. This wrapper provides an additional argumentmanagerto your handler. The manager can be used to keep track of subscription channels and stop them if necessary# websockets.py@websocket_pubsub("/messages",authenticate=True)defmessage_pusher(ws,params,manager,user,**kwargs):subscription=user.subscriptions.get(channel__name="messages")reader=subscription.get_reader(manager=manager)@reader.callbackdefcallback(channel_name,message):to_client={channel_name:{"author":message.author.username,"body":message.body}}ws.send_str(json.dumps())returnTruelistener=yield fromreader.listen()yield fromlistenerThis example shows the main purpose of theredis_pubsubpackage, which is to listen for updates on a redis channel and push the publication to a client. Lets break it down line by lineretreive the users subscriptioncreate a managed ChannelReader object for this subscriptionregister a callback to be executed whenever a new publication is receivedbegin listening for changeslisten until the channel is closedThe most fruitful method offerd by a SubscriptionManager islisten_to_all_subscriptionswhich takes two arguments, a subscriber and a callback, and publishes subscriptions as they arrive# websockets.py@websocket_pubsub("/subscriptions",authenticate=True)defsubscriptions(ws,params,manager,user,**kwargs):defcallback(channel_name,message):ws.send_str(message.serialize())returnTruemanager.listen_to_all_subscriptions(user,callback)whileTrue:message=yield fromws.receive()ifmessage.tpnotin(MsgType.error,MsgType.close):message=json.loads(message)ifmessage["action"]=="unsubscribe":subscription=user.subscriptions.get(channel__name=message["channe"])subscription.active=Falsesubscription.save()reader=manager.readers[message["channel"]]yield frommanager.remove(reader)elifmessage["action"]=="subscribe":channel=Channel.objects.get(name=message["channel"])reader=channel.subscribe(user).get_reader(manager=manager)reader.callback(callback)yield fromreader.listen()else:breakThe callback in this example will keep all subscription channels open and push messages to a client until the websocket has closed. This code provides a simple means of managing users with a multitude of subscriptions. Thewhileloop here also handles unsubscribing and subscribing to new channelsNoteA callback function should never receive from a websocket or else a RuntimeError will be raised.Deployingwhen deploying an application with websockets/aiohttp you will not be able to use the normal django deployment proceedures. Since your django application will be a component of an AioHttp application object, you will have to use Gunicorn as an application server. Using utilities from theredis_pubsub.contrib.websocketsmodule you can create a deployment file simply# deployment.pyimportasynciofromdjango.core.wsgiimportget_wsgi_applicationfromaiohttp_wsgiimportWSGIHandlerfromredis_pubsub.contrib.websocketsimportsetupwsgi_app=get_wsgi_application()# django.setup() is called herewsgi_handler=WSGIHandler(wsgi_app)loop=asyncio.get_event_loop()application=setup(loop=loop)# any url patterns not matched by the Websocket app go to the django app for handlingapplication.router.add_route("*","/{path_info:.*}",wsgi_handler.handle_request)you can then start gunicorn by running:$ gunicorn deployment:application --bind localhost:8080 --worker-class aiohttp.worker.GunicornWebWorker