Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 3abf6c78

History | View | Annotate | Download (20.2 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 0caf68e9 Sofia Papagiannaki
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 0caf68e9 Sofia Papagiannaki
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 0f4fa26d Sofia Papagiannaki
        m, created = self.membership_set.get_or_create(person=person)
158 0f4fa26d Sofia Papagiannaki
        # update date_joined in any case
159 0f4fa26d Sofia Papagiannaki
        m.date_joined=datetime.now()
160 0f4fa26d 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 ffb1e7a8 Sofia Papagiannaki
    def quota(self):
176 0f4fa26d Sofia Papagiannaki
        d = defaultdict(int)
177 0f4fa26d Sofia Papagiannaki
        for q in self.astakosgroupquota_set.all():
178 0f4fa26d Sofia Papagiannaki
            d[q.resource] += q.limit
179 ffb1e7a8 Sofia Papagiannaki
        return d
180 01ac12d5 Sofia Papagiannaki
    
181 01ac12d5 Sofia Papagiannaki
    @property
182 d68590fd Sofia Papagiannaki
    def has_undefined_policies(self):
183 d68590fd Sofia Papagiannaki
        # TODO: can avoid query?
184 d68590fd Sofia Papagiannaki
        return Resource.objects.filter(~Q(astakosgroup=self)).exists()
185 0f4fa26d Sofia Papagiannaki
    
186 0f4fa26d Sofia Papagiannaki
    @property
187 0f4fa26d Sofia Papagiannaki
    def owners(self):
188 0f4fa26d Sofia Papagiannaki
        return self.owner.all()
189 0f4fa26d Sofia Papagiannaki
    
190 0f4fa26d Sofia Papagiannaki
    @owners.setter
191 0f4fa26d Sofia Papagiannaki
    def owners(self, l):
192 0f4fa26d Sofia Papagiannaki
        self.owner = l
193 0f4fa26d Sofia Papagiannaki
        map(self.approve_member, l)
194 8e45d6fd Sofia Papagiannaki
195 0905ccd2 Sofia Papagiannaki
class AstakosUser(User):
196 890b0eaf Sofia Papagiannaki
    """
197 890b0eaf Sofia Papagiannaki
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
198 890b0eaf Sofia Papagiannaki
    """
199 0905ccd2 Sofia Papagiannaki
    # Use UserManager to get the create_user method, etc.
200 0905ccd2 Sofia Papagiannaki
    objects = UserManager()
201 6c736ed7 Kostas Papadimitriou
202 5ed6816e Sofia Papagiannaki
    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
203 5ed6816e Sofia Papagiannaki
    provider = models.CharField('Provider', max_length=255, blank=True)
204 6c736ed7 Kostas Papadimitriou
205 64cd4730 Antony Chazapis
    #for invitations
206 92defad4 Sofia Papagiannaki
    user_level = DEFAULT_USER_LEVEL
207 a196eb7e Sofia Papagiannaki
    level = models.IntegerField('Inviter level', default=user_level)
208 ebd369d0 Sofia Papagiannaki
    invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
209 6c736ed7 Kostas Papadimitriou
210 64cd4730 Antony Chazapis
    auth_token = models.CharField('Authentication Token', max_length=32,
211 0905ccd2 Sofia Papagiannaki
                                  null=True, blank=True)
212 0905ccd2 Sofia Papagiannaki
    auth_token_created = models.DateTimeField('Token creation date', null=True)
213 0905ccd2 Sofia Papagiannaki
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
214 6c736ed7 Kostas Papadimitriou
215 64cd4730 Antony Chazapis
    updated = models.DateTimeField('Update date')
216 890b0eaf Sofia Papagiannaki
    is_verified = models.BooleanField('Is verified?', default=False)
217 6c736ed7 Kostas Papadimitriou
218 15efc749 Sofia Papagiannaki
    # ex. screen_name for twitter, eppn for shibboleth
219 15efc749 Sofia Papagiannaki
    third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
220 6c736ed7 Kostas Papadimitriou
221 ebd369d0 Sofia Papagiannaki
    email_verified = models.BooleanField('Email verified?', default=False)
222 6c736ed7 Kostas Papadimitriou
223 59f598f1 Sofia Papagiannaki
    has_credits = models.BooleanField('Has credits?', default=False)
224 270dd48d Sofia Papagiannaki
    has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
225 0a569195 Sofia Papagiannaki
    date_signed_terms = models.DateTimeField('Signed terms date', null=True, blank=True)
226 59f598f1 Sofia Papagiannaki
    
227 751d24cf Sofia Papagiannaki
    activation_sent = models.DateTimeField('Activation sent data', null=True, blank=True)
228 751d24cf Sofia Papagiannaki
    
229 8e45d6fd Sofia Papagiannaki
    policy = models.ManyToManyField(Resource, null=True, through='AstakosUserQuota')
230 8e45d6fd Sofia Papagiannaki
    
231 8e45d6fd Sofia Papagiannaki
    astakos_groups = models.ManyToManyField(AstakosGroup, verbose_name=_('agroups'), blank=True,
232 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."),
233 8e45d6fd Sofia Papagiannaki
        through='Membership')
234 8e45d6fd Sofia Papagiannaki
    
235 18ffbee1 Sofia Papagiannaki
    __has_signed_terms = False
236 18ffbee1 Sofia Papagiannaki
    __groupnames = []
237 18ffbee1 Sofia Papagiannaki
    
238 8e45d6fd Sofia Papagiannaki
    owner = models.ManyToManyField(AstakosGroup, related_name='owner', null=True)
239 8e45d6fd Sofia Papagiannaki
    
240 74b273d8 Sofia Papagiannaki
    class Meta:
241 74b273d8 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
242 74b273d8 Sofia Papagiannaki
    
243 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
244 18ffbee1 Sofia Papagiannaki
        super(AstakosUser, self).__init__(*args, **kwargs)
245 18ffbee1 Sofia Papagiannaki
        self.__has_signed_terms = self.has_signed_terms
246 18ffbee1 Sofia Papagiannaki
        if self.id:
247 0caf68e9 Sofia Papagiannaki
            self.__groupnames = [g.name for g in self.astakos_groups.all()]
248 18ffbee1 Sofia Papagiannaki
        else:
249 18ffbee1 Sofia Papagiannaki
            self.is_active = False
250 18ffbee1 Sofia Papagiannaki
    
251 0905ccd2 Sofia Papagiannaki
    @property
252 0905ccd2 Sofia Papagiannaki
    def realname(self):
253 0905ccd2 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
254 6c736ed7 Kostas Papadimitriou
255 0905ccd2 Sofia Papagiannaki
    @realname.setter
256 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
257 0905ccd2 Sofia Papagiannaki
        parts = value.split(' ')
258 0905ccd2 Sofia Papagiannaki
        if len(parts) == 2:
259 0905ccd2 Sofia Papagiannaki
            self.first_name = parts[0]
260 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[1]
261 0905ccd2 Sofia Papagiannaki
        else:
262 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[0]
263 6c736ed7 Kostas Papadimitriou
264 64cd4730 Antony Chazapis
    @property
265 64cd4730 Antony Chazapis
    def invitation(self):
266 64cd4730 Antony Chazapis
        try:
267 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
268 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
269 64cd4730 Antony Chazapis
            return None
270 ffb1e7a8 Sofia Papagiannaki
    
271 ffb1e7a8 Sofia Papagiannaki
    @property
272 ffb1e7a8 Sofia Papagiannaki
    def quota(self):
273 ffb1e7a8 Sofia Papagiannaki
        d = defaultdict(int)
274 ffb1e7a8 Sofia Papagiannaki
        for q in  self.astakosuserquota_set.all():
275 ffb1e7a8 Sofia Papagiannaki
            d[q.resource.name] += q.limit
276 ffb1e7a8 Sofia Papagiannaki
        for g in self.astakos_groups.all():
277 ffb1e7a8 Sofia Papagiannaki
            if not g.is_enabled:
278 ffb1e7a8 Sofia Papagiannaki
                continue
279 ffb1e7a8 Sofia Papagiannaki
            for r, limit in g.quota.iteritems():
280 ffb1e7a8 Sofia Papagiannaki
                d[r] += limit
281 ffb1e7a8 Sofia Papagiannaki
        # TODO set default for remaining
282 ffb1e7a8 Sofia Papagiannaki
        return d
283 ffb1e7a8 Sofia Papagiannaki
        
284 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
285 64cd4730 Antony Chazapis
        if update_timestamps:
286 64cd4730 Antony Chazapis
            if not self.id:
287 0905ccd2 Sofia Papagiannaki
                self.date_joined = datetime.now()
288 64cd4730 Antony Chazapis
            self.updated = datetime.now()
289 18ffbee1 Sofia Papagiannaki
        
290 18ffbee1 Sofia Papagiannaki
        # update date_signed_terms if necessary
291 18ffbee1 Sofia Papagiannaki
        if self.__has_signed_terms != self.has_signed_terms:
292 18ffbee1 Sofia Papagiannaki
            self.date_signed_terms = datetime.now()
293 18ffbee1 Sofia Papagiannaki
        
294 9c01d9d1 Sofia Papagiannaki
        if not self.id:
295 9c01d9d1 Sofia Papagiannaki
            # set username
296 9c01d9d1 Sofia Papagiannaki
            while not self.username:
297 9c01d9d1 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
298 9c01d9d1 Sofia Papagiannaki
                try:
299 9c01d9d1 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
300 9c01d9d1 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
301 9c01d9d1 Sofia Papagiannaki
                    self.username = username
302 9c01d9d1 Sofia Papagiannaki
            if not self.provider:
303 9c01d9d1 Sofia Papagiannaki
                self.provider = 'local'
304 9c01d9d1 Sofia Papagiannaki
        report_user_event(self)
305 591d0505 Sofia Papagiannaki
        self.validate_unique_email_isactive()
306 751d24cf Sofia Papagiannaki
        if self.is_active and self.activation_sent:
307 751d24cf Sofia Papagiannaki
            # reset the activation sent
308 751d24cf Sofia Papagiannaki
            self.activation_sent = None
309 8e45d6fd Sofia Papagiannaki
        
310 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
311 18ffbee1 Sofia Papagiannaki
        
312 18ffbee1 Sofia Papagiannaki
        # set group if does not exist
313 18ffbee1 Sofia Papagiannaki
        groupname = 'shibboleth' if self.provider == 'shibboleth' else 'default'
314 18ffbee1 Sofia Papagiannaki
        if groupname not in self.__groupnames:
315 18ffbee1 Sofia Papagiannaki
            try:
316 0caf68e9 Sofia Papagiannaki
                group = AstakosGroup.objects.get(name = groupname)
317 0caf68e9 Sofia Papagiannaki
                Membership(group=group, person=self, date_joined=datetime.now()).save()
318 0caf68e9 Sofia Papagiannaki
            except AstakosGroup.DoesNotExist, e:
319 18ffbee1 Sofia Papagiannaki
                logger.exception(e)
320 64cd4730 Antony Chazapis
    
321 64cd4730 Antony Chazapis
    def renew_token(self):
322 64cd4730 Antony Chazapis
        md5 = hashlib.md5()
323 0905ccd2 Sofia Papagiannaki
        md5.update(self.username)
324 64cd4730 Antony Chazapis
        md5.update(self.realname.encode('ascii', 'ignore'))
325 64cd4730 Antony Chazapis
        md5.update(asctime())
326 6c736ed7 Kostas Papadimitriou
327 64cd4730 Antony Chazapis
        self.auth_token = b64encode(md5.digest())
328 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
329 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
330 92defad4 Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
331 111f3da6 Sofia Papagiannaki
        msg = 'Token renewed for %s' % self.email
332 111f3da6 Sofia Papagiannaki
        logger._log(LOGGING_LEVEL, msg, [])
333 6c736ed7 Kostas Papadimitriou
334 64cd4730 Antony Chazapis
    def __unicode__(self):
335 3abf6c78 Sofia Papagiannaki
        return '%s (%s)' % (self.realname, self.email)
336 0a569195 Sofia Papagiannaki
    
337 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
338 0a569195 Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username = self.username)
339 0a569195 Sofia Papagiannaki
        q = q.filter(email = self.email)
