Revision b22de10a

/dev/null
1
[
2
    {
3
        "model": "im.memberacceptpolicy",
4
        "pk": 1,
5
        "fields": {
6
            "policy": "auto_accept",
7
            "description": "new join requests are automatically accepted by the system"
8
        }
9
    },
10
    {
11
        "model": "im.memberacceptpolicy",
12
        "pk": 2,
13
        "fields": {
14
            "policy": "owner_accepts",
15
            "description": "new join requests must be accepted by the owner of the project"
16
        }
17
    },
18
    {
19
        "model": "im.memberacceptpolicy",
20
        "pk": 3,
21
        "fields": {
22
            "policy": "closed",
23
            "description": "no new members can join the project, even if old ones leave"
24
        }
25
    }
26
]
b/snf-astakos-app/astakos/im/fixtures/member_accept_policies.json
1
[
2
    {
3
        "model": "im.memberrejectpolicy",
4
        "pk": 1,
5
        "fields": {
6
            "policy": "auto_accept",
7
            "description": "remove requests are automatically accepted by the system"
8
        }
9
    },
10
    {
11
        "model": "im.memberrejectpolicy",
12
        "pk": 2,
13
        "fields": {
14
            "policy": "owner_accepts",
15
            "description": "remove requests must be accepted by the owner of the project"
16
        }
17
    },
18
    {
19
        "model": "im.memberrejectpolicy",
20
        "pk": 3,
21
        "fields": {
22
            "policy": "closed",
23
            "description": "members can not leave the project"
24
        }
25
    }
26
]
b/snf-astakos-app/astakos/im/fixtures/member_reject_policies.json
1
[
2
    {
3
        "model": "im.memberacceptpolicy",
4
        "pk": 1,
5
        "fields": {
6
            "policy": "auto_accept",
7
            "description": "leave requests are automatically accepted by the system"
8
        }
9
    },
10
    {
11
        "model": "im.memberacceptpolicy",
12
        "pk": 2,
13
        "fields": {
14
            "policy": "owner_accepts",
15
            "description": "leave requests must be accepted by the owner of the project"
16
        }
17
    },
18
    {
19
        "model": "im.memberacceptpolicy",
20
        "pk": 3,
21
        "fields": {
22
            "policy": "closed",
23
            "description": "no member can leave the project"
24
        }
25
    }
26
]
b/snf-astakos-app/astakos/im/migrations/0036_auto__chg_field_project_termination_date__chg_field_project_last_appro.py
1
# encoding: utf-8
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7
class Migration(SchemaMigration):
8

  
9
    def forwards(self, orm):
10
        
11
        # Changing field 'Project.termination_date'
12
        db.alter_column('im_project', 'termination_date', self.gf('django.db.models.fields.DateTimeField')(null=True))
13

  
14
        # Changing field 'Project.last_approval_date'
15
        db.alter_column('im_project', 'last_approval_date', self.gf('django.db.models.fields.DateTimeField')(null=True))
16

  
17
        # Adding field 'ProjectDefinition.homepage'
18
        db.add_column('im_projectdefinition', 'homepage', self.gf('django.db.models.fields.URLField')(max_length=255, null=True, blank=True), keep_default=False)
19

  
20

  
21
    def backwards(self, orm):
22
        
23
        # User chose to not deal with backwards NULL issues for 'Project.termination_date'
24
        raise RuntimeError("Cannot reverse this migration. 'Project.termination_date' and its values cannot be restored.")
25

  
26
        # User chose to not deal with backwards NULL issues for 'Project.last_approval_date'
27
        raise RuntimeError("Cannot reverse this migration. 'Project.last_approval_date' and its values cannot be restored.")
28

  
29
        # Deleting field 'ProjectDefinition.homepage'
30
        db.delete_column('im_projectdefinition', 'homepage')
