Posted in August 2010

Django + Tornado = Chat

Just finished up a quick one day project…  Was curious about Torando and since I’ve been playing with Django wondered about mixing the two…   Of course at one level there isn’t much reason to put django and tornado together since django has a “fine” webserver built in and Torando is all about async.

Thus you need to invent an app that requires async, traditional http chat with long polling is the trick.  So, pulling a demo from the NodeJS land and building a bunch of glue work I ended up with a working Django+Torando = Chat application.  Sorry there is no demo, since it looks and works pretty much like the NodeJS chat application (borrowed their UI and JS).

However if you want to check out the code take a look at this:

http://github.com/koblas/django-on-tornado

The key part that’s interesting is this ability to add asyn requests to a django handler:

from django_tornado.decorator import asynchronous

@asynchronous
def recv(request, handler) :
    response = {}

    if 'since' not in request.GET :
        return ChatResponseError('Must supply since parameter')
    if 'id' not in request.GET :
        return ChatResponseError('Must supply id parameter')

    id = request.GET['id']
    session = Session.get(id)
    if session :
        session.poke()

    since = int(request.GET['since'])

    def on_new_messages(messages) :
        if handler.request.connection.stream.closed():
            return
        handler.finish({ 'messages': messages, 'rss' : channel.size() })

    channel.query(handler.async_callback(on_new_messages), since)

Python wierdness

Just frustrated, why the output is

Broken = 2 and Wierd = 3 at one level make no sense, since after all you would think that if you referenced a member variable it’s context would remain the same…

class Foo :
    BROKEN = 1
    WIERD  = 1

    def __init__(self) :
        print('Before broken=%d weird=%d' %( self.BROKEN, self.WIERD))
        self.BROKEN += 1
        self.__class__.WIERD += 1
        print('after  broken=%d weird=%d' %( self.BROKEN, self.WIERD))

Foo()
Foo()

# Output is :
# Before broken=1 weird=1
# after  broken=2 weird=2
# Before broken=1 weird=2
# after  broken=2 weird=3
Tagged

Customers are your assets

I’ve been wrangling with use registration for years, partly because it’s an interesting time sink.  Partly because it’s annoying when it’s wrong.  Plus, usually when you start building any application in this day and age you need some form of user registration.  The basic systems these days are:

  • Username and Password  (e.g. yahoo, google)
    • Key signup form elements: Username, Email, Password, Captcha
  • Email address and Password (e.g. facebook, linkedin)
    • Key signup form elements: Email, Password, Capthca
  • Pure Facebook Connect (or Google Connect, etc.)
    • Key signup form elements: Big Facebook button
  • Hybrid systems
    • ..topic of discussion…

Fundamentally username+password and email+password are just variants of themselves, really coming down to how you are known on the system (unique handle “johndoe77″ vs. real name “John Doe”).  Both systems typically will send you and email asking you to confirm your email and they always have a Captcha since the spammers are going to bully you into submission if you don’t. It’s really hard to innoviate in this space it’s just getting the implementation right, but I’ve build four or fourty of these in the past.

The new kid on the block is the Facebook Connent, Google Connect, OAuth/OpenID systems.  The happy part is that as sites start implementing these systems we can stop having to type our passwords into site after site after site — do you really make unique passwords?!?.   The challenge is that as a service you still want to have connection with your user.  Things I’ve noticed:

  • Google gives you email address _easily_ (thanks guys)
  • Facebook gives you a handle to email address and prompts for permission (so, so)
  • LinkedIn you’re just a pain in this front, I want a relationship with your user, not just some handle to use your funked up messaging platform.
  • Twitter/Yahoo – data exchange is a pain.

Back to owning our user!  It’s easy to own the user if they type a password, it gets progressively harder when we start using the Open provider marketplace to have the user.

