]> git.parisson.com Git - django-social-auth.git/commitdiff
Partial pipeline. Refs #90
authorMatías Aguirre <matiasaguirre@gmail.com>
Mon, 6 Feb 2012 17:16:28 +0000 (15:16 -0200)
committerMatías Aguirre <matiasaguirre@gmail.com>
Mon, 6 Feb 2012 17:16:28 +0000 (15:16 -0200)
13 files changed:
README.rst
doc/pipeline.rst
example/app/pipeline.py [new file with mode: 0644]
example/app/views.py
example/local_settings.py.template
example/templates/form.html [new file with mode: 0644]
example/urls.py
social_auth/backends/__init__.py
social_auth/backends/google.py
social_auth/backends/pipeline/__init__.py
social_auth/backends/pipeline/misc.py [new file with mode: 0644]
social_auth/urls.py
social_auth/views.py

index 226906b2f6d344124361795a43670f8cebc07367..a787846976965ff4b3d828e698f717d62bf107dc 100644 (file)
@@ -374,6 +374,40 @@ If any function returns something else beside a ``dict`` or ``None``, the
 workflow will be cut and the value returned immediately, this is useful to
 return ``HttpReponse`` instances like ``HttpResponseRedirect``.
 
+----------------
+Partial Pipeline
+----------------
+
+It's possible to cut the pipeline process to return to the user asking for more
+data and resume the process later, to accomplish this add the entry
+``social_auth.backends.pipeline.misc.save_status_to_session`` (or a similar
+implementation) to the pipeline setting before any entry that returns an
+``HttpResponse`` instance::
+
+    SOCIAL_AUTH_PIPELINE = (
+        ...
+        social_auth.backends.pipeline.misc.save_status_to_session,
+        app.pipeline.redirect_to_basic_user_data_form
+        ...
+    )
+
+When it's time to resume the process just redirect the user to
+``/complete/<backend>/`` view. By default the pipeline will be resumed in the
+next entry after ``save_status_to_session`` but this can be modified by setting
+the following setting to the import path of the pipeline entry to resume
+processing::
+
+    SOCIAL_AUTH_PIPELINE_RESUME_ENTRY = <a zero based index>
+
+``save_status_to_session`` saves needed data into user session, the key can be
+defined by ``SOCIAL_AUTH_PARTIAL_PIPELINE_KEY`` which default value is
+``partial_pipeline``::
+
+    SOCIAL_AUTH_PARTIAL_PIPELINE_KEY = 'partial_pipeline'
+
+Check the `example application`_ to check a basic usage.
+
+
 ---------------
 Deprecated bits
 ---------------
@@ -899,3 +933,4 @@ Base work is copyrighted by:
 .. _Flickr OAuth: http://www.flickr.com/services/api/
 .. _Flickr App Garden: http://www.flickr.com/services/apps/create/
 .. _danielgtaylor: https://github.com/danielgtaylor
+.. _example application: https://github.com/omab/django-social-auth/blob/master/example/local_settings.py.template#L21
index d8c9f943c84912b04141d9e0386d8fa2cbb23cb3..13cf8c10201da295570de7ed5e57c123b19deda0 100644 (file)
@@ -53,4 +53,38 @@ workflow will be cut and the value returned immediately, this is useful to
 return ``HttpReponse`` instances like ``HttpResponseRedirect``.
 
 
