Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ b22de10a

History | View | Annotate | Download (54.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 64cd4730 Antony Chazapis
38 64cd4730 Antony Chazapis
from time import asctime
39 64cd4730 Antony Chazapis
from datetime import datetime, timedelta
40 64cd4730 Antony Chazapis
from base64 import b64encode
41 ef20ea07 Sofia Papagiannaki
from urlparse import urlparse
42 d2633501 Kostas Papadimitriou
from urllib import quote
43 8f5a3a06 Sofia Papagiannaki
from random import randint
44 ffb1e7a8 Sofia Papagiannaki
from collections import defaultdict
45 64cd4730 Antony Chazapis
46 49790d9d Sofia Papagiannaki
from django.db import models, IntegrityError
47 9a06d96f Olga Brani
from django.contrib.auth.models import User, UserManager, Group, Permission
48 0a569195 Sofia Papagiannaki
from django.utils.translation import ugettext as _
49 49790d9d Sofia Papagiannaki
from django.db import transaction
50 0a569195 Sofia Papagiannaki
from django.core.exceptions import ValidationError
51 c0b26605 Sofia Papagiannaki
from django.db.models.signals import (
52 c0b26605 Sofia Papagiannaki
    pre_save, post_save, post_syncdb, post_delete
53 c0b26605 Sofia Papagiannaki
)
54 9a06d96f Olga Brani
from django.contrib.contenttypes.models import ContentType
55 9a06d96f Olga Brani
56 fc1e2f02 Sofia Papagiannaki
from django.dispatch import Signal
57 e6759494 Sofia Papagiannaki
from django.db.models import Q
58 d2633501 Kostas Papadimitriou
from django.core.urlresolvers import reverse
59 d2633501 Kostas Papadimitriou
from django.utils.http import int_to_base36
60 d2633501 Kostas Papadimitriou
from django.contrib.auth.tokens import default_token_generator
61 8f8c43b2 Sofia Papagiannaki
from django.conf import settings
62 bf0c6de5 Sofia Papagiannaki
from django.utils.importlib import import_module
63 d2633501 Kostas Papadimitriou
from django.core.validators import email_re
64 64cd4730 Antony Chazapis
65 e1a80257 Sofia Papagiannaki
from astakos.im.settings import (
66 e1a80257 Sofia Papagiannaki
    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
67 e1a80257 Sofia Papagiannaki
    AUTH_TOKEN_DURATION, BILLING_FIELDS,
68 71a38edf Sofia Papagiannaki
    EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
69 71a38edf Sofia Papagiannaki
    GROUP_CREATION_SUBJECT
70 e1a80257 Sofia Papagiannaki
)
71 c0b26605 Sofia Papagiannaki
from astakos.im.endpoints.qh import (
72 c0b26605 Sofia Papagiannaki
    register_users, send_quota, register_resources
73 bf0c6de5 Sofia Papagiannaki
)
74 d2633501 Kostas Papadimitriou
from astakos.im import auth_providers
75 fc1e2f02 Sofia Papagiannaki
from astakos.im.endpoints.aquarium.producer import report_user_event
76 9a06d96f Olga Brani
from astakos.im.functions import send_invitation
77 f8f86e83 root
#from astakos.im.tasks import propagate_groupmembers_quota
78 9c01d9d1 Sofia Papagiannaki
79 e1a80257 Sofia Papagiannaki
from astakos.im.notifications import build_notification
80 e1a80257 Sofia Papagiannaki
81 ae497612 Olga Brani
import astakos.im.messages as astakos_messages
82 64cd4730 Antony Chazapis
83 18ffbee1 Sofia Papagiannaki
logger = logging.getLogger(__name__)
84 18ffbee1 Sofia Papagiannaki
85 9a06d96f Olga Brani
DEFAULT_CONTENT_TYPE = None
86 e65c21df Georgios D. Tsoukalas
_content_type = None
87 e65c21df Georgios D. Tsoukalas
88 e65c21df Georgios D. Tsoukalas
def get_content_type():
89 e65c21df Georgios D. Tsoukalas
    global _content_type
90 e65c21df Georgios D. Tsoukalas
    if _content_type is not None:
91 e65c21df Georgios D. Tsoukalas
        return _content_type
92 e65c21df Georgios D. Tsoukalas
93 e65c21df Georgios D. Tsoukalas
    try:
94 e65c21df Georgios D. Tsoukalas
        content_type = ContentType.objects.get(app_label='im', model='astakosuser')
95 e65c21df Georgios D. Tsoukalas
    except:
96 e65c21df Georgios D. Tsoukalas
        content_type = DEFAULT_CONTENT_TYPE
97 e65c21df Georgios D. Tsoukalas
    _content_type = content_type
98 e65c21df Georgios D. Tsoukalas
    return content_type
99 9a06d96f Olga Brani
100 9a06d96f Olga Brani
RESOURCE_SEPARATOR = '.'
101 9a06d96f Olga Brani
102 9ee0c6a2 Sofia Papagiannaki
inf = float('inf')
103 5ce3ce4f Sofia Papagiannaki
104 8e45d6fd Sofia Papagiannaki
class Service(models.Model):
105 e1a80257 Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
106 8e45d6fd Sofia Papagiannaki
    url = models.FilePathField()
107 8e45d6fd Sofia Papagiannaki
    icon = models.FilePathField(blank=True)
108 e1a80257 Sofia Papagiannaki
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
109 8e45d6fd Sofia Papagiannaki
                                  null=True, blank=True)
110 e1a80257 Sofia Papagiannaki
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
111 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
112 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
113 5ce3ce4f Sofia Papagiannaki
114 8e45d6fd Sofia Papagiannaki
    def renew_token(self):
115 8e45d6fd Sofia Papagiannaki
        md5 = hashlib.md5()
116 8e45d6fd Sofia Papagiannaki
        md5.update(self.name.encode('ascii', 'ignore'))
117 8e45d6fd Sofia Papagiannaki
        md5.update(self.url.encode('ascii', 'ignore'))
118 8e45d6fd Sofia Papagiannaki
        md5.update(asctime())
119 8e45d6fd Sofia Papagiannaki
120 8e45d6fd Sofia Papagiannaki
        self.auth_token = b64encode(md5.digest())
121 8e45d6fd Sofia Papagiannaki
        self.auth_token_created = datetime.now()
122 8e45d6fd Sofia Papagiannaki
        self.auth_token_expires = self.auth_token_created + \
123 5ce3ce4f Sofia Papagiannaki
            timedelta(hours=AUTH_TOKEN_DURATION)
124 5ce3ce4f Sofia Papagiannaki
125 8e45d6fd Sofia Papagiannaki
    def __str__(self):
126 8e45d6fd Sofia Papagiannaki
        return self.name
127 8e45d6fd Sofia Papagiannaki
128 9a06d96f Olga Brani
    @property
129 9a06d96f Olga Brani
    def resources(self):
130 9a06d96f Olga Brani
        return self.resource_set.all()
131 9a06d96f Olga Brani
132 9a06d96f Olga Brani
    @resources.setter
133 9a06d96f Olga Brani
    def resources(self, resources):
134 9a06d96f Olga Brani
        for s in resources:
135 9a06d96f Olga Brani
            self.resource_set.create(**s)
136 2e90e3ec Kostas Papadimitriou
137 9a06d96f Olga Brani
    def add_resource(self, service, resource, uplimit, update=True):
138 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
139 9a06d96f Olga Brani
        resource = Resource.objects.get(service__name=service, name=resource)
140 9a06d96f Olga Brani
        if update:
141 9a06d96f Olga Brani
            AstakosUserQuota.objects.update_or_create(user=self,
142 9a06d96f Olga Brani
                                                      resource=resource,
143 9a06d96f Olga Brani
                                                      defaults={'uplimit': uplimit})
144 9a06d96f Olga Brani
        else:
145 9a06d96f Olga Brani
            q = self.astakosuserquota_set
146 9a06d96f Olga Brani
            q.create(resource=resource, uplimit=uplimit)
147 9a06d96f Olga Brani
148 5ce3ce4f Sofia Papagiannaki
149 8e45d6fd Sofia Papagiannaki
class ResourceMetadata(models.Model):
150 e1a80257 Sofia Papagiannaki
    key = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
151 e1a80257 Sofia Papagiannaki
    value = models.CharField(_('Value'), max_length=255)
152 8e45d6fd Sofia Papagiannaki
153 5ce3ce4f Sofia Papagiannaki
154 8e45d6fd Sofia Papagiannaki
class Resource(models.Model):
155 e1a80257 Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
156 8e45d6fd Sofia Papagiannaki
    meta = models.ManyToManyField(ResourceMetadata)
157 8e45d6fd Sofia Papagiannaki
    service = models.ForeignKey(Service)
158 e1a80257 Sofia Papagiannaki
    desc = models.TextField(_('Description'), null=True)
159 e1a80257 Sofia Papagiannaki
    unit = models.CharField(_('Name'), null=True, max_length=255)
160 e1a80257 Sofia Papagiannaki
    group = models.CharField(_('Group'), null=True, max_length=255)
161 5ce3ce4f Sofia Papagiannaki
162 8e45d6fd Sofia Papagiannaki
    def __str__(self):
163 9a06d96f Olga Brani
        return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
164 8e45d6fd Sofia Papagiannaki
165 5ce3ce4f Sofia Papagiannaki
166 8e45d6fd Sofia Papagiannaki
class GroupKind(models.Model):
167 e1a80257 Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
168 5ce3ce4f Sofia Papagiannaki
169 8e45d6fd Sofia Papagiannaki
    def __str__(self):
170 8e45d6fd Sofia Papagiannaki
        return self.name
171 8e45d6fd Sofia Papagiannaki
172 5ce3ce4f Sofia Papagiannaki
173 8e45d6fd Sofia Papagiannaki
class AstakosGroup(Group):
174 8e45d6fd Sofia Papagiannaki
    kind = models.ForeignKey(GroupKind)
175 2b1a5f5d Olga Brani
    homepage = models.URLField(
176 e1a80257 Sofia Papagiannaki
        _('Homepage Url'), max_length=255, null=True, blank=True)
177 e1a80257 Sofia Papagiannaki
    desc = models.TextField(_('Description'), null=True)
178 9a06d96f Olga Brani
    policy = models.ManyToManyField(
179 9a06d96f Olga Brani
        Resource,
180 9a06d96f Olga Brani
        null=True,
181 9a06d96f Olga Brani
        blank=True,
182 9a06d96f Olga Brani
        through='AstakosGroupQuota'
183 9a06d96f Olga Brani
    )
184 9a06d96f Olga Brani
    creation_date = models.DateTimeField(
185 e1a80257 Sofia Papagiannaki
        _('Creation date'),
186 9a06d96f Olga Brani
        default=datetime.now()
187 9a06d96f Olga Brani
    )
188 67be1883 Olga Brani
    issue_date = models.DateTimeField('Start date', null=True)
189 9a06d96f Olga Brani
    expiration_date = models.DateTimeField(
190 e1a80257 Sofia Papagiannaki
        _('Expiration date'),
191 e1a80257 Sofia Papagiannaki
        null=True
192 9a06d96f Olga Brani
    )
193 9a06d96f Olga Brani
    moderation_enabled = models.BooleanField(
194 e1a80257 Sofia Papagiannaki
        _('Moderated membership?'),
195 9a06d96f Olga Brani
        default=True
196 9a06d96f Olga Brani
    )
197 9a06d96f Olga Brani
    approval_date = models.DateTimeField(
198 e1a80257 Sofia Papagiannaki
        _('Activation date'),
199 9a06d96f Olga Brani
        null=True,
200 9a06d96f Olga Brani
        blank=True
201 9a06d96f Olga Brani
    )
202 9a06d96f Olga Brani
    estimated_participants = models.PositiveIntegerField(
203 e1a80257 Sofia Papagiannaki
        _('Estimated #members'),
204 9a06d96f Olga Brani
        null=True,
205 9a06d96f Olga Brani
        blank=True,
206 9a06d96f Olga Brani
    )
207 9a06d96f Olga Brani
    max_participants = models.PositiveIntegerField(
208 e1a80257 Sofia Papagiannaki
        _('Maximum numder of participants'),
209 9a06d96f Olga Brani
        null=True,
210 9a06d96f Olga Brani
        blank=True
211 9a06d96f Olga Brani
    )
212 2e90e3ec Kostas Papadimitriou
213 8e45d6fd Sofia Papagiannaki
    @property
214 8e45d6fd Sofia Papagiannaki
    def is_disabled(self):
215 ffb1e7a8 Sofia Papagiannaki
        if not self.approval_date:
216 ffb1e7a8 Sofia Papagiannaki
            return True
217 ffb1e7a8 Sofia Papagiannaki
        return False
218 5ce3ce4f Sofia Papagiannaki
219 8e45d6fd Sofia Papagiannaki
    @property
220 ffb1e7a8 Sofia Papagiannaki
    def is_enabled(self):
221 8e45d6fd Sofia Papagiannaki
        if self.is_disabled:
222 8e45d6fd Sofia Papagiannaki
            return False
223 8e45d6fd Sofia Papagiannaki
        if not self.issue_date:
224 8e45d6fd Sofia Papagiannaki
            return False
225 8e45d6fd Sofia Papagiannaki
        if not self.expiration_date:
226 8e45d6fd Sofia Papagiannaki
            return True
227 8e45d6fd Sofia Papagiannaki
        now = datetime.now()
228 8e45d6fd Sofia Papagiannaki
        if self.issue_date > now:
229 8e45d6fd Sofia Papagiannaki
            return False
230 8e45d6fd Sofia Papagiannaki
        if now >= self.expiration_date:
231 8e45d6fd Sofia Papagiannaki
            return False
