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