Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 28252c7f

History | View | Annotate | Download (19.6 kB)

1 aba1e498 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 6c736ed7 Kostas Papadimitriou
#
3 64cd4730 Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 64cd4730 Antony Chazapis
# without modification, are permitted provided that the following
5 64cd4730 Antony Chazapis
# conditions are met:
6 6c736ed7 Kostas Papadimitriou
#
7 64cd4730 Antony Chazapis
#   1. Redistributions of source code must retain the above
8 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
9 64cd4730 Antony Chazapis
#      disclaimer.
10 6c736ed7 Kostas Papadimitriou
#
11 64cd4730 Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
13 64cd4730 Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 64cd4730 Antony Chazapis
#      provided with the distribution.
15 6c736ed7 Kostas Papadimitriou
#
16 64cd4730 Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 64cd4730 Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 64cd4730 Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 64cd4730 Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 64cd4730 Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 64cd4730 Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 64cd4730 Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 64cd4730 Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 64cd4730 Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 64cd4730 Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 64cd4730 Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 64cd4730 Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 6c736ed7 Kostas Papadimitriou
#
29 64cd4730 Antony Chazapis
# The views and conclusions contained in the software and
30 64cd4730 Antony Chazapis
# documentation are those of the authors and should not be
31 64cd4730 Antony Chazapis
# interpreted as representing official policies, either expressed
32 64cd4730 Antony Chazapis
# or implied, of GRNET S.A.
33 64cd4730 Antony Chazapis
34 64cd4730 Antony Chazapis
import hashlib
35 15efc749 Sofia Papagiannaki
import uuid
36 18ffbee1 Sofia Papagiannaki
import logging
37 0a569195 Sofia Papagiannaki
import json
38 64cd4730 Antony Chazapis
39 64cd4730 Antony Chazapis
from time import asctime
40 64cd4730 Antony Chazapis
from datetime import datetime, timedelta
41 64cd4730 Antony Chazapis
from base64 import b64encode
42 0a569195 Sofia Papagiannaki
from urlparse import urlparse, urlunparse
43 8f5a3a06 Sofia Papagiannaki
from random import randint
44 64cd4730 Antony Chazapis
45 49790d9d Sofia Papagiannaki
from django.db import models, IntegrityError
46 18ffbee1 Sofia Papagiannaki
from django.contrib.auth.models import User, UserManager, Group
47 0a569195 Sofia Papagiannaki
from django.utils.translation import ugettext as _
48 0a569195 Sofia Papagiannaki
from django.core.exceptions import ValidationError
49 49790d9d Sofia Papagiannaki
from django.template.loader import render_to_string
50 49790d9d Sofia Papagiannaki
from django.core.mail import send_mail
51 49790d9d Sofia Papagiannaki
from django.db import transaction
52 ff9290ec Sofia Papagiannaki
from django.db.models.signals import post_save, post_syncdb
53 d68590fd Sofia Papagiannaki
from django.db.models import Q
54 64cd4730 Antony Chazapis
55 49790d9d Sofia Papagiannaki
from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, \
56 111f3da6 Sofia Papagiannaki
    AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION, SITENAME, \
57 111f3da6 Sofia Papagiannaki
    EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL
58 9c01d9d1 Sofia Papagiannaki
59 9c01d9d1 Sofia Papagiannaki
QUEUE_CLIENT_ID = 3 # Astakos.
60 64cd4730 Antony Chazapis
61 18ffbee1 Sofia Papagiannaki
logger = logging.getLogger(__name__)
62 18ffbee1 Sofia Papagiannaki
63 8e45d6fd Sofia Papagiannaki
class Service(models.Model):
64 8e45d6fd Sofia Papagiannaki
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
65 8e45d6fd Sofia Papagiannaki
    url = models.FilePathField()
66 8e45d6fd Sofia Papagiannaki
    icon = models.FilePathField(blank=True)
67 8e45d6fd Sofia Papagiannaki
    auth_token = models.CharField('Authentication Token', max_length=32,
68 8e45d6fd Sofia Papagiannaki
                                  null=True, blank=True)
69 8e45d6fd Sofia Papagiannaki
    auth_token_created = models.DateTimeField('Token creation date', null=True)
70 8e45d6fd Sofia Papagiannaki
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
71 8e45d6fd Sofia Papagiannaki
    
72 8e45d6fd Sofia Papagiannaki
    def save(self, **kwargs):
73 8e45d6fd Sofia Papagiannaki
        if not self.id:
74 8e45d6fd Sofia Papagiannaki
            self.renew_token()
75 8e45d6fd Sofia Papagiannaki
        self.full_clean()
76 8e45d6fd Sofia Papagiannaki
        super(Service, self).save(**kwargs)
77 8e45d6fd Sofia Papagiannaki
    
