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

-- coding: utf-8 -# Django from django.db import models # Swamp Dragon from swampdragon.models import SelfPublishModel from .serializers import TodoListSerializer, TodoItemSerializer class TodoItem(SelfPublishModel, models.Model): serializer_class = TodoItemSerializer todo_list = models.ForeignKey(TodoList) done = models.BooleanField(default=False) text = models.CharField(max_length=100) def unicode(self): return u"{text} ({status})".format( text=self.text, status=(u"✓" if self.done else u"×") )

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

-- coding: utf-8 -# Django from django.db import models # Swamp Dragon from swampdragon.models import SelfPublishModel from .serializers import TodoListSerializer, TodoItemSerializer class TodoItem(SelfPublishModel, models.Model): serializer_class = TodoItemSerializer todo_list = models.ForeignKey(TodoList) done = models.BooleanField(default=False) text = models.CharField(max_length=100) def unicode(self): return u"{text} ({status})".format( text=self.text, status=(u"✓" if self.done else u"×") )

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

-- coding: utf-8 -# Django from django.db import models # Swamp Dragon from swampdragon.models import SelfPublishModel from .serializers import TodoListSerializer, TodoItemSerializer class TodoItem(SelfPublishModel, models.Model): serializer_class = TodoItemSerializer todo_list = models.ForeignKey(TodoList) done = models.BooleanField(default=False) text = models.CharField(max_length=100) def unicode(self): return u"{text} ({status})".format( text=self.text, status=(u"✓" if self.done else u"×") )

1 2 3 4 5 6 7 8 9 10 11

-- coding: utf-8 -# Swamp Dragon from swampdragon.serializers.model_serializer import ModelSerializer class TodoItemSerializer(ModelSerializer): class Meta: model = 'todo.TodoItem' publish_fields = ('done', 'text') update_fields = ('done', )

1 2 3 4 5 6 7 8 9 10 11

-- coding: utf-8 -# Swamp Dragon from swampdragon.serializers.model_serializer import ModelSerializer class TodoItemSerializer(ModelSerializer): class Meta: model = 'todo.TodoItem' publish_fields = ('done', 'text') update_fields = ('done', )

1 2 3 4 5 6 7 8 9 10 11

-- coding: utf-8 -# Swamp Dragon from swampdragon.serializers.model_serializer import ModelSerializer class TodoItemSerializer(ModelSerializer): class Meta: model = 'todo.TodoItem' publish_fields = ('done', 'text') update_fields = ('done', )

1 2 3 4 5 6 7 8 9 10 11

-- coding: utf-8 -# Swamp Dragon from swampdragon.serializers.model_serializer import ModelSerializer class TodoItemSerializer(ModelSerializer): class Meta: model = 'todo.TodoItem' publish_fields = ('done', 'text') update_fields = ('done', )

1 2 3 4 5 6 7 8 9 10 11

-- coding: utf-8 -# Swamp Dragon from swampdragon.serializers.model_serializer import ModelSerializer class TodoItemSerializer(ModelSerializer): class Meta: model = 'todo.TodoItem' publish_fields = ('done', 'text') update_fields = ('done', )

1 2 3 4 5 6 7 8 9 10 11

-- coding: utf-8 -# Swamp Dragon from swampdragon.serializers.model_serializer import ModelSerializer class TodoItemSerializer(ModelSerializer): class Meta: model = 'todo.TodoItem' publish_fields = ('done', 'text') update_fields = ('done', )

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

-- coding: utf-8 -# Swamp Dragon from swampdragon import route_handler from swampdragon.route_handler import ModelRouter from .models import TodoList, TodoItem from .serializers import TodoListSerializer, TodoItemSerializer class TodoItemRouter(ModelRouter): route_name = 'todo-item' serializer_class = TodoItemSerializer model = TodoItem def get_object(self, **kwargs): return self.model.objects.get(pk=kwargs['id']) def get_query_set(self, **kwargs): return self.model.objects.filter(todo_list__id=kwargs['list_id']) route_handler.register(TodoItemRouter)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

-- coding: utf-8 -# Swamp Dragon from swampdragon import route_handler from swampdragon.route_handler import ModelRouter from .models import TodoList, TodoItem from .serializers import TodoListSerializer, TodoItemSerializer class TodoItemRouter(ModelRouter): route_name = 'todo-item' serializer_class = TodoItemSerializer model = TodoItem def get_object(self, **kwargs): return self.model.objects.get(pk=kwargs['id']) def get_query_set(self, **kwargs): return self.model.objects.filter(todo_list__id=kwargs['list_id']) route_handler.register(TodoItemRouter)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

-- coding: utf-8 -# Swamp Dragon from swampdragon import route_handler from swampdragon.route_handler import ModelRouter from .models import TodoList, TodoItem from .serializers import TodoListSerializer, TodoItemSerializer class TodoItemRouter(ModelRouter): route_name = 'todo-item' serializer_class = TodoItemSerializer model = TodoItem def get_object(self, **kwargs): return self.model.objects.get(pk=kwargs['id']) def get_query_set(self, **kwargs): return self.model.objects.filter(todo_list__id=kwargs['list_id']) route_handler.register(TodoItemRouter)

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

-- coding: utf-8 -# Swamp Dragon from swampdragon import route_handler from swampdragon.route_handler import ModelRouter from .models import TodoList, TodoItem from .serializers import TodoListSerializer, TodoItemSerializer class TodoItemRouter(ModelRouter): route_name = 'todo-item' serializer_class = TodoItemSerializer model = TodoItem def get_object(self, **kwargs): return self.model.objects.get(pk=kwargs['id']) def get_query_set(self, **kwargs): return self.model.objects.filter(todo_list__id=kwargs['list_id']) route_handler.register(TodoItemRouter)

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