Revision 9a06d96f snf-astakos-app/astakos/im/models.py

b/snf-astakos-app/astakos/im/models.py
41 41
from random import randint
42 42
from collections import defaultdict
43 43

  
44
from django.db import models
45
from django.contrib.auth.models import User, UserManager, Group
44
from django.db import models, IntegrityError
45
from django.contrib.auth.models import User, UserManager, Group, Permission
46 46
from django.utils.translation import ugettext as _
47 47
from django.core.exceptions import ValidationError
48 48
from django.db import transaction
49
from django.db.models.signals import pre_save, post_save, post_syncdb, post_delete
49
from django.db.models.signals import (pre_save, post_save, post_syncdb,
50
                                      post_delete)
51
from django.contrib.contenttypes.models import ContentType
52

  
50 53
from django.dispatch import Signal
51 54
from django.db.models import Q
52 55

  
......
56 59
from astakos.im.endpoints.quotaholder import (register_users, send_quota,
57 60
                                              register_resources)
58 61
from astakos.im.endpoints.aquarium.producer import report_user_event
59

  
62
from astakos.im.functions import send_invitation
60 63
from astakos.im.tasks import propagate_groupmembers_quota
64
from astakos.im.functions import send_invitation
61 65

  
62 66
logger = logging.getLogger(__name__)
63 67

  
68
DEFAULT_CONTENT_TYPE = None
69
try:
70
    content_type = ContentType.objects.get(app_label='im', model='astakosuser')
71
except:
72
    content_type = DEFAULT_CONTENT_TYPE
73

  
74
RESOURCE_SEPARATOR = '.'
75

  
64 76

  
65 77
class Service(models.Model):
66 78
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
......
75 87
    def save(self, **kwargs):
76 88
        if not self.id:
77 89
            self.renew_token()
78
        self.full_clean()
79 90
        super(Service, self).save(**kwargs)
80 91

  
81 92
    def renew_token(self):
......
92 103
    def __str__(self):
93 104
        return self.name
94 105

  
106
    @property
107
    def resources(self):
108
        return self.resource_set.all()
109

  
110
    @resources.setter
111
    def resources(self, resources):
112
        for s in resources:
113
            self.resource_set.create(**s)
114
    
115
    def add_resource(self, service, resource, uplimit, update=True):
116
        """Raises ObjectDoesNotExist, IntegrityError"""
117
        resource = Resource.objects.get(service__name=service, name=resource)
118
        if update:
119
            AstakosUserQuota.objects.update_or_create(user=self,
120
                                                      resource=resource,
121
                                                      defaults={'uplimit': uplimit})
122
        else:
123
            q = self.astakosuserquota_set
124
            q.create(resource=resource, uplimit=uplimit)
125

  
95 126

  
96 127
class ResourceMetadata(models.Model):
97 128
    key = models.CharField('Name', max_length=255, unique=True, db_index=True)
......
102 133
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
103 134
    meta = models.ManyToManyField(ResourceMetadata)
104 135
    service = models.ForeignKey(Service)
136
    desc = models.TextField('Description', null=True)
137
    unit = models.CharField('Name', null=True, max_length=255)
138
    group = models.CharField('Group', null=True, max_length=255)
105 139

  
106 140
    def __str__(self):
107
        return '%s.%s' % (self.service, self.name)
141
        return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
108 142

  
109 143

  
110 144
class GroupKind(models.Model):
......
119 153
    homepage = models.URLField(
120 154
        'Homepage Url', max_length=255, null=True, blank=True)
121 155
    desc = models.TextField('Description', null=True)
122
    policy = models.ManyToManyField(Resource, null=True, blank=True,
123
                                    through='AstakosGroupQuota')
124
    creation_date = models.DateTimeField('Creation date',
125
                                         default=datetime.now())
156
    policy = models.ManyToManyField(
157
        Resource,
158
        null=True,
159
        blank=True,
160
        through='AstakosGroupQuota'
161
    )
162
    creation_date = models.DateTimeField(
163
        'Creation date',
164
        default=datetime.now()
165
    )
126 166
    issue_date = models.DateTimeField('Issue date', null=True)
127
    expiration_date = models.DateTimeField('Expiration date', null=True)
128
    moderation_enabled = models.BooleanField('Moderated membership?',
129
                                             default=True)
130
    approval_date = models.DateTimeField('Activation date', null=True,
131
                                         blank=True)
132
    estimated_participants = models.PositiveIntegerField('Estimated #members',
133
                                                         null=True)
134

  
167
    expiration_date = models.DateTimeField(
168
        'Expiration date',
169
         null=True
170
    )
171
    moderation_enabled = models.BooleanField(
172
        'Moderated membership?',
173
        default=True
174
    )
175
    approval_date = models.DateTimeField(
176
        'Activation date',
177
        null=True,
178
        blank=True
179
    )
180
    estimated_participants = models.PositiveIntegerField(
181
        'Estimated #members',
182
        null=True,
183
        blank=True,
184
    )
