Revision fc1e2f02 snf-astakos-app/astakos/im/models.py

b/snf-astakos-app/astakos/im/models.py
38 38
from time import asctime
39 39
from datetime import datetime, timedelta
40 40
from base64 import b64encode
41
from urlparse import urlparse
42 41
from random import randint
43 42
from collections import defaultdict
44 43

  
......
47 46
from django.utils.translation import ugettext as _
48 47
from django.core.exceptions import ValidationError
49 48
from django.db import transaction
50
from django.db.models.signals import post_save, post_syncdb
49
from django.db.models.signals import pre_save, post_save, post_syncdb, post_delete
50
from django.dispatch import Signal
51 51
from django.db.models import Q
52 52

  
53
from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, \
54
    AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION, \
55
    EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL
53
from astakos.im.settings import (DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
54
    AUTH_TOKEN_DURATION, BILLING_FIELDS, EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL
55
)
56
from astakos.im.endpoints.quotaholder import register_users, send_quota
57
from astakos.im.endpoints.aquarium.producer import report_user_event
56 58

  
57
QUEUE_CLIENT_ID = 3 # Astakos.
59
from astakos.im.tasks import propagate_groupmembers_quota
58 60

  
59 61
logger = logging.getLogger(__name__)
60 62

  
......
108 110
class AstakosGroup(Group):
109 111
    kind = models.ForeignKey(GroupKind)
110 112
    desc = models.TextField('Description', null=True)
111
    policy = models.ManyToManyField(Resource, null=True, blank=True, through='AstakosGroupQuota')
112
    creation_date = models.DateTimeField('Creation date', default=datetime.now())
113
    policy = models.ManyToManyField(Resource, null=True, blank=True,
114
        through='AstakosGroupQuota'
115
    )
116
    creation_date = models.DateTimeField('Creation date',
117
        default=datetime.now()
118
    )
113 119
    issue_date = models.DateTimeField('Issue date', null=True)
114 120
    expiration_date = models.DateTimeField('Expiration date', null=True)
115
    moderation_enabled = models.BooleanField('Moderated membership?', default=True)
116
    approval_date = models.DateTimeField('Activation date', null=True, blank=True)
117
    estimated_participants = models.PositiveIntegerField('Estimated #participants', null=True)
121
    moderation_enabled = models.BooleanField('Moderated membership?',
122
        default=True
123
    )
124
    approval_date = models.DateTimeField('Activation date', null=True,
125
        blank=True
126
    )
127
    estimated_participants = models.PositiveIntegerField('Estimated #members',
128
        null=True
129
    )
118 130
    
119 131
    @property
120 132
    def is_disabled(self):
......
137 149
            return False
138 150
        return True
139 151
    
140
    @property
141
    def participants(self):
142
        return len(self.approved_members)
152
#     @property
153
#     def participants(self):
154
#         return len(self.approved_members)
143 155
    
144 156
    def enable(self):
157
        if self.is_enabled:
158
            return
145 159
        self.approval_date = datetime.now()
146 160
        self.save()
161
        quota_disturbed.send(sender=self, users=approved_members)
162
        update_groupmembers_quota.apply_async(args=[self], eta=self.issue_date)
163
        update_groupmembers_quota.apply_async(args=[self], eta=self.expiration_date)
147 164
    
148 165
    def disable(self):
166
        if self.is_disabled:
167
            return
149 168
        self.approval_date = None
150 169
        self.save()
170
        quota_disturbed.send(sender=self, users=self.approved_members)
151 171
    
152 172
    def approve_member(self, person):
153 173
        m, created = self.membership_set.get_or_create(person=person)
......
160 180
    
161 181
    @property
162 182
    def members(self):
163
        return map(lambda m:m.person, self.membership_set.all())
183
        return [m.person for m in self.membership_set.all()]
164 184
    
165 185
    @property
166 186
    def approved_members(self):
167
        f = filter(lambda m:m.is_approved, self.membership_set.all())
168
        return map(lambda m:m.person, f)
