Revision 9f8ad4c5

b/db/fixtures/initial_data.json
51 51
            "public": 1,
52 52
            "link": 1
53 53
        }
54
    },
55
    {
56
        "model" : "db.Invitations",
57
        "pk" : 1,
58
        "fields": {
59
            "source": 1,
60
            "target": 1,
61
            "accepted": "True",
62
            "level" : -1
63
        }
54 64
    }
55 65
] 
b/db/migrations/0017_auto__add_field_invitations_level.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
        # Adding field 'Invitations.level'
12
        db.add_column('db_invitations', 'level', self.gf('django.db.models.fields.IntegerField')(null=True), keep_default=False)
13

  
14

  
15
    def backwards(self, orm):
16
        
17
        # Deleting field 'Invitations.level'
18
        db.delete_column('db_invitations', 'level')
19

  
20

  
21
    models = {
22
        'db.debit': {
23
            'Meta': {'object_name': 'Debit'},
24
            'description': ('django.db.models.fields.TextField', [], {}),
25
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
26
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}),
27
            'vm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']"}),
28
            'when': ('django.db.models.fields.DateTimeField', [], {})
29
        },
30
        'db.disk': {
31
            'Meta': {'object_name': 'Disk'},
32
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
33
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
35
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']", 'null': 'True', 'blank': 'True'}),
36
            'size': ('django.db.models.fields.PositiveIntegerField', [], {}),
37
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
38
            'vm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']", 'null': 'True', 'blank': 'True'})
39
        },
40
        'db.flavor': {
41
            'Meta': {'unique_together': "(('cpu', 'ram', 'disk'),)", 'object_name': 'Flavor'},
42
            'cpu': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
43
            'disk': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
44
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
45
            'ram': ('django.db.models.fields.IntegerField', [], {'default': '0'})
46
        },
47
        'db.flavorcost': {
48
            'Meta': {'object_name': 'FlavorCost'},
49
            'cost_active': ('django.db.models.fields.PositiveIntegerField', [], {}),
50
            'cost_inactive': ('django.db.models.fields.PositiveIntegerField', [], {}),
51
            'effective_from': ('django.db.models.fields.DateTimeField', [], {}),
52
            'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}),
53
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
54
        },
55
        'db.image': {
56
            'Meta': {'object_name': 'Image'},
57
            'backend_id': ('django.db.models.fields.CharField', [], {'default': "'debian_base'", 'max_length': '50'}),
58
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
59
            'format': ('django.db.models.fields.CharField', [], {'default': "'dump'", 'max_length': '30'}),
60
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
61
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
62
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']", 'null': 'True', 'blank': 'True'}),
63
            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
64
            'sourcevm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']", 'null': 'True'}),
65
            'state': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
66
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
67
        },
68
        'db.imagemetadata': {
69
            'Meta': {'object_name': 'ImageMetadata'},
70
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
71
            'image': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Image']"}),
72
            'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
73
            'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'})
74
        },
75
        'db.invitations': {
76
            'Meta': {'object_name': 'Invitations'},
77
            'accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
78
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
79
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
80
            'level': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
81
            'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source'", 'to': "orm['db.SynnefoUser']"}),
82
            'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'target'", 'to': "orm['db.SynnefoUser']"}),
83
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
84
        },
85
        'db.limit': {
86
            'Meta': {'object_name': 'Limit'},
87
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
88
            'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
89
            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}),
90
            'value': ('django.db.models.fields.IntegerField', [], {})
91
        },
92
        'db.network': {
93
            'Meta': {'object_name': 'Network'},
94
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
95
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96
            'link': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['db.NetworkLink']"}),
97
            'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'through': "orm['db.NetworkInterface']", 'symmetrical': 'False'}),
98
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
99
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']", 'null': 'True'}),
100
            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
101
            'state': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
102
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
103
        },
104
        'db.networkinterface': {
105
            'Meta': {'object_name': 'NetworkInterface'},
106
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
107
            'firewall_profile': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
108
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109
            'index': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
110
            'ipv4': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}),
111
            'ipv6': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
112
            'mac': ('django.db.models.fields.CharField', [], {'max_length': '17', 'null': 'True'}),
113
            'machine': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.VirtualMachine']"}),
114
            'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'nics'", 'to': "orm['db.Network']"}),
115
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
116
        },
117
        'db.networklink': {
118
            'Meta': {'object_name': 'NetworkLink'},
119
            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
120
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121
            'index': ('django.db.models.fields.IntegerField', [], {}),
122
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
123
            'network': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': "orm['db.Network']"})
124
        },
125
        'db.synnefouser': {
126
            'Meta': {'object_name': 'SynnefoUser'},
127
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
128
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
129
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
130
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
131
            'credit': ('django.db.models.fields.IntegerField', [], {}),
132
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
133
            'max_invitations': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
134
            'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
135
            'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
136
            'tmp_auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
137
            'tmp_auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
138
            'type': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
139
            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
140
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
141
        },
142
        'db.virtualmachine': {
143
            'Meta': {'object_name': 'VirtualMachine'},
144
            'action': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
145
            'backendjobid': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
146
            'backendjobstatus': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
147
            'backendlogmsg': ('django.db.models.fields.TextField', [], {'null': 'True'}),
148
            'backendopcode': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
149
            'charged': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 7, 6, 9, 57, 57, 523638)'}),
150
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
151
            'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
152
            'flavor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Flavor']"}),
153
            'hostid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
154
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
155
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
156
            'operstate': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
157
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}),
158
            'sourceimage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.Image']"}),
159
            'suspended': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
160
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
161
        },
162
        'db.virtualmachinegroup': {
163
            'Meta': {'object_name': 'VirtualMachineGroup'},
164
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
165
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
166
            'machines': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['db.VirtualMachine']", 'symmetrical': 'False'}),
167
            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
168
            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.SynnefoUser']"}),
169
            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
170
        },
171
        'db.virtualmachinemetadata': {
172
            'Meta': {'object_name': 'VirtualMachineMetadata'},
173
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
174
            'meta_key': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
175
            'meta_value': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
176
            'vm': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['db.VirtualMachine']"})
177
        }
178
    }
179

  
180
    complete_apps = ['db']
b/db/models.py
447 447
    accepted = models.BooleanField('Is the invitation accepted?', default=False)
448 448
    created = models.DateTimeField(auto_now_add=True)
449 449
    updated = models.DateTimeField(auto_now=True)
450
    level = models.IntegerField('Invitation depth level', null=True)
450 451

  
451 452
    class Meta:
452 453
        verbose_name = u'Invitation'
b/invitations/invitations.py
238 238
    )
239 239

  
240 240

  
241
def get_invitee_level(source):
242
    return get_user_inv_level(source) + 1
243

  
244

  
245
def get_user_inv_level(u):
246
    inv = Invitations.objects.filter(target = u)
247

  
248
    if inv is None:
249
        raise Exception("User without invitation", u)
250

  
251
    return inv[0].level
252

  
253

  
241 254
@transaction.commit_on_success
242 255
def add_invitation(source, name, email):
243 256
    """
......
263 276
    if not r:
264 277
        raise Exception("Invited user cannot be added")
265 278

  
279
    u = target[0]
280
    invitee_level = get_invitee_level(source)
281

  
282
    u.max_invitations = settings.INVITATIONS_PER_LEVEL[invitee_level]
283
    u.save()
284

  
266 285
    inv = Invitations()
267 286
    inv.source = source
268
    inv.target = target[0]
287
    inv.target = u
288
    inv.level = invitee_level
269 289
    inv.save()
270 290
    return inv
271 291

  
b/invitations/tests.py
33 33

  
34 34
import invitations
35 35
from synnefo.db.models import SynnefoUser
36
from django.conf import settings
36 37

  
37 38

  
38 39
class InvitationsTestCase(TestCase):
......
43 44
        self.client = Client()
44 45

  
45 46
    def test_add_invitation(self):
47
        """
48
            Tests whether invitations can be added
49
        """
46 50
        source = SynnefoUser.objects.filter(auth_token = self.token)[0]
47 51
        invitations.add_invitation(source, "Test", "test@gmail.com")
48 52

  
......
58 62
        except invitations.AlreadyInvited:
59 63
            self.assertTrue(True)
60 64

  
61
        # 
65
    def test_get_invitee_level(self):
66
        """
67
            Checks whether invitation levels and their limits are being respected
68
        """
69
        source = SynnefoUser.objects.filter(auth_token = self.token)[0]
70

  
71
        self.assertEqual(invitations.get_user_inv_level(source), -1)
72

  
73
        inv = invitations.add_invitation(source, "Test", "test@gmail.com")
74
        self.assertEqual(inv.target.max_invitations, settings.INVITATIONS_PER_LEVEL[0])
75
        self.assertEqual(invitations.get_user_inv_level(inv.target), 0)
76

  
77
        inv = invitations.add_invitation(inv.target, "Test2", "test2@gmail.com")
78
        self.assertEqual(invitations.get_user_inv_level(inv.target), 1)
79
        self.assertEqual(inv.target.max_invitations, settings.INVITATIONS_PER_LEVEL[1])
b/logic/users.py
46 46
    user.uniq = unq
47 47
    user.type = t
48 48
    user.credit = 10 #TODO: Fix this when we have a per group policy
49
    if hasattr(user, 'max_invitations'):
50
        user.max_invitations = settings.MAX_INVITATIONS
51 49
    user.save()
52 50
    create_auth_token(user)
53 51

  
b/settings.d/50-invitations.conf
35 35
# 
36 36
# -*- coding: utf-8 -*-
37 37

  
38
# Max number of invitations allowed per user
39
MAX_INVITATIONS = 3
38
# Max number of invitations allowed per level
39
INVITATIONS_PER_LEVEL = {
40
   #Level  #Max Invitations
41
    0   :   10000,
42
    1   :   3,
43
    2   :   2,
44
    3   :   1,
45
    4   :   0
46
}
40 47

  
41 48
# Key to encrypt X-Auth-Token with when sending invitations
42 49
INVITATION_ENCR_KEY = '8d342f6e7a0366c632978a80257019af'
43 50

  
44 51
# Days for which an invitation is active
45
INVITATION_VALID_DAYS = 30
52
INVITATION_VALID_DAYS = 180
46 53

  
47 54
# SMTP server to use when sending out emails
48 55
#SMTP_SERVER="127.0.0.1"

Also available in: Unified diff