Project Notifications
[astakos] / snf-astakos-app / astakos / im / models.py
index cba332b..e77c3e3 100644 (file)
@@ -69,7 +69,10 @@ from astakos.im.settings import (
     DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
     AUTH_TOKEN_DURATION, BILLING_FIELDS,
     EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
-    GROUP_CREATION_SUBJECT
+    SITENAME, SERVICES,
+    PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
+    PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
+    PROJECT_MEMBERSHIP_CHANGE_SUBJECT
 )
 from astakos.im.endpoints.qh import (
     register_users, send_quota, register_resources
@@ -157,12 +160,15 @@ class ResourceMetadata(models.Model):
 
 
 class Resource(models.Model):
-    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
+    name = models.CharField(_('Name'), max_length=255)
     meta = models.ManyToManyField(ResourceMetadata)
     service = models.ForeignKey(Service)
     desc = models.TextField(_('Description'), null=True)
     unit = models.CharField(_('Name'), null=True, max_length=255)
     group = models.CharField(_('Group'), null=True, max_length=255)
+    
+    class Meta:
+        unique_together = ("name", "service")
 
     def __str__(self):
         return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
@@ -317,7 +323,19 @@ class AstakosGroup(Group):
         self.owner = l
         map(self.approve_member, l)
 
-
+_default_quota = {}
+def get_default_quota():
+    global _default_quota
+    if _default_quota:
+        return _default_quota
+    for s, data in SERVICES.iteritems():
+        map(
+            lambda d:_default_quota.update(
+                {'%s%s%s' % (s, RESOURCE_SEPARATOR, d.get('name')):d.get('uplimit', 0)}
+            ),
+            data.get('resources', {})
+        )
+    return _default_quota
 
 class AstakosUserManager(UserManager):
 
@@ -446,6 +464,8 @@ class AstakosUser(User):
     def quota(self):
         """Returns a dict with the sum of quota limits per resource"""
         d = defaultdict(int)
+        default_quota = get_default_quota()
+        d.update(default_quota)
         for q in self.policies:
             d[q.resource] += q.uplimit or inf
         for m in self.projectmembership_set.select_related().all():
@@ -456,7 +476,7 @@ class AstakosUser(User):
                 continue
             grants = p.application.definition.projectresourcegrant_set.all()
             for g in grants:
-                d[g.resource] += g.member_limit or inf
+                d[str(g.resource)] += g.member_limit or inf
         # TODO set default for remaining
         return d
 
@@ -1097,10 +1117,6 @@ class ProjectDefinition(models.Model):
         through='ProjectResourceGrant'
     )
     
-    def save(self):
-        self.validate_name()
-        super(ProjectDefinition, self).save()
-        
     @property
     def violated_resource_grants(self):
         return False
@@ -1143,7 +1159,7 @@ class ProjectDefinition(models.Model):
         )
         if q:
             raise ValidationError(
-                {'name': [_(astakos_messages.UNIQUE_PROJECT_NAME_CONSTRAIN_ERR)]}
+                _(astakos_messages.UNIQUE_PROJECT_NAME_CONSTRAIN_ERR)
             )
 
 
@@ -1195,20 +1211,23 @@ class ProjectApplication(models.Model):
 
 
     @staticmethod
-    def submit(definition, resource_policies, applicant, comments, precursor_application=None, commit=True):
-        application = None
+    def submit(definition, resource_policies, applicant, comments,
+               precursor_application=None, commit=True):
+
+        application = ProjectApplication()
         if precursor_application:
-            precursor_application_id = precursor_application.id
-            application = precursor_application
-            application.id = None
+            application.precursor_application = precursor_application
+            application.owner = precursor_application.owner
         else:
-            application = ProjectApplication(owner=applicant)
+            application.owner = applicant
+
         application.definition = definition
-        application.definition.id = None
+        application.definition.resource_policies = resource_policies
         application.applicant = applicant
         application.comments = comments
         application.issue_date = datetime.now()
         application.state = PENDING
+
         if commit:
             application.save()
             application.definition.resource_policies = resource_policies
@@ -1218,34 +1237,50 @@ class ProjectApplication(models.Model):
                     precursor = ProjectApplication.objects.get(id=precursor_application_id)
                 except:
                     pass
-                precursor.state = REPLACED
-                precursor.save()
-                application.precursor_application_id = precursor
+                application.precursor_application = precursor
                 application.save()
-        else:
-            notification = build_notification(
-                settings.SERVER_EMAIL,
-                [i[1] for i in settings.ADMINS],
-                _(GROUP_CREATION_SUBJECT) % {'group':application.definition.name},
-                _('An new project application identified by %(id)s has been submitted.') % application.__dict__
-            )
-            notification.send()
+
+        notification = build_notification(
+            settings.SERVER_EMAIL,
+            [i[1] for i in settings.ADMINS],
+            _(PROJECT_CREATION_SUBJECT) % application.definition.__dict__,
+            template='im/projects/project_creation_notification.txt',
+            dictionary={'object':application}
+        )
+        notification.send()
         return application
-        
+
     def approve(self, approval_user=None):
         """
         If approval_user then during owner membership acceptance
         it is checked whether the request_user is eligible.
+
+        Raises:
+            ValidationError: if there is other alive project with the same name
+
         """
+        try:
+            self.definition.validate_name()
+        except ValidationError, e:
+            raise PermissionDenied(e.messages[0])
         if self.state != PENDING:
-            return
-        create = False
+            raise PermissionDenied(_(PROJECT_ALREADY_ACTIVE))
+
         try:
-            self.precursor_application.project
-        except:
-            create = True
+            precursor = self.precursor_application
+            project = precursor.project
+            project.application = self
+            prev_approval_date = project.last_approval_date
+            project.last_approval_date = datetime.now()
+            project.save()
 
-        if create:
+            p = precursor
+            while p:
+                p.state = REPLACED
+                p.save()
+                p = p.precursor_application
+
+        except:
             kwargs = {
                 'application':self,
                 'creation_date':datetime.now(),
@@ -1253,34 +1288,33 @@ class ProjectApplication(models.Model):
             }
             project = _create_object(Project, **kwargs)
             project.accept_member(self.owner, approval_user)
-        else:
-            project = self.precursor_application.project
-            project.application = self
-            project.last_approval_date = datetime.now()
-            project.save()
+            precursor = None
+
         self.state = APPROVED
         self.save()
 
         notification = build_notification(
             settings.SERVER_EMAIL,
             [self.owner.email],
-            _('Project application has been approved on %s alpha2 testing' % SITENAME),
-            _('Your application request %(id)s has been apporved.')
+            _(PROJECT_APPROVED_SUBJECT) % self.definition.__dict__,
+            template='im/projects/project_approval_notification.txt',
+            dictionary={'object':self}
         )
         notification.send()
 
         rejected = self.project.sync()
         if rejected:
             # revert to precursor
-            project.application = app.precursor_application
-            if project.application:
-                project.last_approval_date = last_approval_date
+            if precursor:
+                project.application = precursor
+                project.last_approval_date = prev_approval_date
                 project.save()
+
             rejected = project.sync()
             if rejected:
                 raise Exception(_(astakos_messages.QH_SYNC_ERROR))
         else:
-            project.last_application_synced = app
+            project.last_application_synced = self
             project.save()
 
 
@@ -1325,9 +1359,9 @@ class Project(models.Model):
     
     @property
     def is_suspended(self):
-        if not self.termination_date:
+        if self.termination_date:
             return False
-        if not self.last_approval_date:
+        if self.last_approval_date:
             if not self.definition.violated_resource_grants:
                 return False
 #             if not self.violated_members_number_limit:
@@ -1380,7 +1414,7 @@ class Project(models.Model):
         m, created = ProjectMembership.objects.get_or_create(
             person=user, project=self
         )
