Revision 5ce3ce4f snf-astakos-app/astakos/im/models.py

b/snf-astakos-app/astakos/im/models.py
51 51
from django.db.models import Q
52 52

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

  
......
60 60

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

  
63

  
63 64
class Service(models.Model):
64 65
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
65 66
    url = models.FilePathField()
......
67 68
    auth_token = models.CharField('Authentication Token', max_length=32,
68 69
                                  null=True, blank=True)
69 70
    auth_token_created = models.DateTimeField('Token creation date', null=True)
70
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
71
    
71
    auth_token_expires = models.DateTimeField(
72
        'Token expiration date', null=True)
73

  
72 74
    def save(self, **kwargs):
73 75
        if not self.id:
74 76
            self.renew_token()
75 77
        self.full_clean()
76 78
        super(Service, self).save(**kwargs)
77
    
79

  
78 80
    def renew_token(self):
79 81
        md5 = hashlib.md5()
80 82
        md5.update(self.name.encode('ascii', 'ignore'))
......
84 86
        self.auth_token = b64encode(md5.digest())
85 87
        self.auth_token_created = datetime.now()
86 88
        self.auth_token_expires = self.auth_token_created + \
87
                                  timedelta(hours=AUTH_TOKEN_DURATION)
88
    
89
            timedelta(hours=AUTH_TOKEN_DURATION)
90

  
89 91
    def __str__(self):
90 92
        return self.name
91 93

  
94

  
92 95
class ResourceMetadata(models.Model):
93 96
    key = models.CharField('Name', max_length=255, unique=True, db_index=True)
94 97
    value = models.CharField('Value', max_length=255)
95 98

  
99

  
96 100
class Resource(models.Model):
97 101
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
98 102
    meta = models.ManyToManyField(ResourceMetadata)
99 103
    service = models.ForeignKey(Service)
100
    
104

  
101 105
    def __str__(self):
102 106
        return '%s : %s' % (self.service, self.name)
103 107

  
108

  
104 109
class GroupKind(models.Model):
105 110
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
106
    
111

  
107 112
    def __str__(self):
108 113
        return self.name
109 114

  
115

  
110 116
class AstakosGroup(Group):
111 117
    kind = models.ForeignKey(GroupKind)
112
    homepage = models.CharField('Homepage', max_length=255, null=True, blank=True)
118
    homepage = models.CharField(
119
        'Homepage', max_length=255, null=True, blank=True)
113 120
    desc = models.TextField('Description', null=True)
114 121
    policy = models.ManyToManyField(Resource, null=True, blank=True,
115
        through='AstakosGroupQuota'
116
    )
122
                                    through='AstakosGroupQuota'
123
                                    )
117 124
    creation_date = models.DateTimeField('Creation date',
118
        default=datetime.now()
119
    )
125
                                         default=datetime.now()
126
                                         )
120 127
    issue_date = models.DateTimeField('Issue date', null=True)
121 128
    expiration_date = models.DateTimeField('Expiration date', null=True)
122 129
    moderation_enabled = models.BooleanField('Moderated membership?',
123
        default=True
124
    )
130
                                             default=True
131
                                             )
125 132
    approval_date = models.DateTimeField('Activation date', null=True,
126
        blank=True
127
    )
133
                                         blank=True
134
                                         )
128 135
    estimated_participants = models.PositiveIntegerField('Estimated #members',
129
        null=True
130
    )
131
    
136
                                                         null=True
137
                                                         )
138

  
132 139
    @property
133 140
    def is_disabled(self):
134 141
        if not self.approval_date:
135 142
            return True
136 143
        return False
137
    
144

  
138 145
    @property
139 146
    def is_enabled(self):
140 147
        if self.is_disabled:
......
149 156
        if now >= self.expiration_date:
150 157
            return False
151 158
        return True
152
    
159

  
153 160
    def enable(self):
154 161
        if self.is_enabled:
155 162
            return
156 163
        self.approval_date = datetime.now()
157 164
        self.save()
158 165
        quota_disturbed.send(sender=self, users=self.approved_members)
159
        propagate_groupmembers_quota.apply_async(args=[self], eta=self.issue_date)
