Effortless real time apps in Django @aaronbassett – rawtech.io
A presentation at DjangoCon Europe 2015 in May 2015 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