Profile page after login
authorSofia Papagiannaki <papagian@gmail.com>
Thu, 24 Nov 2011 12:04:54 +0000 (14:04 +0200)
committerSofia Papagiannaki <papagian@gmail.com>
Thu, 24 Nov 2011 12:04:54 +0000 (14:04 +0200)
- enable/disable by setting FORCE_PROFILE_UPDATE
- db migration add_field_user_is_verified
- change template inheritance

Refs: #1584

22 files changed:
pithos/im/migrations/0002_auto__add_field_user_is_verified.py [new file with mode: 0644]
pithos/im/models.py
pithos/im/target/invitation.py
pithos/im/target/local.py
pithos/im/target/shibboleth.py
pithos/im/target/util.py
pithos/im/templates/admin.html
pithos/im/templates/admin_base.html [new file with mode: 0644]
pithos/im/templates/base.html
pithos/im/templates/index.html
pithos/im/templates/invitations_list.html
pithos/im/templates/local_base.html [deleted file]
pithos/im/templates/local_create.html
pithos/im/templates/reclaim.html
pithos/im/templates/reset.html
pithos/im/templates/users_create.html
pithos/im/templates/users_info.html
pithos/im/templates/users_list.html
pithos/im/templates/users_profile.html [new file with mode: 0644]
pithos/im/urls.py
pithos/im/views.py
pithos/settings.py.dist

diff --git a/pithos/im/migrations/0002_auto__add_field_user_is_verified.py b/pithos/im/migrations/0002_auto__add_field_user_is_verified.py
new file mode 100644 (file)
index 0000000..648739f
--- /dev/null
@@ -0,0 +1,54 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'User.is_verified'
+        db.add_column('im_user', 'is_verified', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'User.is_verified'
+        db.delete_column('im_user', 'is_verified')
+
+
+    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'}),
+            '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'}),
+            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.user': {
+            'Meta': {'object_name': 'User'},
+            'affiliation': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'email': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            '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'}),
+            'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '16'}),
+            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {})
+        }
+    }
+
+    complete_apps = ['im']
index 3513194..58f1a68 100644 (file)
@@ -79,6 +79,8 @@ class User(models.Model):
     created = models.DateTimeField('Creation date')
     updated = models.DateTimeField('Update date')
     
+    is_verified = models.BooleanField('Verified?', default=False)
+    
     @property
     def quota(self):
         return get_quota(self.uniq)
index f55d7de..2901534 100644 (file)
@@ -37,6 +37,8 @@ from datetime import datetime
 
 from django.conf import settings
 from django.http import HttpResponseBadRequest
+from django.core.urlresolvers import reverse
+from django.utils.http import urlencode
 
 from pithos.im.models import Invitation
 from pithos.im.target.util import get_or_create_user, prepare_response
@@ -59,5 +61,12 @@ def login(request):
                                 invitation.realname,
                                 'Invitation',
                                 invitation.inviter.level + 1)
+    
     next = request.GET.get('next')
+    if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
+        profile_url = reverse('pithos.im.views.users_profile', args=(user.id,))
+        next = urlencode({'next': next})
+        profile_url = profile_url + '?' + next
+        return prepare_response(request, user, profile_url)
+    
     return prepare_response(request, user, next, 'renew' in request.GET)
index cb4238f..29b6b4e 100644 (file)
@@ -34,6 +34,8 @@
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
 from django.conf import settings
 from django.template.loader import render_to_string
+from django.core.urlresolvers import reverse
+from django.utils.http import urlencode
 
 from pithos.im.target.util import prepare_response
 from pithos.im.models import User
@@ -62,6 +64,12 @@ def login(request):
         return HttpResponseBadRequest('Unverified account')
     
     next = request.POST.get('next')
