Django powered AJAX Chat – Part 1


In the neverending adventure of developing our game website we have stumbled upon the “live chat” rock. I’ve seen some interesting things here using a pure django implementation for chat and there using django and comet for chat. I browsed the first one and I didn’t like it too much, and the comet implementation is clearly out of the picture for now. We have to finish this project in 4 to 6 weeks and we cannot take the risk of messing around with yet another technology (although I will keep this django+comet link bookmarked for future use).

So I decided to make one quick and dirty chat app myself. I had a very clear idea of how I wanted to implement the chat, it is really simple after all:

  1. Client loads HTML + Javascript
  2. Client queries the Server every X seconds for new messages
  3. Server responds messages serialized in JSON format
  4. Client attaches new messages to the message box

This does fail regarding performance, so I did a little bit of reading regarding the matter of creating html based chat applications. It turns out there’s this Comet model to allow persistent or long lived HTTP requests. The idea behind that is simple, for out chat application for instance, instead of quering the server every X seconds just make a request and wait… wait until the server prints SOMETHING into the request. This does save us of making many queries but falls short when implemeting over a normal web server, say Apache. This happens because for every open connection Apache assigns either a thread or a child process to the request. If you know a little about OSs or how the Apache HTTP server works, you would have figured out by now that this implementation can effectivelly occupy all of Apache’s threads discarding any other requests by other clients. FAIL!

To save us from that, diverse solutions exists. Long story short: You can use the Twisted Networking Engine along with Apache, having Apache serve the normal HTTP requests while all other requests (such as our nice chat application) requests are handled by Twisted, which is optimized for this scenarios. Sounds great, doesn’t it? Still, we won’t go this way in the making of this project, not now.

Instead, let’s just make a plain simple server-killer 100% Django chat application! We’ll be needing Django and jQuery.

Heed

I have already posted a follow-up for this post here: https://pythonhaven.wordpress.com/2009/07/13/django-powered-ajax-chat-–-part-2/. It covers the final implementation I used, explaining the application in-depth, and also points you to the google code site where you can get the source code wrapped up in a nice example project. The motivation behind this project is written down in the post you are reading now (part 1), but the implementation is covered in the linked post (part 2).

The models

The philosophy that I have for this application is:

  • Extreme low coupling with the rest of the apps
  • Internationalization capable

Bare with me as I struggle to respect those two aspects.

So first of all we create a chat room model. This will represent a virtual space where messages can be sent and where people can read other people’s messages, the model is as follows:

class Room(models.Model):
    '''Representation of a generic chat room'''
    belongs_to_type = models.CharField(max_length=100, blank=True, null=True)
    belongs_to_id = models.IntegerField(blank=True, null=True)
    created = models.DateTimeField()
    comment = models.TextField(blank=True, null=True)
    objects = RoomManager() # custom manager

    class Meta():
        unique_together = (('belongs_to_type', 'belongs_to_id'))

    def say(self, type, sender, message):
        '''Say something in le chat'''
        m = Message(self, type, sender, message)
        m.save()
        return m

    def messages(self, after=None):
        m = Message.objects.filter(room=self)
        if after:
            m = m.filter(pk__gt=after)
        return m

    def __unicode__(self):
        return 'Chat for %s %d' % (self.belongs_to_type, self.belongs_to_id)

As you have notices this model has two strange, very strange, fields: belongs_to_type and belongs_to_id. I took this off django’s admin login feature. Basically when creating a chat room, you can create a weak reference to ANYTHING you want. For instance let’s say you have project screens and you want each project to have a chatroom of it’s own, you just have to create a room with belongs_to_type = “project” and belongs_to_id = <the id of your project here>. This will then behave like a reverse ForeignKey, instead of having the project point to the chat room, the chat room will point to the project, but, the chat room can also point to… documents, profiles, groups, etc. The possibilities are endless.

Besides that I also defined a custom model manager in the line 06 , I added a couple of utility functions for the creation of rooms and the quick retrieval of rooms:

