Friday, November 30, 2012

Google App Engine Facebook Application Example Using Python

This is a detailed example built on Facebook Python SDK example. In this example I'll take it step by step from the very beginning to login, get user data, get friends list, invite friends, and post to wall. So let's start. (also check the Ruby on Rails with Koala example)

- Download "facebook.py" from this link on GitHub.

- Add the downloaded file to your Google App Engine project.

- You can now make you own Facebook application code or re-use the current example (which I choose to do).

- Take these global variables and imports into your application (your GAE python file)
FACEBOOK_APP_ID = "123456789" #your own FB app id here
FACEBOOK_APP_SECRET = "756483568435475" #your own FB app secret here
INVITATION_TEXT = "I invite you to try my app. It is amazing!"

import facebook
import os.path
import wsgiref.handlers
import logging
import urllib2
import hashlib

from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template
from google.appengine.api.urlfetch import fetch

import webapp2

- Add this Model and Base Class that will be used to determine the user ID from cookies
class User(db.Model):
    id = db.StringProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)
    name = db.StringProperty(required=True)
    profile_url = db.StringProperty(required=True)
    access_token = db.StringProperty(required=True)

class BaseHandler(webapp.RequestHandler):
    """Provides access to the active Facebook user in self.current_user

    The property is lazy-loaded on first access, using the cookie saved
    by the Facebook JavaScript SDK to determine the user ID of the active
    user. See http://developers.facebook.com/docs/authentication/ for
    more information.
    """
    @property
    def current_user(self):
        if not hasattr(self, "_current_user"):
            self._current_user = None
            cookie = facebook.get_user_from_cookie(
                self.request.cookies, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET)
            if cookie:
                # Store a local instance of the user data so we don't need
                # a round-trip to Facebook on every request
                user = User.get_by_key_name(cookie["uid"])
                if not user:
                    graph = facebook.GraphAPI(cookie["access_token"])
                    profile = graph.get_object("me")
                    user = User(key_name=str(profile["id"]),
                                id=str(profile["id"]),
                                name=profile["name"],
                                profile_url=profile["link"],
                                access_token=cookie["access_token"])
                    user.put()
                elif user.access_token != cookie["access_token"]:
                    user.access_token = cookie["access_token"]
                    user.put()
                self._current_user = user
        return self._current_user

- Now let's create our handler. Note that in case you wan to use your application inside Facebook frame, you will have to handle POST request too.

- Logging In and Inviting Friends

class HomeHandler(BaseHandler):
    def get(self):
        self.show_main()

    def post(self):
        self.show_main()
        
    def show_main(self):
        path = os.path.join(os.path.dirname(__file__), "templates/main.html")
        args = dict(current_user=self.current_user,
                    facebook_app_id=FACEBOOK_APP_ID,
                    invitation_text=INVITATION_TEXT)
        self.response.out.write(template.render(path, args))

- Now let's create the template for the HTML template. Here is how it goes: If the passed "current_user" parameter is valid, it will show the content of your app and a link to a JQuery function to invite friends using Facebook Javascript SDK initialized at the bottom. Otherwise, it will show a Facebook login button.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>Application Title</title>
    
    <script type="text/javascript" src="/js/jquery-1.8.2.min.js"></script>
    <script type="text/javascript">
        $('#sendRequest').click(function() {
          FB.ui(
            {
              method  : 'apprequests',
              message : $(this).attr('data-message')
            },
            function (response) {
              // If response is null the user canceled the dialog
              if (response != null) {
                //logResponse(response);
              }
            }
          );
        });
    });
    </script>
  </head>
  <body>
  
    {% if not current_user %}
    <div style="margin:0 auto; text-align:center;">
        <fb:login-button autologoutlink="false" scope="publish_stream"></fb:login-button>
    </div> 
    
    {% else %}
    
    <br>
    Welcome! Here is my application.
    <br>
    <a href="#" id="sendRequest" data-message="{{invitation_text}}">
      Send Requests
    </a>
    
    {% endif %}

    <div id="fb-root"></div>
    <script>
      window.fbAsyncInit = function() {
        FB.init({appId: '{{ facebook_app_id }}', status: true, cookie: true,
                 xfbml: true});
        FB.Event.subscribe('{% if current_user %}auth.logout{% else %}auth.login{% endif %}', function(response) {
          window.location.reload();
        });
      };
      (function() {
        var e = document.createElement('script');
        e.type = 'text/javascript';
        e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js';
        e.async = true;
        document.getElementById('fb-root').appendChild(e);
      }());
    </script>
  </body>