340 0a569195 Sofia Papagiannaki
        if q.count() != 0:
341 0a569195 Sofia Papagiannaki
            return True
342 0a569195 Sofia Papagiannaki
        return False
343 0a569195 Sofia Papagiannaki
    
344 591d0505 Sofia Papagiannaki
    def validate_unique_email_isactive(self):
345 0a569195 Sofia Papagiannaki
        """
346 0a569195 Sofia Papagiannaki
        Implements a unique_together constraint for email and is_active fields.
347 0a569195 Sofia Papagiannaki
        """
348 0a569195 Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username = self.username)
349 0a569195 Sofia Papagiannaki
        q = q.filter(email = self.email)
350 0a569195 Sofia Papagiannaki
        q = q.filter(is_active = self.is_active)
351 0a569195 Sofia Papagiannaki
        if q.count() != 0:
352 0a569195 Sofia Papagiannaki
            raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
353 09e7393c Sofia Papagiannaki
    
354 09e7393c Sofia Papagiannaki
    def signed_terms(self):
355 09e7393c Sofia Papagiannaki
        term = get_latest_terms()
356 09e7393c Sofia Papagiannaki
        if not term:
357 09e7393c Sofia Papagiannaki
            return True
358 09e7393c Sofia Papagiannaki
        if not self.has_signed_terms:
