restructure app
authorSofia Papagiannaki <papagian@gmail.com>
Tue, 17 Jan 2012 12:20:41 +0000 (14:20 +0200)
committerSofia Papagiannaki <papagian@gmail.com>
Tue, 17 Jan 2012 12:20:41 +0000 (14:20 +0200)
- extend django user model
- pass template_name and extra_context in views
- incorporate context processors
- use different backends for invitations and simple workflow

Refs: #1823

42 files changed:
astakos/im/admin/__init__.py [moved from astakos/im/migrations/__init__.py with 100% similarity]
astakos/im/admin/templates/admin.html [moved from astakos/im/templates/admin.html with 100% similarity]
astakos/im/admin/templates/admin_base.html [moved from astakos/im/templates/admin_base.html with 58% similarity]
astakos/im/admin/templates/invitations_list.html [moved from astakos/im/templates/invitations_list.html with 88% similarity]
astakos/im/admin/templates/pending_users.html [moved from astakos/im/templates/pending_users.html with 91% similarity]
astakos/im/admin/templates/users_create.html [moved from astakos/im/templates/users_create.html with 61% similarity]
astakos/im/admin/templates/users_info.html [moved from astakos/im/templates/users_info.html with 70% similarity]
astakos/im/admin/templates/users_list.html [moved from astakos/im/templates/users_list.html with 75% similarity]
astakos/im/admin/urls.py [new file with mode: 0644]
astakos/im/admin/views.py [new file with mode: 0644]
astakos/im/api.py
astakos/im/auth_backends.py [new file with mode: 0644]
astakos/im/backends/__init__.py [new file with mode: 0644]
astakos/im/backends/invitations.py [new file with mode: 0644]
astakos/im/backends/simple.py [new file with mode: 0644]
astakos/im/context_processors.py [new file with mode: 0644]
astakos/im/fixtures/admin_user.json
astakos/im/fixtures/auth_test_data.json
astakos/im/forms.py
astakos/im/migrations/0001_initial.py [deleted file]
astakos/im/migrations/0002_auto__add_field_user_is_verified.py [deleted file]
astakos/im/migrations/0003_auto__add_field_user_provider__add_field_user_openidurl__add_field_inv.py [deleted file]
astakos/im/models.py
astakos/im/target/invitation.py
astakos/im/target/local.py
astakos/im/target/shibboleth.py
astakos/im/target/twitter.py
astakos/im/target/util.py
astakos/im/templates/index.html
astakos/im/templates/local_create.html [deleted file]
astakos/im/templates/reclaim.html
astakos/im/templates/register.html
astakos/im/templates/reset.html
astakos/im/templates/signup.html
astakos/im/templates/users_profile.html
astakos/im/templates/welcome.txt [new file with mode: 0644]
astakos/im/urls.py
astakos/im/util.py
astakos/im/views.py
astakos/middleware/auth.py
astakos/settings.d/00-apps.conf
astakos/settings.d/20-im.conf

similarity index 58%
rename from astakos/im/templates/admin_base.html
rename to astakos/im/admin/templates/admin_base.html
index 3cc6273..5fcb957 100644 (file)
@@ -3,16 +3,16 @@
 {% block tabs %}
 <ul class="tabs">
   <li{% ifequal tab "home" %} class="active"{% endifequal %}>
-    <a href="{% url astakos.im.views.admin %}">Home</a>
+    <a href="{% url astakos.im.admin.views.admin %}">Home</a>
   </li>
   <li{% ifequal tab "users" %} class="active"{% endifequal %}>
-    <a href="{% url astakos.im.views.users_list %}">Users</a>
+    <a href="{% url astakos.im.admin.views.users_list %}">Users</a>
   </li>
   <li{% ifequal tab "pending" %} class="active"{% endifequal %}>
-    <a href="{% url astakos.im.views.pending_users %}">Pending Users</a>
+    <a href="{% url astakos.im.admin.views.pending_users %}">Pending Users</a>
   </li>
   <li{% ifequal tab "invitations" %} class="active"{% endifequal %}>
-    <a href="{% url astakos.im.views.invitations_list %}">Invitations</a>
+    <a href="{% url astakos.im.admin.views.invitations_list %}">Invitations</a>
   </li>
 </ul>
 {% endblock %}
   <thead>
     <tr>
       <th>ID</th>
-      <th>Uniq</th>
+      <th>Username</th>
       <th>Real Name</th>
       <th>Code</th>
-      <th>Inviter Uniq</th>
+      <th>Inviter username</th>
       <th>Inviter Real Name</th>
       <th>Is consumed</th>
       <th>Created</th>
     {% for inv in invitations %}
     <tr>
       <td>{{ inv.id }}</td>
-      <td>{{ inv.uniq }}</td>
+      <td>{{ inv.username }}</td>
       <td>{{ inv.realname }}</td>
       <td>{{ inv.code }}</td>
-      <td>{{ inv.inviter.uniq }}</td>
+      <td>{{ inv.inviter.username }}</td>
       <td>{{ inv.inviter.realname }}</td>
       <td>{{ inv.is_consumed }}</td>
       <td>{{ inv.created }}</td>
@@ -67,7 +67,7 @@
 </div>
 {% endif %}
 
-<a class="btn success" href="{% url astakos.im.views.invitations_export %}">Export</a>
+<a class="btn success" href="{% url astakos.im.admin.views.invitations_export %}">Export</a>
 
 <br /><br />
 {% endblock body %}
similarity index 91%
rename from astakos/im/templates/pending_users.html
rename to astakos/im/admin/templates/pending_users.html
index ac7e5b4..fcd1a73 100644 (file)
@@ -18,7 +18,7 @@
   <thead>
     <tr>
       <th>ID</th>
-      <th>Uniq</th>
+      <th>Username</th>
       <th>Real Name</th>
       <th>Affiliation</th>
       <th>Email</th>
     {% for user in users %}
     <tr>
       <td>{{ user.id }}</td>
-      <td>{{ user.uniq }}</td>
+      <td>{{ user.username }}</td>
       <td>{{ user.realname }}</td>
       <td>{{ user.affiliation }}</td>
       <td>{{ user.email }}</td>
       <td>{{ user.inviter.realname }}</td>
       <td>
-        <form action="{% url astakos.im.views.users_activate user.id %}" method="post">
+        <form action="{% url astakos.im.admin.views.users_activate user.id %}" method="post">
         <input type="hidden" name="page" value="{{ page }}">
         <button type="submit" class="btn primary">Activate</button>
         </form>
similarity index 61%
rename from astakos/im/templates/users_create.html
rename to astakos/im/admin/templates/users_create.html
index 85f43bc..079e972 100644 (file)
@@ -2,18 +2,32 @@
 
 {% block body %}
 
-<form action="{% url astakos.im.views.users_create %}" method="post">
+<form action="{% url astakos.im.admin.views.users_create %}" method="post">
   <div class="clearfix">
-    <label for="user-uniq">Uniq</label>
+    <label for="user-username">Username</label>
     <div class="input">
-      <input class="span4" id="user-uniq" name="uniq" type="text" />
+      <input class="span4" id="user-username" name="username" type="text" />
+    </div>
+  </div>
+  
+  <div class="clearfix">
+    <label for="user-email">Email</label>
+    <div class="input">
+      <input class="span4" id="user-email" name="email" type="text" />
     </div>
   </div>
 
   <div class="clearfix">
-    <label for="user-realname">Real Name</label>
+    <label for="user-first-name">First Name</label>
+    <div class="input">
+      <input class="span4" id="user-first-name" name="first_name" type="text" />
+    </div>
+  </div>
+  
+  <div class="clearfix">
+    <label for="user-last-name">Last Name</label>
     <div class="input">
-      <input class="span4" id="user-realname" name="realname" type="text" />
+      <input class="span4" id="user-last-name" name="last_name" type="text" />
     </div>
   </div>
 
similarity index 70%
rename from astakos/im/templates/users_info.html
rename to astakos/im/admin/templates/users_info.html
index 832c2d2..fc89252 100644 (file)
@@ -4,7 +4,7 @@
 
 {% block body %}
 
-<form action="{% url astakos.im.views.users_modify user.id %}" method="post">
+<form action="{% url astakos.im.admin.views.users_modify user.id %}" method="post">
   <div class="clearfix">
     <label for="user-id">ID</label>
     <div class="input">
   </div>
 
   <div class="clearfix">
-    <label for="user-uniq">Uniq</label>
+    <label for="user-username">Username</label>
     <div class="input">
-      <input class="span4" id="user-uniq" name="uniq" value="{{ user.uniq }}" type="text" />
+      <input class="span4" id="user-username" name="username" value="{{ user.username }}" type="text" />
     </div>
   </div>
 
   <div class="clearfix">
-    <label for="user-realname">Real Name</label>
+    <label for="user-first-name">First Name</label>
     <div class="input">
-      <input class="span4" id="user-realname" name="realname" value="{{ user.realname }}" type="text" />
+      <input class="span4" id="user-first-name" name="first_name" value="{{ user.first_name }}" type="text" />
+    </div>
+  </div>
+  
+  <div class="clearfix">
+    <label for="user-last-name">Last Name</label>
+    <div class="input">
+      <input class="span4" id="user-last-name" name="last_name" value="{{ user.last_name }}" type="text" />
     </div>
   </div>
 
@@ -32,7 +39,7 @@
       <ul class="inputs-list">
         <li>
           <label>
-            <input type="checkbox" id="user-admin" name="admin"{% if user.is_admin %} checked{% endif %}>
+            <input type="checkbox" id="user-admin" name="admin"{% if user.is_superuser %} checked{% endif %}>
           </label>
         </li>
       </ul>
   </div>
 
   <div class="clearfix">
-    <label for="user-state">State</label>
+    <label for="user-is-active">Is active?</label>
     <div class="input">
-      <select class="medium" id="user-state" name="state">
-        {% for state in states %}
-        <option{% ifequal state user.state %} selected{% endifequal %}>{{ state }}</option>
-        {% endfor %}
-      </select>
+      <ul class="inputs-list">
+        <li>
+          <label>
+            <input type="checkbox" id="user-is-active" name="is_active"{% if user.is_active %} checked{% endif %}>
+          </label>
+        </li>
+      </ul>
     </div>
   </div>
 
   </div>
 
   <div class="clearfix">
-    <label for="user-created">Created</label>
+    <label for="user-date-joined">Created</label>
     <div class="input">
-      <span class="uneditable-input" id="user-created">{{ user.created }}</span>
+      <span class="uneditable-input" id="user-date-joined">{{ user.date_joined }}</span>
     </div>
   </div>
 
     <button type="submit" class="btn primary">Save Changes</button>
     <button type="reset" class="btn">Reset</button>
         &nbsp;&nbsp;
-    <a class="btn danger needs-confirm" href="{% url astakos.im.views.users_delete user.id %}">Delete User</a>
+    <a class="btn danger needs-confirm" href="{% url astakos.im.admin.views.users_delete user.id %}">Delete User</a>
   </div>
 
   <div class="alert-message block-message error">
     <p><strong>WARNING:</strong> Are you sure you want to delete this user?</p>
     <div class="alert-actions">