</html>

- At this point, you should be having a working Python Google App Engine Facebook application. Next I'll go though some basic information that you may need to implement in your service. You may prefer XML, JSON, or a custom format. Anyways, here is the basic example that you may customize as you want and add more handling for special cases.

- User Profile

class ProfileHandler(BaseHandler):
    def get(self):
        self.show_profile()

    def post(self):
        self.show_profile()
        
    def show_profile(self):
        profile = ''
        current_user = self.current_user
        if current_user:
            graph = facebook.GraphAPI(current_user.access_token)
            profile = profile = graph.get_object("me")
        path = os.path.join(os.path.dirname(__file__), "templates/profile.html")
        args = dict(current_user=current_user,
                    facebook_app_id=FACEBOOK_APP_ID,
                    profile=profile)
        self.response.out.write(template.render(path, args))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title></title>
  </head>
  <body>
    
    {% if current_user %}
        {% if profile %}
            {{profile}}
        {% endif %}
    {% endif %}

  </body>
</html>

- Friends List

class FriendsHandler(BaseHandler):
    def get(self):
        self.show_friends()

    def post(self):
        self.show_friends()
        
    def show_friends(self):
        friends_list = []
        current_user = self.current_user
        if current_user:
            graph = facebook.GraphAPI(current_user.access_token)
            friends_list = graph.get_connections("me", "friends")['data']
        path = os.path.join(os.path.dirname(__file__), "templates/friends.html")
        args = dict(current_user=current_user,
                    facebook_app_id=FACEBOOK_APP_ID,
                    friends_list=friends_list)
        self.response.out.write(template.render(path, args))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title></title>
  </head>
  <body>
    
    {% if current_user %}
        {% if friends_list %}
            {{friends_list}}
        {% else %}
        {% endif %}
    {% else %}
    {% endif %}

  </body>
</html>

- Post Text to Wall (Arabic-friendly)

class PostTextHandler(BaseHandler):
    def post(self):
        text = self.request.get('text')
        if text:
            text = text.encode('utf-8') # for non-english text  
            current_user = self.current_user
            if current_user:
                graph = facebook.GraphAPI(current_user.access_token)
                graph.put_object("me", "feed", message=text)
                self.response.out.write('yes') # just a feedback reponse
            else:
                self.response.out.write('no') # just a feedback reponse
        else:
            self.response.out.write('no') # just a feedback reponse

- And off course do not forget to add these handlers to the suitable URLs for your service. (side note: the /?$ sign is useful to allow URL with/out the slash)
app = webapp2.WSGIApplication([
                            ('/', HomeHandler),
                            ('/profile/?$', ProfileHandler),
                            ('/friends/?$', FriendsHandler),
                            ('/posttext/?$', PostTextHandler)])

5 comments:

  1. but when i've already login the Facebook icon still shown.

    ReplyDelete
    Replies
    1. Please note that I pass a "current_user" variable to the html template. Then in the template I use:
      {% if not current_user %}

      {% endif %}

      So I'f I'm logged in, there will be a valid "current_user: variable, so the condition will be false and the button should not appear. Please make sure your variable is valid.

      Or you may make a different template or different condition as another solution.

      Delete
  2. how to access email of user,It shows null when i run this code for email.

    ReplyDelete
  3. Thanks! This helped a lot. I had only one problem when adding the #rsendRequest jQuery click function. It had to come at the end of the HTML and could not be part of the head.

    ReplyDelete