Revision 73fbaec4 snf-astakos-app/astakos/im/models.py

b/snf-astakos-app/astakos/im/models.py
49 49
from django.db import transaction
50 50
from django.core.exceptions import ValidationError
51 51
from django.db.models.signals import (
52
    pre_save, post_save, post_syncdb, post_delete
53
)
52
    pre_save, post_save, post_syncdb, post_delete)
54 53
from django.contrib.contenttypes.models import ContentType
55 54

  
56 55
from django.dispatch import Signal
......
61 60
from django.conf import settings
62 61
from django.utils.importlib import import_module
63 62
from django.core.validators import email_re
64
from django.core.exceptions import PermissionDenied
65
from django.views.generic.create_update import lookup_object
66
from django.core.exceptions import ObjectDoesNotExist
63
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
67 64

  
68 65
from astakos.im.settings import (
69 66
    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
70 67
    AUTH_TOKEN_DURATION, BILLING_FIELDS,
71 68
    EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
72
    SITENAME, SERVICES,
73
    PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
74
    PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
75
    PROJECT_MEMBERSHIP_CHANGE_SUBJECT
76
)
69
    SITENAME, SERVICES)
77 70
from astakos.im.endpoints.qh import (
78
    register_users, send_quota, register_resources
79
)
71
    register_users, send_quota, register_resources)
80 72
from astakos.im import auth_providers
81
from astakos.im.endpoints.aquarium.producer import report_user_event
82
from astakos.im.functions import send_invitation
73
#from astakos.im.endpoints.aquarium.producer import report_user_event
83 74
#from astakos.im.tasks import propagate_groupmembers_quota
84 75

  
85
from astakos.im.notifications import build_notification
86

  
87 76
import astakos.im.messages as astakos_messages
88 77

  
89 78
logger = logging.getLogger(__name__)
......
453 442
        except Invitation.DoesNotExist:
454 443
            return None
455 444

  
456
    def invite(self, email, realname):
457
        inv = Invitation(inviter=self, username=email, realname=realname)
458
        inv.save()
459
        send_invitation(inv)
460
        self.invitations = max(0, self.invitations - 1)
461
        self.save()
462

  
463 445
    @property
464 446
    def quota(self):
465 447
        """Returns a dict with the sum of quota limits per resource"""
......
474 456
            p = m.project
475 457
            if not p.is_active:
476 458
                continue
477
            grants = p.current_application.definition.projectresourcegrant_set.all()
459
            grants = p.current_application.projectresourcegrant_set.all()
478 460
            for g in grants:
479
                d[str(g.resource)] += g.member_limit or inf
461
                d[str(g.resource)] += g.member_capacity or inf
480 462
        # TODO set default for remaining
481 463
        return d
482 464

  
......
591 573
        if self.id:
592 574
            q = q.filter(~Q(id = self.id))
593 575
        if q.count() != 0:
594
            raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
576
            m = 'Another account with the same email = %(email)s & \
577
                is_active = %(is_active)s found.' % __self__
578
            raise ValidationError(m)
595 579

  
596 580
    @property
597 581
    def signed_terms(self):
......
936 920
            except AstakosUser.DoesNotExist:
937 921
                pass
938 922
            else:
939
                raise ValueError(_(astakos_messages.NEW_EMAIL_ADDR_RESERVED))
923
                raise ValueError(_('The new email address is reserved.'))
940 924
            # update user
941 925
            user = AstakosUser.objects.get(pk=email_change.user_id)
942 926
            user.email = email_change.new_email_address
......
944 928
            email_change.delete()
945 929
            return user
946 930
        except EmailChange.DoesNotExist:
947
            raise ValueError(_(astakos_messages.INVALID_ACTIVATION_KEY))
931
            raise ValueError(_('Invalid activation key.'))
948 932

  
949 933

  
950 934
class EmailChange(models.Model):
951
    new_email_address = models.EmailField(_(u'new e-mail address'),
952
                                          help_text=_(astakos_messages.EMAIL_CHANGE_NEW_ADDR_HELP))
935
    new_email_address = models.EmailField(
936
        _(u'new e-mail address'),
937
        help_text=_('Your old email address will be used until you verify your new one.'))