160
        propagate_groupmembers_quota.apply_async(args=[self], eta=self.expiration_date)
161
    
166
        propagate_groupmembers_quota.apply_async(
167
            args=[self], eta=self.issue_date)
168
        propagate_groupmembers_quota.apply_async(
169
            args=[self], eta=self.expiration_date)
170

  
162 171
    def disable(self):
163 172
        if self.is_disabled:
164 173
            return
165 174
        self.approval_date = None
166 175
        self.save()
167 176
        quota_disturbed.send(sender=self, users=self.approved_members)
168
    
177

  
169 178
    def approve_member(self, person):
170 179
        m, created = self.membership_set.get_or_create(person=person)
171 180
        # update date_joined in any case
172
        m.date_joined=datetime.now()
181
        m.date_joined = datetime.now()
173 182
        m.save()
174
    
183

  
175 184
    def disapprove_member(self, person):
176 185
        self.membership_set.remove(person=person)
177
    
186

  
178 187
    @property
179 188
    def members(self):
180 189
        return [m.person for m in self.membership_set.all()]
181
    
190

  
182 191
    @property
183 192
    def approved_members(self):
184 193
        return [m.person for m in self.membership_set.all() if m.is_approved]
185
    
194

  
186 195
    @property
187 196
    def quota(self):
188 197
        d = defaultdict(int)
189 198
        for q in self.astakosgroupquota_set.all():
190 199
            d[q.resource] += q.limit
191 200
        return d
192
    
201

  
193 202
    @property
194 203
    def owners(self):
195 204
        return self.owner.all()
196
    
205

  
197 206
    @owners.setter
198 207
    def owners(self, l):
199 208
        self.owner = l
200 209
        map(self.approve_member, l)