185
    max_participants = models.PositiveIntegerField(
186
        'Maximum numder of participants',
187
        null=True,
188
        blank=True
189
    )
190
    
135 191
    @property
136 192
    def is_disabled(self):
137 193
        if not self.approval_date:
......
184 240
    def members(self):
185 241
        q = self.membership_set.select_related().all()
186 242
        return [m.person for m in q]
187

  
243
    
188 244
    @property
189 245
    def approved_members(self):
190 246
        q = self.membership_set.select_related().all()
......
196 252
        for q in self.astakosgroupquota_set.select_related().all():
197 253
            d[q.resource] += q.uplimit
198 254
        return d
199

  
255
    
256
    def add_policy(self, service, resource, uplimit, update=True):
257
        """Raises ObjectDoesNotExist, IntegrityError"""
258
        print '#', locals()
259
        resource = Resource.objects.get(service__name=service, name=resource)
260
        if update:
261
            AstakosGroupQuota.objects.update_or_create(
262
                group=self,
263
                resource=resource,
264
                defaults={'uplimit': uplimit}
265
            )
266
        else:
267
            q = self.astakosgroupquota_set
268
            q.create(resource=resource, uplimit=uplimit)
269
    
270
    @property
271
    def policies(self):
272
        return self.astakosgroupquota_set.select_related().all()
273

  
274
    @policies.setter
275
    def policies(self, policies):
276
        for p in policies:
277
            service = p.get('service', None)
278
            resource = p.get('resource', None)
279
            uplimit = p.get('uplimit', 0)
280
            update = p.get('update', True)
281
            self.add_policy(service, resource, uplimit, update)
282
    
200 283
    @property
201 284
    def owners(self):
202 285
        return self.owner.all()
......
244 327

  
245 328
    has_credits = models.BooleanField('Has credits?', default=False)
246 329
    has_signed_terms = models.BooleanField(
247
        'Agree with the terms?', default=False)
330
        'I agree with the terms', default=False)
248 331
    date_signed_terms = models.DateTimeField(
249 332
        'Signed terms date', null=True, blank=True)
250 333

  
......
256 339

  
257 340
    astakos_groups = models.ManyToManyField(
258 341
        AstakosGroup, verbose_name=_('agroups'), blank=True,
259
        help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."),
342
        help_text=_("""In addition to the permissions manually assigned, this
343
                    user will also get all permissions granted to each group
344
                    he/she is in."""),
260 345
        through='Membership')
261 346

  
262 347
    __has_signed_terms = False
348
    disturbed_quota = models.BooleanField('Needs quotaholder syncing',
349
                                           default=False, db_index=True)
263 350

  
264 351
    owner = models.ManyToManyField(
265 352
        AstakosGroup, related_name='owner', null=True)
......
270 357
    def __init__(self, *args, **kwargs):
271 358
        super(AstakosUser, self).__init__(*args, **kwargs)
272 359
        self.__has_signed_terms = self.has_signed_terms
273
        if not self.id:
360
        if not self.id and not self.is_active:
274 361
            self.is_active = False
275 362

  
276 363
    @property
......
286 373
        else:
287 374
            self.last_name = parts[0]
288 375

  
376
    def add_permission(self, pname):
377
        if self.has_perm(pname):
378
            return
379
        p, created = Permission.objects.get_or_create(codename=pname,
380
                                                      name=pname.capitalize(),
381
                                                      content_type=content_type)
382
        self.user_permissions.add(p)
383

  
384
    def remove_permission(self, pname):
385
        if self.has_perm(pname):
386
            return
387
        p = Permission.objects.get(codename=pname,
388
                                   content_type=content_type)
389
        self.user_permissions.remove(p)
390

  
289 391
    @property
290 392
    def invitation(self):
291 393
        try:
......
293 395
        except Invitation.DoesNotExist:
294 396
            return None
295 397

  
398
    def invite(self, email, realname):
399
        inv = Invitation(inviter=self, username=email, realname=realname)
400
        inv.save()
401
        send_invitation(inv)
402
        self.invitations = max(0, self.invitations - 1)
403
        self.save()
404

  
296 405
    @property
297 406
    def quota(self):
407
        """Returns a dict with the sum of quota limits per resource"""
298 408
        d = defaultdict(int)
299
        for q in self.astakosuserquota_set.select_related().all():
409
        for q in self.policies:
300 410
            d[q.resource] += q.uplimit
301
        for m in self.membership_set.select_related().all():
411
        for m in self.extended_groups:
302 412
            if not m.is_approved:
303 413
                continue
304 414
            g = m.group
......
306 416
                continue
307 417
            for r, uplimit in g.quota.iteritems():
308 418
                d[r] += uplimit
309
        
419

  
310 420
        # TODO set default for remaining
311 421
        return d
312 422

  
423
    @property
424
    def policies(self):
425
        return self.astakosuserquota_set.select_related().all()
426

  
427
    @policies.setter
428
    def policies(self, policies):
429
        for p in policies:
430
            service = policies.get('service', None)
