Commits

John Costa committed 9a2b96d

current work

Comments (0)

Files changed (90)

+__author__ = 'jcosta'
+import os
+import time
+
+import MySQLdb
+import psycopg2
+import _mysql_exceptions
+
+
+def create_dbs():
+    deadline = time.time() + 60
+    while time.time() < deadline:
+        try:
+            print("create_dbs: let's go.")
+            # TODO: original project didn't need this
+            os.environ['DJANGO_SETTINGS_MODULE'] = 'hellodjango.settings'
+            django_settings = __import__(os.environ['DJANGO_SETTINGS_MODULE'], fromlist='DATABASES')
+
+            print("create_dbs: got settings.")
+            databases = django_settings.DATABASES
+            for name, db in databases.iteritems():
+                host = db['HOST']
+                user = db['USER']
+                password = db['PASSWORD']
+                port = db['PORT']
+                db_name = db['NAME']
+                db_type = db['ENGINE']
+                # see if it is mysql
+                if db_type.endswith('mysql'):
+                    print 'creating database %s on %s' % (db_name, host)
+                    db = MySQLdb.connect(user=user,
+                                        passwd=password,
+                                        host=host,
+                                        port=port)
+                    cur = db.cursor()
+                    print("Check if database is already there.")
+                    cur.execute("""SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
+                                 WHERE SCHEMA_NAME = %s""", (db_name,))
+                    results = cur.fetchone()
+                    if not results:
+                        print("Database %s doesn't exist, lets create it." % db_name)
+                        sql = """CREATE DATABASE IF NOT EXISTS %s """ % (db_name,)
+                        print("> %s" % sql)
+                        cur.execute(sql)
+                        print(".....")
+                    else:
+                        print("database already exists, moving on to next step.")
+                    exit(0)
+                # see if it is postgresql
+                elif db_type.endswith('postgresql_psycopg2'):
+                    print 'creating database %s on %s' % (db_name, host)
+                    con = psycopg2.connect(host=host, user=user, password=password, port=port, database='postgres')
+                    con.set_isolation_level(0)
+                    cur = con.cursor()
+                    try:
+                        cur.execute('CREATE DATABASE %s' % db_name)
+                    except psycopg2.ProgrammingError as detail:
+                        print detail
+                        print 'moving right along...'
+                    exit(0)
+                else:
+                    print("ERROR: {0} is not supported by this script, you will need to create your database by hand.".format(db_type))
+                    exit(1)
+        except psycopg2.OperationalError:
+            print "Could not connect to database. Waiting a little bit."
+            time.sleep(10)
+        except _mysql_exceptions.OperationalError:
+            print "Could not connect to database. Waiting a little bit."
+            time.sleep(10)
+
+
+    print 'Could not connect to database after 1 minutes. Something is wrong.'
+    exit(1)
+
+if __name__ == '__main__':
+    import sys
+    print("create_dbs start")
+    create_dbs()
+    print("create_dbs all done")
+

chat/hellodjango/.gitignore

+*.pyc
+*.DS_Store
Add a comment to this file

chat/hellodjango/__init__.py

Empty file added.

Add a comment to this file

chat/hellodjango/chat/__init__.py

Empty file added.

chat/hellodjango/chat/events.py

+
+from django.shortcuts import get_object_or_404
+from django.utils.html import strip_tags
+from django_socketio import events
+
+from hellodjango.chat.models import ChatRoom
+
+
+@events.on_message(channel="^room-")
+def message(request, socket, context, message):
+    """
+    Event handler for a room receiving a message. First validates a
+    joining user's name and sends them the list of users.
+    """
+    room = get_object_or_404(ChatRoom, id=message["room"])
+    if message["action"] == "start":
+        name = strip_tags(message["name"])
+        user, created = room.users.get_or_create(name=name)
+        if not created:
+            socket.send({"action": "in-use"})
+        else:
+            context["user"] = user
+            users = [u.name for u in room.users.exclude(id=user.id)]
+            socket.send({"action": "started", "users": users})
+            user.session = socket.session.session_id
+            user.save()
+            joined = {"action": "join", "name": user.name, "id": user.id}
+            socket.send_and_broadcast_channel(joined)
+    else:
+        try:
+            user = context["user"]
+        except KeyError:
+            return
+        if message["action"] == "message":
+            message["message"] = strip_tags(message["message"])
+            message["name"] = user.name
+            socket.send_and_broadcast_channel(message)
+
+
+@events.on_finish(channel="^room-")
+def finish(request, socket, context):
+    """
+    Event handler for a socket session ending in a room. Broadcast
+    the user leaving and delete them from the DB.
+    """
+    try:
+        user = context["user"]
+    except KeyError:
+        return
+    left = {"action": "leave", "name": user.name, "id": user.id}
+    socket.broadcast_channel(left)
+    user.delete()

chat/hellodjango/chat/models.py

+
+from django.db import models
+from django.template.defaultfilters import slugify
+
+
+class ChatRoom(models.Model):
+
+    name = models.CharField(max_length=20)
+    slug = models.SlugField(blank=True)
+
+    class Meta:
+        ordering = ("name",)
+
+    def __unicode__(self):
+        return self.name
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ("room", (self.slug,))
+
+    def save(self, *args, **kwargs):
+        if not self.slug:
+            self.slug = slugify(self.name)
+        super(ChatRoom, self).save(*args, **kwargs)
+
+class ChatUser(models.Model):
+
+    name = models.CharField(max_length=20)
+    session = models.CharField(max_length=20)
+    room = models.ForeignKey("chat.ChatRoom", related_name="users")
+
+    class Meta:
+        ordering = ("name",)
+
+    def __unicode__(self):
+        return self.name

chat/hellodjango/chat/static/css/chat.css