+Partial Pipeline
+----------------
+
+It's possible to cut the pipeline process to return to the user asking for more
+data and resume the process later, to accomplish this add the entry
+``social_auth.backends.pipeline.misc.save_status_to_session`` (or a similar
+implementation) to the pipeline setting before any entry that returns an
+``HttpResponse`` instance::
+
+    SOCIAL_AUTH_PIPELINE = (
+        ...
+        social_auth.backends.pipeline.misc.save_status_to_session,
+        app.pipeline.redirect_to_basic_user_data_form
+        ...
+    )
+
+When it's time to resume the process just redirect the user to
+``/complete/<backend>/`` view. By default the pipeline will be resumed in the
+next entry after ``save_status_to_session`` but this can be modified by setting
+the following setting to the import path of the pipeline entry to resume
+processing::
+
+    SOCIAL_AUTH_PIPELINE_RESUME_ENTRY = <a zero based index>
+
+``save_status_to_session`` saves needed data into user session, the key can be
+defined by ``SOCIAL_AUTH_PARTIAL_PIPELINE_KEY`` which default value is
+``partial_pipeline``::
+
+    SOCIAL_AUTH_PARTIAL_PIPELINE_KEY = 'partial_pipeline'
+
+Check the `example application`_ to check a basic usage.
+
+
 .. _django-social-auth: https://github.com/omab/django-social-auth
+.. _example application: https://github.com/omab/django-social-auth/blob/master/example/local_settings.py.template#L21
diff --git a/example/app/pipeline.py b/example/app/pipeline.py
new file mode 100644 (file)
index 0000000..0d81b17
--- /dev/null
@@ -0,0 +1,14 @@
+from django.http import HttpResponseRedirect
+
+
+def username(request, *args, **kwargs):
+    if kwargs.get('user'):
+        username = kwargs['user'].username
+    else:
+        username = request.session.get('saved_username')
+    return { 'username': username }
+
+
+def redirect_to_form(*args, **kwargs):
+    if not kwargs['request'].session.get('saved_username') and kwargs.get('user') is None:
+        return HttpResponseRedirect('/form/')
index 002e86c7fab29860b15e7a742419bc2c97d08f39..ef457fc8f1406464d20087cf96be18f233f9846d 100644 (file)
@@ -2,10 +2,11 @@ from django.http import HttpResponseRedirect
 from django.contrib.auth import logout as auth_logout
 from django.contrib.auth.decorators import login_required
 from django.template import RequestContext
-from django.shortcuts import render_to_response
+from django.shortcuts import render_to_response, redirect
 from django.contrib.messages.api import get_messages
 
 from social_auth import __version__ as version
+from social_auth.utils import setting
 
 
 def home(request):
@@ -19,8 +20,10 @@ def home(request):
 @login_required
 def done(request):
     """Login complete view, displays user data"""
-    ctx = {'version': version,
-           'last_login': request.session.get('social_auth_last_login_backend')}
+    ctx = {
+        'version': version,
+        'last_login': request.session.get('social_auth_last_login_backend')
+    }
     return render_to_response('done.html', ctx, RequestContext(request))
 
 def error(request):
@@ -34,3 +37,12 @@ def logout(request):
     """Logs out user"""
     auth_logout(request)
     return HttpResponseRedirect('/')
+
+
+def form(request):
+    if request.method == 'POST' and request.POST.get('username'):
+        name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline')
+        request.session['saved_username'] = request.POST['username']
+        backend = request.session[name]['backend']
+        return redirect('socialauth_complete', backend=backend)
+    return render_to_response('form.html', {}, RequestContext(request))
index 6e733b419cdf001006a8a126db451b974aa0bf9c..7ac26354fe4fa86d8bb950923dc347f3d7f62567 100644 (file)
@@ -19,3 +19,15 @@ GITHUB_APP_ID                     = ''
 GITHUB_API_SECRET                 = ''
 FOURSQUARE_CONSUMER_KEY           = ''
 FOURSQUARE_CONSUMER_SECRET        = ''