359 09e7393c Sofia Papagiannaki
            return False
360 09e7393c Sofia Papagiannaki
        if not self.date_signed_terms:
361 09e7393c Sofia Papagiannaki
            return False
362 09e7393c Sofia Papagiannaki
        if self.date_signed_terms < term.date:
363 09e7393c Sofia Papagiannaki
            self.has_signed_terms = False
364 f0f92965 Sofia Papagiannaki
            self.date_signed_terms = None
365 09e7393c Sofia Papagiannaki
            self.save()
366 09e7393c Sofia Papagiannaki
            return False
367 09e7393c Sofia Papagiannaki
        return True
368 8e45d6fd Sofia Papagiannaki
369 8e45d6fd Sofia Papagiannaki
class Membership(models.Model):
370 8e45d6fd Sofia Papagiannaki
    person = models.ForeignKey(AstakosUser)
371 8e45d6fd Sofia Papagiannaki
    group = models.ForeignKey(AstakosGroup)
372 01ac12d5 Sofia Papagiannaki
    date_requested = models.DateField(default=datetime.now(), blank=True)
373 01ac12d5 Sofia Papagiannaki
    date_joined = models.DateField(null=True, db_index=True, blank=True)
374 8e45d6fd Sofia Papagiannaki
    
375 8e45d6fd Sofia Papagiannaki
    class Meta:
376 8e45d6fd Sofia Papagiannaki
        unique_together = ("person", "group")
377 8e45d6fd Sofia Papagiannaki
    
378 ffb1e7a8 Sofia Papagiannaki
    def save(self, *args, **kwargs):
379 28252c7f Sofia Papagiannaki
        if not self.id:
380 28252c7f Sofia Papagiannaki
            if not self.group.moderation_enabled:
381 28252c7f Sofia Papagiannaki
                self.date_joined = datetime.now()
382 ffb1e7a8 Sofia Papagiannaki
        super(Membership, self).save(*args, **kwargs)
383 28252c7f Sofia Papagiannaki
    
384 8e45d6fd Sofia Papagiannaki
    @property
385 8e45d6fd Sofia Papagiannaki
    def is_approved(self):
386 8e45d6fd Sofia Papagiannaki
        if self.date_joined:
387 8e45d6fd Sofia Papagiannaki
            return True
388 8e45d6fd Sofia Papagiannaki
        return False
389 01ac12d5 Sofia Papagiannaki
    
390 01ac12d5 Sofia Papagiannaki
    def approve(self):
391 01ac12d5 Sofia Papagiannaki
        self.date_joined = datetime.now()
392 01ac12d5 Sofia Papagiannaki
        self.save()
393 01ac12d5 Sofia Papagiannaki
        