431
            resource = policies.get('resource', None)
432
            uplimit = policies.get('uplimit', 0)
433
            update = policies.get('update', True)
434
            self.add_policy(service, resource, uplimit, update)
435

  
436
    def add_policy(self, service, resource, uplimit, update=True):
437
        """Raises ObjectDoesNotExist, IntegrityError"""
438
        resource = Resource.objects.get(service__name=service, name=resource)
439
        if update:
440
            AstakosUserQuota.objects.update_or_create(user=self,
441
                                                      resource=resource,
442
                                                      defaults={'uplimit': uplimit})
443
        else:
444
            q = self.astakosuserquota_set
445
            q.create(resource=resource, uplimit=uplimit)
446

  
447
    def remove_policy(self, service, resource):
448
        """Raises ObjectDoesNotExist, IntegrityError"""
449
        resource = Resource.objects.get(service__name=service, name=resource)
450
        q = self.policies.get(resource=resource).delete()
451

  
452
    @property
453
    def extended_groups(self):
454
        return self.membership_set.select_related().all()
455

  
456
    @extended_groups.setter
457
    def extended_groups(self, groups):
458
        #TODO exceptions
459
        for name in groups:
460
            group = AstakosGroup.objects.get(name=name)
461
            self.membership_set.create(group=group)
462

  
313 463
    def save(self, update_timestamps=True, **kwargs):
314 464
        if update_timestamps:
315 465
            if not self.id:
......
330 480
                    self.username = username
331 481
            if not self.provider:
332 482
                self.provider = 'local'
483
            self.email = self.email.lower()
333 484
        self.validate_unique_email_isactive()
334 485
        if self.is_active and self.activation_sent:
335 486
            # reset the activation sent
......
386 537
            return False
387 538
        return True
388 539

  
540
    def store_disturbed_quota(self, set=True):
541
        self.disturbed_qutoa = set
542
        self.save()
543

  
389 544

  
390 545
class Membership(models.Model):
391 546
    person = models.ForeignKey(AstakosUser)
......
417 572
        self.delete()
418 573
        quota_disturbed.send(sender=self, users=(self.person,))
419 574

  
575
class AstakosQuotaManager(models.Manager):
576
    def _update_or_create(self, **kwargs):
577
        assert kwargs, \
578
            'update_or_create() must be passed at least one keyword argument'
579
        obj, created = self.get_or_create(**kwargs)
580
        defaults = kwargs.pop('defaults', {})
581
        if created:
582
            return obj, True, False
583
        else:
584
            try:
585
                params = dict(
586
                    [(k, v) for k, v in kwargs.items() if '__' not in k])
587
                params.update(defaults)
588
                for attr, val in params.items():
589
                    if hasattr(obj, attr):
590
                        setattr(obj, attr, val)
591
                sid = transaction.savepoint()
592
                obj.save(force_update=True)
593
                transaction.savepoint_commit(sid)
594
                return obj, False, True
595
            except IntegrityError, e:
596
                transaction.savepoint_rollback(sid)
597
                try:
598
                    return self.get(**kwargs), False, False
599
                except self.model.DoesNotExist:
600
                    raise e
601

  
602
    update_or_create = _update_or_create
420 603

  
421 604
class AstakosGroupQuota(models.Model):
605
    objects = AstakosQuotaManager()
422 606
    limit = models.PositiveIntegerField('Limit', null=True)    # obsolete field
423 607
    uplimit = models.BigIntegerField('Up limit', null=True)
424 608
    resource = models.ForeignKey(Resource)
......
427 611
    class Meta:
428 612
        unique_together = ("resource", "group")
429 613

  
430

  
431 614
class AstakosUserQuota(models.Model):
615
    objects = AstakosQuotaManager()
432 616
    limit = models.PositiveIntegerField('Limit', null=True)    # obsolete field
433 617
    uplimit = models.BigIntegerField('Up limit', null=True)
434 618
    resource = models.ForeignKey(Resource)
......
518 702

  
519 703

  
520 704
class EmailChange(models.Model):
521
    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.'))
705
    new_email_address = models.EmailField(_(u'new e-mail address'),
706
                                          help_text=_(u'Your old email address will be used until you verify your new one.'))
522 707
    user = models.ForeignKey(
523 708
        AstakosUser, unique=True, related_name='emailchange_user')
524 709
    requested_at = models.DateTimeField(default=datetime.now())
......
606 791
    else:
607 792
        get = AstakosUser.__getattribute__
608 793
        l = filter(lambda f: get(db_instance, f) != get(instance, f),
609
                   BILLING_FIELDS
610
                   )
794
                   BILLING_FIELDS)
611 795
        instance.aquarium_report = True if l else False
612 796

  
613 797

  
......
619 803
    set_default_group(instance)
620 804
    # TODO handle socket.error & IOError
621 805
    register_users((instance,))
806
    instance.renew_token()
622 807

  
623 808

  
624 809
def resource_post_save(sender, instance, created, **kwargs):

Also available in: Unified diff