How to get data from external API to Django Rest Framework - django

I am creating a DRF project (only API's), where logged in user can specify how many people's randomly generated credentials he wants to receive.
Here's my problem:
I want to get these credentials from an external API (https://randomuser.me/api).
This website generates random users data in quantity specified in the url's "results" parameter.
Ex. https://randomuser.me/api/?results=40
My question is:
How can I even get this data? I know JavaScript fetch() method might be useful but I don't actually know how to connect it with Django Rest Framework, and then manipulate it.
I want to show the data to the user after he sends the POST request (only specifying the number of users to be generated) and also save the results in the database, so he can access them later on (through GET request).
If you have any ideas or tips I would be very grateful.
Thank you!

Here is how you can make an API call in a Django Rest Framework API View:
Since you want to store external API request in a database. This is a example of model to store the user result.
models.py
from django.conf import settings
class Credential(models.Models):
""" A user can have many generated credentials """
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
value = models.CharField()
# Here we override the save method to avoid that each user request create new credentials on top of the existing one
def __str__(self):
return f"{self.user.username} - {self.value}"
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
# Assume that you have installed requests: pip install requests
import requests
import json
class GenerateCredential(APIVIew):
""" This view make and external api call, save the result and return
the data generated as json object """
# Only authenticated user can make request on this view
permission_classes = (IsAuthenticated, )
def get(self, request, format=None):
# The url is like https://localhost:8000/api/?results=40
results = self.request.query_params.get('type')
response = {}
# Make an external api request ( use auth if authentication is required for the external API)
r = requests.get('https://randomuser.me/api/?results=40', auth=('user', 'pass'))
r_status = r.status_code
# If it is a success
if r_status = 200:
# convert the json result to python object
data = json.loads(r.json)
# Loop through the credentials and save them
# But it is good to avoid that each user request create new
# credentials on top of the existing one
# ( you can retrieve and delete the old one and save the news credentials )
for c in data:
credential = Credential(user = self.request.user, value=c)
credential.save()
response['status'] = 200
response['message'] = 'success'
response['credentials'] = data
else:
response['status'] = r.status_code
response['message'] = 'error'
response['credentials'] = {}
return Response(response)
class UserCredentials(APIView):
"""This view return the current authenticated user credentials """
permission_classes = (IsAuthenticated, )
def get(self, request, format=None):
current_user = self.request.user
credentials = Credential.objects.filter(user__id=current_user)
return Response(credentials)
NB : These views assume that the user who make the request is authenticated, more infos here. Because we need user to save the retrieved credentials
in the database.
urls.py
path('api/get_user_credentials/', views.UserCredentials.as_view()),
path('api/generate_credentials/', views.GenerateCredentials.as_view()),
.js
const url = "http://localhost:8000/api/generate_credentials/";
# const url = "http://localhost:8000/api/get_user_credentials/";
fetch(url)
.then((resp) => resp.json())
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.log(error);
});