394 01ac12d5 Sofia Papagiannaki
    def disapprove(self):
395 01ac12d5 Sofia Papagiannaki
        self.delete()
396 8e45d6fd Sofia Papagiannaki
397 8e45d6fd Sofia Papagiannaki
class AstakosGroupQuota(models.Model):
398 8e45d6fd Sofia Papagiannaki
    limit = models.PositiveIntegerField('Limit')
399 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
400 8e45d6fd Sofia Papagiannaki
    group = models.ForeignKey(AstakosGroup, blank=True)
401 8e45d6fd Sofia Papagiannaki
    
402 8e45d6fd Sofia Papagiannaki
    class Meta:
403 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "group")
404 8e45d6fd Sofia Papagiannaki
405 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
406 8e45d6fd Sofia Papagiannaki
    limit = models.PositiveIntegerField('Limit')
407 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
408 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
409 8e45d6fd Sofia Papagiannaki
    
410 8e45d6fd Sofia Papagiannaki
    class Meta:
411 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
412 09e7393c Sofia Papagiannaki
413 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
414 270dd48d Sofia Papagiannaki
    """
415 270dd48d Sofia Papagiannaki
    Model for approval terms
416 270dd48d Sofia Papagiannaki
    """
417 6c736ed7 Kostas Papadimitriou
418 270dd48d Sofia Papagiannaki
    date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
419 270dd48d Sofia Papagiannaki
    location = models.CharField('Terms location', max_length=255)
420 270dd48d Sofia Papagiannaki
421 64cd4730 Antony Chazapis
class Invitation(models.Model):
422 890b0eaf Sofia Papagiannaki
    """
423 890b0eaf Sofia Papagiannaki
    Model for registring invitations
424 890b0eaf Sofia Papagiannaki
    """
425 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
426 64cd4730 Antony Chazapis
                                null=True)
427 64cd4730 Antony Chazapis
    realname = models.CharField('Real name', max_length=255)
428 ebd369d0 Sofia Papagiannaki
    username = models.CharField('Unique ID', max_length=255, unique=True)
429 64cd4730 Antony Chazapis
    code = models.BigIntegerField('Invitation code', db_index=True)
430 64cd4730 Antony Chazapis
    is_consumed = models.BooleanField('Consumed?', default=False)
431 64cd4730 Antony Chazapis
    created = models.DateTimeField('Creation date', auto_now_add=True)
432 64cd4730 Antony Chazapis
    consumed = models.DateTimeField('Consumption date', null=True, blank=True)
433 64cd4730 Antony Chazapis
    
434 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
435 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
436 8f5a3a06 Sofia Papagiannaki
        if not self.id:
437 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
438 8f5a3a06 Sofia Papagiannaki
    
439 64cd4730 Antony Chazapis
    def consume(self):
440 64cd4730 Antony Chazapis
        self.is_consumed = True
441 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
442 64cd4730 Antony Chazapis
        self.save()
443 6c736ed7 Kostas Papadimitriou
444 64cd4730 Antony Chazapis
    def __unicode__(self):
445 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
446 9c01d9d1 Sofia Papagiannaki
447 9c01d9d1 Sofia Papagiannaki
def report_user_event(user):
448 9c01d9d1 Sofia Papagiannaki
    def should_send(user):
