signup & register views
authorSofia Papagiannaki <papagian@gmail.com>
Fri, 16 Dec 2011 16:51:32 +0000 (18:51 +0200)
committerSofia Papagiannaki <papagian@gmail.com>
Fri, 16 Dec 2011 16:51:32 +0000 (18:51 +0200)
Ref #1689

14 files changed:
pithos/im/forms.py [new file with mode: 0644]
pithos/im/migrations/0003_auto__add_field_user_provider__add_field_user_openidurl__add_field_inv.py [moved from pithos/im/migrations/0003_auto__add_field_user_provider__del_field_invitation_is_accepted__del_f.py with 86% similarity]
pithos/im/models.py
pithos/im/static/local.png [new file with mode: 0644]
pithos/im/static/openid.png [new file with mode: 0644]
pithos/im/target/local.py
pithos/im/target/util.py
pithos/im/templates/index.html
pithos/im/templates/pending_users.html [new file with mode: 0644]
pithos/im/templates/register.html [new file with mode: 0644]
pithos/im/templates/signup.html [new file with mode: 0644]
pithos/im/templates/users_profile.html
pithos/im/urls.py
pithos/im/views.py

diff --git a/pithos/im/forms.py b/pithos/im/forms.py
new file mode 100644 (file)
index 0000000..6682522
--- /dev/null
@@ -0,0 +1,150 @@
+# Copyright 2011 GRNET S.A. All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+# 
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+# 
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django import forms
+from django.utils.translation import ugettext as _
+from django.conf import settings
+
+from pithos.im.models import User
+
+openid_providers = (
+('Google','https://www.google.com/accounts/o8/id'),
+('Yahoo', 'http://yahoo.com/'),
+('AOL','http://openid.aol.com/%s/'),
+('OpenID', None),
+('MyOpenID','http://%s.myopenid.com/'),
+('LiveJournal', 'http://%s.livejournal.com/'),
+('Flickr', 'http://flickr.com/%s/'),
+('Technorati', 'http://technorati.com/people/technorati/%s/'),
+('Wordpress', 'http://%s.wordpress.com/'),
+('Blogger', 'http://%s.blogspot.com/'),
+('Verisign', 'http://%s.pip.verisignlabs.com/'),
+('Vidoop', 'http://%s.myvidoop.com/'),
+('ClaimID','http://claimid.com/%s')    
+)
+
+class RegisterForm(forms.Form):
+    uniq = forms.CharField(widget=forms.widgets.TextInput())
+    provider = forms.CharField(widget=forms.TextInput(),
+                                label=u'Identity Provider')
+    email = forms.EmailField(widget=forms.TextInput(),
+                             label=_('Email address'))
+    realname = forms.CharField(widget=forms.TextInput(),
+                                label=u'Real Name')
+    
+    def __init__(self, *args, **kwargs):
+        super(forms.Form, self).__init__(*args, **kwargs)
+        
+        #set readonly form fields
+        self.fields['provider'].widget.attrs['readonly'] = True
+    
+    def clean_uniq(self):
+        """
+        Validate that the uniq is alphanumeric and is not already
+        in use.
+        
+        """
+        try:
+            user = User.objects.get(uniq__iexact=self.cleaned_data['uniq'])
+        except User.DoesNotExist:
+            return self.cleaned_data['uniq']
+        raise forms.ValidationError(_("A user with that uniq already exists."))
+
+class ShibbolethRegisterForm(RegisterForm):
+    pass
+
+class TwitterRegisterForm(RegisterForm):
+    pass
+
+class OpenidRegisterForm(RegisterForm):
+    openidurl = forms.ChoiceField(widget=forms.Select,
+                                  choices=((url, l) for l, url in openid_providers))
+
+class LocalRegisterForm(RegisterForm):
+    """ local signup form"""
+    password = forms.CharField(widget=forms.PasswordInput(render_value=False),
+                                label=_('Password'))
+    password2 = forms.CharField(widget=forms.PasswordInput(render_value=False),
+                                label=_('Confirm Password'))
+    
+    def __init__(self, *args, **kwargs):
+        super(LocalRegisterForm, self).__init__(*args, **kwargs)
+    
+    def clean_uniq(self):
+        """
+        Validate that the uniq is alphanumeric and is not already
+        in use.
+        
+        """
+        try:
+            user = User.objects.get(uniq__iexact=self.cleaned_data['uniq'])
+        except User.DoesNotExist:
+            return self.cleaned_data['uniq']
+        raise forms.ValidationError(_("A user with that uniq already exists."))
+    
+    def clean(self):
+        """
+        Verifiy that the values entered into the two password fields
+        match. Note that an error here will end up in
+        ``non_field_errors()`` because it doesn't apply to a single
+        field.
+        
+        """
+        if 'password' in self.cleaned_data and 'password2' in self.cleaned_data:
+            if self.cleaned_data['password'] != self.cleaned_data['password2']:
+                raise forms.ValidationError(_("The two password fields didn't match."))
+        return self.cleaned_data
+
+class InvitedRegisterForm(RegisterForm):
+    inviter = forms.CharField(widget=forms.TextInput(),
+                                label=_('Inviter Real Name'))
+    
+    def __init__(self, *args, **kwargs):
+        super(RegisterForm, self).__init__(*args, **kwargs)
+        
+        #set readonly form fields
+        self.fields['uniq'].widget.attrs['readonly'] = True
+        self.fields['inviter'].widget.attrs['readonly'] = True
+        self.fields['provider'].widget.attrs['provider'] = True
+
+class InvitedLocalRegisterForm(LocalRegisterForm, InvitedRegisterForm):
+    pass
+
+class InvitedOpenidRegisterForm(OpenidRegisterForm, InvitedRegisterForm):
+    pass
+
+class InvitedTwitterRegisterForm(TwitterRegisterForm, InvitedRegisterForm):
+    pass
+
+class InvitedShibbolethRegisterForm(ShibbolethRegisterForm, InvitedRegisterForm):
+    pass
\ No newline at end of file
@@ -11,11 +11,8 @@ class Migration(SchemaMigration):
         # Adding field 'User.provider'
         db.add_column('im_user', 'provider', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
 
-        # Deleting field 'Invitation.is_accepted'
-        db.delete_column('im_invitation', 'is_accepted')
-
-        # Deleting field 'Invitation.accepted'
-        db.delete_column('im_invitation', 'accepted')
+        # Adding field 'User.openidurl'
+        db.add_column('im_user', 'openidurl', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
 
         # Adding field 'Invitation.is_consumed'
         db.add_column('im_invitation', 'is_consumed', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
@@ -29,11 +26,8 @@ class Migration(SchemaMigration):
         # Deleting field 'User.provider'
         db.delete_column('im_user', 'provider')
 
-        # Adding field 'Invitation.is_accepted'
-        db.add_column('im_invitation', 'is_accepted', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
-
-        # Adding field 'Invitation.accepted'
-        db.add_column('im_invitation', 'accepted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)
+        # Deleting field 'User.openidurl'
+        db.delete_column('im_user', 'openidurl')
 
         # Deleting field 'Invitation.is_consumed'
         db.delete_column('im_invitation', 'is_consumed')
@@ -45,11 +39,13 @@ class Migration(SchemaMigration):
     models = {
         'im.invitation': {
             'Meta': {'object_name': 'Invitation'},
+            'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
             'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
             'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
             'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
             'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
             'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.User']"}),
+            'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
             'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
             'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
             'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
@@ -67,6 +63,7 @@ class Migration(SchemaMigration):
             'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
             'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
             'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            'openidurl': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
             'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
             'provider': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
             'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
index cf2e5fa..0fbac27 100644 (file)
@@ -43,6 +43,7 @@ from django.db import models
 
 from pithos.im.interface import get_quota, set_quota
 
+from hashlib import new as newhasher
 
 class User(models.Model):
     ACCOUNT_STATE = (
@@ -83,6 +84,8 @@ class User(models.Model):
     
     is_verified = models.BooleanField('Verified?', default=False)
     
+    openidurl = models.CharField('OpenID url', max_length=255, default='')
+    
     @property
     def quota(self):
         return get_quota(self.uniq)
@@ -93,14 +96,22 @@ class User(models.Model):
     
     @property
     def invitation(self):
-        return Invitation.objects.get(uniq=self.uniq)
-   
+        try:
+            return Invitation.objects.get(uniq=self.uniq)
+        except Invitation.DoesNotExist:
+            return None
+    
     def save(self, update_timestamps=True, **kwargs):
         if update_timestamps:
             if not self.id:
                 self.created = datetime.now()
             self.updated = datetime.now()
+        
         super(User, self).save(**kwargs)
+        
+        #invitation consume
+        if self.invitation and not self.invitation.is_consumed:
+            self.invitation.consume()
     
     def renew_token(self):
         md5 = hashlib.md5()
@@ -122,9 +133,18 @@ class Invitation(models.Model):
     realname = models.CharField('Real name', max_length=255)
     uniq = models.CharField('Unique ID', max_length=255)
     code = models.BigIntegerField('Invitation code', db_index=True)
+    #obsolete: we keep it just for transfering the data
+    is_accepted = models.BooleanField('Accepted?', default=False)
     is_consumed = models.BooleanField('Consumed?', default=False)
     created = models.DateTimeField('Creation date', auto_now_add=True)
+    #obsolete: we keep it just for transfering the data
+    accepted = models.DateTimeField('Acceptance date', null=True, blank=True)
     consumed = models.DateTimeField('Consumption date', null=True, blank=True)
     
+    def consume(self):
+        self.is_consumed = True
+        self.consumed = datetime.now()
+        self.save()
+        
     def __unicode__(self):
         return '%s -> %s [%d]' % (self.inviter, self.uniq, self.code)
diff --git a/pithos/im/static/local.png b/pithos/im/static/local.png
new file mode 100644 (file)
index 0000000..5316a18
Binary files /dev/null and b/pithos/im/static/local.png differ
diff --git a/pithos/im/static/openid.png b/pithos/im/static/openid.png
new file mode 100644 (file)
index 0000000..241df20
Binary files /dev/null and b/pithos/im/static/openid.png differ
index 1f9685b..2417338 100644 (file)
@@ -40,6 +40,8 @@ from pithos.im.models import User
 
 from urllib import unquote
 
+from hashlib import new as newhasher
+
 def login(request):
     username = request.POST.get('username')
     password = request.POST.get('password')
@@ -55,6 +57,10 @@ def login(request):
     except User.DoesNotExist:
         return HttpResponseBadRequest('No such user')
     
+    hasher = newhasher('sha256')
+    hasher.update(password)
+    password = hasher.hexdigest()
+    
     if not password or user.password != password:
         return HttpResponseBadRequest('Wrong password')
     
@@ -62,20 +68,19 @@ def login(request):
         return HttpResponseBadRequest('Unverified account')
     
     next = request.POST.get('next')
-    
     return prepare_response(request, user, next)
-
-#def activate(request):
-#    token = request.GET.get('auth')
-#    next = request.GET.get('next')
-#    try:
-#        user = User.objects.get(auth_token=token)
-#    except User.DoesNotExist:
-#        return HttpResponseBadRequest('No such user')
-#    
-#    user.state = 'ACTIVE'
-#    user.save()
-#    return prepare_response(request, user, next, renew=True)
+    
+def activate(request):
+    token = request.GET.get('auth')
+    next = request.GET.get('next')
+    try:
+        user = User.objects.get(auth_token=token)
+    except User.DoesNotExist:
+        return HttpResponseBadRequest('No such user')
+    
+    user.state = 'ACTIVE'
+    user.save()
+    return prepare_response(request, user, next, renew=True)
 
 def reset_password(request):
     if request.method == 'GET':
index 20c1cdc..a23fed4 100644 (file)
@@ -44,7 +44,6 @@ from django.core.urlresolvers import reverse
 
 from pithos.im.models import User
 
-
 def get_or_create_user(uniq, realname, affiliation, level):
     """Find or register a user into the internal database
        and issue a token for subsequent requests.
@@ -55,7 +54,8 @@ def get_or_create_user(uniq, realname, affiliation, level):
             'realname': realname,
             'affiliation': affiliation,
             'level': level,
-            'invitations': settings.INVITATIONS_PER_LEVEL[level]
+            'invitations': settings.INVITATIONS_PER_LEVEL[level],
+            'state':'PENDING',
         })
     if created:
         user.renew_token()
index 9e5c8ca..7a76e1d 100644 (file)
@@ -26,7 +26,7 @@
             </div>
           </fieldset>
          <div>
-            <a href="{% url pithos.im.views.local_create %}">Sign up</a>
+            <a href="{% url pithos.im.views.register 'local' %}">Sign up</a>
          </div>
          <div>
             <a href="{% url pithos.im.views.reclaim_password %}">Forgot your password?</a>
diff --git a/pithos/im/templates/pending_users.html b/pithos/im/templates/pending_users.html
new file mode 100644 (file)
index 0000000..d5effb3
--- /dev/null
@@ -0,0 +1,71 @@
+{% extends "admin_base.html" %}
+
+{% load formatters %}
+
+{% block body %}
+
+<div class="row">
+  <div class="offset10 span3">
+    <form method="get">
+      <div class="input">
+        <input class="span3" name="filter" type="search" placeholder="search" value="{{ filter }}">
+      </div>
+    </form>
+  </div>
+</div>
+
+<table class="zebra-striped id-sorted">
+  <thead>
+    <tr>
+      <th>ID</th>
+      <th>Uniq</th>
+      <th>Real Name</th>
+      <th>Affiliation</th>
+      <th>Email</th>
+      <th>Inviter</th>
+      <th>Action</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for user in users %}
+    <tr>
+      <td>{{ user.id }}</td>
+      <td>{{ user.uniq }}</td>
+      <td>{{ user.realname }}</td>
+      <td>{{ user.affiliation }}</td>
+      <td>{{ user.email }}</td>
+      <td>{{ user.inviter.realname }}</td>
+      <td>
+        <form action="{% url pithos.im.views.users_activate user.id %}" method="post">
+        <input type="hidden" name="page" value="{{ page }}">
+        <button type="submit" class="btn primary">Activate</button>
+        </form>
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+
+{% if pages|length > 1 %}
+<div class="pagination">
+  <ul>
+    {% if prev %}
+      <li class="prev"><a href="?page={{ prev }}{% if filter %}&filter={{ filter }}{% endif %}">&larr; Previous</a></li>
+    {% else %}
+      <li class="prev disabled"><a href="#">&larr; Previous</a></li>
+    {% endif %}
+
+    {% for p in pages %}
+      <li{% if page == p %} class="active"{% endif %}><a href="?page={{ p }}{% if filter %}&filter={{ filter }}{% endif %}">{{ p }}</a></li>
+    {% endfor %}
+
+    {% if next %}
+      <li class="next"><a href="?page={{ next }}{% if filter %}&filter={{ filter }}{% endif %}">&rarr; Next</a></li>
+    {% else %}
+      <li class="next disabled"><a href="#">&rarr; Next</a></li>
+    {% endif %}
+  </ul>
+</div>
+{% endif %}
+
+<br /><br />
+{% endblock body %}
diff --git a/pithos/im/templates/register.html b/pithos/im/templates/register.html
new file mode 100644 (file)
index 0000000..9f591b5
--- /dev/null
@@ -0,0 +1,18 @@
+{% extends "base.html" %}
+
+{% block title%}
+        <h1>Welcome</h1>{{provider}}
+{% endblock title%}
+
+{% block body %}
+<form action={%url pithos.im.views.register provider%} method="post">
+    {{ formset.as_p }}
+<div>
+    <button type="submit" class="btn primary">Register</button>
+</div>
+<input type="hidden" name="provider" value="{{ provider }}">
+<input type="hidden" name="code" value="{{ code }}">
+<input type="hidden" name="next" value="{{ next }}">
+
+</form>
+{% endblock body %}
\ No newline at end of file
diff --git a/pithos/im/templates/signup.html b/pithos/im/templates/signup.html
new file mode 100644 (file)
index 0000000..c6ebebb
--- /dev/null
@@ -0,0 +1,28 @@
+{% extends "base.html" %}
+
+{% block title%}
+        <h1>Welcome</h1>
+{% endblock title%}
+
+{% block body %}
+<form action={%url pithos.im.views.signup %} method="post">
+    <p>Select an Identity Provider:</p>
+    {% for choice in im_modules%}
+    <div class="row">
+        <div class="span8">
+            <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice }}" />
+            <label for="choice{{ forloop.counter }}"><img src="/im/static/{{ choice }}.png" width="80"></label>
+        </div>
+    </div>
+    {% endfor %}
+<div>
+    {% if inv %}
+    <input type=hidden  name="code" value={{ inv.code }} />
+    {% endif %}
+    <button type="submit" class="btn primary">Signup</button>
+</div>
+<input type="hidden" name="provider" value="{{ provider }}">
+<input type="hidden" name="code" value="{{ code }}">
+<input type="hidden" name="next" value="{{ next }}">
+</form>
+{% endblock body %}
\ No newline at end of file
index fe2745d..fbc73c3 100644 (file)
 
   <div class="actions">
     <input type="hidden" name="next" value="{{ next }}">
+    <input type="hidden" name="auth" value="{{ user.auth_token }}">
     <button type="submit" class="btn primary">Verify</button>
   </div>
 
index 05f32bd..35ffcfe 100644 (file)
@@ -56,13 +56,9 @@ urlpatterns = patterns('pithos.im.views',
     (r'^profile/edit/?$', 'users_edit'),
     
     (r'^signup/?$', 'signup'),
-    (r'^local/create/?$', 'local_create'),
-    (r'^openid/create/?$', 'openid_create'),
-)
-
-
-urlpatterns += patterns('',
-    (r'^account/', include('django_authopenid.urls')),
+    (r'^register/(\w+)?$', 'register'),
+    #(r'^signup/complete/?$', 'signup_complete'),
+    #(r'^local/create/?$', 'local_create'),
 )
 
 urlpatterns += patterns('pithos.im.target',
@@ -82,14 +78,14 @@ if 'local' in settings.IM_MODULES:
     )
     urlpatterns += patterns('pithos.im.target',
         (r'^local/?$', 'local.login'),
-#        (r'^local/activate/?$', 'local.activate'),
+        (r'^local/activate/?$', 'local.activate'),
         (r'^local/reset/?$', 'local.reset_password')
     )
 
-#if 'invitation' in settings.IM_STANDARD_MODULES:
-#    urlpatterns += patterns('pithos.im.views',
-#        (r'^invite/?$', 'invite'),
-#    )
+if settings.INVITATIONS_ENABLED:
+    urlpatterns += patterns('pithos.im.views',
+        (r'^invite/?$', 'invite'),
+    )
 #    urlpatterns += patterns('pithos.im.target',
 #        (r'^login/invitation/?$', 'invitation.login')
 #    )
index e269e12..f41933b 100644 (file)
@@ -35,6 +35,7 @@ import json
 import logging
 import socket
 import csv
+import sys
 
 from datetime import datetime
 from functools import wraps
@@ -47,18 +48,23 @@ from django.core.mail import send_mail
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
 from django.shortcuts import redirect
 from django.template.loader import render_to_string
+from django.shortcuts import render_to_response
 from django.utils.http import urlencode
 from django.utils.translation import ugettext as _
 from django.core.urlresolvers import reverse
-from django import forms
+from django.forms import Form
+from django.forms.formsets import formset_factory
+from openid.consumer.consumer import Consumer, \
+    SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
 
-from django_authopenid.forms import *
+from hashlib import new as newhasher
 
 from urllib import quote
 
+from pithos.im.openid_store import PithosOpenIDStore
 from pithos.im.models import User, Invitation
 from pithos.im.util import isoformat
-
+from pithos.im.forms import *
 
 def render_response(template, tab=None, status=200, **kwargs):
     if tab is None:
@@ -334,78 +340,6 @@ def send_verification(baseurl, user):
     send_mail('Pithos account activation', message, sender, [user.email])
     logging.info('Sent activation %s', user)
 
-def local_create(request):
-    if request.method == 'GET':
-        provider = request.GET.get('provider', None)
-        if not provider:
-            return HttpResponseBadRequest('No provider')
-        code = request.GET.get('code', None)
-        kwargs = {'provider':provider}
-        if code:
-            try:
-                invitation = Invitation.objects.get(code = code)
-                kwargs['inv']=invitation
-            except Invitation.DoesNotExist:
-                return HttpResponseBadRequest('Wrong invitation code')
-        return render_response('local_create.html', **kwargs)
-    elif request.method == 'POST':
-        username = request.POST.get('uniq')
-        realname = request.POST.get('realname')
-        email = request.POST.get('email')
-        password = request.POST.get('password')
-        retype_password = request.POST.get('retype_passwords')
-        status = 'success'
-        cookie_value = None
-        if not username:
-            status = 'error'
-            message = 'No username provided'
-        elif not password:
-            status = 'error'
-            message = 'No password provided'
-        elif not retype_password:
-            status = 'error'
-            message = 'Need to enter password twice'
-        elif password != retype_password:
-            status = 'error'
-            message = 'Passwords do not match'
-        elif not email:
-            status = 'error'
-            message = 'No email provided'
-        
-        if status == 'success':
-            try:
-                user = User.objects.get(uniq=username)
-                status = 'error'
-                message = 'Username is not available'
-            except User.DoesNotExist:
-                user = User()
-                user.uniq = username 
-                user.realname = realname
-                user.email = request.POST.get('email')
-                user.password = request.POST.get('password')
-                user.is_admin = False
-                user.quota = 0
-                user.state = 'ACTIVE' if is_preaccepted(user) else 'PENDING'
-                if user.invitation:
-                    user.invitation.is_consumed = True
-                user.level = 1
-                user.renew_token()
-                try:
-                    send_verification(request.build_absolute_uri('/').rstrip('/'), user)
-                    message = _('Verification sent to %s' % user.email)
-                    user.save()
-                except (SMTPException, socket.error) as e:
-                    print e
-                    status = 'error'
-                    name = 'strerror'
-                    message = getattr(e, name) if hasattr(e, name) else e
-        
-        html = render_to_string('local_create.html', {
-                'status': status,
-                'message': message})
-        response = HttpResponse(html)
-        return response
-
 def send_password(baseurl, user):
     url = settings.PASSWORD_RESET_TARGET % (baseurl,
                                             quote(user.uniq),
@@ -425,7 +359,6 @@ def reclaim_password(request):
         return render_response('reclaim.html')
     elif request.method == 'POST':
         username = request.POST.get('uniq')
-        username = '%s@local' % username
         try:
             user = User.objects.get(uniq=username)
             try:
@@ -554,7 +487,10 @@ def users_create(request):
 @requires_login
 def users_profile(request):
     next = request.GET.get('next')
-    user = User.objects.get(uniq=request.user)
+    try:
+        user = User.objects.get(uniq=request.user)
+    except User.DoesNotExist:
+        user = User.objects.get(auth_token=request.GET.get('auth', None))
     states = [x[0] for x in User.ACCOUNT_STATE]
     return render_response('users_profile.html',
                             user=user,
@@ -563,7 +499,12 @@ def users_profile(request):
 
 @requires_login
 def users_edit(request):
-    user = User.objects.get(uniq=request.user)
+    try:
+        user = User.objects.get(uniq=request.user)
+    except User.DoesNotExist:
+        token = request.POST.get('auth', None)
+        users = User.objects.all()
+        user = User.objects.get(auth_token=token)
     user.realname = request.POST.get('realname')
     user.affiliation = request.POST.get('affiliation')
     user.is_verified = True
@@ -579,44 +520,208 @@ def users_edit(request):
             'status': status,
             'message': message})
     return HttpResponse(html)
+    
+def signup(request):
+    if request.method == 'GET':
+        kwargs = {'im_modules':settings.IM_MODULES,
+                  'next':request.GET.get('next', ''),
+                  'code':request.GET.get('code', '')}
+        return render_response('signup.html', **kwargs)
+    elif request.method == 'POST':
+        provider = request.POST.get('choice')
+        if not provider:
+            return on_failure(_('No provider selected'), template='signup.html')
+        
+        kwargs = {'code':request.POST.get('code', ''),
+                  'next':request.POST.get('next', '')}
+        url = '%s%s?' %(reverse('pithos.im.views.register'), provider)
+        for k,v in kwargs.items():
+            if v:
+                url = '%s%s=%s&' %(url, k, v)
+        return redirect(url)
+
+def render_registration(provider, code='', next=''):
+    initial_data = {'provider':provider}
+    if settings.INVITATIONS_ENABLED and code:
+        try:
+            print '#', type(code), code
+            invitation = Invitation.objects.get(code=code)
+            if invitation.is_consumed:
+                return HttpResponseBadRequest('Invitation has beeen used')
+            initial_data.update({'uniq':invitation.uniq,
+                                 'email':invitation.uniq,
+                                 'realname':invitation.realname})
+            try:
+                inviter = User.objects.get(uniq=invitation.inviter)
+                initial_data['inviter'] = inviter.realname
+            except User.DoesNotExist:
+                pass
+        except Invitation.DoesNotExist:
+            return on_failure(_('Wrong invitation code'), template='register.html')
+    
+    prefix = 'Invited' if code else ''
+    formclassname = '%s%sRegisterForm' %(prefix, provider.capitalize())
+    formclass_ = getattr(sys.modules['pithos.im.forms'], formclassname)
+    RegisterFormSet = formset_factory(formclass_, extra=0)
+    formset = RegisterFormSet(initial=[initial_data])
+    return render_response('register.html',
+                           formset=formset,
+                           next=next,
+                           filter=filter,
+                           code=code)
 
 def is_preaccepted(user):
-    return True if settings.INVITATIONS_ENABLED and user.invitation else False
+    if user.invitation and not user.invitation.is_consumed:
+        return True
+    
+    return False
 
-def signup(request):
+def should_send_verification():
+    if not settings.INVITATIONS_ENABLED:
+        return True    
+    return False
+
+def register(request, provider):
+    print '---', request
+    code = request.GET.get('code')
+    next = request.GET.get('next')
     if request.method == 'GET':
-        kwargs = {'im_modules':settings.IM_MODULES}
-        if settings.INVITATIONS_ENABLED:
-            code = request.GET.get('code')
-            if not code:
-                return HttpResponseBadRequest('No code')
-            try:
-                invitation = Invitation.objects.get(code=code)
-                if invitation.is_consumed:
-                    return HttpResponseBadRequest('Invitation has beeen used')
-            except Invitation.DoesNotExist:
-                return HttpResponseBadRequest('Wrong invitation code')
-            kwargs['inv'] = invitation
-        return render_response('signup.html', **kwargs)    
+        code = request.GET.get('code', '')
+        next = request.GET.get('next', '')
+        if provider not in settings.IM_MODULES:
+            return on_failure(_('Invalid provider'))
+        return render_registration(provider, code, next)
     elif request.method == 'POST':
-        choice = request.POST.get('choice')
-        if not choice:
-            return HttpResponseBadRequest('No provider selected')
+        provider = request.POST.get('form-0-provider')
+        inviter = request.POST.get('form-0-inviter')
+        
+        #instantiate the form
+        prefix = 'Invited' if inviter else ''
+        formclassname = '%sRegisterForm' %(provider.capitalize())
+        formclass_ = getattr(sys.modules['pithos.im.forms'], formclassname)
+        RegisterFormSet = formset_factory(formclass_, extra=0)
+        formset = RegisterFormSet(request.POST)
+        if not formset.is_valid():
+            return render_to_response('register.html',
+                                      {'formset':formset,
+                                       'code':code,
+                                       'next':next}) 
         
-        provider = choice
+        user = User()
+        for form in formset.forms:
+            for field in form.fields:
+                if hasattr(user, field):
+                    setattr(user, field, form.cleaned_data[field])
+            break
         
-        code = request.POST.get('code')
+        if user.openidurl:
+            redirect_url = reverse('pithos.im.views.create')
+            return ask_openid(request, 
+                        user.openidurl,
+                        redirect_url,
+                        'signup')
         
-        if provider == 'local':
-            url = reverse('pithos.im.views.local.create')
-            if settings.INVITATIONS_ENABLED and code:
-                url = '%s?code=%s&provider=%s' % (url, code, provider)
-            return redirect(url)
-        elif provider == 'twitter':
-            url = reverse('pithos.im.views.openid_create')
+        #save hashed password
+        if user.password:
+            hasher = newhasher('sha256')
+            hasher.update(user.password)
+            user.password = hasher.hexdigest() 
+            
+        user.renew_token()
+        
+        if is_preaccepted(user):
+            user.state = 'ACTIVE'
+            user.save()
+            url = reverse('pithos.im.views.index')
             return redirect(url)
+        
+        status = 'success'
+        if should_send_verification():
+            try:
+                send_verification(request.build_absolute_uri('/').rstrip('/'), user)
+                message = _('Verification sent to %s' % user.email)
+                user.save()
+            except (SMTPException, socket.error) as e:
+                status = 'error'
+                name = 'strerror'
+                message = getattr(e, name) if hasattr(e, name) else e
+        else:
+            user.save()
+            message = _('Registration completed. You will receive an email upon your account\'s activation')
+        
+        return info(status, message)
+
+#def discover_extensions(openid_url):
+#    service = discover(openid_url)
+#    use_ax = False
+#    use_sreg = False
+#    for endpoint in service[1]:
+#        if not use_sreg:
+#            use_sreg = sreg.supportsSReg(endpoint)
+#        if not use_ax:
+#            use_ax = endpoint.usesExtension("http://openid.net/srv/ax/1.0")
+#        if use_ax and use_sreg: break
+#    return use_ax, use_sreg
+#
+#def ask_openid(request, openid_url, redirect_to, on_failure=None):
+#    """ basic function to ask openid and return response """
+#    on_failure = on_failure or signin_failure
+#    sreg_req = None
+#    ax_req = None
+#    
+#    trust_root = getattr(
+#        settings, 'OPENID_TRUST_ROOT', request.build_absolute_uri() + '/'
+#    )
+#    request.session = {}
+#    consumer = Consumer(request.session, PithosOpenIDStore())
+#    try:
+#        auth_request = consumer.begin(openid_url)
+#    except DiscoveryFailure:
+#        msg = _("The OpenID %s was invalid") % openid_url
+#        return on_failure(request, msg)
+#    
+#     get capabilities
+#    use_ax, use_sreg = discover_extensions(openid_url)
+#    if use_sreg:
+#         set sreg extension
+#         we always ask for nickname and email
+#        sreg_attrs = getattr(settings, 'OPENID_SREG', {})
+#        sreg_attrs.update({ "optional": ['nickname', 'email'] })
+#        sreg_req = sreg.SRegRequest(**sreg_attrs)
+#    if use_ax:
+#         set ax extension
+#         we always ask for nickname and email
+#        ax_req = ax.FetchRequest()
+#        ax_req.add(ax.AttrInfo('http://schema.openid.net/contact/email', 
+#                                alias='email', required=True))
+#        ax_req.add(ax.AttrInfo('http://schema.openid.net/namePerson/friendly', 
+#                                alias='nickname', required=True))
+#                      
+#         add custom ax attrs          
+#        ax_attrs = getattr(settings, 'OPENID_AX', [])
+#        for attr in ax_attrs:
+#            if len(attr) == 2:
+#                ax_req.add(ax.AttrInfo(attr[0], required=alias[1]))
+#            else:
+#                ax_req.add(ax.AttrInfo(attr[0]))
+#       
+#    if sreg_req is not None:
+#        auth_request.addExtension(sreg_req)
+#    if ax_req is not None:
+#        auth_request.addExtension(ax_req)
+#    
+#    redirect_url = auth_request.redirectURL(trust_root, redirect_to)
+#    return HttpResponseRedirect(redirect_url)
+
+def info(status, message, template='base.html'):
+    html = render_to_string(template, {
+            'status': status,
+            'message': message})
+    response = HttpResponse(html)
+    return response
 
-def openid_create(request):
-    form1 = OpenidSigninForm()
-    kwargs = {'form1':form1}  
-    return render_response('openid_create.html') 
\ No newline at end of file
+def on_success(message, template='base.html'):
+    return info('success', message)
+    
+def on_failure(message, template='base.html'):
+    return info('error', message)
\ No newline at end of file