78 8e45d6fd Sofia Papagiannaki
    def renew_token(self):
79 8e45d6fd Sofia Papagiannaki
        md5 = hashlib.md5()
80 8e45d6fd Sofia Papagiannaki
        md5.update(self.name.encode('ascii', 'ignore'))
81 8e45d6fd Sofia Papagiannaki
        md5.update(self.url.encode('ascii', 'ignore'))
82 8e45d6fd Sofia Papagiannaki
        md5.update(asctime())
83 8e45d6fd Sofia Papagiannaki
84 8e45d6fd Sofia Papagiannaki
        self.auth_token = b64encode(md5.digest())
85 8e45d6fd Sofia Papagiannaki
        self.auth_token_created = datetime.now()
86 8e45d6fd Sofia Papagiannaki
        self.auth_token_expires = self.auth_token_created + \
87 8e45d6fd Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
88 8e45d6fd Sofia Papagiannaki
    
89 8e45d6fd Sofia Papagiannaki
    def __str__(self):
90 8e45d6fd Sofia Papagiannaki
        return self.name
91 8e45d6fd Sofia Papagiannaki
92 8e45d6fd Sofia Papagiannaki
class ResourceMetadata(models.Model):
93 8e45d6fd Sofia Papagiannaki
    key = models.CharField('Name', max_length=255, unique=True, db_index=True)
94 8e45d6fd Sofia Papagiannaki
    value = models.CharField('Value', max_length=255)
95 8e45d6fd Sofia Papagiannaki
96 8e45d6fd Sofia Papagiannaki
class Resource(models.Model):
97 8e45d6fd Sofia Papagiannaki
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
98 8e45d6fd Sofia Papagiannaki
    meta = models.ManyToManyField(ResourceMetadata)
99 8e45d6fd Sofia Papagiannaki
    service = models.ForeignKey(Service)
100 8e45d6fd Sofia Papagiannaki
    
101 8e45d6fd Sofia Papagiannaki
    def __str__(self):
102 8e45d6fd Sofia Papagiannaki
        return '%s : %s' % (self.service, self.name)
103 8e45d6fd Sofia Papagiannaki
104 8e45d6fd Sofia Papagiannaki
class GroupKind(models.Model):
105 8e45d6fd Sofia Papagiannaki
    name = models.CharField('Name', max_length=255, unique=True, db_index=True)
106 8e45d6fd Sofia Papagiannaki
    
107 8e45d6fd Sofia Papagiannaki
    def __str__(self):
108 8e45d6fd Sofia Papagiannaki
        return self.name
109 8e45d6fd Sofia Papagiannaki
110 8e45d6fd Sofia Papagiannaki
class AstakosGroup(Group):
111 8e45d6fd Sofia Papagiannaki
    kind = models.ForeignKey(GroupKind)
112 8e45d6fd Sofia Papagiannaki
    desc = models.TextField('Description', null=True)
113 8e45d6fd Sofia Papagiannaki
    policy = models.ManyToManyField(Resource, null=True, blank=True, through='AstakosGroupQuota')
114 8e45d6fd Sofia Papagiannaki
    creation_date = models.DateTimeField('Creation date', default=datetime.now())
115 8e45d6fd Sofia Papagiannaki
    issue_date = models.DateTimeField('Issue date', null=True)
116 8e45d6fd Sofia Papagiannaki
    expiration_date = models.DateTimeField('Expiration date', null=True)
117 28252c7f Sofia Papagiannaki
    moderation_enabled = models.BooleanField('Moderated membership?', default=True)
118 8e45d6fd Sofia Papagiannaki
    approval_date = models.DateTimeField('Activation date', null=True, blank=True)
119 76c68fd6 Sofia Papagiannaki
    estimated_participants = models.PositiveIntegerField('Estimated #participants', null=True)
120 8e45d6fd Sofia Papagiannaki
    
121 8e45d6fd Sofia Papagiannaki
    @property
122 8e45d6fd Sofia Papagiannaki
    def is_disabled(self):
123 8e45d6fd Sofia Papagiannaki
        if not approval_date:
124 8e45d6fd Sofia Papagiannaki
            return False
125 8e45d6fd Sofia Papagiannaki
        return True
126 8e45d6fd Sofia Papagiannaki
    
127 8e45d6fd Sofia Papagiannaki
    @property
128 8e45d6fd Sofia Papagiannaki
    def is_active(self):
129 8e45d6fd Sofia Papagiannaki
        if self.is_disabled:
130 8e45d6fd Sofia Papagiannaki
            return False
131 8e45d6fd Sofia Papagiannaki
        if not self.issue_date:
132 8e45d6fd Sofia Papagiannaki
            return False
133 8e45d6fd Sofia Papagiannaki
        if not self.expiration_date:
134 8e45d6fd Sofia Papagiannaki
            return True
135 8e45d6fd Sofia Papagiannaki
        now = datetime.now()
136 8e45d6fd Sofia Papagiannaki
        if self.issue_date > now:
137 8e45d6fd Sofia Papagiannaki
            return False
138 8e45d6fd Sofia Papagiannaki
        if now >= self.expiration_date:
139 8e45d6fd Sofia Papagiannaki
            return False
140 8e45d6fd Sofia Papagiannaki
        return True
141 8e45d6fd Sofia Papagiannaki
    
142 8e45d6fd Sofia Papagiannaki
    @property
143 8e45d6fd Sofia Papagiannaki
    def participants(self):
144 01ac12d5 Sofia Papagiannaki
        return len(self.approved_members)
145 8e45d6fd Sofia Papagiannaki
    
146 8e45d6fd Sofia Papagiannaki
    def approve(self):
147 8e45d6fd Sofia Papagiannaki
        self.approval_date = datetime.now()
148 8e45d6fd Sofia Papagiannaki
        self.save()
149 8e45d6fd Sofia Papagiannaki
    
150 8e45d6fd Sofia Papagiannaki
    def disapprove(self):
151 8e45d6fd Sofia Papagiannaki
        self.approval_date = None
152 8e45d6fd Sofia Papagiannaki
        self.save()
153 8e45d6fd Sofia Papagiannaki
    
154 01ac12d5 Sofia Papagiannaki
    def approve_member(self, person):
155 01ac12d5 Sofia Papagiannaki
        try:
156 01ac12d5 Sofia Papagiannaki
            self.membership_set.create(person=person, date_joined=datetime.now())
157 01ac12d5 Sofia Papagiannaki
        except IntegrityError:
158 01ac12d5 Sofia Papagiannaki
            m = self.membership_set.get(person=person)
159 01ac12d5 Sofia Papagiannaki
            m.date_joined = datetime.now()
160 01ac12d5 Sofia Papagiannaki
            m.save()
161 8e45d6fd Sofia Papagiannaki
    
162 01ac12d5 Sofia Papagiannaki
    def disapprove_member(self, person):
163 01ac12d5 Sofia Papagiannaki
        self.membership_set.remove(person=person)
164 01ac12d5 Sofia Papagiannaki
    
165 01ac12d5 Sofia Papagiannaki
    @property
166 01ac12d5 Sofia Papagiannaki
    def members(self):
167 01ac12d5 Sofia Papagiannaki
        return map(lambda m:m.person, self.membership_set.all())
168 d68590fd Sofia Papagiannaki
    
169 01ac12d5 Sofia Papagiannaki
    @property
170 01ac12d5 Sofia Papagiannaki
    def approved_members(self):
171 01ac12d5 Sofia Papagiannaki
        f = filter(lambda m:m.is_approved, self.membership_set.all())
172 01ac12d5 Sofia Papagiannaki
        return map(lambda m:m.person, f)
173 d68590fd Sofia Papagiannaki
    
174 01ac12d5 Sofia Papagiannaki
    @property
175 01ac12d5 Sofia Papagiannaki
    def policies(self):
176 01ac12d5 Sofia Papagiannaki
        return self.astakosgroupquota_set.all()
177 01ac12d5 Sofia Papagiannaki
    
178 01ac12d5 Sofia Papagiannaki
    @property
179 d68590fd Sofia Papagiannaki
    def has_undefined_policies(self):
180 d68590fd Sofia Papagiannaki
        # TODO: can avoid query?
181 d68590fd Sofia Papagiannaki
        return Resource.objects.filter(~Q(astakosgroup=self)).exists()
182 8e45d6fd Sofia Papagiannaki
183 0905ccd2 Sofia Papagiannaki
class AstakosUser(User):
184 890b0eaf Sofia Papagiannaki
    """
185 890b0eaf Sofia Papagiannaki
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
186 890b0eaf Sofia Papagiannaki
    """
187 0905ccd2 Sofia Papagiannaki
    # Use UserManager to get the create_user method, etc.
188 0905ccd2 Sofia Papagiannaki
    objects = UserManager()
189 6c736ed7 Kostas Papadimitriou
190 5ed6816e Sofia Papagiannaki
    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
191 5ed6816e Sofia Papagiannaki
    provider = models.CharField('Provider', max_length=255, blank=True)
192 6c736ed7 Kostas Papadimitriou
193 64cd4730 Antony Chazapis
    #for invitations
194 92defad4 Sofia Papagiannaki
    user_level = DEFAULT_USER_LEVEL
195 a196eb7e Sofia Papagiannaki
    level = models.IntegerField('Inviter level', default=user_level)
196 ebd369d0 Sofia Papagiannaki
    invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