201 210

  
211

  
202 212
class AstakosUser(User):
203 213
    """
204 214
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
......
212 222
    #for invitations
213 223
    user_level = DEFAULT_USER_LEVEL
214 224
    level = models.IntegerField('Inviter level', default=user_level)
215
    invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
225
    invitations = models.IntegerField(
226
        'Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
216 227

  
217 228
    auth_token = models.CharField('Authentication Token', max_length=32,
218 229
                                  null=True, blank=True)
219 230
    auth_token_created = models.DateTimeField('Token creation date', null=True)
220
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
231
    auth_token_expires = models.DateTimeField(
232
        'Token expiration date', null=True)
221 233

  
222 234
    updated = models.DateTimeField('Update date')
223 235
    is_verified = models.BooleanField('Is verified?', default=False)
224 236

  
225 237
    # ex. screen_name for twitter, eppn for shibboleth
226
    third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
238
    third_party_identifier = models.CharField(
239
        'Third-party identifier', max_length=255, null=True, blank=True)
227 240

  
228 241
    email_verified = models.BooleanField('Email verified?', default=False)
229 242

  
230 243
    has_credits = models.BooleanField('Has credits?', default=False)
231
    has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
232
    date_signed_terms = models.DateTimeField('Signed terms date', null=True, blank=True)
233
    
234
    activation_sent = models.DateTimeField('Activation sent data', null=True, blank=True)
235
    
236
    policy = models.ManyToManyField(Resource, null=True, through='AstakosUserQuota')
237
    
238
    astakos_groups = models.ManyToManyField(AstakosGroup, verbose_name=_('agroups'), blank=True,
244
    has_signed_terms = models.BooleanField(
245
        'Agree with the terms?', default=False)
246
    date_signed_terms = models.DateTimeField(
247
        'Signed terms date', null=True, blank=True)
248

  
249
    activation_sent = models.DateTimeField(
250
        'Activation sent data', null=True, blank=True)
251

  
252
    policy = models.ManyToManyField(
253
        Resource, null=True, through='AstakosUserQuota')
254

  
255
    astakos_groups = models.ManyToManyField(
256
        AstakosGroup, verbose_name=_('agroups'), blank=True,
239 257
        help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."),
240 258
        through='Membership')
241
    
259

  
242 260
    __has_signed_terms = False
243
    
244
    owner = models.ManyToManyField(AstakosGroup, related_name='owner', null=True)
245
    
261

  
262
    owner = models.ManyToManyField(
263
        AstakosGroup, related_name='owner', null=True)
264

  
246 265
    class Meta:
247 266
        unique_together = ("provider", "third_party_identifier")
248
    
267

  
249 268
    def __init__(self, *args, **kwargs):
250 269
        super(AstakosUser, self).__init__(*args, **kwargs)
251 270
        self.__has_signed_terms = self.has_signed_terms
252 271
        if not self.id:
253 272
            self.is_active = False
254
    
273

  
255 274
    @property
256 275
    def realname(self):
257
        return '%s %s' %(self.first_name, self.last_name)
276
        return '%s %s' % (self.first_name, self.last_name)
258 277

  
259 278
    @realname.setter
260 279
    def realname(self, value):
......
271 290
            return Invitation.objects.get(username=self.email)
272 291
        except Invitation.DoesNotExist:
273 292
            return None
274
    
293

  
275 294
    @property
276 295
    def quota(self):
277 296
        d = defaultdict(int)
278
        for q in  self.astakosuserquota_set.all():
297
        for q in self.astakosuserquota_set.all():
279 298
            d[q.resource.name] += q.limit
280 299
        for m in self.membership_set.all():
281 300
            if not m.is_approved:
......
287 306
                d[r] += limit
288 307
        # TODO set default for remaining
289 308
        return d
290
        
309

  
291 310
    def save(self, update_timestamps=True, **kwargs):
292 311
        if update_timestamps:
293 312
            if not self.id:
294 313
                self.date_joined = datetime.now()
295 314
            self.updated = datetime.now()
296
        
315

  
297 316
        # update date_signed_terms if necessary
298 317
        if self.__has_signed_terms != self.has_signed_terms:
299 318
            self.date_signed_terms = datetime.now()
300
        
319

  
301 320
        if not self.id:
302 321
            # set username
303 322
            while not self.username:
304
                username =  uuid.uuid4().hex[:30]
323
                username = uuid.uuid4().hex[:30]
305 324
                try:
306
                    AstakosUser.objects.get(username = username)
325
                    AstakosUser.objects.get(username=username)
307 326
                except AstakosUser.DoesNotExist:
308 327
                    self.username = username
309 328
            if not self.provider:
......
312 331
        if self.is_active and self.activation_sent:
313 332
            # reset the activation sent
314 333
            self.activation_sent = None
315
        
334

  
316 335
        super(AstakosUser, self).save(**kwargs)
317
    
336

  
318 337
    def renew_token(self):
319 338
        md5 = hashlib.md5()
320 339
        md5.update(self.username)
......
324 343
        self.auth_token = b64encode(md5.digest())
325 344
        self.auth_token_created = datetime.now()
326 345
        self.auth_token_expires = self.auth_token_created + \
327
                                  timedelta(hours=AUTH_TOKEN_DURATION)
346
            timedelta(hours=AUTH_TOKEN_DURATION)
328 347
        msg = 'Token renewed for %s' % self.email
329 348
        logger.log(LOGGING_LEVEL, msg)
330 349

  
331 350
    def __unicode__(self):
332 351
        return '%s (%s)' % (self.realname, self.email)
333
    
352

  
334 353
    def conflicting_email(self):
335
        q = AstakosUser.objects.exclude(username = self.username)
336
        q = q.filter(email = self.email)
354
        q = AstakosUser.objects.exclude(username=self.username)
355
        q = q.filter(email=self.email)
337 356
        if q.count() != 0:
338 357
            return True
339 358
        return False
340
    
359

  
341 360
    def validate_unique_email_isactive(self):
342 361
        """
343 362
        Implements a unique_together constraint for email and is_active fields.
