Django powered AJAX Chat – Part 2


I’m sorry I left my last Django powered AJAX Chat – Part 1 post so abandoned, we have been dealing with an ever increasing amount of study stuff at the uni and I didn’t have much time to keep on writing about our chat application. I have been working hard to improve some of the functionality of the application, both on the Django side and the jQuery side. There are still many rough edges, but this time I’ve got some more along the lines of plug&play. So, just for those impatient ones who like to see to believe, this is a screenshot of it in action:

Screenshot of the Django chat application using the jQuery Javascript library
Screenshot of the Django chat application using the jQuery Javascript library, you can appreciate the use of special formatting for users who are joining the room, normal formatting for user messages and... yes... smileys!

Pretty neat, huh? There is a lot of ground to cover with this one, so maybe I’ll split this post in two… we will see.

The Idea

Have a pure Django + jQuery chat application working on our site. The users will be presented with a GUI like the one above, listing the messages in the chat room, and will be allowed to submit messages though a text box. A Javascript process on the user’s browser will be quering the server every N seconds for new messages and the server will respond in JSON format, returning a list of the new messages (if any) along with their authors, a timestamp (optional) and an id (very important). If there are new messages, they will be formatted and correctly appended to the chat box. Different types of messages should be supported, among them are: user joins, user leaves, system notifications, special messages, and of course normal user messages. Extras: Try to make it feel as natural as possible, using soft scrolling when new messages arrive, capturing the press of the Enter key when typing a message to automatically send the written text, clear the input box and allow the user to keep on typing… and smilies!

Prerequisites

Before we start, some prerequisites: Django must have the auth and contenttype apps installed, if you are using the Django Admin they are all set up and you can just keep on reading. Rule of thumb: if the admin works, chat will too.

Where Can I Download This and Other Announcements

I though I might as well upload this as a Google Code project (gotta love version control), so you can checkout the project at: http://code.google.com/p/django-jchat/, or download the zip file in the downloads tab. I still strongly recommend that you read my rambles about the code below, for you can gain an overall insight of the code and what I wanted to achieve with each line of code.

Also consider that this is a post I started writing in May… but got hold back until now due to time constraints… so the pasted code could be… a little bit outdated.

The Models

To begin let’s revise the models, as you might recall from the last post we had only two models, a Room model and a Message model. (I didn’t work on a Session model to display the list of users because we already had something like that, so if you want to add one yourself, be my guest, and make sure to drop me a mail on how you modified/improved it :D.)

These models basically behave like your average IRC chat server, you have several chat rooms which users can join and message in. I wanted to make sure my chat app was as portable as possible, so I first worked on a home made solution to allow my Room models to reference different kinds of objects, needless to say that is was sloppy. Unsatisfied with my solution I started looking around for better ones. It didn’t take me long until I stumbled upon Django Admin Log model, which had the capacity of referencing each of my site’s models. Looking into the code I discovered the magic of contenttype’s GenericType and generic.GenericForeignKey classes. They are a work of art! They allow me to establish relationships between my models and any other model with just three extra fields in my models and almost no programming! This is how the Room model looks now:

from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType, ContentTypeManager
from django.contrib.contenttypes import generic
from datetime import datetime

class RoomManager(models.Manager):
    '''Custom model manager for rooms, this is used for "table-level" operations.
    All methods defined here can be invoked through the Room.objects class.
    @see: http://docs.djangoproject.com/en/1.0/topics/db/managers/#topics-db-managers
    Also see GenericTypes from the contenttypes django app!
    @see: http://docs.djangoproject.com/en/1.0/ref/contrib/contenttypes/''' 

    def create(self, object):
        '''Creates a new chat room and registers it to the calling object'''
        r = self.model(content_object=object)
        r.save()
        return r

    def get_for_object(self, object):
        '''Try to get a room related to the object passed.'''
        return self.get(content_type=ContentType.objects.get_for_model(object), object_id=object.pk)

    def get_or_create(self, object):
        '''Save us from the hassle of validating the return value of get_for_object and create a room if none exists'''
        try:
            return self.get_for_object(object)
        except Room.DoesNotExist:
            return self.create(object)