197 6c736ed7 Kostas Papadimitriou
198 64cd4730 Antony Chazapis
    auth_token = models.CharField('Authentication Token', max_length=32,
199 0905ccd2 Sofia Papagiannaki
                                  null=True, blank=True)
200 0905ccd2 Sofia Papagiannaki
    auth_token_created = models.DateTimeField('Token creation date', null=True)
201 0905ccd2 Sofia Papagiannaki
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
202 6c736ed7 Kostas Papadimitriou
203 64cd4730 Antony Chazapis
    updated = models.DateTimeField('Update date')
204 890b0eaf Sofia Papagiannaki
    is_verified = models.BooleanField('Is verified?', default=False)
205 6c736ed7 Kostas Papadimitriou
206 15efc749 Sofia Papagiannaki
    # ex. screen_name for twitter, eppn for shibboleth
207 15efc749 Sofia Papagiannaki
    third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
208 6c736ed7 Kostas Papadimitriou
209 ebd369d0 Sofia Papagiannaki
    email_verified = models.BooleanField('Email verified?', default=False)
210 6c736ed7 Kostas Papadimitriou
211 59f598f1 Sofia Papagiannaki
    has_credits = models.BooleanField('Has credits?', default=False)
212 270dd48d Sofia Papagiannaki
    has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
213 0a569195 Sofia Papagiannaki
    date_signed_terms = models.DateTimeField('Signed terms date', null=True, blank=True)
214 59f598f1 Sofia Papagiannaki
    
215 751d24cf Sofia Papagiannaki
    activation_sent = models.DateTimeField('Activation sent data', null=True, blank=True)
216 751d24cf Sofia Papagiannaki
    
217 8e45d6fd Sofia Papagiannaki
    policy = models.ManyToManyField(Resource, null=True, through='AstakosUserQuota')
218 8e45d6fd Sofia Papagiannaki
    
219 8e45d6fd Sofia Papagiannaki
    astakos_groups = models.ManyToManyField(AstakosGroup, verbose_name=_('agroups'), blank=True,
220 8e45d6fd Sofia Papagiannaki
        help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."),
221 8e45d6fd Sofia Papagiannaki
        through='Membership')
222 8e45d6fd Sofia Papagiannaki
    
223 18ffbee1 Sofia Papagiannaki
    __has_signed_terms = False
224 18ffbee1 Sofia Papagiannaki
    __groupnames = []
225 18ffbee1 Sofia Papagiannaki
    
226 8e45d6fd Sofia Papagiannaki
    owner = models.ManyToManyField(AstakosGroup, related_name='owner', null=True)
227 8e45d6fd Sofia Papagiannaki
    
228 74b273d8 Sofia Papagiannaki
    class Meta:
229 74b273d8 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
230 74b273d8 Sofia Papagiannaki
    
231 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
232 18ffbee1 Sofia Papagiannaki
        super(AstakosUser, self).__init__(*args, **kwargs)
233 18ffbee1 Sofia Papagiannaki
        self.__has_signed_terms = self.has_signed_terms
234 18ffbee1 Sofia Papagiannaki
        if self.id:
235 18ffbee1 Sofia Papagiannaki
            self.__groupnames = [g.name for g in self.groups.all()]
236 18ffbee1 Sofia Papagiannaki
        else:
237 18ffbee1 Sofia Papagiannaki
            self.is_active = False
238 18ffbee1 Sofia Papagiannaki
    
239 0905ccd2 Sofia Papagiannaki
    @property
240 0905ccd2 Sofia Papagiannaki
    def realname(self):
241 0905ccd2 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
242 6c736ed7 Kostas Papadimitriou
243 0905ccd2 Sofia Papagiannaki
    @realname.setter
244 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
245 0905ccd2 Sofia Papagiannaki
        parts = value.split(' ')
246 0905ccd2 Sofia Papagiannaki
        if len(parts) == 2:
247 0905ccd2 Sofia Papagiannaki
            self.first_name = parts[0]
248 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[1]
249 0905ccd2 Sofia Papagiannaki
        else:
250 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[0]
251 6c736ed7 Kostas Papadimitriou
252 64cd4730 Antony Chazapis
    @property
253 64cd4730 Antony Chazapis
    def invitation(self):
254 64cd4730 Antony Chazapis
        try:
255 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
256 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
257 64cd4730 Antony Chazapis
            return None
258 6c736ed7 Kostas Papadimitriou
259 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
260 64cd4730 Antony Chazapis
        if update_timestamps:
261 64cd4730 Antony Chazapis
            if not self.id:
262 0905ccd2 Sofia Papagiannaki
                self.date_joined = datetime.now()
263 64cd4730 Antony Chazapis
            self.updated = datetime.now()