232 8e45d6fd Sofia Papagiannaki
        return True
233 5ce3ce4f Sofia Papagiannaki
234 ffb1e7a8 Sofia Papagiannaki
    def enable(self):
235 fc1e2f02 Sofia Papagiannaki
        if self.is_enabled:
236 fc1e2f02 Sofia Papagiannaki
            return
237 8e45d6fd Sofia Papagiannaki
        self.approval_date = datetime.now()
238 8e45d6fd Sofia Papagiannaki
        self.save()
239 20d50182 Sofia Papagiannaki
        quota_disturbed.send(sender=self, users=self.approved_members)
240 f8f86e83 root
        #propagate_groupmembers_quota.apply_async(
241 f8f86e83 root
        #    args=[self], eta=self.issue_date)
242 f8f86e83 root
        #propagate_groupmembers_quota.apply_async(
243 f8f86e83 root
        #    args=[self], eta=self.expiration_date)
244 5ce3ce4f Sofia Papagiannaki
245 ffb1e7a8 Sofia Papagiannaki
    def disable(self):
246 fc1e2f02 Sofia Papagiannaki
        if self.is_disabled:
247 fc1e2f02 Sofia Papagiannaki
            return
248 8e45d6fd Sofia Papagiannaki
        self.approval_date = None
249 8e45d6fd Sofia Papagiannaki
        self.save()
250 fc1e2f02 Sofia Papagiannaki
        quota_disturbed.send(sender=self, users=self.approved_members)
251 5ce3ce4f Sofia Papagiannaki
252 01ac12d5 Sofia Papagiannaki
    def approve_member(self, person):
253 0f4fa26d Sofia Papagiannaki
        m, created = self.membership_set.get_or_create(person=person)
254 e1a80257 Sofia Papagiannaki
        m.approve()
255 5ce3ce4f Sofia Papagiannaki
256 01ac12d5 Sofia Papagiannaki
    @property
257 01ac12d5 Sofia Papagiannaki
    def members(self):
258 032ade79 Sofia Papagiannaki
        q = self.membership_set.select_related().all()
259 032ade79 Sofia Papagiannaki
        return [m.person for m in q]
260 2e90e3ec Kostas Papadimitriou
261 01ac12d5 Sofia Papagiannaki
    @property
262 01ac12d5 Sofia Papagiannaki
    def approved_members(self):
263 032ade79 Sofia Papagiannaki
        q = self.membership_set.select_related().all()
264 032ade79 Sofia Papagiannaki
        return [m.person for m in q if m.is_approved]
265 5ce3ce4f Sofia Papagiannaki
266 01ac12d5 Sofia Papagiannaki
    @property
267 ffb1e7a8 Sofia Papagiannaki
    def quota(self):
268 0f4fa26d Sofia Papagiannaki
        d = defaultdict(int)
269 dfdc64d2 Sofia Papagiannaki
        for q in self.astakosgroupquota_set.select_related().all():
270 9ee0c6a2 Sofia Papagiannaki
            d[q.resource] += q.uplimit or inf
271 ffb1e7a8 Sofia Papagiannaki
        return d
272 2e90e3ec Kostas Papadimitriou
273 9a06d96f Olga Brani
    def add_policy(self, service, resource, uplimit, update=True):
274 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
275 9a06d96f Olga Brani
        resource = Resource.objects.get(service__name=service, name=resource)
276 9a06d96f Olga Brani
        if update:
277 9a06d96f Olga Brani
            AstakosGroupQuota.objects.update_or_create(
278 9a06d96f Olga Brani
                group=self,
279 9a06d96f Olga Brani
                resource=resource,
280 9a06d96f Olga Brani
                defaults={'uplimit': uplimit}
281 9a06d96f Olga Brani
            )
282 9a06d96f Olga Brani
        else:
283 9a06d96f Olga Brani
            q = self.astakosgroupquota_set
284 9a06d96f Olga Brani
            q.create(resource=resource, uplimit=uplimit)
285 2e90e3ec Kostas Papadimitriou
286 9a06d96f Olga Brani
    @property
287 9a06d96f Olga Brani
    def policies(self):
288 9a06d96f Olga Brani
        return self.astakosgroupquota_set.select_related().all()
289 9a06d96f Olga Brani
290 9a06d96f Olga Brani
    @policies.setter
291 9a06d96f Olga Brani
    def policies(self, policies):
292 9a06d96f Olga Brani
        for p in policies:
293 9a06d96f Olga Brani
            service = p.get('service', None)
294 9a06d96f Olga Brani
            resource = p.get('resource', None)
295 9a06d96f Olga Brani
            uplimit = p.get('uplimit', 0)
296 9a06d96f Olga Brani
            update = p.get('update', True)
297 9a06d96f Olga Brani
            self.add_policy(service, resource, uplimit, update)
298 2e90e3ec Kostas Papadimitriou
299 01ac12d5 Sofia Papagiannaki
    @property
300 0f4fa26d Sofia Papagiannaki
    def owners(self):
301 0f4fa26d Sofia Papagiannaki
        return self.owner.all()
302 5ce3ce4f Sofia Papagiannaki
303 032ade79 Sofia Papagiannaki
    @property
304 032ade79 Sofia Papagiannaki
    def owner_details(self):
305 032ade79 Sofia Papagiannaki
        return self.owner.select_related().all()
306 032ade79 Sofia Papagiannaki
307 0f4fa26d Sofia Papagiannaki
    @owners.setter
308 0f4fa26d Sofia Papagiannaki
    def owners(self, l):
309 0f4fa26d Sofia Papagiannaki
        self.owner = l
310 0f4fa26d Sofia Papagiannaki
        map(self.approve_member, l)
311 8e45d6fd Sofia Papagiannaki
312 5ce3ce4f Sofia Papagiannaki
313 d2633501 Kostas Papadimitriou
314 6b9a334b Sofia Papagiannaki
class AstakosUserManager(UserManager):
315 d2633501 Kostas Papadimitriou
316 d2633501 Kostas Papadimitriou
    def get_auth_provider_user(self, provider, **kwargs):
317 d2633501 Kostas Papadimitriou
        """
318 d2633501 Kostas Papadimitriou
        Retrieve AstakosUser instance associated with the specified third party
319 d2633501 Kostas Papadimitriou
        id.
320 d2633501 Kostas Papadimitriou
        """
321 d2633501 Kostas Papadimitriou
        kwargs = dict(map(lambda x: ('auth_providers__%s' % x[0], x[1]),
322 d2633501 Kostas Papadimitriou
                          kwargs.iteritems()))
323 d2633501 Kostas Papadimitriou
        return self.get(auth_providers__module=provider, **kwargs)
324 d2633501 Kostas Papadimitriou
325 0905ccd2 Sofia Papagiannaki
class AstakosUser(User):
326 890b0eaf Sofia Papagiannaki
    """
327 890b0eaf Sofia Papagiannaki
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
328 890b0eaf Sofia Papagiannaki
    """
329 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
330 d2633501 Kostas Papadimitriou
                                   null=True)
331 d2633501 Kostas Papadimitriou
332 d2633501 Kostas Papadimitriou
    # DEPRECATED FIELDS: provider, third_party_identifier moved in
333 d2633501 Kostas Papadimitriou
    #                    AstakosUserProvider model.
334 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True,
335 d2633501 Kostas Papadimitriou
                                null=True)
336 d2633501 Kostas Papadimitriou
    # ex. screen_name for twitter, eppn for shibboleth
337 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'),
338 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
339 d2633501 Kostas Papadimitriou
                                              blank=True)
340 d2633501 Kostas Papadimitriou
341 6c736ed7 Kostas Papadimitriou
342 64cd4730 Antony Chazapis
    #for invitations
343 92defad4 Sofia Papagiannaki
    user_level = DEFAULT_USER_LEVEL
344 e1a80257 Sofia Papagiannaki
    level = models.IntegerField(_('Inviter level'), default=user_level)
345 5ce3ce4f Sofia Papagiannaki
    invitations = models.IntegerField(
346 e1a80257 Sofia Papagiannaki
        _('Invitations left'), default=INVITATIONS_PER_LEVEL.get(user_level, 0))
347 6c736ed7 Kostas Papadimitriou
348 e1a80257 Sofia Papagiannaki
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
349 0905ccd2 Sofia Papagiannaki
                                  null=True, blank=True)
350 e1a80257 Sofia Papagiannaki
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
351 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
352 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
353 6c736ed7 Kostas Papadimitriou
354 e1a80257 Sofia Papagiannaki
    updated = models.DateTimeField(_('Update date'))
355 e1a80257 Sofia Papagiannaki
    is_verified = models.BooleanField(_('Is verified?'), default=False)
356 6c736ed7 Kostas Papadimitriou
357 e1a80257 Sofia Papagiannaki
    email_verified = models.BooleanField(_('Email verified?'), default=False)
358 6c736ed7 Kostas Papadimitriou
359 e1a80257 Sofia Papagiannaki
    has_credits = models.BooleanField(_('Has credits?'), default=False)
360 5ce3ce4f Sofia Papagiannaki
    has_signed_terms = models.BooleanField(
361 e1a80257 Sofia Papagiannaki
        _('I agree with the terms'), default=False)
362 5ce3ce4f Sofia Papagiannaki
    date_signed_terms = models.DateTimeField(
363 e1a80257 Sofia Papagiannaki
        _('Signed terms date'), null=True, blank=True)
364 5ce3ce4f Sofia Papagiannaki
365 5ce3ce4f Sofia Papagiannaki
    activation_sent = models.DateTimeField(
366 e1a80257 Sofia Papagiannaki
        _('Activation sent data'), null=True, blank=True)
367 5ce3ce4f Sofia Papagiannaki
368 5ce3ce4f Sofia Papagiannaki
    policy = models.ManyToManyField(
369 5ce3ce4f Sofia Papagiannaki
        Resource, null=True, through='AstakosUserQuota')
370 5ce3ce4f Sofia Papagiannaki
371 5ce3ce4f Sofia Papagiannaki
    astakos_groups = models.ManyToManyField(
372 5ce3ce4f Sofia Papagiannaki
        AstakosGroup, verbose_name=_('agroups'), blank=True,
373 ae497612 Olga Brani
        help_text=_(astakos_messages.ASTAKOSUSER_GROUPS_HELP),
374 8e45d6fd Sofia Papagiannaki
        through='Membership')
375 5ce3ce4f Sofia Papagiannaki
376 18ffbee1 Sofia Papagiannaki
    __has_signed_terms = False
377 e1a80257 Sofia Papagiannaki
    disturbed_quota = models.BooleanField(_('Needs quotaholder syncing'),
378 9a06d96f Olga Brani
                                           default=False, db_index=True)
379 d2633501 Kostas Papadimitriou
380 d2633501 Kostas Papadimitriou
    objects = AstakosUserManager()
381 fbaa4f3c Kostas Papadimitriou
382 5ce3ce4f Sofia Papagiannaki
    owner = models.ManyToManyField(
383 5ce3ce4f Sofia Papagiannaki
        AstakosGroup, related_name='owner', null=True)
384 5ce3ce4f Sofia Papagiannaki
385 74b273d8 Sofia Papagiannaki
    class Meta:
386 74b273d8 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
387 d2633501 Kostas Papadimitriou
388 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
389 18ffbee1 Sofia Papagiannaki
        super(AstakosUser, self).__init__(*args, **kwargs)
390 18ffbee1 Sofia Papagiannaki
        self.__has_signed_terms = self.has_signed_terms
391 a3637508 Sofia Papagiannaki
        if not self.id:
392 18ffbee1 Sofia Papagiannaki
            self.is_active = False
393 5ce3ce4f Sofia Papagiannaki
394 0905ccd2 Sofia Papagiannaki
    @property
395 0905ccd2 Sofia Papagiannaki
    def realname(self):
396 5ce3ce4f Sofia Papagiannaki
        return '%s %s' % (self.first_name, self.last_name)
397 6c736ed7 Kostas Papadimitriou
398 0905ccd2 Sofia Papagiannaki
    @realname.setter
399 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
400 0905ccd2 Sofia Papagiannaki
        parts = value.split(' ')
401 0905ccd2 Sofia Papagiannaki
        if len(parts) == 2:
402 0905ccd2 Sofia Papagiannaki
            self.first_name = parts[0]
403 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[1]
404 0905ccd2 Sofia Papagiannaki
        else:
405 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[0]
406 6c736ed7 Kostas Papadimitriou
407 9a06d96f Olga Brani
    def add_permission(self, pname):
408 9a06d96f Olga Brani
        if self.has_perm(pname):
409 9a06d96f Olga Brani
            return
410 e65c21df Georgios D. Tsoukalas
        p, created = Permission.objects.get_or_create(
411 e65c21df Georgios D. Tsoukalas
                                    codename=pname,
412 e65c21df Georgios D. Tsoukalas
                                    name=pname.capitalize(),
413 e65c21df Georgios D. Tsoukalas
                                    content_type=get_content_type())
414 9a06d96f Olga Brani
        self.user_permissions.add(p)
415 9a06d96f Olga Brani
416 9a06d96f Olga Brani
    def remove_permission(self, pname):
417 9a06d96f Olga Brani
        if self.has_perm(pname):
418 9a06d96f Olga Brani
            return
419 9a06d96f Olga Brani
        p = Permission.objects.get(codename=pname,
420 e65c21df Georgios D. Tsoukalas
                                   content_type=get_content_type())
421 9a06d96f Olga Brani
        self.user_permissions.remove(p)
422 9a06d96f Olga Brani
423 64cd4730 Antony Chazapis
    @property
424 64cd4730 Antony Chazapis
    def invitation(self):
425 64cd4730 Antony Chazapis
        try:
426 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
427 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
428 64cd4730 Antony Chazapis
            return None
429 6c736ed7 Kostas Papadimitriou
430 9a06d96f Olga Brani
    def invite(self, email, realname):
431 9a06d96f Olga Brani
        inv = Invitation(inviter=self, username=email, realname=realname)
432 9a06d96f Olga Brani
        inv.save()
433 9a06d96f Olga Brani
        send_invitation(inv)
434 9a06d96f Olga Brani
        self.invitations = max(0, self.invitations - 1)
435 9a06d96f Olga Brani
        self.save()
436 9a06d96f Olga Brani
437 ffb1e7a8 Sofia Papagiannaki
    @property
438 ffb1e7a8 Sofia Papagiannaki
    def quota(self):
439 9a06d96f Olga Brani
        """Returns a dict with the sum of quota limits per resource"""
440 ffb1e7a8 Sofia Papagiannaki
        d = defaultdict(int)
441 9a06d96f Olga Brani
        for q in self.policies:
442 9ee0c6a2 Sofia Papagiannaki
            d[q.resource] += q.uplimit or inf
443 9a06d96f Olga Brani
        for m in self.extended_groups:
444 fc1e2f02 Sofia Papagiannaki
            if not m.is_approved:
445 fc1e2f02 Sofia Papagiannaki
                continue
446 fc1e2f02 Sofia Papagiannaki
            g = m.group
447 ffb1e7a8 Sofia Papagiannaki
            if not g.is_enabled:
448 ffb1e7a8 Sofia Papagiannaki
                continue
449 670de92a Sofia Papagiannaki
            for r, uplimit in g.quota.iteritems():
450 9ee0c6a2 Sofia Papagiannaki
                d[r] += uplimit or inf
451 ffb1e7a8 Sofia Papagiannaki
        # TODO set default for remaining
452 ffb1e7a8 Sofia Papagiannaki
        return d
453 5ce3ce4f Sofia Papagiannaki
454 9a06d96f Olga Brani
    @property
455 9a06d96f Olga Brani
    def policies(self):
456 9a06d96f Olga Brani
        return self.astakosuserquota_set.select_related().all()
457 9a06d96f Olga Brani
458 9a06d96f Olga Brani
    @policies.setter
459 9a06d96f Olga Brani
    def policies(self, policies):
460 9a06d96f Olga Brani
        for p in policies:
461 9a06d96f Olga Brani
            service = policies.get('service', None)
462 9a06d96f Olga Brani
            resource = policies.get('resource', None)
463 9a06d96f Olga Brani
            uplimit = policies.get('uplimit', 0)
464 9a06d96f Olga Brani
            update = policies.get('update', True)
465 9a06d96f Olga Brani
            self.add_policy(service, resource, uplimit, update)
466 9a06d96f Olga Brani
467 9a06d96f Olga Brani
    def add_policy(self, service, resource, uplimit, update=True):
468 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
469 9a06d96f Olga Brani
        resource = Resource.objects.get(service__name=service, name=resource)
470 9a06d96f Olga Brani
        if update:
471 9a06d96f Olga Brani
            AstakosUserQuota.objects.update_or_create(user=self,
472 9a06d96f Olga Brani
                                                      resource=resource,
473 9a06d96f Olga Brani
                                                      defaults={'uplimit': uplimit})
474 9a06d96f Olga Brani
        else:
475 9a06d96f Olga Brani
            q = self.astakosuserquota_set
476 9a06d96f Olga Brani
            q.create(resource=resource, uplimit=uplimit)
477 9a06d96f Olga Brani
478 9a06d96f Olga Brani
    def remove_policy(self, service, resource):
479 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
480 9a06d96f Olga Brani
        resource = Resource.objects.get(service__name=service, name=resource)
481 9a06d96f Olga Brani
        q = self.policies.get(resource=resource).delete()
482 9a06d96f Olga Brani
483 9a06d96f Olga Brani
    @property
484 9a06d96f Olga Brani
    def extended_groups(self):
485 9a06d96f Olga Brani
        return self.membership_set.select_related().all()
486 9a06d96f Olga Brani
487 9a06d96f Olga Brani
    @extended_groups.setter
488 9a06d96f Olga Brani
    def extended_groups(self, groups):
489 9a06d96f Olga Brani
        #TODO exceptions
490 40a0cd8b Sofia Papagiannaki
        for name in (groups or ()):
491 9a06d96f Olga Brani
            group = AstakosGroup.objects.get(name=name)
492 9a06d96f Olga Brani
            self.membership_set.create(group=group)
493 9a06d96f Olga Brani
494 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
495 64cd4730 Antony Chazapis
        if update_timestamps:
496 64cd4730 Antony Chazapis
            if not self.id:
497 0905ccd2 Sofia Papagiannaki
                self.date_joined = datetime.now()
498 64cd4730 Antony Chazapis
            self.updated = datetime.now()
499 5ce3ce4f Sofia Papagiannaki
500 18ffbee1 Sofia Papagiannaki
        # update date_signed_terms if necessary
501 18ffbee1 Sofia Papagiannaki
        if self.__has_signed_terms != self.has_signed_terms:
502 18ffbee1 Sofia Papagiannaki
            self.date_signed_terms = datetime.now()
503 5ce3ce4f Sofia Papagiannaki
504 9c01d9d1 Sofia Papagiannaki
        if not self.id:
505 9c01d9d1 Sofia Papagiannaki
            # set username
506 a706ae94 Sofia Papagiannaki
            self.username = self.email
507 fbaa4f3c Kostas Papadimitriou
508 591d0505 Sofia Papagiannaki
        self.validate_unique_email_isactive()
509 751d24cf Sofia Papagiannaki
        if self.is_active and self.activation_sent:
510 751d24cf Sofia Papagiannaki
            # reset the activation sent
511 751d24cf Sofia Papagiannaki
            self.activation_sent = None
512 5ce3ce4f Sofia Papagiannaki
513 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
514 2e90e3ec Kostas Papadimitriou
515 bf0c6de5 Sofia Papagiannaki
    def renew_token(self, flush_sessions=False, current_key=None):
516 64cd4730 Antony Chazapis
        md5 = hashlib.md5()
517 8f8c43b2 Sofia Papagiannaki
        md5.update(settings.SECRET_KEY)
518 0905ccd2 Sofia Papagiannaki
        md5.update(self.username)
519 64cd4730 Antony Chazapis
        md5.update(self.realname.encode('ascii', 'ignore'))
520 64cd4730 Antony Chazapis
        md5.update(asctime())
521 2e90e3ec Kostas Papadimitriou
522 64cd4730 Antony Chazapis
        self.auth_token = b64encode(md5.digest())
523 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
524 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
525 92defad4 Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
526 bf0c6de5 Sofia Papagiannaki
        if flush_sessions:
527 bf0c6de5 Sofia Papagiannaki
            self.flush_sessions(current_key)
528 111f3da6 Sofia Papagiannaki
        msg = 'Token renewed for %s' % self.email
529 aab4d540 Sofia Papagiannaki
        logger.log(LOGGING_LEVEL, msg)
530 6c736ed7 Kostas Papadimitriou
531 bf0c6de5 Sofia Papagiannaki
    def flush_sessions(self, current_key=None):
532 bf0c6de5 Sofia Papagiannaki
        q = self.sessions
533 bf0c6de5 Sofia Papagiannaki
        if current_key:
534 bf0c6de5 Sofia Papagiannaki
            q = q.exclude(session_key=current_key)
535 2e90e3ec Kostas Papadimitriou
536 bf0c6de5 Sofia Papagiannaki
        keys = q.values_list('session_key', flat=True)
537 bf0c6de5 Sofia Papagiannaki
        if keys:
538 bf0c6de5 Sofia Papagiannaki
            msg = 'Flushing sessions: %s' % ','.join(keys)
539 c0b26605 Sofia Papagiannaki
            logger.log(LOGGING_LEVEL, msg, [])
540 bf0c6de5 Sofia Papagiannaki
        engine = import_module(settings.SESSION_ENGINE)
541 bf0c6de5 Sofia Papagiannaki
        for k in keys:
542 bf0c6de5 Sofia Papagiannaki
            s = engine.SessionStore(k)
543 bf0c6de5 Sofia Papagiannaki
            s.flush()
544 bf0c6de5 Sofia Papagiannaki
545 64cd4730 Antony Chazapis
    def __unicode__(self):
546 3abf6c78 Sofia Papagiannaki
        return '%s (%s)' % (self.realname, self.email)
547 5ce3ce4f Sofia Papagiannaki
548 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
549 5ce3ce4f Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username=self.username)
550 789a5951 Sofia Papagiannaki
        q = q.filter(email__iexact=self.email)
551 0a569195 Sofia Papagiannaki
        if q.count() != 0:
552 0a569195 Sofia Papagiannaki
            return True
553 0a569195 Sofia Papagiannaki
        return False
554 5ce3ce4f Sofia Papagiannaki
555 591d0505 Sofia Papagiannaki
    def validate_unique_email_isactive(self):
556 0a569195 Sofia Papagiannaki
        """
557 0a569195 Sofia Papagiannaki
        Implements a unique_together constraint for email and is_active fields.
558 0a569195 Sofia Papagiannaki
        """
559 e6759494 Sofia Papagiannaki
        q = AstakosUser.objects.all()
560 0a569195 Sofia Papagiannaki
        q = q.filter(email = self.email)
561 0a569195 Sofia Papagiannaki
        q = q.filter(is_active = self.is_active)
562 e6759494 Sofia Papagiannaki
        if self.id:
563 e6759494 Sofia Papagiannaki
            q = q.filter(~Q(id = self.id))
564 0a569195 Sofia Papagiannaki
        if q.count() != 0:
565 ae497612 Olga Brani
            raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
566 5ce3ce4f Sofia Papagiannaki
567 fcf90160 Sofia Papagiannaki
    @property
568 09e7393c Sofia Papagiannaki
    def signed_terms(self):
569 09e7393c Sofia Papagiannaki
        term = get_latest_terms()
570 09e7393c Sofia Papagiannaki
        if not term:
571 09e7393c Sofia Papagiannaki
            return True
572 09e7393c Sofia Papagiannaki
        if not self.has_signed_terms:
573 09e7393c Sofia Papagiannaki
            return False
574 09e7393c Sofia Papagiannaki
        if not self.date_signed_terms:
575 09e7393c Sofia Papagiannaki
            return False
576 09e7393c Sofia Papagiannaki
        if self.date_signed_terms < term.date:
577 09e7393c Sofia Papagiannaki
            self.has_signed_terms = False
578 f0f92965 Sofia Papagiannaki
            self.date_signed_terms = None
579 09e7393c Sofia Papagiannaki
            self.save()
580 09e7393c Sofia Papagiannaki
            return False
581 09e7393c Sofia Papagiannaki
        return True
582 09e7393c Sofia Papagiannaki
583 d2633501 Kostas Papadimitriou
    def set_invitations_level(self):
584 d2633501 Kostas Papadimitriou
        """
585 d2633501 Kostas Papadimitriou
        Update user invitation level
586 d2633501 Kostas Papadimitriou
        """
587 d2633501 Kostas Papadimitriou
        level = self.invitation.inviter.level + 1
588 d2633501 Kostas Papadimitriou
        self.level = level
589 d2633501 Kostas Papadimitriou
        self.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
590 d2633501 Kostas Papadimitriou
591 d2633501 Kostas Papadimitriou
    def can_login_with_auth_provider(self, provider):
592 d2633501 Kostas Papadimitriou
        if not self.has_auth_provider(provider):
593 d2633501 Kostas Papadimitriou
            return False
594 d2633501 Kostas Papadimitriou
        else:
595 d2633501 Kostas Papadimitriou
            return auth_providers.get_provider(provider).is_available_for_login()
596 d2633501 Kostas Papadimitriou
597 f432088a Kostas Papadimitriou
    def can_add_auth_provider(self, provider, **kwargs):
598 d2633501 Kostas Papadimitriou
        provider_settings = auth_providers.get_provider(provider)
599 d2633501 Kostas Papadimitriou
        if not provider_settings.is_available_for_login():
600 d2633501 Kostas Papadimitriou
            return False
601 f432088a Kostas Papadimitriou
602 d2633501 Kostas Papadimitriou
        if self.has_auth_provider(provider) and \
603 d2633501 Kostas Papadimitriou
           provider_settings.one_per_user:
604 d2633501 Kostas Papadimitriou
            return False
605 f432088a Kostas Papadimitriou
606 f432088a Kostas Papadimitriou
        if 'identifier' in kwargs:
607 f432088a Kostas Papadimitriou
            try:
608 f432088a Kostas Papadimitriou
                # provider with specified params already exist
609 f432088a Kostas Papadimitriou
                existing_user = AstakosUser.objects.get_auth_provider_user(provider,
610 f432088a Kostas Papadimitriou
                                                                   **kwargs)
611 f432088a Kostas Papadimitriou
            except AstakosUser.DoesNotExist:
612 f432088a Kostas Papadimitriou
                return True
613 f432088a Kostas Papadimitriou
            else:
614 f432088a Kostas Papadimitriou
                return False
615 f432088a Kostas Papadimitriou
616 d2633501 Kostas Papadimitriou
        return True
617 d2633501 Kostas Papadimitriou
618 d2633501 Kostas Papadimitriou
    def can_remove_auth_provider(self, provider):
619 d2633501 Kostas Papadimitriou
        if len(self.get_active_auth_providers()) <= 1:
620 d2633501 Kostas Papadimitriou
            return False
621 d2633501 Kostas Papadimitriou
        return True
622 d2633501 Kostas Papadimitriou
623 d2633501 Kostas Papadimitriou
    def can_change_password(self):
