Commit 07b046bf authored by Denis Bilenko's avatar Denis Bilenko

add webchat: Tornado's long polling chat demo adapted to run on django and gevent.wsgi2

parent 11ee826e
An example of AJAX chat taken from Tornado demos and converted to use django and gevent.
To start the server, run
$ python run.py
import uuid
import simplejson
from django.shortcuts import render_to_response
from django.template.loader import render_to_string
from django.http import HttpResponse
from gevent.event import Event
from webchat import settings
messages = []
MESSAGES_SIZE = 200
new_message_event = Event()
def new_message(from_, body):
data = {'id': str(uuid.uuid4()), 'from': from_, 'body': body}
data['html'] = render_to_string('message.html', dictionary={'message': data})
return data
def last_message_id():
if messages:
return messages[-1]['id']
def main(request):
if messages:
request.session['cursor'] = messages[-1]['id']
return render_to_response('index.html', {'MEDIA_URL': settings.MEDIA_URL, 'messages': messages})
def message_new(request):
name = request.META.get('REMOTE_ADDR') or 'Anonymous'
msg = new_message(name, request.POST['body'])
messages.append(msg)
if len(messages)>MESSAGES_SIZE:
del messages[0]
new_message_event.set()
new_message_event.clear()
return json_response(msg)
def message_updates(request):
cursor = request.session.get('cursor')
if cursor==last_message_id():
new_message_event.wait()
assert cursor!=last_message_id(), cursor
try:
for index, m in enumerate(messages):
if m['id'] == cursor:
return json_response({'messages': messages[index+1:]})
return json_response({'messages': messages})
finally:
if messages:
request.session['cursor'] = messages[-1]['id']
else:
request.session.pop('cursor', None)
def json_response(value, **kwargs):
kwargs.setdefault('content_type', 'text/javascript; charset=UTF-8')
return HttpResponse(simplejson.dumps(value), **kwargs)
#!/usr/bin/python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)
#!/usr/bin/python
from gevent import monkey; monkey.patch_all()
from gevent import wsgi2
import os
import traceback
from django.core.handlers.wsgi import WSGIHandler
from django.core.management import call_command
from django.core.signals import got_request_exception
os.environ['DJANGO_SETTINGS_MODULE'] = 'webchat.settings'
def exception_printer(sender, **kwargs):
traceback.print_exc()
got_request_exception.connect(exception_printer)
call_command('syncdb')
print 'Serving on 8088...'
wsgi2.WSGIServer(('', 8088), WSGIHandler()).serve_forever()
from os.path import dirname, join, abspath
__dir__ = dirname(abspath(__file__))
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = ()
MANAGERS = ADMINS
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = ':memory:'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''
TIME_ZONE = 'America/Chicago'
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
USE_I18N = True
MEDIA_ROOT = join(__dir__, 'static')
MEDIA_URL = '/media/'
SECRET_KEY = 'nv8(yg*&1-lon-8i-3jcs0y!01+rem*54051^5xt#^tzujdj!c'
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
)
ROOT_URLCONF = 'webchat.urls'
TEMPLATE_DIRS = (
join(__dir__, 'templates')
)
INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'webchat.chat',
)
/*
* Copyright 2009 FriendFeed
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
body {
background: white;
margin: 10px;
}
body,
input {
font-family: sans-serif;
font-size: 10pt;
color: black;
}
table {
border-collapse: collapse;
border: 0;
}
td {
border: 0;
padding: 0;
}
#body {
position: absolute;
bottom: 10px;
left: 10px;
}
#input {
margin-top: 0.5em;
}
#inbox .message {
padding-top: 0.25em;
}
#nav {
float: right;
z-index: 99;
}
// Copyright 2009 FriendFeed
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
$(document).ready(function() {
if (!window.console) window.console = {};
if (!window.console.log) window.console.log = function() {};
$("#messageform").live("submit", function() {
newMessage($(this));
return false;
});
$("#messageform").live("keypress", function(e) {
if (e.keyCode == 13) {
newMessage($(this));
return false;
}
});
$("#message").select();
updater.poll();
});
function newMessage(form) {
var message = form.formToDict();
var disabled = form.find("input[type=submit]");
disabled.disable();
$.postJSON("/a/message/new", message, function(response) {
updater.showMessage(response);
if (message.id) {
form.parent().remove();
} else {
form.find("input[type=text]").val("").select();
disabled.enable();
}
});
}
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
jQuery.postJSON = function(url, args, callback) {
args._xsrf = getCookie("_xsrf");
$.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
success: function(response) {
if (callback) callback(eval("(" + response + ")"));
}, error: function(response) {
console.log("ERROR:", response)
}});
};
jQuery.fn.formToDict = function() {
var fields = this.serializeArray();
var json = {}
for (var i = 0; i < fields.length; i++) {
json[fields[i].name] = fields[i].value;
}
if (json.next) delete json.next;
return json;
};
jQuery.fn.disable = function() {
this.enable(false);
return this;
};
jQuery.fn.enable = function(opt_enable) {
if (arguments.length && !opt_enable) {
this.attr("disabled", "disabled");
} else {
this.removeAttr("disabled");
}
return this;
};
var updater = {
errorSleepTime: 500,
cursor: null,
poll: function() {
var args = {"_xsrf": getCookie("_xsrf")};
if (updater.cursor) args.cursor = updater.cursor;
$.ajax({url: "/a/message/updates", type: "POST", dataType: "text",
data: $.param(args), success: updater.onSuccess,
error: updater.onError});
},
onSuccess: function(response) {
try {
updater.newMessages(eval("(" + response + ")"));
} catch (e) {
updater.onError();
return;
}
updater.errorSleepTime = 500;
window.setTimeout(updater.poll, 0);
},
onError: function(response) {
updater.errorSleepTime *= 2;
console.log("Poll error; sleeping for", updater.errorSleepTime, "ms");
window.setTimeout(updater.poll, updater.errorSleepTime);
},
newMessages: function(response) {
if (!response.messages) return;
updater.cursor = response.cursor;
var messages = response.messages;
updater.cursor = messages[messages.length - 1].id;
console.log(messages.length, "new messages, cursor:", updater.cursor);
for (var i = 0; i < messages.length; i++) {
updater.showMessage(messages[i]);
}
},
showMessage: function(message) {
var existing = $("#m" + message.id);
if (existing.length > 0) return;
var node = $(message.html);
node.hide();
$("#inbox").append(node);
node.slideDown();
}
};
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Chat Demo</title>
<link rel="stylesheet" href="{{ MEDIA_URL }}chat.css" type="text/css"/>
</head>
<body>
<div id="body">
<div id="inbox">
{% for message in messages %}
{% include "message.html" %}
{% endfor %}
</div>
<div id="input">
<form action="/a/message/new" method="post" id="messageform">
<table>
<tr>
<td><input name="body" id="message" style="width:500px"/></td>
<td style="padding-left:5px">
<input type="submit" value="{{ _("Post") }}"/>
<input type="hidden" name="next" value="{{ request.path }}"/>
</td>
</tr>
</table>
</form>
</div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
<script src="{{MEDIA_URL}}chat.js" type="text/javascript"></script>
</body>
</html>
<div class="message" id="m{{ message.id }}"><b>{{ message.from }}: </b>{{ message.body }}</div>
from django.conf.urls.defaults import patterns
from webchat import settings
urlpatterns = patterns('webchat.chat.views',
('^$', 'main'),
('^a/message/new$', 'message_new'),
('^a/message/updates$', 'message_updates'))
urlpatterns += patterns('django.views.static',
(r'^%s(?P<path>.*)$' % settings.MEDIA_URL.lstrip('/'),
'serve', {
'document_root': settings.MEDIA_ROOT,
'show_indexes': True }))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment