bug fixing
[astakos] / snf-astakos-app / astakos / im / models.py
1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
2
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 import hashlib
35 import uuid
36
37 from time import asctime
38 from datetime import datetime, timedelta
39 from base64 import b64encode
40 from urlparse import urlparse
41
42 from django.db import models
43 from django.contrib.auth.models import User, UserManager
44
45 from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION
46 from astakos.im.queue.userevent import UserEvent
47 from synnefo.lib.queue import exchange_connect, exchange_send, exchange_close, Receipt
48
49 QUEUE_CLIENT_ID = 3 # Astakos.
50
51 class AstakosUser(User):
52     """
53     Extends ``django.contrib.auth.models.User`` by defining additional fields.
54     """
55     # Use UserManager to get the create_user method, etc.
56     objects = UserManager()
57     
58     affiliation = models.CharField('Affiliation', max_length=255, blank=True)
59     provider = models.CharField('Provider', max_length=255, blank=True)
60     
61     #for invitations
62     user_level = DEFAULT_USER_LEVEL
63     level = models.IntegerField('Inviter level', default=user_level)
64     invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
65     
66     auth_token = models.CharField('Authentication Token', max_length=32,
67                                   null=True, blank=True)
68     auth_token_created = models.DateTimeField('Token creation date', null=True)
69     auth_token_expires = models.DateTimeField('Token expiration date', null=True)
70     
71     updated = models.DateTimeField('Update date')
72     is_verified = models.BooleanField('Is verified?', default=False)
73     
74     # ex. screen_name for twitter, eppn for shibboleth
75     third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
76     
77     email_verified = models.BooleanField('Email verified?', default=False)
78     
79     has_credits = models.BooleanField('Has credits?', default=False)
80     has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
81     date_signed_terms = models.DateTimeField('Signed terms date', null=True)
82     
83     @property
84     def realname(self):
85         return '%s %s' %(self.first_name, self.last_name)
86     
87     @realname.setter
88     def realname(self, value):
89         parts = value.split(' ')
90         if len(parts) == 2:
91             self.first_name = parts[0]
92             self.last_name = parts[1]
93         else:
94             self.last_name = parts[0]
95     
96     @property
97     def invitation(self):
98         try:
99             return Invitation.objects.get(username=self.email)
100         except Invitation.DoesNotExist:
101             return None
102     
103     def save(self, update_timestamps=True, **kwargs):
104         if update_timestamps:
105             if not self.id:
106                 self.date_joined = datetime.now()
107             self.updated = datetime.now()
108         if not self.id:
109             # set username
110             while not self.username:
111                 username =  uuid.uuid4().hex[:30]
112                 try:
113                     AstakosUser.objects.get(username = username)
114                 except AstakosUser.DoesNotExist, e:
115                     self.username = username
116             self.is_active = False
117             if not self.provider:
118                 self.provider = 'local'
119         report_user_event(self)
120         super(AstakosUser, self).save(**kwargs)
121     
122     def renew_token(self):
123         md5 = hashlib.md5()
124         md5.update(self.username)
125         md5.update(self.realname.encode('ascii', 'ignore'))
126         md5.update(asctime())
127         
128         self.auth_token = b64encode(md5.digest())
129         self.auth_token_created = datetime.now()
130         self.auth_token_expires = self.auth_token_created + \
131                                   timedelta(hours=AUTH_TOKEN_DURATION)
132     
133     def __unicode__(self):
134         return self.username
135
136 class ApprovalTerms(models.Model):
137     """
138     Model for approval terms
139     """
140     
141     date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
142     location = models.CharField('Terms location', max_length=255)
143
144 class Invitation(models.Model):
145     """
146     Model for registring invitations
147     """
148     inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
149                                 null=True)
150     realname = models.CharField('Real name', max_length=255)
151     username = models.CharField('Unique ID', max_length=255, unique=True)
152     code = models.BigIntegerField('Invitation code', db_index=True)
153     #obsolete: we keep it just for transfering the data
154     is_accepted = models.BooleanField('Accepted?', default=False)
155     is_consumed = models.BooleanField('Consumed?', default=False)
156     created = models.DateTimeField('Creation date', auto_now_add=True)
157     #obsolete: we keep it just for transfering the data
158     accepted = models.DateTimeField('Acceptance date', null=True, blank=True)
159     consumed = models.DateTimeField('Consumption date', null=True, blank=True)
160     
161     def consume(self):
162         self.is_consumed = True
163         self.consumed = datetime.now()
164         self.save()
165         
166     def __unicode__(self):
167         return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
168
169 def report_user_event(user):
170     def should_send(user):
171         # report event incase of new user instance
172         # or if specific fields are modified
173         if not user.id:
174             return True
175         db_instance = AstakosUser.objects.get(id = user.id)
176         for f in BILLING_FIELDS:
177             if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
178                 return True
179         return False
180     
181     if QUEUE_CONNECTION and should_send(user):
182         eventType = 'create' if not user.id else 'modify'
183         body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
184         conn = exchange_connect(QUEUE_CONNECTION)
185         parts = urlparse(QUEUE_CONNECTION)
186         exchange = parts.path[1:]
187         routing_key = '%s.user' % exchange
188         exchange_send(conn, routing_key, body)
189         exchange_close(conn)