from time import asctime
from datetime import datetime, timedelta
from base64 import b64encode
+from urlparse import urlparse
+from urllib import quote
from random import randint
from collections import defaultdict
from django.db import models, IntegrityError
from django.contrib.auth.models import User, UserManager, Group, Permission
from django.utils.translation import ugettext as _
-from django.core.exceptions import ValidationError
from django.db import transaction
-from django.db.models.signals import (pre_save, post_save, post_syncdb,
- post_delete)
+from django.core.exceptions import ValidationError
+from django.db.models.signals import (
+ pre_save, post_save, post_syncdb, post_delete
+)
from django.contrib.contenttypes.models import ContentType
from django.dispatch import Signal
from django.db.models import Q
-
-from astakos.im.settings import (DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
- AUTH_TOKEN_DURATION, BILLING_FIELDS,
- EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL)
-from astakos.im.endpoints.quotaholder import (register_users, send_quota,
- register_resources)
+from django.core.urlresolvers import reverse
+from django.utils.http import int_to_base36
+from django.contrib.auth.tokens import default_token_generator
+from django.conf import settings
+from django.utils.importlib import import_module
+from django.core.validators import email_re
+from django.core.exceptions import PermissionDenied
+from django.views.generic.create_update import lookup_object
+from django.core.exceptions import ObjectDoesNotExist
+
+from astakos.im.settings import (
+ DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
+ AUTH_TOKEN_DURATION, BILLING_FIELDS,
+ EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
+ SITENAME, SERVICES,
+ PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
+ PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
+ PROJECT_MEMBERSHIP_CHANGE_SUBJECT
+)
+from astakos.im.endpoints.qh import (
+ register_users, send_quota, register_resources
+)
+from astakos.im import auth_providers
from astakos.im.endpoints.aquarium.producer import report_user_event
from astakos.im.functions import send_invitation
-from astakos.im.tasks import propagate_groupmembers_quota
-from astakos.im.functions import send_invitation
+#from astakos.im.tasks import propagate_groupmembers_quota
+
+from astakos.im.notifications import build_notification
+
+import astakos.im.messages as astakos_messages
logger = logging.getLogger(__name__)
DEFAULT_CONTENT_TYPE = None
-try:
- content_type = ContentType.objects.get(app_label='im', model='astakosuser')
-except:
- content_type = DEFAULT_CONTENT_TYPE
+_content_type = None
+
+PENDING, APPROVED, REPLACED, UNKNOWN = 'Pending', 'Approved', 'Replaced', 'Unknown'
+
+def get_content_type():
+ global _content_type
+ if _content_type is not None:
+ return _content_type
+
+ try:
+ content_type = ContentType.objects.get(app_label='im', model='astakosuser')
+ except:
+ content_type = DEFAULT_CONTENT_TYPE
+ _content_type = content_type
+ return content_type
RESOURCE_SEPARATOR = '.'
+inf = float('inf')
class Service(models.Model):
- name = models.CharField('Name', max_length=255, unique=True, db_index=True)
+ name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
url = models.FilePathField()
icon = models.FilePathField(blank=True)
- auth_token = models.CharField('Authentication Token', max_length=32,
+ 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_created = models.DateTimeField(_('Token creation date'), null=True)
auth_token_expires = models.DateTimeField(
- 'Token expiration date', null=True)
-
- def save(self, **kwargs):
- if not self.id:
- self.renew_token()
- super(Service, self).save(**kwargs)
+ _('Token expiration date'), null=True)
def renew_token(self):
md5 = hashlib.md5()
def resources(self, resources):
for s in resources:
self.resource_set.create(**s)
-
+
def add_resource(self, service, resource, uplimit, update=True):
"""Raises ObjectDoesNotExist, IntegrityError"""
resource = Resource.objects.get(service__name=service, name=resource)
class ResourceMetadata(models.Model):
- key = models.CharField('Name', max_length=255, unique=True, db_index=True)
- value = models.CharField('Value', max_length=255)
+ key = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
+ value = models.CharField(_('Value'), max_length=255)
class Resource(models.Model):
- name = models.CharField('Name', max_length=255, unique=True, db_index=True)
+ name = models.CharField(_('Name'), max_length=255)
meta = models.ManyToManyField(ResourceMetadata)
service = models.ForeignKey(Service)
- desc = models.TextField('Description', null=True)
- unit = models.CharField('Name', null=True, max_length=255)
- group = models.CharField('Group', null=True, max_length=255)
+ desc = models.TextField(_('Description'), null=True)
+ unit = models.CharField(_('Name'), null=True, max_length=255)
+ group = models.CharField(_('Group'), null=True, max_length=255)
+
+ class Meta:
+ unique_together = ("name", "service")
def __str__(self):
return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
class GroupKind(models.Model):
- name = models.CharField('Name', max_length=255, unique=True, db_index=True)
+ name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
def __str__(self):
return self.name
class AstakosGroup(Group):
kind = models.ForeignKey(GroupKind)
homepage = models.URLField(
- 'Homepage Url', max_length=255, null=True, blank=True)
- desc = models.TextField('Description', null=True)
+ _('Homepage Url'), max_length=255, null=True, blank=True)
+ desc = models.TextField(_('Description'), null=True)
policy = models.ManyToManyField(
Resource,
null=True,
through='AstakosGroupQuota'
)
creation_date = models.DateTimeField(
- 'Creation date',
+ _('Creation date'),
default=datetime.now()
)
- issue_date = models.DateTimeField('Issue date', null=True)
+ issue_date = models.DateTimeField(
+ _('Start date'),
+ null=True
+ )
expiration_date = models.DateTimeField(
- 'Expiration date',
- null=True
+ _('Expiration date'),
+ null=True
)
moderation_enabled = models.BooleanField(
- 'Moderated membership?',
+ _('Moderated membership?'),
default=True
)
approval_date = models.DateTimeField(
- 'Activation date',
+ _('Activation date'),
null=True,
blank=True
)
estimated_participants = models.PositiveIntegerField(
- 'Estimated #members',
+ _('Estimated #members'),
null=True,
blank=True,
)
max_participants = models.PositiveIntegerField(
- 'Maximum numder of participants',
+ _('Maximum numder of participants'),
null=True,
blank=True
)
-
+
@property
def is_disabled(self):
if not self.approval_date:
self.approval_date = datetime.now()
self.save()
quota_disturbed.send(sender=self, users=self.approved_members)
- propagate_groupmembers_quota.apply_async(
- args=[self], eta=self.issue_date)
- propagate_groupmembers_quota.apply_async(
- args=[self], eta=self.expiration_date)
+ #propagate_groupmembers_quota.apply_async(
+ # args=[self], eta=self.issue_date)
+ #propagate_groupmembers_quota.apply_async(
+ # args=[self], eta=self.expiration_date)
def disable(self):
if self.is_disabled:
def approve_member(self, person):
m, created = self.membership_set.get_or_create(person=person)
- # update date_joined in any case
- m.date_joined = datetime.now()
- m.save()
-
- def disapprove_member(self, person):
- self.membership_set.remove(person=person)
+ m.approve()
@property
def members(self):
q = self.membership_set.select_related().all()
return [m.person for m in q]
-
+
@property
def approved_members(self):
q = self.membership_set.select_related().all()
def quota(self):
d = defaultdict(int)
for q in self.astakosgroupquota_set.select_related().all():
- d[q.resource] += q.uplimit
+ d[q.resource] += q.uplimit or inf
return d
-
+
def add_policy(self, service, resource, uplimit, update=True):
"""Raises ObjectDoesNotExist, IntegrityError"""
- print '#', locals()
resource = Resource.objects.get(service__name=service, name=resource)
if update:
AstakosGroupQuota.objects.update_or_create(
else:
q = self.astakosgroupquota_set
q.create(resource=resource, uplimit=uplimit)
-
+
@property
def policies(self):
return self.astakosgroupquota_set.select_related().all()
uplimit = p.get('uplimit', 0)
update = p.get('update', True)
self.add_policy(service, resource, uplimit, update)
-
+
@property
def owners(self):
return self.owner.all()
self.owner = l
map(self.approve_member, l)
+_default_quota = {}
+def get_default_quota():
+ global _default_quota
+ if _default_quota:
+ return _default_quota
+ for s, data in SERVICES.iteritems():
+ map(
+ lambda d:_default_quota.update(
+ {'%s%s%s' % (s, RESOURCE_SEPARATOR, d.get('name')):d.get('uplimit', 0)}
+ ),
+ data.get('resources', {})
+ )
+ return _default_quota
+
+class AstakosUserManager(UserManager):
+
+ def get_auth_provider_user(self, provider, **kwargs):
+ """
+ Retrieve AstakosUser instance associated with the specified third party
+ id.
+ """
+ kwargs = dict(map(lambda x: ('auth_providers__%s' % x[0], x[1]),
+ kwargs.iteritems()))
+ return self.get(auth_providers__module=provider, **kwargs)
class AstakosUser(User):
"""
Extends ``django.contrib.auth.models.User`` by defining additional fields.
"""
- # Use UserManager to get the create_user method, etc.
- objects = UserManager()
+ affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
+ null=True)
+
+ # DEPRECATED FIELDS: provider, third_party_identifier moved in
+ # AstakosUserProvider model.
+ provider = models.CharField(_('Provider'), max_length=255, blank=True,
+ null=True)
+ # ex. screen_name for twitter, eppn for shibboleth
+ third_party_identifier = models.CharField(_('Third-party identifier'),
+ max_length=255, null=True,
+ blank=True)
- affiliation = models.CharField('Affiliation', max_length=255, blank=True)
- provider = models.CharField('Provider', max_length=255, blank=True)
#for invitations
user_level = DEFAULT_USER_LEVEL
- level = models.IntegerField('Inviter level', default=user_level)
+ level = models.IntegerField(_('Inviter level'), default=user_level)
invitations = models.IntegerField(
- 'Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
+ _('Invitations left'), default=INVITATIONS_PER_LEVEL.get(user_level, 0))
- auth_token = models.CharField('Authentication Token', max_length=32,
+ 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_created = models.DateTimeField(_('Token creation date'), null=True)
auth_token_expires = models.DateTimeField(
- 'Token expiration date', null=True)
+ _('Token expiration date'), null=True)
- updated = models.DateTimeField('Update date')
- is_verified = models.BooleanField('Is verified?', default=False)
+ updated = models.DateTimeField(_('Update date'))
+ is_verified = models.BooleanField(_('Is verified?'), default=False)
- # 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)
- email_verified = models.BooleanField('Email verified?', default=False)
-
- has_credits = models.BooleanField('Has credits?', default=False)
+ has_credits = models.BooleanField(_('Has credits?'), default=False)
has_signed_terms = models.BooleanField(
- 'I agree with the terms', default=False)
+ _('I agree with the terms'), default=False)
date_signed_terms = models.DateTimeField(
- 'Signed terms date', null=True, blank=True)
+ _('Signed terms date'), null=True, blank=True)
activation_sent = models.DateTimeField(
- 'Activation sent data', null=True, blank=True)
+ _('Activation sent data'), null=True, blank=True)
policy = models.ManyToManyField(
Resource, null=True, through='AstakosUserQuota')
astakos_groups = models.ManyToManyField(
AstakosGroup, verbose_name=_('agroups'), blank=True,
- help_text=_("""In addition to the permissions manually assigned, this
- user will also get all permissions granted to each group
- he/she is in."""),
+ help_text=_(astakos_messages.ASTAKOSUSER_GROUPS_HELP),
through='Membership')
__has_signed_terms = False
- disturbed_quota = models.BooleanField('Needs quotaholder syncing',
+ disturbed_quota = models.BooleanField(_('Needs quotaholder syncing'),
default=False, db_index=True)
+ objects = AstakosUserManager()
+
owner = models.ManyToManyField(
AstakosGroup, related_name='owner', null=True)
def __init__(self, *args, **kwargs):
super(AstakosUser, self).__init__(*args, **kwargs)
self.__has_signed_terms = self.has_signed_terms
- if not self.id and not self.is_active:
+ if not self.id:
self.is_active = False
@property
def add_permission(self, pname):
if self.has_perm(pname):
return
- p, created = Permission.objects.get_or_create(codename=pname,
- name=pname.capitalize(),
- content_type=content_type)
+ p, created = Permission.objects.get_or_create(
+ codename=pname,
+ name=pname.capitalize(),
+ content_type=get_content_type())
self.user_permissions.add(p)
def remove_permission(self, pname):
if self.has_perm(pname):
return
p = Permission.objects.get(codename=pname,
- content_type=content_type)
+ content_type=get_content_type())
self.user_permissions.remove(p)
@property
def quota(self):
"""Returns a dict with the sum of quota limits per resource"""
d = defaultdict(int)
+ default_quota = get_default_quota()
+ d.update(default_quota)
for q in self.policies:
- d[q.resource] += q.uplimit
- for m in self.extended_groups:
- if not m.is_approved:
+ d[q.resource] += q.uplimit or inf
+ for m in self.projectmembership_set.select_related().all():
+ if not m.acceptance_date:
continue
- g = m.group
- if not g.is_enabled:
+ p = m.project
+ if not p.is_active:
continue
- for r, uplimit in g.quota.iteritems():
- d[r] += uplimit
-
+ grants = p.application.definition.projectresourcegrant_set.all()
+ for g in grants:
+ d[str(g.resource)] += g.member_limit or inf
# TODO set default for remaining
return d
@extended_groups.setter
def extended_groups(self, groups):
#TODO exceptions
- for name in groups:
+ for name in (groups or ()):
group = AstakosGroup.objects.get(name=name)
self.membership_set.create(group=group)
if not self.id:
# set username
- while not self.username:
- username = uuid.uuid4().hex[:30]
- try:
- AstakosUser.objects.get(username=username)
- except AstakosUser.DoesNotExist:
- self.username = username
- if not self.provider:
- self.provider = 'local'
- self.email = self.email.lower()
+ self.username = self.email
+
self.validate_unique_email_isactive()
if self.is_active and self.activation_sent:
# reset the activation sent
super(AstakosUser, self).save(**kwargs)
- def renew_token(self):
+ def renew_token(self, flush_sessions=False, current_key=None):
md5 = hashlib.md5()
+ md5.update(settings.SECRET_KEY)
md5.update(self.username)
md5.update(self.realname.encode('ascii', 'ignore'))
md5.update(asctime())
self.auth_token = b64encode(md5.digest())
self.auth_token_created = datetime.now()
self.auth_token_expires = self.auth_token_created + \
- timedelta(hours=AUTH_TOKEN_DURATION)
+ timedelta(hours=AUTH_TOKEN_DURATION)
+ if flush_sessions:
+ self.flush_sessions(current_key)
msg = 'Token renewed for %s' % self.email
logger.log(LOGGING_LEVEL, msg)
+ def flush_sessions(self, current_key=None):
+ q = self.sessions
+ if current_key:
+ q = q.exclude(session_key=current_key)
+
+ keys = q.values_list('session_key', flat=True)
+ if keys:
+ msg = 'Flushing sessions: %s' % ','.join(keys)
+ logger.log(LOGGING_LEVEL, msg, [])
+ engine = import_module(settings.SESSION_ENGINE)
+ for k in keys:
+ s = engine.SessionStore(k)
+ s.flush()
+
def __unicode__(self):
return '%s (%s)' % (self.realname, self.email)
def conflicting_email(self):
q = AstakosUser.objects.exclude(username=self.username)
- q = q.filter(email=self.email)
+ q = q.filter(email__iexact=self.email)
if q.count() != 0:
return True
return False
"""
Implements a unique_together constraint for email and is_active fields.
"""
- q = AstakosUser.objects.exclude(username=self.username)
- q = q.filter(email=self.email)
- q = q.filter(is_active=self.is_active)
+ q = AstakosUser.objects.all()
+ q = q.filter(email = self.email)
+ q = q.filter(is_active = self.is_active)
+ if self.id:
+ q = q.filter(~Q(id = self.id))
if q.count() != 0:
- raise ValidationError({'__all__': [_('Another account with the same email & is_active combination found.')]})
+ raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
@property
def signed_terms(self):
return False
return True
- def store_disturbed_quota(self, set=True):
- self.disturbed_qutoa = set
- self.save()
+ def set_invitations_level(self):
+ """
+ Update user invitation level
+ """
+ level = self.invitation.inviter.level + 1
+ self.level = level
+ self.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
+
+ def can_login_with_auth_provider(self, provider):
+ if not self.has_auth_provider(provider):
+ return False
+ else:
+ return auth_providers.get_provider(provider).is_available_for_login()
+
+ def can_add_auth_provider(self, provider, **kwargs):
+ provider_settings = auth_providers.get_provider(provider)
+ if not provider_settings.is_available_for_login():
+ return False
+
+ if self.has_auth_provider(provider) and \
+ provider_settings.one_per_user:
+ return False
+
+ if 'identifier' in kwargs:
+ try:
+ # provider with specified params already exist
+ existing_user = AstakosUser.objects.get_auth_provider_user(provider,
+ **kwargs)
+ except AstakosUser.DoesNotExist:
+ return True
+ else:
+ return False
+
+ return True
+
+ def can_remove_auth_provider(self, provider):
+ if len(self.get_active_auth_providers()) <= 1:
+ return False
+ return True
+
+ def can_change_password(self):
+ return self.has_auth_provider('local', auth_backend='astakos')
+
+ def has_auth_provider(self, provider, **kwargs):
+ return bool(self.auth_providers.filter(module=provider,
+ **kwargs).count())
+
+ def add_auth_provider(self, provider, **kwargs):
+ if self.can_add_auth_provider(provider, **kwargs):
+ self.auth_providers.create(module=provider, active=True, **kwargs)
+ else:
+ raise Exception('Cannot add provider')
+
+ def add_pending_auth_provider(self, pending):
+ """
+ Convert PendingThirdPartyUser object to AstakosUserAuthProvider entry for
+ the current user.
+ """
+ if not isinstance(pending, PendingThirdPartyUser):
+ pending = PendingThirdPartyUser.objects.get(token=pending)
+
+ provider = self.add_auth_provider(pending.provider,
+ identifier=pending.third_party_identifier)
+
+ if email_re.match(pending.email or '') and pending.email != self.email:
+ self.additionalmail_set.get_or_create(email=pending.email)
+
+ pending.delete()
+ return provider
+
+ def remove_auth_provider(self, provider, **kwargs):
+ self.auth_providers.get(module=provider, **kwargs).delete()
+
+ # user urls
+ def get_resend_activation_url(self):
+ return reverse('send_activation', {'user_id': self.pk})
+
+ def get_activation_url(self, nxt=False):
+ url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
+ quote(self.auth_token))
+ if nxt:
+ url += "&next=%s" % quote(nxt)
+ return url
+
+ def get_password_reset_url(self, token_generator=default_token_generator):
+ return reverse('django.contrib.auth.views.password_reset_confirm',
+ kwargs={'uidb36':int_to_base36(self.id),
+ 'token':token_generator.make_token(self)})
+
+ def get_auth_providers(self):
+ return self.auth_providers.all()
+
+ def get_available_auth_providers(self):
+ """
+ Returns a list of providers available for user to connect to.
+ """
+ providers = []
+ for module, provider_settings in auth_providers.PROVIDERS.iteritems():
+ if self.can_add_auth_provider(module):
+ providers.append(provider_settings(self))
+
+ return providers
+
+ def get_active_auth_providers(self):
+ providers = []
+ for provider in self.auth_providers.active():
+ if auth_providers.get_provider(provider.module).is_available_for_login():
+ providers.append(provider)
+ return providers
+
+ @property
+ def auth_providers_display(self):
+ return ",".join(map(lambda x:unicode(x), self.auth_providers.active()))
+
+
+class AstakosUserAuthProviderManager(models.Manager):
+
+ def active(self):
+ return self.filter(active=True)
+
+
+class AstakosUserAuthProvider(models.Model):
+ """
+ Available user authentication methods.
+ """
+ affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
+ null=True, default=None)
+ user = models.ForeignKey(AstakosUser, related_name='auth_providers')
+ module = models.CharField(_('Provider'), max_length=255, blank=False,
+ default='local')
+ identifier = models.CharField(_('Third-party identifier'),
+ max_length=255, null=True,
+ blank=True)
+ active = models.BooleanField(default=True)
+ auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
+ default='astakos')
+
+ objects = AstakosUserAuthProviderManager()
+
+ class Meta:
+ unique_together = (('identifier', 'module', 'user'), )
+
+ @property
+ def settings(self):
+ return auth_providers.get_provider(self.module)
+
+ @property
+ def details_display(self):
+ return self.settings.details_tpl % self.__dict__
+
+ def can_remove(self):
+ return self.user.can_remove_auth_provider(self.module)
+
+ def delete(self, *args, **kwargs):
+ ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs)
+ if self.module == 'local':
+ self.user.set_unusable_password()
+ self.user.save()
+ return ret
+
+ def __repr__(self):
+ return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
+
+ def __unicode__(self):
+ if self.identifier:
+ return "%s:%s" % (self.module, self.identifier)
+ if self.auth_backend:
+ return "%s:%s" % (self.module, self.auth_backend)
+ return self.module
+
class Membership(models.Model):
return False
def approve(self):
+ if self.is_approved:
+ return
+ if self.group.max_participants:
+ assert len(self.group.approved_members) + 1 <= self.group.max_participants, \
+ 'Maximum participant number has been reached.'
self.date_joined = datetime.now()
self.save()
quota_disturbed.send(sender=self, users=(self.person,))
def disapprove(self):
+ approved = self.is_approved()
self.delete()
- quota_disturbed.send(sender=self, users=(self.person,))
+ if approved:
+ quota_disturbed.send(sender=self, users=(self.person,))
-class AstakosQuotaManager(models.Manager):
+class ExtendedManager(models.Manager):
def _update_or_create(self, **kwargs):
assert kwargs, \
'update_or_create() must be passed at least one keyword argument'
update_or_create = _update_or_create
class AstakosGroupQuota(models.Model):
- objects = AstakosQuotaManager()
- limit = models.PositiveIntegerField('Limit', null=True) # obsolete field
- uplimit = models.BigIntegerField('Up limit', null=True)
+ objects = ExtendedManager()
+ limit = models.PositiveIntegerField(_('Limit'), null=True) # obsolete field
+ uplimit = models.BigIntegerField(_('Up limit'), null=True)
resource = models.ForeignKey(Resource)
group = models.ForeignKey(AstakosGroup, blank=True)
unique_together = ("resource", "group")
class AstakosUserQuota(models.Model):
- objects = AstakosQuotaManager()
- limit = models.PositiveIntegerField('Limit', null=True) # obsolete field
- uplimit = models.BigIntegerField('Up limit', null=True)
+ objects = ExtendedManager()
+ limit = models.PositiveIntegerField(_('Limit'), null=True) # obsolete field
+ uplimit = models.BigIntegerField(_('Up limit'), null=True)
resource = models.ForeignKey(Resource)
user = models.ForeignKey(AstakosUser)
"""
date = models.DateTimeField(
- 'Issue date', db_index=True, default=datetime.now())
- location = models.CharField('Terms location', max_length=255)
+ _('Issue date'), db_index=True, default=datetime.now())
+ location = models.CharField(_('Terms location'), max_length=255)
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, unique=True)
- code = models.BigIntegerField('Invitation code', db_index=True)
- is_consumed = models.BooleanField('Consumed?', default=False)
- created = models.DateTimeField('Creation date', auto_now_add=True)
- consumed = models.DateTimeField('Consumption date', null=True, blank=True)
+ realname = models.CharField(_('Real name'), max_length=255)
+ username = models.CharField(_('Unique ID'), max_length=255, unique=True)
+ code = models.BigIntegerField(_('Invitation code'), db_index=True)
+ is_consumed = models.BooleanField(_('Consumed?'), default=False)
+ created = models.DateTimeField(_('Creation date'), auto_now_add=True)
+ consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True)
def __init__(self, *args, **kwargs):
super(Invitation, self).__init__(*args, **kwargs)
raise EmailChange.DoesNotExist
# is there an active user with this address?
try:
- AstakosUser.objects.get(email=email_change.new_email_address)
+ AstakosUser.objects.get(email__iexact=email_change.new_email_address)
except AstakosUser.DoesNotExist:
pass
else:
- raise ValueError(_('The new email address is reserved.'))
+ raise ValueError(_(astakos_messages.NEW_EMAIL_ADDR_RESERVED))
# update user
user = AstakosUser.objects.get(pk=email_change.user_id)
user.email = email_change.new_email_address
email_change.delete()
return user
except EmailChange.DoesNotExist:
- raise ValueError(_('Invalid activation key'))
+ raise ValueError(_(astakos_messages.INVALID_ACTIVATION_KEY))
class EmailChange(models.Model):
new_email_address = models.EmailField(_(u'new e-mail address'),
- help_text=_(u'Your old email address will be used until you verify your new one.'))
+ help_text=_(astakos_messages.EMAIL_CHANGE_NEW_ADDR_HELP))
user = models.ForeignKey(
AstakosUser, unique=True, related_name='emailchange_user')
requested_at = models.DateTimeField(default=datetime.now())
pass
return None
+class PendingThirdPartyUser(models.Model):
+ """
+ Model for registring successful third party user authentications
+ """
+ third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
+ provider = models.CharField(_('Provider'), max_length=255, blank=True)
+ email = models.EmailField(_('e-mail address'), blank=True, null=True)
+ first_name = models.CharField(_('first name'), max_length=30, blank=True)
+ last_name = models.CharField(_('last name'), max_length=30, blank=True)
+ affiliation = models.CharField('Affiliation', max_length=255, blank=True)
+ username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
+ token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
+ created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
+
+ class Meta:
+ unique_together = ("provider", "third_party_identifier")
+
+ @property
+ def realname(self):
+ return '%s %s' %(self.first_name, self.last_name)
+
+ @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]
+
+ def save(self, **kwargs):
+ if not self.id:
+ # set username
+ while not self.username:
+ username = uuid.uuid4().hex[:30]
+ try:
+ AstakosUser.objects.get(username = username)
+ except AstakosUser.DoesNotExist, e:
+ self.username = username
+ super(PendingThirdPartyUser, self).save(**kwargs)
+
+ def generate_token(self):
+ self.password = self.third_party_identifier
+ self.last_login = datetime.now()
+ self.token = default_token_generator.make_token(self)
+
+class SessionCatalog(models.Model):
+ session_key = models.CharField(_('session key'), max_length=40)
+ user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
+
+class MemberJoinPolicy(models.Model):
+ policy = models.CharField(_('Policy'), max_length=255, unique=True, db_index=True)
+ description = models.CharField(_('Description'), max_length=80)
+
+ def __str__(self):
+ return self.policy
+
+class MemberLeavePolicy(models.Model):
+ policy = models.CharField(_('Policy'), max_length=255, unique=True, db_index=True)
+ description = models.CharField(_('Description'), max_length=80)
+
+ def __str__(self):
+ return self.policy
+
+_auto_accept_join = False
+def get_auto_accept_join():
+ global _auto_accept_join
+ if _auto_accept_join is not False:
+ return _auto_accept_join
+ try:
+ auto_accept = MemberJoinPolicy.objects.get(policy='auto_accept')
+ except:
+ auto_accept = None
+ _auto_accept_join = auto_accept
+ return auto_accept
+
+_closed_join = False
+def get_closed_join():
+ global _closed_join
+ if _closed_join is not False:
+ return _closed_join
+ try:
+ closed = MemberJoinPolicy.objects.get(policy='closed')
+ except:
+ closed = None
+ _closed_join = closed
+ return closed
+
+_auto_accept_leave = False
+def get_auto_accept_leave():
+ global _auto_accept_leave
+ if _auto_accept_leave is not False:
+ return _auto_accept_leave
+ try:
+ auto_accept = MemberLeavePolicy.objects.get(policy='auto_accept')
+ except:
+ auto_accept = None
+ _auto_accept_leave = auto_accept
+ return auto_accept
+
+_closed_leave = False
+def get_closed_leave():
+ global _closed_leave
+ if _closed_leave is not False:
+ return _closed_leave
+ try:
+ closed = MemberLeavePolicy.objects.get(policy='closed')
+ except:
+ closed = None
+ _closed_leave = closed
+ return closeds
+
+class ProjectDefinition(models.Model):
+ name = models.CharField(max_length=80)
+ homepage = models.URLField(max_length=255, null=True, blank=True)
+ description = models.TextField(null=True)
+ start_date = models.DateTimeField()
+ end_date = models.DateTimeField()
+ member_join_policy = models.ForeignKey(MemberJoinPolicy)
+ member_leave_policy = models.ForeignKey(MemberLeavePolicy)
+ limit_on_members_number = models.PositiveIntegerField(null=True,blank=True)
+ resource_grants = models.ManyToManyField(
+ Resource,
+ null=True,
+ blank=True,
+ through='ProjectResourceGrant'
+ )
+
+ @property
+ def violated_resource_grants(self):
+ return False
+
+ def add_resource_policy(self, service, resource, uplimit, update=True):
+ """Raises ObjectDoesNotExist, IntegrityError"""
+ resource = Resource.objects.get(service__name=service, name=resource)
+ if update:
+ ProjectResourceGrant.objects.update_or_create(
+ project_definition=self,
+ resource=resource,
+ defaults={'member_limit': uplimit}
+ )
+ else:
+ q = self.projectresourcegrant_set
+ q.create(resource=resource, member_limit=uplimit)
+
+ @property
+ def resource_policies(self):
+ return self.projectresourcegrant_set.all()
+
+ @resource_policies.setter
+ def resource_policies(self, policies):
+ for p in policies:
+ service = p.get('service', None)
+ resource = p.get('resource', None)
+ uplimit = p.get('uplimit', 0)
+ update = p.get('update', True)
+ self.add_resource_policy(service, resource, uplimit, update)
+
+ def validate_name(self):
+ """
+ Validate name uniqueness among all active projects.
+ """
+ alive_projects = list(get_alive_projects())
+ q = filter(
+ lambda p: p.definition.name == self.name and \
+ p.application.id != self.projectapplication.id,
+ alive_projects
+ )
+ if q:
+ raise ValidationError(
+ _(astakos_messages.UNIQUE_PROJECT_NAME_CONSTRAIN_ERR)
+ )
+
+
+class ProjectResourceGrant(models.Model):
+ objects = ExtendedManager()
+ member_limit = models.BigIntegerField(null=True)
+ project_limit = models.BigIntegerField(null=True)
+ resource = models.ForeignKey(Resource)
+ project_definition = models.ForeignKey(ProjectDefinition, blank=True)
+
+ class Meta:
+ unique_together = ("resource", "project_definition")
+
+
+class ProjectApplication(models.Model):
+ states_list = [PENDING, APPROVED, REPLACED, UNKNOWN]
+ states = dict((k, v) for k, v in enumerate(states_list))
+
+ applicant = models.ForeignKey(
+ AstakosUser,
+ related_name='my_project_applications',
+ db_index=True)
+ owner = models.ForeignKey(
+ AstakosUser,
+ related_name='own_project_applications',
+ db_index=True
+ )
+ comments = models.TextField(null=True, blank=True)
+ definition = models.OneToOneField(ProjectDefinition)
+ issue_date = models.DateTimeField()
+ precursor_application = models.OneToOneField('ProjectApplication',
+ null=True,
+ blank=True,
+ db_index=True
+ )
+ state = models.CharField(max_length=80, default=UNKNOWN)
+
+ @property
+ def follower(self):
+ try:
+ return ProjectApplication.objects.get(precursor_application=self)
+ except ProjectApplication.DoesNotExist:
+ return
+
+ def save(self):
+ self.definition.save()
+ self.definition = self.definition
+ super(ProjectApplication, self).save()
+
+
+ @staticmethod
+ def submit(definition, resource_policies, applicant, comments,
+ precursor_application=None, commit=True):
+
+ application = ProjectApplication()
+ if precursor_application:
+ application.precursor_application = precursor_application
+ application.owner = precursor_application.owner
+ else:
+ application.owner = applicant
+
+ application.definition = definition
+ application.definition.resource_policies = resource_policies
+ application.applicant = applicant
+ application.comments = comments
+ application.issue_date = datetime.now()
+ application.state = PENDING
+
+ if commit:
+ application.save()
+ application.definition.resource_policies = resource_policies
+ # better implementation ???
+ if precursor_application:
+ try:
+ precursor = ProjectApplication.objects.get(id=precursor_application_id)
+ except:
+ pass
+ application.precursor_application = precursor
+ application.save()
+
+ notification = build_notification(
+ settings.SERVER_EMAIL,
+ [i[1] for i in settings.ADMINS],
+ _(PROJECT_CREATION_SUBJECT) % application.definition.__dict__,
+ template='im/projects/project_creation_notification.txt',
+ dictionary={'object':application}
+ )
+ notification.send()
+ return application
+
+ def approve(self, approval_user=None):
+ """
+ If approval_user then during owner membership acceptance
+ it is checked whether the request_user is eligible.
+
+ Raises:
+ ValidationError: if there is other alive project with the same name
+
+ """
+ try:
+ self.definition.validate_name()
+ except ValidationError, e:
+ raise PermissionDenied(e.messages[0])
+ if self.state != PENDING:
+ raise PermissionDenied(_(PROJECT_ALREADY_ACTIVE))
+
+ try:
+ precursor = self.precursor_application
+ project = precursor.project
+ project.application = self
+ prev_approval_date = project.last_approval_date
+ project.last_approval_date = datetime.now()
+ project.save()
+
+ p = precursor
+ while p:
+ p.state = REPLACED
+ p.save()
+ p = p.precursor_application
+
+ except:
+ kwargs = {
+ 'application':self,
+ 'creation_date':datetime.now(),
+ 'last_approval_date':datetime.now(),
+ }
+ project = _create_object(Project, **kwargs)
+ project.accept_member(self.owner, approval_user)
+ precursor = None
+
+ self.state = APPROVED
+ self.save()
+
+ notification = build_notification(
+ settings.SERVER_EMAIL,
+ [self.owner.email],
+ _(PROJECT_APPROVED_SUBJECT) % self.definition.__dict__,
+ template='im/projects/project_approval_notification.txt',
+ dictionary={'object':self}
+ )
+ notification.send()
+
+ rejected = self.project.sync()
+ if rejected:
+ # revert to precursor
+ if precursor:
+ project.application = precursor
+ project.last_approval_date = prev_approval_date
+ project.save()
+
+ rejected = project.sync()
+ if rejected:
+ raise Exception(_(astakos_messages.QH_SYNC_ERROR))
+ else:
+ project.last_application_synced = self
+ project.save()
+
+
+class Project(models.Model):
+ application = models.OneToOneField(ProjectApplication, related_name='project')
+ creation_date = models.DateTimeField()
+ last_approval_date = models.DateTimeField(null=True)
+ termination_start_date = models.DateTimeField(null=True)
+ termination_date = models.DateTimeField(null=True)
+ members = models.ManyToManyField(AstakosUser, through='ProjectMembership')
+ membership_dirty = models.BooleanField(default=False)
+ last_application_synced = models.OneToOneField(
+ ProjectApplication, related_name='last_project', null=True, blank=True
+ )
+
+
+ @property
+ def definition(self):
+ return self.application.definition
+
+ @property
+ def violated_members_number_limit(self):
+ return len(self.approved_members) <= self.definition.limit_on_members_number
+
+ @property
+ def is_active(self):
+ if not self.last_approval_date:
+ return False
+ if self.termination_date:
+ return False
+ if self.definition.violated_resource_grants:
+ return False
+# if self.violated_members_number_limit:
+# return False
+ return True
+
+ @property
+ def is_terminated(self):
+ if not self.termination_date:
+ return False
+ return True
+
+ @property
+ def is_suspended(self):
+ if self.termination_date:
+ return False
+ if self.last_approval_date:
+ if not self.definition.violated_resource_grants:
+ return False
+# if not self.violated_members_number_limit:
+# return False
+ return True
+
+ @property
+ def is_alive(self):
+ return self.is_active or self.is_suspended
+
+ @property
+ def is_inconsistent(self):
+ now = datetime.now()
+ if self.creation_date > now:
+ return True
+ if self.last_approval_date > now:
+ return True
+ if self.terminaton_date > now:
+ return True
+ return False
+
+ @property
+ def is_synchronized(self):
+ return self.last_application_synced == self.application and \
+ not self.membership_dirty and \
+ (not self.termination_start_date or termination_date)
+
+ @property
+ def approved_members(self):
+ return [m.person for m in self.projectmembership_set.filter(~Q(acceptance_date=None))]
+
+ def sync(self, specific_members=()):
+ if self.is_synchronized:
+ return
+ members = specific_members or self.approved_members
+ c, rejected = send_quota(self.approved_members)
+ return rejected
+
+ def accept_member(self, user, request_user=None):
+ """
+ Raises:
+ django.exceptions.PermissionDenied
+ astakos.im.models.AstakosUser.DoesNotExist
+ """
+ if isinstance(user, int):
+ try:
+ user = lookup_object(AstakosUser, user, None, None)
+ except Http404:
+ raise AstakosUser.DoesNotExist()
+ m, created = ProjectMembership.objects.get_or_create(
+ person=user, project=self
+ )
+ m.accept(delete_on_failure=created, request_user=None)
+
+ def reject_member(self, user, request_user=None):
+ """
+ Raises:
+ django.exceptions.PermissionDenied
+ astakos.im.models.AstakosUser.DoesNotExist
+ astakos.im.models.ProjectMembership.DoesNotExist
+ """
+ if isinstance(user, int):
+ try:
+ user = lookup_object(AstakosUser, user, None, None)
+ except Http404:
+ raise AstakosUser.DoesNotExist()
+ m = ProjectMembership.objects.get(person=user, project=self)
+ m.reject()
+
+ def remove_member(self, user, request_user=None):
+ """
+ Raises:
+ django.exceptions.PermissionDenied
+ astakos.im.models.AstakosUser.DoesNotExist
+ astakos.im.models.ProjectMembership.DoesNotExist
+ """
+ if isinstance(user, int):
+ try:
+ user = lookup_object(AstakosUser, user, None, None)
+ except Http404:
+ raise AstakosUser.DoesNotExist()
+ m = ProjectMembership.objects.get(person=user, project=self)
+ m.remove()
+
+ def terminate(self):
+ self.termination_start_date = datetime.now()
+ self.terminaton_date = None
+ self.save()
+
+ rejected = self.sync()
+ if not rejected:
+ self.termination_start_date = None
+ self.terminaton_date = datetime.now()
+ self.save()
+
+ notification = build_notification(
+ settings.SERVER_EMAIL,
+ [self.application.owner.email],
+ _(PROJECT_TERMINATION_SUBJECT) % self.definition.__dict__,
+ template='im/projects/project_termination_notification.txt',
+ dictionary={'object':self.application}
+ )
+ notification.send()
+
+ def suspend(self):
+ self.last_approval_date = None
+ self.save()
+ self.sync()
+ notification = build_notification(
+ settings.SERVER_EMAIL,
+ [self.application.owner.email],
+ _(PROJECT_SUSPENSION_SUBJECT) % self.definition.__dict__,
+ template='im/projects/project_suspension_notification.txt',
+ dictionary={'object':self.application}
+ )
+ notification.send()
+
+class ProjectMembership(models.Model):
+ person = models.ForeignKey(AstakosUser)
+ project = models.ForeignKey(Project)
+ request_date = models.DateField(default=datetime.now())
+ acceptance_date = models.DateField(null=True, db_index=True)
+ leave_request_date = models.DateField(null=True)
+
+ class Meta:
+ unique_together = ("person", "project")
+
+ def accept(self, delete_on_failure=False, request_user=None):
+ """
+ Raises:
+ django.exception.PermissionDenied
+ astakos.im.notifications.NotificationError
+ """
+ try:
+ if request_user and \
+ (not self.project.application.owner == request_user and \
+ not request_user.is_superuser):
+ raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
+ if not self.project.is_alive:
+ raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % self.project.__dict__)
+ if self.project.definition.member_join_policy == 'closed':
+ raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
+ if len(self.project.approved_members) + 1 > self.project.definition.limit_on_members_number:
+ raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
+ except PermissionDenied, e:
+ if delete_on_failure:
+ m.delete()
+ raise
+ if self.acceptance_date:
+ return
+ self.acceptance_date = datetime.now()
+ self.save()
+ notification = build_notification(
+ settings.SERVER_EMAIL,
+ [self.person.email],
+ _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % self.project.definition.__dict__,
+ template='im/projects/project_membership_change_notification.txt',
+ dictionary={'object':self.project.application, 'action':'accepted'}
+ ).send()
+ self.sync()
+
+ def reject(self, request_user=None):
+ """
+ Raises:
+ django.exception.PermissionDenied,
+ astakos.im.notifications.NotificationError
+ """
+ if request_user and \
+ (not self.project.application.owner == request_user and \
+ not request_user.is_superuser):
+ raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
+ if not self.project.is_alive:
+ raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
+ history_item = ProjectMembershipHistory(
+ person=self.person,
+ project=self.project,
+ request_date=self.request_date,
+ rejection_date=datetime.now()
+ )
+ self.delete()
+ history_item.save()
+ notification = build_notification(
+ settings.SERVER_EMAIL,
+ [self.person.email],
+ _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % self.project.definition.__dict__,
+ template='im/projects/project_membership_change_notification.txt',
+ dictionary={'object':self.project.application, 'action':'rejected'}
+ ).send()
+
+ def remove(self, request_user=None):
+ """
+ Raises:
+ django.exception.PermissionDenied
+ astakos.im.notifications.NotificationError
+ """
+ if request_user and \
+ (not self.project.application.owner == request_user and \
+ not request_user.is_superuser):
+ raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
+ if not self.project.is_alive:
+ raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % self.project.__dict__)
+ history_item = ProjectMembershipHistory(
+ id=self.id,
+ person=self.person,
+ project=self.project,
+ request_date=self.request_date,
+ removal_date=datetime.now()
+ )
+ self.delete()
+ history_item.save()
+ notification = build_notification(
+ settings.SERVER_EMAIL,
+ [self.person.email],
+ _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % self.project.definition.__dict__,
+ template='im/projects/project_membership_change_notification.txt',
+ dictionary={'object':self.project.application, 'action':'removed'}
+ ).send()
+ self.sync()
+
+ def leave(self):
+ leave_policy = self.project.application.definition.member_leave_policy
+ if leave_policy == get_auto_accept_leave():
+ self.remove()
+ else:
+ self.leave_request_date = datetime.now()
+ self.save()
+
+ def sync(self):
+ # set membership_dirty flag
+ self.project.membership_dirty = True
+ self.project.save()
+
+ rejected = self.project.sync(specific_members=[self.person])
+ if not rejected:
+ # if syncing was successful unset membership_dirty flag
+ self.membership_dirty = False
+ self.project.save()
+
+
+class ProjectMembershipHistory(models.Model):
+ person = models.ForeignKey(AstakosUser)
+ project = models.ForeignKey(Project)
+ request_date = models.DateField(default=datetime.now())
+ removal_date = models.DateField(null=True)
+ rejection_date = models.DateField(null=True)
+
+
+def filter_queryset_by_property(q, property):
+ """
+ Incorporate list comprehension for filtering querysets by property
+ since Queryset.filter() operates on the database level.
+ """
+ return (p for p in q if getattr(p, property, False))
+
+def get_alive_projects():
+ return filter_queryset_by_property(
+ Project.objects.all(),
+ 'is_alive'
+ )
+
+def get_active_projects():
+ return filter_queryset_by_property(
+ Project.objects.all(),
+ 'is_active'
+ )
+
+def _create_object(model, **kwargs):
+ o = model.objects.create(**kwargs)
+ o.save()
+ return o
+
def create_astakos_user(u):
try:
except AstakosUser.DoesNotExist:
extended_user = AstakosUser(user_ptr_id=u.pk)
extended_user.__dict__.update(u.__dict__)
- extended_user.renew_token()
extended_user.save()
+ if not extended_user.has_auth_provider('local'):
+ extended_user.add_auth_provider('local')
except BaseException, e:
logger.exception(e)
- pass
def fix_superusers(sender, **kwargs):
admins = User.objects.filter(is_superuser=True)
for u in admins:
create_astakos_user(u)
+post_syncdb.connect(fix_superusers)
def user_post_save(sender, instance, created, **kwargs):
if not created:
return
create_astakos_user(instance)
-
-
-def set_default_group(user):
- try:
- default = AstakosGroup.objects.get(name='default')
- Membership(
- group=default, person=user, date_joined=datetime.now()).save()
- except AstakosGroup.DoesNotExist, e:
- logger.exception(e)
+post_save.connect(user_post_save, sender=User)
def astakosuser_pre_save(sender, instance, **kwargs):
l = filter(lambda f: get(db_instance, f) != get(instance, f),
BILLING_FIELDS)
instance.aquarium_report = True if l else False
+pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
+
+def set_default_group(user):
+ try:
+ default = AstakosGroup.objects.get(name='default')
+ Membership(
+ group=default, person=user, date_joined=datetime.now()).save()
+ except AstakosGroup.DoesNotExist, e:
+ logger.exception(e)
def astakosuser_post_save(sender, instance, created, **kwargs):
set_default_group(instance)
# TODO handle socket.error & IOError
register_users((instance,))
- instance.renew_token()
+post_save.connect(astakosuser_post_save, sender=AstakosUser)
def resource_post_save(sender, instance, created, **kwargs):
if not created:
return
register_resources((instance,))
+post_save.connect(resource_post_save, sender=Resource)
+
+
+def on_quota_disturbed(sender, users, **kwargs):
+# print '>>>', locals()
+ if not users:
+ return
+ send_quota(users)
+
+quota_disturbed = Signal(providing_args=["users"])
+quota_disturbed.connect(on_quota_disturbed)
def send_quota_disturbed(sender, instance, **kwargs):
if not instance.is_enabled:
return
quota_disturbed.send(sender=sender, users=users)
-
-
-def on_quota_disturbed(sender, users, **kwargs):
- print '>>>', locals()
- if not users:
- return
- send_quota(users)
-
-post_syncdb.connect(fix_superusers)
-post_save.connect(user_post_save, sender=User)
-pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
-post_save.connect(astakosuser_post_save, sender=AstakosUser)
-post_save.connect(resource_post_save, sender=Resource)
-
-quota_disturbed = Signal(providing_args=["users"])
-quota_disturbed.connect(on_quota_disturbed)
-
post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
post_delete.connect(send_quota_disturbed, sender=Membership)
post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
+
+
+def renew_token(sender, instance, **kwargs):
+ if not instance.auth_token:
+ instance.renew_token()
+pre_save.connect(renew_token, sender=AstakosUser)
+pre_save.connect(renew_token, sender=Service)
+
+
+def check_closed_join_membership_policy(sender, instance, **kwargs):
+ if instance.id:
+ return
+ join_policy = instance.project.application.definition.member_join_policy
+ if join_policy == get_closed_join():
+ raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
+pre_save.connect(check_closed_join_membership_policy, sender=ProjectMembership)
+
+
+def check_auto_accept_join_membership_policy(sender, instance, created, **kwargs):
+ if not created:
+ return
+ join_policy = instance.project.application.definition.member_join_policy
+ if join_policy == get_auto_accept_join():
+ instance.accept()
+post_save.connect(check_auto_accept_join_membership_policy, sender=ProjectMembership)
\ No newline at end of file