Using PyFacebook without the Facebook middleware

Recently I have been experimenting with using PyFacebook to create a Facebook app. In the early stages of development I ran into a bit of a quandry: how to you use PyFacebook with Django, without using the PyFacebook middleware?

This is not an inrrelevant question, because the PyFacebook middleware is just bloat for any views being loaded that do not directly communicate with Facebook, and is even inappropriate for some views that do communicate directly with Facebook (requests made with the FBJSM Ajax object).

So, without further ado, lets look at how we can use PyFacebook with Django, without using the middleware.

You'll need to import the PyFacebook library into your views.py file to use the examples below. If you install the PyFacebook library into your Python path, it'll look like this:

import facebook.djangofb as facebook

Adding Facebook instances to requests

The first thing we want to do is figure out how to add Facebook instances to requests passed to our views.

def add_fb_instance(request):
    # if already has a facebook instance, immediately return
    if getattr(request, 'facebook', None) is not None:
        return request
    # auth_token is other important possible param
    api_key = settings.FACEBOOK_API_KEY
    secret_key = settings.FACEBOOK_SECRET_KEY
    app_name = getattr(settings, 'FACEBOOK_APP_NAME', None)
    callback_path = getattr(settings, 'FACEBOOK_CALLBACK_PATH', None)
    internal = getattr(settings, 'FACEBOOK_INTERNAL', True)
    request.facebook = facebook.Facebook(
        api_key=api_key,
        secret_key=secret_key,
        app_name=app_name,
        callback_path=callback_path,
        internal=internal
        )

The usage is fairly simple:

def canvas(request):
    add_fb_instance(request)
    fb = request.facebook
    # do whatever
    return direct_to_template(request, 'fb/canvas.fbml',
                              extra_context={'request':request})

The add_fb_instance adds a Facebook instance to the request at request.facebook. Since that is the only thing the PyFacebook middleware does, we've already recreated the majority of its functionality. Still we probably want a little more, like forcing users to log in before accessing a view.

Requiring login

Requiring that users log in to our app is also straightforward.

def require_fb_login(request, next=None):
    if getattr(request, 'facebook', None) is None:
        add_fb_instance(request)
    fb = request.facebook
    if not fb.check_session(request):
        return fb.redirect(fb.get_login_url(next=next))

Using the require_fb_login function works like this:

def canvas(request):
    redirect = require_fb_login(request)
    if redirect is not None: return redirect
    user = get_fb_user(request.facebook)
    return direct_to_template(request, 'fb/canvas.fbml',
                              extra_context={'request':request, 'fbuser': user})

This will redirect the user to the app login page if they have not already logged in. Notice that it will add a Facebook instance to our request if one doesn't already exist. Thus explcitily calling add_fb_instance(request) isn't necessary (but its probably advisable, since it makes it much more explicit that a Facebook instance has been added to the request).

Using the Facebook instance to do something

Okay, so now we have Facebook instances attached to our requests... but what can they do for us? Well, lets look into that. We'll make a model that stores information about our logged in user, and also use the Facebook instance to populate some data in our model.

Lets make our model:

class User(models.Model):
    facebook_id = models.IntegerField()
    name = models.CharField(blank=True, null=True, max_length=60)

Okay, a simple model. Now, lets make a function that takes a Facebook instance and retrieves or creates the User model for it, and populates the name field with data from Facebook.

from django.shortcuts import get_object_or_404

def get_fb_user(facebook):
    user, created = User.objects.get_or_create(facebook_id=int(facebook.uid))
    # if the object is newly created
    if created is True:
        # get first and last name using FQL
        query = "SELECT uid, first_name, last_name FROM user WHERE uid=%s" % facebook.uid
        # FQL results a list of dicts, retrieve the first (and only) one
        results = facebook.fql.query(query)[0]
        user.name = "%s %s" % (results[u'first_name'], results[u'last_name'])
        user.save()
    return user

Here we are using Facebook Query Language to retrieve information. The nice thing about FQL is that you can do very complex queries, and also that this one example will be enough for you to figure out retrieving other data via PyFacebook: just write a new FQL query and call it in the same way.

Calling facebook.fql.query(your_query) returns a list of dictionaries. Since we are calling a query that can only return one instance (a user's user id), we are just grabbing the first result, but in other queries you would want to be more discriminating (most likely iterating over the results).

Now, lets look at using the get_fb_user function:

def my_view(request):
    add_fb_instance(request)
    user = get_fb_user(request.facebook)
    return direct_to_template(request, 'fb/canvas.fbml',
                              extra_context={'request':request, 'fbuser': user})

Notice that we need to call the add_fb_instance on request before we can use the request's facebook instance to get our user (it doesn't exist before we call the add_fb_instance).

Tying it all together.

Okay, now for a slightly more complex example that uses all the things we have written, along with a bit of other stuff sprinkled in.

from django.shortcuts import get_object_or_404

def club(request, id):
    add_fb_instance(request)
    redirect = require_fb_login(request)
    if redirect is not None: return redirect
    user = get_fb_user(request.facebook)
    club = get_object_or_404(Club, pk=int(id))
    if user in club.admins.all():
        is_admin = True
    else:
        is_admin = False
    return direct_to_template(request, 'fb/club.fbml',
                              extra_context={'fbuser':user,
                                             'club':club,
                                             'is_admin':is_admin,
                                             })

So, the view for this view is passing us an 'id' as well. So the urls.py looks something like this:

from django.conf.urls.defaults import *

urlpatterns = patterns('bookface.fb.views',
    (r'^$', 'canvas'),
    (r'^club/(?P<id>\d+)/$', 'club'),
)

Because the regular expression in the URL only allows digits, we can call the int function directly on the id (but its still not a great idea to ignore error handling).

So we add a Facebook instance to the request, then verify that the user is logged into Facebook. If they are not logged in, we redirect them to the login page for our app. Then we retrieve the User instance associated with the logged-in Facebook user. Then we use the id we're being passed to retrieve or create a club.

Then we check if the user is one of the club's admins. We pass a bunch of extra context to the template renderer (the user, club, and if the user is an admin in the club).

And thats all there is to it.

Let me know if you have any questions.

All Rights Reserved, Will Larson 2007 - 2014.