+
+* {font-size:16px; font-family:sans-serif;}
+h1 a {font-size:24px;}
+body {margin:20px 0 0 0;}
+#main {padding-bottom:100px;}
+
+form {position:fixed; bottom:0; background:#ccc; width:100%;
+      border-top:1px solid #999; display:none;}
+input {border:1px solid #999; padding:10px; border-radius:5px;}
+#message, #name {width:600px; margin:20px 0 20px 20px;}
+#submit, #leave {cursor:pointer; margin-left:10px; padding:10px 30px;}
+
+ul {margin:0; padding:0; list-style-type:none;}
+h1, p, li {margin:10px 20px;}
+
+#users {position:fixed; right:0; top:0; bottom:0; width:200px;
+        background:#eee; display:none; overflow:hidden;
+        border-left:1px solid #ccc;}
+
+.room {float:left;}
+form label {margin-left:10px;}
+.system {font-weight:bold; color:#f00;}

chat/hellodjango/chat/static/js/chat.js

+$(function() {
+
+    var name, started = false;
+
+    var addItem = function(selector, item) {
+        var template = $(selector).find('script[type="text/x-jquery-tmpl"]');
+        template.tmpl(item).appendTo(selector);
+    };
+
+    var addUser = function(data, show) {
+        addItem('#users', data);
+        if (show) {
+            data.message = 'joins';
+            addMessage(data);
+        }
+    };
+
+    var removeUser = function(data) {
+        $('#user-' + data.id).remove();
+        data.message = 'leaves';
+        addMessage(data);
+    };
+
+    var addMessage = function(data) {
+        var d = new Date();
+        var win = $(window), doc = $(window.document);
+        var bottom = win.scrollTop() + win.height() == doc.height();
+        data.time = $.map([d.getHours(), d.getMinutes(), d.getSeconds()],
+                          function(s) {
+                              s = String(s);
+                              return (s.length == 1 ? '0' : '') + s;
+                          }).join(':');
+        addItem('#messages', data);
+        if (bottom) {
+            window.scrollBy(0, 10000);
+        }
+    };
+
+    $('form').submit(function() {
+        var value = $('#message').val();
+        if (value) {
+            if (!started) {
+                name = value;
+                data = {room: window.room, action: 'start', name: name};
+            } else {
+                data = {room: window.room, action: 'message', message: value};
+            }
+            socket.send(data);
+        }
+        $('#message').val('').focus();
+        return false;
+    });
+
+    $('#leave').click(function() {
+        location = '/';
+    });
+
+    var socket;
+
+    var connected = function() {
+        socket.subscribe('room-' + window.room);
+        if (name) {
+            socket.send({room: window.room, action: 'start', name: name});
+        } else {
+            showForm();
+        }
+    };
+
+    var disconnected = function() {
+        setTimeout(start, 1000);
+    };
+
+    var messaged = function(data) {
+        switch (data.action) {
+            case 'in-use':
+                alert('Name is in use, please choose another');
+                break;
+            case 'started':
+                started = true;
+                $('#submit').val('Send');
+                $('#users').slideDown();
+                $.each(data.users, function(i, name) {
+                    addUser({name: name});
+                });
+                break;
+            case 'join':
+                addUser(data, true);
+                break;
+            case 'leave':
+                removeUser(data);
+                break;
+            case 'message':
+                addMessage(data);
+                break;
+            case 'system':
+                data['name'] = 'SYSTEM';
+                addMessage(data);
+                break;
+        }
+    };
+
+    var start = function() {
+        socket = new io.Socket();
+        socket.connect();
+        socket.on('connect', connected);
+        socket.on('disconnect', disconnected);
+        socket.on('message', messaged);
+    };
+
+    start();
+
+});

chat/hellodjango/chat/templates/base.html

+<!doctype html>
+<html lang="en">
+<head>
+
+    <meta charset="utf-8">
+    <title>{% block title %}Chat{% endblock %}</title>
+    <link rel="stylesheet" href="{{ STATIC_URL }}css/chat.css">
+    {% block extra_css %}{% endblock %}
+    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
+    <script>
+        var showForm = function() {
+            $('form').slideDown(function() {
+                $('form input[type="text"]').focus();
+            });
+        };
+    </script>
+    {% block extra_js %}{% endblock %}
+
+</head>
+<body>
+    <div id="main">{% block main %}{% endblock %}</div>
+    {% block form %}{% endblock %}
+</body>

chat/hellodjango/chat/templates/room.html

+{% extends "base.html" %}
+
+{% block title %}{{ room }}{% endblock %}
+
+{% block extra_js %}
+<script src="http://code.jquery.com/jquery-latest.min.js"></script>
+<script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
+{% load socketio_tags %}
+{% socketio %}
+<script src="{{ STATIC_URL }}js/chat.js"></script>
+<script>window.room = {{ room.id }};</script>
+{% endblock %}
+
+{% block main %}
+<ul id="messages">
+    <script type="text/x-jquery-tmpl"><li class="${action}">(${time}) ${name}: ${message}</li></script>
+</ul>
+<ul id="users">
+    <script type="text/x-jquery-tmpl"><li id="user-${id}">${name}</li></script>
+</ul>
+{% endblock %}
+
+{% block form %}
+<form>
+    <input type="text" id="message" name="message">
+    <input type="submit" id="submit" value="Join">
+    <input type="button" id="leave" value="Leave">
+</form>
+{% endblock %}

chat/hellodjango/chat/templates/rooms.html

+{% extends "base.html" %}
+
+{% block extra_js %}
+<script>$(showForm);</script>
+{% endblock %}
+
+{% block main %}
+{% for room in rooms %}
+<div class="room">
+    <h1><a href="{{ room.get_absolute_url }}">{{ room }}</a></h1>
+    <ul>
+        {% for user in room.users.all %}
+        <li>{{ user }}</li>
+        {% endfor %}
+    </ul>
+</div>
+{% empty %}
+<p>There are currently no rooms! Add one below.</p>
+{% endfor %}
+<br clear="all">
+{% endblock %}
+
+{% block form %}
+<form method="post" action="{% url create %}">
+    <input type="text" id="name" name="name">
+    <input type="submit" id="submit" value="Add Room">
+    {% csrf_token %}
+</form>
+{% endblock %}

chat/hellodjango/chat/templates/system_message.html

+{% extends "base.html" %}
+
+{% block extra_js %}
+<script>$(showForm);</script>
+{% endblock %}
+
+{% block main %}
+<p>{{ message }}</p>
+{% endblock %}
+
+{% block form %}
+<form method="post">
+    <input type="text" id="message" name="message" value="{{ request.POST.message }}">
+    <label for="room">Room:</label>
+    <select id="room" name="room">
+        <option value="">All Rooms</option>
+        {% for room in rooms %}
+        <option{% if request.POST.room == room.id %} selected{% endif %}
+            value="{{ room.id }}">{{ room }}</option>
+        {% endfor %}
+    </select>
+    <input type="submit" id="submit" value="Send">
+    {% csrf_token %}
+</form>
+{% endblock %}

chat/hellodjango/chat/urls.py

+
+from django.conf.urls.defaults import patterns, include, url
+
+
+urlpatterns = patterns("chat.views",
+    url("^$", "rooms", name="rooms"),
+    url("^create/$", "create", name="create"),
+    url("^system_message/$", "system_message", name="system_message"),
+    url("^(?P<slug>.*)$", "room", name="room"),
+)

chat/hellodjango/chat/views.py

+
+from django.contrib.auth.decorators import user_passes_test
+from django.shortcuts import get_object_or_404, render, redirect
+from django_socketio import broadcast, broadcast_channel, NoSocket
+
+from hellodjango.chat.models import ChatRoom
+
+
+def rooms(request, template="rooms.html"):
+    """
+    Homepage - lists all rooms.
+    """
+    context = {"rooms": ChatRoom.objects.all()}
+    return render(request, template, context)
+
+
+def room(request, slug, template="room.html"):
+    """
+    Show a room.
+    """
+    context = {"room": get_object_or_404(ChatRoom, slug=slug)}
+    return render(request, template, context)
+
+
+def create(request):
+    """
+    Handles post from the "Add room" form on the homepage, and
+    redirects to the new room.
+    """
+    name = request.POST.get("name")
+    if name:
+        room, created = ChatRoom.objects.get_or_create(name=name)
+        return redirect(room)
+    return redirect(rooms)
+
+
+@user_passes_test(lambda user: user.is_staff)
+def system_message(request, template="system_message.html"):
+    context = {"rooms": ChatRoom.objects.all()}
+    if request.method == "POST":
+        room = request.POST["room"]
+        data = {"action": "system", "message": request.POST["message"]}
+        try:
+            if room:
+                broadcast_channel(data, channel="room-" + room)
+            else:
+                broadcast(data)
+        except NoSocket, e:
+            context["message"] = e
+        else:
+            context["message"] = "Message sent"
+    return render(request, template, context)

chat/hellodjango/manage.py

+#!/usr/bin/env python
+from django.core.management import execute_manager
+import imp
+from hellodjango import settings
+
+try:
+    imp.find_module('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" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)

chat/hellodjango/settings.py

+# Django settings for hellodjango project.
+import os
+
+import json
+with open('/home/dotcloud/environment.json') as f:
+  env = json.load(f)
+
+settings_dir = os.path.dirname(__file__)
+PROJECT_ROOT = os.path.abspath(os.path.dirname(settings_dir))
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql_psycopg2',
+        'NAME': 'happydb',
+        'USER': env['DOTCLOUD_DB_SQL_LOGIN'],
+        'PASSWORD': env['DOTCLOUD_DB_SQL_PASSWORD'],
+        'HOST': env['DOTCLOUD_DB_SQL_HOST'],
+        'PORT': int(env['DOTCLOUD_DB_SQL_PORT']),
+    }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = '/home/dotcloud/data/media/'
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = '/media/'
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = '/home/dotcloud/volatile/static/'
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+    os.path.join(PROJECT_ROOT, 'static/'),
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+    'django.contrib.staticfiles.finders.FileSystemFinder',
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '@ei90c#1l730c5ru0*2$1zuqr90^fy3_4(wyys&^3ojqqytkwy'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader',
+    'django.template.loaders.app_directories.Loader',
+#     'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'hellodjango.urls'
+
+TEMPLATE_DIRS = (
+    os.path.join(PROJECT_ROOT, 'hellodjango/templates/'),
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    # Uncomment the next line to enable the admin:
+    'django.contrib.admin',
+    # Uncomment the next line to enable admin documentation:
+    # 'django.contrib.admindocs',
+    'django_socketio',
+    'hellodjango.chat',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': True,
+    'formatters': {
+        'verbose': {
+            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
+        },
+        'simple': {
+            'format': '%(levelname)s %(message)s'
+        },
+    },
+    'handlers': {
+        'null': {
+            'level':'DEBUG',
+            'class':'django.utils.log.NullHandler',
+        },
+        'console': {
+            'level': 'DEBUG',
+            'class': 'logging.StreamHandler',
+            'formatter': 'verbose'
+        },
+        'log_file': {
+            'level': 'DEBUG',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'formatter': 'verbose',
+            'filename': '/var/log/supervisor/blogapp.log',
+            'maxBytes': 1024*1024*25, # 25 MB
+            'backupCount': 5,
+        },
+        'mail_admins': {
+            'level': 'ERROR',
+            'class': 'django.utils.log.AdminEmailHandler'
+        }
+    },
+    'loggers': {
+        'django': {
+            'handlers': ['console', 'log_file', 'mail_admins'],
+            'level': 'INFO',
+            'propagate': True,
+        },
+        'django.request': {
+            'handlers': ['console', 'log_file', 'mail_admins'],
+            'level': 'ERROR',
+            'propagate': False,
+        },
+        'django.db.backends': {
+            'handlers': ['console', 'log_file', 'mail_admins'],
+            'level': 'INFO',
+            'propagate': False,
+        },
+        # Catch All Logger -- Captures any other logging
+        '': {
+            'handlers': ['console', 'log_file', 'mail_admins'],
+            'level': 'INFO',
+            'propagate': True,
+        }
+    }
+}
+
+SOCKETIO_HOST = env['DOTCLOUD_SOCKET_HTTP_HOST']
+SOCKETIO_PORT = '80'

chat/hellodjango/templates/helloworld.html

+<html>
+<head><title>Hello World!</title>
+<body>
+    <p>Hello World. The current day and time is {{current_time|date:"DATETIME_FORMAT"}}.</p>
+</body>
+</html>

chat/hellodjango/urls.py

+from django.conf import settings
+from django.conf.urls.defaults import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+    ("^chat/", include('chat.urls')),
+    url("", include('django_socketio.urls')),
+    url(r'^admin/', include(admin.site.urls)),
+)
+
+if settings.DEBUG:
+    urlpatterns += patterns('django.contrib.staticfiles.views',
+        url(r'^static/(?P<path>.*)$', 'serve'),
+    )
+#!/usr/bin/env python
+import os
+os.environ['DJANGO_SETTINGS_MODULE'] = 'hellodjango.settings'
+
+from django.contrib.auth.models import User
+u, created = User.objects.get_or_create(username='admin')
+if created:
+    u.set_password('password')
+    u.is_superuser = True
+    u.is_staff = True
+    u.save()
+location /media/ { root /home/dotcloud/data ; }
+location /static/ { root /home/dotcloud/volatile ; }
+#!/bin/sh
+python createdb.py
+python hellodjango/manage.py syncdb --noinput
+python mkadmin.py
+mkdir -p /home/dotcloud/data/media /home/dotcloud/volatile/static
+python hellodjango/manage.py collectstatic --noinput

chat/requirements.txt

+Django==1.3.1
+django-redis==1.4.5
+gunicorn
+git+https://github.com/johncosta/django-socketio.git

chat/static/404.html

+404 Error: Page not found

chat/static/500.html

+500 error

chat/static/502.html

+502 error

chat/static/503.html

+503 error

chat/static/504.html

+504 error
Add a comment to this file

chat/static/favicon.ico

Added
New image

chat/static/robots.txt

+User-agent: *
+Disallow:
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),'hellodjango')))
+os.environ['DJANGO_SETTINGS_MODULE'] = 'hellodjango.settings'
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()