+    if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
+        profile_url = reverse('pithos.im.views.users_profile', args=(user.id,))
+        next = urlencode({'next': next})
+        profile_url = profile_url + '?' + next
+        return prepare_response(request, user, profile_url)
+    
     return prepare_response(request, user, next)
 
 def activate(request):
index 1bf95e1..d9c62d7 100644 (file)
@@ -32,6 +32,9 @@
 # or implied, of GRNET S.A.
 
 from django.http import HttpResponseBadRequest
+from django.core.urlresolvers import reverse
+from django.utils.http import urlencode
+from django.conf import settings
 
 from pithos.im.target.util import get_or_create_user, prepare_response
 
@@ -66,7 +69,15 @@ def login(request):
     
     affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
     
+    user = get_or_create_user(eppn, realname, affiliation, 0)
+    next = request.GET.get('next')
+    if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
+        profile_url = reverse('pithos.im.views.users_profile', args=(user.id,))
+        next = urlencode({'next': next})
+        profile_url = profile_url + '?' + next
+        return prepare_response(request, user, profile_url)
+    
     return prepare_response(request,
-                            get_or_create_user(eppn, realname, affiliation, 0),
-                            request.GET.get('next'),
+                            user,
+                            next,
                             'renew' in request.GET)
index 7a9723c..c4816ba 100644 (file)
@@ -80,7 +80,7 @@ def prepare_response(request, user, next='', renew=False):
         # TODO: Avoid redirect loops.
         parts = list(urlsplit(next))
         # Do not pass on user and token if we are on the same server.
-        if request.get_host() != parts[1]:
+        if parts[1] and request.get_host() != parts[1]:
             parts[3] = urlencode({'user': user.uniq, 'token': user.auth_token})
             next = urlunsplit(parts)
     
index 4c025c2..ae3cee4 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "admin_base.html" %}
 
 {% block body %}
 <ul class="unstyled">
diff --git a/pithos/im/templates/admin_base.html b/pithos/im/templates/admin_base.html
new file mode 100644 (file)
index 0000000..21d62ae
--- /dev/null
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+    
+{% block tabs %}
+<ul class="tabs">
+  <li{% ifequal tab "home" %} class="active"{% endifequal %}>
+    <a href="{% url pithos.im.views.admin %}">Home</a>
+  </li>
+  <li{% ifequal tab "users" %} class="active"{% endifequal %}>
+    <a href="{% url pithos.im.views.users_list %}">Users</a>
+  </li>
+  <li{% ifequal tab "invitations" %} class="active"{% endifequal %}>
+    <a href="{% url pithos.im.views.invitations_list %}">Invitations</a>
+  </li>
+</ul>
+{% endblock %}
+
+{% block body %}{% endblock %}
index 40fef24..f43fbaf 100644 (file)
     <div style="padding: 5px 0px 0px 0px">
       <img src="/im/static/banner.png" width="900" height="200">
     </div>
+    {% block title %}{% endblock %}
+    
+    {% if message %}
+    <br />
+    <div class="alert-message.{{ status }}">
+      <p>{{ message }}</p>
+    </div>
+    {% endif %}
 
-    <ul class="tabs">
-      <li{% ifequal tab "home" %} class="active"{% endifequal %}>
-        <a href="{% url pithos.im.views.admin %}">Home</a>
-      </li>
-      <li{% ifequal tab "users" %} class="active"{% endifequal %}>
-        <a href="{% url pithos.im.views.users_list %}">Users</a>
-      </li>
-      <li{% ifequal tab "invitations" %} class="active"{% endifequal %}>
-        <a href="{% url pithos.im.views.invitations_list %}">Invitations</a>
-      </li>
-    </ul>
+    {% block tabs %}{% endblock %}
 
     {% block body %}{% endblock %}
   </div>
index 8df09cd..8d994e3 100644 (file)
@@ -1,4 +1,4 @@
-{% extends 'local_base.html'%}
+{% extends 'base.html'%}
 
 {% block title%}
         <h2>Welcome</h2>