-      <a class="btn danger" href="{% url astakos.im.views.users_delete user.id %}">Delete</a>
+      <a class="btn danger" href="{% url astakos.im.admin.views.users_delete user.id %}">Delete</a>
       <a class="btn alert-close">Cancel</a>
     </div>
   </div>
similarity index 75%
rename from astakos/im/templates/users_list.html
rename to astakos/im/admin/templates/users_list.html
index 6e10081..17bf3cc 100644 (file)
   <thead>
     <tr>
       <th>ID</th>
-      <th>Uniq</th>
+      <th>Username</th>
       <th>Real Name</th>
       <th>Admin</th>
       <th>Affiliation</th>
-      <th>State</th>
+      <th>Is active?</th>
       <th>Quota</th>
       <th>Updated</th>
     </tr>
   <tbody>
     {% for user in users %}
     <tr>
-      <td><a href="{% url astakos.im.views.users_info user.id %}">{{ user.id }}</a></td>
-      <td><a href="{% url astakos.im.views.users_info user.id %}">{{ user.uniq }}</a></td>
+      <td><a href="{% url astakos.im.admin.views.users_info user.id %}">{{ user.id }}</a></td>
+      <td><a href="{% url astakos.im.admin.views.users_info user.id %}">{{ user.username }}</a></td>
       <td>{{ user.realname }}</td>
-      <td>{{ user.is_admin }}</td>
+      <td>{{ user.is_superuser }}</td>
       <td>{{ user.affiliation }}</td>
-      <td>{{ user.state }}</td>
+      <td>{{ user.is_active }}</td>
       <td>{{ user.quota|GiB }} GiB</td>
       <td>{{ user.updated }}</td>
     </tr>
@@ -65,8 +65,8 @@
 </div>
 {% endif %}
 
-<a class="btn success" href="{% url astakos.im.views.users_create %}">Create a user</a>
-<a class="btn success" href="{% url astakos.im.views.users_export %}">Export</a>
+<a class="btn success" href="{% url astakos.im.admin.views.users_create %}">Create a user</a>
+<a class="btn success" href="{% url astakos.im.admin.views.users_export %}">Export</a>
 
 <br /><br />
 {% endblock body %}
