Revision b22de10a snf-astakos-app/astakos/im/models.py

b/snf-astakos-app/astakos/im/models.py
1017 1017
    def __str__(self):
1018 1018
        return self.policy
1019 1019

  
1020
class MemberRejectPolicy(models.Model):
1021
    policy = models.CharField(_('Policy'), max_length=255, unique=True, db_index=True)
1022
    description = models.CharField(_('Description'), max_length=80)
1023

  
1024
    def __str__(self):
1025
        return self.policy
1026

  
1020 1027
_auto_accept = False
1021 1028
def get_auto_accept():
1022 1029
    global _auto_accept
......
1047 1054
    def save(self):
1048 1055
        self.validate_name()
1049 1056
        super(ProjectDefinition, self).save()
1050
    
1051
    def validate_name(self):
1052
        """
1053
        Validate name uniqueness among all active projects.
1054
        """
1055
        alive_projects = list(get_alive_projects())
1056
        q = filter(lambda p: p.definition.name==self.name, alive_projects)
1057
        if q:
1058
            raise ValidationError({'name': [_(astakos_messages.UNIQUE_PROJECT_NAME_CONSTRAIN_ERR)]})
1059
    
1057
        
1060 1058
    @property
1061 1059
    def violated_resource_grants(self):
1062 1060
        return False
......
1086 1084
            uplimit = p.get('uplimit', 0)
1087 1085
            update = p.get('update', True)
1088 1086
            self.add_resource_policy(service, resource, uplimit, update)
1089
    
1090
    def get_absolute_url(self):
1091
        return reverse('project_application_detail', args=(self.serial,))
1092

  
1093 1087

  
1094 1088
class ProjectResourceGrant(models.Model):
1095 1089
    objects = ExtendedManager()
......
1108 1102
        unique=True,
1109 1103
        default=uuid.uuid4().hex[:30]
1110 1104
    )
1111
    applicant = models.ForeignKey(AstakosUser, related_name='my_project_applications')
1112
    owner = models.ForeignKey(AstakosUser, related_name='own_project_applications')
1105
    applicant = models.ForeignKey(
1106
        AstakosUser,
1107
        related_name='my_project_applications',
1108
        db_index=True)
1109
    owner = models.ForeignKey(
1110
        AstakosUser,
1111
        related_name='own_project_applications',
1112
        db_index=True
1113
    )
1113 1114
    comments = models.TextField(null=True, blank=True)
1114 1115
    definition = models.OneToOneField(ProjectDefinition)
1115 1116
    issue_date = models.DateTimeField()
......
1129 1130
    application = models.OneToOneField(ProjectApplication, related_name='project')
1130 1131
    creation_date = models.DateTimeField()
1131 1132
    last_approval_date = models.DateTimeField(null=True)
1133
    termination_start_date = models.DateTimeField(null=True)
1132 1134
    termination_date = models.DateTimeField(null=True)
1133 1135
    members = models.ManyToManyField(AstakosUser, through='ProjectMembership')
