├── __init__.py ├── django_facebook ├── __init__.py ├── templatetags │ ├── __init__.py │ └── facebook.py ├── templates │ ├── tags │ │ ├── facebook_init.html │ │ └── facebook_load.html │ └── facebook_debug.html ├── models.py ├── decorators.py ├── auth.py └── middleware.py ├── .gitignore ├── setup.py └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_facebook/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_facebook/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /django_facebook/templates/tags/facebook_init.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /django_facebook/templates/tags/facebook_load.html: -------------------------------------------------------------------------------- 1 |
2 | 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-facebook', 5 | version='0.1', 6 | description='Replace Django Authentication with Facebook', 7 | long_description=open('README.md').read(), 8 | author='Aidan Lister', 9 | author_email='aidan@php.net', 10 | url='http://github.com/aidanlister/django-facebook', 11 | license='MPL', 12 | packages=find_packages(), 13 | include_package_data=True, 14 | zip_safe=False, 15 | install_requires=[], 16 | classifiers=[ 17 | 'Development Status :: 4 - Beta', 18 | 'Environment :: Web Environment', 19 | 'Framework :: Django', 20 | 'Intended Audience :: Developers', 21 | 'Operating System :: OS Independent', 22 | 'Programming Language :: Python', 23 | 'Topic :: Software Development :: Libraries :: Python Modules', 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /django_facebook/templates/facebook_debug.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block facebook_code %} 4 | FB.getLoginStatus(function(response) { 5 | var update = $('#getLoginStatus') 6 | if (response.session) { 7 | update.text('response.session.uid = ' + response.session.uid) 8 | } else { 9 | update.text('response.session = null') 10 | } 11 | }); 12 | {% endblock %} 13 | 14 | 15 | {% block content %} 16 | 17 | 18 | 19 | 20 | 21 | 22 |
request.user{{ request.user }}
request.facebook.uid{{ request.facebook.uid }}
request.facebook.user{{ request.facebook.user }}
request.COOKIES{{ request.COOKIES }}
FB.getLoginStatusLoading ...
23 | 24 | 25 | 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /django_facebook/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | GENDERS = ( 5 | ('M', 'Male'), 6 | ('F', 'Female'), 7 | ) 8 | 9 | class FacebookProfile(models.Model): 10 | user = models.ForeignKey(User) 11 | uid = models.CharField(max_length=31) 12 | name = models.CharField(max_length=100) 13 | first_name = models.CharField(max_length=31) 14 | middle_name = models.CharField(max_length=31, null=True, blank=True) 15 | last_name = models.CharField(max_length=31) 16 | link = models.URLField(null=True, blank=True) 17 | birthday = models.DateField(null=True, blank=True) 18 | hometown = models.CharField(max_length=31, null=True, blank=True) 19 | bio = models.TextField(null=True, blank=True) 20 | gender = models.CharField(max_length=1, choices=GENDERS, null=True, blank=True) 21 | modified = models.DateTimeField() 22 | 23 | def __unicode__(self): 24 | return u"%s" % (self.name) 25 | -------------------------------------------------------------------------------- /django_facebook/templatetags/facebook.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | register = template.Library() 4 | 5 | FACEBOOK_EXTENDED_PERMISSIONS = getattr(settings, 'FACEBOOK_EXTENDED_PERMISSIONS', []) 6 | 7 | @register.inclusion_tag('tags/facebook_load.html') 8 | def facebook_load(): 9 | pass 10 | 11 | @register.tag 12 | def facebook_init(parser, token): 13 | nodelist = parser.parse(('endfacebook',)) 14 | parser.delete_first_token() 15 | return FacebookNode(nodelist) 16 | 17 | class FacebookNode(template.Node): 18 | """ Allow code to be added inside the facebook asynchronous closure. """ 19 | def __init__(self, nodelist): 20 | try: 21 | app_id = settings.FACEBOOK_APP_ID 22 | except AttributeError: 23 | raise template.TemplateSyntaxError, "%r tag requires FACEBOOK_APP_ID to be configured." \ 24 | % token.contents.split()[0] 25 | self.app_id = app_id 26 | self.nodelist = nodelist 27 | 28 | def render(self, context): 29 | t = template.loader.get_template('tags/facebook_init.html') 30 | code = self.nodelist.render(context) 31 | custom_context = context 32 | custom_context['code'] = code 33 | custom_context['app_id'] = self.app_id 34 | return t.render(context) 35 | 36 | @register.simple_tag 37 | def facebook_perms(): 38 | return ",".join(FACEBOOK_EXTENDED_PERMISSIONS) 39 | -------------------------------------------------------------------------------- /django_facebook/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import update_wrapper, wraps 2 | from django.contrib.auth import REDIRECT_FIELD_NAME 3 | from django.http import HttpResponse, HttpResponseRedirect 4 | from django.utils.decorators import available_attrs 5 | from django.utils.http import urlquote 6 | 7 | def facebook_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME): 8 | """ 9 | Decorator for views that checks that the user is logged in, redirecting 10 | to the log-in page if necessary. 11 | """ 12 | def _passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): 13 | if not login_url: 14 | from django.conf import settings 15 | login_url = settings.LOGIN_URL 16 | 17 | def decorator(view_func): 18 | def _wrapped_view(request, *args, **kwargs): 19 | if test_func(request): 20 | return view_func(request, *args, **kwargs) 21 | path = urlquote(request.get_full_path()) 22 | tup = login_url, redirect_field_name, path 23 | return HttpResponseRedirect('%s?%s=%s' % tup) 24 | return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) 25 | return decorator 26 | 27 | actual_decorator = _passes_test( 28 | lambda r: r.facebook, 29 | redirect_field_name=redirect_field_name 30 | ) 31 | 32 | if function: 33 | return actual_decorator(function) 34 | return actual_decorator 35 | -------------------------------------------------------------------------------- /django_facebook/auth.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth.models import User 3 | from django.contrib.auth.backends import ModelBackend 4 | import re 5 | 6 | from models import FacebookProfile 7 | 8 | FACEBOOK_PREPOPULATE_USER_DATA = getattr(settings, 'FACEBOOK_PREPOPULATE_USER_DATA', None) 9 | FACEBOOK_EXTENDED_PERMISSIONS = getattr(settings, 'FACEBOOK_EXTENDED_PERMISSIONS', None) 10 | 11 | class FacebookBackend(ModelBackend): 12 | """ Authenticate a facebook user. """ 13 | def authenticate(self, fb_uid=None, fb_object=None): 14 | """ If we receive a facebook uid then the cookie has already been validated. """ 15 | if fb_uid: 16 | user, created = User.objects.get_or_create(username=fb_uid) 17 | 18 | # Consider replacing this synchronous data request (out to Facebook 19 | # and back) with an asynchronous request, using Celery or similar tool 20 | if FACEBOOK_PREPOPULATE_USER_DATA and created and fb_object: 21 | fb_user = fb_object.graph.get_object(u'me') 22 | user.first_name = fb_user['first_name'] 23 | user.last_name = fb_user['last_name'] 24 | 25 | if 'email' in FACEBOOK_EXTENDED_PERMISSIONS and 'email' in fb_user: 26 | user.email = fb_user['email'] 27 | 28 | user.save() 29 | 30 | profile = FacebookProfile() 31 | 32 | profile.user = user 33 | 34 | if 'birthday' in fb_user: 35 | match = re.search('(\d+)/(\d+)/(\d+)', fb_user['birthday']) 36 | if match: 37 | profile.birthday = "%s-%s-%s" % (match.group(3), match.group(1), match.group(2)) 38 | 39 | profile.uid = fb_user['id'] 40 | profile.name = fb_user['name'] 41 | profile.first_name = fb_user['first_name'] 42 | profile.last_name = fb_user['last_name'] 43 | 44 | if 'middle_name' in fb_user: 45 | profile.middle_name = fb_user['middle_name'] 46 | 47 | 48 | if 'link' in fb_user: 49 | profile.link = fb_user['link'] 50 | 51 | if 'hometown' in fb_user: 52 | profile.hometown = fb_user['hometown']['name'] 53 | 54 | if 'bio' in fb_user: 55 | profile.bio = fb_user['bio'] 56 | 57 | if 'gender' in fb_user: 58 | profile.gender = fb_user['gender'][0].upper() 59 | 60 | profile.modified = fb_user['updated_time'].replace('T', ' ').replace('+', '.') 61 | 62 | profile.save() 63 | 64 | return user 65 | return None 66 | -------------------------------------------------------------------------------- /django_facebook/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib import auth 3 | import facebook 4 | import datetime 5 | 6 | 7 | class DjangoFacebook(object): 8 | """ Simple accessor object for the Facebook user. """ 9 | def __init__(self, user): 10 | self.user = user 11 | self.uid = user['uid'] 12 | self.graph = facebook.GraphAPI(user['access_token']) 13 | 14 | 15 | class FacebookDebugCookieMiddleware(object): 16 | """ Sets an imaginary cookie to make it easy to work from a development environment. 17 | 18 | This should be a raw string as is sent from a browser to the server, obtained by 19 | LiveHeaders, Firebug or similar. The middleware takes care of naming the cookie 20 | correctly. This should initialised before FacebookMiddleware. 21 | """ 22 | def process_request(self, request): 23 | cookie_name = "fbs_" + settings.FACEBOOK_APP_ID 24 | request.COOKIES[cookie_name] = settings.FACEBOOK_DEBUG_COOKIE 25 | return None 26 | 27 | 28 | class FacebookDebugTokenMiddleware(object): 29 | """ Forces a specific access token to be used. 30 | 31 | This should be used instead of FacebookMiddleware. Make sure you have 32 | FACEBOOK_DEBUG_UID and FACEBOOK_DEBUG_TOKEN set in your configuration. 33 | """ 34 | def process_request(self, request): 35 | user = { 36 | 'uid':settings.FACEBOOK_DEBUG_UID, 37 | 'access_token':settings.FACEBOOK_DEBUG_TOKEN, 38 | } 39 | request.facebook = Facebook(user) 40 | return None 41 | 42 | 43 | class FacebookMiddleware(object): 44 | """ Transparently integrate Django accounts with Facebook. 45 | 46 | If the user presents with a valid facebook cookie, then we want them to be 47 | automatically logged in as that user. We rely on the authentication backend 48 | to create the user if it does not exist. 49 | 50 | We do not want to persist the facebook login, so we avoid calling auth.login() 51 | with the rationale that if they log out via fb:login-button we want them to 52 | be logged out of Django also. 53 | 54 | We also want to allow people to log in with other backends, which means we 55 | need to be careful before replacing request.user. 56 | """ 57 | def process_request(self, request): 58 | fb_user = facebook.get_user_from_cookie(request.COOKIES, 59 | settings.FACEBOOK_APP_ID, settings.FACEBOOK_SECRET_KEY) 60 | request.facebook = DjangoFacebook(fb_user) if fb_user else None 61 | 62 | if fb_user and request.user.is_anonymous(): 63 | user = auth.authenticate(fb_uid=fb_user['uid'], fb_object=request.facebook) 64 | if user: 65 | user.last_login = datetime.datetime.now() 66 | user.save() 67 | request.user = user 68 | 69 | return None 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Facebook integration with your Django website 2 | ============================================= 3 | 4 | Installation: 5 | ------------ 6 | Simply add ``django_facebook`` to your INSTALLED_APPS and configure 7 | the following settings: 8 | 9 | FACEBOOK_APP_ID = '' 10 | FACEBOOK_API_KEY = '' 11 | FACEBOOK_SECRET_KEY = '' 12 | 13 | # Custom settings 14 | FACEBOOK_PREPOPULATE_USER_DATA = False 15 | FACEBOOK_EXTENDED_PERMISSIONS = [] # Ex: ['email', 'user_birthday'] 16 | 17 | # Optionally for debugging 18 | FACEBOOK_DEBUG_COOKIE = '' 19 | FACEBOOK_DEBUG_TOKEN = '' 20 | 21 | 22 | Templates: 23 | --------- 24 | A few helpers for using the Javascript SDK can be enabled by adding 25 | this to your base template in the ```` section: 26 | 27 | {% load facebook %} 28 | {% facebook_init %} 29 | {% block facebook_code %}{% endblock %} 30 | {% endfacebook %} 31 | 32 | And this should be added just before your ```` tag: 33 | 34 | {% facebook_load %} 35 | 36 | The ``facebook_load`` template tag inserts the code required to 37 | asynchronously load the facebook javascript SDK. The ``facebook_init`` 38 | tag calls ``FB.init`` with your configured application settings. It is 39 | best to put your facebook related javascript into the ``facebook_code`` 40 | region so that it can be called by the asynchronous handler. 41 | 42 | You may find the ``facebook_perms`` tag useful, which takes the setting 43 | in FACEBOOK_EXTENDED_PERMISSIONS and prints the extended permissions out 44 | in a comma-separated list. 45 | 46 | 48 | 49 | 50 | A helpful debugging page to view the status of your facebook login can 51 | be enabled by adding this to your url configuration: 52 | 53 | (r'^facebook_debug/', direct_to_template, {'template':'facebook_debug.html'}), 54 | 55 | 56 | Once this is in place you are ready to start with the facebook javascript SDK! 57 | 58 | This module also provides all of the tools necessary for working with facebook 59 | on the backend: 60 | 61 | 62 | Middleware: 63 | ---------- 64 | This provides seamless access to the Facebook Graph via request object. 65 | 66 | If a user accesses your site with a valid facebook cookie, your views 67 | will have access to request.facebook.graph and you can begin querying 68 | the graph immediately. For example, to get the users friends: 69 | 70 | def friends(request): 71 | if request.facebook: 72 | friends = request.facebook.graph.get_connections('me', 'friends') 73 | 74 | To use the middleware, simply add this to your MIDDLEWARE_CLASSES: 75 | 'django_facebook.middleware.FacebookMiddleware' 76 | 77 | 78 | ``FacebookDebugCookieMiddleware`` allows you to set a cookie in your settings 79 | file and use this to simulate facebook logins offline. 80 | 81 | ``FacebookDebugTokenMiddleware`` allows you to set a uid and access_token to 82 | force facebook graph availability. 83 | 84 | 85 | Authentication: 86 | -------------- 87 | This provides seamless integration with the Django user system. 88 | 89 | If a user accesses your site with a valid facebook cookie, a user 90 | account is automatically created or retrieved based on the facebook UID. 91 | 92 | To use the backend, add this to your AUTHENTICATION_BACKENDS: 93 | 'django_facebook.auth.FacebookBackend' 94 | 95 | Don't forget to include the default backend if you want to use standard 96 | logins for users as well: 97 | 'django.contrib.auth.backends.ModelBackend' 98 | 99 | 100 | Decorators: 101 | ---------- 102 | ``@facebook_required`` is a decorator which ensures the user is currently 103 | logged in with facebook and has access to the facebook graph. It is a replacement 104 | for ``@login_required`` if you are not using the facebook authentication backend. 105 | --------------------------------------------------------------------------------