From 103af0dd254a2e3b71d70e575f8b809b045b2675 Mon Sep 17 00:00:00 2001 From: Alex Lewis Date: Sat, 29 Sep 2012 00:54:34 +1000 Subject: [PATCH] Consolidation Improved namespace structure, settings --- bodyrep/apps/core/auth/__init__.py | 0 bodyrep/apps/core/auth/views.py | 73 +++ bodyrep/apps/core/forms.py | 15 +- bodyrep/apps/core/views.py | 27 +- bodyrep/media/css/site.css | 6 + bodyrep/media/js/application.js | 4 +- bodyrep/settings.py | 3 + .../templates/core/errors/memberNotFound.html | 7 + bodyrep/templates/core/user/editProfile.html | 4 +- bodyrep/templates/navbar.html | 2 +- bodyrep/urls.py | 23 +- .../utils/{user_functions.py => functions.py} | 60 ++- bodyrep/utils/view_functions.py | 39 -- bodyrep/vendor/__init__.py | 0 bodyrep/vendor/facebook.py | 457 ++++++++++++++++++ 15 files changed, 629 insertions(+), 91 deletions(-) create mode 100644 bodyrep/apps/core/auth/__init__.py create mode 100644 bodyrep/apps/core/auth/views.py create mode 100644 bodyrep/templates/core/errors/memberNotFound.html rename bodyrep/utils/{user_functions.py => functions.py} (68%) delete mode 100644 bodyrep/utils/view_functions.py create mode 100644 bodyrep/vendor/__init__.py create mode 100644 bodyrep/vendor/facebook.py diff --git a/bodyrep/apps/core/auth/__init__.py b/bodyrep/apps/core/auth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bodyrep/apps/core/auth/views.py b/bodyrep/apps/core/auth/views.py new file mode 100644 index 0000000..0d7add8 --- /dev/null +++ b/bodyrep/apps/core/auth/views.py @@ -0,0 +1,73 @@ +import urllib +import urlparse + +from django.contrib.auth.decorators import login_required +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.http import HttpResponseForbidden, HttpResponseRedirect +from django.conf import settings +from utils.functions import ajax_login_required, render_to + +from vendor import facebook + +@render_to('core/auth/facebook.html') +def facebook_connect(request): + facebook_app_id = settings.FACEBOOK_APP_ID + facebook_secret = settings.FACEBOOK_SECRET + + args = { + "client_id": facebook_app_id, + "redirect_uri": "http://" + Site.objects.get_current().domain + reverse('facebook-connect'), + "scope": "offline_access,user_website,publish_actions", + "display": "popup", + } + + verification_code = request.REQUEST.get('code') + if verification_code: + args["client_secret"] = facebook_secret + args["code"] = verification_code + uri = "https://graph.facebook.com/oauth/access_token?" + \ + urllib.urlencode(args) + response_text = urllib.urlopen(uri).read() + response = urlparse.parse_qs(response_text) + + if "access_token" not in response: + logging.user(request, "~BB~FRFailed Facebook connect") + return dict(error="Facebook has returned an error. Try connecting again.") + + access_token = response["access_token"][-1] + + # Get the user's profile. + graph = facebook.GraphAPI(access_token) + profile = graph.get_object("me") + uid = profile["id"] + + # Be sure that two people aren't using the same Facebook account. + existing_user = MSocialServices.objects.filter(facebook_uid=uid) + if existing_user and existing_user[0].user_id != request.user.pk: + user = User.objects.get(pk=existing_user[0].user_id) + #logging.user(request, "~BB~FRFailed FB connect, another user: %s" % user.username) + return dict(error=("Another user (%s, %s) has " + "already connected with those Facebook credentials." + % (user.username, user.email or "no email"))) + + social_services, _ = MSocialServices.objects.get_or_create(user_id=request.user.pk) + social_services.facebook_uid = uid + social_services.facebook_access_token = access_token + social_services.syncing_facebook = True + social_services.save() + + SyncFacebookFriends.delay(user_id=request.user.pk) + + #srequest, "~BB~FRFinishing Facebook connect") + return {} + elif request.REQUEST.get('error'): + #logging.user(request, "~BB~FRFailed Facebook connect") + return {'error': '%s... Try connecting again.' % request.REQUEST.get('error')} + else: + # Start the OAuth process + #logging.user(request, "~BB~FRStarting Facebook connect") + url = "https://www.facebook.com/dialog/oauth?" + urllib.urlencode(args) + return {'next': url} + \ No newline at end of file diff --git a/bodyrep/apps/core/forms.py b/bodyrep/apps/core/forms.py index 33398a7..7eef456 100644 --- a/bodyrep/apps/core/forms.py +++ b/bodyrep/apps/core/forms.py @@ -1,9 +1,13 @@ import datetime + +# Django from django import forms from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.contrib.auth import authenticate from django.db.models import Q + +# Br from apps.core.models import Members class LoginForm(forms.Form): @@ -13,7 +17,6 @@ class LoginForm(forms.Form): password = forms.CharField(label=_("Password"), widget=forms.PasswordInput(attrs={'tabindex': 2, 'class': 'NB-input'}), required=False) - # error_messages={'required': 'Please enter a password.'}) def __init__(self, *args, **kwargs): self.user_cache = None @@ -35,18 +38,16 @@ class LoginForm(forms.Form): if self.user_cache is None: self.user_cache = authenticate(username=email_username[0].username, password="") if self.user_cache is None: - # logging.info(" ***> [%s] Bad Login: TRYING JK-LESS PASSWORD" % username) jkless_password = password.replace('j', '').replace('k', '') self.user_cache = authenticate(username=username, password=jkless_password) if self.user_cache is None: - #logging.info(" ***> [%s] Bad Login" % username) - raise forms.ValidationError(_("Whoopsy-daisy. Try again.")) - else: + raise forms.ValidationError(_("Login failed. Try again")) +# else: # Supreme fuck-up. Accidentally removed the letters J and K from # all user passwords. Re-save with correct password. #logging.info(" ***> [%s] FIXING JK-LESS PASSWORD" % username) - self.user_cache.set_password(password) - self.user_cache.save() + # self.user_cache.set_password(password) + # self.user_cache.save() if not self.user_cache.is_active: raise forms.ValidationError(_("This account is inactive.")) elif username and not user: diff --git a/bodyrep/apps/core/views.py b/bodyrep/apps/core/views.py index 04ff199..f3c16ef 100644 --- a/bodyrep/apps/core/views.py +++ b/bodyrep/apps/core/views.py @@ -1,7 +1,8 @@ import datetime import time import sys -from django.shortcuts import get_object_or_404 + +# Django from django.shortcuts import render, render_to_response from django.contrib.auth.decorators import login_required from django.template.loader import render_to_string @@ -13,20 +14,17 @@ from django.contrib.auth import logout as logout_user from django.contrib.auth.models import User from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404 from django.conf import settings -from django.core.mail import mail_admins -from django.core.validators import email_re -from django.core.mail import EmailMultiAlternatives from django.contrib.sites.models import Site -from bodyrep.apps.core.forms import LoginForm, EditProfileForm from django.core.context_processors import csrf from django.template import RequestContext + +# Libs from annoying.decorators import render_to, ajax_request +# BR +from apps.core.forms import LoginForm, EditProfileForm from apps.core.models import Members - -from utils.user_functions import get_user, ajax_login_required -from utils.view_functions import get_argument_or_404, render_to, is_true -#from utils.ratelimit import ratelimit +from utils.functions import get_user, render_to @render_to('core/landing.html') def index(request): @@ -40,9 +38,13 @@ def showUserProfile(request): member = Members.objects.get(username=request.user.username) return {'member': member} +@login_required @render_to('core/member/profile.html') def showMemberProfile(request, username): - member = Members.objects.get(username=username) + try: + member = Members.objects.get(username=username) + except Members.DoesNotExist: + return render_to_response('core/errors/memberNotFound.html') if request.user.is_anonymous(): return login(request) @@ -53,7 +55,7 @@ def showMemberProfile(request, username): @render_to('core/auth/login.html') def login(request): if not request.user.is_anonymous(): - return HttpResponseRedirect(reverse('mprofile')) + return HttpResponseRedirect(reverse('showUserProfile')) if request.method == "POST": if request.POST.get('submit', '').startswith('log'): @@ -75,7 +77,7 @@ def dologin(request): form = LoginForm(request.POST, prefix='login') if form.is_valid(): login_user(request, form.get_user()) - return HttpResponseRedirect(reverse('mprofile')) + return HttpResponseRedirect(reverse('showUserProfile')) else: message = form.errors.items()[0][1][0] @@ -92,6 +94,7 @@ def logout(request): @ajax_request @render_to('core/user/editProfile.html') def editUserProfile(request): + sys.stderr.write('xxx\n\n') if request.user.is_anonymous(): return login(request) else: diff --git a/bodyrep/media/css/site.css b/bodyrep/media/css/site.css index 68c8144..c5de81f 100644 --- a/bodyrep/media/css/site.css +++ b/bodyrep/media/css/site.css @@ -12,6 +12,12 @@ display: none; width : 170px; border-right : 1px solid #e6e6e6; } +#banner p { + padding: 20px 0px; + font-size: 15px; + font-weight: bold; + color: navy; +} .mc-profile-pic { width : 140px; diff --git a/bodyrep/media/js/application.js b/bodyrep/media/js/application.js index 982306f..87f1728 100644 --- a/bodyrep/media/js/application.js +++ b/bodyrep/media/js/application.js @@ -1,7 +1,7 @@ $(function() { $('#edprf').unbind().live('click', function() { - $.get('/app_dev.php/m/profile/edit', {}, function(data) { + $.get('/m/profile/edit', {}, function(data) { $('#mcnt').html(data); }); }); @@ -19,7 +19,7 @@ $(function() { $.post(frm.attr('action'), frm.serialize(), function(response) { if(response.result) - $('#mcnt').append('Updated'); + $('#banner').html('

