From 8316698a5a8ea26eac9d3a3545890988cc1e300c Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Wed, 7 Mar 2012 18:02:48 +0200 Subject: [PATCH] Automatically activate users whose email matches specific email patterns defined in settings (ASTAKOS_RE_USER_EMAIL_PATTERNS) & minor other fixes: * introduce email_verified AstakosUser field and if False during signup send email verification * show recaptcha fields in signup form in case of invited user * do not allow multiple invitations with the same receiver * enable user level modification by ``snf-manage modifyuser`` Refs: #2166 --- snf-astakos-app/README | 1 + snf-astakos-app/astakos/im/backends.py | 47 +++++++++-- snf-astakos-app/astakos/im/forms.py | 9 +- .../astakos/im/management/commands/inviteuser.py | 3 + .../astakos/im/management/commands/modifyuser.py | 8 ++ .../0003_auto__add_unique_invitation_username.py | 87 +++++++++++++++++++ ...4_auto__add_field_astakosuser_email_verified.py | 88 ++++++++++++++++++++ snf-astakos-app/astakos/im/models.py | 6 +- snf-astakos-app/astakos/im/settings.py | 2 + snf-astakos-app/astakos/im/util.py | 2 +- snf-astakos-app/astakos/im/views.py | 8 +- 11 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 snf-astakos-app/astakos/im/migrations/0003_auto__add_unique_invitation_username.py create mode 100644 snf-astakos-app/astakos/im/migrations/0004_auto__add_field_astakosuser_email_verified.py diff --git a/snf-astakos-app/README b/snf-astakos-app/README index f6927bf..18b0b1f 100644 --- a/snf-astakos-app/README +++ b/snf-astakos-app/README @@ -70,6 +70,7 @@ ASTAKOS_RECAPTCHA_OPTIONS {'theme': 'white'} (see: http://code.google.com/intl/el-GR/apis/recaptcha/docs/customization.html) ASTAKOS_LOGOUT_NEXT Where the user should be redirected after logout (if not set and no next parameter is defined it renders login page with message) +ASTAKOS_RE_USER_EMAIL_PATTERNS [] Email patterns that are automatically activated ex. ['^[a-zA-Z0-9\._-]+@grnet\.gr$'] ============================== ============================================================================= =========================================================================================== Administrator functions diff --git a/snf-astakos-app/astakos/im/backends.py b/snf-astakos-app/astakos/im/backends.py index 12e7037..9bcaf54 100644 --- a/snf-astakos-app/astakos/im/backends.py +++ b/snf-astakos-app/astakos/im/backends.py @@ -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 \ diff --git a/snf-astakos-app/astakos/im/forms.py b/snf-astakos-app/astakos/im/forms.py index 633312c..8ca90b1 100644 --- a/snf-astakos-app/astakos/im/forms.py +++ b/snf-astakos-app/astakos/im/forms.py @@ -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 diff --git a/snf-astakos-app/astakos/im/management/commands/inviteuser.py b/snf-astakos-app/astakos/im/management/commands/inviteuser.py index a16b64d..ecc8fd3 100644 --- a/snf-astakos-app/astakos/im/management/commands/inviteuser.py +++ b/snf-astakos-app/astakos/im/management/commands/inviteuser.py @@ -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") diff --git a/snf-astakos-app/astakos/im/management/commands/modifyuser.py b/snf-astakos-app/astakos/im/management/commands/modifyuser.py index 2a6eeb9..cf77dcd 100644 --- a/snf-astakos-app/astakos/im/management/commands/modifyuser.py +++ b/snf-astakos-app/astakos/im/management/commands/modifyuser.py @@ -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', @@ -100,6 +104,10 @@ class Command(BaseCommand): 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) diff --git a/snf-astakos-app/astakos/im/migrations/0003_auto__add_unique_invitation_username.py b/snf-astakos-app/astakos/im/migrations/0003_auto__add_unique_invitation_username.py new file mode 100644 index 0000000..3c67560 --- /dev/null +++ b/snf-astakos-app/astakos/im/migrations/0003_auto__add_unique_invitation_username.py @@ -0,0 +1,87 @@ +# 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'] diff --git a/snf-astakos-app/astakos/im/migrations/0004_auto__add_field_astakosuser_email_verified.py b/snf-astakos-app/astakos/im/migrations/0004_auto__add_field_astakosuser_email_verified.py new file mode 100644 index 0000000..6b27b42 --- /dev/null +++ b/snf-astakos-app/astakos/im/migrations/0004_auto__add_field_astakosuser_email_verified.py @@ -0,0 +1,88 @@ +# 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'] diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py index 9f73e0a..09c047c 100644 --- a/snf-astakos-app/astakos/im/models.py +++ b/snf-astakos-app/astakos/im/models.py @@ -56,7 +56,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) @@ -69,6 +69,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 def realname(self): return '%s %s' %(self.first_name, self.last_name) @@ -127,7 +129,7 @@ class Invitation(models.Model): inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent', null=True) realname = models.CharField('Real name', max_length=255) - username = models.CharField('Unique ID', max_length=255) + username = models.CharField('Unique ID', max_length=255, unique=True) code = models.BigIntegerField('Invitation code', db_index=True) #obsolete: we keep it just for transfering the data is_accepted = models.BooleanField('Accepted?', default=False) diff --git a/snf-astakos-app/astakos/im/settings.py b/snf-astakos-app/astakos/im/settings.py index 631a5cb..4a0b1e6 100644 --- a/snf-astakos-app/astakos/im/settings.py +++ b/snf-astakos-app/astakos/im/settings.py @@ -61,3 +61,5 @@ RECAPTCHA_OPTIONS = getattr(settings, 'ASTAKOS_RECAPTCHA_OPTIONS', {'theme': 'wh # Set where the user should be redirected after logout LOGOUT_NEXT = getattr(settings, 'ASTAKOS_LOGOUT_NEXT', '') +# Set user email patterns that are automatically activated +RE_USER_EMAIL_PATTERNS = getattr(settings, 'ASTAKOS_RE_USER_EMAIL_PATTERNS', []) \ No newline at end of file diff --git a/snf-astakos-app/astakos/im/util.py b/snf-astakos-app/astakos/im/util.py index 285f460..c82eebb 100644 --- a/snf-astakos-app/astakos/im/util.py +++ b/snf-astakos-app/astakos/im/util.py @@ -75,7 +75,7 @@ def get_or_create_user(email, realname='', first_name='', last_name='', affiliat 'password':password, 'affiliation':affiliation, 'level':level, - 'invitations':INVITATIONS_PER_LEVEL[level], + 'invitations':INVITATIONS_PER_LEVEL.get(level, 0), 'provider':provider, 'realname':realname, 'first_name':first_name, diff --git a/snf-astakos-app/astakos/im/views.py b/snf-astakos-app/astakos/im/views.py index 31011e6..20249a9 100644 --- a/snf-astakos-app/astakos/im/views.py +++ b/snf-astakos-app/astakos/im/views.py @@ -50,6 +50,7 @@ from django.db import transaction from django.contrib.auth import logout as auth_logout from django.utils.http import urlencode from django.http import HttpResponseRedirect +from django.db.utils import IntegrityError from astakos.im.models import AstakosUser, Invitation from astakos.im.backends import get_backend @@ -177,6 +178,10 @@ def invite(request, template_name='im/invitations.html', extra_context={}): status = messages.ERROR message = getattr(e, 'strerror', '') transaction.rollback() + except IntegrityError, e: + status = messages.ERROR + message = _('There is already invitation for %s' % username) + transaction.rollback() else: status = messages.ERROR message = _('No invitations left') @@ -261,7 +266,7 @@ def signup(request, on_failure='im/signup.html', on_success='im/signup_complete. Upon successful user creation if ``next`` url parameter is present the user is redirected there otherwise renders the same page with a success message. - On unsuccessful creation, renders the same page with an error message. + On unsuccessful creation, renders ``on_failure`` with an error message. **Arguments** @@ -405,5 +410,6 @@ def activate(request): return HttpResponseBadRequest('No such user') user.is_active = True + user.email_verified = True user.save() return prepare_response(request, user, next, renew=True) -- 1.7.10.4