+
+SOCIAL_AUTH_PIPELINE = (
+    'social_auth.backends.pipeline.social.social_auth_user',
+    'social_auth.backends.pipeline.associate.associate_by_email',
+    'social_auth.backends.pipeline.misc.save_status_to_session',
+    'app.pipeline.redirect_to_form',
+    'app.pipeline.username',
+    'social_auth.backends.pipeline.user.create_user',
+    'social_auth.backends.pipeline.social.associate_user',
+    'social_auth.backends.pipeline.social.load_extra_data',
+    'social_auth.backends.pipeline.user.update_user_details',
+)
diff --git a/example/templates/form.html b/example/templates/form.html
new file mode 100644 (file)
index 0000000..fc6e814
--- /dev/null
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block heading %}User basic form{% endblock %}
+
+{% block content %}
+<form action="" method="post">
+    {% csrf_token %}
+    <p>
+        <label for="id_username">Username</label>
+        <input type="text" name="username" id="id_username" value="" />
+    </p>
+
+    <input type="submit" value="Send" />
+</form>
+{% endblock %}
index bcede4173ae7d1d6c63f18fb46a373658135f6e6..8344d27d21de525b18131312233d0e121b913b03 100644 (file)
@@ -1,7 +1,7 @@
 from django.conf.urls.defaults import patterns, url, include
 from django.contrib import admin
 
-from app.views import home, done, logout, error
+from app.views import home, done, logout, error, form
 
 
 admin.autodiscover()
@@ -11,6 +11,7 @@ urlpatterns = patterns('',
     url(r'^done/$', done, name='done'),
     url(r'^error/$', error, name='error'),
     url(r'^logout/$', logout, name='logout'),
+    url(r'^form/$', form, name='form'),
     url(r'^admin/', include(admin.site.urls)),
     url(r'', include('social_auth.urls')),
 )
index d026475d52a2f4c85b31c87abcd4947981e6804c..0adf4c99f50d1a1938d7f51c351063033affb9f4 100644 (file)
@@ -98,11 +98,22 @@ class SocialAuthBackend(ModelBackend):
             return None
 
         response = kwargs.get('response')
