Home » Odeon Blogs » Tudor, UI/UX Maestro »

Facebook Photo Upload From Google App Engine

Facebook Photo Upload From Google App Engine

The Facebook Graph API can handle photo uploads. If no album ID has been specified, the photo is uploaded to a new album, which is named after your application. The following implementation is a Kay Framework deployment, but with a few changes can be done in other frameworks too.

The form

  1. <form enctype="multipart/form-data" action="/facebook/photo_upload/" method="POST">
  2. <input type="hidden" name="MAX_FILE_SIZE" value="100000" />
  3. <input type="hidden" name="access_token" value="{{ access_token }}" />
  4. Choose a file to upload: <input name="file" type="file" /><br />
  5. <input type="submit" value="Upload File" />
  6. </form>

Where:

access_token - OAuth2 token retrieved from Facebook. I've used the official Python SDK for Facebook Graph API. You can grab it from here, and the actual library is here..

action="/facebook/photo_upload/" - a url mapped inside my application to process the form post.

The view

This is where the form is processed. It retrieves the logged in Facebook user, then sends a multi-part post request to Facebook's API.

  1. def test_graph_api(request):
  2. import facebook, utils
  3. auth = facebook.get_user_from_cookie(request.cookies, FACEBOOK_APP_ID, FACEBOOK_SECRET)
  4. if not auth:
  5. return NotFound()
  6. access_token = auth['access_token']
  7. out = ''
  8. if request.method == "POST":
  9. out = utils.posturl('https://graph.facebook.com/me/photos', [('access_token', request.form['access_token'])],
  10. [('myfile', 'myimage.jpg', request.files['file'].stream.read())])
  11. return render_to_response('fb/index.html',
  12. {'access_token': access_token,
  13. 'out': out})

After posting to Facebook, it prints the response from Facebook which should contain the new photo's ID and should look like the code snippet bellow. Note: this is a string, not a dictionary, so you need to parse it.

  1. {"id":414686348004}

On line 9 utils.posturl() is a helper defined in the next section.

The helpers

This is the fun part, because here I post a multi-part form to Facebook.

  1. def posturl(url, fields, files):
  2. import urlparse
  3. urlparts = urlparse.urlsplit(url)
  4. return post_multipart(urlparts[1], urlparts[2], fields,files)
  1. def post_multipart(host, selector, fields, files):
  2. """
  3. Post fields and files to an http host as multipart/form-data.
  4. fields is a sequence of (name, value) elements for regular form fields.
  5. files is a sequence of (name, filename, value) elements for data to be uploaded as files
  6. Return the server's response page.
  7. """
  8. import httplib
  9. content_type, body = encode_multipart_formdata(fields, files)
  10. h = httplib.HTTPS(host)
  11. h.putrequest('POST', selector)
  12. h.putheader('content-type', content_type)
  13. h.putheader('content-length', str(len(body)))
  14. h.endheaders()
  15. h.send(body)
  16. errcode, errmsg, headers = h.getreply()
  17. return h.file.read()
  1. def encode_multipart_formdata(fields, files):
  2. """
  3. fields is a sequence of (name, value) elements for regular form fields.
  4. files is a sequence of (name, filename, value) elements for data to be uploaded as files
  5. Return (content_type, body) ready for httplib.HTTP instance
  6. """
  7. BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_'
  8. CRLF = '\r\n'
  9. L = []
  10. for (key, value) in fields:
  11. L.append('--' + BOUNDARY)
  12. L.append('Content-Disposition: form-data; name="%s"' % key)
  13. L.append('')
  14. L.append(value)
  15. for (key, filename, value) in files:
  16. L.append('--' + BOUNDARY)
  17. L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
  18. L.append('Content-Type: %s' % get_content_type(filename))
  19. L.append('')
  20. L.append(value)
  21. L.append('--' + BOUNDARY + '--')
  22. L.append('')
  23. listy = []
  24. for element in L:
  25. try:
  26. listy.append(element.decode('string_escape'))
  27. except:
  28. listy.append(element)
  29. body = CRLF.join(listy)
  30. content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
  31. return content_type, body
  1. def get_content_type(filename):
  2. import mimetypes
  3. return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

This code snipped is heavily inspired by: http://code.activestate.com/recipes/146306-http-client-to-post-using-multipartform-data/, with one major change to handle binary data. From line 46 to line 52, binary data is converted in a plain string. This method is not perfect.

Who finds a better solution gets a cupcake. Leave a comment below to claim your cupcake.

Photo source: http://www.flickr.com/photos/emdot/95460346/


Categories: GAE facebook

Discussion

  1. vipul chauhan on Jun 02, 2011 - 7:33 said:

    hello how are you?


  2. Javier on Jul 25, 2011 - 6:42 said:

    Hi Tudor!

    First of all, congrats for the whole site. There are loads of useful information in it.

    I'm trying to develop a Facebook application with GAE which needs to upload photos. That's how I landed here! but I have a doubt about the code above, in line 7 of encode_multipart_formdata which reads:

    BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_

    Where should i place the end quote for that? Is that a comment or the variable content?

    I dont even know if this is a newbie question!

    All the best & Thanks in advance,

    Javier


  3. End of the line. The snippet is fixed now.


  4. Javier on Jul 25, 2011 - 8:19 said:

    Thanks very much ! :-)


  5. Javier on Jul 27, 2011 - 13:15 said:

    Hi again!

    I have implemented your code in my example app, and it works great, but a significant percentage of images get corrupted to the facebook album (something like a 20-25%). The images that fail always fail; those that work, always work. However, uploading those images that fail using Facebook itself works perfectly.

    I have collected three images that give us that problem. Is there any chance you could try uploading them yourselves? I assume I could be doing something wrong imlementing your code, but the 80% of successful uploads puzzles me completely.

    These are the "offending" images:

    http://cjaronu.files.wordpress.com/2009/08/bush-the-joker002-copy5b15d.jpg
    http://data0001.at.ua/bikini.jpg
    http://data0001.at.ua/oooo.jpg

    Any help would be very welcome.
    All the best,

    Javier


  6. thanhbin on Nov 11, 2011 - 20:18 said:

    I met a very strange error :
    'Morsel' object has no attribute 'strip'
    You can help me?




Leave a Comment :

(required)


(required)




(required)




(required)






Leave a Comment


Page generated in: 0.23s