31

  
32

  
33
    models = {
34
        'auth.group': {
35
            'Meta': {'object_name': 'Group'},
36
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
37
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
38
            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
39
        },
40
        'auth.permission': {
41
            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
42
            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
43
            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
44
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
45
            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
46
        },
47
        'auth.user': {
48
            'Meta': {'object_name': 'User'},
49
            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
50
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
51
            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
52
            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
53
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
54
            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
55
            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
56
            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
57
            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
58
            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
59
            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
60
            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
61
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
62
        },
63
        'contenttypes.contenttype': {
64
            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
65
            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
66
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67
            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
68
            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
69
        },
70
        'im.additionalmail': {
71
            'Meta': {'object_name': 'AdditionalMail'},
72
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
73
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
75
        },
76
        'im.approvalterms': {
77
            'Meta': {'object_name': 'ApprovalTerms'},
78
            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 23, 16, 19, 367473)', 'db_index': 'True'}),
79
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
80
            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
81
        },
82
        'im.astakosgroup': {
83
            'Meta': {'object_name': 'AstakosGroup', '_ormbases': ['auth.Group']},
84
            'approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
85
            'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 23, 16, 19, 359649)'}),
86
            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
87
            'estimated_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
88
            'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
89
            'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
90
            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
91
            'issue_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
92
            'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.GroupKind']"}),
93
            'max_participants': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
94
            'moderation_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
95
            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosGroupQuota']", 'blank': 'True'})
96
        },
97
        'im.astakosgroupquota': {
98
            'Meta': {'unique_together': "(('resource', 'group'),)", 'object_name': 'AstakosGroupQuota'},
99
            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']", 'blank': 'True'}),
100
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
101
            'limit': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
102
            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
103
            'uplimit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'})
104
        },
105
        'im.astakosuser': {
106
            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
107
            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
108
            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
109
            'astakos_groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosGroup']", 'symmetrical': 'False', 'through': "orm['im.Membership']", 'blank': 'True'}),
110
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
111
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
112
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
113
            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
114
            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
115
            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
116
            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
117
            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
118
            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '100'}),
119
            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
120
            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
121
            'owner': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'owner'", 'null': 'True', 'to': "orm['im.AstakosGroup']"}),
122
            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
123
            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
124
            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
125
            'updated': ('django.db.models.fields.DateTimeField', [], {}),
126
            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'})
127
        },
128
        'im.astakosuserauthprovider': {
129
            'Meta': {'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
130
            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
131
            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
132
            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
133
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
134
            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
135
            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
136
            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
137
        },
138
        'im.astakosuserquota': {
139
            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
140
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
141
            'limit': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
142
            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
143
            'uplimit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
144
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
145
        },
146
        'im.emailchange': {
147
            'Meta': {'object_name': 'EmailChange'},
148
            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
149
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
150
            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
151
            'requested_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 12, 6, 23, 16, 19, 369393)'}),
152
            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchange_user'", 'unique': 'True', 'to': "orm['im.AstakosUser']"})
153
        },
154
        'im.groupkind': {
155
            'Meta': {'object_name': 'GroupKind'},
156
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
157
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
158
        },
159
        'im.invitation': {
160
            'Meta': {'object_name': 'Invitation'},
161
            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
162
            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
163
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
164
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
165
            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
166
            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
167
            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
168
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
169
        },
170
        'im.memberacceptpolicy': {
171
            'Meta': {'object_name': 'MemberAcceptPolicy'},
172
            'description': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
173
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
174
            'policy': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
175
        },
176
        'im.membership': {
177
            'Meta': {'unique_together': "(('person', 'group'),)", 'object_name': 'Membership'},
178
            'date_joined': ('django.db.models.fields.DateField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
179
            'date_requested': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 12, 6, 23, 16, 19, 365072)', 'blank': 'True'}),
180
            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosGroup']"}),
181
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
182
            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
183
        },
184
        'im.pendingthirdpartyuser': {
185
            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
186
            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
187
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
188
            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
189
            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
190
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191
            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
192
            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
193
            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
194
            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
195
            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
196
        },
