When I searched for a package to implement WebSocket communication using Django and AngularJS, Because there was a Swamp Dragon that has all the tools for WebSocket communication with Django I made a standard Todo management site.
swampdragon: http://swampdragon.net/ github: https://github.com/jonashagstedt/swampdragon
Since Django's WSGI is HTTP, WebSocket communication is not possible, so you need to set up another server. When creating a project via SwampDragon, a file called server.py is generated This will start the server. (You can also do it with /manage.py runsd.py) The server is running the web framework Tornado Inside server.py, the URL required for Connection is generated and started.
The Router class is mapped to the URL of server.py Sending to the client using the SockJS connection held by the Router class.
It serializes and maps Django's model and redis. When saving a model, the Pub / Sub function of redis notifies the client in real time.
※「pub/sub」 Abbreviation for "publish" and "subscribe", which means "publish" and "subscribe" in Japanese. When someone "publishes" an event on a particular channel Everyone who "subscribes" to the channel will be notified of the event.
A service (js) that allows the client to connect to the server and manipulate the data It doesn't take much time because everything is prepared. (Third party Objective-c but some for iOS)
--Getting a list of data --Get a list of data (with Pager) --Single acquisition of data --Add new data --Data update --Data deletion --Data subscription --Unsubscribe from data
Since user information after logging in can be retained on the server side It is possible to include user ID etc. in queries such as data acquisition and update. There is no need to have user information on the client side. Swamp Dragon author provides to hold a session Swamp Dragon-auth package needs to be installed.
The directory structure is as follows The app uses AngularJS and there are multiple JS files, but I'm omitting them
application
├── app
│ ├── index.html
│ ├── login.html
├── manage.py
├── module
│ ├── middleware.py
│ └── todo
│ ├── models.py
│ ├── routers.py
│ └── serializers.py
├── server.py
├── settings.py
├── urls.py
└── wsgi.py
settings.py Settings for middleware and swampdragon
INSTALLED_APPS = (
:
'swampdragon',
'module.todo',
)
MIDDLEWARE_CLASSES = (
:
'module.middleware.AuthenticationMiddleware',
)
# SwampDragon settings
SWAMP_DRAGON_CONNECTION = ('swampdragon_auth.socketconnection.HttpDataConnection', '/data')
DRAGON_URL = 'http://localhost:9999/'
urls.py Since the processing equivalent to Views is performed by AngularJS on the client, prepare a URL only for index.html.
# -*- coding: utf-8 -*-
from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic import TemplateView
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'),
url(r'^login', 'django.contrib.auth.views.login', kwargs={'template_name': 'login.html'}, name='login'),
url(r'^logout', 'django.contrib.auth.views.logout_then_login', kwargs={'login_url': 'login'}, name='logout'),
url(r'^admin/', include(admin.site.urls)),
]
middleware.py Access that is not logged in should be skipped to the login page
# -*- coding: utf-8 -*-
from django.shortcuts import redirect
class AuthenticationMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated():
if request.path.startswith('/login'):
return redirect('/')
elif request.path == '/':
return redirect('login')
serializers.py A class that defines the exchange of data with the front end --model: Define the model with "module name.model class name" --publish_fields: Definition of columns to notify clients --update_fields: Define columns that can be updated from the client
from swampdragon.serializers.model_serializer import ModelSerializer
class UserSerializer(ModelSerializer):
class Meta:
model = 'auth.User'
publish_fields = ('username',)
class TodoListSerializer(ModelSerializer):
class Meta:
model = 'todo.TodoList'
publish_fields = ('name', 'description')
update_fields = ('name', 'description')
class TodoItemSerializer(ModelSerializer):
class Meta:
model = 'todo.TodoItem'
publish_fields = ('todolist_id', 'done', 'name', 'updated_at')
update_fields = ('todolist_id', 'done', 'name')
models.py Inherit SelfPublishModel so that Publish is notified at save. Define the class that has been serialized with serializer_class.
# -*- coding: utf-8 -*-
from django.db import models
from swampdragon.models import SelfPublishModel
from .serializers import TodoListSerializer, TodoItemSerializer
class TodoList(SelfPublishModel, models.Model):
serializer_class = TodoListSerializer
user_id = models.IntegerField()
name = models.CharField(max_length=100)
description = models.TextField(u'Description', blank=True, null=True)
created_at = models.DateTimeField(u'Creation date and time', auto_now_add=True)
updated_at = models.DateTimeField(u'Update date and time', auto_now=True)
class Meta:
index_together = ['user_id', 'id']
class TodoItem(SelfPublishModel, models.Model):
serializer_class = TodoItemSerializer
user_id = models.IntegerField()
name = models.CharField(max_length=100)
todolist_id = models.IntegerField()
done = models.BooleanField(u'Completion flag', default=False)
created_at = models.DateTimeField(u'Creation date and time', auto_now_add=True)
updated_at = models.DateTimeField(u'Update date and time', auto_now=True)
class Meta:
index_together = ['user_id', 'id']
routers.py URL to be registered in WebSocket dedicated server, class to define mapped process --route_name: Path of URL registered in WebSocket dedicated server (http: // localhost: 9999 / todo-list feeling) --get_initial: Define additional parameters to include when adding, updating or deleting data from the client --get_subscription_contexts: Define parameters to include in the channel when the client subscribes --get_object: Query processing when data is requested by itself (required) --get_query_set: Query processing when data is requested in a list (required)
# -*- coding: utf-8 -*-
from swampdragon import route_handler
from swampdragon.route_handler import ModelRouter
from module.todo.models import TodoList, TodoItem
from module.todo.serializers import UserSerializer, TodoListSerializer, TodoItemSerializer
class UserRouter(ModelRouter):
route_name = 'user'
serializer_class = UserSerializer
def get_object(self, **kwargs):
return self.connection.user
def get_query_set(self, **kwargs):
pass
class TodoListRouter(ModelRouter):
route_name = 'todo-list'
serializer_class = TodoListSerializer
model = TodoList
def get_initial(self, verb, **kwargs):
kwargs['user_id'] = self.connection.user.id
return kwargs
def get_subscription_contexts(self, **kwargs):
#Create a unique channel with your user ID so that update notifications are sent only to logged-in users(todolist|user_id:Become one channel)
kwargs['user_id'] = self.connection.user.id
return kwargs
def get_object(self, **kwargs):
user_list = self.model.objects.filter(id=kwargs['id'], user_id=self.connection.user.id)
return user_list[0] if user_list else None
def get_query_set(self, **kwargs):
user_id = self.connection.user.id
return self.model.objects.filter(user_id=user_id)
class TodoItemRouter(ModelRouter):
route_name = 'todo-item'
serializer_class = TodoItemSerializer
model = TodoItem
def get_initial(self, verb, **kwargs):
kwargs['user_id'] = self.connection.user.id
return kwargs
def get_subscription_contexts(self, **kwargs):
kwargs['user_id'] = self.connection.user.id
return kwargs
def get_object(self, **kwargs):
user_list = self.model.objects.filter(id=kwargs['id'], user_id=self.connection.user.id)
return user_list[0] if user_list else None
def get_query_set(self, **kwargs):
user_id = self.connection.user.id
return self.model.objects.filter(user_id=user_id)
route_handler.register(UserRouter)
route_handler.register(TodoListRouter)
route_handler.register(TodoItemRouter)
Client uses AngularJS side $ dragon is a service provided by Swamp Dragon.
angular.module('todoApp')
.controller('PageController', ['$scope', '$dragon', '$dataHandler',
function ($scope, $dragon, $dataHandler) {
$scope.todoListChannel = 'todoListClient';
$scope.todoItemChannel = 'todoItemClient';
//When accessing the first page
$dragon.onReady(function() {
// todolist,Subscribe to todoItem information(Change notification will be sent)
$dragon.subscribe('todo-list', $scope.todoListChannel, {}).then(function(response) {
$scope.TodoListMapper = new DataMapper(response.data);
});
$dragon.subscribe('todo-item', $scope.todoItemChannel, {}).then(function(response) {
$scope.todoItemMapper = new DataMapper(response.data);
});
// todolist, todoItem,Get user data
$dragon.getSingle('user', {}).then(function(response) {
$dataHandler.user = response.data;
});
$dragon.getList('todo-list', {list_id: 1}).then(function(response) {
$dataHandler.todoLists = response.data;
});
$dragon.getList('todo-item', {list_id: 1}).then(function(response) {
$dataHandler.todoItems = response.data;
});
});
// todolist,Change notification when there is a save in todoItem
$dragon.onChannelMessage(function(channels, message) {
if (indexOf.call(channels, $scope.todoListChannel) > -1) {
$scope.$apply(function() {
$scope.TodoListMapper.mapData($dataHandler.todoLists, message);
});
}
if (indexOf.call(channels, $scope.todoItemChannel) > -1) {
$scope.$apply(function() {
$scope.todoItemMapper.mapData($dataHandler.todoItems, message);
});
}
});
}]);
python ./manage.py runserver
python server.py
The server side is really easy and I think it's familiar to anyone who has touched Django. However, on the client side, processing such as JS callback that updates the screen to HTML in real time is done. Atmosphere that seems to have to work hard. If you use AngularJS, you can do data binding of HTML and JS variables, so it seems that you can improve the trouble of updating the screen.
It seems that there is not much introduction record in Japan, but if you look at github It seems that it is being used here and there, so I'd like to expect it in the future.
The code of the Todo management site created this time is placed on github. https://github.com/fujimisakari/todo-server