diff --git a/astakos/im/admin/urls.py b/astakos/im/admin/urls.py
new file mode 100644 (file)
index 0000000..c580104
--- /dev/null
@@ -0,0 +1,50 @@
+# 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.conf.urls.defaults import patterns
+
+urlpatterns = patterns('astakos.im.admin.views',
+    (r'^$', 'admin'),
+    
+    (r'^users/?$', 'users_list'),
+    (r'^users/(\d+)/?$', 'users_info'),
+    (r'^users/create$', 'users_create'),
+    (r'^users/(\d+)/modify/?$', 'users_modify'),
+    (r'^users/(\d+)/delete/?$', 'users_delete'),
+    (r'^users/export/?$', 'users_export'),
+    (r'^users/pending/?$', 'pending_users'),
+    (r'^users/activate/(\d+)/?$', 'users_activate'),
+    
+    (r'^invitations/?$', 'invitations_list'),
+    (r'^invitations/export/?$', 'invitations_export'),
+)
\ No newline at end of file
diff --git a/astakos/im/admin/views.py b/astakos/im/admin/views.py
new file mode 100644 (file)
index 0000000..a8d0b83
--- /dev/null
@@ -0,0 +1,345 @@
+# 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.
+
+import json
+import logging
+import socket
+import csv
+import sys
+
+from datetime import datetime
+from functools import wraps
+from math import ceil
+from random import randint
+from smtplib import SMTPException
+from hashlib import new as newhasher
+from urllib import quote
+
+from django.conf import settings
+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.utils.http import urlencode
+from django.utils.translation import ugettext as _
+from django.core.urlresolvers import reverse
+
+#from astakos.im.openid_store import PithosOpenIDStore
+from astakos.im.models import AstakosUser, Invitation
+from astakos.im.util import isoformat, get_or_create_user, get_context
+from astakos.im.forms import *
+from astakos.im.backends import get_backend
+from astakos.im.views import render_response, index
+
+def requires_admin(func):
+    @wraps(func)
+    def wrapper(request, *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)
+            if not request.user.is_superuser:
+                return HttpResponse('Forbidden', status=403)
+        return func(request, *args)
+    return wrapper
+
+@requires_admin
+def admin(request, template_name='admin.html', extra_context={}):
+    stats = {}
+    stats['users'] = AstakosUser.objects.count()
+    stats['pending'] = AstakosUser.objects.filter(is_active = False).count()
+    
+    invitations = Invitation.objects.all()
+    stats['invitations'] = invitations.count()
+    stats['invitations_consumed'] = invitations.filter(is_consumed=True).count()
+    
+    kwargs = {'tab': 'home', 'stats': stats}
+    context = get_context(request, extra_context, **kwargs)
+    return render_response(template_name, context_instance = context)
+
+@requires_admin
+def users_list(request, template_name='users_list.html', extra_context={}):
+    users = AstakosUser.objects.order_by('id')
+    
+    filter = request.GET.get('filter', '')
+    if filter:
+        if filter.startswith('-'):
+            users = users.exclude(username__icontains=filter[1:])
+        else:
+            users = users.filter(username__icontains=filter)
+    
+    try:
+        page = int(request.GET.get('page', 1))
+    except ValueError:
+        page = 1
+    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+    limit = offset + settings.ADMIN_PAGE_LIMIT
+    
+    npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
+    prev = page - 1 if page > 1 else None
+    next = page + 1 if page < npages else None
+    
+    kwargs = {'users':users[offset:limit],
+              'filter':filter,
+              'pages':range(1, npages + 1),
+              'prev':prev,
+              'next':next}
+    context = get_context(request, extra_context, **kwargs)
+    return render_response(template_name, context_instance = context)
+
+@requires_admin
+def users_info(request, user_id, template_name='users_info.html', extra_context={}):
+    if not extra_context:
+        extra_context = {}
+    kwargs = {'user':AstakosUser.objects.get(id=user_id)}
+    context = get_context(request, extra_context, **kwargs)
+    return render_response(template_name, context_instance = context)
+
+@requires_admin
+def users_modify(request, user_id):
+    user = AstakosUser.objects.get(id=user_id)
+    user.username = request.POST.get('username')
+    user.first_name = request.POST.get('first_name')
+    user.last_name = request.POST.get('last_name')
+    user.is_superuser = True if request.POST.get('admin') else False
+    user.affiliation = request.POST.get('affiliation')
+    user.is_active = True if request.POST.get('is_active') else False
+    user.invitations = int(request.POST.get('invitations') or 0)
+    #user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
+    user.auth_token = request.POST.get('auth_token')
+    try:
+        auth_token_expires = request.POST.get('auth_token_expires')
+        d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ')
+        user.auth_token_expires = d
+    except ValueError:
+        pass
+    user.save()
+    return redirect(users_info, user.id)
+
+@requires_admin
+def users_delete(request, user_id):
+    user = AstakosUser.objects.get(id=user_id)
+    user.delete()
+    return redirect(users_list)
+
+@requires_admin
+def pending_users(request, template_name='pending_users.html', extra_context={}):
+    users = AstakosUser.objects.order_by('id')
+    
+    users = users.filter(is_active = False)
+    
+    filter = request.GET.get('filter', '')
+    if filter:
+        if filter.startswith('-'):
+            users = users.exclude(username__icontains=filter[1:])
+        else:
+            users = users.filter(username__icontains=filter)
+    
+    try:
+        page = int(request.GET.get('page', 1))
+    except ValueError:
+        page = 1
+    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+    limit = offset + settings.ADMIN_PAGE_LIMIT
+    
+    npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
+    prev = page - 1 if page > 1 else None
+    next = page + 1 if page < npages else None
+    kwargs = {'users':users[offset:limit],
+              'filter':filter,
+              'pages':range(1, npages + 1),
+              'page':page,
+              'prev':prev,
+              'next':next}
+    return render_response(template_name,
+                            context_instance = get_context(request, extra_context, **kwargs))
+
+def _send_greeting(baseurl, user):
+    url = reverse('astakos.im.views.index')
+    subject = _('Welcome to %s' %settings.SERVICE_NAME)
+    message = render_to_string('welcome.txt', {
+                'user': user,
+                'url': url,
+                'baseurl': baseurl,
+                'service': settings.SERVICE_NAME,
+                'support': settings.DEFAULT_CONTACT_EMAIL})
+    sender = settings.DEFAULT_FROM_EMAIL
+    send_mail(subject, message, sender, [user.email])
+    logging.info('Sent greeting %s', user)
+
+@requires_admin
+def users_activate(request, user_id, template_name='pending_users.html', extra_context={}):
+    user = AstakosUser.objects.get(id=user_id)
+    user.is_active = True
+    status = 'success'
+    try:
+        _send_greeting(request.build_absolute_uri('/').rstrip('/'), user)
+        message = _('Greeting 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
+    
+    users = AstakosUser.objects.order_by('id')
+    users = users.filter(is_active = False)
+    
+    try:
+        page = int(request.POST.get('page', 1))
+    except ValueError:
+        page = 1
+    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+    limit = offset + settings.ADMIN_PAGE_LIMIT
+    
+    npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
+    prev = page - 1 if page > 1 else None
+    next = page + 1 if page < npages else None
+    kwargs = {'users':users[offset:limit],
+              'filter':'',
+              'pages':range(1, npages + 1),
+              'page':page,
+              'prev':prev,
+              'next':next,
+              'message':message}
+    return render_response(template_name,
+                           context_instance = get_context(request, extra_context, **kwargs))
+
+@requires_admin
+def invitations_list(request, template_name='invitations_list.html', extra_context={}):
+    invitations = Invitation.objects.order_by('id')
+    
+    filter = request.GET.get('filter', '')
+    if filter:
+        if filter.startswith('-'):
+            invitations = invitations.exclude(username__icontains=filter[1:])
+        else:
+            invitations = invitations.filter(username__icontains=filter)
+    
+    try:
+        page = int(request.GET.get('page', 1))
+    except ValueError:
+        page = 1
+    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+    limit = offset + settings.ADMIN_PAGE_LIMIT
+    
+    npages = int(ceil(1.0 * invitations.count() / settings.ADMIN_PAGE_LIMIT))
+    prev = page - 1 if page > 1 else None
+    next = page + 1 if page < npages else None
+    kwargs = {'invitations':invitations[offset:limit],
+              'filter':filter,
+              'pages':range(1, npages + 1),
+              'page':page,
+              'prev':prev,
+              'next':next}
+    return render_response(template_name,
+                           context_instance = get_context(request, extra_context, **kwargs))
+
+@requires_admin
+def invitations_export(request):
+    # Create the HttpResponse object with the appropriate CSV header.
+    response = HttpResponse(mimetype='text/csv')
+    response['Content-Disposition'] = 'attachment; filename=invitations.csv'
+
+    writer = csv.writer(response)
+    writer.writerow(['ID',
+                     'Username',
+                     'Real Name',
+                     'Code',
+                     'Inviter username',
+                     'Inviter Real Name',
+                     'Is_accepted',
+                     'Created',
+                     'Accepted',])
+    invitations = Invitation.objects.order_by('id')
+    for inv in invitations:
+        
+        writer.writerow([inv.id,
+                         inv.username.encode("utf-8"),
+                         inv.realname.encode("utf-8"),
+                         inv.code,
+                         inv.inviter.username.encode("utf-8"),
+                         inv.inviter.realname.encode("utf-8"),
+                         inv.is_accepted,
+                         inv.created,
+                         inv.accepted])
+
+    return response
+
+
+@requires_admin
+def users_export(request):
+    # Create the HttpResponse object with the appropriate CSV header.
+    response = HttpResponse(mimetype='text/csv')
+    response['Content-Disposition'] = 'attachment; filename=users.csv'
+
+    writer = csv.writer(response)
+    writer.writerow(['ID',
+                     'Username',
+                     'Real Name',
+                     'Admin',
+                     'Affiliation',
+                     'Is active?',
+                     'Quota (GiB)',
+                     'Updated',])
+    users = AstakosUser.objects.order_by('id')
+    for u in users:
+        writer.writerow([u.id,
+                         u.username.encode("utf-8"),
+                         u.realname.encode("utf-8"),
+                         u.is_superuser,
+                         u.affiliation.encode("utf-8"),
+                         u.is_active,
+                         u.quota,
+                         u.updated])
+
+    return response
+
+@requires_admin
+def users_create(request, template_name='users_create.html', extra_context={}):
+    if request.method == 'GET':
+        return render_response(template_name,
+                               context_instance=get_context(request, extra_context))
+    if request.method == 'POST':
+        user = AstakosUser()
+        user.username = request.POST.get('username')
+        user.email = request.POST.get('email')
+        user.first_name = request.POST.get('first_name')
+        user.last_name = request.POST.get('last_name')
+        user.is_superuser = True if request.POST.get('admin') else False
+        user.affiliation = request.POST.get('affiliation')
+        user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
+        user.renew_token()
+        user.provider = 'local'
+        user.save()
+        return redirect(users_info, user.id)
index 69bb374..118d46c 100644 (file)
@@ -38,7 +38,7 @@ from django.http import HttpResponse
 from django.utils import simplejson as json
 
 from astakos.im.faults import BadRequest, Unauthorized, ServiceUnavailable
-from astakos.im.models import User
+from astakos.im.models import AstakosUser
 
 import datetime
 
@@ -68,12 +68,12 @@ def authenticate(request):
             return render_fault(request, BadRequest('Missing X-Auth-Token'))
         
         try:
-            user = User.objects.get(auth_token=x_auth_token)
-        except User.DoesNotExist, e:
+            user = AstakosUser.objects.get(auth_token=x_auth_token)
+        except AstakosUser.DoesNotExist, e:
             return render_fault(request, Unauthorized('Invalid X-Auth-Token')) 
         
         # Check if the is active.
-        if user.state != 'ACTIVE':
+        if not user.is_active:
             return render_fault(request, Unauthorized('User inactive'))
         
         # Check if the token has expired.
diff --git a/astakos/im/auth_backends.py b/astakos/im/auth_backends.py
new file mode 100644 (file)
index 0000000..891d101
--- /dev/null
@@ -0,0 +1,31 @@
+from django.conf import settings
+from django.contrib.auth.backends import ModelBackend
+#from django.core.exceptions import ImproperlyConfigured
+#from django.db.models import get_model
+
+from astakos.im.models import AstakosUser
+
+class AstakosUserModelBackend(ModelBackend):
+    def authenticate(self, username=None, password=None):
+        try:
+            user = AstakosUser.objects.get(username=username)
+            if user.check_password(password):
+                return user
+        except AstakosUser.DoesNotExist:
+            return None
+
+    def get_user(self, user_id):
+        try:
+            return AstakosUser.objects.get(pk=user_id)
+        except AstakosUser.DoesNotExist:
+            return None
+
+    #@property
+    #def user_class(self):
+    #    if not hasattr(self, '_user_class'):
+    #        #self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
+    #        self._user_class = get_model('astakos.im', 'astakosuser')
+    #        print '#', self._user_class
+    #        if not self._user_class:
+    #            raise ImproperlyConfigured('Could not get custom user model')
+    #    return self._user_class
\ No newline at end of file
diff --git a/astakos/im/backends/__init__.py b/astakos/im/backends/__init__.py
new file mode 100644 (file)
index 0000000..14a55a5
--- /dev/null
@@ -0,0 +1,54 @@
+# 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.conf import settings
+from django.utils.importlib import import_module
+
+def get_backend():
+    """
+    Return an instance of a registration backend,
+    according to the INVITATIONS_ENABLED setting.
+
+    """
+    module = 'invitations' if settings.INVITATIONS_ENABLED else 'simple'
+    module = 'astakos.im.backends.%s' %module
+    backend_class_name = 'Backend'
+    try:
+        mod = import_module(module)
+    except ImportError, e:
+        raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
+    try:
+        backend_class = getattr(mod, backend_class_name)
+    except AttributeError:
+        raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
+    return backend_class()
\ No newline at end of file
diff --git a/astakos/im/backends/invitations.py b/astakos/im/backends/invitations.py
new file mode 100644 (file)
index 0000000..ad738d4
--- /dev/null
@@ -0,0 +1,81 @@
+# 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 astakos.im.forms import InvitedLocalRegisterForm, LocalRegisterForm
+from astakos.im.models import AstakosUser, Invitation
+
+class Backend(object):
+    def get_signup_form(self, request):
+        code = request.GET.get('code', '')
+        formclass = 'LocalRegisterForm'
+        if request.method == 'GET':
+            initial_data = None
+            if code:
+                formclass = 'InvitedLocalRegiterForm'
+                invitation = Invitation.objects.get(code=code)
+                if invitation.is_consumed:
+                    return HttpResponseBadRequest('Invitation has beeen used')
+                initial_data.update({'username':invitation.username,
+                                       'email':invitation.username,
+                                       'realname':invitation.realname})
+                inviter = AstakosUser.objects.get(username=invitation.inviter)
+                initial_data['inviter'] = inviter.realname
+        else:
+            initial_data = request.POST
+        return globals()[formclass](initial_data)
+    
+    def is_preaccepted(user, code):
+        invitation = self.invitation
+        if invitation and not invitation.is_consumed and invitation.code == code:
+            return True
+        return False
+    
+    def signup(self, request, form):
+        kwargs = {}
+        for field in form.fields:
+            if hasattr(AstakosUser(), field):
+                kwargs[field] = form.cleaned_data[field]
+        user = get_or_create_user(**kwargs)
+        
+        code = request.POST.get('code')
+        if is_preaccepted(user, code):
+            user.is_active = True
+            user.save()
+            message = _('Registration completed. You can now login.')
+            next = request.POST.get('next')
+            if next:
+                return redirect(next)
+        else:
+            message = _('Registration completed. You will receive an email upon your account\'s activation')
+        status = 'success'
+        return status, message
\ No newline at end of file
diff --git a/astakos/im/backends/simple.py b/astakos/im/backends/simple.py
new file mode 100644 (file)
index 0000000..40b3f15
--- /dev/null
@@ -0,0 +1,85 @@
+# 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.
+import socket
+import logging
+
+from django.core.mail import send_mail
+from django.conf import settings
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext as _
+from smtplib import SMTPException
+from urllib import quote
+
+from astakos.im.forms import LocalRegisterForm
+from astakos.im.util import get_or_create_user
+from astakos.im.models import AstakosUser
+
+class Backend(object):
+    def get_signup_form(self, request):
+        initial_data = request.POST if request.method == 'POST' else None
+        return LocalRegisterForm(initial_data)
+    
+    def signup(self, request, form, success_url):
+        kwargs = {}
+        for field in form.fields:
+            if hasattr(AstakosUser(), field):
+                kwargs[field] = form.cleaned_data[field]
+        user = get_or_create_user(**kwargs)
+        
+        status = 'success'
+        try:
+            send_verification(request.build_absolute_uri('/').rstrip('/'), user)
+            message = _('Verification sent to %s' % user.email)
+        except (SMTPException, socket.error) as e:
+            status = 'error'
+            name = 'strerror'
+            message = getattr(e, name) if hasattr(e, name) else e
+        
+        if user and status == 'error':
+            #delete created user
+            user.delete()
+        return status, message
+
+def send_verification(baseurl, user):
+    url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
+                                              quote(user.auth_token),
+                                              quote(baseurl))
+    message = render_to_string('activation.txt', {
+            'user': user,
+            'url': url,
+            'baseurl': baseurl,
+            'service': settings.SERVICE_NAME,
+            'support': settings.DEFAULT_CONTACT_EMAIL})
+    sender = settings.DEFAULT_FROM_EMAIL
+    send_mail('Pithos account activation', message, sender, [user.email])
+    logging.info('Sent activation %s', user)
\ No newline at end of file
diff --git a/astakos/im/context_processors.py b/astakos/im/context_processors.py
new file mode 100644 (file)
index 0000000..89389d5
--- /dev/null
@@ -0,0 +1,44 @@
+# 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.conf import settings
+
+def im_modules(request):
+    return {'im_modules': settings.IM_MODULES}
+
+def next(request):
+    return {'next' : request.GET.get('next', '')}
+
+def code(request):
+    return {'code' : request.GET.get('code', '')}
+    
\ No newline at end of file
index 3bd4d46..28b9709 100644 (file)
@@ -1,18 +1,13 @@
 [
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 1,
         "fields": {
-            "uniq": "admin",
-            "password": "admin",
             "level": 0,
-            "state": ACTIVE,
             "invitations": 10000,
-                       "is_admin": true,
             "auth_token": "0000",
             "auth_token_created": "2011-09-11 09:17:14",
             "auth_token_expires": "2012-09-11 09:17:14",
-            "created": "2011-09-11",
             "updated": "2011-09-11"
            }
     }