index cb0d1b7..9e662a0 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "admin_base.html" %}
 
 {% load formatters %}
 
diff --git a/pithos/im/templates/local_base.html b/pithos/im/templates/local_base.html
deleted file mode 100644 (file)
index 3b1a157..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8" />
-  <title>{{ title|default:"User Login" }}</title>
-  <link rel="stylesheet" href="/im/static/bootstrap.css">
-  <script src="/im/static/jquery.js"></script>
-  <script src="/im/static/jquery.tablesorter.js"></script>
-  <script src="/im/static/main.js"></script>
-</head>
-<body>
-  <div class="container">
-    <div style="padding: 5px 0px 0px 0px">
-      <img src="/im/static/banner.png" width="900" height="200">
-    </div>
-    {% block title%}{% endblock title%}
-    
-    {% if message %}
-    <br />
-    <div class="alert-message.{{ status }}">
-      <p>{{ message }}</p>
-    </div>
-    {% endif %}
-    
-    {% block body%}{% endblock %}
-  </div>
-</body>
-</html>
index 468e58f..fe77b83 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "local_base.html" %}
+{% extends "base.html" %}
 
 {% block title%}
         <h2>Sign up</h2>
index 9f88582..5b9b20f 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "local_base.html" %}
+{% extends "base.html" %}
 
 {% block title%}
         <h2>Reclaim password</h2>
index c01e2d0..a9a8116 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "local_base.html" %}
+{% extends "base.html" %}
 
 {% block title%}
         <h2>Reset password</h2>
index e40957f..8f5558d 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "admin_base.html" %}
 
 {% block body %}
 
index 16b2f44..0d99d77 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "admin_base.html" %}
 
 {% load formatters %}
 
index 03679e7..c4e1900 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "base.html" %}
+{% extends "admin_base.html" %}
 
 {% load formatters %}
 