187
        return [m.person for m in self.membership_set.all() if m.is_approved]
169 188
    
170 189
    @property
171 190
    def quota(self):
......
175 194
        return d
176 195
    
177 196
    @property
178
    def has_undefined_policies(self):
179
        # TODO: can avoid query?
180
        return Resource.objects.filter(~Q(astakosgroup=self)).exists()
181
    
182
    @property
183 197
    def owners(self):
184 198
        return self.owner.all()
185 199
    
......
266 280
        d = defaultdict(int)
267 281
        for q in  self.astakosuserquota_set.all():
268 282
            d[q.resource.name] += q.limit
269
        for g in self.astakos_groups.all():
283
        for m in self.membership_set.all():
284
            if not m.is_approved:
285
                continue
286
            g = m.group
270 287
            if not g.is_enabled:
271 288
                continue
272 289
            for r, limit in g.quota.iteritems():
......
294 311
                    self.username = username
295 312
            if not self.provider:
296 313
                self.provider = 'local'
297
        report_user_event(self)
298 314
        self.validate_unique_email_isactive()
299 315
        if self.is_active and self.activation_sent:
300 316
            # reset the activation sent
......
374 390
    def approve(self):
375 391
        self.date_joined = datetime.now()
376 392
        self.save()
377
        
393
        quota_disturbed.send(sender=self, users=(self.person,))
394
    
378 395
    def disapprove(self):
379 396
        self.delete()
397
        quota_disturbed.send(sender=self, users=(self.person,))
380 398

  
381 399
class AstakosGroupQuota(models.Model):
382 400
    limit = models.PositiveIntegerField('Limit')
......
428 446
    def __unicode__(self):
429 447
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
430 448

  
431
def report_user_event(user):
432
    def should_send(user):
433
        # report event incase of new user instance
434
        # or if specific fields are modified
435
        if not user.id:
436
            return True
437
        try:
438
            db_instance = AstakosUser.objects.get(id = user.id)
439
        except AstakosUser.DoesNotExist:
440
            return True
441
        for f in BILLING_FIELDS:
442
            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
443
                return True
444
        return False
445

  
446
    if QUEUE_CONNECTION and should_send(user):
447

  
448
        from astakos.im.queue.userevent import UserEvent
449
        from synnefo.lib.queue import exchange_connect, exchange_send, \
450
                exchange_close
451

  
452
        eventType = 'create' if not user.id else 'modify'
453
        body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
454
        conn = exchange_connect(QUEUE_CONNECTION)
455
        parts = urlparse(QUEUE_CONNECTION)
456
        exchange = parts.path[1:]
457
        routing_key = '%s.user' % exchange
458
        exchange_send(conn, routing_key, body)
459
        exchange_close(conn)
460

  
461
def _generate_invitation_code():
462
    while True:
463
        code = randint(1, 2L**63 - 1)
464
        try:
465
            Invitation.objects.get(code=code)
466
            # An invitation with this code already exists, try again
467
        except Invitation.DoesNotExist:
468
            return code
469

  
470
def get_latest_terms():
471
    try:
472
        term = ApprovalTerms.objects.order_by('-id')[0]
473
        return term
474
    except IndexError:
475
        pass
476
    return None
477

  
478 449
class EmailChangeManager(models.Manager):
479 450
    @transaction.commit_on_success
480 451
    def change_email(self, activation_key):
......
534 505
    owner = models.ForeignKey(AstakosUser)
535 506
    email = models.EmailField()
536 507

  
508
def _generate_invitation_code():
509
    while True:
510
        code = randint(1, 2L**63 - 1)
511
        try:
512
            Invitation.objects.get(code=code)
513
            # An invitation with this code already exists, try again
514
        except Invitation.DoesNotExist:
515
            return code
516

  
517
def get_latest_terms():
518
    try:
519
        term = ApprovalTerms.objects.order_by('-id')[0]
520
        return term
521
    except IndexError:
522
        pass
523
    return None