index 01c2323..6985104 100644 (file)
 [
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 1,
         "fields": {
-            "uniq": "test",
+            "username": "test",
             "level": 0,
-            "state": "ACTIVE",
             "invitations": 10000,
+            "password": "0000",
             "auth_token": "0000",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 2,
         "fields": {
-            "uniq": "verigak",
+            "username": "verigak",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
-            "is_admin": 1,
+            "is_superuser": 1,
+            "password": "0001",
             "auth_token": "0001",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 3,
         "fields": {
-            "uniq": "chazapis",
+            "username": "chazapis",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
+            "password": "0002",
             "auth_token": "0002",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 4,
         "fields": {
-            "uniq": "gtsouk",
+            "username": "gtsouk",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
+            "password": "0003",
             "auth_token": "0003",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 5,
         "fields": {
-            "uniq": "papagian",
+            "username": "papagian",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
+            "password": "0004",
             "auth_token": "0004",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 6,
         "fields": {
-            "uniq": "louridas",
+            "username": "louridas",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
+            "password": "0005",
             "auth_token": "0005",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 7,
         "fields": {
-            "uniq": "chstath",
+            "username": "chstath",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
+            "password": "0006",
             "auth_token": "0006",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 8,
         "fields": {
-            "uniq": "pkanavos",
+            "username": "pkanavos",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
+            "password": "0007",
             "auth_token": "0007",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 9,
         "fields": {
-            "uniq": "mvasilak",
+            "username": "mvasilak",
             "level": 1,
-            "state": "ACTIVE",
             "invitations": 3,
+            "password": "0008",
             "auth_token": "0008",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     },
     {
-        "model": "im.User",
+        "model": "im.AstakosUser",
         "pk": 10,
         "fields": {
-            "uniq": "διογένης",
+            "username": "διογένης",
             "level": 2,
-            "state": "ACTIVE",
             "invitations": 2,
+            "password": "0009",
             "auth_token": "0009",
             "auth_token_created": "2011-04-07 09:17:14",
             "auth_token_expires": "2015-04-07 09:17:14",
-            "created": "2011-02-06",
             "updated": "2011-02-06"
            }
     }
index a95f4a3..9a71bda 100644 (file)
 from django import forms
 from django.utils.translation import ugettext as _
 from django.conf import settings
+from hashlib import new as newhasher
 
-from astakos.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')    
-)
+from astakos.im.models import AstakosUser
 
 class RegisterForm(forms.Form):
-    uniq = forms.CharField(widget=forms.widgets.TextInput())
-    provider = forms.CharField(widget=forms.TextInput(),
-                                label=u'Identity Provider')
+    username = forms.CharField(widget=forms.widgets.TextInput())
     email = forms.EmailField(widget=forms.TextInput(),
                              label=_('Email address'))
-    realname = forms.CharField(widget=forms.TextInput(),
-                                label=u'Real Name')
+    first_name = forms.CharField(widget=forms.TextInput(),
+                                label=u'First Name', required=False)
+    last_name = forms.CharField(widget=forms.TextInput(),
+                                label=u'Last Name', required=False)
     
     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):
+    def clean_username(self):
         """
-        Validate that the uniq is alphanumeric and is not already
+        Validate that the username 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))
+            user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username'])
+        except AstakosUser.DoesNotExist:
+            return self.cleaned_data['username']
+        raise forms.ValidationError(_("A user with that username already exists."))
 
 class LocalRegisterForm(RegisterForm):
     """ local signup form"""
@@ -100,17 +72,17 @@ class LocalRegisterForm(RegisterForm):
     def __init__(self, *args, **kwargs):
         super(LocalRegisterForm, self).__init__(*args, **kwargs)
     
-    def clean_uniq(self):
+    def clean_username(self):
         """
-        Validate that the uniq is alphanumeric and is not already
+        Validate that the username 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."))
+            user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username'])
+        except AstakosUser.DoesNotExist:
+            return self.cleaned_data['username']
+        raise forms.ValidationError(_("A user with that username already exists."))
     
     def clean(self):
         """
@@ -133,18 +105,13 @@ class InvitedRegisterForm(RegisterForm):
         super(RegisterForm, self).__init__(*args, **kwargs)
         
         #set readonly form fields
-        self.fields['uniq'].widget.attrs['readonly'] = True
+        self.fields['username'].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
+class LoginForm(forms.Form):
+    username = forms.CharField(widget=forms.widgets.TextInput())
+    password = forms.CharField(widget=forms.PasswordInput(render_value=False),
+                                label=_('Password'))
\ No newline at end of file
diff --git a/astakos/im/migrations/0001_initial.py b/astakos/im/migrations/0001_initial.py
deleted file mode 100644 (file)
index 4c0b4e0..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-# 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 model 'User'
-        db.create_table('im_user', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('uniq', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)),
-            ('realname', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
-            ('email', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
-            ('affiliation', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
-            ('state', self.gf('django.db.models.fields.CharField')(default='ACTIVE', max_length=16)),
-            ('level', self.gf('django.db.models.fields.IntegerField')(default=4)),
-            ('invitations', self.gf('django.db.models.fields.IntegerField')(default=0)),
-            ('password', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
-            ('is_admin', self.gf('django.db.models.fields.BooleanField')(default=False)),
-            ('auth_token', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
-            ('auth_token_created', self.gf('django.db.models.fields.DateTimeField')(null=True)),
-            ('auth_token_expires', self.gf('django.db.models.fields.DateTimeField')(null=True)),
-            ('created', self.gf('django.db.models.fields.DateTimeField')()),
-            ('updated', self.gf('django.db.models.fields.DateTimeField')()),
-        ))
-        db.send_create_signal('im', ['User'])
-
-        # Adding model 'Invitation'
-        db.create_table('im_invitation', (
-            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('inviter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='invitations_sent', null=True, to=orm['im.User'])),
-            ('realname', self.gf('django.db.models.fields.CharField')(max_length=255)),
-            ('uniq', self.gf('django.db.models.fields.CharField')(max_length=255)),
-            ('code', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
-            ('is_accepted', self.gf('django.db.models.fields.BooleanField')(default=False)),
-            ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
-            ('accepted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
-        ))
-        db.send_create_signal('im', ['Invitation'])
-
-
-    def backwards(self, orm):
-        
-        # Deleting model 'User'
-        db.delete_table('im_user')
-
-        # Deleting model 'Invitation'
-        db.delete_table('im_invitation')
-
-
-    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'}),
-            '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']
diff --git a/astakos/im/migrations/0002_auto__add_field_user_is_verified.py b/astakos/im/migrations/0002_auto__add_field_user_is_verified.py
deleted file mode 100644 (file)
index 648739f..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-# 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']
diff --git a/astakos/im/migrations/0003_auto__add_field_user_provider__add_field_user_openidurl__add_field_inv.py b/astakos/im/migrations/0003_auto__add_field_user_provider__add_field_user_openidurl__add_field_inv.py
deleted file mode 100644 (file)
index 3ca4cb0..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-# 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.provider'
-        db.add_column('im_user', 'provider', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
-
-        # 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)
-
-        # Adding field 'Invitation.consumed'
-        db.add_column('im_invitation', 'consumed', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)
-
-
-    def backwards(self, orm):
-        
-        # Deleting field 'User.provider'
-        db.delete_column('im_user', 'provider')
-
-        # Deleting field 'User.openidurl'
-        db.delete_column('im_user', 'openidurl')
-
-        # Deleting field 'Invitation.is_consumed'
-        db.delete_column('im_invitation', 'is_consumed')
-
-        # Deleting field 'Invitation.consumed'
-        db.delete_column('im_invitation', 'consumed')
-
-
-    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'})
-        },
-        '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'}),
-            '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'}),
-            'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '16'}),
-            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
-            'updated': ('django.db.models.fields.DateTimeField', [], {})
-        }
-    }
-
-    complete_apps = ['im']
index 00dd645..68936ec 100644 (file)
@@ -40,74 +40,63 @@ from base64 import b64encode
 
 from django.conf import settings
 from django.db import models
+from django.contrib.auth.models import User, UserManager
 
 from astakos.im.interface import get_quota, set_quota
 
-from hashlib import new as newhasher
-
-class User(models.Model):
-    ACCOUNT_STATE = (
-        ('ACTIVE', 'Active'),
-        ('DELETED', 'Deleted'),
-        ('SUSPENDED', 'Suspended'),
-        ('UNVERIFIED', 'Unverified'),
-        ('PENDING', 'Pending')
-    )
-    
-    uniq = models.CharField('Unique ID', max_length=255, null=True)
+class AstakosUser(User):
+    # Use UserManager to get the create_user method, etc.
+    objects = UserManager()
     
-    realname = models.CharField('Real Name', max_length=255, default='')
-    email = models.CharField('Email', max_length=255, default='')
     affiliation = models.CharField('Affiliation', max_length=255, default='')
     provider = models.CharField('Provider', max_length=255, default='')
-    state = models.CharField('Account state', choices=ACCOUNT_STATE,
-                                max_length=16, default='PENDING')
     
     #for invitations
     level = models.IntegerField('Inviter level', default=4)
     invitations = models.IntegerField('Invitations left', default=0)
     
-    #for local
-    password = models.CharField('Password', max_length=255, default='')
-    
-    is_admin = models.BooleanField('Admin?', default=False)
-    
     auth_token = models.CharField('Authentication Token', max_length=32,
-                                    null=True, blank=True)
-    auth_token_created = models.DateTimeField('Token creation date',
-                                                null=True)
-    auth_token_expires = models.DateTimeField('Token expiration date',
-                                                null=True)
+                                  null=True, blank=True)
+    auth_token_created = models.DateTimeField('Token creation date', null=True)
+    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
     
-    created = models.DateTimeField('Creation date')
     updated = models.DateTimeField('Update date')
     
-    is_verified = models.BooleanField('Verified?', default=False)
+    @property
+    def realname(self):
+        return '%s %s' %(self.first_name, self.last_name)
     
-    openidurl = models.CharField('OpenID url', max_length=255, default='')
+    @realname.setter
+    def realname(self, value):
+        parts = value.split(' ')
+        if len(parts) == 2:
+            self.first_name = parts[0]
+            self.last_name = parts[1]
+        else:
+            self.last_name = parts[0]
     
     @property
     def quota(self):
-        return get_quota(self.uniq)
+        return get_quota(self.username)
 
     @quota.setter
     def quota(self, value):
-        set_quota(self.uniq, value)
+        set_quota(self.username, value)
     
     @property
     def invitation(self):
         try:
-            return Invitation.objects.get(uniq=self.uniq)
+            return Invitation.objects.get(username=self.username)
         except Invitation.DoesNotExist:
             return None
     
     def save(self, update_timestamps=True, **kwargs):
         if update_timestamps:
             if not self.id:
-                self.created = datetime.now()
+                self.date_joined = datetime.now()
             self.updated = datetime.now()
         
-        super(User, self).save(**kwargs)
+        super(AstakosUser, self).save(**kwargs)
         
         #invitation consume
         if self.invitation and not self.invitation.is_consumed:
@@ -115,7 +104,7 @@ class User(models.Model):
     
     def renew_token(self):
         md5 = hashlib.md5()
-        md5.update(self.uniq)
+        md5.update(self.username)
         md5.update(self.realname.encode('ascii', 'ignore'))
         md5.update(asctime())
         
@@ -125,13 +114,13 @@ class User(models.Model):
                                   timedelta(hours=settings.AUTH_TOKEN_DURATION)
     
     def __unicode__(self):
-        return self.uniq
+        return self.username
 
 class Invitation(models.Model):
-    inviter = models.ForeignKey(User, related_name='invitations_sent',
+    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
                                 null=True)
     realname = models.CharField('Real name', max_length=255)
-    uniq = models.CharField('Unique ID', max_length=255)
+    username = 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)
@@ -147,4 +136,4 @@ class Invitation(models.Model):
         self.save()
         
     def __unicode__(self):
-        return '%s -> %s [%d]' % (self.inviter, self.uniq, self.code)
+        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
index 167467c..af27353 100644 (file)
@@ -39,8 +39,8 @@ from django.conf import settings
 from django.http import HttpResponseBadRequest
 
 from astakos.im.models import Invitation
-from astakos.im.target.util import get_or_create_user, prepare_response
-
+from astakos.im.target.util import prepare_response
+from astakos.im.util import get_or_create_user
 
 def login(request):
     code = request.GET.get('code')
index 54cd42e..05064bc 100644 (file)
 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
 from django.conf import settings
 from django.template.loader import render_to_string
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.auth import authenticate
+from django.utils.translation import ugettext as _
 
 from astakos.im.target.util import prepare_response
-from astakos.im.models import User
+from astakos.im.models import AstakosUser
+from astakos.im.forms import LoginForm
 
 from urllib import unquote
 
 from hashlib import new as newhasher
 
-def login(request):
-    username = request.POST.get('username')
-    password = request.POST.get('password')
+def login(request, on_failure='index.html'):
+    """
+    on_failure: whatever redirect accepts as to
+    """
+    form = LoginForm(request.POST)
     
-    if not username:
-        return HttpResponseBadRequest('No user')
+    if not form.is_valid():
+        return render_to_response(on_failure,
+                                  {'form':form},
+                                  context_instance=RequestContext(request))
     
-    if not password:
-        return HttpResponseBadRequest('No password')
+    user = authenticate(**form.cleaned_data)
+    status = 'success'
+    if not user:
+        status = 'error'
+        message = _('Cannot authenticate account')
+    elif not user.is_active:
+        status = 'error'
+        message = _('Inactive account')
     
-    try:
-        user = User.objects.get(uniq=username)
-    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')
-    
-    if user.state == 'UNVERIFIED':
-        return HttpResponseBadRequest('Unverified account')
+    if status == 'error':
+        return render_to_response(on_failure,
+                                  {'form':form,
+                                   'message': _('Unverified account')},
+                                  context_instance=RequestContext(request))
     
     next = request.POST.get('next')
     return prepare_response(request, user, next)
@@ -74,11 +80,11 @@ def activate(request):
     token = request.GET.get('auth')
     next = request.GET.get('next')
     try:
-        user = User.objects.get(auth_token=token)
-    except User.DoesNotExist:
+        user = AstakosUser.objects.get(auth_token=token)
+    except AstakosUser.DoesNotExist:
         return HttpResponseBadRequest('No such user')
     
-    user.state = 'ACTIVE'
+    user.is_active = True
     user.save()
     return prepare_response(request, user, next, renew=True)
 
@@ -108,8 +114,8 @@ def reset_password(request):
             status = 'error'
             message = 'Bad Request: missing token'
         try:
-            user = User.objects.get(auth_token=token)
-            if username != user.uniq:
+            user = AstakosUser.objects.get(auth_token=token)
+            if username != user.username:
                 status = 'error'
                 message = 'Bad Request: username mismatch'
             else:
@@ -117,7 +123,7 @@ def reset_password(request):
                 user.status = 'NORMAL'
                 user.save()
                 return prepare_response(request, user, next, renew=True)
-        except User.DoesNotExist:
+        except AstakosUser.DoesNotExist:
             status = 'error'
             message = 'Bad Request: invalid token'
             
index 7973fe6..d35be68 100644 (file)
@@ -34,7 +34,8 @@
 from django.http import HttpResponseBadRequest
 from django.core.urlresolvers import reverse
 
-from astakos.im.target.util import get_or_create_user, prepare_response
+from astakos.im.target.util import prepare_response
+from astakos.im.util import get_or_create_user
 
 
 class Tokens:
index da7c376..d9c5fef 100644 (file)
@@ -40,7 +40,8 @@ from django.conf import settings
 from django.http import HttpResponse
 from django.utils import simplejson as json
 
-from astakos.im.target.util import get_or_create_user, prepare_response
+from astakos.im.target.util import prepare_response
+from astakos.im.util import get_or_create_user
 
 # It's probably a good idea to put your consumer's OAuth token and
 # OAuth secret into your project's settings. 
@@ -112,9 +113,9 @@ def authenticated(request):
     # These two things will likely never be used. Alternatively, you 
     # can prompt them for their email here. Either way, the password 
     # should never be used.
-    uniq = '%s@twitter.com' % access_token['screen_name']
+    username = '%s@twitter.com' % access_token['screen_name']
     realname = access_token['user_id']
     
     return prepare_response(request,
-                            get_or_create_user(uniq, realname, 'Twitter', 0),
+                            get_or_create_user(username, realname, 'Twitter', 0),
                             request_token.get('next'))
index 6eee88c..263bfcd 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-import logging
 import datetime
 
 from urlparse import urlsplit, urlunsplit
 from urllib import quote
 
-from django.conf import settings
 from django.http import HttpResponse
 from django.utils.http import urlencode
 from django.core.urlresolvers import reverse
-
-from astakos.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.
-    """
-    
-    user, created = User.objects.get_or_create(uniq=uniq,
-        defaults={
-            'realname': realname,
-            'affiliation': affiliation,
-            'level': level,
-            'invitations': settings.INVITATIONS_PER_LEVEL[level],
-            'state':'PENDING',
-        })
-    if created:
-        user.renew_token()
-        user.save()
-        logging.info('Created user %s', user)
-    
-    return user
+from django.conf import settings
 
 def prepare_response(request, user, next='', renew=False):
     """Return the unique username and the token
@@ -74,7 +51,9 @@ def prepare_response(request, user, next='', renew=False):
        expired, if the 'renew' parameter is present.
     """
     
-    if renew or user.auth_token_expires < datetime.datetime.now():
+    auth_token = user.auth_token
+    auth_token_expires = user.auth_token_expires
+    if renew or auth_token_expires < datetime.datetime.now():
         user.renew_token()
         user.save()
         
@@ -83,7 +62,7 @@ def prepare_response(request, user, next='', renew=False):
         parts = list(urlsplit(next))
         # Do not pass on user and token if we are on the same server.
         if parts[1] and request.get_host() != parts[1]:
-            parts[3] = urlencode({'user': user.uniq, 'token': user.auth_token})
+            parts[3] = urlencode({'user': user.username, 'token': auth_token})
             next = urlunsplit(parts)
     
     if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
@@ -93,14 +72,14 @@ def prepare_response(request, user, next='', renew=False):
         next = reverse('astakos.im.views.users_profile') + params
     
     response = HttpResponse()
-    expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
-    cookie_value = quote(user.uniq + '|' + user.auth_token)
+    expire_fmt = auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
+    cookie_value = quote(user.username + '|' + auth_token)
     response.set_cookie('_pithos2_a', value=cookie_value, expires=expire_fmt, path='/')
 
     if not next:
-        response['X-Auth-User'] = user.uniq
-        response['X-Auth-Token'] = user.auth_token
-        response.content = user.uniq + '\n' + user.auth_token + '\n'
+        response['X-Auth-User'] = user.username
+        response['X-Auth-Token'] = auth_token
+        response.content = user.username + '\n' + auth_token + '\n'
         response.status_code = 200
     else:
         response['Location'] = next
index 106f211..736d4ad 100644 (file)
@@ -3,6 +3,7 @@
 {% block title%}
         <h2>Welcome</h2>
         <p>Choose how to login. Or move on to <a href="admin">admin</a>.</p>
+        <p>Don't have an account? <a href="{% url astakos.im.views.signup %}">Sign up</a>.</p>
 {% endblock title%}
     
 {% block body%}
       <div class="span4">
         <h4>Local account</h4>
         <form action="{% url astakos.im.target.local.login %}" method="post" class="form-stacked">
-          <fieldset>
-            <div class="clearfix">
-              <label for="username">Username:</label>
-              <div class="input">
-                <input class="span3" id="user-username" name="username" type="text" />
-              </div>
-            </div>
-            <div class="clearfix">
-              <label for="password">Password:</label>
-              <div class="input">
-                <input class="span3" id="user-password" name="password" type="password" />
-              </div>
-            </div>
-          </fieldset>
-         <div>
-            <a href="{% url astakos.im.views.register 'local' %}">Sign up</a>
-         </div>
+          {{ form.as_p }}
          <div>
             <a href="{% url astakos.im.views.reclaim_password %}">Forgot your password?</a>
          </div>
         </form>
       </div>
     {% endif %}
-    {% if other_modules %}
       <div class="span8">
-        <h4>Other provider</h4>
-        {% for o in other_modules %}
-            <a href="/im/login/{{ o }}{% ifnotequal next "" %}?next={{ next|urlencode }}{% endifnotequal %}" alt="{{ o|title }}"><img src="/im/static/{{ o }}.png" width="120" height="120"></a>
+        {% for o in im_modules %}
+            {% if o != 'local' %}
+                <a href="/im/login/{{ o }}{% ifnotequal next "" %}?next={{ next|urlencode }}{% endifnotequal %}" alt="{{ o|title }}"><img src="/im/static/{{ o }}.png" width="120" height="120"></a>
+            {% endif %}
         {% endfor %}
       </div>
-    {% endif %}
     </div>
 {% endblock body%}
diff --git a/astakos/im/templates/local_create.html b/astakos/im/templates/local_create.html
deleted file mode 100644 (file)
index eb7ce9f..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-{% extends "base.html" %}
-
-{% block title%}
-        <h2>Sign up</h2>
-{% endblock title%}
-
-{% block body %}
-<form action="{% url astakos.im.views.local_create %}" method="post">
-{% if provider == 'local'%}
-  <div class="clearfix">
-    <label for="user-uniq">Username</label>
-    <div class="input">
-      <input class="span4" id="user-uniq" name="uniq" {%if inv %} value={{ inv.uniq }} {% endif %} type="text" />
-    </div>
-  </div>
-  
-  <div class="clearfix">
-    <label for="user-password">Password</label>
-    <div class="input">
-      <input class="span4" id="user-password" name="password" type="password" />
-    </div>
-  </div>
-  
-  <div class="clearfix">
-    <label for="user-password">Confirm Password</label>
-    <div class="input">
-      <input class="span4" id="user-retype-password" name="retype_password" type="password" />
-    </div>
-  </div>
-
-  <div class="clearfix">
-    <label for="user-realname">Real Name</label>
-    <div class="input">
-      <input class="span4" id="user-realname" name="realname" {%if inv %} value={{ inv.realname }} {% endif %} type="text" />
-    </div>
-  </div>
-  
-  <div class="clearfix">
-    <label for="user-email">Email</label>
-    <div class="input">
-      <input class="span4" id="user-email" name="email" type="text" />
-    </div>
-  </div>
-{% endif %}
-
-{% if provider == 'twitter' %}
-    <div class="clearfix">
-        <label for="user-uniq">Twitter </label>
-        <div class="input">
-            <input class="span4" id="user-uniq" name="uniq" {%if inv %} value={{ inv.uniq }} {% endif %} type="text" />
-        </div>
-    </div>
-{% endif %}
-
-  <div class="actions">
-    <button type="submit" class="btn primary">Create</button>
-    <button type="reset" class="btn">Reset</button>
-  </div>
-</form>
-{% endblock body %}
index 9162cff..5bb7e11 100644 (file)
@@ -9,7 +9,7 @@
   <div class="clearfix">
     <label for="user-uniq">Username</label>
     <div class="input">
-      <input class="span4" id="user-uniq" name="uniq" type="text" />
+      <input class="span4" id="user-username" name="username" type="text" />
     </div>
   </div>
   
index 625319a..362e9b5 100644 (file)
@@ -5,12 +5,11 @@
 {% endblock title%}
 
 {% block body %}
-<form action={%url astakos.im.views.register provider%} method="post">
+<form action={%url astakos.im.views.register%} method="post">
     {{ form.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 }}">
 
index 0be0cb4..1348395 100644 (file)
@@ -7,7 +7,7 @@
 {% block body %}
 <form action="{% url astakos.im.target.local.reset_password %}" method="post">
   <div class="clearfix">
-    <label for="user-uniq">password</label>
+    <label for="user-password">password</label>
     <div class="input">
       <input class="span4" id="user-password" name="password" type="password" />
     </div>
index 3078fb4..0e48233 100644 (file)
@@ -2,27 +2,31 @@
 
 {% block title%}
         <h1>Welcome</h1>
+        <p>Choose how to signup.</p>
 {% endblock title%}
 
-{% block body %}
-<form action={%url astakos.im.views.signup %} method="post">
-    <p>Select an Identity Provider:</p>
-    {% for choice in im_modules%}
+{% block body%}
     <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 }} />
+    {% if "local" in im_modules %}
+      <div class="span4">
+        <h4>Local account</h4>
+        <form action="" method="post" class="form-stacked">
+          {{ form.as_p }}
+         <br>
+          <div class="">
+            <input type="hidden" name="next" value="{{ next }}">
+            <input type="hidden" name="code" value="{{ code }}">
+            <button type="submit" class="btn primary">Go</button>
+          </div>
+        </form>
+      </div>
     {% 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
+    <div class="span8">
+        {% for o in im_modules %}
+            {% if o != 'local' %}
+                <a href="/im/login/{{ o }}{% ifnotequal next "" %}?next={{ next|urlencode }}{% endifnotequal %}" alt="{{ o|title }}"><img src="/im/static/{{ o }}.png" width="120" height="120"></a>
+            {% endif %}
+        {% endfor %}
+      </div>
+    </div>
+{% endblock body%}
index 71dacdd..90b0f87 100644 (file)
   </div>
 
   <div class="clearfix">
-    <label for="user-uniq">Uniq</label>
+    <label for="user-username">Username</label>
     <div class="input">
-        <span class="uneditable-input" id="user-uniq">{{ user.uniq }}</span>
+        <span class="uneditable-input" id="user-username">{{ user.username }}</span>
     </div>
   </div>
 
   <div class="clearfix">
-    <label for="user-realname">Real Name</label>
+    <label for="user-first-name">Real Name</label>
     <div class="input">
-      <input class="span4" id="user-realname" name="realname" value="{{ user.realname }}" type="text" />
+      <input class="span4" id="user-first-name" name="first_name" value="{{ user.first_name }}" type="text" />
+    </div>
+  </div>
+  
+  <div class="clearfix">
+    <label for="user-last-name">Real Name</label>
+    <div class="input">
+      <input class="span4" id="user-last-name" name="last_name" value="{{ user.last_name }}" type="text" />
     </div>
   </div>
 
@@ -36,7 +43,7 @@
       <ul class="inputs-list">
         <li>
           <label>
-            <input type="checkbox" id="user-admin" name="admin"{% if user.is_admin %} checked{% endif %} disabled="disabled">
+            <input type="checkbox" id="user-admin" name="admin"{% if user.is_superuser %} checked{% endif %} disabled="disabled">
           </label>
         </li>
       </ul>
   </div>
 
   <div class="clearfix">
-    <label for="user-state">State</label>
+    <label for="user-is-active">Is active?</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>
+      <ul class="inputs-list">
+        <li>
+          <label>
+            <input type="checkbox" id="user-is-active" name="is_active"{% if user.is_active %} checked{% endif %} disabled="disabled">
+          </label>
+        </li>
+      </ul>
     </div>
   </div>
 
   <div class="clearfix">
     <label for="user-created">Created</label>
     <div class="input">
-      <span class="uneditable-input" id="user-created">{{ user.created }}</span>
+      <span class="uneditable-input" id="user-date-joined">{{ user.date_joined }}</span>
     </div>
   </div>
 
diff --git a/astakos/im/templates/welcome.txt b/astakos/im/templates/welcome.txt
new file mode 100644 (file)
index 0000000..e542cd8
--- /dev/null
@@ -0,0 +1,70 @@
+--- A translation in English follows ---
+
+Αγαπητέ/η {{ user.realname }},
+
+Ο λογαρισμός σας για την υπηρεσία {{ service }} της ΕΔΕΤκατά την Alpha (πιλοτική)
+φάση λειτουργίας της έχει ενεργοποιηθεί.
+
+Για να συνδεθείτε, χρησιμοποιήστε τον παρακάτω σύνδεσμο:
+
+{{ url }}
+
+Σημείωση:
+
+Η υπηρεσία θα είναι για μερικές εβδομάδες σε φάση λειτουργίας Alpha. Αν
+και έχουμε κάνει ότι είναι δυνατό για να εξασφαλίσουμε την ποιότητα της
+υπηρεσίας, δεν αποκλείεται να εμφανιστούν προβλήματα στο λογισμικό
+διαχείρισης ή η υπηρεσία να μην είναι διαθέσιμη κατά διαστήματα. Για
+αυτό το λόγο, σας παρακαλούμε να μη μεταφέρετε ακόμη σημαντικά κομμάτια
+της δουλειάς σας στην υπηρεσία {{ service }}. Επίσης, παρακαλούμε να έχετε
+υπόψη σας ότι όλα τα δεδομένα, θα διαγραφούν κατά τη μετάβαση από την
+έκδοση Alpha στην έκδοση Beta. Θα υπάρξει έγκαιρη ειδοποίησή σας πριν
+από τη μετάβαση αυτή.
+
+Περισσότερα για την υπηρεσία θα βρείτε στο {{ baseurl }}, αφού
+έχετε ενεργοποιήσει την πρόσκλησή σας.
+
+Αναμένουμε τα σχόλια και τις παρατηρήσεις σας, για να βελτιώσουμε τη
+λειτουργικότητα και την αξιοπιστία της καινοτομικής αυτής υπηρεσίας.
+
+Για όποιες παρατηρήσεις ή προβλήματα στη λειτουργεία της υπηρεσίας μπορείτε να
+απευθυνθείτε στο {{ support }}.
+
+Σας ευχαριστούμε πολύ για τη συμμετοχή στην Alpha λειτουργία του {{ service }}.
+
+Με εκτίμηση,
+
+Εθνικό Δίκτυο Έρευνας και Τεχνολογίας (ΕΔΕΤ/GRNET)
+
+--
+
+Dear {{ user.realname }},
+
+Your account for GRNET's {{ service }} service for its Alpha test phase has been
+activated.
+
+To login, please use the following link:
+
+{{ url }}
+
+Please note:
+
+This service is, for a few weeks, in Alpha. Although every care has been
+taken to ensure high quality, it is possible there may still be software
+bugs, or periods of limited service availability. For this reason, we
+kindly request you do not transfer important parts of your work to
+{{ service }}, yet. Also, please bear in mind that all data, will be deleted
+when the service moves to Beta test. Before the transition, you will be
+notified in time.
+
+For more information, please visit {{ baseurl }}, after
+activating your invitation.
+
+We look forward to your feedback, to improve the functionality and
+reliability of this innovative service.
+
+For any remarks or problems you can contact {{ support }}.
+
+Thank you for participating in the Alpha test of {{ service }}.
+
+Greek Research and Technonogy Network - GRNET
index 1d9853f..595f07f 100644 (file)
@@ -38,25 +38,13 @@ urlpatterns = patterns('astakos.im.views',
     (r'^$', 'index'),
     (r'^login/?$', 'index'),
     
-    (r'^admin/?$', 'admin'),
-    
-    (r'^admin/users/?$', 'users_list'),
-    (r'^admin/users/(\d+)/?$', 'users_info'),
-    (r'^admin/users/create$', 'users_create'),
-    (r'^admin/users/(\d+)/modify/?$', 'users_modify'),
-    (r'^admin/users/(\d+)/delete/?$', 'users_delete'),
-    (r'^admin/users/export/?$', 'users_export'),
-    (r'^admin/users/pending/?$', 'pending_users'),
-    (r'^admin/users/activate/(\d+)/?$', 'users_activate'),
-    
-    (r'^admin/invitations/?$', 'invitations_list'),
-    (r'^admin/invitations/export/?$', 'invitations_export'),
+    (r'^admin/', include('astakos.im.admin.urls')),
     
     (r'^profile/?$', 'users_profile'),
     (r'^profile/edit/?$', 'users_edit'),
     
     (r'^signup/?$', 'signup'),
-    (r'^register/(\w+)?$', 'register'),
+    #(r'^register/(\w+)?$', 'register'),
     #(r'^signup/complete/?$', 'signup_complete'),
     #(r'^local/create/?$', 'local_create'),
 )
@@ -70,7 +58,6 @@ urlpatterns += patterns('',
                                 {'document_root': settings.PROJECT_PATH + '/im/static'})
 )
 
-
 if 'local' in settings.IM_MODULES:
     urlpatterns += patterns('astakos.im.views',
 #        (r'^local/create/?$', 'local_create'),
index 4f06a06..385460d 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+import logging
+
 from datetime import tzinfo, timedelta
+from django.conf import settings
+from django.template import RequestContext
 
+from astakos.im.models import AstakosUser
 
 class UTC(tzinfo):
    def utcoffset(self, dt):
@@ -48,3 +53,34 @@ def isoformat(d):
    """Return an ISO8601 date string that includes a timezone."""
 
    return d.replace(tzinfo=UTC()).isoformat()
+
+def get_or_create_user(username, realname=None, first_name=None, last_name=None, affiliation=None, level=0, provider='local', password=None, email=None):
+    """Find or register a user into the internal database
+       and issue a token for subsequent requests.
+    """
+    
+    user, created = AstakosUser.objects.get_or_create(username=username,
+        defaults={
+            'is_active': False,
+            'password':password,
+            'email':email,
+            'affiliation':affiliation,
+            'level':level,
+            'invitations':settings.INVITATIONS_PER_LEVEL[level],
+            'provider':provider,
+            'realname':realname,
+            'first_name':first_name,
+            'last_name':last_name
+        })
+    if created:
+        user.renew_token()
+        user.save()
+        logging.info('Created user %s', user)
+    
+    return user
+
+def get_context(request, extra_context={}, **kwargs):
+    if not extra_context:
+        extra_context = {}
+    extra_context.update(kwargs)
+    return RequestContext(request, extra_context)
index b93d171..1e29416 100644 (file)
@@ -42,6 +42,8 @@ from functools import wraps
 from math import ceil
 from random import randint
 from smtplib import SMTPException
+from hashlib import new as newhasher
+from urllib import quote
 
 from django.conf import settings
 from django.core.mail import send_mail
@@ -52,28 +54,20 @@ 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.forms import Form
-from django.forms.formsets import formset_factory
-#from openid.consumer.consumer import Consumer, \
-#    SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
-
-from hashlib import new as newhasher
-
-from urllib import quote
 
 #from astakos.im.openid_store import PithosOpenIDStore
-from astakos.im.models import User, Invitation
-from astakos.im.util import isoformat
+from astakos.im.models import AstakosUser, Invitation
+from astakos.im.util import isoformat, get_or_create_user, get_context
 from astakos.im.forms import *
+from astakos.im.backends import get_backend
 
-def render_response(template, tab=None, status=200, **kwargs):
+def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
     if tab is None:
         tab = template.partition('_')[0]
     kwargs.setdefault('tab', tab)
-    html = render_to_string(template, kwargs)
+    html = render_to_string(template, kwargs, context_instance=context_instance)
     return HttpResponse(html, status=status)
 
-
 def requires_login(func):
     @wraps(func)
     def wrapper(request, *args):
@@ -85,179 +79,13 @@ def requires_login(func):
         return func(request, *args)
     return wrapper
 
-def requires_admin(func):
-    @wraps(func)
-    def wrapper(request, *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)
-            if not request.user.is_admin:
-                return HttpResponse('Forbidden', status=403)
-        return func(request, *args)
-    return wrapper
-
-
-def index(request):
-    kwargs = {'im_modules':settings.IM_MODULES,
-              'other_modules':settings.IM_MODULES[1:]}
-    return render_response('index.html',
-                           next=request.GET.get('next', ''),
-                           **kwargs)
-
-
-@requires_admin
-def admin(request):
-    stats = {}
-    stats['users'] = User.objects.count()
-    stats['pending'] = User.objects.filter(state = 'PENDING').count()
-    
-    invitations = Invitation.objects.all()
-    stats['invitations'] = invitations.count()
-    stats['invitations_consumed'] = invitations.filter(is_consumed=True).count()
-    
-    return render_response('admin.html', tab='home', stats=stats)
-
-
-@requires_admin
-def users_list(request):
-    users = User.objects.order_by('id')
-    
-    filter = request.GET.get('filter', '')
-    if filter:
-        if filter.startswith('-'):
-            users = users.exclude(uniq__icontains=filter[1:])
-        else:
-            users = users.filter(uniq__icontains=filter)
-    
-    try:
-        page = int(request.GET.get('page', 1))
-    except ValueError:
-        page = 1
-    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
-    limit = offset + settings.ADMIN_PAGE_LIMIT
-    
-    npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
-    prev = page - 1 if page > 1 else None
-    next = page + 1 if page < npages else None
-    return render_response('users_list.html',
-                            users=users[offset:limit],
-                            filter=filter,
-                            pages=range(1, npages + 1),
-                            page=page,
-                            prev=prev,
-                            next=next)
-
-@requires_admin
-def users_info(request, user_id):
-    user = User.objects.get(id=user_id)
-    states = [x[0] for x in User.ACCOUNT_STATE]
-    return render_response('users_info.html',
-                            user=user,
-                            states=states)
+def index(request, template_name='index.html', extra_context={}):
+    print '#', get_context(request, extra_context)
+    return render_response(template_name,
+                           form = LoginForm(),
+                           context_instance = get_context(request, extra_context))
 
-
-@requires_admin
-def users_modify(request, user_id):
-    user = User.objects.get(id=user_id)
-    user.uniq = request.POST.get('uniq')
-    user.realname = request.POST.get('realname')
-    user.is_admin = True if request.POST.get('admin') else False
-    user.affiliation = request.POST.get('affiliation')
-    user.state = request.POST.get('state')
-    user.invitations = int(request.POST.get('invitations') or 0)
-    user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
-    user.auth_token = request.POST.get('auth_token')
-    try:
-        auth_token_expires = request.POST.get('auth_token_expires')
-        d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ')
-        user.auth_token_expires = d
-    except ValueError:
-        pass
-    user.save()
-    return redirect(users_info, user.id)
-
-@requires_admin
-def users_delete(request, user_id):
-    user = User.objects.get(id=user_id)
-    user.delete()
-    return redirect(users_list)
-
-@requires_admin
-def pending_users(request):
-    users = User.objects.order_by('id')
-    
-    users = users.filter(state = 'PENDING')
-    
-    try:
-        page = int(request.GET.get('page', 1))
-    except ValueError:
-        page = 1
-    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
-    limit = offset + settings.ADMIN_PAGE_LIMIT
-    
-    npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
-    prev = page - 1 if page > 1 else None
-    next = page + 1 if page < npages else None
-    return render_response('pending_users.html',
-                            users=users[offset:limit],
-                            filter=filter,
-                            pages=range(1, npages + 1),
-                            page=page,
-                            prev=prev,
-                            next=next)
-
-def send_greeting(baseurl, user):
-    url = baseurl
-    subject = _('Welcome to Pithos')
-    message = render_to_string('welcome.txt', {
-                'user': user,
-                'url': url,
-                'baseurl': baseurl,
-                'service': settings.SERVICE_NAME,
-                'support': settings.DEFAULT_CONTACT_EMAIL})
-    sender = settings.DEFAULT_FROM_EMAIL
-    send_mail(subject, message, sender, [user.email])
-    logging.info('Sent greeting %s', user)
-
-@requires_admin
-def users_activate(request, user_id):
-    user = User.objects.get(id=user_id)
-    user.state = 'ACTIVE'
-    status = 'success'
-    try:
-        send_greeting(request.build_absolute_uri('/').rstrip('/'), user)
-        message = _('Greeting 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
-    
-    users = User.objects.order_by('id')
-    users = users.filter(state = 'PENDING')
-    
-    try:
-        page = int(request.POST.get('page', 1))
-    except ValueError:
-        page = 1
-    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
-    limit = offset + settings.ADMIN_PAGE_LIMIT
-    
-    npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
-    prev = page - 1 if page > 1 else None
-    next = page + 1 if page < npages else None
-    return render_response('pending_users.html',
-                            users=users[offset:limit],
-                            filter=filter,
-                            pages=range(1, npages + 1),
-                            page=page,
-                            prev=prev,
-                            next=next,
-                            message=message)
-
-def generate_invitation_code():
+def _generate_invitation_code():
     while True:
         code = randint(1, 2L**63 - 1)
         try:
@@ -266,8 +94,7 @@ def generate_invitation_code():
         except Invitation.DoesNotExist:
             return code
 
-
-def send_invitation(baseurl, inv):
+def _send_invitation(baseurl, inv):
     url = settings.SIGNUP_TARGET % (baseurl, inv.code, quote(baseurl))
     subject = _('Invitation to Pithos')
     message = render_to_string('invitation.txt', {
@@ -277,34 +104,33 @@ def send_invitation(baseurl, inv):
                 'service': settings.SERVICE_NAME,
                 'support': settings.DEFAULT_CONTACT_EMAIL})
     sender = settings.DEFAULT_FROM_EMAIL
-    send_mail(subject, message, sender, [inv.uniq])
+    send_mail(subject, message, sender, [inv.username])
     logging.info('Sent invitation %s', inv)
 
-
 @requires_login
-def invite(request):
+def invite(request, template_name='invitations.html', extra_context={}):
     status = None
     message = None
     inviter = request.user
 
     if request.method == 'POST':
-        uniq = request.POST.get('uniq')
+        username = request.POST.get('uniq')
         realname = request.POST.get('realname')
         
         if inviter.invitations > 0:
-            code = generate_invitation_code()
+            code = _generate_invitation_code()
             invitation, created = Invitation.objects.get_or_create(
                 inviter=inviter,
-                uniq=uniq,
+                username=username,
                 defaults={'code': code, 'realname': realname})
             
             try:
-                send_invitation(request.build_absolute_uri('/').rstrip('/'), invitation)
+                _send_invitation(request.build_absolute_uri('/').rstrip('/'), invitation)
                 if created:
                     inviter.invitations = max(0, inviter.invitations - 1)
                     inviter.save()
                 status = 'success'
-                message = _('Invitation sent to %s' % uniq)
+                message = _('Invitation sent to %s' % username)
             except (SMTPException, socket.error) as e:
                 status = 'error'
                 message = getattr(e, 'strerror', '')
@@ -313,36 +139,21 @@ def invite(request):
             message = _('No invitations left')
 
     if request.GET.get('format') == 'json':
-        sent = [{'email': inv.uniq,
+        sent = [{'email': inv.username,
                  'realname': inv.realname,
                  'is_accepted': inv.is_accepted}
                     for inv in inviter.invitations_sent.all()]
         rep = {'invitations': inviter.invitations, 'sent': sent}
         return HttpResponse(json.dumps(rep))
     
-    html = render_to_string('invitations.html', {
-            'user': inviter,
-            'status': status,
-            'message': message})
-    return HttpResponse(html)
+    kwargs = {'user': inviter, 'status': status, 'message': message}
+    context = get_context(request, extra_context, **kwargs)
+    return render_response(template_name,
+                           context_instance = context)
 
-def send_verification(baseurl, user):
-    url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
-                                              quote(user.auth_token),
-                                              quote(baseurl))
-    message = render_to_string('activation.txt', {
-            'user': user,
-            'url': url,
-            'baseurl': baseurl,
-            'service': settings.SERVICE_NAME,
-            'support': settings.DEFAULT_CONTACT_EMAIL})
-    sender = settings.DEFAULT_FROM_EMAIL
-    send_mail('Pithos account activation', message, sender, [user.email])
-    logging.info('Sent activation %s', user)
-
-def send_password(baseurl, user):
+def _send_password(baseurl, user):
     url = settings.PASSWORD_RESET_TARGET % (baseurl,
-                                            quote(user.uniq),
+                                            quote(user.username),
                                             quote(baseurl))
     message = render_to_string('password.txt', {
             'user': user,
@@ -354,158 +165,54 @@ def send_password(baseurl, user):
     send_mail('Pithos password recovering', message, sender, [user.email])
     logging.info('Sent password %s', user)
 
-def reclaim_password(request):
+def reclaim_password(request, template_name='reclaim.html', extra_context={}):
     if request.method == 'GET':
-        return render_response('reclaim.html')
+        return render_response(template_name,
+                               context_instance = get_context(request, extra_context))
     elif request.method == 'POST':
-        username = request.POST.get('uniq')
+        username = request.POST.get('username')
         try:
-            user = User.objects.get(uniq=username)
+            user = AstakosUser.objects.get(username=username)
             try:
-                send_password(request.build_absolute_uri('/').rstrip('/'), user)
+                _send_password(request.build_absolute_uri('/').rstrip('/'), user)
                 status = 'success'
                 message = _('Password reset sent to %s' % user.email)
-                user.status = 'UNVERIFIED'
+                user.is_active = False
                 user.save()
             except (SMTPException, socket.error) as e:
                 status = 'error'
                 name = 'strerror'
                 message = getattr(e, name) if hasattr(e, name) else e
-        except User.DoesNotExist:
+        except AstakosUser.DoesNotExist:
             status = 'error'
             message = 'Username does not exist'
         
-        html = render_to_string('reclaim.html', {
-                'status': status,
-                'message': message})
-        return HttpResponse(html)
-
-@requires_admin
-def invitations_list(request):
-    invitations = Invitation.objects.order_by('id')
-    
-    filter = request.GET.get('filter', '')
-    if filter:
-        if filter.startswith('-'):
-            invitations = invitations.exclude(uniq__icontains=filter[1:])
-        else:
-            invitations = invitations.filter(uniq__icontains=filter)
-    
-    try:
-        page = int(request.GET.get('page', 1))
-    except ValueError:
-        page = 1
-    offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
-    limit = offset + settings.ADMIN_PAGE_LIMIT
-    
-    npages = int(ceil(1.0 * invitations.count() / settings.ADMIN_PAGE_LIMIT))
-    prev = page - 1 if page > 1 else None
-    next = page + 1 if page < npages else None
-    return render_response('invitations_list.html',
-                            invitations=invitations[offset:limit],
-                            filter=filter,
-                            pages=range(1, npages + 1),
-                            page=page,
-                            prev=prev,
-                            next=next)
-
-@requires_admin
-def invitations_export(request):
-    # Create the HttpResponse object with the appropriate CSV header.
-    response = HttpResponse(mimetype='text/csv')
-    response['Content-Disposition'] = 'attachment; filename=invitations.csv'
-
-    writer = csv.writer(response)
-    writer.writerow(['ID',
-                     'Uniq',
-                     'Real Name',
-                     'Code',
-                     'Inviter Uniq',
-                     'Inviter Real Name',
-                     'Is_accepted',
-                     'Created',
-                     'Accepted',])
-    invitations = Invitation.objects.order_by('id')
-    for inv in invitations:
-        writer.writerow([inv.id,
-                         inv.uniq.encode("utf-8"),
-                         inv.realname.encode("utf-8"),
-                         inv.code,
-                         inv.inviter.uniq.encode("utf-8"),
-                         inv.inviter.realname.encode("utf-8"),
-                         inv.is_accepted,
-                         inv.created,
-                         inv.accepted])
-
-    return response
-
-
-@requires_admin
-def users_export(request):
-    # Create the HttpResponse object with the appropriate CSV header.
-    response = HttpResponse(mimetype='text/csv')
-    response['Content-Disposition'] = 'attachment; filename=users.csv'
-
-    writer = csv.writer(response)
-    writer.writerow(['ID',
-                     'Uniq',
-                     'Real Name',
-                     'Admin',
-                     'Affiliation',
-                     'State',
-                     'Quota (GiB)',
-                     'Updated',])
-    users = User.objects.order_by('id')
-    for u in users:
-        writer.writerow([u.id,
-                         u.uniq.encode("utf-8"),
-                         u.realname.encode("utf-8"),
-                         u.is_admin,
-                         u.affiliation.encode("utf-8"),
-                         u.state.encode("utf-8"),
-                         u.quota,
-                         u.updated])
-
-    return response
-
-@requires_admin
-def users_create(request):
-    if request.method == 'GET':
-        return render_response('users_local_create.html')
-    if request.method == 'POST':
-        user = User()
-        user.uniq = request.POST.get('uniq')
-        user.realname = request.POST.get('realname')
-        user.is_admin = True if request.POST.get('admin') else False
-        user.affiliation = request.POST.get('affiliation')
-        user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
-        user.renew_token()
-        user.provider = 'local'
-        user.save()
-        return redirect(users_info, user.id)
+        kwargs = {'status': status, 'message': message}
+        return render_response(template_name,
+                                context_instance = get_context(request, extra_context, **kwargs))
 
 @requires_login
-def users_profile(request):
-    next = request.GET.get('next')
+def users_profile(request, template_name='users_profile.html', extra_context={}):
     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,
-                            states=states,
-                            next=next)
+        user = AstakosUser.objects.get(username=request.user)
+    except AstakosUser.DoesNotExist:
+        token = request.GET.get('auth', None)
+        user = AstakosUser.objects.get(auth_token=token)
+    return render_response(template_name,
+                           context_instance = get_context(request,
+                                                          extra_context,
+                                                          user=user))
 
 @requires_login
-def users_edit(request):
+def users_edit(request, template_name='users_profile.html', extra_context={}):
     try:
-        user = User.objects.get(uniq=request.user)
-    except User.DoesNotExist:
+        user = AstakosUser.objects.get(username=request.user)
+    except AstakosUser.DoesNotExist:
         token = request.POST.get('auth', None)
-        users = User.objects.all()
-        user = User.objects.get(auth_token=token)
-    user.realname = request.POST.get('realname')
+        #users = AstakosUser.objects.all()
+        user = AstakosUser.objects.get(auth_token=token)
+    user.first_name = request.POST.get('first_name')
+    user.last_name = request.POST.get('last_name')
     user.affiliation = request.POST.get('affiliation')
     user.is_verified = True
     user.save()
@@ -515,208 +222,44 @@ def users_edit(request):
     
     status = 'success'
     message = _('Profile has been updated')
-    html = render_to_string('users_profile.html', {
-            'user': user,
-            'status': status,
-            'message': message})
-    return HttpResponse(html)
+    return render_response(template_name,
+                           context_instance = get_context(request, extra_context, **kwargs))
     
-def signup(request):
+def signup(request, template_name='signup.html', extra_context={}, backend=None, success_url = None):
+    if not backend:
+        backend = get_backend()
     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('astakos.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())
-    form = globals()[formclassname]()
-    return render_response('register.html',
-                           form=form,
-                           next=next,
-                           filter=filter,
-                           code=code,
-                           provider=provider)
-
-def is_preaccepted(user):
-    if user.invitation and not user.invitation.is_consumed:
-        return True
-    
-    return False
-
-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':
-        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)
+            form = backend.get_signup_form(request)
+            return render_response(template_name,
+                               form=form,
+                               context_instance = get_context(request, extra_context))
+        except Exception, e:
+            return _on_failure(e, template_name=template_name)
     elif request.method == 'POST':
-        provider = request.POST.get('provider')
-        code = request.POST.get('code')
-        
-        #instantiate the form
-        prefix = 'Invited' if code else ''
-        formclassname = '%s%sRegisterForm' %(prefix, provider.capitalize())
-        form = globals()[formclassname](request.POST)
-        if not form.is_valid():
-            return render_to_response('register.html',
-                                      {'form':form,
-                                       'code':code,
-                                       'next':next}) 
-        
-        user = User()
-        for field in form.fields:
-            if hasattr(user, field):
-                setattr(user, field, form.cleaned_data[field])
-        
-        if user.openidurl:
-            redirect_url = reverse('astakos.im.views.create')
-            return ask_openid(request, 
-                        user.openidurl,
-                        redirect_url,
-                        'signup')
-        
-        #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('astakos.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, {
+        try:
+            form = backend.get_signup_form(request)
+            if not form.is_valid():
+                return render_response(template_name,
+                                       form = form,
+                                       context_instance = get_context(request, extra_context))
+            status, message = backend.signup(request, form, success_url)
+            next = request.POST.get('next')
+            if next:
+                return redirect(next)
+            return _info(status, message)
+        except Exception, e:
+            return _on_failure(e, template_name=template_name)
+
+def _info(status, message, template_name='base.html'):
+    html = render_to_string(template_name, {
             'status': status,
             'message': message})
     response = HttpResponse(html)
     return response
 
-def on_success(message, template='base.html'):
-    return info('success', message)
+def _on_success(message, template_name='base.html'):
+    return _info('success', message, template_name)
     
-def on_failure(message, template='base.html'):
-    return info('error', message)
+def _on_failure(message, template_name='base.html'):
+    return _info('error', message, template_name)
index aa89fab..f99d3e1 100644 (file)
 from time import time, mktime
 from urllib import quote, unquote
 
-from astakos.im.models import User
-
+from astakos.im.models import AstakosUser
 
 def get_user_from_token(token):
     try:
-        return User.objects.get(auth_token=token)
-    except User.DoesNotExist:
+        return AstakosUser.objects.get(auth_token=token)
+    except AstakosUser.DoesNotExist:
         return None
 
-
 class AuthMiddleware(object):
     def process_request(self, request):
         request.user = None
@@ -69,7 +67,7 @@ class AuthMiddleware(object):
             return
         
         # Check if the is active.
-        if user.state != 'ACTIVE':
+        if not user.is_active:
             return
         
         # Check if the token has expired.
@@ -77,11 +75,12 @@ class AuthMiddleware(object):
             return
         
         request.user = user
-        request.user_uniq = user.uniq
+        request.user_uniq = user.username
     
     def process_response(self, request, response):
         if getattr(request, 'user', None) and getattr(request, 'set_auth_cookie', False):
-            expire_fmt = request.user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
-            cookie_value = quote(request.user.uniq + '|' + request.user.auth_token)
+            user_profile = request.user.get_profile()
+            expire_fmt = user_profile.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
+            cookie_value = quote(request.user.uniq + '|' + user_profile.auth_token)
             response.set_cookie('_pithos2_a', value=cookie_value, expires=expire_fmt, path='/')
-        return response
+        return response
\ No newline at end of file
index 103bbf5..2dc5b2e 100644 (file)
@@ -4,6 +4,7 @@ TEMPLATE_LOADERS = (
     'django.template.loaders.app_directories.Loader',
 )
 
+
 MIDDLEWARE_CLASSES = (
     'django.middleware.common.CommonMiddleware',
     'astakos.middleware.LoggingConfigMiddleware',
@@ -17,9 +18,22 @@ TEMPLATE_DIRS = (
     # Put strings here, like "/home/html/django_templates".
     # Always use forward slashes, even on Windows.
     # Don't forget to use absolute paths, not relative paths.
+    '%s/im/admin/templates/' %PROJECT_PATH,
 )
 
 INSTALLED_APPS = (
     'astakos.im',
-    'south'
+    'south',
+    'django.contrib.auth',
+    'django.contrib.contenttypes'
 )
+
+TEMPLATE_CONTEXT_PROCESSORS = ('astakos.im.context_processors.im_modules',
+                               'astakos.im.context_processors.next',
+                               'astakos.im.context_processors.code',)
+
+AUTHENTICATION_BACKENDS = (
+    'astakos.im.auth_backends.AstakosUserModelBackend',
+)
+
+CUSTOM_USER_MODEL = 'astakos.im.AstakosUser'
\ No newline at end of file
index 98b18e1..f392a41 100644 (file)
@@ -20,11 +20,11 @@ INVITATIONS_PER_LEVEL = {
     4   :   0
 }
 
-SERVICE_NAME = 'Pithos'
+SERVICE_NAME = 'Astakos'
 
 # Address to use for outgoing emails
-DEFAULT_FROM_EMAIL = 'Pithos <no-reply@grnet.gr>'
-DEFAULT_CONTACT_EMAIL = 'support@pithos.grnet.gr'
+DEFAULT_FROM_EMAIL = '%s <no-reply@grnet.gr>' %SERVICE_NAME
+DEFAULT_CONTACT_EMAIL = 'support@%s.grnet.gr' %SERVICE_NAME.lower()
 
 # Where users should signup with their invitation code
 SIGNUP_TARGET = '%s/im/signup/?code=%d&next=%s'
@@ -42,4 +42,4 @@ IM_MODULES = ['local', 'twitter', 'shibboleth']
 FORCE_PROFILE_UPDATE = False
 
 #Enable invitations
-INVITATIONS_ENABLED = True
+INVITATIONS_ENABLED = True
\ No newline at end of file