197
        'im.project': {
198
            'Meta': {'object_name': 'Project'},
199
            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
200
            'creation_date': ('django.db.models.fields.DateTimeField', [], {}),
201
            'last_approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
202
            'last_synced_application': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'last_project'", 'unique': 'True', 'null': 'True', 'to': "orm['im.ProjectApplication']"}),
203
            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
204
            'serial': ('django.db.models.fields.CharField', [], {'default': "'9dacd97c1c1446649791deaaae0414'", 'unique': 'True', 'max_length': '30', 'primary_key': 'True'}),
205
            'termination_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'})
206
        },
207
        'im.projectapplication': {
208
            'Meta': {'object_name': 'ProjectApplication'},
209
            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'my_project_applications'", 'to': "orm['im.AstakosUser']"}),
210
            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
211
            'definition': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['im.ProjectDefinition']", 'unique': 'True'}),
212
            'issue_date': ('django.db.models.fields.DateTimeField', [], {}),
213
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'own_project_applications'", 'to': "orm['im.AstakosUser']"}),
214
            'precursor_application': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['im.ProjectApplication']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
215
            'serial': ('django.db.models.fields.CharField', [], {'default': "'d23622e32e0a48e9b8f15ee063776f'", 'unique': 'True', 'max_length': '30', 'primary_key': 'True'})
216
        },
217
        'im.projectdefinition': {
218
            'Meta': {'object_name': 'ProjectDefinition'},
219
            'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
220
            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
221
            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
222
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
223
            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
224
            'member_accept_policy': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.MemberAcceptPolicy']"}),
225
            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
226
            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
227
            'start_date': ('django.db.models.fields.DateTimeField', [], {})
228
        },
229
        'im.projectmembership': {
230
            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
231
            'decision_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'db_index': 'True'}),
232
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
233
            'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
234
            'issue_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2012, 12, 6, 23, 16, 19, 378504)'}),
235
            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
236
            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"})
237
        },
238
        'im.projectresourcegrant': {
239
            'Meta': {'unique_together': "(('resource', 'project_definition'),)", 'object_name': 'ProjectResourceGrant'},
240
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
241
            'member_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
242
            'project_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectDefinition']", 'blank': 'True'}),
243
            'project_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
244
            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
245
        },
246
        'im.resource': {
247
            'Meta': {'object_name': 'Resource'},
248
            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
249
            'group': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
250
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
251
            'meta': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.ResourceMetadata']", 'symmetrical': 'False'}),
252
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
253
            'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Service']"}),
254
            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'})
255
        },
256
        'im.resourcemetadata': {
257
            'Meta': {'object_name': 'ResourceMetadata'},
258
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
259
            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
260
            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
261
        },
262
        'im.service': {
263
            'Meta': {'object_name': 'Service'},
264
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
265
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
266
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
267
            'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
268
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
269
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
270
            'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'})
271
        },
272
        'im.sessioncatalog': {
273
            'Meta': {'object_name': 'SessionCatalog'},
274
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
275
            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
276
            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
277
        }
278
    }
279

  
280
    complete_apps = ['im']
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)
b/snf-astakos-app/docs/projects_specs.rst
164 164
``members``
165 165
    *the set of members for this project*
166 166

  
167
``membership_updated``
167
``membership_dirty``
168 168
    *boolean attribute declaring that the project
169 169
    needs membership synchronization.
170 170
    It must be atomically set and committed before
......
234 234

  
235 235
    An alive project is delcared synchronized by setting
236 236
    ``last_application_synced`` to be equal to the ``application``,
237
    and setting ``membership_updated`` to false,
237
    and setting ``membership_dirty`` to false,
238 238

  
239 239
    Semantically, the project becomes synchronized when its application
240 240
    definition has been fully implemented and committed to quotaholder,
......
248 248
    a project is declared synchronized if and only if:
249 249

  
250 250
    - ``last_application_synced`` equals ``application``
251
    - ``membership_updated`` is false
251
    - ``membership_dirty`` is false
252 252
    - ``termination_start_date`` is null or ``termination_date`` is set
253 253

  
254 254
    Depending on which of the previous three clauses fail,

Also available in: Unified diff