1134
    last_synced_application = models.OneToOneField(
1136
    membership_dirty = models.BooleanField(default=False)
1137
    last_application_synced = models.OneToOneField(
1135 1138
        ProjectApplication, related_name='last_project', null=True, blank=True
1136 1139
    )
1137 1140
    
1138 1141
    @property
1139 1142
    def definition(self):
1140 1143
        return self.application.definition
1141
    
1144

  
1145
    @property
1146
    def violated_members_number_limit(self):
1147
        return len(self.approved_members) <= self.definition.limit_on_members_number
1148

  
1142 1149
    @property
1143 1150
    def is_valid(self):
1144 1151
        try:
......
1147 1154
            return False
1148 1155
        else:
1149 1156
            return True
1150
    
1157
        
1151 1158
    @property
1152 1159
    def is_active(self):
1153 1160
        if not self.is_valid:
......
1158 1165
            return False
1159 1166
        if self.definition.violated_resource_grants:
1160 1167
            return False
1168
#         if self.violated_members_number_limit:
1169
#             return False
1161 1170
        return True
1162 1171
    
1163 1172
    @property
......
1177 1186
        if not self.last_approval_date:
1178 1187
            if not self.definition.violated_resource_grants:
1179 1188
                return False
1189
#             if not self.violated_members_number_limit:
1190
#                 return False
1180 1191
        return True
1181 1192
    
1182 1193
    @property
......
1195 1206
        return False
1196 1207
    
1197 1208
    @property
1198
    def approved_members(self):
1199
        return [m.person for m in self.members.filter(is_accepted=True)]
1200
    
1201
    def suspend(self):
1202
        self.last_approval_date = None
1203
        self.save()
1209
    def is_synchronized(self):
1210
        return self.last_application_synced == self.application and \
1211
            not self.membership_dirty and \
1212
            (not self.termination_start_date or termination_date)
1204 1213
    
1205
    def terminate(self):
1206
        self.terminaton_date = datetime.now()
1207
        self.save()
1208
    
1209
    def sync(self):
1210
        c, rejected = send_quota(self.approved_members)
1214
    @property
1215
    def approved_members(self):
1216
        return [m.person for m in self.projectmembership_set.filter(is_accepted=True)]
1217
        
1218
    def sync(self, specific_members=()):
1219
        if self.is_synchornized():
1220
            return
1221
        members = specific_members or self.approved_members
1222
        c, rejected = send_quota(members)
1211 1223
        return rejected
1212 1224
    
1213 1225
    def add_member(self, user, request_user=None):
......
1229 1241
            return
1230 1242
        if created:
1231 1243
            m.issue_date = datetime.now()
1244
        
1232 1245
        m.is_accepted = True
1233 1246
        m.decision_date = datetime.now()
1234 1247
        m.save()
1248
        
1249
        # set membership_dirty flag
1250
        self.membership_dirty = True
1251
        self.save()
1252
        
1253
        rejected = self.sync([user])
1254
        if not rejected:
1255
            # if syncing was successful unset membership_dirty flag
1256
            self.membership_dirty = False
1257
            self.save()
1258
        
1235 1259
        notification = build_notification(
1236 1260
            settings.SERVER_EMAIL,
1237 1261
            [user.email],
1238 1262
            _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__,
1239 1263
            _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__
1240 1264
        )
1241
        notification.send()
1242

  
1265
    
1243 1266
    def remove_member(self, user, request_user=None):
1244 1267
        if user.is_digit():
1245 1268
            user = _lookup_object(AstakosUser, id=user)
......
1254 1277
        m.is_accepted = False
1255 1278
        m.decision_date = datetime.now()
1256 1279
        m.save()
1280
        
1281
        # set membership_dirty flag
1282
        self.membership_dirty = True
1283
        self.save()
1284
        
1285
        rejected = self.sync([user])
1286
        if not rejected:
1287
            # if syncing was successful unset membership_dirty flag
1288
            self.membership_dirty = False
1289
            self.save()
1290
            
1257 1291
        notification = build_notification(
1258 1292
            settings.SERVER_EMAIL,
1259 1293
            [user.email],
......
1261 1295
            _('Your membership on project %(name)s has been removed.') % project.definition.__dict__
1262 1296
        )
1263 1297
        notification.send()
1298
            
1299

  
1300
    def validate_name(self):
1301
        """
1302
        Validate name uniqueness among all active projects.
1303
        """
1304
        alive_projects = list(get_alive_projects())
1305
        q = filter(
1306
            lambda p: p.definition.name == self.definition.name and \
1307
                p.application.serial != self.application.serial,
1308
            alive_projects
1309
        )
1310
        if q:
1311
            raise ValidationError(
1312
                {'name': [_(astakos_messages.UNIQUE_PROJECT_NAME_CONSTRAIN_ERR)]}
1313
            )
1314
    
1315
    @classmethod
1316
    def submit(definition, applicant, comments, precursor_application=None, commit=True):
1317
        if precursor_application and precursor_application.project.is_valid:
1318
            application = precursor_application.copy()
1319
            application.precursor_application = precursor_application
1320
        else:
1321
            application = ProjectApplication(owner=applicant)
1322
        application.definition = definition
1323
        application.applicant = applicant
1324
        application.comments = comments
1325
        application.issue_date = datetime.now()
1326
        if commit:
1327
            definition.save()
1328
            application.save()
1329
        if applicant.is_superuser():
1330
            self.approve_application()
1331
        notification = build_notification(
1332
            settings.SERVER_EMAIL,
1333
            [i[1] for i in settings.ADMINS],
1334
            _(GROUP_CREATION_SUBJECT) % {'group':application.definition.name},
1335
            _('An new project application identified by %(serial)s has been submitted.') % application.__dict__
1336
        )
1337
        notification.send()
1338
        return application
1339
    
1340
    def approve(self, approval_user=None):
1341
        """
1342
        If approval_user then during owner membership acceptance
1343
        it is checked whether the request_user is eligible.
1344
        """
1345
        if not self.precursor_application:
1346
            kwargs = {
1347
                'application':self,
1348
                'creation_date':datetime.now(),
1349
                'last_approval_date':datetime.now(),
1350
            }
1351
            project = _create_object(Project, **kwargs)
1352
            project.add_member(self.owner, approval_user)
1353
        else:
1354
            project = self.precursor_application.project
1355
            last_approval_date = project.last_approval_date
1356
            if project.is_valid:
1357
                project.application = app
1358
                project.last_approval_date = datetime.now()
1359
                project.save()
1360
            else:
1361
                raise Exception(_(astakos_messages.INVALID_PROJECT) % project.__dict__)
1362
        
1363
        rejected = self.sync()
1364
        if rejected:
1365
            # revert to precursor
1366
            project.appication = app.precursor_application
1367
            if project.application:
1368
                project.last_approval_date = last_approval_date
1369
                project.save()
1370
            rejected = synchonize_project(project.serial)
1371
            if rejected:
1372
                raise Exception(_(astakos_messages.QH_SYNC_ERROR))
1373
        else:
1374
            project.last_application_synced = app
1375
            project.save()
1376
            sender, recipients, subject, message
1377
            notification = build_notification(
1378
                settings.SERVER_EMAIL,
1379
                [project.owner.email],
1380
                _('Project application has been approved on %s alpha2 testing' % SITENAME),
1381
                _('Your application request %(serial)s has been apporved.')
1382
            )
1383
            notification.send()
1384
    
1385
    def terminate(self):
1386
        self.termination_start_date = datetime.now()
1387
        self.terminaton_date = None
1388
        self.save()
1389
        
1390
        rejected = self.sync()
1391
        if not rejected:
1392
            self.termination_start_date = None
1393
            self.terminaton_date = datetime.now()
1394
            self.save()
1395
            
1396
            notification = build_notification(
1397
                settings.SERVER_EMAIL,
1398
                [self.application.owner.email],
1399
                _('Project %(name)s has been terminated.') %  self.definition.__dict__,
1400
                _('Project %(name)s has been terminated.') %  self.definition.__dict__
1401
            )
1402
            notification.send()
1403

  
1404
    def suspend(self):
1405
        self.last_approval_date = None
1406
        self.save()
1407
        notification = build_notification(
1408
            settings.SERVER_EMAIL,
1409
            [self.application.owner.email],
1410
            _('Project %(name)s has been suspended.') %  self.definition.__dict__,
1411
            _('Project %(name)s has been suspended.') %  self.definition.__dict__
1412
        )
1413
        notification.send()
1414

  
1264 1415

  
1265 1416

  
1266 1417
class ProjectMembership(models.Model):
......
1322 1473
def list_applications():
1323 1474
    return ProjectApplication.objects.all()
1324 1475

  
1325
def submit_application(definition, applicant, comments, precursor_application=None, commit=True):
1326
    if precursor_application:
1327
        application = precursor_application.copy()
1328
        application.precursor_application = precursor_application
1329
    else:
1330
        application = ProjectApplication(owner=applicant)
1331
    application.definition = definition
1332
    application.applicant = applicant
1333
    application.comments = comments
1334
    application.issue_date = datetime.now()
1335
    if commit:
1336
        definition.save()
1337
        application.save()
1338
    notification = build_notification(
1339
        settings.SERVER_EMAIL,
1340
        [i[1] for i in settings.ADMINS],
1341
        _(GROUP_CREATION_SUBJECT) % {'group':application.definition.name},
1342
        _('An new project application identified by %(serial)s has been submitted.') % application.__dict__
1343
    )
1344
    notification.send()
1345
    return application
1346
    
1347
def approve_application(serial, request_user=None):
1348
    app = _lookup_object(ProjectApplication, serial=serial)
1349
    if not app.precursor_application:
1350
        kwargs = {
1351
            'application':app,
1352
            'creation_date':datetime.now(),
1353
            'last_approval_date':datetime.now(),
1354
        }
1355
        project = _create_object(Project, **kwargs)
1356
        project.add_member(app.owner, request_user)
1357
    else:
1358
        project = app.precursor_application.project
1359
        last_approval_date = project.last_approval_date
1360
        if project.is_valid:
1361
            project.application = app
1362
            project.last_approval_date = datetime.now()
1363
            project.save()
1364
        else:
1365
            raise Exception(_(astakos_messages.INVALID_PROJECT) % project.__dict__)
1366
    
1367
    rejected = synchonize_project(project.serial)
1368
    if rejected:
1369
        # revert to precursor
1370
        project.appication = app.precursor_application
1371
        if project.application:
1372
            project.last_approval_date = last_approval_date
1373
        project.save()
1374
        rejected = synchonize_project(project.serial)
1375
        if rejected:
1376
            raise Exception(_(astakos_messages.QH_SYNC_ERROR))
1377
    else:
1378
        project.last_application_synced = app
1379
        project.save()
1380
        sender, recipients, subject, message
1381
        notification = build_notification(
1382
            settings.SERVER_EMAIL,
1383
            [project.owner.email],
1384
            _('Project application has been approved on %s alpha2 testing' % SITENAME),
1385
            _('Your application request %(serial)s has been apporved.')
1386
        )
1387
        notification.send()
1388

  
1389 1476

  
1390 1477
def list_projects(filter_property=None):
1391 1478
    if filter_property:
......
1395 1482
        )