-        m.accept(user, delete_on_failure=created, request_user=None)
+        m.accept(delete_on_failure=created, request_user=None)
 
     def reject_member(self, user, request_user=None):
         """
@@ -1423,22 +1457,25 @@ class Project(models.Model):
             self.terminaton_date = datetime.now()
             self.save()
             
-            notification = build_notification(
-                settings.SERVER_EMAIL,
-                [self.application.owner.email],
-                _('Project %(name)s has been terminated.') %  self.definition.__dict__,
-                _('Project %(name)s has been terminated.') %  self.definition.__dict__
-            )
-            notification.send()
+        notification = build_notification(
+            settings.SERVER_EMAIL,
+            [self.application.owner.email],
+            _(PROJECT_TERMINATION_SUBJECT) % self.definition.__dict__,
+            template='im/projects/project_termination_notification.txt',
+            dictionary={'object':self.application}
+        )
+        notification.send()
 
     def suspend(self):
         self.last_approval_date = None
         self.save()
+        self.sync()
         notification = build_notification(
             settings.SERVER_EMAIL,
             [self.application.owner.email],
-            _('Project %(name)s has been suspended.') %  self.definition.__dict__,
-            _('Project %(name)s has been suspended.') %  self.definition.__dict__
+            _(PROJECT_SUSPENSION_SUBJECT) % self.definition.__dict__,
+            template='im/projects/project_suspension_notification.txt',
+            dictionary={'object':self.application}
         )
         notification.send()
 
@@ -1480,8 +1517,9 @@ class ProjectMembership(models.Model):
         notification = build_notification(
             settings.SERVER_EMAIL,
             [self.person.email],
-            _('Your membership on project %(name)s has been accepted.') % self.project.definition.__dict__,
-            _('Your membership on project %(name)s has been accepted.') % self.project.definition.__dict__
+            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % self.project.definition.__dict__,
+            template='im/projects/project_membership_change_notification.txt',
+            dictionary={'object':self.project.application, 'action':'accepted'}
         ).send()
         self.sync()
     
@@ -1502,13 +1540,15 @@ class ProjectMembership(models.Model):
             project=self.project,
             request_date=self.request_date,
             rejection_date=datetime.now()
-        ).save()
+        )
         self.delete()
+        history_item.save()
         notification = build_notification(
             settings.SERVER_EMAIL,
             [self.person.email],
-            _('Your membership on project %(name)s has been rejected.') % self.project.definition.__dict__,
-            _('Your membership on project %(name)s has been rejected.') % self.project.definition.__dict__
+            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % self.project.definition.__dict__,
+            template='im/projects/project_membership_change_notification.txt',
+            dictionary={'object':self.project.application, 'action':'rejected'}
         ).send()
     
     def remove(self, request_user=None):
@@ -1535,8 +1575,9 @@ class ProjectMembership(models.Model):
         notification = build_notification(
             settings.SERVER_EMAIL,
             [self.person.email],
-            _('Your membership on project %(name)s has been removed.') % self.project.definition.__dict__,
-            _('Your membership on project %(name)s has been removed.') % self.project.definition.__dict__
+            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % self.project.definition.__dict__,
+            template='im/projects/project_membership_change_notification.txt',
+            dictionary={'object':self.project.application, 'action':'removed'}
         ).send()
         self.sync()
     
@@ -1553,11 +1594,11 @@ class ProjectMembership(models.Model):
         self.project.membership_dirty = True
         self.project.save()
         
-        rejected = self.project.sync(specific_members=[self])
+        rejected = self.project.sync(specific_members=[self.person])
         if not rejected:
             # if syncing was successful unset membership_dirty flag
             self.membership_dirty = False
-            self.save()
+            self.project.save()
         
 
 class ProjectMembershipHistory(models.Model):
@@ -1716,8 +1757,9 @@ pre_save.connect(check_closed_join_membership_policy, sender=ProjectMembership)
 
 
 def check_auto_accept_join_membership_policy(sender, instance, created, **kwargs):
-    if created:
-        join_policy = instance.project.application.definition.member_join_policy
-        if join_policy == get_auto_accept_join():
-            instance.accept()
+    if not created:
+        return
+    join_policy = instance.project.application.definition.member_join_policy
+    if join_policy == get_auto_accept_join():
+        instance.accept()
 post_save.connect(check_auto_accept_join_membership_policy, sender=ProjectMembership)
\ No newline at end of file