Two-Faced Django Part 1: Building a project that exists simultaniously on Facebook and the web
- You can find details about seeing a live version of this project, both web and Facebook interfaces, here.
Recently I have been spending a lot of time with Django and PyFacebook. There is a decent enough tutorial for getting started with PyFacebook, but based on reading some of the comments on the mailing list, it seems like a more comprehensive walkthrough might be appreciated.
This series of articles aims, in the clear step-by-step style of the Django tutorial, to take you through the process of building a Django application that exists as both a simple web application, and also as a Facebook application. The web and Facebook applications will store information in the same database, using the same models, and thus users of one interface will be able to interact with the other.
Also, in the spirit of capitalism and entrepreneurship, we are going to invade the Django tutorial's turf and build a big and better toy polling application. At this moment venture capitalists are trembling across Silicon Valley at the hit to their pocket books due to these polling apps. Or something alone those lines.
Lets take a look at what we're going to accomplish over the next hour or three:
- Building models that we will share between the web app and the Facebook app.
- Using generic views to avoid writing as much code as possible.
- Templates using both HTML and FBML (Facebook Markup Language) for rendering our content.
- Ajax using both Facebook's Javascript, and the JQuery javascript library.
- Using the Django testing framework to bring Test Driven Development to the web.
- Using newforms to validate data, as well as represent it.
A few things this series is unfortunately not going to cover:
- How to adapt command line instructions to a Windows. I don't have a Windows box to play with.
- Setting up mod_python or any other deployment mechanism on your server.
This tutorial aims to cover a lot of ground, but don't worry, it doesn't expect much previous knowledge from you. As long as you have a terminal, Python, and Subversion, we're going to get through this together.
Now, lets get started.
Installing Django
The very first thing we need to do is to install Django. We are using the SVN version of Django, which at the time of this writing is version 6794. So lets grab a copy. First navigate to a place you want the library to live.
Personally mine is in a directory like this '/Users/will/django/'. But its entirely a personal stylistic choice. Choose a directory, and go there and then type:
svn co http://code.djangoproject.com/svn/django/trunk/ django-trunk
This will checkout the most recent version of the Django trunk into a folder named 'django-trunk'.
Now we need to tell Python where Django is. Do that... we need to know where Python is. So lets find out:
python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"
/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages
The location of your library may be a bit different. Thats okay. Now lets teach Python how to find Django.
ln -s `pwd`/django-trunk/django /Library/Frameworks/Python.framework/Versions/2.5/lib/python2.4/site-packages/django
This creates a symbolic link in your site-packages folder, so that Python can operate as if your you can installed your package there in the first place.
Lets confirm that this just worked. Lets fire up Python...
python -v
(The -v flag makes python very verbose about what it is doing, which is a bit painful for actually working with the interpreter, but not so bad now since we're just checking that an import works.) And now lets try to import Django.
>>> import django
import django # directory django
# django/__init__.pyc matches django/__init__.py
import django # precompiled from django/__init__.pyc
If things are not set up correctly, you'll get this instead:
>>> import django
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ImportError: No module named django
If that happens, reread the instructions above and try to figure out where you went wrong, and try again.
Installing PyFacebook
Now we're going to go through the same steps to install PyFacebook.
Checkout PyFacebook from SVN:
svn checkout http://pyfacebook.googlecode.com/svn/trunk/ pyfacebook
Figure out your site-packages folder:
python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()" /Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages
Symbolically link your checked out library to your site-packages folder.
ln -s `pwd`/django-trunk/django /Library/Frameworks/Python.framework/Versions/2.5/lib/python2.4/site-packages/django
Make sure it worked.
python
and then...
import facebook
As long as you don't get an ImportError thrown, then you have correctly configured everything.
Setting up our project folder.
First we need to start a new Django project:
django-admin.py startproject polling
(If django-admin.py isn't in your path, then you'll need to refer to it via a relative or absolute path. It will be at "django-trunk/django/bin/django-admin.py", but you'll have to remember where you decided to checkout django-trunk.)
Now lets go into the project directory
cd polling
And our project is born. Now we're going to create three applications, each of which will be responsible for a separate slice of our project.
Our three apps will be:
- core: will hold our models, and all code that is not specific to either the web app or the Facebook app.
- web: will hold all code and resources specific to our web app.
- fb: will hold all code and resources specific to our Facebook app.
So now lets create those apps.
python manage.py startapp core
python manage.py startapp web
python manage.py startapp fb
PyFacebook has some functionality for setting up special facebook oriented Django apps, but it isn't particularly helpful, so we'll be setting things up ourselves.
Setting up our fb app
First your want to [download this file][middleware_replacement] into the polling/fb directory. It is explained more here, but is basically an unsophisticated set of functions I put together to avoid using the PyFacebook middleware. Save it as helpers.py, so it should be sitting at
polling/fb/helpers.py
Okay. Now we want to open up and edit the views.py file in fb.
emacs polling/fb/helpers.py
(Full disclosure: its actually okay to use something other than emacs. Although, I can't officially condone it.)
And you'll want to throw all of these imports into it:
from django.http import HttpResponse, HttpResponseRedirect
from django.views.generic.simple import direct_to_template
from django.shortcuts import get_object_or_404
from django.utils import simplejson
from polling.fb.helpers import *
from polling.core.models import *
Now save it. And forget about it for the time being.
Including our new apps in the polling/urls.py file
Fortunately including our new apps in the polling/urls.py file is quite painless. First lets open it up...
emacs polling/urls.py
And edit it so that it looks like this:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^facebook/', include('polling.fb.urls')),
(r'^', include('polling.web.urls')),
)
Save that, and we're done playing with urls.py for the time being.
A folder for our templates...
Move into the pollings folder, and now we're going to make a place to store all of our templates we make.
mkdir templates
mkdir templates/fb
mkdir templates/web
Remember that these folders should be at polling/templates, polling/templates/fb, and polling/templates/web. We'll be telling Django where to find these folders in a moment, when we edit the settings.py file.
A brief lesson in tedium: settings.py
Okay, the heading may be overstating the case a bit, its just not my favorite part of setting up a Django project. Fortunately, it won't take very long either. I promise I'll endure as long as you do.
So we have two groups of things to deal with in settings.py, the generic Django setup, and setting up the entries we need for PyFacebook. Setting up PyFacebook is easier, so lets start with that.
You'll need to add these global variables (just paste em in anywhere, is the less snobby explanation) to your polling/settings.py file. *Please note that you'll need to change the first values to real values, supplied to you via the Developer application on Facebook.*
FACEBOOK_API_KEY = '11111111111111111111111111111111111'
FACEBOOK_SECRET_KEY = '11111111111111111111111111111111111'
FACEBOOK_APP_NAME = "application_name"
FACEBOOK_INTERNAL = True
FACEBOOK_CALLBACK_PATH = "/facebook/"
Not too much to explain, but the callback path is set to "/facebook/" because that is where we included our fb app in polling/urls.py.
Now lets get to work at configuring the rest of our settings.py file.
We're going to use SQLite for our database (we're developing, not creating a production site):
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/Users/will/django/polling/poll.db'
You'll need to change the database name to a real place on your system, preferiably in the polling folder we are working in.
ADMIN_MEDIA_PREFIX = '/admin_media/'
We're going to be setting up the devel media server in a bit, and will be using the /media/ prefix for it, so we won't want any competition.
TEMPLATE_DIRS = ('/Users/will/django/polling/templates/')
Like usual, you'll need to change the path to represent where the polling folder is on your filesystem.
And finally, installing our apps.
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'polling.core',
'polling.web',
'polling.fb',
)
Since all our models will actually be living in polling.core, we don't actually have to add the polling.web and polling.fb apps to the list of installed apps, but its a bit more consistent to do so anyway (and if we don't, but later did add some models to one or the other, we might be very confused why they weren't being noticed).
Phew. We're done with messing with settings.py.
Using the Django devel server to serve media
Again, as I've mentioned before, we're working on putting together our development setup, not a production setup. We are going to use the devel server to serve our media, but only because its very convenient: this is not an adequate solution to real world static file serving.
Okay, with that warning out of the way, lets get to it.
First we need to make a few folders (starting from inside the polling folder):
mkdir media
mkdir media/web
mkdir media/fb
And then we need to open up polling/urls.py for just a quick moment, and edit it so it looks like this:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/Users/will/django/polling/media/'}),
(r'^admin/', include('django.contrib.admin.urls')),
(r'^facebook/', include('polling.fb.urls')),
(r'^', include('polling.web.urls')),
)
Like usual, the reference to /Users/will/django/polling/media/ will have to be edited to reflect the location of your polling folder.
And we're done... setting up
If you didn't feel like doing all the changes yourself, [here is a zipped copy of my setup][polling1]. You will have to edit the polling/settings.py abd polling/urls.py to refer to the correct places on your system.
With that little change, we're finally finished setting everything up. Take a break. Drink some tea, and come back when you're ready to continue to part two where we start putting together our models.