1396 1483
    return Project.objects.all()
1397 1484

  
1398
def suspend_project(serial):
1399
    project = _lookup_object(Project, serial=serial)
1400
    project.suspend()
1401
    notification = build_notification(
1402
        settings.SERVER_EMAIL,
1403
        [project.owner.email],
1404
        _('Project %(name)s has been suspended.') %  project.definition.__dict__,
1405
        _('Project %(name)s has been suspended.') %  project.definition.__dict__
1406
    )
1407
    notification.send()
1408

  
1409
def terminate_project(serial):
1410
    project = _lookup_object(Project, serial=serial)
1411
    project.termination()
1412
    notification = build_notification(
1413
        settings.SERVER_EMAIL,
1414
        [project.owner.email],
1415
        _('Project %(name)s has been terminated.') %  project.definition.__dict__,
1416
        _('Project %(name)s has been terminated.') %  project.definition.__dict__
1417
    )
1418
    notification.send()
1419 1485

  
1420 1486
def synchonize_project(serial):
1421 1487
    project = _lookup_object(Project, serial=serial)
1422
    if project.app != project.last_application_synced:
1423
        return project.sync()
1424
     
1488
    return project.sync()
1489

  
1490

  
1425 1491
def create_astakos_user(u):
1426 1492
    try:
1427 1493
        AstakosUser.objects.get(user_ptr=u.pk)

Also available in: Unified diff