264 18ffbee1 Sofia Papagiannaki
        
265 18ffbee1 Sofia Papagiannaki
        # update date_signed_terms if necessary
266 18ffbee1 Sofia Papagiannaki
        if self.__has_signed_terms != self.has_signed_terms:
267 18ffbee1 Sofia Papagiannaki
            self.date_signed_terms = datetime.now()
268 18ffbee1 Sofia Papagiannaki
        
269 9c01d9d1 Sofia Papagiannaki
        if not self.id:
270 9c01d9d1 Sofia Papagiannaki
            # set username
271 9c01d9d1 Sofia Papagiannaki
            while not self.username:
272 9c01d9d1 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
273 9c01d9d1 Sofia Papagiannaki
                try:
274 9c01d9d1 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
275 9c01d9d1 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
276 9c01d9d1 Sofia Papagiannaki
                    self.username = username
277 9c01d9d1 Sofia Papagiannaki
            if not self.provider:
278 9c01d9d1 Sofia Papagiannaki
                self.provider = 'local'
279 9c01d9d1 Sofia Papagiannaki
        report_user_event(self)
280 591d0505 Sofia Papagiannaki
        self.validate_unique_email_isactive()
281 751d24cf Sofia Papagiannaki
        if self.is_active and self.activation_sent:
282 751d24cf Sofia Papagiannaki
            # reset the activation sent
283 751d24cf Sofia Papagiannaki
            self.activation_sent = None
284 8e45d6fd Sofia Papagiannaki
        
285 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
286 18ffbee1 Sofia Papagiannaki
        
287 18ffbee1 Sofia Papagiannaki
        # set group if does not exist
288 18ffbee1 Sofia Papagiannaki
        groupname = 'shibboleth' if self.provider == 'shibboleth' else 'default'
289 18ffbee1 Sofia Papagiannaki
        if groupname not in self.__groupnames:
290 18ffbee1 Sofia Papagiannaki
            try:
291 18ffbee1 Sofia Papagiannaki
                group = Group.objects.get(name = groupname)
292 18ffbee1 Sofia Papagiannaki
                self.groups.add(group)
293 18ffbee1 Sofia Papagiannaki
            except Group.DoesNotExist, e:
294 18ffbee1 Sofia Papagiannaki
                logger.exception(e)
295 64cd4730 Antony Chazapis
    
296 64cd4730 Antony Chazapis
    def renew_token(self):
297 64cd4730 Antony Chazapis
        md5 = hashlib.md5()
298 0905ccd2 Sofia Papagiannaki
        md5.update(self.username)
299 64cd4730 Antony Chazapis
        md5.update(self.realname.encode('ascii', 'ignore'))
300 64cd4730 Antony Chazapis
        md5.update(asctime())
301 6c736ed7 Kostas Papadimitriou
302 64cd4730 Antony Chazapis
        self.auth_token = b64encode(md5.digest())
303 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
304 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
305 92defad4 Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
306 111f3da6 Sofia Papagiannaki
        msg = 'Token renewed for %s' % self.email
307 111f3da6 Sofia Papagiannaki
        logger._log(LOGGING_LEVEL, msg, [])
308 6c736ed7 Kostas Papadimitriou
309 64cd4730 Antony Chazapis
    def __unicode__(self):
310 0905ccd2 Sofia Papagiannaki
        return self.username
311 0a569195 Sofia Papagiannaki
    
312 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
313 0a569195 Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username = self.username)
314 0a569195 Sofia Papagiannaki
        q = q.filter(email = self.email)
315 0a569195 Sofia Papagiannaki
        if q.count() != 0:
316 0a569195 Sofia Papagiannaki
            return True
317 0a569195 Sofia Papagiannaki
        return False
318 0a569195 Sofia Papagiannaki
    
319 591d0505 Sofia Papagiannaki
    def validate_unique_email_isactive(self):
320 0a569195 Sofia Papagiannaki
        """
321 0a569195 Sofia Papagiannaki
        Implements a unique_together constraint for email and is_active fields.
322 0a569195 Sofia Papagiannaki
        """
323 0a569195 Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username = self.username)
324 0a569195 Sofia Papagiannaki
        q = q.filter(email = self.email)
325 0a569195 Sofia Papagiannaki
        q = q.filter(is_active = self.is_active)
326 0a569195 Sofia Papagiannaki
        if q.count() != 0:
327 0a569195 Sofia Papagiannaki
            raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
328 09e7393c Sofia Papagiannaki
    
329 09e7393c Sofia Papagiannaki
    def signed_terms(self):
330 09e7393c Sofia Papagiannaki
        term = get_latest_terms()
331 09e7393c Sofia Papagiannaki
        if not term:
332 09e7393c Sofia Papagiannaki
            return True
