Commit ebd369d0 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

merge with master

parent 3a9f4931
......@@ -117,6 +117,7 @@ Request Parameter Name Value
====================== =========================
next The URI to redirect to when the process is finished
renew Force token renewal (no value parameter)
force Force logout current user (no value parameter)
====================== =========================
External systems outside the domain scope can acquire the user information by a cookie set identified by ASTAKOS_COOKIE_NAME setting.
......
=======
v0.3.3
======
- Updated grnet styles
- Several styling fixes
- Display page menu
- Minor improvements in cloudbar js and styles
- Use synnefo.lib.context_processors.cloudbar to display the cloudbar
You should set the CLOUDBAR_* settings to point to your astakos urls
(see sample conf file in snf-astakos-app/conf/20-snf-astakos-app-cloudbar.conf)
- Updated snf-common dependency to >=0.9.0
- New ASTAKOS_RE_USER_EMAIL_PATTERNS setting
- Support for multiple accounts authentication
- New --set-active and --set-inactive in modifyuser command
- Fixed circular redirects when visiting login page from the logout one
- Removed im.context_processors.cloudbar (now using snf-common processor)
v0.3.2
======
......
......@@ -4,6 +4,6 @@ global-include */templates/* */fixtures/* */static/*
global-exclude */.DS_Store
include astakos/settings.d/*
recursive-include astakos/im/templates/ *.html *.txt
recursive-include astakos/im/static/ *.js *.css *.less *.html *.txt *.png
recursive-include astakos/im/static/ *.js *.css *.less *.html *.txt *.png *.htc
prune docs
prune other
......@@ -73,6 +73,7 @@ ASTAKOS_LOGOUT_NEXT
ASTAKOS_BILLING_FIELDS ['id', 'is_active', 'provider', 'third_party_identifier'] AstakosUser fields to propagate in the billing system
ASTAKOS_QUEUE_CONNECTION The queue connection ex. 'rabbitmq://guest:guest@localhost:5672/astakos.userEvent.#'
(if it is not set, it does not send messages)
ASTAKOS_RE_USER_EMAIL_PATTERNS [] Email patterns that are automatically activated ex. ['^[a-zA-Z0-9\._-]+@grnet\.gr$']
============================== ============================================================================= ===========================================================================================
Administrator functions
......
......@@ -117,17 +117,23 @@ def get_services(request):
return HttpResponse(content=data, mimetype=mimetype)
def get_menu(request):
if request.method != 'GET':
raise BadRequest('Method not allowed.')
location = request.GET.get('location', '')
exclude = []
index_url = reverse('index')
login_url = reverse('login')
logout_url = reverse('astakos.im.views.logout')
absolute = lambda (url): request.build_absolute_uri(url)
index_url = absolute(reverse('astakos.im.views.index'))
if urlparse(location).query.rfind('next=') == -1:
l = index_url, login_url, logout_url
forbidden = []
for url in l:
url = url.rstrip('/')
forbidden.extend([url, url + '/', absolute(url), absolute(url + '/')])
if location not in forbidden:
index_url = '%s?next=%s' % (index_url, quote(location))
l = [{ 'url': index_url, 'name': "Sign in"}]
l = [{ 'url': absolute(index_url), 'name': "Sign in"}]
if request.user.is_authenticated():
l = []
l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
l.append({ 'url': absolute(reverse('astakos.im.views.index')),
'name': request.user.email})
l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
'name': "View your profile" })
......
......@@ -48,10 +48,11 @@ from urlparse import urljoin
from astakos.im.models import AstakosUser, Invitation
from astakos.im.forms import *
from astakos.im.util import get_invitation
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
import socket
import logging
import re
logger = logging.getLogger(__name__)
......@@ -78,7 +79,15 @@ def get_backend(request):
raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
return backend_class(request)
class InvitationsBackend(object):
class SignupBackend(object):
def _is_preaccepted(self, user):
# return True if user email matches specific patterns
for pattern in RE_USER_EMAIL_PATTERNS:
if re.match(pattern, user.email):
return True
return False
class InvitationsBackend(SignupBackend):
"""
A registration backend which implements the following workflow: a user
supplies the necessary registation information, if the request contains a valid
......@@ -93,6 +102,7 @@ class InvitationsBackend(object):
"""
self.request = request
self.invitation = get_invitation(request)
super(InvitationsBackend, self).__init__()
def get_signup_form(self, provider):
"""
......@@ -136,6 +146,8 @@ class InvitationsBackend(object):
If there is a valid, not-consumed invitation code for the specific user
returns True else returns False.
"""
if super(InvitationsBackend, self)._is_preaccepted(user):
return True
invitation = self.invitation
if not invitation:
return False
......@@ -145,7 +157,7 @@ class InvitationsBackend(object):
return False
@transaction.commit_manually
def signup(self, form, admin_email_template_name='im/admin_notification.txt'):
def signup(self, form, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
"""
Initially creates an inactive user account. If the user is preaccepted
(has a valid invitation code) the user is activated and if the request
......@@ -159,9 +171,18 @@ class InvitationsBackend(object):
try:
user = form.save()
if self._is_preaccepted(user):
user.is_active = True
user.save()
message = _('Registration completed. You can now login.')
if user.email_verified:
user.is_active = True
user.save()
message = _('Registration completed. You can now login.')
else:
try:
_send_verification(self.request, user, email_template_name)
message = _('Verification sent to %s' % user.email)
except (SMTPException, socket.error) as e:
status = messages.ERROR
name = 'strerror'
message = getattr(e, name) if hasattr(e, name) else e
else:
_send_notification(user, admin_email_template_name)
message = _('Your request for an account was successfully sent \
......@@ -183,7 +204,7 @@ class InvitationsBackend(object):
transaction.commit()
return status, message, user
class SimpleBackend(object):
class SimpleBackend(SignupBackend):
"""
A registration backend which implements the following workflow: a user
supplies the necessary registation information, an incative user account is
......@@ -191,6 +212,7 @@ class SimpleBackend(object):
"""
def __init__(self, request):
self.request = request
super(SimpleBackend, self).__init__()
def get_signup_form(self, provider):
"""
......@@ -207,7 +229,14 @@ class SimpleBackend(object):
ip = self.request.META.get('REMOTE_ADDR',
self.request.META.get('HTTP_X_REAL_IP', None))
return globals()[formclass](initial_data, ip=ip)
def _is_preaccepted(self, user):
if super(SimpleBackend, self)._is_preaccepted(user):
return True
if MODERATION_ENABLED:
return False
return True
@transaction.commit_manually
def signup(self, form, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
"""
......@@ -233,7 +262,7 @@ class SimpleBackend(object):
"""
user = form.save()
status = messages.SUCCESS
if MODERATION_ENABLED:
if not self._is_preaccepted(user):
try:
_send_notification(user, admin_email_template_name)
message = _('Your request for an account was successfully sent \
......
......@@ -33,8 +33,11 @@
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL, \
COOKIE_NAME
from astakos.im.api import get_menu
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils import simplejson as json
def im_modules(request):
return {'im_modules': IM_MODULES}
......@@ -51,18 +54,10 @@ def invitations(request):
def media(request):
return {'IM_STATIC_URL' : IM_STATIC_URL}
def cloudbar(request):
"""
Cloudbar configuration
"""
CB_LOCATION = getattr(settings, 'CLOUDBAR_LOCATION', IM_STATIC_URL + 'cloudbar/')
CB_COOKIE_NAME = getattr(settings, 'CLOUDBAR_COOKIE_NAME', COOKIE_NAME)
CB_ACTIVE_SERVICE = getattr(settings, 'CLOUDBAR_ACTIVE_SERVICE', 'cloud')
def menu(request):
absolute = lambda (url): request.build_absolute_uri(url)
return {'CLOUDBAR_LOC': CB_LOCATION,
'CLOUDBAR_COOKIE_NAME': CB_COOKIE_NAME,
'ACTIVE_SERVICE': CB_ACTIVE_SERVICE,
'GET_SERVICES_URL': absolute(reverse('astakos.im.api.get_services')),
'GET_MENU_URL': absolute(reverse('astakos.im.api.get_menu'))}
resp = get_menu(request)
menu_items = json.loads(resp.content)[1:]
for item in menu_items:
item['is_active'] = absolute(request.path) == item['url']
return {'menu':menu_items}
......@@ -54,7 +54,7 @@ class LocalUserCreationForm(UserCreationForm):
"""
Extends the built in UserCreationForm in several ways:
* Adds email, first_name and last_name field.
* Adds email, first_name, last_name, recaptcha_challenge_field, recaptcha_response_field field.
* The username field isn't visible and it is assigned a generated id.
* User created is not active.
"""
......@@ -134,7 +134,9 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
"""
super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['email', 'inviter', 'first_name',
'last_name', 'password1', 'password2']
'last_name', 'password1', 'password2',
'recaptcha_challenge_field',
'recaptcha_response_field']
#set readonly form fields
self.fields['inviter'].widget.attrs['readonly'] = True
self.fields['email'].widget.attrs['readonly'] = True
......@@ -144,7 +146,8 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
user = super(InvitedLocalUserCreationForm, self).save(commit=False)
level = user.invitation.inviter.level + 1
user.level = level
user.invitations = INVITATIONS_PER_LEVEL[level]
user.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
user.email_verified = True
if commit:
user.save()
return user
......
......@@ -40,7 +40,7 @@ from ._common import get_user
class Command(BaseCommand):
args = "<user id or email> [user id or email] ..."
args = "<user ID or email> [user ID or email] ..."
help = "Activates one or more users"
@transaction.commit_manually
......
......@@ -36,6 +36,7 @@ import socket
from smtplib import SMTPException
from django.core.management.base import BaseCommand, CommandError
from django.db.utils import IntegrityError
from astakos.im.functions import invite
......@@ -63,5 +64,7 @@ class Command(BaseCommand):
self.stdout.write("Invitation sent to '%s'\n" % (email,))
except (SMTPException, socket.error) as e:
raise CommandError("Error sending the invitation")
except IntegrityError, e:
raise CommandError("There is already an invitation for %s" % (email,))
else:
raise CommandError("No invitations left")
......@@ -39,7 +39,7 @@ from ._common import get_user
class Command(BaseCommand):
args = "<user_id or email>"
args = "<user ID or email>"
help = "Modify a user's attributes"
option_list = BaseCommand.option_list + (
......@@ -47,6 +47,10 @@ class Command(BaseCommand):
dest='invitations',
metavar='NUM',
help="Update user's invitations"),
make_option('--level',
dest='level',
metavar='NUM',
help="Update user's level"),
make_option('--password',
dest='password',
metavar='PASSWORD',
......@@ -66,7 +70,12 @@ class Command(BaseCommand):
dest='noadmin',
default=False,
help="Revoke user's admin rights"),
make_option('--inactive',
make_option('--set-active',
action='store_true',
dest='active',
default=False,
help="Change user's state to inactive"),
make_option('--set-inactive',
action='store_true',
dest='inactive',
default=False,
......@@ -75,7 +84,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("Please provide a user_id or email")
raise CommandError("Please provide a user ID or email")
user = get_user(args[0])
if not user:
......@@ -86,10 +95,19 @@ class Command(BaseCommand):
elif options.get('noadmin'):
user.is_superuser = False
if options.get('active'):
user.is_active = True
elif options.get('inactive'):
user.is_active = False
invitations = options.get('invitations')
if invitations is not None:
user.invitations = int(invitations)
level = options.get('level')
if level is not None:
user.level = int(level)
password = options.get('password')
if password is not None:
user.set_password(password)
......@@ -97,6 +115,4 @@ class Command(BaseCommand):
if options['renew_token']:
user.renew_token()
if options.get('inactive'):
user.is_active = False
user.save()
\ No newline at end of file
user.save()
......@@ -39,6 +39,7 @@ from ._common import format_bool, format_date
class Command(BaseCommand):
args = "<invitation ID>"
help = "Show invitation info"
def handle(self, *args, **options):
......
......@@ -39,11 +39,12 @@ from ._common import format_bool, format_date
class Command(BaseCommand):
args = "<user ID or email>"
help = "Show user info"
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("Please provide a user_id or email")
raise CommandError("Please provide a user ID or email")
email_or_id = args[0]
try:
......
# 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 unique constraint on 'Invitation', fields ['username']
db.create_unique('im_invitation', ['username'])
def backwards(self, orm):
# Removing unique constraint on 'Invitation', fields ['username']
db.delete_unique('im_invitation', ['username'])
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'im.astakosuser': {
'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'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'}),
'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {}),
'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
},
'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.AstakosUser']"}),
'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'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
}
}
complete_apps = ['im']
# 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 'AstakosUser.email_verified'
db.add_column('im_astakosuser', 'email_verified', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
def backwards(self, orm):
# Deleting field 'AstakosUser.email_verified'
db.delete_column('im_astakosuser', 'email_verified')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'im.astakosuser': {
'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'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'}),
'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'updated': ('django.db.models.fields.DateTimeField', [], {}),
'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
},
'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.AstakosUser']"}),
'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'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
}
}
complete_apps = ['im']
......@@ -59,7 +59,7 @@ class AstakosUser(User):
#for invitations
user_level = DEFAULT_USER_LEVEL
level = models.IntegerField('Inviter level', default=user_level)
invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL[user_level])
invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
auth_token = models.CharField('Authentication Token', max_length=32,
null=True, blank=True)
......@@ -72,6 +72,8 @@ class AstakosUser(User):
# ex. screen_name for twitter, eppn for shibboleth
third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
email_verified = models.BooleanField('Email verified?', default=False)
@property