**how to call external api in Django view**
#convert img in base64string
def encode(img):
with open(img, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read())
return encoded_string
#Django View Function
from django.conf import settings
def store_new_camera(request):
Data = {}
try:
lChannel_no = request.POST.get('name')
lCamera_name = request.POST.get('name')
lCamera_id = request.POST.get('camera_id')
lCompany_id = request.POST.get('company_id')
lWearhouse = request.POST.get('wearhouse')
media_dir = settings.MEDIA_ROOT
image_dir = media_dir + "\\" + str('media') + "\\" + str('entrance_img') + "\\" + str(temp_file_name)
data = encode(image_dir)
picture = data
try:
url = "#BaseURL or endpoint"
payload = '[{\"cameraId\":"' + str(lCamera_id) + '","companyId\":"' + str(lCompany_id) + '",\"name\": "' + str(
lCamera_name) + '",\"warehouse\":"' + str(lWearhouse) + '",\"picture\": "' + str(
picture) + '", \"channelNo\":"' + str(lChannel_no) + '"\r\n\t}]'
headers = {
'HodHodApiKey': 'xyz',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
except Exception as e:
import os, sys
exception_type, exception_object, exception_traceback = sys.exc_info()
filename = exception_traceback.tb_frame.f_code.co_filename
line_number = exception_traceback.tb_lineno
print("Exception type: ", exception_type)
print("File name: ", filename)
print("Line number: ", line_number)

Related

flask_login - current_user

I am using the flask_login extension in my flask app to login users. As you must be knowing, this extension has a variable that stores a current_user. The code is working perfectly, except when it comes to testing it.
When I am testing the code (using unittest), I register a "test user" and log it in. But the current_user variable does not keep the user that logged in.
Here is my app code; the part that adds in a category (current_user gets set when a user logs in) :
def post(self):
# Get the access token from the header
auth_header = request.headers.get('Authorization')
access_token = auth_header.split(" ")[1]
if access_token:
# Attempt to decode the token and get the User ID
user_id = User.decode_token(access_token)
if not isinstance(user_id, str):
# Go ahead and handle the request, the user is authenticated
data = request.get_json()
if data['name']:
category = Category(name = data['name'], user_id = user_id)
db.session.add(category)
db.session.commit()
response = {'id' : category.id,
'category_name' : category.name,
'created_by' : current_user.first_name
}
return response
else:
# user is not legit, so the payload is an error message
message = user_id
response = {
'message': message
}
return response
Here is my code that tests the app:
import unittest
import os
import json
import app
from app import create_app, db
class CategoryTestCase(unittest.TestCase):
"""This class represents the Category test case"""
def setUp(self):
"""setup test variables"""
self.app = create_app(config_name="testing")
self.client = self.app.test_client
self.category_data = {'name' : 'Yummy'}
# binds the app with the current context
with self.app.app_context():
#create all tables
db.session.close()
db.drop_all()
db.create_all()
def register_user(self, first_name='Tester', last_name='Api', username='apitester', email='tester#api.com', password='abc'):
"""This helper method helps register a test user"""
user_data = {
'first_name' : first_name,
'last_name' : last_name,
'username' : username,
'email' : email,
'password' : password
}
return self.client().post('/api/v1.0/register', data=json.dumps(user_data), content_type='application/json')
def login_user(self, email='tester#api.com', password='abc'):
"""this helper method helps log in a test user"""
user_data = {
'email' : email,
'password' : password
}
return self.client().post('/api/v1.0/login', data=json.dumps(user_data), content_type='application/json')
def test_category_creation(self):
"""Test that the Api can create a category"""
self.register_user()
login_result = self.login_user()
token = json.loads(login_result.data)
token = token['access_token']
# Create a category by going to that link
response = self.client().post('/api/v1.0/category', headers=dict(Authorization="Bearer " + token), data=json.dumps(self.category_data), content_type='application/json')
self.assertEquals(response.status_code, 201)
You need to use the same context which you used for logging in. So this is what you need to add in your code:
with self.client() as c:
Then use the c to make get, post, or any other request you want. Here is a complete example:
import unittest
from app import create_app
class CategoryTestCase(unittest.TestCase):
"""This class represents the Category test case"""
def setUp(self):
"""setup test variables"""
self.app = create_app(config_name="testing")
self.client = self.app.test_client
self.category_data = {'name' : 'Yummy'}
def test_category_creation(self):
"""Test that the user can create a category"""
with self.client() as c:
# use the c to make get, post or any other requests
login_response = c.post("""login the user""")
# Create a category by going to that link using the same context i.e c
response = c.post('/api/v1.0/category', self.category_data)

Django REST Framework - unittest client failing to resolve hyperlinks relation for POST

I have this test:
class AttributeTest(APITestCase):
def setUp(self):
user1 = User.objects.create(pk=1, username='pepa', email='ads#asasd.cz', is_active=True, is_staff=True)
user1.set_password('mypass')
user1.save()
self.c1 = Campaign.objects.create(pk=1, owner=user1, project_name='c1')
def test(self):
campaign_url = 'http://testserver/api/campaigns/{}/'.format(self.c1.pk)
self.client.login(username='pepa', password='mypass')
data = {
"label": "something_here",
"parent_campaign": campaign_url,
}
# campaign clearly exists (created in setUp) and GET retrieve it:
assert self.client.get(campaign_url).json()['project_name'] == 'c1'
# I can even try it myself using pdb
# but this doesn't work - response return 400 Bad Request
# complaining about the very same hyperlink I can GET above
response = self.client.post('/api/keys', data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
but when run, it fails with {'parent_campaign': ['Invalid hyperlink - No URL match.']}.
When I try using curl or browsable API (outside the test environment), everything works as expected.
My serializer corresponding to the /api/keys:
class AttributeSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='api:key-detail')
parent_campaign = serializers.HyperlinkedRelatedField(
view_name='api:campaign-detail',
lookup_field='cid',
queryset=Campaign.objects.all())
def _get_user_campaigns(self):
user = self.context['view'].request.user
return Campaign.objects.filter(owner=user)
def get_fields(self, *args, **kwargs):
fields = super(AttributeSerializer, self).get_fields(*args, **kwargs)
fields['parent_campaign'].queryset = self._get_user_campaigns()
return fields
class Meta:
model = Key
fields = ("id", 'url', "label", 'parent_campaign')
Using serializer directly:
(Pdb) from api.attribute.serializers import AttributeSerializer
(Pdb) ser = AttributeSerializer(data=data)
(Pdb) ser.is_valid()
True
(Pdb) ser.save()
<Key: Something1 | MAROO | CID: lrvyw93>
Try reversing your url name and passing c1.pk as a url parameter, not just formatting it into your url:
from rest_framework.reverse import reverse
campaign_url_name = 'api:campaign-detail' # Use URL name instead of raw URL path
response = self.client.get(reverse(campaign_url_name, kwargs={'pk': self.c1.pk}))
I don't know why, but the results of tests had to be somehow cached. I restarted the PC and it worked with exactly the same commit. Solved.

rauth/flask: How to login via Twitter?

The provided example in rauth is using the PIN instead of the callback. I don't understand how this should work via web callback.
1) Minor problem:
According to twitter, if oauth_callback URL is passed in, then it should be used instead whatever entry is in the https://dev.twitter.com settings. However this doesn't seem to be true, if I dont set it to http://127.0.0.1:8080/twitter/authorized it would never get to that Url after a successful authorization.
app.add_url_rule('/twitter/login', view_func=views.twitter_login)
app.add_url_rule('/twitter/authorized', 'twitter_authorized', view_func=views.twitter_authorized)
def twitter_login():
request_token, request_token_secret = twitter.get_request_token()
redirect_uri = url_for('twitter_authorized', _external=True)
params = {'oauth_callback': redirect_uri, 'request_token':request_token}
return redirect(twitter.get_authorize_url(**params))
2) Major problem is here:
I can see the request.args has both ['oauth_token'] and ['oauth_verifier'].
But I don't understand how to use them to get the twitter session for obtaining user details such as picture and display name:
def twitter_authorized():
tw_session = twitter.get_auth_session(request_token ??? , request_token_secret ???)
resp = tw_session.get("account/verify_credentials.json", params={'format':'json'})
me = resp.json()
user = User.get_or_create(...)
if user:
login_user(user)
return redirect(url_for('index'))
If someone could shed some light on this, would be highly appreciated.
Here's a working Twitter sign-in examples using Flask based on the Facebook example:
'''
twitter
-------
A simple Flask demo app that shows how to login with Twitter via rauth.
Please note: you must do `from twitter import db; db.create_all()` from
the interpreter before running this example!
'''
from flask import (Flask, flash, request, redirect, render_template, session,
url_for)
from flask.ext.sqlalchemy import SQLAlchemy
from rauth.service import OAuth1Service
from rauth.utils import parse_utf8_qsl
# Flask config
SQLALCHEMY_DATABASE_URI = 'sqlite:///twitter.db'
SECRET_KEY = '\xfb\x12\xdf\xa1#i\xd6>V\xc0\xbb\x8fp\x16#Z\x0b\x81\xeb\x16'
DEBUG = True
TW_KEY = 'oZSbVzKCeyAZTDxw1RKog'
TW_SECRET = 'TuNoqA6NzEBS3Zrb8test7bxQfKTlBfLTXsZ8RaKAo'
# Flask setup
app = Flask(__name__)
app.config.from_object(__name__)
db = SQLAlchemy(app)
# rauth OAuth 1.0 service wrapper
twitter = OAuth1Service(
name='twitter',
consumer_key=TW_KEY,
consumer_secret=TW_SECRET,
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
authorize_url='https://api.twitter.com/oauth/authorize',
base_url='https://api.twitter.com/1.1/')
# models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
fb_id = db.Column(db.String(120))
def __init__(self, username, fb_id):
self.username = username
self.fb_id = fb_id
def __repr__(self):
return '<User %r>' % self.username
#staticmethod
def get_or_create(username, fb_id):
user = User.query.filter_by(username=username).first()
if user is None:
user = User(username, fb_id)
db.session.add(user)
db.session.commit()
return user
# views
#app.route('/')
def index():
return render_template('login.html')
#app.route('/twitter/login')
def login():
oauth_callback = url_for('authorized', _external=True)
params = {'oauth_callback': oauth_callback}
r = twitter.get_raw_request_token(params=params)
data = parse_utf8_qsl(r.content)
session['twitter_oauth'] = (data['oauth_token'],
data['oauth_token_secret'])
return redirect(twitter.get_authorize_url(data['oauth_token'], **params))
#app.route('/twitter/authorized')
def authorized():
request_token, request_token_secret = session.pop('twitter_oauth')
# check to make sure the user authorized the request
if not 'oauth_token' in request.args:
flash('You did not authorize the request')
return redirect(url_for('index'))
try:
creds = {'request_token': request_token,
'request_token_secret': request_token_secret}
params = {'oauth_verifier': request.args['oauth_verifier']}
sess = twitter.get_auth_session(params=params, **creds)
except Exception, e:
flash('There was a problem logging into Twitter: ' + str(e))
return redirect(url_for('index'))
verify = sess.get('account/verify_credentials.json',
params={'format':'json'}).json()
User.get_or_create(verify['screen_name'], verify['id'])
flash('Logged in as ' + verify['name'])
return redirect(url_for('index'))
if __name__ == '__main__':
db.create_all()
app.run()
Hope that helps!