createdb.py

-import os
-import time
-
-import MySQLdb
-import psycopg2
-import _mysql_exceptions
-from wsgi import *
-
-def create_dbs():
-    deadline = time.time() + 60
-    while time.time() < deadline:
-        try:
-            print("create_dbs: let's go.")
-            django_settings = __import__(os.environ['DJANGO_SETTINGS_MODULE'], fromlist='DATABASES')
-            print("create_dbs: got settings.")
-            databases = django_settings.DATABASES
-            for name, db in databases.iteritems():
-                host = db['HOST']
-                user = db['USER']
-                password = db['PASSWORD']
-                port = db['PORT']
-                db_name = db['NAME']
-                db_type = db['ENGINE']
-                # see if it is mysql
-                if db_type.endswith('mysql'):
-                    print 'creating database %s on %s' % (db_name, host)
-                    db = MySQLdb.connect(user=user,
-                                        passwd=password,
-                                        host=host,
-                                        port=port)
-                    cur = db.cursor()
-                    print("Check if database is already there.")
-                    cur.execute("""SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
-                                 WHERE SCHEMA_NAME = %s""", (db_name,))
-                    results = cur.fetchone()
-                    if not results:
-                        print("Database %s doesn't exist, lets create it." % db_name)
-                        sql = """CREATE DATABASE IF NOT EXISTS %s """ % (db_name,)
-                        print("> %s" % sql)
-                        cur.execute(sql)
-                        print(".....")
-                    else:
-                        print("database already exists, moving on to next step.")
-                    exit(0)
-                # see if it is postgresql
-                elif db_type.endswith('postgresql_psycopg2'):
-                    print 'creating database %s on %s' % (db_name, host)
-                    con = psycopg2.connect(host=host, user=user, password=password, port=port, database='postgres')
-                    con.set_isolation_level(0)
-                    cur = con.cursor()
-                    try:
-                        cur.execute('CREATE DATABASE %s' % db_name)
-                    except psycopg2.ProgrammingError as detail:
-                        print detail
-                        print 'moving right along...'
-                    exit(0)
-                else:
-                    print("ERROR: {0} is not supported by this script, you will need to create your database by hand.".format(db_type))
-                    exit(1)
-        except psycopg2.OperationalError:
-            print "Could not connect to database. Waiting a little bit."
-            time.sleep(10)
-        except _mysql_exceptions.OperationalError:
-            print "Could not connect to database. Waiting a little bit."
-            time.sleep(10)
-
-
-    print 'Could not connect to database after 1 minutes. Something is wrong.'
-    exit(1)
-
-if __name__ == '__main__':
-    import sys
-    print("create_dbs start")
-    create_dbs()
-    print("create_dbs all done")
-
   systempackages:
     - libevent-dev
     - python-all-dev