624 d2633501 Kostas Papadimitriou
        return self.has_auth_provider('local', auth_backend='astakos')
625 d2633501 Kostas Papadimitriou
626 d2633501 Kostas Papadimitriou
    def has_auth_provider(self, provider, **kwargs):
627 d2633501 Kostas Papadimitriou
        return bool(self.auth_providers.filter(module=provider,
628 d2633501 Kostas Papadimitriou
                                               **kwargs).count())
629 d2633501 Kostas Papadimitriou
630 d2633501 Kostas Papadimitriou
    def add_auth_provider(self, provider, **kwargs):
631 f432088a Kostas Papadimitriou
        if self.can_add_auth_provider(provider, **kwargs):
632 f432088a Kostas Papadimitriou
            self.auth_providers.create(module=provider, active=True, **kwargs)
633 f432088a Kostas Papadimitriou
        else:
634 f432088a Kostas Papadimitriou
            raise Exception('Cannot add provider')
635 d2633501 Kostas Papadimitriou
636 d2633501 Kostas Papadimitriou
    def add_pending_auth_provider(self, pending):
637 d2633501 Kostas Papadimitriou
        """
638 d2633501 Kostas Papadimitriou
        Convert PendingThirdPartyUser object to AstakosUserAuthProvider entry for
639 d2633501 Kostas Papadimitriou
        the current user.
640 d2633501 Kostas Papadimitriou
        """
641 d2633501 Kostas Papadimitriou
        if not isinstance(pending, PendingThirdPartyUser):
642 d2633501 Kostas Papadimitriou
            pending = PendingThirdPartyUser.objects.get(token=pending)
643 d2633501 Kostas Papadimitriou
644 d2633501 Kostas Papadimitriou
        provider = self.add_auth_provider(pending.provider,
645 d2633501 Kostas Papadimitriou
                               identifier=pending.third_party_identifier)
646 d2633501 Kostas Papadimitriou
647 fbaa4f3c Kostas Papadimitriou
        if email_re.match(pending.email or '') and pending.email != self.email:
648 d2633501 Kostas Papadimitriou
            self.additionalmail_set.get_or_create(email=pending.email)
649 d2633501 Kostas Papadimitriou
650 d2633501 Kostas Papadimitriou
        pending.delete()
651 d2633501 Kostas Papadimitriou
        return provider
652 d2633501 Kostas Papadimitriou
653 d2633501 Kostas Papadimitriou
    def remove_auth_provider(self, provider, **kwargs):
654 d2633501 Kostas Papadimitriou
        self.auth_providers.get(module=provider, **kwargs).delete()
655 d2633501 Kostas Papadimitriou
656 d2633501 Kostas Papadimitriou
    # user urls
657 d2633501 Kostas Papadimitriou
    def get_resend_activation_url(self):
658 d2633501 Kostas Papadimitriou
        return reverse('send_activation', {'user_id': self.pk})
659 d2633501 Kostas Papadimitriou
660 d2633501 Kostas Papadimitriou
    def get_activation_url(self, nxt=False):
661 d2633501 Kostas Papadimitriou
        url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
662 d2633501 Kostas Papadimitriou
                                 quote(self.auth_token))
663 d2633501 Kostas Papadimitriou
        if nxt:
664 d2633501 Kostas Papadimitriou
            url += "&next=%s" % quote(nxt)
665 d2633501 Kostas Papadimitriou
        return url
666 d2633501 Kostas Papadimitriou
667 d2633501 Kostas Papadimitriou
    def get_password_reset_url(self, token_generator=default_token_generator):
668 d2633501 Kostas Papadimitriou
        return reverse('django.contrib.auth.views.password_reset_confirm',
669 d2633501 Kostas Papadimitriou
                          kwargs={'uidb36':int_to_base36(self.id),
670 d2633501 Kostas Papadimitriou
                                  'token':token_generator.make_token(self)})
671 d2633501 Kostas Papadimitriou
672 d2633501 Kostas Papadimitriou
    def get_auth_providers(self):
673 d2633501 Kostas Papadimitriou
        return self.auth_providers.all()
674 d2633501 Kostas Papadimitriou
675 d2633501 Kostas Papadimitriou
    def get_available_auth_providers(self):
676 d2633501 Kostas Papadimitriou
        """
677 d2633501 Kostas Papadimitriou
        Returns a list of providers available for user to connect to.
678 d2633501 Kostas Papadimitriou
        """
679 d2633501 Kostas Papadimitriou
        providers = []
680 d2633501 Kostas Papadimitriou
        for module, provider_settings in auth_providers.PROVIDERS.iteritems():
681 f432088a Kostas Papadimitriou
            if self.can_add_auth_provider(module):
682 d2633501 Kostas Papadimitriou
                providers.append(provider_settings(self))
683 d2633501 Kostas Papadimitriou
684 d2633501 Kostas Papadimitriou
        return providers
685 d2633501 Kostas Papadimitriou
686 d2633501 Kostas Papadimitriou
    def get_active_auth_providers(self):
687 d2633501 Kostas Papadimitriou
        providers = []
688 d2633501 Kostas Papadimitriou
        for provider in self.auth_providers.active():
689 d2633501 Kostas Papadimitriou
            if auth_providers.get_provider(provider.module).is_available_for_login():
690 d2633501 Kostas Papadimitriou
                providers.append(provider)
691 d2633501 Kostas Papadimitriou
        return providers
692 d2633501 Kostas Papadimitriou
693 b778b6fa Kostas Papadimitriou
    @property
694 b778b6fa Kostas Papadimitriou
    def auth_providers_display(self):
695 b778b6fa Kostas Papadimitriou
        return ",".join(map(lambda x:unicode(x), self.auth_providers.active()))
696 b778b6fa Kostas Papadimitriou
697 d2633501 Kostas Papadimitriou
698 d2633501 Kostas Papadimitriou
class AstakosUserAuthProviderManager(models.Manager):
699 d2633501 Kostas Papadimitriou
700 d2633501 Kostas Papadimitriou
    def active(self):
701 d2633501 Kostas Papadimitriou
        return self.filter(active=True)
702 d2633501 Kostas Papadimitriou
703 d2633501 Kostas Papadimitriou
704 d2633501 Kostas Papadimitriou
class AstakosUserAuthProvider(models.Model):
705 d2633501 Kostas Papadimitriou
    """
706 d2633501 Kostas Papadimitriou
    Available user authentication methods.
707 d2633501 Kostas Papadimitriou
    """
708 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
709 d2633501 Kostas Papadimitriou
                                   null=True, default=None)
710 d2633501 Kostas Papadimitriou
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
711 e1a80257 Sofia Papagiannaki
    module = models.CharField(_('Provider'), max_length=255, blank=False,
712 d2633501 Kostas Papadimitriou
                                default='local')
713 e1a80257 Sofia Papagiannaki
    identifier = models.CharField(_('Third-party identifier'),
714 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
715 d2633501 Kostas Papadimitriou
                                              blank=True)
716 d2633501 Kostas Papadimitriou
    active = models.BooleanField(default=True)
717 e1a80257 Sofia Papagiannaki
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
718 d2633501 Kostas Papadimitriou
                                   default='astakos')
719 d2633501 Kostas Papadimitriou
720 d2633501 Kostas Papadimitriou
    objects = AstakosUserAuthProviderManager()
721 d2633501 Kostas Papadimitriou
722 d2633501 Kostas Papadimitriou
    class Meta:
723 d2633501 Kostas Papadimitriou
        unique_together = (('identifier', 'module', 'user'), )
724 d2633501 Kostas Papadimitriou
725 d2633501 Kostas Papadimitriou
    @property
726 d2633501 Kostas Papadimitriou
    def settings(self):
727 d2633501 Kostas Papadimitriou
        return auth_providers.get_provider(self.module)
728 d2633501 Kostas Papadimitriou
729 d2633501 Kostas Papadimitriou
    @property
730 d2633501 Kostas Papadimitriou
    def details_display(self):
731 d2633501 Kostas Papadimitriou
        return self.settings.details_tpl % self.__dict__
732 d2633501 Kostas Papadimitriou
733 d2633501 Kostas Papadimitriou
    def can_remove(self):
734 d2633501 Kostas Papadimitriou
        return self.user.can_remove_auth_provider(self.module)
735 d2633501 Kostas Papadimitriou
736 d2633501 Kostas Papadimitriou
    def delete(self, *args, **kwargs):
737 d2633501 Kostas Papadimitriou
        ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs)
738 5156e663 Kostas Papadimitriou
        if self.module == 'local':
739 5156e663 Kostas Papadimitriou
            self.user.set_unusable_password()
740 5156e663 Kostas Papadimitriou
            self.user.save()
741 d2633501 Kostas Papadimitriou
        return ret
742 d2633501 Kostas Papadimitriou
743 f432088a Kostas Papadimitriou
    def __repr__(self):
744 f432088a Kostas Papadimitriou
        return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
745 f432088a Kostas Papadimitriou
746 b778b6fa Kostas Papadimitriou
    def __unicode__(self):
747 b778b6fa Kostas Papadimitriou
        if self.identifier:
748 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.identifier)
749 b778b6fa Kostas Papadimitriou
        if self.auth_backend:
750 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.auth_backend)
751 b778b6fa Kostas Papadimitriou
        return self.module
752 b778b6fa Kostas Papadimitriou
753 b778b6fa Kostas Papadimitriou
754 d2633501 Kostas Papadimitriou
755 8e45d6fd Sofia Papagiannaki
class Membership(models.Model):
756 8e45d6fd Sofia Papagiannaki
    person = models.ForeignKey(AstakosUser)
757 8e45d6fd Sofia Papagiannaki
    group = models.ForeignKey(AstakosGroup)
758 01ac12d5 Sofia Papagiannaki
    date_requested = models.DateField(default=datetime.now(), blank=True)
759 01ac12d5 Sofia Papagiannaki
    date_joined = models.DateField(null=True, db_index=True, blank=True)
760 5ce3ce4f Sofia Papagiannaki
761 8e45d6fd Sofia Papagiannaki
    class Meta:
762 8e45d6fd Sofia Papagiannaki
        unique_together = ("person", "group")
763 5ce3ce4f Sofia Papagiannaki
764 ffb1e7a8 Sofia Papagiannaki
    def save(self, *args, **kwargs):
765 28252c7f Sofia Papagiannaki
        if not self.id:
766 28252c7f Sofia Papagiannaki
            if not self.group.moderation_enabled:
767 28252c7f Sofia Papagiannaki
                self.date_joined = datetime.now()
768 ffb1e7a8 Sofia Papagiannaki
        super(Membership, self).save(*args, **kwargs)
769 5ce3ce4f Sofia Papagiannaki
770 8e45d6fd Sofia Papagiannaki
    @property
771 8e45d6fd Sofia Papagiannaki
    def is_approved(self):
772 8e45d6fd Sofia Papagiannaki
        if self.date_joined:
773 8e45d6fd Sofia Papagiannaki
            return True
774 8e45d6fd Sofia Papagiannaki
        return False
775 5ce3ce4f Sofia Papagiannaki
776 01ac12d5 Sofia Papagiannaki
    def approve(self):
777 c0b26605 Sofia Papagiannaki
        if self.is_approved:
778 c0b26605 Sofia Papagiannaki
            return
779 ae497612 Olga Brani
        if self.group.max_participants:
780 a4075f5a root
            assert len(self.group.approved_members) + 1 <= self.group.max_participants, \
781 c0b26605 Sofia Papagiannaki
            'Maximum participant number has been reached.'
782 01ac12d5 Sofia Papagiannaki
        self.date_joined = datetime.now()
783 01ac12d5 Sofia Papagiannaki
        self.save()
784 fc1e2f02 Sofia Papagiannaki
        quota_disturbed.send(sender=self, users=(self.person,))
785 5ce3ce4f Sofia Papagiannaki
786 01ac12d5 Sofia Papagiannaki
    def disapprove(self):
787 e1a80257 Sofia Papagiannaki
        approved = self.is_approved()
788 01ac12d5 Sofia Papagiannaki
        self.delete()
789 e1a80257 Sofia Papagiannaki
        if approved:
790 e1a80257 Sofia Papagiannaki
            quota_disturbed.send(sender=self, users=(self.person,))
791 8e45d6fd Sofia Papagiannaki
792 e1a80257 Sofia Papagiannaki
class ExtendedManager(models.Manager):
793 9a06d96f Olga Brani
    def _update_or_create(self, **kwargs):
794 9a06d96f Olga Brani
        assert kwargs, \
795 9a06d96f Olga Brani
            'update_or_create() must be passed at least one keyword argument'
796 9a06d96f Olga Brani
        obj, created = self.get_or_create(**kwargs)
797 9a06d96f Olga Brani
        defaults = kwargs.pop('defaults', {})
798 9a06d96f Olga Brani
        if created:
799 9a06d96f Olga Brani
            return obj, True, False
800 9a06d96f Olga Brani
        else:
801 9a06d96f Olga Brani
            try:
802 9a06d96f Olga Brani
                params = dict(
803 9a06d96f Olga Brani
                    [(k, v) for k, v in kwargs.items() if '__' not in k])
804 9a06d96f Olga Brani
                params.update(defaults)
805 9a06d96f Olga Brani
                for attr, val in params.items():
806 9a06d96f Olga Brani
                    if hasattr(obj, attr):
807 9a06d96f Olga Brani
                        setattr(obj, attr, val)
808 9a06d96f Olga Brani
                sid = transaction.savepoint()
809 9a06d96f Olga Brani
                obj.save(force_update=True)
810 9a06d96f Olga Brani
                transaction.savepoint_commit(sid)
811 9a06d96f Olga Brani
                return obj, False, True
812 9a06d96f Olga Brani
            except IntegrityError, e:
