Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 304acb60

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

494 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
495 49790d9d Sofia Papagiannaki
        after activating.
496 49790d9d Sofia Papagiannaki

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

499 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
500 49790d9d Sofia Papagiannaki
        return ``None``.
501 49790d9d Sofia Papagiannaki

502 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
503 49790d9d Sofia Papagiannaki

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