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
- <form enctype="multipart/form-data" action="/facebook/photo_upload/" method="POST">
- <input type="hidden" name="MAX_FILE_SIZE" value="100000" />
- <input type="hidden" name="access_token" value="{{ access_token }}" />
- Choose a file to upload: <input name="file" type="file" /><br />
- <input type="submit" value="Upload File" />
- </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.
- def test_graph_api(request):
- import facebook, utils
- auth = facebook.get_user_from_cookie(request.cookies, FACEBOOK_APP_ID, FACEBOOK_SECRET)
- if not auth:
- return NotFound()
- access_token = auth['access_token']
- out = ''
- if request.method == "POST":
- out = utils.posturl('https://graph.facebook.com/me/photos', [('access_token', request.form['access_token'])],
- [('myfile', 'myimage.jpg', request.files['file'].stream.read())])
- return render_to_response('fb/index.html',
- {'access_token': access_token,
- '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.
- {"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.
- def posturl(url, fields, files):
- import urlparse
- urlparts = urlparse.urlsplit(url)
- return post_multipart(urlparts[1], urlparts[2], fields,files)
- def post_multipart(host, selector, fields, files):
- """
- Post fields and files to an http host as multipart/form-data.
- fields is a sequence of (name, value) elements for regular form fields.
- files is a sequence of (name, filename, value) elements for data to be uploaded as files
- Return the server's response page.
- """
- import httplib
- content_type, body = encode_multipart_formdata(fields, files)
- h = httplib.HTTPS(host)
- h.putrequest('POST', selector)
- h.putheader('content-type', content_type)
- h.putheader('content-length', str(len(body)))
- h.endheaders()
- h.send(body)
- errcode, errmsg, headers = h.getreply()
- return h.file.read()
- def encode_multipart_formdata(fields, files):
- """
- fields is a sequence of (name, value) elements for regular form fields.
- files is a sequence of (name, filename, value) elements for data to be uploaded as files
- Return (content_type, body) ready for httplib.HTTP instance
- """
- BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_'
- CRLF = '\r\n'
- L = []
- for (key, value) in fields:
- L.append('--' + BOUNDARY)
- L.append('Content-Disposition: form-data; name="%s"' % key)
- L.append('')
- L.append(value)
- for (key, filename, value) in files:
- L.append('--' + BOUNDARY)
- L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
- L.append('Content-Type: %s' % get_content_type(filename))
- L.append('')
- L.append(value)
- L.append('--' + BOUNDARY + '--')
- L.append('')
- listy = []
- for element in L:
- try:
- listy.append(element.decode('string_escape'))
- except:
- listy.append(element)
- body = CRLF.join(listy)
- content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
- return content_type, body
- def get_content_type(filename):
- import mimetypes
- 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/



Discussion
hello how are you?
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
End of the line. The snippet is fixed now.
Thanks very much ! :-)
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
I met a very strange error :
'Morsel' object has no attribute 'strip'
You can help me?
Leave a Comment :
Leave a Comment