Wednesday, October 19, 2011

Comet with Django and Ajax push engine ( APE )

Recently I have implemented APE in my project . Before APE i was using continuous ajax call that is obviously very bad idea... in APE each user has its own pubid , channel , sessId . Channel is used for sending message to another user or vice-versa....

 Here....I just slapped together a really basic message posting app. Just a message with a name and a timestamp.


# Here's the model:
class Message(models.Model):
    msg = models.TextField('Message')
    timestamp = models.DateTimeField('Timestamp', auto_now_add=True)
    posted_by = models.CharField(max_length=50)
# And here's the view:
def show_messages(request):
    if request.method == 'POST':
        new_msg = Message(msg = request.POST['msg'],
                          posted_by = request.POST['posted_by'])
        new_msg.save()
    messages = Message.objects.all()
    return render_to_response('messages.html', {'messages': messages})
And here’s the template:

<style type='text/css'>
    div.message {
        background: #DDDDDD;
        margin: 10px;
        padding: 10px;
    }
</style>
<div id="current_messages">
    {% for message in messages %}
    <div class="message">
        {{ message.msg }}<br />
        Posted by: <strong>{{ message.posted_by }}</strong><br />
        on: {{ message.timestamp }}
    </div>
    {% empty %}
        <div class="message">
            No Messages
        </div>
    {% endfor %}
</div>
<div id="new_message">
    <form id="msgform" method="post">
    <h2>Post a new message</h2>
    Your Name: <input name="posted_by" type="text" /><br />
    Your Message:<br />
    <textarea cols="50" rows="10" name="msg"></textarea><br />
    <input type="submit" value="Submit Message" />
    </form>
</div>

Ok, so that’s the basic setup… Pretty boring, right? If Sue submits a message, Bob has to refresh his page to see it. So, we’re going to add APE into the mix to update Bob right away. Getting APE up running it pretty easy, just follow the instructions over at the APE Wiki. We’ll be using APE’s inlinepush server module. Just for good measure we’ll be tossing some jQuery in as well.
So, the basic idea is we need to connect to a APE “channel” which we’ll use to receive updated messages. We’ll use jQuery to send the messages. So let’s start by using jQuery to submit the form, with the following script:

function append_message(data) {
    fields = data[0].fields;
    message_str = fields.msg + '\nPosted by: <strong>'
                         + fields.posted_by + '</strong> on: '
                         + fields.timestamp;
    new_div = $('<div />').addClass('message').html(message_str);
    $('div#current_messages').append(new_div);
}
$('#msgform').submit(function() {
    $.post('/ajaxsubmit',
            {posted_by: $("input[name='posted_by']").val(),
               msg: $("textarea[name='msg']").val()},
            append_message, 'json');
     //For brevity, we're just going to assume this always works
     $("textarea[name='msg']").val('');
     return false;
});

And here are the updated views:

def show_messages(request):
    messages = Message.objects.all()
    return render_to_response('messages.html', {'messages': messages})
def ajaxsubmit(request):
    new_msg = Message(msg = request.POST['msg'],
                      posted_by = request.POST['posted_by'])
    new_msg.save()
    jsonified_msg = serializers.serialize("json", [new_msg])
    # Again, we're just going to assume this always works
    return HttpResponse(jsonified_msg, mimetype='application/javascript')

And so with that, we can now submit messages without reloading the page. Any other users, however, would need to refresh to see the new messages. That’s where APE comes in. Here’s the APE client code (along with the updated append_message function):

var client = new APE.Client();
client.load();
client.addEvent('load', function() {
    posted_by = prompt('Your name?');
    client.core.start({"name": posted_by});
    $("input[name='posted_by']").val(posted_by);
});
client.addEvent('ready', function() {
    //Once APE is ready, join the messages channel and wait for new messages
    client.core.join('messages');
    client.onRaw('postmsg', function(raw, pipe) {
        append_message(raw.data);
    });
});
function append_message(data) {
    message_str = data.msg + '\nPosted by: <strong>'
                         + data.posted_by + '</strong> on: '
                         + data.timestamp;
    new_div = $('<div>').addClass('message').html(message_str);
    $('div#current_messages').append(new_div);
}

And here’s the updated view to send new posts to APE:

def ajaxsubmit(request):
    new_msg = Message(msg = request.POST['msg'],
                      posted_by = request.POST['posted_by'])
    new_msg.save()
    # Again, we're just going to assume this always works
    cmd = [{'cmd': 'inlinepush',
            'params': {
                'password': settings.APE_PASSWORD,
                'raw': 'postmsg',
                'channel': 'messages',
                'data': {
                    'msg': new_msg.msg,
                    'posted_by': new_msg.posted_by,
                    'timestamp': new_msg.timestamp
                }
            }
    }]
    url = settings.APE_SERVER + urllib2.quote(json.dumps(cmd))
    response = urllib2.urlopen(url)
    # Updating the message is handled by APE, so just return an empty 200
    return HttpResponse()
You’ll notice I’ve added two settings to settings.py:
APE_PASSWORD = 'testpasswd'
APE_SERVER = 'http://ape-test.local:6969/?'

And that’s all there is to it! Like I said earlier, this is a very incomplete application, and a lot of the “boilerplate” work is left as an exercise for the reader. It should be more than enough to get you on your way though. Perhaps in a later post I’ll move the APE update from inside the view to a post_save signal on the model itself. That’s for another time though… UPDATE: Well, another time is today. Check out this post about adding a signal handler.

Reference : http://www.alittletothewright.com/index.php/2010/01/comet-with-django-and-ape/

If any question or error then feel free to comment here........before that i would like to write some problems/guidelines/alternative solution  that i had used in my project.....

1)  Ready event not working when page refresh :
-> yes.........to get it working you have to use session to store each user .........OR ........without session u can append random no to name when calling client.core.start that will load new user each time .

2)  Implement chat...
-> To implement chat u need to register new channel for each user ...when somebody wants to chat he/she will send message to that channel using pipe.send method and the target user will check for new msg using client.onRaw method.......

1 comment:

  1. How about security issues? What if I want to build a chat system, when logged-in users can write to each other? ie. any two users could have a channel. How would you prevent this agains Cross Site Requests?

    ReplyDelete