How to send request from one django server to another server

I have a django problem. I want to send datas which are from browser or business logic
on my django server to another django server or just the same server but different port, to handle the request. How can I do? I have tried to achieve using socket,but it seems no working.
Following is my code:
accept the client's request:
def im(request):
userp = None
try:
userp = UserProfile.objects.get(user = request.user)
except:
pass
if not userp:
return HttpResponse("error")
print '111'
if request.method == "GET":
import json
msg = json.loads(request.GET.get('msg'))
try:
msg['from_id'] = userp.id
if msg.get('type', '') == 'sync': #页面同步消息
msg['to_id'] = userp.id
push_msg(msg)
return HttpResponse("success")
except:
return HttpResponse("error")
#return HttpResponseRedirect("http://127.0.0.1:9000/on_message")
return HttpResponse("error")
helper.py:push_msg:
def push_msg(msg):
print '111'
params = str(msg)
headers = {"Content-type":"application/x-www-form-urlencoded", "Accept":"text/plain"}
conn = httplib.HTTPConnection("http://127.0.0.1:9000/push_msg/")
conn.request("POST", "/cgi-bin/query", params, headers)
url(r'^push_msg/$', 'chat.events.on_message')
events.py:on_message
def on_message(request):
msg = request.POST.get('msg')
msg = eval(msg)
try:
print 'handle messages'
from_id = int(msg['from_id'])
to_id = int(msg['to_id'])
user_to = UserProfile.objects.get(id = msg['to_id'])
django_socketio.broadcast_channel(msg, user_to.channel)
if msg.get('type', '') == 'chat':
ct = Chat.objects.send_msg(from_id=from_id,to_id=to_id,content=data['content'],type=1)
ct.read = 1
ct.save()
except:
pass
use python requests module to do this requests has more features then httplib2 and it is very easy to use http://docs.python-requests.org/
I have used httplib2 to accomplish something similar. From the httplib2 documentation try:
import httplib2
import urllib
data = {'name': 'fred', 'address': '123 shady lane'}
body = urllib.urlencode(data)
h = httplib2.Http()
resp, content = h.request("http://example.com", method="POST", body=body)
You should then be able to handle the POST in your second django server, and return the appropriate results to the first django server.