524

  
537 525
def create_astakos_user(u):
538 526
    try:
539 527
        AstakosUser.objects.get(user_ptr=u.pk)
......
542 530
        extended_user.__dict__.update(u.__dict__)
543 531
        extended_user.renew_token()
544 532
        extended_user.save()
545
    except:
533
    except BaseException, e:
534
        logger.exception(e)
546 535
        pass
547 536

  
548
def superuser_post_syncdb(sender, **kwargs):
549
    # if there was created a superuser
550
    # associate it with an AstakosUser
537
def fix_superusers(sender, **kwargs):
538
    # Associate superusers with AstakosUser
551 539
    admins = User.objects.filter(is_superuser=True)
552 540
    for u in admins:
553 541
        create_astakos_user(u)
554 542

  
555
post_syncdb.connect(superuser_post_syncdb)
556

  
557
def superuser_post_save(sender, instance, **kwargs):
558
    if instance.is_superuser:
559
        create_astakos_user(instance)
560

  
561
post_save.connect(superuser_post_save, sender=User)
562

  
563
def set_default_group(sender, instance, created, **kwargs):
543
def user_post_save(sender, instance, created, **kwargs):
564 544
    if not created:
565 545
        return
546
    create_astakos_user(instance)
547

  
548
def set_default_group(user):
566 549
    try:
567 550
        default = AstakosGroup.objects.get(name = 'default')
568
        Membership(group=default, person=instance, date_joined=datetime.now()).save()
551
        Membership(group=default, person=user, date_joined=datetime.now()).save()
569 552
    except AstakosGroup.DoesNotExist, e:
570 553
        logger.exception(e)
571 554

  
572
post_save.connect(set_default_group, sender=AstakosUser)
573

  
574
def get_resources():
575
    # use cache
576
    return Resource.objects.select_related().all()
555
def astakosuser_pre_save(sender, instance, **kwargs):
556
    instance.aquarium_report = False
557
    instance.new = False
558
    try:
559
        db_instance = AstakosUser.objects.get(id = instance.id)
560
    except AstakosUser.DoesNotExist:
561
        # create event
562
        instance.aquarium_report = True
563
        instance.new = True
564
    else:
565
        get = AstakosUser.__getattribute__
566
        l = filter(lambda f: get(db_instance, f) != get(instance, f),
567
            BILLING_FIELDS
568
        )
569
        instance.aquarium_report = True if l else False
570

  
571
def astakosuser_post_save(sender, instance, created, **kwargs):
572
    if instance.aquarium_report:
573
        report_user_event(instance, create=instance.new)
574
    if not created:
575
        return
576
    set_default_group(instance)
577
    # TODO handle socket.error & IOError
578
    register_users((instance,))
579

  
580
def send_quota_disturbed(sender, instance, **kwargs):
581
    users = []
582
    extend = users.extend
583
    if sender == Membership:
584
        if not instance.group.is_enabled:
585
            return
586
        extend([instance.person])
587
    elif sender == AstakosUserQuota:
588
        extend([instance.user])
589
    elif sender == AstakosGroupQuota:
590
        if not instance.group.is_enabled:
591
            return
592
        extend(instance.group.astakosuser_set.all())
593
    elif sender == AstakosGroup:
594
        if not instance.is_enabled:
595
            return
596
    quota_disturbed.send(sender=sender, users=users)
597

  
598
def on_quota_disturbed(sender, users, **kwargs):
599
    print '>>>', locals()
600
    if not users:
601
        return
602
    send_quota(users)
603

  
604
post_syncdb.connect(fix_superusers)
605
post_save.connect(user_post_save, sender=User)
606
pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
607
post_save.connect(astakosuser_post_save, sender=AstakosUser)
608

  
609
quota_disturbed = Signal(providing_args=["users"])
610
quota_disturbed.connect(on_quota_disturbed)
611

  
612
post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
613
post_delete.connect(send_quota_disturbed, sender=Membership)
614
post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
615
post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
616
post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
617
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)

Also available in: Unified diff