+  approot: chat
+socket:
+  type: python
+  systempackages:
+      - libevent-dev
+      - python-all-dev
+  approot: socket
 db:
   type: postgresql
 cache:

gunicorn.conf

-workers = 1
-worker_class = 'geventwebsocket.gunicorn.workers.GeventWebSocketWorker'
-pidfile = '/tmp/gunicorn.pid'
-debug = True
-loglevel = 'debug'
-errorlog = '/var/log/supervisor/gunicorn.log'
-daemon = True
-django_settings='/home/dotcloud/current/hellodjango/settings.py'
-timeout=30
-bind = "127.0.0.1:{0}".format(5000)

hellodjango/.gitignore

-*.pyc
-*.DS_Store
Add a comment to this file

hellodjango/__init__.py

Empty file removed.

Add a comment to this file

hellodjango/chat/__init__.py

Empty file removed.

hellodjango/chat/events.py

-
-from django.shortcuts import get_object_or_404
-from django.utils.html import strip_tags
-from django_socketio import events
-
-from chat.models import ChatRoom
-
-
-@events.on_message(channel="^room-")
-def message(request, socket, context, message):
-    """
-    Event handler for a room receiving a message. First validates a
-    joining user's name and sends them the list of users.
-    """
-    room = get_object_or_404(ChatRoom, id=message["room"])
-    if message["action"] == "start":
-        name = strip_tags(message["name"])
-        user, created = room.users.get_or_create(name=name)
-        if not created:
-            socket.send({"action": "in-use"})
-        else:
-            context["user"] = user
-            users = [u.name for u in room.users.exclude(id=user.id)]
-            socket.send({"action": "started", "users": users})
-            user.session = socket.session.session_id
-            user.save()
-            joined = {"action": "join", "name": user.name, "id": user.id}
-            socket.send_and_broadcast_channel(joined)
-    else:
-        try:
-            user = context["user"]
-        except KeyError:
-            return
-        if message["action"] == "message":
-            message["message"] = strip_tags(message["message"])
-            message["name"] = user.name
-            socket.send_and_broadcast_channel(message)
-
-
-@events.on_finish(channel="^room-")
-def finish(request, socket, context):
-    """
-    Event handler for a socket session ending in a room. Broadcast
-    the user leaving and delete them from the DB.
-    """
-    try:
-        user = context["user"]
-    except KeyError:
-        return
-    left = {"action": "leave", "name": user.name, "id": user.id}
-    socket.broadcast_channel(left)
-    user.delete()

hellodjango/chat/models.py

-
-from django.db import models
-from django.template.defaultfilters import slugify
-
-
-class ChatRoom(models.Model):
-
-    name = models.CharField(max_length=20)
-    slug = models.SlugField(blank=True)
-
-    class Meta:
-        ordering = ("name",)
-
-    def __unicode__(self):
-        return self.name
-
-    @models.permalink
-    def get_absolute_url(self):
-        return ("room", (self.slug,))
-
-    def save(self, *args, **kwargs):
-        if not self.slug:
-            self.slug = slugify(self.name)
-        super(ChatRoom, self).save(*args, **kwargs)
-
-class ChatUser(models.Model):
-
-    name = models.CharField(max_length=20)
-    session = models.CharField(max_length=20)
-    room = models.ForeignKey("chat.ChatRoom", related_name="users")
-
-    class Meta:
-        ordering = ("name",)
-
-    def __unicode__(self):
-        return self.name