class Room(models.Model):
    '''Representation of a generic chat room'''
    content_type = models.ForeignKey(ContentType) # to what kind of object is this related
    object_id = models.PositiveIntegerField() # to which instace of the aforementioned object is this related
    content_object = generic.GenericForeignKey('content_type','object_id') # use both up, USE THIS WHEN INSTANCING THE MODEL
    created = models.DateTimeField(default=datetime.now())
    comment = models.TextField(blank=True, null=True)
    objects = RoomManager() # custom manager

    def __add_message(self, type, sender, message=None):
        '''Generic function for adding a message to the chat room'''
        m = Message(room=self, type=type, author=sender, message=message)
        m.save()
        return m

    def say(self, sender, message):
        '''Say something in to the chat room'''
        return self.__add_message('m', sender, message)
    def join(self, user):
        '''A user has joined'''
        return self.__add_message('j', user)

    def leave(self, user):
        '''A user has leaved'''
        return self.__add_message('l', user)

    def messages(self, after_pk=None, after_date=None):
        '''List messages, after the given id or date'''
        m = Message.objects.filter(room=self)
        if after_pk:
            m = m.filter(pk__gt=after_pk)
        if after_date:
            m = m.filter(timestamp__gte=after_date)
        return m.order_by('pk')

    def last_message_id(self):
        '''Return last message sent to room'''
        m = Message.objects.filter(room=self).order_by('-pk')
        if m:
            return m[0].id
        else:
            return 0

    def __unicode__(self):
        return 'Chat for %s %d' % (self.content_type, self.object_id)
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType, ContentTypeManager
from django.contrib.contenttypes import generic
from datetime import datetime
class RoomManager(models.Manager):
”’Custom model manager for rooms, this is used for “table-level” operations.
All methods defined here can be invoked through the Room.objects class.
Also see GenericTypes from the contenttypes django app!
def create(self, object):
”’Creates a new chat room and registers it to the calling object”’
r = self.model(content_object=object)
r.save()
return r
def get_for_object(self, object):
”’Try to get a room related to the object passed.”’
return self.get(content_type=ContentType.objects.get_for_model(object), object_id=object.pk)
def get_or_create(self, object):
”’Save us from the hassle of validating the return value of get_for_object and create a room if none exists”’
try:
return self.get_for_object(object)
except Room.DoesNotExist:
return self.create(object)
class Room(models.Model):
”’Representation of a generic chat room”’
content_type = models.ForeignKey(ContentType) # to what kind of object is this related
object_id = models.PositiveIntegerField() # to which instace of the aforementioned object is this related
content_object = generic.GenericForeignKey(‘content_type’,’object_id’) # use both up, USE THIS WHEN INSTANCING THE MODEL
created = models.DateTimeField(default=datetime.now())
comment = models.TextField(blank=True, null=True)
objects = RoomManager() # custom manager
def __add_message(self, type, sender, message=None):
”’Generic function for adding a message to the chat room”’
m = Message(room=self, type=type, author=sender, message=message)
m.save()
return m
def say(self, sender, message):
”’Say something in to the chat room”’
return self.__add_message(‘m’, sender, message)
def join(self, user):
”’A user has joined”’
return self.__add_message(‘j’, user)
def leave(self, user):
”’A user has leaved”’
return self.__add_message(‘l’, user)
def messages(self, after_pk=None, after_date=None):
”’List messages, after the given id or date”’
m = Message.objects.filter(room=self)
if after_pk:
m = m.filter(pk__gt=after_pk)
if after_date:
m = m.filter(timestamp__gte=after_date)
return m.order_by(‘pk’)
def last_message_id(self):
”’Return last message sent to room”’
m = Message.objects.filter(room=self).order_by(‘-pk’)
if m:
return m[0].id
else:
return 0
def __unicode__(self):

return ‘Chat for %s %d’ % (self.content_type, self.object_id)

I hope the commentaries are enough for most of you, I don’t want to bore you down with details, and the comments make the code (in my humble opinion) pretty much self explanatory. In any case, just use the comments below to share your inquiries on the code. Don’t despair, I will elaborate on the methods defined at RoomManager later on when we talk about the views.

In any case, the Message model was pretty much unchanged, for the sake of completness I’ll post it here too:

MESSAGE_TYPE_CHOICES = (
    ('s','system'),
    ('a','action'),
    ('m', 'message'),
    ('j','join'),
    ('l','leave'),
    ('n','notification')
)

