Saturday, September 13, 2008

Using Facebook API in Python

Welcome to the tutorial on how to integrate Google AppEngine and Facebook Applications.

For this tutorial I am going to modify the guestbook application provided with Google AppEngine. Download the Python Wrapper for Facebook here and place it in the same folder where guestbook.py resides.

This tutorial assumes that you have basic knowledge of how to create an application using Google AppEngine. If not, please read the Getting Started Guide and continue this tutorial after you have acquainted with AppEngine.

Since we are not going to use the Google user management, change the DB Model:

class Greeting(db.Model):
author = db.StringProperty() # change the author to a string
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

I created my Facebook Application in FBML mode (if you are not sure what this is, don't worry about it now). This means that all the requests are post requests. Therefore, change all the Get request handlers to Post handlers.


class MainPage(webapp.RequestHandler):
def post(self):
# Both these keys are provided to you when you create a Facebook Application.
api_key = '<your_api_key>'
secret_key = '<your_secret_key>'

# Initialize the Facebook Object.
self.facebookapi = facebook.Facebook(api_key, secret_key)

# Checks to make sure that the user is logged into Facebook.
if self.facebookapi.check_session(self.request):
pass
else:
# If not redirect them to your application add page.
url = self.facebookapi.get_add_url()
self.response.out.write('<fb:redirect url="' + url + '" />')
return

# Checks to make sure the user has added your application.
if self.facebookapi.added:
pass
else:
# If not redirect them to your application add page.
url = self.facebookapi.get_add_url()
self.response.out.write('<fb:redirect url="' + url + '" />')
return

# Get the information about the user.
user = self.facebookapi.users.getInfo( [self.facebookapi.uid], ['uid', 'name', 'birthday', 'relationship_status'])[0]

# Display a welcome message to the user along with all the greetings.
self.response.out.write("<html> <body>")
self.response.out.write('Hello %s,<br>' % user['name'])
self.response.out.write('Welcome to guestbook in facebook.<br>')
self.response.out.write('See all the entries in your guestbook below.<br><br>')

greetings = db.GqlQuery("SELECT * "
"FROM Greeting "
"ORDER BY date DESC LIMIT 10")

for greeting in greetings:
if greeting.author:
# change the greeting.author.nickname() to just greeting.author
# since we change the author from a user property to a string property
self.response.out.write('<b>%s</b> wrote:' % greeting.author)
else:
self.response.out.write('An anonymous person wrote:')
self.response.out.write('<blockquote>%s</blockquote>' %
cgi.escape(greeting.content))

self.response.out.write("""
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")



Now your main page is ready. Now to handle the sign action. In the sign action, we have to do the basic Facebook checks instead of the google user check and then just insert the greeting into the table.

class Guestbook(webapp.RequestHandler):
def post(self):
# Both these keys are provided to you when you create a Facebook Application.
api_key = '<your_api_key>'
secret_key = '<your_secret_key>'

# Initialize the Facebook Object.
self.facebookapi = facebook.Facebook(api_key, secret_key)

# Checks to make sure that the user is logged into Facebook.
if self.facebookapi.check_session(self.request):
pass

else:
# If not redirect them to your application add page.
url = self.facebookapi.get_add_url()
self.response.out.write('<fb:redirect url="' + url + '" />')
return

# Checks to make sure the user has added your application.
if self.facebookapi.added:
pass
else:
# If not redirect them to your application add page.
url = self.facebookapi.get_add_url()
self.response.out.write('<fb:redirect url="' + url + '" />')
return

# Get the information about the user.
user = self.facebookapi.users.getInfo( [self.facebookapi.uid], ['uid', 'name', 'birthday', 'relationship_status'])[0]

greeting = Greeting()
greeting.author = user['name']
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')

Now our coding is done for this example. This is a simple example which can easily be expanded to handle complex situations.

Next: Setup Google AppEngine

If you have any questions or comments, please feel free to email at support@sparklextreme.com.

17 comments:

Neo42 said...

To the author,

I suggest rereading this page assuming you know nothing about anything. As a reader I was confused by the part where it says "the same place where guestbook.py" exists.

I was also confused as to what the names of the two files you're creating are.

Unknown said...

nice tutorial

Johan Hernandez said...

very helpful my friend. keep writting.

sidonath said...

Thank you for the useful and concise tutorial. It got me started with my app pretty fast.

Few things worth noting though:

* The guestbook5_datastore.py file should be edited

* Once you download PyFacebook's __init__.py you should rename it to facebook.py and add "import facebook" at the top of the aforementioned file

* You should not use body tag with FBML. Simply remove all <body> references

* <form action="/sign" method="post"> -- there should be no slash in front of /sign because that would overwrite your app name.

SustyApps said...

I wish the sample code actually incorporated the comments that sidonath provided, because I didn't see them, and wasted about 5 hours! Thanks sidonath for the notes, esp on the "/sign" and the "body" tag.

jp said...

Hi

Nice sample, but I can't make it work.

The only code the browser gets is
[fb:redirect url="http://www.facebook.com/install.php?api_key=xxxx&v=1.0" /]

I'm missing something? why the browser is not redirected to that page?

Unknown said...

I wish your code had indentation.

Unknown said...

I had some problems with the /sign page. It turned out I needed to append a / to the end of the canvas url in the Facebook application settings.

Chris said...

Very useful, but lacking just the right number of things to be useful. I assumed that guestbook.py is where guestbook should go (needs about 4 imports not shown) and then I changed my app.yaml to include a url header redirecting requests for /sign to guestbook.py. making several corrections from the comments (all of which were necessary, btw) I got my app running, but signing the guestbook generates the dreaded 404 error. I'll post my solution here when I find one. Any ideas in the mean time would be great!

Wolke said...

really useful,
I also want to study how to connect facebook and GAE,so thx.

Andris said...

THANK YOU!!!!!!!!!!!!!!!!!!!!!!!!! My head almost exploded on the issue of getting Facebook and GAE working together, your code got me back on track!

Chris said...

So, I found an amazing way to get past ALL of the nonsense of facebook... USE AN I-FRAME INSTEAD OF FBML!!! Seriously, I switched over, it's 100% easier. I've yet to find a disadvantage.

Unknown said...

Chris:
If you found a way of getting pyFacebook and iframe working, then please share it.
As far as I can see it either does not work at all or it give the auth-token-loop.

So please share :)

Chris said...

Lynge,

So my previous way of working in an iframe was flawed, but I think I got a workable system. Basically it work like this:

1. Set canvas url to a page like myapp.appengine.com/fb

2. Use this to call a python code similar to the one in the above blog (nearly idential) to get, for example, the username

3. respond with your iframe tag:

url="http://myapp.appspot.com/?user="+username

self.response.out.write('<fb:iframe src=\"'+url+'\">')

4. Design you app (FBML free!) to utilize the data passed in through the header:

user=self.request.get("user")

If you (or anyone else) is interested in this I'll write it up as a blog. Just email me (you should be able to get my addy from clicking on my name. I'm not going to type it here). This might be a flawed system but I'm going to stick with it because it will allow me to run a facebook and a www version of my app with minimal effort.

Chris said...

Update on my last comment. I wrote up my way to work in an iframe:

http://pretime.blogspot.com

Feedback would be awesome.

bowmanb said...

awesome, thanks

BiGGy said...

Very good article indeed and many thanks to @sidonath