hellodjango/chat/static/css/chat.css

-
-* {font-size:16px; font-family:sans-serif;}
-h1 a {font-size:24px;}
-body {margin:20px 0 0 0;}
-#main {padding-bottom:100px;}
-
-form {position:fixed; bottom:0; background:#ccc; width:100%;
-      border-top:1px solid #999; display:none;}
-input {border:1px solid #999; padding:10px; border-radius:5px;}
-#message, #name {width:600px; margin:20px 0 20px 20px;}
-#submit, #leave {cursor:pointer; margin-left:10px; padding:10px 30px;}
-
-ul {margin:0; padding:0; list-style-type:none;}
-h1, p, li {margin:10px 20px;}
-
-#users {position:fixed; right:0; top:0; bottom:0; width:200px;
-        background:#eee; display:none; overflow:hidden;
-        border-left:1px solid #ccc;}
-
-.room {float:left;}
-form label {margin-left:10px;}
-.system {font-weight:bold; color:#f00;}

hellodjango/chat/static/js/chat.js

-$(function() {
-
-    var name, started = false;
-
-    var addItem = function(selector, item) {
-        var template = $(selector).find('script[type="text/x-jquery-tmpl"]');
-        template.tmpl(item).appendTo(selector);
-    };
-
-    var addUser = function(data, show) {
-        addItem('#users', data);
-        if (show) {
-            data.message = 'joins';
-            addMessage(data);
-        }
-    };
-
-    var removeUser = function(data) {
-        $('#user-' + data.id).remove();
-        data.message = 'leaves';
-        addMessage(data);
-    };
-
-    var addMessage = function(data) {
-        var d = new Date();
-        var win = $(window), doc = $(window.document);
-        var bottom = win.scrollTop() + win.height() == doc.height();
-        data.time = $.map([d.getHours(), d.getMinutes(), d.getSeconds()],
-                          function(s) {
-                              s = String(s);
-                              return (s.length == 1 ? '0' : '') + s;
-                          }).join(':');
-        addItem('#messages', data);
-        if (bottom) {
-            window.scrollBy(0, 10000);
-        }
-    };
-
-    $('form').submit(function() {
-        var value = $('#message').val();
-        if (value) {
-            if (!started) {
-                name = value;
-                data = {room: window.room, action: 'start', name: name};
-            } else {
-                data = {room: window.room, action: 'message', message: value};
-            }
-            socket.send(data);
-        }
-        $('#message').val('').focus();
-        return false;
-    });
-
-    $('#leave').click(function() {
-        location = '/';
-    });
-
-    var socket;
-
-    var connected = function() {
-        socket.subscribe('room-' + window.room);
-        if (name) {
-            socket.send({room: window.room, action: 'start', name: name});
-        } else {
-            showForm();
-        }
-    };
-
-    var disconnected = function() {
-        setTimeout(start, 1000);
-    };
-
-    var messaged = function(data) {
-        switch (data.action) {
-            case 'in-use':
-                alert('Name is in use, please choose another');
-                break;
-            case 'started':
-                started = true;
-                $('#submit').val('Send');
-                $('#users').slideDown();
-                $.each(data.users, function(i, name) {
-                    addUser({name: name});
-                });
-                break;
-            case 'join':
-                addUser(data, true);
-                break;
-            case 'leave':
-                removeUser(data);
-                break;
-            case 'message':
-                addMessage(data);
-                break;
-            case 'system':
-                data['name'] = 'SYSTEM';
-                addMessage(data);
-                break;
-        }
-    };
-
-    var start = function() {
-        socket = new io.Socket();
-        socket.connect();
-        socket.on('connect', connected);
-        socket.on('disconnect', disconnected);
-        socket.on('message', messaged);
-    };
-
-    start();
-
-});

hellodjango/chat/templates/base.html

-<!doctype html>
-<html lang="en">
-<head>
-
-    <meta charset="utf-8">
-    <title>{% block title %}Chat{% endblock %}</title>
-    <link rel="stylesheet" href="{{ STATIC_URL }}css/chat.css">
-    {% block extra_css %}{% endblock %}
-    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
-    <script>
-        var showForm = function() {
-            $('form').slideDown(function() {
-                $('form input[type="text"]').focus();
-            });
-        };
-    </script>
-    {% block extra_js %}{% endblock %}
-
-</head>
-<body>
-    <div id="main">{% block main %}{% endblock %}</div>
-    {% block form %}{% endblock %}
-</body>

hellodjango/chat/templates/room.html

-{% extends "base.html" %}
-
-{% block title %}{{ room }}{% endblock %}
-
-{% block extra_js %}
-<script src="http://code.jquery.com/jquery-latest.min.js"></script>
-<script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
-{% load socketio_tags %}
-{% socketio %}
-<script src="{{ STATIC_URL }}js/chat.js"></script>
-<script>window.room = {{ room.id }};</script>
-{% endblock %}
-
-{% block main %}
-<ul id="messages">
-    <script type="text/x-jquery-tmpl"><li class="${action}">(${time}) ${name}: ${message}</li></script>
-</ul>
-<ul id="users">
-    <script type="text/x-jquery-tmpl"><li id="user-${id}">${name}</li></script>
-</ul>
-{% endblock %}
-
-{% block form %}
-<form>
-    <input type="text" id="message" name="message">
-    <input type="submit" id="submit" value="Join">
-    <input type="button" id="leave" value="Leave">
-</form>
-{% endblock %}

hellodjango/chat/templates/rooms.html

-{% extends "base.html" %}
-
-{% block extra_js %}
-<script>$(showForm);</script>
-{% endblock %}
-
-{% block main %}
-{% for room in rooms %}
-<div class="room">
-    <h1><a href="{{ room.get_absolute_url }}">{{ room }}</a></h1>
-    <ul>
-        {% for user in room.users.all %}
-        <li>{{ user }}</li>
-        {% endfor %}
-    </ul>
-</div>
-{% empty %}
-<p>There are currently no rooms! Add one below.</p>
-{% endfor %}
-<br clear="all">
-{% endblock %}
-
-{% block form %}
-<form method="post" action="{% url create %}">
-    <input type="text" id="name" name="name">
-    <input type="submit" id="submit" value="Add Room">
-    {% csrf_token %}
-</form>
-{% endblock %}

hellodjango/chat/templates/system_message.html

-{% extends "base.html" %}
-
-{% block extra_js %}
-<script>$(showForm);</script>
-{% endblock %}
-
-{% block main %}
-<p>{{ message }}</p>
-{% endblock %}
-
-{% block form %}
-<form method="post">
-    <input type="text" id="message" name="message" value="{{ request.POST.message }}">
-    <label for="room">Room:</label>
-    <select id="room" name="room">
-        <option value="">All Rooms</option>
-        {% for room in rooms %}
-        <option{% if request.POST.room == room.id %} selected{% endif %}
-            value="{{ room.id }}">{{ room }}</option>
-        {% endfor %}
-    </select>
-    <input type="submit" id="submit" value="Send">
-    {% csrf_token %}
-</form>
-{% endblock %}

hellodjango/chat/urls.py

-
-from django.conf.urls.defaults import patterns, include, url
-
-
-urlpatterns = patterns("chat.views",
-    url("^$", "rooms", name="rooms"),
-    url("^create/$", "create", name="create"),
-    url("^system_message/$", "system_message", name="system_message"),
-    url("^(?P<slug>.*)$", "room", name="room"),
-)

hellodjango/chat/views.py

-
-from django.contrib.auth.decorators import user_passes_test
-from django.shortcuts import get_object_or_404, render, redirect
-from django_socketio import broadcast, broadcast_channel, NoSocket
-
-from chat.models import ChatRoom
-
-
-def rooms(request, template="rooms.html"):
-    """
-    Homepage - lists all rooms.
-    """
-    context = {"rooms": ChatRoom.objects.all()}
-    return render(request, template, context)
-
-
-def room(request, slug, template="room.html"):
-    """
-    Show a room.
-    """
-    context = {"room": get_object_or_404(ChatRoom, slug=slug)}
-    return render(request, template, context)
-
-
-def create(request):
-    """
-    Handles post from the "Add room" form on the homepage, and
-    redirects to the new room.
-    """
-    name = request.POST.get("name")
-    if name:
-        room, created = ChatRoom.objects.get_or_create(name=name)
-        return redirect(room)
-    return redirect(rooms)
-
-
-@user_passes_test(lambda user: user.is_staff)
-def system_message(request, template="system_message.html"):
-    context = {"rooms": ChatRoom.objects.all()}
-    if request.method == "POST":
-        room = request.POST["room"]
-        data = {"action": "system", "message": request.POST["message"]}
-        try:
-            if room:
-                broadcast_channel(data, channel="room-" + room)
-            else:
-                broadcast(data)
-        except NoSocket, e:
-            context["message"] = e
-        else:
-            context["message"] = "Message sent"
-    return render(request, template, context)

hellodjango/manage.py

-#!/usr/bin/env python
-from django.core.management import execute_manager
-import imp
-try:
-    imp.find_module('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" % __file__)
-    sys.exit(1)
-
-import settings
-
-if __name__ == "__main__":
-    execute_manager(settings)

hellodjango/settings.py

-# Django settings for hellodjango project.
-import os
-
-import json
-with open('/home/dotcloud/environment.json') as f:
-  env = json.load(f)
-
-settings_dir = os.path.dirname(__file__)
-PROJECT_ROOT = os.path.abspath(os.path.dirname(settings_dir))
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-
-ADMINS = (
-    # ('Your Name', 'your_email@example.com'),
-)
-
-MANAGERS = ADMINS
-
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.postgresql_psycopg2',
-        'NAME': 'happydb',
-        'USER': env['DOTCLOUD_DB_SQL_LOGIN'],
-        'PASSWORD': env['DOTCLOUD_DB_SQL_PASSWORD'],
-        'HOST': env['DOTCLOUD_DB_SQL_HOST'],
-        'PORT': int(env['DOTCLOUD_DB_SQL_PORT']),
-    }
-}
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# On Unix systems, a value of None will cause Django to use the same
-# timezone as the operating system.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
-TIME_ZONE = 'America/Chicago'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en-us'
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale
-USE_L10N = True
-
-# Absolute filesystem path to the directory that will hold user-uploaded files.
-# Example: "/home/media/media.lawrence.com/media/"
-MEDIA_ROOT = '/home/dotcloud/data/media/'
-
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash.
-# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
-MEDIA_URL = '/media/'
-
-# Absolute path to the directory static files should be collected to.
-# Don't put anything in this directory yourself; store your static files
-# in apps' "static/" subdirectories and in STATICFILES_DIRS.
-# Example: "/home/media/media.lawrence.com/static/"
-STATIC_ROOT = '/home/dotcloud/volatile/static/'
-
-# URL prefix for static files.
-# Example: "http://media.lawrence.com/static/"
-STATIC_URL = '/static/'
-
-# Additional locations of static files
-STATICFILES_DIRS = (
-    os.path.join(PROJECT_ROOT, 'static/'),
-)
-
-# List of finder classes that know how to find static files in
-# various locations.
-STATICFILES_FINDERS = (
-    'django.contrib.staticfiles.finders.FileSystemFinder',
-    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
-#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
-)
-
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = '@ei90c#1l730c5ru0*2$1zuqr90^fy3_4(wyys&^3ojqqytkwy'
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
-    'django.template.loaders.filesystem.Loader',
-    'django.template.loaders.app_directories.Loader',
-#     'django.template.loaders.eggs.Loader',
-)
-
-MIDDLEWARE_CLASSES = (
-    'django.middleware.common.CommonMiddleware',
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-)
-
-ROOT_URLCONF = 'hellodjango.urls'
-
-TEMPLATE_DIRS = (
-    os.path.join(PROJECT_ROOT, 'hellodjango/templates/'),
-)
-
-INSTALLED_APPS = (
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.sites',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    # Uncomment the next line to enable the admin:
-    'django.contrib.admin',
-    # Uncomment the next line to enable admin documentation:
-    # 'django.contrib.admindocs',
-    'django_socketio',
-    'chat',
-)
-
-# A sample logging configuration. The only tangible logging
-# performed by this configuration is to send an email to
-# the site admins on every HTTP 500 error.
-# See http://docs.djangoproject.com/en/dev/topics/logging for
-# more details on how to customize your logging configuration.
-LOGGING = {
-    'version': 1,
-    'disable_existing_loggers': True,
-    'formatters': {
-        'verbose': {
-            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
-        },
-        'simple': {
-            'format': '%(levelname)s %(message)s'
-        },
-    },
-    'handlers': {
-        'null': {
-            'level':'DEBUG',
-            'class':'django.utils.log.NullHandler',
-        },
-        'console': {
-            'level': 'DEBUG',
-            'class': 'logging.StreamHandler',
-            'formatter': 'verbose'
-        },
-        'log_file': {
-            'level': 'DEBUG',
-            'class': 'logging.handlers.RotatingFileHandler',
-            'formatter': 'verbose',
-            'filename': '/var/log/supervisor/blogapp.log',
-            'maxBytes': 1024*1024*25, # 25 MB
-            'backupCount': 5,
-        },
-        'mail_admins': {
-            'level': 'ERROR',
-            'class': 'django.utils.log.AdminEmailHandler'
-        }
-    },
-    'loggers': {
-        'django': {
-            'handlers': ['console', 'log_file', 'mail_admins'],
-            'level': 'INFO',
-            'propagate': True,
-        },
-        'django.request': {
-            'handlers': ['console', 'log_file', 'mail_admins'],
-            'level': 'ERROR',
-            'propagate': False,
-        },
-        'django.db.backends': {
-            'handlers': ['console', 'log_file', 'mail_admins'],
-            'level': 'INFO',
-            'propagate': False,
-        },
-        # Catch All Logger -- Captures any other logging
-        '': {
-            'handlers': ['console', 'log_file', 'mail_admins'],
-            'level': 'INFO',
-            'propagate': True,
-        }
-    }
-}
-
-SOCKETIO_HOST = '127.0.0.1'
-SOCKETIO_PORT = '5000'

hellodjango/templates/helloworld.html

-<html>
-<head><title>Hello World!</title>
-<body>
-    <p>Hello World. The current day and time is {{current_time|date:"DATETIME_FORMAT"}}.</p>
-</body>
-</html>

hellodjango/urls.py

-from django.conf import settings
-from django.conf.urls.defaults import patterns, include, url
-
-# Uncomment the next two lines to enable the admin:
-from django.contrib import admin
-admin.autodiscover()
-
-urlpatterns = patterns('',
-    ("^chat/", include('chat.urls')),
-    url("", include('django_socketio.urls')),
-    url(r'^admin/', include(admin.site.urls)),
-)
-
-if settings.DEBUG:
-    urlpatterns += patterns('django.contrib.staticfiles.views',
-        url(r'^static/(?P<path>.*)$', 'serve'),
-    )

mkadmin.py

-#!/usr/bin/env python
-from wsgi import *
-from django.contrib.auth.models import User
-u, created = User.objects.get_or_create(username='admin')
-if created:
-    u.set_password('password')
-    u.is_superuser = True
-    u.is_staff = True
-    u.save()

nginx.conf

-location /media/ { root /home/dotcloud/data ; }
-location /static/ { root /home/dotcloud/volatile ; }
-location /socket.io/xhr-polling/ { proxy_pass http://localhost:5000; }

postinstall

-#!/bin/sh
-python createdb.py
-python hellodjango/manage.py syncdb --noinput
-python mkadmin.py
-mkdir -p /home/dotcloud/data/media /home/dotcloud/volatile/static
-python hellodjango/manage.py collectstatic --noinput

requirements.txt

-Django==1.3.1
-django-redis==1.4.5
-gunicorn
-django-socketio

socket/__init__.py

+__author__ = 'jcosta'

socket/gunicorn.conf

+workers = 1
+worker_class = 'geventwebsocket.gunicorn.workers.GeventWebSocketWorker'
+pidfile = '/tmp/gunicorn.pid'
+debug = True
+loglevel = 'debug'
+errorlog = '/var/log/supervisor/gunicorn.log'
+django_settings='/home/dotcloud/current/hellodjango/settings.py'

socket/hellodjango/.gitignore

+*.pyc
+*.DS_Store
Add a comment to this file

socket/hellodjango/__init__.py

Empty file added.

Add a comment to this file

socket/hellodjango/chat/__init__.py

Empty file added.

socket/hellodjango/chat/events.py

+
+from django.shortcuts import get_object_or_404
+from django.utils.html import strip_tags
+from django_socketio import events
+
+from chat.models import ChatRoom
+
+
+@events.on_message(channel="^room-")
+def message(request, socket, context, message):
+    """
+    Event handler for a room receiving a message. First validates a
+    joining user's name and sends them the list of users.
+    """
+    room = get_object_or_404(ChatRoom, id=message["room"])
+    if message["action"] == "start":
+        name = strip_tags(message["name"])
+        user, created = room.users.get_or_create(name=name)
+        if not created:
+            socket.send({"action": "in-use"})
+        else:
+            context["user"] = user
+            users = [u.name for u in room.users.exclude(id=user.id)]
+            socket.send({"action": "started", "users": users})
+            user.session = socket.session.session_id
+            user.save()
+            joined = {"action": "join", "name": user.name, "id": user.id}
+            socket.send_and_broadcast_channel(joined)
+    else:
+        try:
+            user = context["user"]
+        except KeyError:
+            return
+        if message["action"] == "message":
+            message["message"] = strip_tags(message["message"])
+            message["name"] = user.name
+            socket.send_and_broadcast_channel(message)
+
+
+@events.on_finish(channel="^room-")
+def finish(request, socket, context):
+    """
+    Event handler for a socket session ending in a room. Broadcast
+    the user leaving and delete them from the DB.
+    """
+    try:
+        user = context["user"]
+    except KeyError:
+        return
+    left = {"action": "leave", "name": user.name, "id": user.id}
+    socket.broadcast_channel(left)
+    user.delete()

socket/hellodjango/chat/models.py

+
+from django.db import models
+from django.template.defaultfilters import slugify
+
+
+class ChatRoom(models.Model):
+
+    name = models.CharField(max_length=20)
+    slug = models.SlugField(blank=True)
+
+    class Meta:
+        ordering = ("name",)
+
+    def __unicode__(self):
+        return self.name
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ("room", (self.slug,))
+
+    def save(self, *args, **kwargs):
+        if not self.slug:
+            self.slug = slugify(self.name)
+        super(ChatRoom, self).save(*args, **kwargs)
+
+class ChatUser(models.Model):
+
+    name = models.CharField(max_length=20)
+    session = models.CharField(max_length=20)
+    room = models.ForeignKey("chat.ChatRoom", related_name="users")
+
+    class Meta:
+        ordering = ("name",)
+
+    def __unicode__(self):
+        return self.name

socket/hellodjango/chat/static/css/chat.css

+
+* {font-size:16px; font-family:sans-serif;}
+h1 a {font-size:24px;}
+body {margin:20px 0 0 0;}
+#main {padding-bottom:100px;}
+
+form {position:fixed; bottom:0; background:#ccc; width:100%;
+      border-top:1px solid #999; display:none;}
+input {border:1px solid #999; padding:10px; border-radius:5px;}
+#message, #name {width:600px; margin:20px 0 20px 20px;}
+#submit, #leave {cursor:pointer; margin-left:10px; padding:10px 30px;}
+
+ul {margin:0; padding:0; list-style-type:none;}
+h1, p, li {margin:10px 20px;}
+
+#users {position:fixed; right:0; top:0; bottom:0; width:200px;
+        background:#eee; display:none; overflow:hidden;
+        border-left:1px solid #ccc;}
+
+.room {float:left;}
+form label {margin-left:10px;}
+.system {font-weight:bold; color:#f00;}

socket/hellodjango/chat/static/js/chat.js

+$(function() {
+
+    var name, started = false;
+
+    var addItem = function(selector, item) {
+        var template = $(selector).find('script[type="text/x-jquery-tmpl"]');
+        template.tmpl(item).appendTo(selector);
+    };
+
+    var addUser = function(data, show) {
+        addItem('#users', data);
+        if (show) {
+            data.message = 'joins';
+            addMessage(data);
+        }
+    };
+
+    var removeUser = function(data) {
+        $('#user-' + data.id).remove();
+        data.message = 'leaves';
+        addMessage(data);
+    };
+
+    var addMessage = function(data) {
+        var d = new Date();
+        var win = $(window), doc = $(window.document);
+        var bottom = win.scrollTop() + win.height() == doc.height();
+        data.time = $.map([d.getHours(), d.getMinutes(), d.getSeconds()],
+                          function(s) {
+                              s = String(s);
+                              return (s.length == 1 ? '0' : '') + s;
+                          }).join(':');
+        addItem('#messages', data);
+        if (bottom) {
+            window.scrollBy(0, 10000);
+        }
+    };
+
+    $('form').submit(function() {
+        var value = $('#message').val();
+        if (value) {
+            if (!started) {
+                name = value;
+                data = {room: window.room, action: 'start', name: name};
+            } else {
+                data = {room: window.room, action: 'message', message: value};
+            }
+            socket.send(data);
+        }
+        $('#message').val('').focus();
+        return false;
+    });
+
+    $('#leave').click(function() {
+        location = '/';
+    });
+
+    var socket;
+
+    var connected = function() {
+        socket.subscribe('room-' + window.room);
+        if (name) {
+            socket.send({room: window.room, action: 'start', name: name});
+        } else {
+            showForm();
+        }
+    };
+
+    var disconnected = function() {
+        setTimeout(start, 1000);
+    };
+
+    var messaged = function(data) {
+        switch (data.action) {
+            case 'in-use':
+                alert('Name is in use, please choose another');
+                break;
+            case 'started':
+                started = true;
+                $('#submit').val('Send');
+                $('#users').slideDown();
+                $.each(data.users, function(i, name) {
+                    addUser({name: name});
+                });
+                break;
+            case 'join':
+                addUser(data, true);
+                break;
+            case 'leave':
+                removeUser(data);
+                break;
+            case 'message':
+                addMessage(data);
+                break;
+            case 'system':
+                data['name'] = 'SYSTEM';
+                addMessage(data);
+                break;
+        }
+    };
+
+    var start = function() {
+        socket = new io.Socket();
+        socket.connect();
+        socket.on('connect', connected);
+        socket.on('disconnect', disconnected);
+        socket.on('message', messaged);
+    };
+
+    start();
+
+});

socket/hellodjango/chat/templates/base.html

+<!doctype html>
+<html lang="en">
+<head>
+
+    <meta charset="utf-8">
+    <title>{% block title %}Chat{% endblock %}</title>
+    <link rel="stylesheet" href="{{ STATIC_URL }}css/chat.css">
+    {% block extra_css %}{% endblock %}
+    <script src="http://code.jquery.com/jquery-latest.min.js"></script>
+    <script>
+        var showForm = function() {
+            $('form').slideDown(function() {
+                $('form input[type="text"]').focus();
+            });
+        };
+    </script>
+    {% block extra_js %}{% endblock %}
+
+</head>
+<body>
+    <div id="main">{% block main %}{% endblock %}</div>
+    {% block form %}{% endblock %}
+</body>

socket/hellodjango/chat/templates/room.html

+{% extends "base.html" %}
+
+{% block title %}{{ room }}{% endblock %}
+
+{% block extra_js %}
+<script src="http://code.jquery.com/jquery-latest.min.js"></script>
+<script src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
+{% load socketio_tags %}
+{% socketio %}
+<script src="{{ STATIC_URL }}js/chat.js"></script>
+<script>window.room = {{ room.id }};</script>
+{% endblock %}
+
+{% block main %}
+<ul id="messages">
+    <script type="text/x-jquery-tmpl"><li class="${action}">(${time}) ${name}: ${message}</li></script>
+</ul>
+<ul id="users">
+    <script type="text/x-jquery-tmpl"><li id="user-${id}">${name}</li></script>
+</ul>
+{% endblock %}
+
+{% block form %}
+<form>
+    <input type="text" id="message" name="message">
+    <input type="submit" id="submit" value="Join">
+    <input type="button" id="leave" value="Leave">
+</form>
+{% endblock %}

socket/hellodjango/chat/templates/rooms.html

+{% extends "base.html" %}
+
+{% block extra_js %}
+<script>$(showForm);</script>
+{% endblock %}
+
+{% block main %}
+{% for room in rooms %}
+<div class="room">
+    <h1><a href="{{ room.get_absolute_url }}">{{ room }}</a></h1>
+    <ul>
+        {% for user in room.users.all %}
+        <li>{{ user }}</li>
+        {% endfor %}
+    </ul>
+</div>
+{% empty %}
+<p>There are currently no rooms! Add one below.</p>
+{% endfor %}
+<br clear="all">
+{% endblock %}
+
+{% block form %}
+<form method="post" action="{% url create %}">
+    <input type="text" id="name" name="name">
+    <input type="submit" id="submit" value="Add Room">
+    {% csrf_token %}
+</form>