class Message(models.Model):
    '''A message that belongs to a chat room'''
    room = models.ForeignKey(Room)
    type = models.CharField(max_length=1, choices=MESSAGE_TYPE_CHOICES)
    #sender = models.CharField(max_length=50, blank=True)
    author = models.ForeignKey(User, related_name='author', blank=True, null=True)
    message = models.CharField(max_length=255, blank=True, null=True)
    timestamp = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        '''Each message type has a special representation, return that representation.
        This will also be translator AKA i18l friendly.'''
        if self.type == 's':
            return u'SYSTEM: %s' % self.message
        if self.type == 'n':
            return u'NOTIFICATION: %s' % self.message
        elif self.type == 'j':
            return 'JOIN: %s' % self.author
        elif self.type == 'l':
            return 'LEAVE: %s' % self.author
        elif self.type == 'a':
            return 'ACTION: %s > %s' % (self.author, self.message)
        return self.message

Again it should be somewhat self explanatory ;). If in doubt, just ask.

Views

Phase one complete, we have our pretty nifty models done, now we need to take care of some basic views (and their respective urls) to interact with the chat app. The view itseft does nothing special, it recieves messages sent by users, or returns the list of messages after a certain message, recieves log in and logout events, among other things. I did develop a quick test view (marked in dark orange), and we’ll use it to explain how to create a chat room for your site. But first, the views:

# -*- encoding: UTF-8 -*-
'''
Chat application views, some are tests... some are not
@author: Federico Cáceres <fede.caceres@gmail.com>
'''
from datetime import datetime

from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
from django.contrib.auth.models import User 

from chat.models import Room, Message

@login_required
def send(request):
    '''
    Expects the following POST parameters:
    chat_room_id
    message
    '''
    p = request.POST
    r = Room.objects.get(id=int(p['chat_room_id']))
    r.say(request.user, p['message'])
    return HttpResponse('')

@login_required
def sync(request):
    '''Return last message id

    EXPECTS the following POST parameters:
    id
    '''
    if request.method != 'POST':
        raise Http404
    post = request.POST

    if not post.get('id', None):
        raise Http404

    r = Room.objects.get(id=post['id'])

    lmid = r.last_message_id()    

    return HttpResponse(jsonify({'last_message_id':lmid}))

@login_required
def receive(request):
    '''
    Returned serialized data

    EXPECTS the following POST parameters:
    id
    offset

    This could be useful:
    @see: http://www.djangosnippets.org/snippets/622/
    '''
    if request.method != 'POST':
        raise Http404
    post = request.POST

    if not post.get('id', None) or not post.get('offset', None):
        raise Http404

    try:
        room_id = int(post['id'])
    except:
        raise Http404

    try:
        offset = int(post['offset'])
    except:
        offset = 0

    r = Room.objects.get(id=room_id)

    m = r.messages(offset)

    return HttpResponse(jsonify(m, ['id','author','message','type']))

@login_required
def join(request):
    '''
    Expects the following POST parameters:
    chat_room_id
    message
    '''
    p = request.POST
    r = Room.objects.get(id=int(p['chat_room_id']))
    r.join(request.user)
    return HttpResponse('')

@login_required
def leave(request):
    '''
    Expects the following POST parameters:
    chat_room_id
    message
    '''
    p = request.POST
    r = Room.objects.get(id=int(p['chat_room_id']))
    r.leave(request.user)
    return HttpResponse('')

@login_required
def test(request):
    '''Test the chat application'''

    u = User.objects.get(id=1) # always attach to first user id
    r = Room.objects.get_or_create(u)

    return render_to_response('chat/chat.html', {'js': ['/media/js/mg/chat.js'], 'chat_id':r.pk}, context_instance=RequestContext(request))

def jsonify(object, fields=None, to_dict=False):
    '''Funcion utilitaria para convertir un query set a formato JSON'''
    try:
        import json
    except ImportError:
        import django.utils.simplejson as json

    out = []

    if type(object) not in [dict,list,tuple] :
        for i in object:
            tmp = {}
            if fields:
                for field in fields:
                    tmp[field] = unicode(i.__getattribute__(field))
            else:
                for attr, value in i.__dict__.iteritems():
                    tmp[attr] = value
            out.append(tmp)
    else:
        out = object

    if to_dict:
        return out
    else:
        return json.dumps(out)

(sorry for the cheap template of the blog 😦 )