449 9c01d9d1 Sofia Papagiannaki
        # report event incase of new user instance
450 9c01d9d1 Sofia Papagiannaki
        # or if specific fields are modified
451 9c01d9d1 Sofia Papagiannaki
        if not user.id:
452 9c01d9d1 Sofia Papagiannaki
            return True
453 8e45d6fd Sofia Papagiannaki
        try:
454 8e45d6fd Sofia Papagiannaki
            db_instance = AstakosUser.objects.get(id = user.id)
455 8e45d6fd Sofia Papagiannaki
        except AstakosUser.DoesNotExist:
456 8e45d6fd Sofia Papagiannaki
            return True
457 9c01d9d1 Sofia Papagiannaki
        for f in BILLING_FIELDS:
458 9c01d9d1 Sofia Papagiannaki
            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
459 9c01d9d1 Sofia Papagiannaki
                return True
460 9c01d9d1 Sofia Papagiannaki
        return False
461 6c736ed7 Kostas Papadimitriou
462 3a9f4931 Sofia Papagiannaki
    if QUEUE_CONNECTION and should_send(user):
463 6c736ed7 Kostas Papadimitriou
464 6c736ed7 Kostas Papadimitriou
        from astakos.im.queue.userevent import UserEvent
465 6c736ed7 Kostas Papadimitriou
        from synnefo.lib.queue import exchange_connect, exchange_send, \
466 6c736ed7 Kostas Papadimitriou
                exchange_close
467 6c736ed7 Kostas Papadimitriou
468 59f598f1 Sofia Papagiannaki
        eventType = 'create' if not user.id else 'modify'
469 59f598f1 Sofia Papagiannaki
        body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
470 3a9f4931 Sofia Papagiannaki
        conn = exchange_connect(QUEUE_CONNECTION)
471 9e19989d Sofia Papagiannaki
        parts = urlparse(QUEUE_CONNECTION)
472 270dd48d Sofia Papagiannaki
        exchange = parts.path[1:]
473 270dd48d Sofia Papagiannaki
        routing_key = '%s.user' % exchange
474 9c01d9d1 Sofia Papagiannaki
        exchange_send(conn, routing_key, body)
475 68cb6899 Sofia Papagiannaki
        exchange_close(conn)
476 8f5a3a06 Sofia Papagiannaki
477 8f5a3a06 Sofia Papagiannaki
def _generate_invitation_code():
478 8f5a3a06 Sofia Papagiannaki
    while True:
479 8f5a3a06 Sofia Papagiannaki
        code = randint(1, 2L**63 - 1)
480 8f5a3a06 Sofia Papagiannaki
        try:
481 8f5a3a06 Sofia Papagiannaki
            Invitation.objects.get(code=code)
482 8f5a3a06 Sofia Papagiannaki
            # An invitation with this code already exists, try again
483 8f5a3a06 Sofia Papagiannaki
        except Invitation.DoesNotExist:
484 09e7393c Sofia Papagiannaki
            return code
485 09e7393c Sofia Papagiannaki
486 09e7393c Sofia Papagiannaki
def get_latest_terms():
487 09e7393c Sofia Papagiannaki
    try:
488 09e7393c Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
489 09e7393c Sofia Papagiannaki
        return term
490 09e7393c Sofia Papagiannaki
    except IndexError:
491 09e7393c Sofia Papagiannaki
        pass
492 49790d9d Sofia Papagiannaki
    return None
493 49790d9d Sofia Papagiannaki
494 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
495 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
496 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
497 49790d9d Sofia Papagiannaki
        """
498 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
499 49790d9d Sofia Papagiannaki
        ``User`` if valid.
500 49790d9d Sofia Papagiannaki

501 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
502 49790d9d Sofia Papagiannaki
        after activating.
503 49790d9d Sofia Papagiannaki

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

506 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
507 49790d9d Sofia Papagiannaki
        return ``None``.
508 49790d9d Sofia Papagiannaki

509 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
510 49790d9d Sofia Papagiannaki

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