953 938
    user = models.ForeignKey(
954 939
        AstakosUser, unique=True, related_name='emailchange_user')
955 940
    requested_at = models.DateTimeField(default=datetime.now())
......
1164 1149
        verified = (status == self.STATUS_SYNCED)
1165 1150
        return state, verified
1166 1151

  
1167

  
1168
class ProjectResourceGrant(models.Model):
1169

  
1170
    resource = models.ForeignKey(Resource)
1171
    project_application = models.ForeignKey(ProjectApplication, blank=True)
1172
    project_capacity     = models.BigIntegerField(null=True)
1173
    project_import_limit = models.BigIntegerField(null=True)
1174
    project_export_limit = models.BigIntegerField(null=True)
1175
    member_capacity      = models.BigIntegerField(null=True)
1176
    member_import_limit  = models.BigIntegerField(null=True)
1177
    member_export_limit  = models.BigIntegerField(null=True)
1178

  
1179
    objects = ExtendedManager()
1180

  
1181
    class Meta:
1182
        unique_together = ("resource", "project_application")
1183

  
1184

  
1185 1152
class ProjectApplication(models.Model):
1186 1153
    states_list = [PENDING, APPROVED, REPLACED, UNKNOWN]
1187 1154
    states = dict((k, v) for v, k in enumerate(states_list))
......
1224 1191
        resource = Resource.objects.get(service__name=service, name=resource)
1225 1192
        if update:
1226 1193
            ProjectResourceGrant.objects.update_or_create(
1227
                project_definition=self,
1194
                project_application=self,
1228 1195
                resource=resource,
1229
                defaults={'member_limit': uplimit}
1196
                defaults={'member_capacity': uplimit}
1230 1197
            )
1231 1198
        else:
1232 1199
            q = self.projectresourcegrant_set
1233
            q.create(resource=resource, member_limit=uplimit)
1200
            q.create(resource=resource, member_capacity=uplimit)
1234 1201

  
1235 1202
    @property
1236 1203
    def resource_policies(self):
......
1252 1219
        except ProjectApplication.DoesNotExist:
1253 1220
            return
1254 1221

  
1255
    @staticmethod
1256
    def submit_view(definition, resource_policies, applicant, comments,
1222
    def submit(self, resource_policies, applicant, comments,
1257 1223
               precursor_application=None):
1258 1224

  
1259
        application = ProjectApplication()
1260 1225
        if precursor_application:
1261
            application.precursor_application = precursor_application
1262
            application.owner = precursor_application.owner
1226
            self.precursor_application = precursor_application
1227
            self.owner = precursor_application.owner
1263 1228
        else:
1264
            application.owner = applicant
1265

  
1266
        application.definition = definition
1267
        application.definition.id = None
1268
        application.applicant = applicant
1269
        application.comments = comments
1270
        application.issue_date = datetime.now()
1271
        application.state = PENDING
1272
        application.save()
1273
        application.definition.resource_policies = resource_policies
1274
        
1275
        try:
1276
            notification = build_notification(
1277
                settings.SERVER_EMAIL,
1278
                [i[1] for i in settings.ADMINS],
1279
                _(PROJECT_CREATION_SUBJECT) % application.definition.__dict__,
1280
                template='im/projects/project_creation_notification.txt',
1281
                dictionary={'object':application}
1282
            ).send()
1283
        except NotificationError, e:
1284
            logger.error(e.messages)
1285

  
1286
        return application
1229
            self.owner = applicant
1230

  
1231
        self.id = None
1232
        self.applicant = applicant
1233
        self.comments = comments
1234
        self.issue_date = datetime.now()
1235
        self.state = PENDING
1236
        self.save()
1237
        self.resource_policies = resource_policies
1287 1238

  
1288 1239
    def _get_project(self):
1289 1240
        precursor = self
......
1309 1260
        if not transaction.is_managed():
1310 1261
            raise AssertionError("NOPE")
1311 1262

  
1312
        new_project_name = self.definition.name
1263
        new_project_name = self.name
1313 1264
        if self.state != PENDING:
1314 1265
            m = _("cannot approve: project '%s' in state '%s'") % (
1315 1266
                    new_project_name, self.state)
......
1331 1282

  
1332 1283
        project.last_application_approved = self
1333 1284
        project.last_approval_date = now
1285
        project.save()
1334 1286
        #ProjectMembership.add_to_project(self)
1335 1287
        project.add_member(self.owner)
1336
        project.save()
1337 1288

  
1338 1289
        precursor = self.precursor_application
1339 1290
        while precursor:
......
1347 1298
        transaction.commit()
1348 1299
        project.check_sync()
1349 1300

  
1350
    def approve_view():
1351
        rejected = self.project.sync()
1352
        
1353
        try:
1354
            notification = build_notification(
1355
                settings.SERVER_EMAIL,
1356
                [self.owner.email],
1357
                _(PROJECT_APPROVED_SUBJECT) % self.definition.__dict__,
1358
                template='im/projects/project_approval_notification.txt',
1359
                dictionary={'object':self}
1360
            ).send()
1361
        except NotificationError, e:
1362
            logger.error(e.messages)
1301
class ProjectResourceGrant(models.Model):
1363 1302

  
1303
    resource = models.ForeignKey(Resource)
1304
    project_application = models.ForeignKey(ProjectApplication, blank=True)
1305
    project_capacity     = models.BigIntegerField(null=True)
1306
    project_import_limit = models.BigIntegerField(null=True)
1307
    project_export_limit = models.BigIntegerField(null=True)
1308
    member_capacity      = models.BigIntegerField(null=True)
1309
    member_import_limit  = models.BigIntegerField(null=True)
1310
    member_export_limit  = models.BigIntegerField(null=True)
1311

  
1312
    objects = ExtendedManager()
1313

  
1314
    class Meta:
1315
        unique_together = ("resource", "project_application")
1364 1316

  
1365 1317
class Project(SyncedModel):
1366 1318
    application                 =   models.OneToOneField(
......
1384 1336
                                            max_length=80,
1385 1337
                                            db_index=True,
1386 1338
                                            unique=True)
1339
    state = models.CharField(max_length=80, default=UNKNOWN)
1387 1340

  
1388 1341
    @property
1389 1342
    def current_application(self):
......
1454 1407
        if self.is_synchronized:
1455 1408
            return
1456 1409
        members = specific_members or self.approved_members
1457
        c, rejected = send_quota(self.approved_members)
1410
        rejected = send_quota(self.approved_members)
1458 1411
        if not rejected:
1459 1412
            self.application = self.last_application_approved
1460 1413
            self.save()
......
1537 1490
            self.termination_date = datetime.now()
1538 1491
            self.save()
1539 1492
            
1540
        try:
1541
            notification = build_notification(
1542
                settings.SERVER_EMAIL,
1543
                [self.current_application.owner.email],
1544
                _(PROJECT_TERMINATION_SUBJECT) % self.definition.__dict__,
1545
                template='im/projects/project_termination_notification.txt',
1546
                dictionary={'object':self.current_application}
1547
            ).send()
1548
        except NotificationError, e:
1549
            logger.error(e.messages)
1493
#         try:
1494
#             notification = build_notification(
1495
#                 settings.SERVER_EMAIL,
1496
#                 [self.current_application.owner.email],
1497
#                 _(PROJECT_TERMINATION_SUBJECT) % self.__dict__,
1498
#                 template='im/projects/project_termination_notification.txt',
1499
#                 dictionary={'object':self.current_application}
1500
#             ).send()
1501
#         except NotificationError, e:
1502
#             logger.error(e.messages)
1550 1503

  
1551 1504
    def suspend(self):
1552 1505
        self.last_approval_date = None
1553 1506
        self.save()
1554 1507
        self.sync()
1555 1508

  
1556
        try:
1557
            notification = build_notification(
1558
                settings.SERVER_EMAIL,
1559
                [self.current_application.owner.email],
1560
                _(PROJECT_SUSPENSION_SUBJECT) % self.definition.__dict__,
1561
                template='im/projects/project_suspension_notification.txt',
1562
                dictionary={'object':self.current_application}
1563
            ).send()
1564
        except NotificationError, e:
1565
            logger.error(e.messages)
1509
#         try:
1510
#             notification = build_notification(
1511
#                 settings.SERVER_EMAIL,
1512
#                 [self.current_application.owner.email],
1513
#                 _(PROJECT_SUSPENSION_SUBJECT) % self.definition.__dict__,
1514
#                 template='im/projects/project_suspension_notification.txt',
1515
#                 dictionary={'object':self.current_application}
1516
#             ).send()
1517
#         except NotificationError, e:
1518
#             logger.error(e.messages)
1566 1519

  
1567 1520

  
1568 1521
QuotaLimits = namedtuple('QuotaLimits', ('holder',
......
1584 1537

  
1585 1538
    def __init__(self, locked=False):
1586 1539
        init = 0 if locked else 1
1587
        from multiprocess import Semaphore
1540
        from multiprocessing import Semaphore
1588 1541
        self._sema = Semaphore(init)
1589 1542

  
1590 1543
    def enter(self):
......
1685 1638
        state, verified = self.sync_verify_get_synced_state()
1686 1639
        if not verified:
1687 1640
            new_state = self.sync_get_new_state()
1688
            m = _("%s: cannot reject: not synched (%s -> %s)") % (
1641
            m = _("%s: cannot reject: not synched (%s -> %s)" % (
1689 1642
                    self, state, new_state))
1690 1643
            raise self.NotSynced(m)
1691 1644

  
......
1730 1683
                            resource     = name,
1731 1684
                            capacity     = f * grant.member_capacity,
1732 1685
                            import_limit = f * grant.member_import_limit,
1733
                            export_limit = f * grant.member_export_limit))
1686
                            export_limit = f * grant.member_export_limit)
1734 1687

  
1735 1688
        # second, add each new limit to its inversed current
1689
        new_grants = self.project.new_application.resource_grants.all()
1736 1690
        for new_grant in new_grants:
1737 1691
            name = grant.resource.name
1738 1692
            cur_grant = tmp_grants.pop(name, None)
......
1775 1729
                    self, state, new_state)