In any case, the code is quite documented, but I can brief you on the inner workings of each view:

  • send: this view recieves the messages sent by users and associates them to the corresponding Room model
  • sync: called when the interface loads, basically it requests the id of the last message sent to a chat room, this is used so that the user when joining a chat room will only request messages sent AFTER his arrival
  • receive: called by the client’s javacript code, returns a list of messages sent. It uses a variable sent though POST called “offset”, basically it is the id of the last message received by the client (or the id received by the sync view) so that the view only returns messages after it
  • join: called when a user joins a chat room
  • leave: called when a user leaves a chat room
  • test: silly test view, basically allows you to test the app by itself without having to associate it with other apps (more on this on the next parragaph)

In a nutshell this test view gets the Room instance that is related to the user whose id = 1 (in my case this is always the admin user, or the first super user created by the django.contrib.auth app, as it is the first user created when running the syncdb manage script for the first time on a project with the django.contrib.auth app installed.) I am doing this because in my case I know that in the worst scenario, at least I will have a user model to which I can bind this chat room to, so that I can save it and send messages to it…

Based on that test view you can learn how to bind a chat Room to anything you’d like. For example: Imagine that you have a complex site, where you have different user groups. you can enter the page of any particular user group and you would like to display a chat room that is exclusive for that group whose page you are joing. So, if you have a UserGroup model, you could write a view like this to get or create the Room instace associated to your UserGroup model:

from django.shortcuts import render_to_response
from yourapp.chat.models import Room
from models import UserGroup

def usergroup_index(request, group_id):
    group = UserGroup.models.get(id=group_id)
    room = Room.objects.get_or_create(group)
    return render_to_response("your_template.html", {'group':group, 'chat_id':room.id})

Just a quick and dirty example, but basically what that get_or_create manager function does is try to get the Room instance that is associated with the object instance you are passing it as a parameter. It is quite magical thanks again to the GenericType class. This allows you, like I mentioned before, to “attach” or “bind” a chat room to any kind of model.

In any case, it is imperative that your render_to_response call (or whichever method you use) sends the room id to the template (I’ll show you how I use it in the next section) or else nothing will happen!

To wrap this up, this is the urls.py file:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    url(r'^request/$', 'forum.views.forum'),

    url(r'^$', 'chat.views.test'),
    url(r'^send/$', 'chat.views.send'),
    url(r'^receive/$', 'chat.views.receive'),
    url(r'^sync/$', 'chat.views.sync'),

    url(r'^join/$', 'chat.views.join'),
    url(r'^leave/$', 'chat.views.leave'),
)

With that done, let’s see how a basic template is set up, and the final piece of the puzzle, the Javascript… in front of it all :D.

Template

From now on, you can just rely on the basic copy-paste method, for you should have no need to touch the html (unless you want to modify the looks of it, in which case you should be modifying the css rules). Fortunately this is very easy, just paste the following somewhere in your code:

<div id="chat"></div>

<script type="text/javascript">
$(window).ready(function(){
	init_chat({{ chat_id }}, "chat");
})
</script>

What we see here is a blank <div> block with the chat id. The chat javscript files will place all html elements here for you, so you don’t need to worry. Then we have a short call to a javascript function, using jQuery’s event binding we tell it to “call init_chat” once the window is done loading (html is ready). That “chat_id” variable in the template is the id of the room that your view got from looking up (or creating) a chat Room, and it is necessary so that the javascript managing your chatroom initializes correctly and knows which is the Room it should query.

Of course, you should also include the jquery.js file, you can download it from jquery.com or directly link at a version left available online by the nice people of Google:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>

You shuld place THAT and the next javascript file in your html so that everything works.

Javascript (jQuery)

This is the script done in Javascript using the jQuery Library… documentation to come soon!

var chat_room_id = undefined;
var last_received = 0;

/**
 * Initialize chat:
 * - Set the room id
 * - Generate the html elements (chat box, forms & inputs, etc)
 * - Sync with server
 * @param chat_room_id the id of the chatroom
 * @param html_el_id the id of the html element where the chat html should be placed
 * @return
 */
function init_chat(chat_id, html_el_id) {
	chat_room_id = chat_id;
	layout_and_bind(html_el_id);
	sync_messages();
}

/**
 * Asks the server which was the last message sent to the room, and stores it's id.
 * This is used so that when joining the user does not request the full list of
 * messages, just the ones sent after he logged in.
 * @return
 */