344 363
        """
345
        q = AstakosUser.objects.exclude(username = self.username)
346
        q = q.filter(email = self.email)
347
        q = q.filter(is_active = self.is_active)
364
        q = AstakosUser.objects.exclude(username=self.username)
365
        q = q.filter(email=self.email)
366
        q = q.filter(is_active=self.is_active)
348 367
        if q.count() != 0:
349
            raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
350
    
368
            raise ValidationError({'__all__': [_('Another account with the same email & is_active combination found.')]})
369

  
351 370
    @property
352 371
    def signed_terms(self):
353 372
        term = get_latest_terms()
......
364 383
            return False
365 384
        return True
366 385

  
386

  
367 387
class Membership(models.Model):
368 388
    person = models.ForeignKey(AstakosUser)
369 389
    group = models.ForeignKey(AstakosGroup)
370 390
    date_requested = models.DateField(default=datetime.now(), blank=True)
371 391
    date_joined = models.DateField(null=True, db_index=True, blank=True)
372
    
392

  
373 393
    class Meta:
374 394
        unique_together = ("person", "group")
375
    
395

  
376 396
    def save(self, *args, **kwargs):
377 397
        if not self.id:
378 398
            if not self.group.moderation_enabled:
379 399
                self.date_joined = datetime.now()
380 400
        super(Membership, self).save(*args, **kwargs)
381
    
401

  
382 402
    @property
383 403
    def is_approved(self):
384 404
        if self.date_joined:
385 405
            return True
386 406
        return False
387
    
407

  
388 408
    def approve(self):
389 409
        self.date_joined = datetime.now()
390 410
        self.save()
391 411
        quota_disturbed.send(sender=self, users=(self.person,))
392
    
412

  
393 413
    def disapprove(self):
394 414
        self.delete()
395 415
        quota_disturbed.send(sender=self, users=(self.person,))
396 416

  
417

  
397 418
class AstakosGroupQuota(models.Model):
398 419
    limit = models.PositiveIntegerField('Limit')
399 420
    resource = models.ForeignKey(Resource)
400 421
    group = models.ForeignKey(AstakosGroup, blank=True)
401
    
422

  
402 423
    class Meta:
403 424
        unique_together = ("resource", "group")
404 425

  
426

  
405 427
class AstakosUserQuota(models.Model):
406 428
    limit = models.PositiveIntegerField('Limit')
407 429
    resource = models.ForeignKey(Resource)
408 430
    user = models.ForeignKey(AstakosUser)
409
    
431

  
410 432
    class Meta:
411 433
        unique_together = ("resource", "user")
412 434

  
435

  
413 436
class ApprovalTerms(models.Model):
414 437
    """
415 438
    Model for approval terms
416 439
    """
417 440

  
418
    date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
441
    date = models.DateTimeField(
442
        'Issue date', db_index=True, default=datetime.now())
419 443
    location = models.CharField('Terms location', max_length=255)
420 444

  
445

  
421 446
class Invitation(models.Model):
422 447
    """
423 448
    Model for registring invitations
......
430 455
    is_consumed = models.BooleanField('Consumed?', default=False)
431 456
    created = models.DateTimeField('Creation date', auto_now_add=True)
432 457
    consumed = models.DateTimeField('Consumption date', null=True, blank=True)
433
    
458

  
434 459
    def __init__(self, *args, **kwargs):
435 460
        super(Invitation, self).__init__(*args, **kwargs)
436 461
        if not self.id:
437 462
            self.code = _generate_invitation_code()
438
    
463

  
439 464
    def consume(self):
440 465
        self.is_consumed = True
441 466
        self.consumed = datetime.now()
......
444 469
    def __unicode__(self):
445 470
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
446 471

  
472

  
447 473
class EmailChangeManager(models.Manager):
448 474
    @transaction.commit_on_success
449 475
    def change_email(self, activation_key):
......
464 490
        Throws ValueError if there is already