1776 1730
            raise AssertionError(m)
1777 1731

  
1732
        quotas = self.get_quotas(factor=factor)
1778 1733
        try:
1779 1734
            failure = add_quotas(quotas)
1780 1735
            if failure:
......
1859 1814
post_save.connect(user_post_save, sender=User)
1860 1815

  
1861 1816

  
1862
def astakosuser_pre_save(sender, instance, **kwargs):
1863
    instance.aquarium_report = False
1864
    instance.new = False
1865
    try:
1866
        db_instance = AstakosUser.objects.get(id=instance.id)
1867
    except AstakosUser.DoesNotExist:
1868
        # create event
1869
        instance.aquarium_report = True
1870
        instance.new = True
1871
    else:
1872
        get = AstakosUser.__getattribute__
1873
        l = filter(lambda f: get(db_instance, f) != get(instance, f),
1874
                   BILLING_FIELDS)
1875
        instance.aquarium_report = True if l else False
1876
pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
1877

  
1878
def set_default_group(user):
1879
    try:
1880
        default = AstakosGroup.objects.get(name='default')
1881
        Membership(
1882
            group=default, person=user, date_joined=datetime.now()).save()
1883
    except AstakosGroup.DoesNotExist, e:
1884
        logger.exception(e)
1817
# def astakosuser_pre_save(sender, instance, **kwargs):
1818
#     instance.aquarium_report = False
1819
#     instance.new = False
1820
#     try:
1821
#         db_instance = AstakosUser.objects.get(id=instance.id)
1822
#     except AstakosUser.DoesNotExist:
1823
#         # create event
1824
#         instance.aquarium_report = True
1825
#         instance.new = True
1826
#     else:
1827
#         get = AstakosUser.__getattribute__
1828
#         l = filter(lambda f: get(db_instance, f) != get(instance, f),
1829
#                    BILLING_FIELDS)
1830
#         instance.aquarium_report = True if l else False
1831
# pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
1832

  
1833
# def set_default_group(user):
1834
#     try:
1835
#         default = AstakosGroup.objects.get(name='default')
1836
#         Membership(
1837
#             group=default, person=user, date_joined=datetime.now()).save()
1838
#     except AstakosGroup.DoesNotExist, e:
1839
#         logger.exception(e)
1885 1840

  
1886 1841

  
1887 1842
def astakosuser_post_save(sender, instance, created, **kwargs):
1888
    if instance.aquarium_report:
1889
        report_user_event(instance, create=instance.new)
1843
#     if instance.aquarium_report:
1844
#         report_user_event(instance, create=instance.new)
1890 1845
    if not created:
1891 1846
        return
1892
    set_default_group(instance)
1847
#     set_default_group(instance)
1893 1848
    # TODO handle socket.error & IOError
1894 1849
    register_users((instance,))
1895 1850
post_save.connect(astakosuser_post_save, sender=AstakosUser)
......
1902 1857
post_save.connect(resource_post_save, sender=Resource)
1903 1858

  
1904 1859

  
1905
def on_quota_disturbed(sender, users, **kwargs):
1906
#     print '>>>', locals()
1907
    if not users:
1908
        return
1909
    send_quota(users)
1910

  
1911
quota_disturbed = Signal(providing_args=["users"])
1912
quota_disturbed.connect(on_quota_disturbed)
1913

  
1914

  
1915
def send_quota_disturbed(sender, instance, **kwargs):
1916
    users = []
1917
    extend = users.extend
1918
    if sender == Membership:
1919
        if not instance.group.is_enabled:
1920
            return
1921
        extend([instance.person])
1922
    elif sender == AstakosUserQuota:
1923
        extend([instance.user])
1924
    elif sender == AstakosGroupQuota:
1925
        if not instance.group.is_enabled:
1926
            return
1927
        extend(instance.group.astakosuser_set.all())
1928
    elif sender == AstakosGroup:
1929
        if not instance.is_enabled:
1930
            return
1931
    quota_disturbed.send(sender=sender, users=users)
1932
post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
1933
post_delete.connect(send_quota_disturbed, sender=Membership)
1934
post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
1935
post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
1936
post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1937
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1860
# def on_quota_disturbed(sender, users, **kwargs):
1861
# #     print '>>>', locals()
1862
#     if not users:
1863
#         return
1864
#     send_quota(users)
1865
# 
1866
# quota_disturbed = Signal(providing_args=["users"])
1867
# quota_disturbed.connect(on_quota_disturbed)
1868

  
1869

  
1870
# def send_quota_disturbed(sender, instance, **kwargs):
1871
#     users = []
1872
#     extend = users.extend
1873
#     if sender == Membership:
1874
#         if not instance.group.is_enabled:
1875
#             return
1876
#         extend([instance.person])
1877
#     elif sender == AstakosUserQuota:
1878
#         extend([instance.user])
1879
#     elif sender == AstakosGroupQuota:
1880
#         if not instance.group.is_enabled:
1881
#             return
1882
#         extend(instance.group.astakosuser_set.all())
1883
#     elif sender == AstakosGroup:
1884
#         if not instance.is_enabled:
1885
#             return
1886
#     quota_disturbed.send(sender=sender, users=users)
1887
# post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
1888
# post_delete.connect(send_quota_disturbed, sender=Membership)
1889
# post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
1890
# post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
1891
# post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1892
# post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1938 1893

  
1939 1894

  
1940 1895
def renew_token(sender, instance, **kwargs):
......
1942 1897
        instance.renew_token()
1943 1898
pre_save.connect(renew_token, sender=AstakosUser)
1944 1899
pre_save.connect(renew_token, sender=Service)
1945

  
1946

  
1947
def check_closed_join_membership_policy(sender, instance, **kwargs):
1948
    if instance.id:
1949
        return
1950
    if instance.person == instance.project.current_application.owner:
1951
        return
1952
    join_policy = instance.project.current_application.definition.member_join_policy
1953
    if join_policy == get_closed_join():
1954
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
1955
pre_save.connect(check_closed_join_membership_policy, sender=ProjectMembership)
1956

  
1957

  
1958
def check_auto_accept_join_membership_policy(sender, instance, created, **kwargs):
1959
    if not created:
1960
        return
1961
    join_policy = instance.project.current_application.definition.member_join_policy
1962
    if join_policy == get_auto_accept_join() and not instance.acceptance_date:
1963
        instance.accept()
1964
post_save.connect(check_auto_accept_join_membership_policy, sender=ProjectMembership)

Also available in: Unified diff