A presentation at DjangoCon Europe 2015 in in Cardiff, UK by Aaron Bassett
Effortless real time apps in Django @aaronbassett – rawtech.io
1.0
rawtech.io
I talk weird.
@aaronbassett
1 (function poll() { 2 new Ajax.Request('/api/', { 3 method:'get', 4 onSuccess: function() { ... }, 5 onFailure: function() { ... } 6 }); 7 8 setTimeout(poll, 1000); 9 }());
1 (function poll() { 2 new Ajax.Request('/api/', { 3 method: 'get', 4 timeout: 60000, 5 onSuccess: function() { 6 // Do something 7 poll(); 8 }, 9 onFailure: function() { 10 // Do something else 11 poll(); 12 } 13 }); 14 }());
HACKS UPON HACKS UPON HACKS, UPON HACKS UPON HACKS, UPON HACKS, UPON HACKS UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS, UPON HACKS
“typical header sizes of 700-800 bytes is common” –Google “SPDY: An experimental protocol for a faster web"
2 bytes.
0x00 UTF8 DATA 0xFF
100,000
871 bytes
696,800,000
665 Mbps
83 MB
100,000
2 bytes
1,600,000
1.526 Mbps
0.2 MB
POLLING
WEB SOCKETS
SWAMP DRAGON swampdragon.net
=
create YET another todo app ✓
create YET another todo app pip install swampdragon ✓ ✓
create YET another todo app pip install swampdragon (apt-get | brew) install redis ✓ ✓ ✓
dragon admin
1 2 3 4 5 6 7 8 9 10 11 12 #!/usr/bin/env python import os import sys from swampdragon.swampdragon_server import run_server os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<project>.settings") host_port = sys.argv[1] if len(sys.argv) > 1 else None run_server(host_port=host_port)
1 2 3 4 5 6 7 8 9 10 11 12 #!/usr/bin/env python import os import sys from swampdragon.swampdragon_server import run_server os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<project>.settings") host_port = sys.argv[1] if len(sys.argv) > 1 else None run_server(host_port=host_port)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
get_list delete create get_single update subscribe unsubscribe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
1 <div class="row" ng-controller="TodoListCtrl"> 2 {% verbatim %} 3 <h1>{{ todoList.name }}</h1> 4 <p>{{ todoList.description }}</p> 5 6 <div ng-repeat="item in todoItems" class="todos"> 7 . . . 8 </div> 9 {% endverbatim %} 10 </div>
1 <div class="row" ng-controller="TodoListCtrl"> 2 {% verbatim %} 3 <h1>{{ todoList.name }}</h1> 4 <p>{{ todoList.description }}</p> 5 6 <div ng-repeat="item in todoItems" class="todos"> 7 . . . 8 </div> 9 {% endverbatim %} 10 </div>
1 <div class="row" ng-controller="TodoListCtrl"> 2 {% verbatim %} 3 <h1>{{ todoList.name }}</h1> 4 <p>{{ todoList.description }}</p> 5 6 <div ng-repeat="item in todoItems" class="todos"> 7 . . . 8 </div> 9 {% endverbatim %} 10 </div>
1 $dragon.onReady(function() { 2 $dragon.subscribe('todo-item', $scope.channel, {todo_list__id: 1}).then(function(response) { 3 $scope.dataMapper = new DataMapper(response.data); 4 }); 5 6 $dragon.getSingle('todo-list', {id:1}).then(function(response) { 7 $scope.todoList = response.data; 8 }); 9 10 $dragon.getList('todo-item', {list_id:1}).then(function(response) { 11 $scope.todoItems = response.data; 12 }); 13 });
$dragon.subscribe('todo-item', $scope.channel, {todo_list__id: 1})
$dragon.subscribe('todo-item', $scope.channel, {todo_list__id: 1})
6 7 8 $dragon.getSingle('todo-list', {id:1}).then(function(response) { $scope.todoList = response.data; });
10 11 12 $dragon.getList('todo-item', {list_id:1}).then(function(response) { $scope.todoItems = response.data; });
1 $dragon.onReady(function() { 2 $dragon.subscribe('todo-item', ...); 3 $dragon.getSingle('todo-list', ...); 4 $dragon.getList('todo-item', ...); 5 });
1 $dragon.onReady(function() { 2 $dragon.subscribe('todo-item', ...); 3 $dragon.getSingle('todo-list', ...); 4 $dragon.getList('todo-item', ...); 5 });
1 $dragon.onReady(function() { 2 $dragon.subscribe('todo-item', ...); 3 $dragon.getSingle('todo-list', ...); 4 $dragon.getList('todo-item', ...); 5 });
1 $dragon.onReady(function() { 2 $dragon.subscribe('todo-item', ...); 3 $dragon.getSingle('todo-list', ...); 4 $dragon.getList('todo-item', ...); 5 });
1 $dragon.onChannelMessage(function(channels, message) { 2 if (indexOf.call(channels, $scope.channel) > -1) { 3 $scope.$apply(function() { 4 $scope.dataMapper.mapData($scope.todoItems, message); 5 }); 6 } 7 });
1 $dragon.onChannelMessage(function(channels, message) { 2 if (indexOf.call(channels, $scope.channel) > -1) { 3 $scope.$apply(function() { 4 $scope.dataMapper.mapData($scope.todoItems, message); 5 }); 6 } 7 });
1 $dragon.onChannelMessage(function(channels, message) { 2 if (indexOf.call(channels, $scope.channel) > -1) { 3 $scope.$apply(function() { 4 $scope.dataMapper.mapData($scope.todoItems, message); 5 }); 6 } 7 });
1 $dragon.onChannelMessage(function(channels, message) { 2 if (indexOf.call(channels, $scope.channel) > -1) { 3 $scope.$apply(function() { 4 $scope.dataMapper.mapData($scope.todoItems, message); 5 }); 6 } 7 });
DEMO TIME!
git.io/vUWyn
PaaS Platform as a Service
DaaS Database as a Service
www.leggetter.co.uk/2013/12/09/choosingrealtime-web-app-tech-stack.html @leggetter
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 class SelfPublishModel(object): 2 3 def _publish(self, action): 4 serializer = self.serializer_class(instance=self) 5 data = serializer.serialize() 6 7 pusher = Pusher( 8 app_id=settings.PUSHER_APP_ID, 9 key=settings.PUSHER_KEY, 10 secret=settings.PUSHER_SECRET 11 ) 12 13 pusher.trigger(self.channel_name, action, data)
1 class SelfPublishModel(object): 2 3 def _publish(self, action): 4 serializer = self.serializer_class(instance=self) 5 data = serializer.serialize() 6 7 pusher = Pusher( 8 app_id=settings.PUSHER_APP_ID, 9 key=settings.PUSHER_KEY, 10 secret=settings.PUSHER_SECRET 11 ) 12 13 pusher.trigger(self.channel_name, action, data)
1 class SelfPublishModel(object): 2 3 def _publish(self, action): 4 serializer = self.serializer_class(instance=self) 5 data = serializer.serialize() 6 7 pusher = Pusher( 8 app_id=settings.PUSHER_APP_ID, 9 key=settings.PUSHER_KEY, 10 secret=settings.PUSHER_SECRET 11 ) 12 13 pusher.trigger(self.channel_name, action, data)
1 var TodoControllers = angular.module('TodoControllers', []); 2 3 TodoControllers.controller('TodoListCtrl', ['$scope', '$pusher', function ($scope, $pusher) { 4 $scope.todoItems = []; 5 6 var client = new Pusher("895dec4d68d4e286a48d"); 7 var pusher = $pusher(client); 8 var todo_items_channel = pusher.subscribe('todo-item'); 9 todo_items_channel.bind('updated', 10 function(data) { 11 for(var i=0; i < $scope.todoItems.length; i++) { 12 if($scope.todoItems[i].id == data.id) { 13 $scope.todoItems[i] = data; 14 return; 15 } 16 } 17 } 18 ); 19 }]);
1 var TodoControllers = angular.module('TodoControllers', []); 2 3 TodoControllers.controller('TodoListCtrl', ['$scope', '$pusher', function ($scope, $pusher) { 4 $scope.todoItems = []; 5 6 var client = new Pusher("895dec4d68d4e286a48d"); 7 var pusher = $pusher(client); 8 var todo_items_channel = pusher.subscribe('todo-item'); 9 todo_items_channel.bind('updated', 10 function(data) { 11 for(var i=0; i < $scope.todoItems.length; i++) { 12 if($scope.todoItems[i].id == data.id) { 13 $scope.todoItems[i] = data; 14 return; 15 } 16 } 17 } 18 ); 19 }]);
1 var TodoControllers = angular.module('TodoControllers', []); 2 3 TodoControllers.controller('TodoListCtrl', ['$scope', '$pusher', function ($scope, $pusher) { 4 $scope.todoItems = []; 5 6 var client = new Pusher("895dec4d68d4e286a48d"); 7 var pusher = $pusher(client); 8 var todo_items_channel = pusher.subscribe('todo-item'); 9 todo_items_channel.bind('updated', 10 function(data) { 11 for(var i=0; i < $scope.todoItems.length; i++) { 12 if($scope.todoItems[i].id == data.id) { 13 $scope.todoItems[i] = data; 14 return; 15 } 16 } 17 } 18 ); 19 }]);
1 var TodoControllers = angular.module('TodoControllers', []); 2 3 TodoControllers.controller('TodoListCtrl', ['$scope', '$pusher', function ($scope, $pusher) { 4 $scope.todoItems = []; 5 6 var client = new Pusher("895dec4d68d4e286a48d"); 7 var pusher = $pusher(client); 8 var todo_items_channel = pusher.subscribe('todo-item'); 9 todo_items_channel.bind('updated', 10 function(data) { 11 for(var i=0; i < $scope.todoItems.length; i++) { 12 if($scope.todoItems[i].id == data.id) { 13 $scope.todoItems[i] = data; 14 return; 15 } 16 } 17 } 18 ); 19 }]);
DEMO TIME!
git.io/vker1
1 2 3 4 5 6 7 8 9 from pusher import Pusher pusher = Pusher( app_id=u'111545', key=u'895dec4d68d4e286a48d', secret=u'3cd18f6810c758057767' ) pusher.trigger(u'channel_name', u'event_name', {u'message': u'hello world'})
1 class PusherMixin(object): 2 3 def render_to_response(self, context, **response_kwargs): 4 5 channel = u"{model}_{pk}".format( 6 model=self.object._meta.model_name, 7 pk=self.object.pk 8 ) 9 event_data = {'user': self.request.user.username} 10 11 pusher = Pusher(app_id=settings.PUSHER_APP_ID, 12 key=settings.PUSHER_KEY, 13 secret=settings.PUSHER_SECRET) 14 pusher.trigger( 15 [channel, ], 16 self.pusher_event_name, 17 event_data 18 ) 19 20 return super(PusherMixin, self).render_to_response(context, **response_kwargs)
1 class PusherMixin(object): 2 3 def render_to_response(self, context, **response_kwargs): 4 5 channel = u"{model}_{pk}".format( 6 model=self.object._meta.model_name, 7 pk=self.object.pk 8 ) 9 event_data = {'user': self.request.user.username} 10 11 pusher = Pusher(app_id=settings.PUSHER_APP_ID, 12 key=settings.PUSHER_KEY, 13 secret=settings.PUSHER_SECRET) 14 pusher.trigger( 15 [channel, ], 16 self.pusher_event_name, 17 event_data 18 ) 19 20 return super(PusherMixin, self).render_to_response(context, **response_kwargs)
1 class PusherMixin(object): 2 3 def render_to_response(self, context, **response_kwargs): 4 5 channel = u"{model}_{pk}".format( 6 model=self.object._meta.model_name, 7 pk=self.object.pk 8 ) 9 event_data = {'user': self.request.user.username} 10 11 pusher = Pusher(app_id=settings.PUSHER_APP_ID, 12 key=settings.PUSHER_KEY, 13 secret=settings.PUSHER_SECRET) 14 pusher.trigger( 15 [channel, ], 16 self.pusher_event_name, 17 event_data 18 ) 19 20 return super(PusherMixin, self).render_to_response(context, **response_kwargs)
1 class PusherMixin(object): 2 3 def render_to_response(self, context, **response_kwargs): 4 5 channel = u"{model}_{pk}".format( 6 model=self.object._meta.model_name, 7 pk=self.object.pk 8 ) 9 event_data = {'user': self.request.user.username} 10 11 pusher = Pusher(app_id=settings.PUSHER_APP_ID, 12 key=settings.PUSHER_KEY, 13 secret=settings.PUSHER_SECRET) 14 pusher.trigger( 15 [channel, ], 16 self.pusher_event_name, 17 event_data 18 ) 19 20 return super(PusherMixin, self).render_to_response(context, **response_kwargs)
1 class PusherMixin(object): 2 3 def render_to_response(self, context, **response_kwargs): 4 5 channel = u"{model}_{pk}".format( 6 model=self.object._meta.model_name, 7 pk=self.object.pk 8 ) 9 event_data = {'user': self.request.user.username} 10 11 pusher = Pusher(app_id=settings.PUSHER_APP_ID, 12 key=settings.PUSHER_KEY, 13 secret=settings.PUSHER_SECRET) 14 pusher.trigger( 15 [channel, ], 16 self.pusher_event_name, 17 event_data 18 ) 19 20 return super(PusherMixin, self).render_to_response(context, **response_kwargs)
1 2 3 4 5 var pusher = new Pusher('{{ settings.PUSHER_KEY }}'); var channel = pusher.subscribe('model_{{ object.pk }}'); channel.bind('update', function(data) { alert(data.user + " has begun updating this object"); });
1 2 3 4 5 var pusher = new Pusher('{{ settings.PUSHER_KEY }}'); var channel = pusher.subscribe('model_{{ object.pk }}'); channel.bind('update', function(data) { alert(data.user + " has begun updating this object"); });
1 2 3 4 5 var pusher = new Pusher('{{ settings.PUSHER_KEY }}'); var channel = pusher.subscribe('model_{{ object.pk }}'); channel.bind('update', function(data) { alert(data.user + " has begun updating this object"); });
1 2 3 4 5 var pusher = new Pusher('{{ settings.PUSHER_KEY }}'); var channel = pusher.subscribe('model_{{ object.pk }}'); channel.bind('update', function(data) { alert(data.user + " has begun updating this object"); });
git.io/vk9uu
@aaronbassett
Talk description from the Pusher blog
Aaron Bassett gave a talk on building real-time applications with Django using a Django package called Swamp Dragon, which consists of Redis, Tornado and Django. Aaron included a step-by-step example of building a simple TODO app. He also showed how you can integrate Pusher to simplify the architecture.
Firstly using Swamp Dragon, Redis and Tornado to power your real-time Django app. Then removing Redis and Tornado completely, using Swamp Dragon purely for object serialization, and utilitizing Pusher’s real-time infrastructure to maintain all the WebSocket connections and deliver all the messages. Of course, you could remove Swamp Dragon all together if you wanted to, but Aaron was covering a migration process and liked the way it handled serialization.