333 09e7393c Sofia Papagiannaki
        if not self.has_signed_terms:
334 09e7393c Sofia Papagiannaki
            return False
335 09e7393c Sofia Papagiannaki
        if not self.date_signed_terms:
336 09e7393c Sofia Papagiannaki
            return False
337 09e7393c Sofia Papagiannaki
        if self.date_signed_terms < term.date:
338 09e7393c Sofia Papagiannaki
            self.has_signed_terms = False
339 f0f92965 Sofia Papagiannaki
            self.date_signed_terms = None
340 09e7393c Sofia Papagiannaki
            self.save()
341 09e7393c Sofia Papagiannaki
            return False
342 09e7393c Sofia Papagiannaki
        return True
343 8e45d6fd Sofia Papagiannaki
    
344 8e45d6fd Sofia Papagiannaki
    def enroll_group(self, group):
345 8e45d6fd Sofia Papagiannaki
        self.membership_set.add(group)
346 8e45d6fd Sofia Papagiannaki
    
347 8e45d6fd Sofia Papagiannaki
    def get_astakos_groups(self, approved=True):
348 8e45d6fd Sofia Papagiannaki
        if approved:
349 8e45d6fd Sofia Papagiannaki
            return self.membership_set().filter(is_approved=True)
350 8e45d6fd Sofia Papagiannaki
        return self.membership_set().all()
351 8e45d6fd Sofia Papagiannaki
352 8e45d6fd Sofia Papagiannaki
class Membership(models.Model):
353 8e45d6fd Sofia Papagiannaki
    person = models.ForeignKey(AstakosUser)
354 8e45d6fd Sofia Papagiannaki
    group = models.ForeignKey(AstakosGroup)
355 01ac12d5 Sofia Papagiannaki
    date_requested = models.DateField(default=datetime.now(), blank=True)
356 01ac12d5 Sofia Papagiannaki
    date_joined = models.DateField(null=True, db_index=True, blank=True)
357 8e45d6fd Sofia Papagiannaki
    
358 8e45d6fd Sofia Papagiannaki
    class Meta:
359 8e45d6fd Sofia Papagiannaki
        unique_together = ("person", "group")
360 8e45d6fd Sofia Papagiannaki
    
361 28252c7f Sofia Papagiannaki
    def save(self):
362 28252c7f Sofia Papagiannaki
        if not self.id:
363 28252c7f Sofia Papagiannaki
            if not self.group.moderation_enabled:
364 28252c7f Sofia Papagiannaki
                self.date_joined = datetime.now()
365 28252c7f Sofia Papagiannaki
        super(Membership, self).save()
366 28252c7f Sofia Papagiannaki
    
367 8e45d6fd Sofia Papagiannaki
    @property
368 8e45d6fd Sofia Papagiannaki
    def is_approved(self):
369 8e45d6fd Sofia Papagiannaki
        if self.date_joined:
370 8e45d6fd Sofia Papagiannaki
            return True
371 8e45d6fd Sofia Papagiannaki
        return False
372 01ac12d5 Sofia Papagiannaki
    
373 01ac12d5 Sofia Papagiannaki
    def approve(self):
374 01ac12d5 Sofia Papagiannaki
        self.date_joined = datetime.now()
375 01ac12d5 Sofia Papagiannaki
        self.save()
376 01ac12d5 Sofia Papagiannaki
        
377 01ac12d5 Sofia Papagiannaki
    def disapprove(self):
378 01ac12d5 Sofia Papagiannaki
        self.delete()
379 8e45d6fd Sofia Papagiannaki
380 8e45d6fd Sofia Papagiannaki
class AstakosGroupQuota(models.Model):
381 8e45d6fd Sofia Papagiannaki
    limit = models.PositiveIntegerField('Limit')
382 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
383 8e45d6fd Sofia Papagiannaki
    group = models.ForeignKey(AstakosGroup, blank=True)
384 8e45d6fd Sofia Papagiannaki
    
385 8e45d6fd Sofia Papagiannaki
    class Meta:
386 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "group")
387 8e45d6fd Sofia Papagiannaki
388 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
389 8e45d6fd Sofia Papagiannaki
    limit = models.PositiveIntegerField('Limit')
390 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
391 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
392 8e45d6fd Sofia Papagiannaki
    
393 8e45d6fd Sofia Papagiannaki
    class Meta:
394 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
395 09e7393c Sofia Papagiannaki
396 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
397 270dd48d Sofia Papagiannaki
    """
398 270dd48d Sofia Papagiannaki
    Model for approval terms
399 270dd48d Sofia Papagiannaki
    """
400 6c736ed7 Kostas Papadimitriou
401 270dd48d Sofia Papagiannaki
    date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
402 270dd48d Sofia Papagiannaki
    location = models.CharField('Terms location', max_length=255)