465 491
        """
466 492
        try:
467
            email_change = self.model.objects.get(activation_key=activation_key)
493
            email_change = self.model.objects.get(
494
                activation_key=activation_key)
468 495
            if email_change.activation_key_expired():
469 496
                email_change.delete()
470 497
                raise EmailChange.DoesNotExist
......
484 511
        except EmailChange.DoesNotExist:
485 512
            raise ValueError(_('Invalid activation key'))
486 513

  
514

  
487 515
class EmailChange(models.Model):
488 516
    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.'))
489
    user = models.ForeignKey(AstakosUser, unique=True, related_name='emailchange_user')
517
    user = models.ForeignKey(
518
        AstakosUser, unique=True, related_name='emailchange_user')
490 519
    requested_at = models.DateTimeField(default=datetime.now())
491
    activation_key = models.CharField(max_length=40, unique=True, db_index=True)
520
    activation_key = models.CharField(
521
        max_length=40, unique=True, db_index=True)
492 522

  
493 523
    objects = EmailChangeManager()
494 524

  
......
496 526
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
497 527
        return self.requested_at + expiration_date < datetime.now()
498 528

  
529

  
499 530
class AdditionalMail(models.Model):
500 531
    """
501 532
    Model for registring invitations
......
503 534
    owner = models.ForeignKey(AstakosUser)
504 535
    email = models.EmailField()
505 536

  
537

  
506 538
def _generate_invitation_code():
507 539
    while True:
508
        code = randint(1, 2L**63 - 1)
540
        code = randint(1, 2L ** 63 - 1)
509 541
        try:
510 542
            Invitation.objects.get(code=code)
511 543
            # An invitation with this code already exists, try again
512 544
        except Invitation.DoesNotExist:
513 545
            return code
514 546

  
547

  
515 548
def get_latest_terms():
516 549
    try:
517 550
        term = ApprovalTerms.objects.order_by('-id')[0]
......
520 553
        pass
521 554
    return None
522 555

  
556

  
523 557
def create_astakos_user(u):
524 558
    try:
525 559
        AstakosUser.objects.get(user_ptr=u.pk)
......
532 566
        logger.exception(e)
533 567
        pass
534 568

  
569

  
535 570
def fix_superusers(sender, **kwargs):
536 571
    # Associate superusers with AstakosUser
537 572
    admins = User.objects.filter(is_superuser=True)
538 573
    for u in admins:
539 574
        create_astakos_user(u)
540 575

  
576

  
541 577
def user_post_save(sender, instance, created, **kwargs):
542 578
    if not created:
543 579
        return
544 580
    create_astakos_user(instance)
545 581

  
582

  
546 583
def set_default_group(user):
547 584
    try:
548
        default = AstakosGroup.objects.get(name = 'default')
549
        Membership(group=default, person=user, date_joined=datetime.now()).save()
585
        default = AstakosGroup.objects.get(name='default')
586
        Membership(
587
            group=default, person=user, date_joined=datetime.now()).save()
550 588
    except AstakosGroup.DoesNotExist, e:
551 589
        logger.exception(e)
552 590

  
591

  
553 592
def astakosuser_pre_save(sender, instance, **kwargs):
554 593
    instance.aquarium_report = False
555 594
    instance.new = False
556 595
    try:
557
        db_instance = AstakosUser.objects.get(id = instance.id)
596
        db_instance = AstakosUser.objects.get(id=instance.id)
558 597
    except AstakosUser.DoesNotExist:
559 598
        # create event
560 599
        instance.aquarium_report = True
......
562 601
    else:
563 602
        get = AstakosUser.__getattribute__
564 603
        l = filter(lambda f: get(db_instance, f) != get(instance, f),
565
            BILLING_FIELDS
566
        )
604
                   BILLING_FIELDS
605
                   )
567 606
        instance.aquarium_report = True if l else False
568 607

  
608

  
569 609
def astakosuser_post_save(sender, instance, created, **kwargs):
570 610
    if instance.aquarium_report:
571 611
        report_user_event(instance, create=instance.new)
......
575 615
    # TODO handle socket.error & IOError
576 616
    register_users((instance,))
577 617

  
618

  
578 619
def send_quota_disturbed(sender, instance, **kwargs):
579 620
    users = []
580 621
    extend = users.extend
......
593 634
            return
594 635
    quota_disturbed.send(sender=sender, users=users)
595 636

  
637

  
596 638
def on_quota_disturbed(sender, users, **kwargs):
597 639
    print '>>>', locals()
598 640
    if not users:
......
612 654
post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
613 655
post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
614 656
post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
615
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
657
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)

Also available in: Unified diff