diff --git a/pithos/im/templates/users_profile.html b/pithos/im/templates/users_profile.html
new file mode 100644 (file)
index 0000000..d564121
--- /dev/null
@@ -0,0 +1,122 @@
+{% extends "base.html" %}
+
+{% load formatters %}
+
+{% block title%}
+        <h2>User Profile</h2>
+{% endblock title%}
+
+{% block body %}
+
+<form action="{% url pithos.im.views.users_edit user.id %}" method="post">
+  <div class="clearfix">
+    <label for="user-id">ID</label>
+    <div class="input">
+      <span class="uneditable-input" id="user-id">{{ user.id }}</span>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-uniq">Uniq</label>
+    <div class="input">
+        <span class="uneditable-input" id="user-uniq">{{ user.uniq }}</span>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-realname">Real Name</label>
+    <div class="input">
+      <input class="span4" id="user-realname" name="realname" value="{{ user.realname }}" type="text" />
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-admin">Admin</label>
+    <div class="input">
+      <ul class="inputs-list">
+        <li>
+          <label>
+            <input type="checkbox" id="user-admin" name="admin"{% if user.is_admin %} checked{% endif %} disabled="disabled">
+          </label>
+        </li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-affiliation">Affiliation</label>
+    <div class="input">
+      <input class="span4" id="user-affiliation" name="affiliation" value="{{ user.affiliation }}" type="text" />
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-state">State</label>
+    <div class="input">
+      <select class="medium" id="user-state" name="state" disabled="disabled">
+        {% for state in states %}
+        <option{% ifequal state user.state %} selected{% endifequal %}>{{ state }}</option>
+        {% endfor %}
+      </select>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-invitations">Invitations</label>
+    <div class="input">
+        <span class="uneditable-input" id="user-invitations">{{ user.invitations }}</span>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-quota">Quota</label>
+    <div class="input">
+      <div class="input-append">
+        <input class="span2" id="user-quota" name="quota" value="{{ user.quota|GiB }}" type="text" readonly="readonly"/>
+        <span class="add-on">GiB</span>
+      </div>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-token">Token</label>
+    <div class="input">
+        <span class="uneditable-input" id="user-token">{{ user.auth_token }}</span>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="token-created">Token Created</label>
+    <div class="input">
+      <span class="uneditable-input" id="token-created">{{ user.auth_token_created }}</span>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="token-expires">Token Expires</label>
+    <div class="input">
+      <span class="uneditable-input" id="token-expires">{{ user.auth_token_expires }}</span>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-created">Created</label>
+    <div class="input">
+      <span class="uneditable-input" id="user-created">{{ user.created }}</span>
+    </div>
+  </div>
+
+  <div class="clearfix">
+    <label for="user-updated">Updated</label>
+    <div class="input">
+      <span class="uneditable-input" id="user-updated">{{ user.updated }}</span>
+    </div>
+  </div>
+
+  <div class="actions">
+    <input type="hidden" name="next" value="{{ next }}">
+    <button type="submit" class="btn primary">Verify</button>
+  </div>
+
+</form>
+{% endblock body %}
index 4caf977..44a1cf6 100644 (file)
@@ -50,6 +50,9 @@ urlpatterns = patterns('pithos.im.views',
     
     (r'^admin/invitations/?$', 'invitations_list'),
     (r'^admin/invitations/export/?$', 'invitations_export'),
+    
+    (r'^profile/(\d+)/?$', 'users_profile'),
+    (r'^profile/(\d+)/edit/?$', 'users_edit'),
 )
 
 urlpatterns += patterns('pithos.im.target',
index cbc5d6d..ee1e81e 100644 (file)
@@ -76,6 +76,24 @@ def requires_login(func):
         return func(request, *args)
     return wrapper
 
+def requires_my_login(func):
+    @wraps(func)
+    def wrapper(request, *args):
+        print '>', request.user, args
+        if not settings.BYPASS_ADMIN_AUTH:
+            if not request.user:
+                next = urlencode({'next': request.build_absolute_uri()})
+                login_uri = reverse(index) + '?' + next
+                return HttpResponseRedirect(login_uri)
+            else:
+                user = User.objects.get(uniq=request.user)
+                if user.id != int(args[0]):
+                    next = urlencode({'next': request.build_absolute_uri()})
+                    login_uri = reverse(index) + '?' + next
+                    return HttpResponseRedirect(login_uri)
+        return func(request, *args)
+    return wrapper
+
 
 def requires_admin(func):
     @wraps(func)
@@ -455,3 +473,33 @@ def users_create(request):
         user.renew_token()
         user.save()
         return redirect(users_info, user.id)
+
+@requires_my_login
+def users_profile(request, user_id):
+    next = request.GET.get('next')
+    user = User.objects.get(id=user_id)
+    states = [x[0] for x in User.ACCOUNT_STATE]
+    return render_response('users_profile.html',
+                            user=user,
+                            states=states,
+                            next=next)
+
+@requires_my_login
+def users_edit(request, user_id):
+    user = User.objects.get(id=user_id)
+    user.realname = request.POST.get('realname')
+    user.affiliation = request.POST.get('affiliation')
+    user.is_verified = True
+    user.save()
+    next = request.POST.get('next')
+    if next:
+        return redirect(next)
+    
+    status = 'success'
+    message = _('Profile has been updated')
+    html = render_to_string('users_profile.html', {
+            'user': user,
+            'status': status,
+            'message': message})
+    return HttpResponse(html)
+    
index 61855f2..0cc86fe 100644 (file)
@@ -204,6 +204,9 @@ PASSWORD_RESET_TARGET = '%s/im/local/reset/' \
                             '?username=%s' \
                             '&next=%s'
 
+# Force user profile verification
+FORCE_PROFILE_UPDATE = False
+
 # The server is behind a proy (apache and gunicorn setup).
 USE_X_FORWARDED_HOST = False