Why should you care? Because users are your biggest asset!  If you don’t realize that you’re not thinking hard enough.  The cost of customer acquisition is $X in this day and age the cost of marketing to a known user is a fraction of $X (say 1/10th).  Which means that if you have a service and spent $100 on Google ad words to find customers the cost to market a new product to them is $10 rather than $100 — plus you don’t have to re-train them hopefully so your support costs are lower.

That brings me to my last world view of customer registration — hybrid registration.  No I don’t have a quick example deployed at the moment (currently in alpha).  The basic premise is that you ask for the basics up front:

  • Username
  • Password
  • Email address

But now instead of the classic “captcha” page you hit them with the Facebook Connect/Google Auth page to confirm their account.  The complexity of getting this working is slightly higher than the Captcha, but the advantage is that in addtion to having about the same barrier as a Captcha from a spam protection standpoint you also now have a secondary form of authentication and potentially access to very useful data about your customer.

  • Friends/Contacts
  • Full Name
  • …etc…

While you’re probably a smart service and don’t hit the customer over the head with “spam your friends” what you can do is save this away for later use, like when “Sally Smith” joins you notice that “Tom Jones” is a friend of hers and you can point this out in “Suggested Friends”.  Sally just thinks that you’re a smart service — maybe goes “how did they know that” — but you’re really  using the information for their own good.

The second benifit is that since you’re still running a user through a classic registration flow, you’ve got an email address to conduct future marketing to.   But not just that you’ve also now got a guaranteed second touch with the customer.  Why’s that important?

  • Your first touch was getting them to signup and confirm their account, because they connected a social network to their account we’ve got a high confidence that it’s a real user.
  • Your second touch is the confirmation email, which the user will now open 30 minutes to a day later.  Where you can remind them of all the cool things they should be doing with your service.
  • Your third touch is the message you send in three to seven days thanking them for signing up for your service and potentially looking at some of their recent behaviors on the site to determine some simple bucketing of their behavior to help send an appropriate message.

If you feel like I jumped from Hybrid authentication to customer contact, you’re right.  But, fundamentally as I’ve played with user authentication and buit a variety of systems and looked at the long term value of that customer.  My mindset has evolved into these tenets:

  • Own your user — don’t give them away
  • Enable them to login with a variety of system — lower the barriers
  • Keep the lifetime value of your user in mind

Hybrid authentication is the way to go!

Tagged , , ,

Zendesk and Django integration

Part of this post is the gratuitous, gosh that was easy to integrate!  Of course part is a small point that I would like developers to think about.

First off here’s the code snippet, which owes it’s history to:

Zendesk Remote Authentication with Django and Unicode Names and Zendesk remote authenticatin with Djnago (the original posting)

def authorize(request):
    if not request.user.is_active :
        return HttpResponseRedirect(reverse('openauth:signin'))

    try:
        timestamp = request.GET['timestamp']
    except KeyError:
        raise Http404

    u = request.user

    data = ''.join((u.profile.name(), u.email, u.username, settings.ZENDESK_TOKEN, timestamp))
    hash = md5(data.encode('UTF-8')).hexdigest()

    url = "%s/access/remote/?%s" % (settings.ZENDESK_URL, urllib.urlencode({
                'name'        : u.profile.name(),
                'email'       : u.email,
                'external_id' : u.username,
                'timestamp'   : timestamp,
                'hash'        : hash,
            }))

    return HttpResponseRedirect(url)

What’s different — or why am I making this post:

  • Use u.get_full_name() rather than the appends with the spaces, in my case I’ve got another object hanging out (profile) which contains the users name.
  • Use the django username as the external_id — I though about using user.id, but since username should be unique and fairly inflexible that’s a good approximation.
  • Use a join rather than a whole bunch of “%s%s%s%s” no formatting needed…

Finally, the big rant the original code used a bunch of formatted prints to build the URL argument.  If you’ve been handed a language with libraries like python and a framework like django, you don’t think about cross site scripting or other breakages (& in the username) which is going to cause problems…. It’s trivial to use urlencode to avoid these problems.