function sync_messages() {
    $.ajax({
        type: 'POST',
        data: {id:window.chat_room_id},
        url:'/chat/sync/',
		dataType: 'json',
		success: function (json) {
        	last_received = json.last_message_id;
		}
    });

	setTimeout("get_messages()", 2000);
}

/**
 * Generate the Chat box's HTML and bind the ajax events
 * @param target_div_id the id of the html element where the chat will be placed
 */
function layout_and_bind(html_el_id) {
		// layout stuff
		var html = '<div id="chat-messages-container">'+
		'<div id="chat-messages"> </div>'+
		'<div id="chat-last"> </div>'+
		'</div>'+
		'<form id="chat-form">'+
		'<input name="message" type="text" class="message" />'+
		'<input type="submit" value="Say"/>'+
		'</form>';

		$("#"+html_el_id).append(html);

		// event stuff
    	$("#chat-form").submit( function () {
            var $inputs = $(this).children('input');
            var values = {};

            $inputs.each(function(i,el) {
            	values[el.name] = $(el).val();
            });
			values['chat_room_id'] = window.chat_room_id;

        	$.ajax({
                data: values,
                dataType: 'json',
                type: 'post',
                url: '/chat/send/'
            });
            $('#chat-form .message').val('');
            return false;
	});
};

/**
 * Gets the list of messages from the server and appends the messages to the chatbox
 */
function get_messages() {
    $.ajax({
        type: 'POST',
        data: {id:window.chat_room_id, offset: window.last_received},
        url:'/chat/receive/',
		dataType: 'json',
		success: function (json) {
			var scroll = false;

			// first check if we are at the bottom of the div, if we are, we shall scroll once the content is added
			var $containter = $("#chat-messages-container");
			if ($containter.scrollTop() == $containter.attr("scrollHeight") - $containter.height())
				scroll = true;

			// add messages
			$.each(json, function(i,m){
				if (m.type == 's')
					$('#chat-messages').append('<div class="system">' + replace_emoticons(m.message) + '</div>');
				else if (m.type == 'm')
					$('#chat-messages').append('<div class="message"><div class="author">'+m.author+'</div>'+replace_emoticons(m.message) + '</div>');
				else if (m.type == 'j')
					$('#chat-messages').append('<div class="join">'+m.author+' has joined</div>');
				else if (m.type == 'l')
					$('#chat-messages').append('<div class="leave">'+m.author+' has left</div>');

				last_received = m.id;
			})

			// scroll to bottom
			if (scroll)
				$("#chat-messages-container").animate({ scrollTop: $("#chat-messages-container").attr("scrollHeight") }, 500);
		}
    });

    // wait for next
    setTimeout("get_messages()", 2000);
}

/**
 * Tells the chat app that we are joining
 */
function chat_join() {
	$.ajax({
		async: false,
        type: 'POST',
        data: {chat_room_id:window.chat_room_id},
        url:'/chat/join/',
    });
}

/**
 * Tells the chat app that we are leaving
 */
function chat_leave() {
	$.ajax({
		async: false,
        type: 'POST',
        data: {chat_room_id:window.chat_room_id},
        url:'/chat/leave/',
    });
}

// attach join and leave events
$(window).load(function(){chat_join()});
$(window).unload(function(){chat_leave()});

// emoticons
var emoticons = {
	'>:D' : 'emoticon_evilgrin.png',
	':D' : 'emoticon_grin.png',
	'=D' : 'emoticon_happy.png',
	':\\)' : 'emoticon_smile.png',
	':O' : 'emoticon_surprised.png',
	':P' : 'emoticon_tongue.png',
	':\\(' : 'emoticon_unhappy.png',
	':3' : 'emoticon_waii.png',
	';\\)' : 'emoticon_wink.png',
	'\\(ball\\)' : 'sport_soccer.png'
}

/**
 * Regular expression maddness!!!
 * Replace the above strings for their img counterpart
 */
function replace_emoticons(text) {
	$.each(emoticons, function(char, img) {
		re = new RegExp(char,'g');
		// replace the following at will
		text = text.replace(re, '<img src="/media/img/silk/'+img+'" />');
	});
	return text;
}

CSS

This is the default css I used:

/* CHAT */

#chat-messages-container {
	overflow: auto; /* used for scrolling */
	width: 100%;
	height: 100px;
	border: 1px solid #777;
	-moz-border-radius: 5px;
}

