├── __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 | | request.user | {{ request.user }} |
18 | | request.facebook.uid | {{ request.facebook.uid }} |
19 | | request.facebook.user | {{ request.facebook.user }} |
20 | | request.COOKIES | {{ request.COOKIES }} |
21 | | FB.getLoginStatus | Loading ... |
22 |
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 |
--------------------------------------------------------------------------------