class RoomManager(models.Manager):
    '''Custom model manager for rooms, this is used for "table-level" operations'''
    def create(self, parent_type, parent_id):
        '''Creates a new chat room and registers it to the calling object'''
        # the first none is for the ID
        r = self.model(None, parent_type, parent_id, datetime.now())
        r.save()
        return r

    def get_room(self, parent_type, parent_id):
        '''Get a room through its parent.'''
        return self.get(belongs_to_type=parent_type, belongs_to_id=parent_id)

I also defined a say function to send a message to the chat room, and a messages function to retrieve the messages of the room.

On to the message, this is the model:

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)
    message = models.CharField(max_length=255)
    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 in ['s','m','n']:
            return u'*** %s' % self.message
        elif self.type == 'j':
            return '*** %s has joined...' % self.sender
        elif self.type == 'l':
            return '*** %s has left...' % self.sender
        elif self.type == 'a':
            return '*** %s %s' % (self.sender, self.message)
        return ''

Simple huh? The message points to the room to which it belongs and then contains a variety of vital information. I was specially interested in the type field, I wanted to have several message types with different representations on the client side, these are the first ones that I thought of:

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

So, those are the message types I was interested in having and as you can see the Message object has different representations depending on the message type and they are Internationalization friendly, great!. I am still not sure though on whether the sender field should be a simple CharField or a ForeingKey to django.contrib.auth.models.Users…

Well, that’s all for now, next time we’ll be seeing some jQuery in action to get this thing rolling.

Until next time!

Part 2

Remember, part 2 is here: https://pythonhaven.wordpress.com/2009/07/13/django-powered-ajax-chat-–-part-2/

12 thoughts on “Django powered AJAX Chat – Part 1

    1. Thanks for the link! But I am actually looking for a django app that I can integrate into my website project so that it integrates nicely with the rest of the components that it is made of. Thanks for the link anyways, I always get ideas from other people’s products. I have done many improvements to my django ajax chat implementation today and I’ll soon post more information on how I did it. It is really simple actually, and works great!

  1. Neat! I was looking for something just like this. I’m eager to see what the views and templates look like.

  2. I’ve recently finished a chat client built around the same ideas (Django, jQuery).

    I look forward to seeing the rest of your code. Comparing and contrasting different solutions to the same problem is always interesting – already I see that you create messages from a room instance, which is probably a better approach than the way I did it (using manager methods on the message table).

    The code for my chat client is here: http://code.google.com/p/django-jqchat/

    1. Thanks for reading my post!
      Actually, I did home heavy modifications on the chat app (I am now using the generic content types for instance) and I’m about to publish it on a coming post :D. I’ll be uploading it all to google code too, so that people can learn from it, or use it like they wish!

      I’ll look into your client’s code too, let’s see if I can borrow some inspiration from it.

      1. I was wondering about your use of generic content types (or the variation that you used above).
        The way I see a chat window, it’s an extension (=extra feature) to another object. Hence I decided to represent it as a OneToOne field on the ‘parent object’.
        By coding it as a generic relation, you could potentially set up several chat windows attached to the same parent object – which to me felt wrong, as I couldn’t see any use cases for this.
        I’d be interested in hearing your thoughts on this.

      2. Oh… you see. I wanted to attach this chat room object to any other models in my project, and I started with a cheap home made solution (as you can see in the post) until I found the generic content types. This way I can have… for example a model representing Documents (a la Google Docs) and a model representing Projects, and attach a chat room to the Document or Project model with little or no hassle (and a lot of DRYness).

        And to enforce a single chat room attached to a model instance, you could just use the Meta: unique_together attribute, set the fields involed in the generic FK to be unique, and you’re done.

        What do you think?

  3. Hi Frederico,

    Great articles! We have some interests in common.
    It’s nice to know that there are such professional people as you are, located in Paraguay.

    Keep up the nice work.

    Saludos de la ciudad de Caacupé,
    Kenny

    1. I’m just sharing the knowled my friend!

      By the way, it is GREAT to know that other people here in Paraguay share my interest in Python and related stuff!!

      Saludos desde Asunción 😀

  4. Pingback: Django | Annotary

Leave a comment