813 9a06d96f Olga Brani
                transaction.savepoint_rollback(sid)
814 9a06d96f Olga Brani
                try:
815 9a06d96f Olga Brani
                    return self.get(**kwargs), False, False
816 9a06d96f Olga Brani
                except self.model.DoesNotExist:
817 9a06d96f Olga Brani
                    raise e
818 9a06d96f Olga Brani
819 9a06d96f Olga Brani
    update_or_create = _update_or_create
820 5ce3ce4f Sofia Papagiannaki
821 8e45d6fd Sofia Papagiannaki
class AstakosGroupQuota(models.Model):
822 e1a80257 Sofia Papagiannaki
    objects = ExtendedManager()
823 e1a80257 Sofia Papagiannaki
    limit = models.PositiveIntegerField(_('Limit'), null=True)    # obsolete field
824 e1a80257 Sofia Papagiannaki
    uplimit = models.BigIntegerField(_('Up limit'), null=True)
825 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
826 8e45d6fd Sofia Papagiannaki
    group = models.ForeignKey(AstakosGroup, blank=True)
827 5ce3ce4f Sofia Papagiannaki
828 8e45d6fd Sofia Papagiannaki
    class Meta:
829 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "group")
830 8e45d6fd Sofia Papagiannaki
831 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
832 e1a80257 Sofia Papagiannaki
    objects = ExtendedManager()
833 e1a80257 Sofia Papagiannaki
    limit = models.PositiveIntegerField(_('Limit'), null=True)    # obsolete field
834 e1a80257 Sofia Papagiannaki
    uplimit = models.BigIntegerField(_('Up limit'), null=True)
835 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
836 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
837 5ce3ce4f Sofia Papagiannaki
838 8e45d6fd Sofia Papagiannaki
    class Meta:
839 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
840 09e7393c Sofia Papagiannaki
841 5ce3ce4f Sofia Papagiannaki
842 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
843 270dd48d Sofia Papagiannaki
    """
844 270dd48d Sofia Papagiannaki
    Model for approval terms
845 270dd48d Sofia Papagiannaki
    """
846 6c736ed7 Kostas Papadimitriou
847 5ce3ce4f Sofia Papagiannaki
    date = models.DateTimeField(
848 e1a80257 Sofia Papagiannaki
        _('Issue date'), db_index=True, default=datetime.now())
849 e1a80257 Sofia Papagiannaki
    location = models.CharField(_('Terms location'), max_length=255)
850 270dd48d Sofia Papagiannaki
851 5ce3ce4f Sofia Papagiannaki
852 64cd4730 Antony Chazapis
class Invitation(models.Model):
853 890b0eaf Sofia Papagiannaki
    """
854 890b0eaf Sofia Papagiannaki
    Model for registring invitations
855 890b0eaf Sofia Papagiannaki
    """
856 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
857 64cd4730 Antony Chazapis
                                null=True)
858 e1a80257 Sofia Papagiannaki
    realname = models.CharField(_('Real name'), max_length=255)
859 e1a80257 Sofia Papagiannaki
    username = models.CharField(_('Unique ID'), max_length=255, unique=True)
860 e1a80257 Sofia Papagiannaki
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
861 e1a80257 Sofia Papagiannaki
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
862 e1a80257 Sofia Papagiannaki
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
863 e1a80257 Sofia Papagiannaki
    consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True)
864 5ce3ce4f Sofia Papagiannaki
865 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
866 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
867 8f5a3a06 Sofia Papagiannaki
        if not self.id:
868 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
869 5ce3ce4f Sofia Papagiannaki
870 64cd4730 Antony Chazapis
    def consume(self):
871 64cd4730 Antony Chazapis
        self.is_consumed = True
872 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
873 64cd4730 Antony Chazapis
        self.save()
874 6c736ed7 Kostas Papadimitriou
875 64cd4730 Antony Chazapis
    def __unicode__(self):
876 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
877 9c01d9d1 Sofia Papagiannaki
878 49790d9d Sofia Papagiannaki
879 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
880 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
881 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
882 49790d9d Sofia Papagiannaki
        """
883 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
884 49790d9d Sofia Papagiannaki
        ``User`` if valid.
885 49790d9d Sofia Papagiannaki

886 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
887 49790d9d Sofia Papagiannaki
        after activating.
888 49790d9d Sofia Papagiannaki

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

891 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
892 49790d9d Sofia Papagiannaki
        return ``None``.
893 49790d9d Sofia Papagiannaki

894 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
895 49790d9d Sofia Papagiannaki

896 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
897 49790d9d Sofia Papagiannaki
        """
898 49790d9d Sofia Papagiannaki
        try:
899 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
900 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
901 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
902 49790d9d Sofia Papagiannaki
                email_change.delete()
903 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
904 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
905 49790d9d Sofia Papagiannaki
            try:
906 789a5951 Sofia Papagiannaki
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
907 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
908 49790d9d Sofia Papagiannaki
                pass
909 49790d9d Sofia Papagiannaki
            else:
910 ae497612 Olga Brani
                raise ValueError(_(astakos_messages.NEW_EMAIL_ADDR_RESERVED))
911 49790d9d Sofia Papagiannaki
            # update user
912 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
913 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
914 49790d9d Sofia Papagiannaki
            user.save()
915 49790d9d Sofia Papagiannaki
            email_change.delete()
916 49790d9d Sofia Papagiannaki
            return user
917 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
918 ae497612 Olga Brani
            raise ValueError(_(astakos_messages.INVALID_ACTIVATION_KEY))
919 49790d9d Sofia Papagiannaki
920 49790d9d Sofia Papagiannaki
921 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
922 9a06d96f Olga Brani
    new_email_address = models.EmailField(_(u'new e-mail address'),
923 ae497612 Olga Brani
                                          help_text=_(astakos_messages.EMAIL_CHANGE_NEW_ADDR_HELP))
924 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
925 5ce3ce4f Sofia Papagiannaki
        AstakosUser, unique=True, related_name='emailchange_user')
926 49790d9d Sofia Papagiannaki
    requested_at = models.DateTimeField(default=datetime.now())
927 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
928 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
929 49790d9d Sofia Papagiannaki
930 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
931 49790d9d Sofia Papagiannaki
932 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
933 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
934 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
935 ff9290ec Sofia Papagiannaki
936 6b03a847 Sofia Papagiannaki
937 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
938 ca828a10 Sofia Papagiannaki
    """
939 ca828a10 Sofia Papagiannaki
    Model for registring invitations
940 ca828a10 Sofia Papagiannaki
    """
941 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
942 1eec103a Sofia Papagiannaki
    email = models.EmailField()
943 ca828a10 Sofia Papagiannaki
944 5ce3ce4f Sofia Papagiannaki
945 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
946 fc1e2f02 Sofia Papagiannaki
    while True:
947 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
948 fc1e2f02 Sofia Papagiannaki
        try:
949 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
950 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
951 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
952 fc1e2f02 Sofia Papagiannaki
            return code
953 fc1e2f02 Sofia Papagiannaki
954 5ce3ce4f Sofia Papagiannaki
955 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
956 fc1e2f02 Sofia Papagiannaki
    try:
957 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
958 fc1e2f02 Sofia Papagiannaki
        return term
959 fc1e2f02 Sofia Papagiannaki
    except IndexError:
960 fc1e2f02 Sofia Papagiannaki
        pass
961 fc1e2f02 Sofia Papagiannaki
    return None
962 fc1e2f02 Sofia Papagiannaki
963 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
964 ef20ea07 Sofia Papagiannaki
    """
965 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
966 ef20ea07 Sofia Papagiannaki
    """
967 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
968 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
969 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
970 ef20ea07 Sofia Papagiannaki
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
971 ef20ea07 Sofia Papagiannaki
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
972 ef20ea07 Sofia Papagiannaki
    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
973 ef20ea07 Sofia Papagiannaki
    username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
974 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
975 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
976 d2633501 Kostas Papadimitriou
977 678b2236 Sofia Papagiannaki
    class Meta:
978 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
979 ef20ea07 Sofia Papagiannaki
980 ef20ea07 Sofia Papagiannaki
    @property
981 ef20ea07 Sofia Papagiannaki
    def realname(self):
982 ef20ea07 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
983 ef20ea07 Sofia Papagiannaki
984 ef20ea07 Sofia Papagiannaki
    @realname.setter
985 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
986 ef20ea07 Sofia Papagiannaki
        parts = value.split(' ')
987 ef20ea07 Sofia Papagiannaki
        if len(parts) == 2:
988 ef20ea07 Sofia Papagiannaki
            self.first_name = parts[0]
989 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[1]
990 ef20ea07 Sofia Papagiannaki
        else:
991 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[0]
992 2e90e3ec Kostas Papadimitriou
993 ef20ea07 Sofia Papagiannaki
    def save(self, **kwargs):
994 ef20ea07 Sofia Papagiannaki
        if not self.id:
995 ef20ea07 Sofia Papagiannaki
            # set username
996 ef20ea07 Sofia Papagiannaki
            while not self.username:
997 ef20ea07 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
998 ef20ea07 Sofia Papagiannaki
                try:
999 ef20ea07 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
1000 ef20ea07 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
1001 ef20ea07 Sofia Papagiannaki
                    self.username = username
1002 ef20ea07 Sofia Papagiannaki
        super(PendingThirdPartyUser, self).save(**kwargs)
1003 ef20ea07 Sofia Papagiannaki
1004 d2633501 Kostas Papadimitriou
    def generate_token(self):
1005 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1006 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1007 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1008 d2633501 Kostas Papadimitriou
1009 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1010 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1011 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1012 bf0c6de5 Sofia Papagiannaki
1013 e1a80257 Sofia Papagiannaki
class MemberAcceptPolicy(models.Model):
1014 e1a80257 Sofia Papagiannaki
    policy = models.CharField(_('Policy'), max_length=255, unique=True, db_index=True)
1015 e1a80257 Sofia Papagiannaki
    description = models.CharField(_('Description'), max_length=80)
1016 e1a80257 Sofia Papagiannaki
1017 e1a80257 Sofia Papagiannaki
    def __str__(self):
1018 e1a80257 Sofia Papagiannaki
        return self.policy
1019 e1a80257 Sofia Papagiannaki
1020 b22de10a Sofia Papagiannaki
class MemberRejectPolicy(models.Model):
1021 b22de10a Sofia Papagiannaki
    policy = models.CharField(_('Policy'), max_length=255, unique=True, db_index=True)
1022 b22de10a Sofia Papagiannaki
    description = models.CharField(_('Description'), max_length=80)
1023 b22de10a Sofia Papagiannaki
1024 b22de10a Sofia Papagiannaki
    def __str__(self):
1025 b22de10a Sofia Papagiannaki
        return self.policy
1026 b22de10a Sofia Papagiannaki
1027 e65c21df Georgios D. Tsoukalas
_auto_accept = False
1028 e65c21df Georgios D. Tsoukalas
def get_auto_accept():
1029 e65c21df Georgios D. Tsoukalas
    global _auto_accept
1030 e65c21df Georgios D. Tsoukalas
    if _auto_accept is not False:
1031 e65c21df Georgios D. Tsoukalas
        return _auto_accept
1032 e65c21df Georgios D. Tsoukalas
    try:
1033 e65c21df Georgios D. Tsoukalas
        auto_accept = MemberAcceptPolicy.objects.get(policy='auto_accept')
1034 e65c21df Georgios D. Tsoukalas
    except:
1035 e65c21df Georgios D. Tsoukalas
        auto_accept = None
1036 e65c21df Georgios D. Tsoukalas
    _auto_accept = auto_accept
1037 e65c21df Georgios D. Tsoukalas
    return auto_accept
1038 e1a80257 Sofia Papagiannaki
1039 e1a80257 Sofia Papagiannaki
class ProjectDefinition(models.Model):
1040 e1a80257 Sofia Papagiannaki
    name = models.CharField(max_length=80)
1041 e1a80257 Sofia Papagiannaki
    homepage = models.URLField(max_length=255, null=True, blank=True)
1042 e1a80257 Sofia Papagiannaki
    description = models.TextField(null=True)
1043 e1a80257 Sofia Papagiannaki
    start_date = models.DateTimeField()
1044 e1a80257 Sofia Papagiannaki
    end_date = models.DateTimeField()
1045 e1a80257 Sofia Papagiannaki
    member_accept_policy = models.ForeignKey(MemberAcceptPolicy)
1046 e1a80257 Sofia Papagiannaki
    limit_on_members_number = models.PositiveIntegerField(null=True,blank=True)
1047 e1a80257 Sofia Papagiannaki
    resource_grants = models.ManyToManyField(
1048 e1a80257 Sofia Papagiannaki
        Resource,
1049 e1a80257 Sofia Papagiannaki
        null=True,
1050 e1a80257 Sofia Papagiannaki
        blank=True,
1051 e1a80257 Sofia Papagiannaki
        through='ProjectResourceGrant'
1052 e1a80257 Sofia Papagiannaki
    )
1053 e1a80257 Sofia Papagiannaki
    
1054 e1a80257 Sofia Papagiannaki
    def save(self):
1055 e1a80257 Sofia Papagiannaki
        self.validate_name()
1056 e1a80257 Sofia Papagiannaki
        super(ProjectDefinition, self).save()
1057 b22de10a Sofia Papagiannaki
        
1058 e1a80257 Sofia Papagiannaki
    @property
1059 e1a80257 Sofia Papagiannaki
    def violated_resource_grants(self):
1060 e1a80257 Sofia Papagiannaki
        return False
1061 e1a80257 Sofia Papagiannaki
    
1062 e1a80257 Sofia Papagiannaki
    def add_resource_policy(self, service, resource, uplimit, update=True):