#chat-messages {
	padding: 5px;
}

#chat-messages .author {
	font-weight: bold;
	margin-right: 0.5em;
	display: inline;
}

#chat-messages .message {
	margin-bottom: 4px;
}

#chat-messages .system {
	font-weight: bold;
	color: #642;
}

#chat-messages .join {
	color: #696;
	background: url(img/silk/door_in.png) top left no-repeat;
	padding-left: 20px;
	margin: 4px 0;
}

#chat-messages .leave {
	color: #966;
	background: url(img/silk/door_out.png) top left no-repeat;
	padding-left: 20px;
	margin: 4px 0;
}

#chat .message {
	width: 80%;
}
The whole mess together

Phew, that was excruciating! Hopefully you got a grasp on how the pieces fit together! But if everything was done correctly, it will work like a charm!

Let me share with you a little extra for reading all the way to the bottom, a little video of me… chatting with my self!

Hope you really enjoyed this, it was an absolute pleasure to work on this app on the marvellous Django framework. As I mentioned on my previous post, there are other ways to do this, which are much more optimized and appropiate, and we also left behind several security and functional issues besides (sending messages to a Room where you have not joined for instance)… but even though of all that, this still works quite well, and is a nice addition to your site. Sure, it may also kill your database if the number of visitors is high enough :P!

Remember you can checkout the whole code from http://code.google.com/p/django-jchat/ it should work right out of the box on any given django project.

And again, if you have questions, just post a comment below and I’ll try to help you out.

Until next time!

Advertisements