How to make facebook app not ask for permission to use friends

Hi I use python for my facebook app and want to restrict my app from gaining permissions to the users friends. How can I accomplish this? Some code is conf.py
# Facebook Application ID and Secret.
FACEBOOK_APP_ID = '103297833078853'
FACEBOOK_APP_SECRET = 'd1f7a3dfeb650b6826456a5660134f58'
# Canvas Page name.
FACEBOOK_CANVAS_NAME = 'cyberfaze'
# A random token for use with the Real-time API.
FACEBOOK_REALTIME_VERIFY_TOKEN = 'RANDOM TOKEN'
# The external URL this application is available at where the Real-time API will
# send it's pings.
EXTERNAL_HREF = 'http://cyberfaze.appspot.com/'
# Facebook User IDs of admins. The poor mans admin system.
ADMIN_USER_IDS = ['5526183']
Here is main.py
#!/usr/bin/env python
# coding: utf-8
# Copyright 2011 Facebook, Inc.
#
# 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.
import os
# dummy config to enable registering django template filters
os.environ[u'DJANGO_SETTINGS_MODULE'] = u'conf'
from google.appengine.dist import use_library
use_library('django', '1.2')
from django.template.defaultfilters import register
from django.utils import simplejson as json
from functools import wraps
from google.appengine.api import urlfetch, taskqueue
from google.appengine.ext import db, webapp
from google.appengine.ext.webapp import util, template
from google.appengine.runtime import DeadlineExceededError
from random import randrange
from uuid import uuid4
import Cookie
import base64
import cgi
import conf
import datetime
import hashlib
import hmac
import logging
import time
import traceback
import urllib
def htmlescape(text):
"""Escape text for use as HTML"""
return cgi.escape(
text, True).replace("'", ''').encode('ascii', 'xmlcharrefreplace')
#register.filter(name=u'get_name')
def get_name(dic, index):
"""Django template filter to render name"""
return dic[index].name
#register.filter(name=u'get_picture')
def get_picture(dic, index):
"""Django template filter to render picture"""
return dic[index].picture
def select_random(lst, limit):
"""Select a limited set of random non Falsy values from a list"""
final = []
size = len(lst)
while limit and size:
index = randrange(min(limit, size))
size = size - 1
elem = lst[index]
lst[index] = lst[size]
if elem:
limit = limit - 1
final.append(elem)
return final
_USER_FIELDS = u'name,email,picture,friends'
class User(db.Model):
user_id = db.StringProperty(required=True)
access_token = db.StringProperty(required=True)
name = db.StringProperty(required=True)
picture = db.StringProperty(required=True)
email = db.StringProperty()
friends = db.StringListProperty()
dirty = db.BooleanProperty()
def refresh_data(self):
"""Refresh this user's data using the Facebook Graph API"""
me = Facebook().api(u'/me',
{u'fields': _USER_FIELDS, u'access_token': self.access_token})
self.dirty = False
self.name = me[u'name']
self.email = me.get(u'email')
self.picture = me[u'picture']
self.friends = [user[u'id'] for user in me[u'friends'][u'data']]
return self.put()
class Run(db.Model):
user_id = db.StringProperty(required=True)
location = db.StringProperty(required=True)
distance = db.FloatProperty(required=True)
date = db.DateProperty(required=True)
#staticmethod
def find_by_user_ids(user_ids, limit=50):
if user_ids:
return Run.gql(u'WHERE user_id IN :1', user_ids).fetch(limit)
else:
return []
#property
def pretty_distance(self):
return u'%.2f' % self.distance
class RunException(Exception):
pass
class FacebookApiError(Exception):
def __init__(self, result):
self.result = result
def __str__(self):
return self.__class__.__name__ + ': ' + json.dumps(self.result)
class Facebook(object):
"""Wraps the Facebook specific logic"""
def __init__(self, app_id=conf.FACEBOOK_APP_ID,
app_secret=conf.FACEBOOK_APP_SECRET):
self.app_id = app_id
self.app_secret = app_secret
self.user_id = None
self.access_token = None
self.signed_request = {}
def api(self, path, params=None, method=u'GET', domain=u'graph'):
"""Make API calls"""
if not params:
params = {}
params[u'method'] = method
if u'access_token' not in params and self.access_token:
params[u'access_token'] = self.access_token
result = json.loads(urlfetch.fetch(
url=u'https://' + domain + u'.facebook.com' + path,
payload=urllib.urlencode(params),
method=urlfetch.POST,
headers={
u'Content-Type': u'application/x-www-form-urlencoded'})
.content)
if isinstance(result, dict) and u'error' in result:
raise FacebookApiError(result)
return result
def load_signed_request(self, signed_request):
"""Load the user state from a signed_request value"""
try:
sig, payload = signed_request.split(u'.', 1)
sig = self.base64_url_decode(sig)
data = json.loads(self.base64_url_decode(payload))
expected_sig = hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest()
# allow the signed_request to function for upto 1 day
if sig == expected_sig and \
data[u'issued_at'] > (time.time() - 86400):
self.signed_request = data
self.user_id = data.get(u'user_id')
self.access_token = data.get(u'oauth_token')
except ValueError, ex:
pass # ignore if can't split on dot
#property
def user_cookie(self):
"""Generate a signed_request value based on current state"""
if not self.user_id:
return
payload = self.base64_url_encode(json.dumps({
u'user_id': self.user_id,
u'issued_at': str(int(time.time())),
}))
sig = self.base64_url_encode(hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest())
return sig + '.' + payload
#staticmethod
def base64_url_decode(data):
data = data.encode(u'ascii')
data += '=' * (4 - (len(data) % 4))
return base64.urlsafe_b64decode(data)
#staticmethod
def base64_url_encode(data):
return base64.urlsafe_b64encode(data).rstrip('=')
class CsrfException(Exception):
pass
class BaseHandler(webapp.RequestHandler):
facebook = None
user = None
csrf_protect = True
def initialize(self, request, response):
"""General initialization for every request"""
super(BaseHandler, self).initialize(request, response)
try:
self.init_facebook()
self.init_csrf()
self.response.headers[u'P3P'] = u'CP=HONK' # iframe cookies in IE
except Exception, ex:
self.log_exception(ex)
raise
def handle_exception(self, ex, debug_mode):
"""Invoked for unhandled exceptions by webapp"""
self.log_exception(ex)
self.render(u'error',
trace=traceback.format_exc(), debug_mode=debug_mode)
def log_exception(self, ex):
"""Internal logging handler to reduce some App Engine noise in errors"""
msg = ((str(ex) or ex.__class__.__name__) +
u': \n' + traceback.format_exc())
if isinstance(ex, urlfetch.DownloadError) or \
isinstance(ex, DeadlineExceededError) or \
isinstance(ex, CsrfException) or \
isinstance(ex, taskqueue.TransientError):
logging.warn(msg)
else:
logging.error(msg)
def set_cookie(self, name, value, expires=None):
"""Set a cookie"""
if value is None:
value = 'deleted'
expires = datetime.timedelta(minutes=-50000)
jar = Cookie.SimpleCookie()
jar[name] = value
jar[name]['path'] = u'/'
if expires:
if isinstance(expires, datetime.timedelta):
expires = datetime.datetime.now() + expires
if isinstance(expires, datetime.datetime):
expires = expires.strftime('%a, %d %b %Y %H:%M:%S')
jar[name]['expires'] = expires
self.response.headers.add_header(*jar.output().split(u': ', 1))
def render(self, name, **data):
"""Render a template"""
if not data:
data = {}
data[u'js_conf'] = json.dumps({
u'appId': conf.FACEBOOK_APP_ID,
u'canvasName': conf.FACEBOOK_CANVAS_NAME,
u'userIdOnServer': self.user.user_id if self.user else None,
})
data[u'logged_in_user'] = self.user
data[u'message'] = self.get_message()
data[u'csrf_token'] = self.csrf_token
data[u'canvas_name'] = conf.FACEBOOK_CANVAS_NAME
self.response.out.write(template.render(
os.path.join(
os.path.dirname(__file__), 'templates', name + '.html'),
data))
def init_facebook(self):
"""Sets up the request specific Facebook and User instance"""
facebook = Facebook()
user = None
# initial facebook request comes in as a POST with a signed_request
if u'signed_request' in self.request.POST:
facebook.load_signed_request(self.request.get('signed_request'))
# we reset the method to GET because a request from facebook with a
# signed_request uses POST for security reasons, despite it
# actually being a GET. in webapp causes loss of request.POST data.
self.request.method = u'GET'
self.set_cookie(
'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
elif 'u' in self.request.cookies:
facebook.load_signed_request(self.request.cookies.get('u'))
# try to load or create a user object
if facebook.user_id:
user = User.get_by_key_name(facebook.user_id)
if user:
# update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# refresh data if we failed in doing so after a realtime ping
if user.dirty:
user.refresh_data()
# restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
try:
friends = [user[u'id'] for user in me[u'friends'][u'data']]
user = User(key_name=facebook.user_id,
user_id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me[u'name'],
email=me.get(u'email'), picture=me[u'picture'])
user.put()
except KeyError, ex:
pass # ignore if can't get the minimum fields
self.facebook = facebook
self.user = user
def init_csrf(self):
"""Issue and handle CSRF token as necessary"""
self.csrf_token = self.request.cookies.get(u'c')
if not self.csrf_token:
self.csrf_token = str(uuid4())[:8]
self.set_cookie('c', self.csrf_token)
if self.request.method == u'POST' and self.csrf_protect and \
self.csrf_token != self.request.POST.get(u'_csrf_token'):
raise CsrfException(u'Missing or invalid CSRF token.')
def set_message(self, **obj):
"""Simple message support"""
self.set_cookie('m', base64.b64encode(json.dumps(obj)) if obj else None)
def get_message(self):
"""Get and clear the current message"""
message = self.request.cookies.get(u'm')
if message:
self.set_message() # clear the current cookie
return json.loads(base64.b64decode(message))
def user_required(fn):
"""Decorator to ensure a user is present"""
#wraps(fn)
def wrapper(*args, **kwargs):
handler = args[0]
if handler.user:
return fn(*args, **kwargs)
handler.redirect(u'/')
return wrapper
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
class UserRunsHandler(BaseHandler):
"""Show a specific user's runs, ensure friendship with the logged in user"""
#user_required
def get(self, user_id):
if self.user.friends.count(user_id) or self.user.user_id == user_id:
user = User.get_by_key_name(user_id)
if not user:
self.set_message(type=u'error',
content=u'That user does not use Run with Friends.')
self.redirect(u'/')
return
self.render(u'user',
user=user,
runs=Run.find_by_user_ids([user_id]),
)
else:
self.set_message(type=u'error',
content=u'You are not allowed to see that.')
self.redirect(u'/')
class RunHandler(BaseHandler):
"""Add a run"""
#user_required
def post(self):
try:
location = self.request.POST[u'location'].strip()
if not location:
raise RunException(u'Please specify a location.')
distance = float(self.request.POST[u'distance'].strip())
if distance < 0:
raise RunException(u'Invalid distance.')
date_year = int(self.request.POST[u'date_year'].strip())
date_month = int(self.request.POST[u'date_month'].strip())
date_day = int(self.request.POST[u'date_day'].strip())
if date_year < 0 or date_month < 0 or date_day < 0:
raise RunException(u'Invalid date.')
date = datetime.date(date_year, date_month, date_day)
run = Run(
user_id=self.user.user_id,
location=location,
distance=distance,
date=date,
)
run.put()
title = run.pretty_distance + u' miles #' + location
publish = u'<a onclick=\'publishRun(' + \
json.dumps(htmlescape(title)) + u')\'>Post to facebook.</a>'
self.set_message(type=u'success',
content=u'Added your run. ' + publish)
except RunException, e:
self.set_message(type=u'error', content=unicode(e))
except KeyError:
self.set_message(type=u'error',
content=u'Please specify location, distance & date.')
except ValueError:
self.set_message(type=u'error',
content=u'Please specify a valid distance & date.')
except Exception, e:
self.set_message(type=u'error',
content=u'Unknown error occured. (' + unicode(e) + u')')
self.redirect(u'/')
class RealtimeHandler(BaseHandler):
"""Handles Facebook Real-time API interactions"""
csrf_protect = False
def get(self):
if (self.request.GET.get(u'setup') == u'1' and
self.user and conf.ADMIN_USER_IDS.count(self.user.user_id)):
self.setup_subscription()
self.set_message(type=u'success',
content=u'Successfully setup Real-time subscription.')
elif (self.request.GET.get(u'hub.mode') == u'subscribe' and
self.request.GET.get(u'hub.verify_token') ==
conf.FACEBOOK_REALTIME_VERIFY_TOKEN):
self.response.out.write(self.request.GET.get(u'hub.challenge'))
logging.info(
u'Successful Real-time subscription confirmation ping.')
return
else:
self.set_message(type=u'error',
content=u'You are not allowed to do that.')
self.redirect(u'/')
def post(self):
body = self.request.body
if self.request.headers[u'X-Hub-Signature'] != (u'sha1=' + hmac.new(
self.facebook.app_secret,
msg=body,
digestmod=hashlib.sha1).hexdigest()):
logging.error(
u'Real-time signature check failed: ' + unicode(self.request))
return
data = json.loads(body)
if data[u'object'] == u'user':
for entry in data[u'entry']:
taskqueue.add(url=u'/task/refresh-user/' + entry[u'id'])
logging.info('Added task to queue to refresh user data.')
else:
logging.warn(u'Unhandled Real-time ping: ' + body)
def setup_subscription(self):
path = u'/' + conf.FACEBOOK_APP_ID + u'/subscriptions'
params = {
u'access_token': conf.FACEBOOK_APP_ID + u'|' +
conf.FACEBOOK_APP_SECRET,
u'object': u'user',
u'fields': _USER_FIELDS,
u'callback_url': conf.EXTERNAL_HREF + u'realtime',
u'verify_token': conf.FACEBOOK_REALTIME_VERIFY_TOKEN,
}
response = self.facebook.api(path, params, u'POST')
logging.info(u'Real-time setup API call response: ' + unicode(response))
class RefreshUserHandler(BaseHandler):
"""Used as an App Engine Task to refresh a single user's data if possible"""
csrf_protect = False
def post(self, user_id):
logging.info('Refreshing user data for ' + user_id)
user = User.get_by_key_name(user_id)
if not user:
return
try:
user.refresh_data()
except FacebookApiError:
user.dirty = True
user.put()
def main():
routes = [
(r'/', RecentRunsHandler),
(r'/user/(.*)', UserRunsHandler),
(r'/run', RunHandler),
(r'/realtime', RealtimeHandler),
(r'/task/refresh-user/(.*)', RefreshUserHandler),
]
application = webapp.WSGIApplication(routes,
debug=os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'))
util.run_wsgi_app(application)
if __name__ == u'__main__':
main()
I think, and I'm still working with this example project myself, that if you modify this line AND strip out any "friend" references then it should work:
_USER_FIELDS = u'name,email,picture,friends'
becomes
_USER_FIELDS = u'name,email,picture'
It will be a lot of work to strip out all the "friend" references, but from what I can tell that's the only string that the app uses to request user information from the graph api.