Profile Updated

'); }); return false; }); diff --git a/bodyrep/settings.py b/bodyrep/settings.py index 407abfa..47dd5f5 100644 --- a/bodyrep/settings.py +++ b/bodyrep/settings.py @@ -60,6 +60,9 @@ PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.BCryptPasswordHasher', ) +FACEBOOK_APP_ID = '' +FACEBOOK_SECRET = '' + ADMIN_MEDIA_PREFIX = '/static/admin/' # Additional locations of static files diff --git a/bodyrep/templates/core/errors/memberNotFound.html b/bodyrep/templates/core/errors/memberNotFound.html new file mode 100644 index 0000000..d72c775 --- /dev/null +++ b/bodyrep/templates/core/errors/memberNotFound.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} + +{% block content %} + +Member not found + +{% endblock %} diff --git a/bodyrep/templates/core/user/editProfile.html b/bodyrep/templates/core/user/editProfile.html index a795eb1..a46901e 100644 --- a/bodyrep/templates/core/user/editProfile.html +++ b/bodyrep/templates/core/user/editProfile.html @@ -1,9 +1,9 @@ {% block content %}

Update Profile

+ - -
+
{% if edit_form.errors %} {% for field, error in edit_form.errors.items %} diff --git a/bodyrep/templates/navbar.html b/bodyrep/templates/navbar.html index b7bc365..053123c 100644 --- a/bodyrep/templates/navbar.html +++ b/bodyrep/templates/navbar.html @@ -21,7 +21,7 @@ diff --git a/bodyrep/urls.py b/bodyrep/urls.py index d86f803..283af09 100644 --- a/bodyrep/urls.py +++ b/bodyrep/urls.py @@ -1,33 +1,38 @@ +# Django libs from django.conf.urls.defaults import patterns, include, url -from apps.core import views as core_views from django.conf import settings from django.contrib import admin +# BR libs +from apps.core import views as coreviews +from apps.core.auth import views as authviews + admin.autodiscover() urlpatterns = patterns('', # Index - url(r'^$', core_views.index, name='index'), + url(r'^$', coreviews.index, name='index'), # Static (r'^s/(?P.*)$', 'django.views.static.serve', {'document_root': settings.STATIC_ROOT}), # User tasks - url(r'^m/profile/?$', core_views.showUserProfile, name='mprofile'), - url(r'^m/profile/save/?$', core_views.saveUserProfile, name='msave'), - url(r'^m/profile/edit/?$', core_views.editUserProfile, name='medit'), + url(r'^m/profile/?$', coreviews.showUserProfile, name='showUserProfile'), + url(r'^m/profile/save/?$', coreviews.saveUserProfile, name='saveUserProfile'), + url(r'^m/profile/edit/?$', coreviews.editUserProfile, name='editUser'), # Auth - url(r'^logout$', core_views.logout, name='logout'), - url(r'^login$', core_views.login, name='login'), - url(r'^dologin$', core_views.dologin, name='dologin'), + url(r'^logout$', coreviews.logout, name='logout'), + url(r'^login$', coreviews.login, name='login'), + url(r'^dologin$', coreviews.dologin, name='dologin'), + url(r'^facebook_connect/?$', authviews.facebook_connect, name='facebook-connect'), # Django url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/', include(admin.site.urls)), # Member profile - url(r'^([a-zA-Z0-9]+)', core_views.showMemberProfile), + url(r'^([a-zA-Z0-9]+)', coreviews.showMemberProfile), ) diff --git a/bodyrep/utils/user_functions.py b/bodyrep/utils/functions.py similarity index 68% rename from bodyrep/utils/user_functions.py rename to bodyrep/utils/functions.py index 910ded2..422099f 100644 --- a/bodyrep/utils/user_functions.py +++ b/bodyrep/utils/functions.py @@ -5,6 +5,16 @@ from django.utils.hashcompat import md5_constructor from django.utils.http import urlquote from django.http import HttpResponseForbidden from django.conf import settings +from django.template import RequestContext +from django.shortcuts import render_to_response + +# BR + + + +# +# NewsBlur functions https://github.com/samuelclay/NewsBlur +# def ajax_login_required(function=None): def _dec(view_func): @@ -25,24 +35,6 @@ def ajax_login_required(function=None): else: return _dec(function) -def admin_only(function=None): - def _dec(view_func): - def _view(request, *args, **kwargs): - if not request.user.is_staff: - return HttpResponseForbidden() - else: - return view_func(request, *args, **kwargs) - - _view.__name__ = view_func.__name__ - _view.__dict__ = view_func.__dict__ - _view.__doc__ = view_func.__doc__ - - return _view - - if function is None: - return _dec - else: - return _dec(function) def get_user(request): if not hasattr(request, 'user'): @@ -106,4 +98,34 @@ def extract_user_agent(request): elif 'WP8' in user_agent: platform = 'WP8' - return platform \ No newline at end of file + return platform + +def render_to(template): + """ + Decorator for Django views that sends returned dict to render_to_response function + with given template and RequestContext as context instance. + + If view doesn't return dict then decorator simply returns output. + Additionally view can return two-tuple, which must contain dict as first + element and string with template name as second. This string will + override template name, given as parameter + + Parameters: + + - template: template name to use + """ + def renderer(func): + def wrapper(request, *args, **kw): + output = func(request, *args, **kw) + if isinstance(output, (list, tuple)): + return render_to_response(output[1], output[0], RequestContext(request)) + elif isinstance(output, dict): + return render_to_response(template, output, RequestContext(request)) + return output + return wrapper + return renderer + +def is_true(value): + if value == 1: + return True + return bool(value) and isinstance(value, basestring) and value.lower() not in ('false', '0') \ No newline at end of file diff --git a/bodyrep/utils/view_functions.py b/bodyrep/utils/view_functions.py deleted file mode 100644 index 580f05a..0000000 --- a/bodyrep/utils/view_functions.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.http import Http404 -from django.template import RequestContext -from django.shortcuts import render_to_response - -def get_argument_or_404(request, param, method='REQUEST'): - try: - return getattr(request, method)[param] - except KeyError: - raise Http404 - -def render_to(template): - """ - Decorator for Django views that sends returned dict to render_to_response function - with given template and RequestContext as context instance. - - If view doesn't return dict then decorator simply returns output. - Additionally view can return two-tuple, which must contain dict as first - element and string with template name as second. This string will - override template name, given as parameter - - Parameters: - - - template: template name to use - """ - def renderer(func): - def wrapper(request, *args, **kw): - output = func(request, *args, **kw) - if isinstance(output, (list, tuple)): - return render_to_response(output[1], output[0], RequestContext(request)) - elif isinstance(output, dict): - return render_to_response(template, output, RequestContext(request)) - return output - return wrapper - return renderer - -def is_true(value): - if value == 1: - return True - return bool(value) and isinstance(value, basestring) and value.lower() not in ('false', '0') \ No newline at end of file diff --git a/bodyrep/vendor/__init__.py b/bodyrep/vendor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bodyrep/vendor/facebook.py b/bodyrep/vendor/facebook.py new file mode 100644 index 0000000..e3120df --- /dev/null +++ b/bodyrep/vendor/facebook.py @@ -0,0 +1,457 @@ +#!/usr/bin/env python +# +# Copyright 2010 Facebook +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Python client library for the Facebook Platform. + +This client library is designed to support the Graph API and the official +Facebook JavaScript SDK, which is the canonical way to implement +Facebook authentication. Read more about the Graph API at +http://developers.facebook.com/docs/api. You can download the Facebook +JavaScript SDK at http://github.com/facebook/connect-js/. + +If your application is using Google AppEngine's webapp framework, your +usage of this module might look like this: + +user = facebook.get_user_from_cookie(self.request.cookies, key, secret) +if user: +graph = facebook.GraphAPI(user["access_token"]) +profile = graph.get_object("me") +friends = graph.get_connections("me", "friends") + +""" + +import cgi +import time +import urllib +import urllib2 +import hashlib +import hmac +import base64 +import logging + +# Find a JSON parser +try: + import simplejson as json +except ImportError: + try: + from django.utils import simplejson as json + except ImportError: + import json +_parse_json = json.loads + +# Find a query string parser +try: + from urlparse import parse_qs +except ImportError: + from cgi import parse_qs + +class GraphAPI(object): + """A client for the Facebook Graph API. + +See http://developers.facebook.com/docs/api for complete documentation +for the API. + +The Graph API is made up of the objects in Facebook (e.g., people, pages, +events, photos) and the connections between them (e.g., friends, +photo tags, and event RSVPs). This client provides access to those +primitive types in a generic way. For example, given an OAuth access +token, this will fetch the profile of the active user and the list +of the user's friends: + +graph = facebook.GraphAPI(access_token) +user = graph.get_object("me") +friends = graph.get_connections(user["id"], "friends") + +You can see a list of all of the objects and connections supported +by the API at http://developers.facebook.com/docs/reference/api/. + +You can obtain an access token via OAuth or by using the Facebook +JavaScript SDK. See http://developers.facebook.com/docs/authentication/ +for details. + +If you are using the JavaScript SDK, you can use the +get_user_from_cookie() method below to get the OAuth access token +for the active user from the cookie saved by the SDK. +""" + def __init__(self, access_token=None): + self.access_token = access_token + + def get_object(self, id, **args): + """Fetchs the given object from the graph.""" + return self.request(id, args) + + def get_objects(self, ids, **args): + """Fetchs all of the given object from the graph. + +We return a map from ID to object. If any of the IDs are invalid, +we raise an exception. +""" + args["ids"] = ",".join(ids) + return self.request("", args) + + def get_connections(self, id, connection_name, **args): + """Fetchs the connections for given object.""" + return self.request(id + "/" + connection_name, args) + + def put_object(self, parent_object, connection_name, **data): + """Writes the given object to the graph, connected to the given parent. + +For example, + +graph.put_object("me", "feed", message="Hello, world") + +writes "Hello, world" to the active user's wall. Likewise, this +will comment on a the first post of the active user's feed: + +feed = graph.get_connections("me", "feed") +post = feed["data"][0] +graph.put_object(post["id"], "comments", message="First!") + +See http://developers.facebook.com/docs/api#publishing for all of +the supported writeable objects. + +Most write operations require extended permissions. For example, +publishing wall posts requires the "publish_stream" permission. See +http://developers.facebook.com/docs/authentication/ for details about +extended permissions. +""" + assert self.access_token, "Write operations require an access token" + return self.request(parent_object + "/" + connection_name, post_args=data) + + def put_wall_post(self, message, attachment={}, profile_id="me"): + """Writes a wall post to the given profile's wall. + +We default to writing to the authenticated user's wall if no +profile_id is specified. + +attachment adds a structured attachment to the status message being +posted to the Wall. It should be a dictionary of the form: + +{"name": "Link name" +"link": "http://www.example.com/", +"caption": "{*actor*} posted a new review", +"description": "This is a longer description of the attachment", +"picture": "http://www.example.com/thumbnail.jpg"} + +""" + return self.put_object(profile_id, "feed", message=message, **attachment) + + def put_comment(self, object_id, message): + """Writes the given comment on the given post.""" + return self.put_object(object_id, "comments", message=message) + + def put_like(self, object_id): + """Likes the given post.""" + return self.put_object(object_id, "likes") + + def delete_object(self, id): + """Deletes the object with the given ID from the graph.""" + self.request(id, post_args={"method": "delete"}) + + def put_photo(self, image, message=None, album_id=None, **kwargs): + """Uploads an image using multipart/form-data +image=File like object for the image +message=Caption for your image +album_id=None posts to /me/photos which uses or creates and uses +an album for your application. +""" + object_id = album_id or "me" + #it would have been nice to reuse self.request; but multipart is messy in urllib + post_args = { +'access_token': self.access_token, +'source': image, +'message': message + } + post_args.update(kwargs) + content_type, body = self._encode_multipart_form(post_args) + req = urllib2.Request("https://graph.facebook.com/%s/photos" % object_id, data=body) + req.add_header('Content-Type', content_type) + try: + data = urllib2.urlopen(req).read() + #For Python 3 use this: + #except urllib2.HTTPError as e: + except urllib2.HTTPError, e: + data = e.read() # Facebook sends OAuth errors as 400, and urllib2 throws an exception, we want a GraphAPIError + try: + response = _parse_json(data) + # Raise an error if we got one, but don't freak out if Facebook just gave us a Bool value + if response and isinstance(response, dict) and response.get("error"): + raise GraphAPIError(response["error"].get("code", 1), + response["error"]["message"]) + except ValueError: + response = data + + return response + + # based on: http://code.activestate.com/recipes/146306/ + def _encode_multipart_form(self, fields): + """Fields are a dict of form name-> value +For files, value should be a file object. +Other file-like objects might work and a fake name will be chosen. +Return (content_type, body) ready for httplib.HTTP instance +""" + BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' + CRLF = '\r\n' + L = [] + for (key, value) in fields.items(): + logging.debug("Encoding %s, (%s)%s" % (key, type(value), value)) + if not value: + continue + L.append('--' + BOUNDARY) + if hasattr(value, 'read') and callable(value.read): + filename = getattr(value,'name','%s.jpg' % key) + L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) + L.append('Content-Type: image/jpeg') + value = value.read() + logging.debug(type(value)) + else: + L.append('Content-Disposition: form-data; name="%s"' % key) + L.append('') + if isinstance(value, unicode): + logging.debug("Convert to ascii") + value = value.encode('ascii') + L.append(value) + L.append('--' + BOUNDARY + '--') + L.append('') + body = CRLF.join(L) + content_type = 'multipart/form-data; boundary=%s' % BOUNDARY + return content_type, body + + def request(self, path, args=None, post_args=None): + """Fetches the given path in the Graph API. + +We translate args to a valid query string. If post_args is given, +we send a POST request to the given path with the given arguments. +""" + if not args: args = {} + if self.access_token: + if post_args is not None: + post_args["access_token"] = self.access_token + else: + args["access_token"] = self.access_token + post_data = None if post_args is None else urllib.urlencode(post_args) + try: + file = urllib2.urlopen("https://graph.facebook.com/" + path + "?" + + urllib.urlencode(args), post_data) + except urllib2.HTTPError, e: + response = _parse_json( e.read() ) + raise GraphAPIError(response["error"]["type"], + response["error"]["message"]) + + try: + fileInfo = file.info() + if fileInfo.maintype == 'text': + response = _parse_json(file.read()) + elif fileInfo.maintype == 'image': + mimetype = fileInfo['content-type'] + response = { + "data": file.read(), + "mime-type": mimetype, + "url": file.url, + } + else: + raise GraphAPIError('Response Error', 'Maintype was not text or image') + finally: + file.close() + if response and isinstance(response, dict) and response.get("error"): + raise GraphAPIError(response["error"]["type"], + response["error"]["message"]) + return response + + def api_request(self, path, args=None, post_args=None): + """Fetches the given path in the Graph API. + +We translate args to a valid query string. If post_args is given, +we send a POST request to the given path with the given arguments. +""" + if not args: args = {} + if self.access_token: + if post_args is not None: + post_args["access_token"] = self.access_token + else: + args["access_token"] = self.access_token + if self.api_key: + if post_args is not None: + post_args["api_key"] = self.api_key + else: + args["api_key"] = self.api_key + if post_args is not None: + post_args["format"] = "json-strings" + else: + args["format"] = "json-strings" + post_data = None if post_args is None else urllib.urlencode(post_args) + file = urllib.urlopen("https://api.facebook.com/method/" + path + "?" + + urllib.urlencode(args), post_data) + try: + response = _parse_json(file.read()) + finally: + file.close() + if response and response.get("error"): + raise GraphAPIError(response["error"]["type"], + response["error"]["message"]) + return response + + + def fql(self, query, args=None, post_args=None): + """FQL query. +Two reasons to have this method: +1. Graph api does not expose some info fields of a user, e.g. +a user's networks/affiliations, we have to fall back to old api. +2. FQL is a strong tool. +Example query: "SELECT affiliations FROM user WHERE uid = me()" +""" + if not args: args = {} + if self.access_token: + if post_args is not None: + post_args["access_token"] = self.access_token + else: + args["access_token"] = self.access_token + post_data = None if post_args is None else urllib.urlencode(post_args) + + args["query"] = query + args["format"]="json" + file = urllib2.urlopen("https://api.facebook.com/method/fql.query?" + + urllib.urlencode(args), post_data) + try: + content = file.read() + response = _parse_json(content) + #Return a list if success, return a dictionary if failed + if type(response) is dict and "error_code" in response: + raise GraphAPIError(response["error_code"],response["error_msg"]) + except Exception, e: + raise e + finally: + file.close() + + return response + + +class GraphAPIError(Exception): + def __init__(self, type, message): + Exception.__init__(self, message) + self.type = type + +def get_user_from_cookie(cookies, app_id, app_secret): + """Parses the cookie set by the official Facebook JavaScript SDK. + +cookies should be a dictionary-like object mapping cookie names to +cookie values. + +If the user is logged in via Facebook, we return a dictionary with the +keys "uid" and "access_token". The former is the user's Facebook ID, +and the latter can be used to make authenticated requests to the Graph API. +If the user is not logged in, we return None. + +Download the official Facebook JavaScript SDK at +http://github.com/facebook/connect-js/. Read more about Facebook +authentication at http://developers.facebook.com/docs/authentication/. +""" + cookie = cookies.get("fbsr_" + app_id, "") + if not cookie: return None + parsed_request = parse_signed_request(cookie, app_secret) + result = get_access_token_from_code(parsed_request["code"], "", + app_id, app_secret) + result["uid"] = parsed_request["user_id"] + return result + +def parse_signed_request(signed_request, app_secret): + """ Return dictionary with signed request data. + +We return a dictionary containing the information in the signed_request. This will +include a user_id if the user has authorised your application, as well as any +information requested in the scope. + +If the signed_request is malformed or corrupted, False is returned. +""" + try: + l = signed_request.split('.', 2) + encoded_sig = str(l[0]) + payload = str(l[1]) + sig = base64.urlsafe_b64decode(encoded_sig + "=" * ((4 - len(encoded_sig) % 4) % 4)) + data = base64.urlsafe_b64decode(payload + "=" * ((4 - len(payload) % 4) % 4)) + except IndexError: + return False # raise ValueError('signed_request malformed') + except TypeError: + return False # raise ValueError('signed_request had corrupted payload') + + data = _parse_json(data) + if data.get('algorithm', '').upper() != 'HMAC-SHA256': + return False # raise ValueError('signed_request used unknown algorithm') + + expected_sig = hmac.new(app_secret, msg=payload, digestmod=hashlib.sha256).digest() + if sig != expected_sig: + return False # raise ValueError('signed_request had signature mismatch') + + return data + +def auth_url(app_id, canvas_url, perms = None): + url = "https://www.facebook.com/dialog/oauth?" + kvps = {'client_id': app_id, 'redirect_uri': canvas_url} + if perms: + kvps['scope'] = ",".join(perms) + return url + urllib.urlencode(kvps) + + +def get_access_token_from_code(code, redirect_uri, app_id, app_secret): + """ +Get a user-specific access token from the "code" returned from a Facebook +OAuth dialog. Returns a dict containing the access token and its expiration +date (if applicable). + +""" + args = { + "code": code, + "redirect_uri": redirect_uri, + "client_id": app_id, + "client_secret": app_secret, + } + # We would use GraphAPI.request() here, except for that the fact that the + # response is a key-value pair, and not JSON. + response = urllib.urlopen("https://graph.facebook.com/oauth/access_token" + + "?" + urllib.urlencode(args)).read() + query_str = parse_qs(response) + if "access_token" in query_str: + result = {"access_token":query_str["access_token"][0]} + if "expires" in query_str: + result["expires"] = query_str["expires"][0] + return result + else: + response = json.loads(response) + raise GraphAPIError(response["error"]["type"], + response["error"]["message"]) + + +def get_app_access_token(app_id, app_secret): + """ +Get the access_token for the app that can be used for insights and creating test users +app_id = retrieved from the developer page +app_secret = retrieved from the developer page +returns the application access_token +""" + # Get an app access token + args = {'grant_type':'client_credentials', + 'client_id':app_id, + 'client_secret':app_secret} + + file = urllib2.urlopen("https://graph.facebook.com/oauth/access_token?" + + urllib.urlencode(args)) + + try: + result = file.read().split("=")[1] + finally: + file.close() + + return result \ No newline at end of file