26 thoughts on “Django powered AJAX Chat – Part 2

  1. I don’t think you’re using a comet design model. It looks like you’re doing plain old polling every 2000 milliseconds with the setTimeout function. I think comet involves leaving an http request open for quite some time.

    1. Indeed Mr. sqwishy! Like I mentioned somewhere (I don’t remember in which post) my intention was not to create an optimized chat application, rather than that I was interested in prototyping a fully functional (and attractive) chat application for our project. I would have liked to implement this app with Http Server Push and Long Polling (or Comet like you mentioned), but I didn’t have the time to study those models and the implications of leaving 10s, 100s, 1000s of connections open on the apache server (we deployed this on an Apache HTTP Server). An option to handle this would have been implementing the chat application over the twisted framework (http://twistedmatrix.com/)… now THAT would have been interesting… or communicating with an XMPP server. The possibilities are (kind of) endless, but my time unfortunately wasn’t, and I needed to finish this quick and dirty.

      It is a good starting point I guess though, you only have to change the way that the views handle the requests so that hold the connection until they have new data and voila, you have a comet-like chat application!

    1. Hi Ivan, I’m glad you find my post and code useful :D.

      No, I haven’t really tested this with other apps but I think I have tried my best to make it as pluggable as possible, if you find problems (or if it is a total success) please, post your results back here, I will be able to help you.

      Maybe I’ll even try using django-jchat with Pinax sometime later (and post some instructions on how to set it up if necessary).

  2. Dear Federico

    I have your chat app working as a Pinax app, all reusable and pluggable and so on. I have had to make some minor changes to the code (all of which I have noted in a changelog.txt file). I should like to write all this up for my blog. If you’d like to see my changes first, and/or if you’d like to fold some/all of them into your google code project email me (ivan at llaisdy dot com) and I’ll send you a zip file.

    I’d like to use jchat as a basis for two things: 1. writing a basic “chatroom” app for Pinax; 2. exploring different kinds of chat client/server technology.

    Thanks again for your work!

    Best wishes

    Ivan

    1. How odd Vladimir, I just did a fresh checkout and tried it with Python 2.6 and Django 1.1 and it works just fine. That is, messages only appear once (as it is inteded).

      Would you mind telling me if you did anything special when testing the app, what browser you are using, your OS, etc? Maybe it is a bug with the javascript. THat way maybe I can help you fix your problem, and you can help me make django-jchat better :D.

      Other than that, I have a couple of things I have to update with this proyect, I’ll try to add better integration with Pinax out of the box for instance, and fix a little bug related to code injection.

    1. I haven’t got time to sit and work on the chat app for a while. But I will try to make the neccesary changes to the project so that integrates nicely with pinax. Maybe I’ll even make a separate blogpost for the release.

      1. Hi, Federico. I mean, a few users chatting. Then new user connected. The chat must show to this user all conversation of that few users, who connected earlier. I run this chat in test server, and it don’t show anything, am I done something wrong?

      2. Oh, I get it. Well, by default whenever a user logs in, there’s this bit of Javascript code that asks the server for the id of the message sent last to this room, usually the id of the message sent to the room just before the user logged in. With that id, the javascript code will ask the server for any messages sent after that one. You could easily change that, to either fetching an id corresponding to the id of the 10th message sent before the user logged in, or (for the demise of your server) don’t fetch any id at all and just get the whole chat history.

        In the javascript realm everything begins with the init_chat function which receives the id of the chat room, it then calls sync_messages which in turn gets the id of the message sent last to this chat room. You could alter the view which returns this id to something that makes more sense for you.

  3. My head will blow right now. I’m just need a simple chat on site. I found just 3 chats on django, django-chat, django-jchat, django-jqchat, all of them needs a big work to do it just usable. I tried so many embedded chats, one of them only in english, others shows reclama, or need registration on their sites, and so on. Sometimes I want go back to WP or Joomla :).

    1. I understand that. Needless to say that this chat application is not optimized for real world usage at all (unless you have lot’s of hardware keeping your database up to speed).

      Embedding this chat application into your site might require a lot of effort, or almost none. If you just want one chat room in your site, it’s easy, just copy the js file, copy the django app to your project, and add a couple of lines into your template so that it inits a “global” chatroom. But, you also have the possibility to mix this app quite nicely with others, in particular thanks to the Generic Relationships you can “attach” a chat room to pretty much any object that’s handled by Django’s ORM. This mixing of course requires, as it is to be expected, quite a bit more of involvement in the setup of things…

      In the end, this is just a demo app, a prototype for a “real” chat system that would in fact be powered by a dedicated chat server (a XMPP server for instance) and *that* will require much more work to set up correctly.

      If you don’t end up using this app, I just hope you enjoyed tinkering with it and maybe got an idea or two on how to do (or not to, which is as important as the former) stuff in your project. Oh, and if you have any idea on how to improve things, just drop me a message or ticket with your idea.

      Good luck!

  4. This is a tidy demo app, with good design and good “old-fashioned” setTimeout polling updates. Plays well with Pinax for example.

    For a comet-based (Facebook style) chat app, I would recommend using an erlang based server like MochiWeb [1,2]. This gives you real-time comms, and it scales nicely. With some (but not too much) brain work, it can be integrated with a django site.

    [1] Comet web chat (with MochiWeb)
    [2] A Million-user Comet Application with Mochiweb, Part 1

    Best wishes

    Ivan

    1. Welcome back, llaisdy, and thanks for the tips! In the near future I might be revisiting the site I created this chat app for, and one of the things I want to work on is this real time chat thingy. If everything goes well, I’ll surely try MochiWeb or any of it’s XMPP compliant relatives/alternatives and blog about it here.

  5. Dear Federico,

    I am trying to run ur app, everything goes fine but when i go to any chat room(simple or complex) it gives me empty chat box, when i type msg, it doesnt show up, and as in ur demo video in chat box it shows admin has joined, in my case nothing is happening. pls suggest me if there are any modifications or settings that needs to be done to make it work.
    I am very new to django so struggling hard to fix it.

  6. When I try to login with the super user account, I keep getting this error, ‘CSRF token missing or incorrect.’ I put the {% csrf_token %} template tag inside the form and the necessary middleware classes but the bug won’t go away. What’s up witht that?

  7. How do you run the demo if using a different database such as Postgres (not sqllite3). What other things you need to alter in the folder?

    1. Well, the code for this demo is now quite old, I don’t know if it will run out of the box on newer django versions. But on the official presentation I did of this module it was running on PostgreSQL with no problems.

      You know, I actually first developed this for PostgreSQL, then I separated the chat app from the rest of the project to share it here, so I can say with confidence, that it should work with little trouble.

  8. Hi Federico!

    loved the code!
    i’m a very new django programmer and this was really educating 🙂

    i know this post is very old but i see you replied here 3 month ago so i hope you see this message..

    after running the code and updating some setting to adapt to a newer django version i’ve started the simple chat..
    for some reason after i enter a message i cant see it in the chat box..
    the server receives the Get and Post commands but for some reason it dosent show the message itself after i send it..

    wondered if you might have an idea how to approach the problem?

    thanks again!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s