1063 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1064 e1a80257 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=service, name=resource)
1065 e1a80257 Sofia Papagiannaki
        if update:
1066 f3342849 Sofia Papagiannaki
            ProjectResourceGrant.objects.update_or_create(
1067 f3342849 Sofia Papagiannaki
                project_definition=self,
1068 e1a80257 Sofia Papagiannaki
                resource=resource,
1069 f3342849 Sofia Papagiannaki
                defaults={'member_limit': uplimit}
1070 e1a80257 Sofia Papagiannaki
            )
1071 e1a80257 Sofia Papagiannaki
        else:
1072 f3342849 Sofia Papagiannaki
            q = self.projectresourcegrant_set
1073 f3342849 Sofia Papagiannaki
            q.create(resource=resource, member_limit=uplimit)
1074 e1a80257 Sofia Papagiannaki
1075 e1a80257 Sofia Papagiannaki
    @property
1076 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1077 f3342849 Sofia Papagiannaki
        return self.projectresourcegrant_set.all()
1078 e1a80257 Sofia Papagiannaki
1079 e1a80257 Sofia Papagiannaki
    @resource_policies.setter
1080 e1a80257 Sofia Papagiannaki
    def resource_policies(self, policies):
1081 e1a80257 Sofia Papagiannaki
        for p in policies:
1082 e1a80257 Sofia Papagiannaki
            service = p.get('service', None)
1083 e1a80257 Sofia Papagiannaki
            resource = p.get('resource', None)
1084 e1a80257 Sofia Papagiannaki
            uplimit = p.get('uplimit', 0)
1085 e1a80257 Sofia Papagiannaki
            update = p.get('update', True)
1086 e1a80257 Sofia Papagiannaki
            self.add_resource_policy(service, resource, uplimit, update)
1087 e1a80257 Sofia Papagiannaki
1088 e1a80257 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1089 e1a80257 Sofia Papagiannaki
    objects = ExtendedManager()
1090 e1a80257 Sofia Papagiannaki
    member_limit = models.BigIntegerField(null=True)
1091 e1a80257 Sofia Papagiannaki
    project_limit = models.BigIntegerField(null=True)
1092 e1a80257 Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
1093 e1a80257 Sofia Papagiannaki
    project_definition = models.ForeignKey(ProjectDefinition, blank=True)
1094 e1a80257 Sofia Papagiannaki
1095 e1a80257 Sofia Papagiannaki
    class Meta:
1096 e1a80257 Sofia Papagiannaki
        unique_together = ("resource", "project_definition")
1097 e1a80257 Sofia Papagiannaki
1098 e1a80257 Sofia Papagiannaki
class ProjectApplication(models.Model):
1099 e1a80257 Sofia Papagiannaki
    serial = models.CharField(
1100 e1a80257 Sofia Papagiannaki
        primary_key=True,
1101 e1a80257 Sofia Papagiannaki
        max_length=30,
1102 e1a80257 Sofia Papagiannaki
        unique=True,
1103 e1a80257 Sofia Papagiannaki
        default=uuid.uuid4().hex[:30]
1104 e1a80257 Sofia Papagiannaki
    )
1105 b22de10a Sofia Papagiannaki
    applicant = models.ForeignKey(
1106 b22de10a Sofia Papagiannaki
        AstakosUser,
1107 b22de10a Sofia Papagiannaki
        related_name='my_project_applications',
1108 b22de10a Sofia Papagiannaki
        db_index=True)
1109 b22de10a Sofia Papagiannaki
    owner = models.ForeignKey(
1110 b22de10a Sofia Papagiannaki
        AstakosUser,
1111 b22de10a Sofia Papagiannaki
        related_name='own_project_applications',
1112 b22de10a Sofia Papagiannaki
        db_index=True
1113 b22de10a Sofia Papagiannaki
    )
1114 e1a80257 Sofia Papagiannaki
    comments = models.TextField(null=True, blank=True)
1115 e1a80257 Sofia Papagiannaki
    definition = models.OneToOneField(ProjectDefinition)
1116 e1a80257 Sofia Papagiannaki
    issue_date = models.DateTimeField()
1117 e1a80257 Sofia Papagiannaki
    precursor_application = models.OneToOneField('ProjectApplication',
1118 e1a80257 Sofia Papagiannaki
        null=True,
1119 e1a80257 Sofia Papagiannaki
        blank=True
1120 e1a80257 Sofia Papagiannaki
    )
1121 e1a80257 Sofia Papagiannaki
1122 e1a80257 Sofia Papagiannaki
class Project(models.Model):
1123 e1a80257 Sofia Papagiannaki
    serial = models.CharField(
1124 e1a80257 Sofia Papagiannaki
        _('username'),
1125 e1a80257 Sofia Papagiannaki
        primary_key=True,
1126 e1a80257 Sofia Papagiannaki
        max_length=30,
1127 e1a80257 Sofia Papagiannaki
        unique=True,
1128 e1a80257 Sofia Papagiannaki
        default=uuid.uuid4().hex[:30]
1129 e1a80257 Sofia Papagiannaki
    )
1130 e1a80257 Sofia Papagiannaki
    application = models.OneToOneField(ProjectApplication, related_name='project')
1131 e1a80257 Sofia Papagiannaki
    creation_date = models.DateTimeField()
1132 2a965273 Sofia Papagiannaki
    last_approval_date = models.DateTimeField(null=True)
1133 b22de10a Sofia Papagiannaki
    termination_start_date = models.DateTimeField(null=True)
1134 2a965273 Sofia Papagiannaki
    termination_date = models.DateTimeField(null=True)
1135 e1a80257 Sofia Papagiannaki
    members = models.ManyToManyField(AstakosUser, through='ProjectMembership')
1136 b22de10a Sofia Papagiannaki
    membership_dirty = models.BooleanField(default=False)
1137 b22de10a Sofia Papagiannaki
    last_application_synced = models.OneToOneField(
1138 e1a80257 Sofia Papagiannaki
        ProjectApplication, related_name='last_project', null=True, blank=True
1139 e1a80257 Sofia Papagiannaki
    )
1140 e1a80257 Sofia Papagiannaki
    
1141 e1a80257 Sofia Papagiannaki
    @property
1142 e1a80257 Sofia Papagiannaki
    def definition(self):
1143 e1a80257 Sofia Papagiannaki
        return self.application.definition
1144 b22de10a Sofia Papagiannaki
1145 b22de10a Sofia Papagiannaki
    @property
1146 b22de10a Sofia Papagiannaki
    def violated_members_number_limit(self):
1147 b22de10a Sofia Papagiannaki
        return len(self.approved_members) <= self.definition.limit_on_members_number
1148 b22de10a Sofia Papagiannaki
1149 e1a80257 Sofia Papagiannaki
    @property
1150 e1a80257 Sofia Papagiannaki
    def is_valid(self):
1151 e1a80257 Sofia Papagiannaki
        try:
1152 e1a80257 Sofia Papagiannaki
            self.application.definition.validate_name()
1153 e1a80257 Sofia Papagiannaki
        except ValidationError:
1154 e1a80257 Sofia Papagiannaki
            return False
1155 e1a80257 Sofia Papagiannaki
        else:
1156 e1a80257 Sofia Papagiannaki
            return True
1157 b22de10a Sofia Papagiannaki
        
1158 e1a80257 Sofia Papagiannaki
    @property
1159 e1a80257 Sofia Papagiannaki
    def is_active(self):
1160 e1a80257 Sofia Papagiannaki
        if not self.is_valid:
1161 e1a80257 Sofia Papagiannaki
            return False
1162 e1a80257 Sofia Papagiannaki
        if not self.last_approval_date:
1163 e1a80257 Sofia Papagiannaki
            return False
1164 e1a80257 Sofia Papagiannaki
        if self.termination_date:
1165 e1a80257 Sofia Papagiannaki
            return False
1166 e1a80257 Sofia Papagiannaki
        if self.definition.violated_resource_grants:
1167 e1a80257 Sofia Papagiannaki
            return False
1168 b22de10a Sofia Papagiannaki
#         if self.violated_members_number_limit:
1169 b22de10a Sofia Papagiannaki
#             return False
1170 e1a80257 Sofia Papagiannaki
        return True
1171 e1a80257 Sofia Papagiannaki
    
1172 e1a80257 Sofia Papagiannaki
    @property
1173 e1a80257 Sofia Papagiannaki
    def is_terminated(self):
1174 e1a80257 Sofia Papagiannaki
        if not self.is_valid:
1175 e1a80257 Sofia Papagiannaki
            return False
1176 e1a80257 Sofia Papagiannaki
        if not self.termination_date:
1177 e1a80257 Sofia Papagiannaki
            return False
1178 e1a80257 Sofia Papagiannaki
        return True
1179 e1a80257 Sofia Papagiannaki
    
1180 e1a80257 Sofia Papagiannaki
    @property
1181 e1a80257 Sofia Papagiannaki
    def is_suspended(self):
1182 e1a80257 Sofia Papagiannaki
        if not self.is_valid:
1183 e1a80257 Sofia Papagiannaki
            return False
1184 e1a80257 Sofia Papagiannaki
        if not self.termination_date:
1185 e1a80257 Sofia Papagiannaki
            return False
1186 e1a80257 Sofia Papagiannaki
        if not self.last_approval_date:
1187 e1a80257 Sofia Papagiannaki
            if not self.definition.violated_resource_grants:
1188 e1a80257 Sofia Papagiannaki
                return False
1189 b22de10a Sofia Papagiannaki
#             if not self.violated_members_number_limit:
1190 b22de10a Sofia Papagiannaki
#                 return False
1191 e1a80257 Sofia Papagiannaki
        return True
1192 e1a80257 Sofia Papagiannaki
    
1193 e1a80257 Sofia Papagiannaki
    @property
1194 e1a80257 Sofia Papagiannaki
    def is_alive(self):
1195 e1a80257 Sofia Papagiannaki
        return self.is_active or self.is_suspended
1196 e1a80257 Sofia Papagiannaki
    
1197 e1a80257 Sofia Papagiannaki
    @property
1198 e1a80257 Sofia Papagiannaki
    def is_inconsistent(self):
1199 e1a80257 Sofia Papagiannaki
        now = datetime.now()
1200 e1a80257 Sofia Papagiannaki
        if self.creation_date > now:
1201 e1a80257 Sofia Papagiannaki
            return True
1202 e1a80257 Sofia Papagiannaki
        if self.last_approval_date > now:
1203 e1a80257 Sofia Papagiannaki
            return True
1204 e1a80257 Sofia Papagiannaki
        if self.terminaton_date > now:
1205 e1a80257 Sofia Papagiannaki
            return True
1206 e1a80257 Sofia Papagiannaki
        return False
1207 e1a80257 Sofia Papagiannaki
    
1208 e1a80257 Sofia Papagiannaki
    @property
1209 b22de10a Sofia Papagiannaki
    def is_synchronized(self):
1210 b22de10a Sofia Papagiannaki
        return self.last_application_synced == self.application and \
1211 b22de10a Sofia Papagiannaki
            not self.membership_dirty and \
1212 b22de10a Sofia Papagiannaki
            (not self.termination_start_date or termination_date)
1213 e1a80257 Sofia Papagiannaki
    
1214 b22de10a Sofia Papagiannaki
    @property
1215 b22de10a Sofia Papagiannaki
    def approved_members(self):
1216 b22de10a Sofia Papagiannaki
        return [m.person for m in self.projectmembership_set.filter(is_accepted=True)]
1217 b22de10a Sofia Papagiannaki
        
1218 b22de10a Sofia Papagiannaki
    def sync(self, specific_members=()):
1219 b22de10a Sofia Papagiannaki
        if self.is_synchornized():
1220 b22de10a Sofia Papagiannaki
            return
1221 b22de10a Sofia Papagiannaki
        members = specific_members or self.approved_members
1222 b22de10a Sofia Papagiannaki
        c, rejected = send_quota(members)
1223 e1a80257 Sofia Papagiannaki
        return rejected
1224 2a965273 Sofia Papagiannaki
    
1225 2a965273 Sofia Papagiannaki
    def add_member(self, user, request_user=None):
1226 2a965273 Sofia Papagiannaki
        if isinstance(user, int):
1227 2a965273 Sofia Papagiannaki
            user = _lookup_object(AstakosUser, id=user)
1228 2a965273 Sofia Papagiannaki
        if request_user and \
1229 2a965273 Sofia Papagiannaki
            (not self.owner == request_user and not request_user.is_superuser):
1230 2a965273 Sofia Papagiannaki
            raise Exception(_(astakos_messages.NOT_ALLOWED))
1231 2a965273 Sofia Papagiannaki
        if not self.is_alive:
1232 2a965273 Sofia Papagiannaki
            raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
1233 2a965273 Sofia Papagiannaki
        if self.definition.member_accept_policy == 'closed':
1234 2a965273 Sofia Papagiannaki
            raise Exception(_(astakos_messages.MEMBER_ACCEPT_POLICY_CLOSED))
1235 2a965273 Sofia Papagiannaki
        if len(self.approved_members) + 1 > self.limit_on_members_number:
1236 2a965273 Sofia Papagiannaki
            raise Exception(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
1237 2a965273 Sofia Papagiannaki
        created, m = ProjectMembership.objects.get_or_create(
1238 2a965273 Sofia Papagiannaki
            person=user, project=project
1239 2a965273 Sofia Papagiannaki
        )
1240 2a965273 Sofia Papagiannaki
        if m.is_accepted:
1241 2a965273 Sofia Papagiannaki
            return
1242 2a965273 Sofia Papagiannaki
        if created:
1243 2a965273 Sofia Papagiannaki
            m.issue_date = datetime.now()
1244 b22de10a Sofia Papagiannaki
        
1245 2a965273 Sofia Papagiannaki
        m.is_accepted = True
1246 2a965273 Sofia Papagiannaki
        m.decision_date = datetime.now()
1247 2a965273 Sofia Papagiannaki
        m.save()
1248 b22de10a Sofia Papagiannaki
        
1249 b22de10a Sofia Papagiannaki
        # set membership_dirty flag
1250 b22de10a Sofia Papagiannaki
        self.membership_dirty = True
1251 b22de10a Sofia Papagiannaki
        self.save()
1252 b22de10a Sofia Papagiannaki
        
1253 b22de10a Sofia Papagiannaki
        rejected = self.sync([user])
1254 b22de10a Sofia Papagiannaki
        if not rejected:
1255 b22de10a Sofia Papagiannaki
            # if syncing was successful unset membership_dirty flag
1256 b22de10a Sofia Papagiannaki
            self.membership_dirty = False
1257 b22de10a Sofia Papagiannaki
            self.save()
1258 b22de10a Sofia Papagiannaki
        
1259 2a965273 Sofia Papagiannaki
        notification = build_notification(
1260 2a965273 Sofia Papagiannaki
            settings.SERVER_EMAIL,
1261 2a965273 Sofia Papagiannaki
            [user.email],
1262 2a965273 Sofia Papagiannaki
            _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__,
1263 2a965273 Sofia Papagiannaki
            _('Your membership on project %(name)s has been accepted.') % project.definition.__dict__
1264 2a965273 Sofia Papagiannaki
        )
1265 b22de10a Sofia Papagiannaki
    
1266 2a965273 Sofia Papagiannaki
    def remove_member(self, user, request_user=None):
1267 2a965273 Sofia Papagiannaki
        if user.is_digit():
1268 2a965273 Sofia Papagiannaki
            user = _lookup_object(AstakosUser, id=user)
1269 2a965273 Sofia Papagiannaki
        if request_user and \
1270 2a965273 Sofia Papagiannaki
            (not self.owner == request_user and not request_user.is_superuser):
1271 2a965273 Sofia Papagiannaki
            raise Exception(_(astakos_messages.NOT_ALLOWED))
1272 2a965273 Sofia Papagiannaki
        if not self.is_alive:
1273 2a965273 Sofia Papagiannaki
            raise Exception(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
1274 2a965273 Sofia Papagiannaki
        m = _lookup_object(ProjectMembership, person=user, project=project)
1275 2a965273 Sofia Papagiannaki
        if not m.is_accepted:
1276 2a965273 Sofia Papagiannaki
            return
1277 2a965273 Sofia Papagiannaki
        m.is_accepted = False
1278 2a965273 Sofia Papagiannaki
        m.decision_date = datetime.now()
1279 2a965273 Sofia Papagiannaki
        m.save()
1280 b22de10a Sofia Papagiannaki
        
1281 b22de10a Sofia Papagiannaki
        # set membership_dirty flag
1282 b22de10a Sofia Papagiannaki
        self.membership_dirty = True
1283 b22de10a Sofia Papagiannaki
        self.save()
1284 b22de10a Sofia Papagiannaki
        
1285 b22de10a Sofia Papagiannaki
        rejected = self.sync([user])
1286 b22de10a Sofia Papagiannaki
        if not rejected:
1287 b22de10a Sofia Papagiannaki
            # if syncing was successful unset membership_dirty flag
1288 b22de10a Sofia Papagiannaki
            self.membership_dirty = False
1289 b22de10a Sofia Papagiannaki
            self.save()
1290 b22de10a Sofia Papagiannaki
            
1291 2a965273 Sofia Papagiannaki
        notification = build_notification(
1292 2a965273 Sofia Papagiannaki
            settings.SERVER_EMAIL,
1293 2a965273 Sofia Papagiannaki
            [user.email],
1294 2a965273 Sofia Papagiannaki
            _('Your membership on project %(name)s has been removed.') % project.definition.__dict__,
1295 2a965273 Sofia Papagiannaki
            _('Your membership on project %(name)s has been removed.') % project.definition.__dict__
1296 2a965273 Sofia Papagiannaki
        )
1297 2a965273 Sofia Papagiannaki
        notification.send()
1298 b22de10a Sofia Papagiannaki
            
1299 b22de10a Sofia Papagiannaki
1300 b22de10a Sofia Papagiannaki
    def validate_name(self):
1301 b22de10a Sofia Papagiannaki
        """
1302 b22de10a Sofia Papagiannaki
        Validate name uniqueness among all active projects.
1303 b22de10a Sofia Papagiannaki
        """
1304 b22de10a Sofia Papagiannaki
        alive_projects = list(get_alive_projects())
1305 b22de10a Sofia Papagiannaki
        q = filter(
1306 b22de10a Sofia Papagiannaki
            lambda p: p.definition.name == self.definition.name and \
1307 b22de10a Sofia Papagiannaki
                p.application.serial != self.application.serial,
1308 b22de10a Sofia Papagiannaki
            alive_projects
1309 b22de10a Sofia Papagiannaki
        )
1310 b22de10a Sofia Papagiannaki
        if q:
1311 b22de10a Sofia Papagiannaki
            raise ValidationError(
1312 b22de10a Sofia Papagiannaki
                {'name': [_(astakos_messages.UNIQUE_PROJECT_NAME_CONSTRAIN_ERR)]}
1313 b22de10a Sofia Papagiannaki
            )
1314 b22de10a Sofia Papagiannaki
    
1315 b22de10a Sofia Papagiannaki
    @classmethod
1316 b22de10a Sofia Papagiannaki
    def submit(definition, applicant, comments, precursor_application=None, commit=True):
1317 b22de10a Sofia Papagiannaki
        if precursor_application and precursor_application.project.is_valid:
1318 b22de10a Sofia Papagiannaki
            application = precursor_application.copy()
1319 b22de10a Sofia Papagiannaki
            application.precursor_application = precursor_application
1320 b22de10a Sofia Papagiannaki
        else:
1321 b22de10a Sofia Papagiannaki
            application = ProjectApplication(owner=applicant)
1322 b22de10a Sofia Papagiannaki
        application.definition = definition
1323 b22de10a Sofia Papagiannaki
        application.applicant = applicant
1324 b22de10a Sofia Papagiannaki
        application.comments = comments
1325 b22de10a Sofia Papagiannaki
        application.issue_date = datetime.now()
1326 b22de10a Sofia Papagiannaki
        if commit:
1327 b22de10a Sofia Papagiannaki
            definition.save()
1328 b22de10a Sofia Papagiannaki
            application.save()
1329 b22de10a Sofia Papagiannaki
        if applicant.is_superuser():
1330 b22de10a Sofia Papagiannaki
            self.approve_application()
1331 b22de10a Sofia Papagiannaki
        notification = build_notification(
1332 b22de10a Sofia Papagiannaki
            settings.SERVER_EMAIL,
1333 b22de10a Sofia Papagiannaki
            [i[1] for i in settings.ADMINS],
1334 b22de10a Sofia Papagiannaki
            _(GROUP_CREATION_SUBJECT) % {'group':application.definition.name},
1335 b22de10a Sofia Papagiannaki
            _('An new project application identified by %(serial)s has been submitted.') % application.__dict__
1336 b22de10a Sofia Papagiannaki
        )
1337 b22de10a Sofia Papagiannaki
        notification.send()
1338 b22de10a Sofia Papagiannaki
        return application
1339 b22de10a Sofia Papagiannaki
    
1340 b22de10a Sofia Papagiannaki
    def approve(self, approval_user=None):
1341 b22de10a Sofia Papagiannaki
        """
1342 b22de10a Sofia Papagiannaki
        If approval_user then during owner membership acceptance
1343 b22de10a Sofia Papagiannaki
        it is checked whether the request_user is eligible.
1344 b22de10a Sofia Papagiannaki
        """
1345 b22de10a Sofia Papagiannaki
        if not self.precursor_application:
1346 b22de10a Sofia Papagiannaki
            kwargs = {
1347 b22de10a Sofia Papagiannaki
                'application':self,
1348 b22de10a Sofia Papagiannaki
                'creation_date':datetime.now(),
1349 b22de10a Sofia Papagiannaki
                'last_approval_date':datetime.now(),
1350 b22de10a Sofia Papagiannaki
            }
1351 b22de10a Sofia Papagiannaki
            project = _create_object(Project, **kwargs)
1352 b22de10a Sofia Papagiannaki
            project.add_member(self.owner, approval_user)
1353 b22de10a Sofia Papagiannaki
        else:
1354 b22de10a Sofia Papagiannaki
            project = self.precursor_application.project
1355 b22de10a Sofia Papagiannaki
            last_approval_date = project.last_approval_date
1356 b22de10a Sofia Papagiannaki
            if project.is_valid:
1357 b22de10a Sofia Papagiannaki
                project.application = app
1358 b22de10a Sofia Papagiannaki
                project.last_approval_date = datetime.now()
1359 b22de10a Sofia Papagiannaki
                project.save()
1360 b22de10a Sofia Papagiannaki
            else:
1361 b22de10a Sofia Papagiannaki
                raise Exception(_(astakos_messages.INVALID_PROJECT) % project.__dict__)
1362 b22de10a Sofia Papagiannaki
        
1363 b22de10a Sofia Papagiannaki
        rejected = self.sync()
1364 b22de10a Sofia Papagiannaki
        if rejected:
1365 b22de10a Sofia Papagiannaki
            # revert to precursor
1366 b22de10a Sofia Papagiannaki
            project.appication = app.precursor_application
1367 b22de10a Sofia Papagiannaki
            if project.application:
1368 b22de10a Sofia Papagiannaki
                project.last_approval_date = last_approval_date
1369 b22de10a Sofia Papagiannaki
                project.save()
1370 b22de10a Sofia Papagiannaki
            rejected = synchonize_project(project.serial)
1371 b22de10a Sofia Papagiannaki
            if rejected:
1372 b22de10a Sofia Papagiannaki
                raise Exception(_(astakos_messages.QH_SYNC_ERROR))
1373 b22de10a Sofia Papagiannaki
        else:
1374 b22de10a Sofia Papagiannaki
            project.last_application_synced = app
1375 b22de10a Sofia Papagiannaki
            project.save()
1376 b22de10a Sofia Papagiannaki
            sender, recipients, subject, message
1377 b22de10a Sofia Papagiannaki
            notification = build_notification(
1378 b22de10a Sofia Papagiannaki
                settings.SERVER_EMAIL,
1379 b22de10a Sofia Papagiannaki
                [project.owner.email],
1380 b22de10a Sofia Papagiannaki
                _('Project application has been approved on %s alpha2 testing' % SITENAME),
1381 b22de10a Sofia Papagiannaki
                _('Your application request %(serial)s has been apporved.')
1382 b22de10a Sofia Papagiannaki
            )
1383 b22de10a Sofia Papagiannaki
            notification.send()
1384 b22de10a Sofia Papagiannaki
    
1385 b22de10a Sofia Papagiannaki
    def terminate(self):
1386 b22de10a Sofia Papagiannaki
        self.termination_start_date = datetime.now()
1387 b22de10a Sofia Papagiannaki
        self.terminaton_date = None
1388 b22de10a Sofia Papagiannaki
        self.save()
1389 b22de10a Sofia Papagiannaki
        
1390 b22de10a Sofia Papagiannaki
        rejected = self.sync()
1391 b22de10a Sofia Papagiannaki
        if not rejected:
1392 b22de10a Sofia Papagiannaki
            self.termination_start_date = None
1393 b22de10a Sofia Papagiannaki
            self.terminaton_date = datetime.now()
1394 b22de10a Sofia Papagiannaki
            self.save()
1395 b22de10a Sofia Papagiannaki
            
1396 b22de10a Sofia Papagiannaki
            notification = build_notification(
1397 b22de10a Sofia Papagiannaki
                settings.SERVER_EMAIL,
1398 b22de10a Sofia Papagiannaki
                [self.application.owner.email],
1399 b22de10a Sofia Papagiannaki
                _('Project %(name)s has been terminated.') %  self.definition.__dict__,
1400 b22de10a Sofia Papagiannaki
                _('Project %(name)s has been terminated.') %  self.definition.__dict__
1401 b22de10a Sofia Papagiannaki
            )
1402 b22de10a Sofia Papagiannaki
            notification.send()
1403 b22de10a Sofia Papagiannaki
1404 b22de10a Sofia Papagiannaki
    def suspend(self):
1405 b22de10a Sofia Papagiannaki
        self.last_approval_date = None
1406 b22de10a Sofia Papagiannaki
        self.save()
1407 b22de10a Sofia Papagiannaki
        notification = build_notification(
1408 b22de10a Sofia Papagiannaki
            settings.SERVER_EMAIL,
1409 b22de10a Sofia Papagiannaki
            [self.application.owner.email],
1410 b22de10a Sofia Papagiannaki
            _('Project %(name)s has been suspended.') %  self.definition.__dict__,
1411 b22de10a Sofia Papagiannaki
            _('Project %(name)s has been suspended.') %  self.definition.__dict__
1412 b22de10a Sofia Papagiannaki
        )
1413 b22de10a Sofia Papagiannaki
        notification.send()
1414 b22de10a Sofia Papagiannaki
1415 2a965273 Sofia Papagiannaki
1416 e1a80257 Sofia Papagiannaki
1417 e1a80257 Sofia Papagiannaki
class ProjectMembership(models.Model):
1418 e1a80257 Sofia Papagiannaki
    person = models.ForeignKey(AstakosUser)
1419 e1a80257 Sofia Papagiannaki
    project = models.ForeignKey(Project)
1420 e1a80257 Sofia Papagiannaki
    issue_date = models.DateField(default=datetime.now())
1421 e1a80257 Sofia Papagiannaki
    decision_date = models.DateField(null=True, db_index=True)
1422 2a965273 Sofia Papagiannaki
    is_accepted = models.BooleanField(default=False)
1423 e1a80257 Sofia Papagiannaki
1424 e1a80257 Sofia Papagiannaki
    class Meta:
1425 e1a80257 Sofia Papagiannaki
        unique_together = ("person", "project")
1426 e1a80257 Sofia Papagiannaki
1427 e1a80257 Sofia Papagiannaki
def filter_queryset_by_property(q, property):
1428 e1a80257 Sofia Papagiannaki
    """
1429 e1a80257 Sofia Papagiannaki
    Incorporate list comprehension for filtering querysets by property
1430 e1a80257 Sofia Papagiannaki
    since Queryset.filter() operates on the database level.
1431 e1a80257 Sofia Papagiannaki
    """
1432 e1a80257 Sofia Papagiannaki
    return (p for p in q if getattr(p, property, False))
1433 e1a80257 Sofia Papagiannaki
1434 e1a80257 Sofia Papagiannaki
def get_alive_projects():
1435 e1a80257 Sofia Papagiannaki
    return filter_queryset_by_property(
1436 e1a80257 Sofia Papagiannaki
        Project.objects.all(),
1437 e1a80257 Sofia Papagiannaki
        'is_alive'
1438 e1a80257 Sofia Papagiannaki
    )
1439 e1a80257 Sofia Papagiannaki
1440 e1a80257 Sofia Papagiannaki
def get_active_projects():
1441 e1a80257 Sofia Papagiannaki
    return filter_queryset_by_property(
1442 e1a80257 Sofia Papagiannaki
        Project.objects.all(),
1443 e1a80257 Sofia Papagiannaki
        'is_active'
1444 e1a80257 Sofia Papagiannaki
    )
1445 e1a80257 Sofia Papagiannaki
1446 e1a80257 Sofia Papagiannaki
def _lookup_object(model, **kwargs):
1447 e1a80257 Sofia Papagiannaki
    """
1448 e1a80257 Sofia Papagiannaki
    Returns an object of the specific model matching the given lookup
1449 e1a80257 Sofia Papagiannaki
    parameters.
1450 e1a80257 Sofia Papagiannaki
    """
1451 e1a80257 Sofia Papagiannaki
    if not kwargs:
1452 e1a80257 Sofia Papagiannaki
        raise MissingIdentifier
1453 e1a80257 Sofia Papagiannaki
    try:
1454 e1a80257 Sofia Papagiannaki
        return model.objects.get(**kwargs)
1455 e1a80257 Sofia Papagiannaki
    except model.DoesNotExist:
1456 e1a80257 Sofia Papagiannaki
        raise ItemNotExists(model._meta.verbose_name, **kwargs)
1457 e1a80257 Sofia Papagiannaki
    except model.MultipleObjectsReturned:
1458 e1a80257 Sofia Papagiannaki
        raise MultipleItemsExist(model._meta.verbose_name, **kwargs)
1459 e1a80257 Sofia Papagiannaki
1460 e1a80257 Sofia Papagiannaki
def _create_object(model, **kwargs):
1461 e1a80257 Sofia Papagiannaki
    o = model.objects.create(**kwargs)
1462 e1a80257 Sofia Papagiannaki
    o.save()
1463 e1a80257 Sofia Papagiannaki
    return o
1464 e1a80257 Sofia Papagiannaki
1465 e1a80257 Sofia Papagiannaki
def _update_object(model, id, save=True, **kwargs):
1466 e1a80257 Sofia Papagiannaki
    o = self._lookup_object(model, id=id)
1467 e1a80257 Sofia Papagiannaki
    if kwargs:
1468 e1a80257 Sofia Papagiannaki
        o.__dict__.update(kwargs)
1469 e1a80257 Sofia Papagiannaki
    if save:
1470 e1a80257 Sofia Papagiannaki
        o.save()
1471 e1a80257 Sofia Papagiannaki
    return o
1472 e1a80257 Sofia Papagiannaki
1473 e1a80257 Sofia Papagiannaki
def list_applications():
1474 2a965273 Sofia Papagiannaki
    return ProjectApplication.objects.all()
1475 e1a80257 Sofia Papagiannaki
1476 e1a80257 Sofia Papagiannaki
1477 e1a80257 Sofia Papagiannaki
def list_projects(filter_property=None):
1478 e1a80257 Sofia Papagiannaki
    if filter_property:
1479 e1a80257 Sofia Papagiannaki
        return filter_queryset_by_property(
1480 e1a80257 Sofia Papagiannaki
            Project.objects.all(),
1481 e1a80257 Sofia Papagiannaki
            filter_property
1482 e1a80257 Sofia Papagiannaki
        )
1483 e1a80257 Sofia Papagiannaki
    return Project.objects.all()
1484 e1a80257 Sofia Papagiannaki
1485 5ce3ce4f Sofia Papagiannaki
1486 e1a80257 Sofia Papagiannaki
def synchonize_project(serial):
1487 e1a80257 Sofia Papagiannaki
    project = _lookup_object(Project, serial=serial)
1488 b22de10a Sofia Papagiannaki
    return project.sync()
1489 b22de10a Sofia Papagiannaki
1490 b22de10a Sofia Papagiannaki
1491 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
1492 ff9290ec Sofia Papagiannaki
    try:
1493 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
1494 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
1495 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
1496 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
1497 ff9290ec Sofia Papagiannaki
        extended_user.save()
1498 67be1883 Olga Brani
        if not extended_user.has_auth_provider('local'):
1499 67be1883 Olga Brani
            extended_user.add_auth_provider('local')
1500 fc1e2f02 Sofia Papagiannaki
    except BaseException, e:
1501 fc1e2f02 Sofia Papagiannaki
        logger.exception(e)
1502 ff9290ec Sofia Papagiannaki
1503 5ce3ce4f Sofia Papagiannaki
1504 fc1e2f02 Sofia Papagiannaki
def fix_superusers(sender, **kwargs):
1505 fc1e2f02 Sofia Papagiannaki
    # Associate superusers with AstakosUser
1506 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
1507 ff9290ec Sofia Papagiannaki
    for u in admins:
1508 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
1509 ff9290ec Sofia Papagiannaki
1510 ff9290ec Sofia Papagiannaki
1511 fc1e2f02 Sofia Papagiannaki
def user_post_save(sender, instance, created, **kwargs):
1512 aa4109d4 Sofia Papagiannaki
    if not created:
1513 aa4109d4 Sofia Papagiannaki
        return
1514 fc1e2f02 Sofia Papagiannaki
    create_astakos_user(instance)
1515 fc1e2f02 Sofia Papagiannaki
1516 5ce3ce4f Sofia Papagiannaki
1517 fc1e2f02 Sofia Papagiannaki
def set_default_group(user):
1518 aa4109d4 Sofia Papagiannaki
    try:
1519 5ce3ce4f Sofia Papagiannaki
        default = AstakosGroup.objects.get(name='default')
1520 5ce3ce4f Sofia Papagiannaki
        Membership(
1521 5ce3ce4f Sofia Papagiannaki
            group=default, person=user, date_joined=datetime.now()).save()
1522 aa4109d4 Sofia Papagiannaki
    except AstakosGroup.DoesNotExist, e:
1523 aa4109d4 Sofia Papagiannaki
        logger.exception(e)
1524 ff9290ec Sofia Papagiannaki
1525 bf0c6de5 Sofia Papagiannaki
1526 fc1e2f02 Sofia Papagiannaki
def astakosuser_pre_save(sender, instance, **kwargs):
1527 fc1e2f02 Sofia Papagiannaki
    instance.aquarium_report = False
1528 fc1e2f02 Sofia Papagiannaki
    instance.new = False
1529 fc1e2f02 Sofia Papagiannaki
    try:
1530 5ce3ce4f Sofia Papagiannaki
        db_instance = AstakosUser.objects.get(id=instance.id)
1531 fc1e2f02 Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
1532 fc1e2f02 Sofia Papagiannaki
        # create event
1533 fc1e2f02 Sofia Papagiannaki
        instance.aquarium_report = True
1534 fc1e2f02 Sofia Papagiannaki
        instance.new = True
1535 fc1e2f02 Sofia Papagiannaki
    else:
1536 fc1e2f02 Sofia Papagiannaki
        get = AstakosUser.__getattribute__
1537 fc1e2f02 Sofia Papagiannaki
        l = filter(lambda f: get(db_instance, f) != get(instance, f),
1538 9a06d96f Olga Brani
                   BILLING_FIELDS)
1539 fc1e2f02 Sofia Papagiannaki
        instance.aquarium_report = True if l else False
1540 fc1e2f02 Sofia Papagiannaki
1541 5ce3ce4f Sofia Papagiannaki
1542 fc1e2f02 Sofia Papagiannaki
def astakosuser_post_save(sender, instance, created, **kwargs):
1543 fc1e2f02 Sofia Papagiannaki
    if instance.aquarium_report:
1544 fc1e2f02 Sofia Papagiannaki
        report_user_event(instance, create=instance.new)
1545 fc1e2f02 Sofia Papagiannaki
    if not created:
1546 fc1e2f02 Sofia Papagiannaki
        return
1547 fc1e2f02 Sofia Papagiannaki
    set_default_group(instance)
1548 fc1e2f02 Sofia Papagiannaki
    # TODO handle socket.error & IOError
1549 fc1e2f02 Sofia Papagiannaki
    register_users((instance,))
1550 fc1e2f02 Sofia Papagiannaki
1551 5ce3ce4f Sofia Papagiannaki
1552 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
1553 bd4f356c Sofia Papagiannaki
    if not created:
1554 bd4f356c Sofia Papagiannaki
        return
1555 bd4f356c Sofia Papagiannaki
    register_resources((instance,))
1556 bd4f356c Sofia Papagiannaki
1557 bd4f356c Sofia Papagiannaki
1558 fc1e2f02 Sofia Papagiannaki
def send_quota_disturbed(sender, instance, **kwargs):
1559 fc1e2f02 Sofia Papagiannaki
    users = []
1560 fc1e2f02 Sofia Papagiannaki
    extend = users.extend
1561 fc1e2f02 Sofia Papagiannaki
    if sender == Membership:
1562 fc1e2f02 Sofia Papagiannaki
        if not instance.group.is_enabled:
1563 fc1e2f02 Sofia Papagiannaki
            return
1564 fc1e2f02 Sofia Papagiannaki
        extend([instance.person])
1565 fc1e2f02 Sofia Papagiannaki
    elif sender == AstakosUserQuota:
1566 fc1e2f02 Sofia Papagiannaki
        extend([instance.user])
1567 fc1e2f02 Sofia Papagiannaki
    elif sender == AstakosGroupQuota:
1568 fc1e2f02 Sofia Papagiannaki
        if not instance.group.is_enabled:
1569 fc1e2f02 Sofia Papagiannaki
            return
1570 fc1e2f02 Sofia Papagiannaki
        extend(instance.group.astakosuser_set.all())
1571 fc1e2f02 Sofia Papagiannaki
    elif sender == AstakosGroup:
1572 fc1e2f02 Sofia Papagiannaki
        if not instance.is_enabled:
1573 fc1e2f02 Sofia Papagiannaki
            return
1574 fc1e2f02 Sofia Papagiannaki
    quota_disturbed.send(sender=sender, users=users)
1575 fc1e2f02 Sofia Papagiannaki
1576 5ce3ce4f Sofia Papagiannaki
1577 fc1e2f02 Sofia Papagiannaki
def on_quota_disturbed(sender, users, **kwargs):
1578 c0b26605 Sofia Papagiannaki
#     print '>>>', locals()
1579 fc1e2f02 Sofia Papagiannaki
    if not users:
1580 fc1e2f02 Sofia Papagiannaki
        return
1581 fc1e2f02 Sofia Papagiannaki
    send_quota(users)
1582 bf0c6de5 Sofia Papagiannaki
1583 bf0c6de5 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
1584 f9aea9c8 Sofia Papagiannaki
    if not instance.auth_token:
1585 bf0c6de5 Sofia Papagiannaki
        instance.renew_token()
1586 bf0c6de5 Sofia Papagiannaki
1587 fc1e2f02 Sofia Papagiannaki
post_syncdb.connect(fix_superusers)
1588 fc1e2f02 Sofia Papagiannaki
post_save.connect(user_post_save, sender=User)
1589 fc1e2f02 Sofia Papagiannaki
pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
1590 fc1e2f02 Sofia Papagiannaki
post_save.connect(astakosuser_post_save, sender=AstakosUser)
1591 bd4f356c Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
1592 fc1e2f02 Sofia Papagiannaki
1593 fc1e2f02 Sofia Papagiannaki
quota_disturbed = Signal(providing_args=["users"])
1594 fc1e2f02 Sofia Papagiannaki
quota_disturbed.connect(on_quota_disturbed)
1595 fc1e2f02 Sofia Papagiannaki
1596 fc1e2f02 Sofia Papagiannaki
post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
1597 fc1e2f02 Sofia Papagiannaki
post_delete.connect(send_quota_disturbed, sender=Membership)
1598 fc1e2f02 Sofia Papagiannaki
post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
1599 fc1e2f02 Sofia Papagiannaki
post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
1600 fc1e2f02 Sofia Papagiannaki
post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1601 a4075f5a root
post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1602 c0b26605 Sofia Papagiannaki
1603 bf0c6de5 Sofia Papagiannaki
pre_save.connect(renew_token, sender=AstakosUser)
1604 2e90e3ec Kostas Papadimitriou
pre_save.connect(renew_token, sender=Service)