-        details = self.get_user_details(response)
-        uid = self.get_user_id(details, response)
-        out = self.pipeline(PIPELINE, backend=self, uid=uid,
-                            details=details, is_new=False,
+
+        if 'pipeline_index' in kwargs:
+            details = kwargs.pop('details')
+            uid = kwargs.pop('uid')
+            is_new = kwargs.pop('is_new')
+            pipeline = PIPELINE[kwargs['pipeline_index']:]
+        else:
+            details = self.get_user_details(response)
+            uid = self.get_user_id(details, response)
+            is_new = False
+            pipeline = PIPELINE
+
+        out = self.pipeline(pipeline, backend=self, uid=uid,
+                            details=details, is_new=is_new,
                             *args, **kwargs)
+
         if not isinstance(out, dict):
             return out
 
@@ -303,6 +314,11 @@ class BaseAuth(object):
         """Completes loging process, must return user instance"""
         raise NotImplementedError('Implement in subclass')
 
+    def continue_pipeline(self, *args, **kwargs):
+        """Continue previos halted pipeline"""
+        kwargs.update({ self.AUTH_BACKEND.name: True })
+        return authenticate(*args, **kwargs)
+
     def auth_extra_arguments(self):
         """Return extra argumens needed on auth process, setting is per bancked
         and defined by <backend name in uppercase>_AUTH_EXTRA_ARGUMENTS.
index 92e4385b66c533318491b3dfc401faa18bce923c..5786938dc7d7e738f8b1dbd9adf1c8581197d04f 100644 (file)
@@ -84,6 +84,7 @@ class GoogleBackend(OpenIDBackend):
         http://axschema.org/contact/email"""
         return details['email']
 
+
 # Auth classes
 class GoogleAuth(OpenIdAuth):
     """Google OpenID authentication"""
index 6af8113e062b3670e5d662bc3a720f9569e71551..ae04cded401036096ead9ceafdb40305c61a3862 100644 (file)
@@ -10,6 +10,7 @@ import warnings
 from django.conf import settings
 
 from social_auth.models import User
+from social_auth.backends import get_backend, PIPELINE
 
 
 USERNAME = 'username'
diff --git a/social_auth/backends/pipeline/misc.py b/social_auth/backends/pipeline/misc.py
new file mode 100644 (file)
index 0000000..3b30deb
--- /dev/null
@@ -0,0 +1,30 @@
+from social_auth.backends import PIPELINE
+from social_auth.utils import setting
+
+
+PIPELINE_ENTRY = 'social_auth.backends.pipeline.misc.save_status_to_session'
+
+
+def save_status_to_session(request, backend, details, response, uid,
+                           *args, **kwargs):
+    """Saves current social-auth status to session."""
+    next_entry = setting('SOCIAL_AUTH_PIPELINE_RESUME_ENTRY')
+
+    try:
+        if next_entry:
+            idx = PIPELINE.index(next_entry)
+        else:
+            idx = PIPELINE.index(PIPELINE_ENTRY) + 1
+    except ValueError:
+        idx = None
+
+    name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline')
+    request.session[name] = {
+        'backend': backend.name,
+        'uid': uid,
+        'details': details,
+        'response': response,
+        'is_new': kwargs.get('is_new', True),
+        'next_index': idx
+    }
+    request.session.modified = True
index e1bb193f9c52ce66e82feb62d8d45d74d874b109..3d41d3c5a36243a4b2b69644595c69446fff0de0 100644 (file)
@@ -6,12 +6,21 @@ from social_auth.views import auth, complete, associate, associate_complete, \
 
 
 urlpatterns = patterns('',
-    url(r'^login/(?P<backend>[^/]+)/$', auth, name='socialauth_begin'),
-    url(r'^complete/(?P<backend>[^/]+)/$', complete, name='socialauth_complete'),
-    url(r'^associate/(?P<backend>[^/]+)/$', associate, name='socialauth_associate_begin'),
+    # authentication
+    url(r'^login/(?P<backend>[^/]+)/$', auth,
+        name='socialauth_begin'),
+    url(r'^complete/(?P<backend>[^/]+)/$', complete,
+        name='socialauth_complete'),
+
+    # association
+    url(r'^associate/(?P<backend>[^/]+)/$', associate,
+        name='socialauth_associate_begin'),
     url(r'^associate/complete/(?P<backend>[^/]+)/$', associate_complete,
         name='socialauth_associate_complete'),
-    url(r'^disconnect/(?P<backend>[^/]+)/$', disconnect, name='socialauth_disconnect'),
-    url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>[^/]+)/$', disconnect,
-        name='socialauth_disconnect_individual'),
+
+    # disconnection
+    url(r'^disconnect/(?P<backend>[^/]+)/$', disconnect,
+        name='socialauth_disconnect'),
+    url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>[^/]+)/$',
+        disconnect, name='socialauth_disconnect_individual'),
 )
index 48a2446297b8d0c391a1b6c9efe56dff68bad51d..46a9018073f0431e1225c6167e20d8b606928f8a 100644 (file)
@@ -49,6 +49,8 @@ def dsa_view(redirect_name=None):
             try:
                 return func(request, backend, *args, **kwargs)
             except Exception, e:  # some error ocurred
+                if setting('DEBUG'):
+                    raise
                 backend_name = backend.AUTH_BACKEND.name
 
                 logger.error(unicode(e), exc_info=True,
@@ -190,4 +192,18 @@ def auth_complete(request, backend, user=None, *args, **kwargs):
     """Complete auth process. Return authenticated user or None."""
     if user and not user.is_authenticated():
         user = None
-    return backend.auth_complete(user=user, request=request, *args, **kwargs)
+
+    name = setting('SOCIAL_AUTH_PARTIAL_PIPELINE_KEY', 'partial_pipeline')
+    if request.session.get(name):
+        data = request.session.pop(name)
+        request.session.modified = True
+        return backend.continue_pipeline(pipeline_index=data['next_index'],
+                                         user=user,
+                                         request=request,
+                                         uid=data['uid'],
+                                         details=data['details'],
+                                         is_new=data['is_new'],
+                                         response=data['response'],
+                                         *args, **kwargs)
+    else:
+        return backend.auth_complete(user=user, request=request, *args, **kwargs)