403 270dd48d Sofia Papagiannaki
404 64cd4730 Antony Chazapis
class Invitation(models.Model):
405 890b0eaf Sofia Papagiannaki
    """
406 890b0eaf Sofia Papagiannaki
    Model for registring invitations
407 890b0eaf Sofia Papagiannaki
    """
408 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
409 64cd4730 Antony Chazapis
                                null=True)
410 64cd4730 Antony Chazapis
    realname = models.CharField('Real name', max_length=255)
411 ebd369d0 Sofia Papagiannaki
    username = models.CharField('Unique ID', max_length=255, unique=True)
412 64cd4730 Antony Chazapis
    code = models.BigIntegerField('Invitation code', db_index=True)
413 64cd4730 Antony Chazapis
    is_consumed = models.BooleanField('Consumed?', default=False)
414 64cd4730 Antony Chazapis
    created = models.DateTimeField('Creation date', auto_now_add=True)
415 64cd4730 Antony Chazapis
    consumed = models.DateTimeField('Consumption date', null=True, blank=True)
416 64cd4730 Antony Chazapis
    
417 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
418 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
419 8f5a3a06 Sofia Papagiannaki
        if not self.id:
420 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
421 8f5a3a06 Sofia Papagiannaki
    
422 64cd4730 Antony Chazapis
    def consume(self):
423 64cd4730 Antony Chazapis
        self.is_consumed = True
424 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
425 64cd4730 Antony Chazapis
        self.save()
426 6c736ed7 Kostas Papadimitriou
427 64cd4730 Antony Chazapis
    def __unicode__(self):
428 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
429 9c01d9d1 Sofia Papagiannaki
430 9c01d9d1 Sofia Papagiannaki
def report_user_event(user):
431 9c01d9d1 Sofia Papagiannaki
    def should_send(user):
432 9c01d9d1 Sofia Papagiannaki
        # report event incase of new user instance
433 9c01d9d1 Sofia Papagiannaki
        # or if specific fields are modified
434 9c01d9d1 Sofia Papagiannaki
        if not user.id:
435 9c01d9d1 Sofia Papagiannaki
            return True
436 8e45d6fd Sofia Papagiannaki
        try:
437 8e45d6fd Sofia Papagiannaki
            db_instance = AstakosUser.objects.get(id = user.id)
438 8e45d6fd Sofia Papagiannaki
        except AstakosUser.DoesNotExist:
439 8e45d6fd Sofia Papagiannaki
            return True
440 9c01d9d1 Sofia Papagiannaki
        for f in BILLING_FIELDS:
441 9c01d9d1 Sofia Papagiannaki
            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
442 9c01d9d1 Sofia Papagiannaki
                return True
443 9c01d9d1 Sofia Papagiannaki
        return False
444 6c736ed7 Kostas Papadimitriou
445 3a9f4931 Sofia Papagiannaki
    if QUEUE_CONNECTION and should_send(user):
446 6c736ed7 Kostas Papadimitriou
447 6c736ed7 Kostas Papadimitriou
        from astakos.im.queue.userevent import UserEvent
448 6c736ed7 Kostas Papadimitriou
        from synnefo.lib.queue import exchange_connect, exchange_send, \
449 6c736ed7 Kostas Papadimitriou
                exchange_close
450 6c736ed7 Kostas Papadimitriou
451 59f598f1 Sofia Papagiannaki
        eventType = 'create' if not user.id else 'modify'
452 59f598f1 Sofia Papagiannaki
        body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
453 3a9f4931 Sofia Papagiannaki
        conn = exchange_connect(QUEUE_CONNECTION)
454 9e19989d Sofia Papagiannaki
        parts = urlparse(QUEUE_CONNECTION)
455 270dd48d Sofia Papagiannaki
        exchange = parts.path[1:]
456 270dd48d Sofia Papagiannaki
        routing_key = '%s.user' % exchange
457 9c01d9d1 Sofia Papagiannaki
        exchange_send(conn, routing_key, body)
458 68cb6899 Sofia Papagiannaki
        exchange_close(conn)
459 8f5a3a06 Sofia Papagiannaki
460 8f5a3a06 Sofia Papagiannaki
def _generate_invitation_code():
461 8f5a3a06 Sofia Papagiannaki
    while True:
462 8f5a3a06 Sofia Papagiannaki
        code = randint(1, 2L**63 - 1)
463 8f5a3a06 Sofia Papagiannaki
        try:
464 8f5a3a06 Sofia Papagiannaki
            Invitation.objects.get(code=code)
465 8f5a3a06 Sofia Papagiannaki
            # An invitation with this code already exists, try again
466 8f5a3a06 Sofia Papagiannaki
        except Invitation.DoesNotExist:
467 09e7393c Sofia Papagiannaki
            return code
468 09e7393c Sofia Papagiannaki
469 09e7393c Sofia Papagiannaki
def get_latest_terms():
470 09e7393c Sofia Papagiannaki
    try:
471 09e7393c Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
472 09e7393c Sofia Papagiannaki
        return term
473 09e7393c Sofia Papagiannaki
    except IndexError:
474 09e7393c Sofia Papagiannaki
        pass
475 49790d9d Sofia Papagiannaki
    return None
476 49790d9d Sofia Papagiannaki
477 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
478 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
479 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
480 49790d9d Sofia Papagiannaki
        """
481 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
482 49790d9d Sofia Papagiannaki
        ``User`` if valid.
483 49790d9d Sofia Papagiannaki

484 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
485 49790d9d Sofia Papagiannaki
        after activating.
486 49790d9d Sofia Papagiannaki

487 49790d9d Sofia Papagiannaki
        If the key is not valid or has expired, return ``None``.
488 49790d9d Sofia Papagiannaki

489 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
490 49790d9d Sofia Papagiannaki
        return ``None``.
491 49790d9d Sofia Papagiannaki

492 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
493 49790d9d Sofia Papagiannaki

494 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
495 49790d9d Sofia Papagiannaki
        """
496 49790d9d Sofia Papagiannaki
        try:
497 49790d9d Sofia Papagiannaki
            email_change = self.model.objects.get(activation_key=activation_key)
498 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
499 49790d9d Sofia Papagiannaki
                email_change.delete()
500 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
501 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
502 49790d9d Sofia Papagiannaki
            try:
503 49790d9d Sofia Papagiannaki
                AstakosUser.objects.get(email=email_change.new_email_address)
504 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
505 49790d9d Sofia Papagiannaki
                pass
506 49790d9d Sofia Papagiannaki
            else:
507 49790d9d Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
508 49790d9d Sofia Papagiannaki
            # update user
509 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
510 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
511 49790d9d Sofia Papagiannaki
            user.save()
512 49790d9d Sofia Papagiannaki
            email_change.delete()
513 49790d9d Sofia Papagiannaki
            return user
514 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
515 49790d9d Sofia Papagiannaki
            raise ValueError(_('Invalid activation key'))
516 49790d9d Sofia Papagiannaki
517 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
518 49790d9d Sofia Papagiannaki
    new_email_address = models.EmailField(_(u'new e-mail address'), help_text=_(u'Your old email address will be used until you verify your new one.'))
519 49790d9d Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, unique=True, related_name='emailchange_user')
520 49790d9d Sofia Papagiannaki
    requested_at = models.DateTimeField(default=datetime.now())
521 49790d9d Sofia Papagiannaki
    activation_key = models.CharField(max_length=40, unique=True, db_index=True)
522 49790d9d Sofia Papagiannaki
523 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
524 49790d9d Sofia Papagiannaki
525 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
526 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
527 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
528 ff9290ec Sofia Papagiannaki
529 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
530 ca828a10 Sofia Papagiannaki
    """
531 ca828a10 Sofia Papagiannaki
    Model for registring invitations
532 ca828a10 Sofia Papagiannaki
    """
533 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
534 1eec103a Sofia Papagiannaki
    email = models.EmailField()
535 ca828a10 Sofia Papagiannaki
536 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
537 ff9290ec Sofia Papagiannaki
    try:
538 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
539 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
540 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
541 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
542 ff9290ec Sofia Papagiannaki
        extended_user.renew_token()
543 ff9290ec Sofia Papagiannaki
        extended_user.save()
544 ff9290ec Sofia Papagiannaki
    except:
545 ff9290ec Sofia Papagiannaki
        pass
546 ff9290ec Sofia Papagiannaki
547 ff9290ec Sofia Papagiannaki
def superuser_post_syncdb(sender, **kwargs):
548 ff9290ec Sofia Papagiannaki
    # if there was created a superuser
549 ff9290ec Sofia Papagiannaki
    # associate it with an AstakosUser
550 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
551 ff9290ec Sofia Papagiannaki
    for u in admins:
552 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
553 ff9290ec Sofia Papagiannaki
554 ff9290ec Sofia Papagiannaki
post_syncdb.connect(superuser_post_syncdb)
555 ff9290ec Sofia Papagiannaki
556 ff9290ec Sofia Papagiannaki
def superuser_post_save(sender, instance, **kwargs):
557 ff9290ec Sofia Papagiannaki
    if instance.is_superuser:
558 ff9290ec Sofia Papagiannaki
        create_astakos_user(instance)
559 ff9290ec Sofia Papagiannaki
560 ff9290ec Sofia Papagiannaki
post_save.connect(superuser_post_save, sender=User)