Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 7deaaa5f

History | View | Annotate | Download (73.7 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 3a72a5d4 Kostas Papadimitriou
import json
38 64cd4730 Antony Chazapis
39 d6fdc91e Georgios D. Tsoukalas
from time import asctime, sleep
40 64cd4730 Antony Chazapis
from datetime import datetime, timedelta
41 64cd4730 Antony Chazapis
from base64 import b64encode
42 ef20ea07 Sofia Papagiannaki
from urlparse import urlparse
43 d2633501 Kostas Papadimitriou
from urllib import quote
44 8f5a3a06 Sofia Papagiannaki
from random import randint
45 65360c65 Georgios D. Tsoukalas
from collections import defaultdict, namedtuple
46 64cd4730 Antony Chazapis
47 d6fdc91e Georgios D. Tsoukalas
from django.db import models, IntegrityError, transaction, connection
48 9a06d96f Olga Brani
from django.contrib.auth.models import User, UserManager, Group, Permission
49 0a569195 Sofia Papagiannaki
from django.utils.translation import ugettext as _
50 49790d9d Sofia Papagiannaki
from django.db import transaction
51 0a569195 Sofia Papagiannaki
from django.core.exceptions import ValidationError
52 c0b26605 Sofia Papagiannaki
from django.db.models.signals import (
53 73fbaec4 Sofia Papagiannaki
    pre_save, post_save, post_syncdb, post_delete)
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 c4b1a172 Kostas Papadimitriou
from django.utils.safestring import mark_safe
64 d2633501 Kostas Papadimitriou
from django.core.validators import email_re
65 73fbaec4 Sofia Papagiannaki
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
66 64cd4730 Antony Chazapis
67 e1a80257 Sofia Papagiannaki
from astakos.im.settings import (
68 e1a80257 Sofia Papagiannaki
    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
69 c7b82fdc Sofia Papagiannaki
    AUTH_TOKEN_DURATION, EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
70 8bc397e8 Sofia Papagiannaki
    SITENAME, SERVICES, MODERATION_ENABLED, RESOURCES_PRESENTATION_DATA)
71 c4b1a172 Kostas Papadimitriou
from astakos.im import settings as astakos_settings
72 c0b26605 Sofia Papagiannaki
from astakos.im.endpoints.qh import (
73 0514bcc7 Giorgos Korfiatis
    register_services, register_resources, qh_add_quota, QuotaLimits,
74 0514bcc7 Giorgos Korfiatis
    qh_query_serials, qh_ack_serials,
75 0514bcc7 Giorgos Korfiatis
    QuotaValues, add_quota_values)
76 d2633501 Kostas Papadimitriou
from astakos.im import auth_providers
77 9c01d9d1 Sofia Papagiannaki
78 ae497612 Olga Brani
import astakos.im.messages as astakos_messages
79 ee45eb81 Giorgos Korfiatis
from .managers import ForUpdateManager
80 64cd4730 Antony Chazapis
81 c11dc0ce Giorgos Korfiatis
from synnefo.lib.quotaholder.api import QH_PRACTICALLY_INFINITE
82 c11dc0ce Giorgos Korfiatis
from synnefo.lib.db.intdecimalfield import intDecimalField
83 c11dc0ce Giorgos Korfiatis
84 18ffbee1 Sofia Papagiannaki
logger = logging.getLogger(__name__)
85 18ffbee1 Sofia Papagiannaki
86 9a06d96f Olga Brani
DEFAULT_CONTENT_TYPE = None
87 e65c21df Georgios D. Tsoukalas
_content_type = None
88 e65c21df Georgios D. Tsoukalas
89 e65c21df Georgios D. Tsoukalas
def get_content_type():
90 e65c21df Georgios D. Tsoukalas
    global _content_type
91 e65c21df Georgios D. Tsoukalas
    if _content_type is not None:
92 e65c21df Georgios D. Tsoukalas
        return _content_type
93 e65c21df Georgios D. Tsoukalas
94 e65c21df Georgios D. Tsoukalas
    try:
95 e65c21df Georgios D. Tsoukalas
        content_type = ContentType.objects.get(app_label='im', model='astakosuser')
96 e65c21df Georgios D. Tsoukalas
    except:
97 e65c21df Georgios D. Tsoukalas
        content_type = DEFAULT_CONTENT_TYPE
98 e65c21df Georgios D. Tsoukalas
    _content_type = content_type
99 e65c21df Georgios D. Tsoukalas
    return content_type
100 9a06d96f Olga Brani
101 9a06d96f Olga Brani
RESOURCE_SEPARATOR = '.'
102 9a06d96f Olga Brani
103 9ee0c6a2 Sofia Papagiannaki
inf = float('inf')
104 5ce3ce4f Sofia Papagiannaki
105 8e45d6fd Sofia Papagiannaki
class Service(models.Model):
106 e1a80257 Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
107 8e45d6fd Sofia Papagiannaki
    url = models.FilePathField()
108 8e45d6fd Sofia Papagiannaki
    icon = models.FilePathField(blank=True)
109 e1a80257 Sofia Papagiannaki
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
110 8e45d6fd Sofia Papagiannaki
                                  null=True, blank=True)
111 e1a80257 Sofia Papagiannaki
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
112 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
113 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
114 7795764b Kostas Papadimitriou
    order = models.PositiveIntegerField(default=0)
115 7795764b Kostas Papadimitriou
116 7795764b Kostas Papadimitriou
    class Meta:
117 7795764b Kostas Papadimitriou
        ordering = ('order', )
118 5ce3ce4f Sofia Papagiannaki
119 08494423 Sofia Papagiannaki
    def renew_token(self, expiration_date=None):
120 8e45d6fd Sofia Papagiannaki
        md5 = hashlib.md5()
121 8e45d6fd Sofia Papagiannaki
        md5.update(self.name.encode('ascii', 'ignore'))
122 8e45d6fd Sofia Papagiannaki
        md5.update(self.url.encode('ascii', 'ignore'))
123 8e45d6fd Sofia Papagiannaki
        md5.update(asctime())
124 8e45d6fd Sofia Papagiannaki
125 8e45d6fd Sofia Papagiannaki
        self.auth_token = b64encode(md5.digest())
126 8e45d6fd Sofia Papagiannaki
        self.auth_token_created = datetime.now()
127 08494423 Sofia Papagiannaki
        if expiration_date:
128 08494423 Sofia Papagiannaki
            self.auth_token_expires = expiration_date
129 08494423 Sofia Papagiannaki
        else:
130 08494423 Sofia Papagiannaki
            self.auth_token_expires = None
131 5ce3ce4f Sofia Papagiannaki
132 8e45d6fd Sofia Papagiannaki
    def __str__(self):
133 8e45d6fd Sofia Papagiannaki
        return self.name
134 8e45d6fd Sofia Papagiannaki
135 9a06d96f Olga Brani
    @property
136 9a06d96f Olga Brani
    def resources(self):
137 9a06d96f Olga Brani
        return self.resource_set.all()
138 9a06d96f Olga Brani
139 9a06d96f Olga Brani
    @resources.setter
140 9a06d96f Olga Brani
    def resources(self, resources):
141 9a06d96f Olga Brani
        for s in resources:
142 9a06d96f Olga Brani
            self.resource_set.create(**s)
143 2e90e3ec Kostas Papadimitriou
144 5ce3ce4f Sofia Papagiannaki
145 8e45d6fd Sofia Papagiannaki
class ResourceMetadata(models.Model):
146 e1a80257 Sofia Papagiannaki
    key = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
147 e1a80257 Sofia Papagiannaki
    value = models.CharField(_('Value'), max_length=255)
148 8e45d6fd Sofia Papagiannaki
149 8bc397e8 Sofia Papagiannaki
_presentation_data = {}
150 8bc397e8 Sofia Papagiannaki
def get_presentation(resource):
151 8bc397e8 Sofia Papagiannaki
    global _presentation_data
152 8bc397e8 Sofia Papagiannaki
    presentation = _presentation_data.get(resource, {})
153 8bc397e8 Sofia Papagiannaki
    if not presentation:
154 8bc397e8 Sofia Papagiannaki
        resource_presentation = RESOURCES_PRESENTATION_DATA.get('resources', {})
155 8bc397e8 Sofia Papagiannaki
        presentation = resource_presentation.get(resource, {})
156 8bc397e8 Sofia Papagiannaki
        _presentation_data[resource] = presentation
157 3c7528c9 Kostas Papadimitriou
    return presentation
158 5ce3ce4f Sofia Papagiannaki
159 8e45d6fd Sofia Papagiannaki
class Resource(models.Model):
160 43e09b6c Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255)
161 8e45d6fd Sofia Papagiannaki
    meta = models.ManyToManyField(ResourceMetadata)
162 8e45d6fd Sofia Papagiannaki
    service = models.ForeignKey(Service)
163 e1a80257 Sofia Papagiannaki
    desc = models.TextField(_('Description'), null=True)
164 e1a80257 Sofia Papagiannaki
    unit = models.CharField(_('Name'), null=True, max_length=255)
165 e1a80257 Sofia Papagiannaki
    group = models.CharField(_('Group'), null=True, max_length=255)
166 0514bcc7 Giorgos Korfiatis
    uplimit = intDecimalField(default=0)
167 425e2e95 Sofia Papagiannaki
168 43e09b6c Sofia Papagiannaki
    class Meta:
169 0514bcc7 Giorgos Korfiatis
        unique_together = ("service", "name")
170 5ce3ce4f Sofia Papagiannaki
171 8e45d6fd Sofia Papagiannaki
    def __str__(self):
172 9a06d96f Olga Brani
        return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
173 8e45d6fd Sofia Papagiannaki
174 0514bcc7 Giorgos Korfiatis
    def full_name(self):
175 0514bcc7 Giorgos Korfiatis
        return str(self)
176 0514bcc7 Giorgos Korfiatis
177 8bc397e8 Sofia Papagiannaki
    @property
178 8bc397e8 Sofia Papagiannaki
    def help_text(self):
179 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('help_text', '')
180 3c7528c9 Kostas Papadimitriou
181 8bc397e8 Sofia Papagiannaki
    @property
182 8bc397e8 Sofia Papagiannaki
    def help_text_input_each(self):
183 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('help_text_input_each', '')
184 8bc397e8 Sofia Papagiannaki
185 8bc397e8 Sofia Papagiannaki
    @property
186 8bc397e8 Sofia Papagiannaki
    def is_abbreviation(self):
187 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('is_abbreviation', False)
188 8bc397e8 Sofia Papagiannaki
189 8bc397e8 Sofia Papagiannaki
    @property
190 8bc397e8 Sofia Papagiannaki
    def report_desc(self):
191 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('report_desc', '')
192 8bc397e8 Sofia Papagiannaki
193 8bc397e8 Sofia Papagiannaki
    @property
194 8bc397e8 Sofia Papagiannaki
    def placeholder(self):
195 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('placeholder', '')
196 8bc397e8 Sofia Papagiannaki
197 8bc397e8 Sofia Papagiannaki
    @property
198 8bc397e8 Sofia Papagiannaki
    def verbose_name(self):
199 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('verbose_name', '')
200 8bc397e8 Sofia Papagiannaki
201 8bc397e8 Sofia Papagiannaki
202 0514bcc7 Giorgos Korfiatis
def load_service_resources():
203 0514bcc7 Giorgos Korfiatis
    ss = []
204 0514bcc7 Giorgos Korfiatis
    rs = []
205 0514bcc7 Giorgos Korfiatis
    for service_name, data in SERVICES.iteritems():
206 0514bcc7 Giorgos Korfiatis
        url = data.get('url')
207 0514bcc7 Giorgos Korfiatis
        resources = data.get('resources') or ()
208 0514bcc7 Giorgos Korfiatis
        service, created = Service.objects.get_or_create(
209 0514bcc7 Giorgos Korfiatis
            name=service_name,
210 0514bcc7 Giorgos Korfiatis
            defaults={'url': url}
211 b4789608 Sofia Papagiannaki
        )
212 0514bcc7 Giorgos Korfiatis
        ss.append(service)
213 0514bcc7 Giorgos Korfiatis
214 0514bcc7 Giorgos Korfiatis
        for resource in resources:
215 0514bcc7 Giorgos Korfiatis
            try:
216 0514bcc7 Giorgos Korfiatis
                resource_name = resource.pop('name', '')
217 0514bcc7 Giorgos Korfiatis
                r, created = Resource.objects.get_or_create(
218 0514bcc7 Giorgos Korfiatis
                    service=service,
219 0514bcc7 Giorgos Korfiatis
                    name=resource_name,
220 0514bcc7 Giorgos Korfiatis
                    defaults=resource)
221 0514bcc7 Giorgos Korfiatis
                rs.append(r)
222 0514bcc7 Giorgos Korfiatis
223 0514bcc7 Giorgos Korfiatis
            except Exception, e:
224 0514bcc7 Giorgos Korfiatis
                print "Cannot create resource ", resource_name
225 0514bcc7 Giorgos Korfiatis
                continue
226 0514bcc7 Giorgos Korfiatis
    register_services(ss)
227 0514bcc7 Giorgos Korfiatis
    register_resources(rs)
228 0514bcc7 Giorgos Korfiatis
229 0514bcc7 Giorgos Korfiatis
def _quota_values(capacity):
230 0514bcc7 Giorgos Korfiatis
    return QuotaValues(
231 0514bcc7 Giorgos Korfiatis
        quantity = 0,
232 0514bcc7 Giorgos Korfiatis
        capacity = capacity,
233 0514bcc7 Giorgos Korfiatis
        import_limit = QH_PRACTICALLY_INFINITE,
234 0514bcc7 Giorgos Korfiatis
        export_limit = QH_PRACTICALLY_INFINITE)
235 0514bcc7 Giorgos Korfiatis
236 0514bcc7 Giorgos Korfiatis
def get_default_quota():
237 0514bcc7 Giorgos Korfiatis
    _DEFAULT_QUOTA = {}
238 0514bcc7 Giorgos Korfiatis
    resources = Resource.objects.all()
239 0514bcc7 Giorgos Korfiatis
    for resource in resources:
240 0514bcc7 Giorgos Korfiatis
        capacity = resource.uplimit
241 0514bcc7 Giorgos Korfiatis
        limits = _quota_values(capacity)
242 0514bcc7 Giorgos Korfiatis
        _DEFAULT_QUOTA[resource.full_name()] = limits
243 0514bcc7 Giorgos Korfiatis
244 0514bcc7 Giorgos Korfiatis
    return _DEFAULT_QUOTA
245 0514bcc7 Giorgos Korfiatis
246 0514bcc7 Giorgos Korfiatis
def get_resource_names():
247 0514bcc7 Giorgos Korfiatis
    _RESOURCE_NAMES = []
248 0514bcc7 Giorgos Korfiatis
    resources = Resource.objects.all()
249 0514bcc7 Giorgos Korfiatis
    _RESOURCE_NAMES = [resource.full_name() for resource in resources]
250 0514bcc7 Giorgos Korfiatis
    return _RESOURCE_NAMES
251 d2633501 Kostas Papadimitriou
252 43332a76 Kostas Papadimitriou
253 6b9a334b Sofia Papagiannaki
class AstakosUserManager(UserManager):
254 d2633501 Kostas Papadimitriou
255 d2633501 Kostas Papadimitriou
    def get_auth_provider_user(self, provider, **kwargs):
256 d2633501 Kostas Papadimitriou
        """
257 d2633501 Kostas Papadimitriou
        Retrieve AstakosUser instance associated with the specified third party
258 d2633501 Kostas Papadimitriou
        id.
259 d2633501 Kostas Papadimitriou
        """
260 d2633501 Kostas Papadimitriou
        kwargs = dict(map(lambda x: ('auth_providers__%s' % x[0], x[1]),
261 d2633501 Kostas Papadimitriou
                          kwargs.iteritems()))
262 d2633501 Kostas Papadimitriou
        return self.get(auth_providers__module=provider, **kwargs)
263 d2633501 Kostas Papadimitriou
264 c630fee6 Kostas Papadimitriou
    def get_by_email(self, email):
265 c630fee6 Kostas Papadimitriou
        return self.get(email=email)
266 c630fee6 Kostas Papadimitriou
267 e5966bd9 Kostas Papadimitriou
    def get_by_identifier(self, email_or_username, **kwargs):
268 e5966bd9 Kostas Papadimitriou
        try:
269 e5966bd9 Kostas Papadimitriou
            return self.get(email__iexact=email_or_username, **kwargs)
270 e5966bd9 Kostas Papadimitriou
        except AstakosUser.DoesNotExist:
271 e5966bd9 Kostas Papadimitriou
            return self.get(username__iexact=email_or_username, **kwargs)
272 e5966bd9 Kostas Papadimitriou
273 e5966bd9 Kostas Papadimitriou
    def user_exists(self, email_or_username, **kwargs):
274 e5966bd9 Kostas Papadimitriou
        qemail = Q(email__iexact=email_or_username)
275 e5966bd9 Kostas Papadimitriou
        qusername = Q(username__iexact=email_or_username)
276 43332a76 Kostas Papadimitriou
        qextra = Q(**kwargs)
277 43332a76 Kostas Papadimitriou
        return self.filter((qemail | qusername) & qextra).exists()
278 43332a76 Kostas Papadimitriou
279 43332a76 Kostas Papadimitriou
    def verified_user_exists(self, email_or_username):
280 43332a76 Kostas Papadimitriou
        return self.user_exists(email_or_username, email_verified=True)
281 43332a76 Kostas Papadimitriou
282 43332a76 Kostas Papadimitriou
    def verified(self):
283 43332a76 Kostas Papadimitriou
        return self.filter(email_verified=True)
284 e5966bd9 Kostas Papadimitriou
285 61a1b2d2 Georgios D. Tsoukalas
    def verified(self):
286 61a1b2d2 Georgios D. Tsoukalas
        return self.filter(email_verified=True)
287 e5966bd9 Kostas Papadimitriou
288 e5966bd9 Kostas Papadimitriou
289 0905ccd2 Sofia Papagiannaki
class AstakosUser(User):
290 890b0eaf Sofia Papagiannaki
    """
291 890b0eaf Sofia Papagiannaki
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
292 890b0eaf Sofia Papagiannaki
    """
293 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
294 d2633501 Kostas Papadimitriou
                                   null=True)
295 d2633501 Kostas Papadimitriou
296 d2633501 Kostas Papadimitriou
    # DEPRECATED FIELDS: provider, third_party_identifier moved in
297 d2633501 Kostas Papadimitriou
    #                    AstakosUserProvider model.
298 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True,
299 d2633501 Kostas Papadimitriou
                                null=True)
300 d2633501 Kostas Papadimitriou
    # ex. screen_name for twitter, eppn for shibboleth
301 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'),
302 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
303 d2633501 Kostas Papadimitriou
                                              blank=True)
304 d2633501 Kostas Papadimitriou
305 6c736ed7 Kostas Papadimitriou
306 64cd4730 Antony Chazapis
    #for invitations
307 92defad4 Sofia Papagiannaki
    user_level = DEFAULT_USER_LEVEL
308 e1a80257 Sofia Papagiannaki
    level = models.IntegerField(_('Inviter level'), default=user_level)
309 5ce3ce4f Sofia Papagiannaki
    invitations = models.IntegerField(
310 e1a80257 Sofia Papagiannaki
        _('Invitations left'), default=INVITATIONS_PER_LEVEL.get(user_level, 0))
311 6c736ed7 Kostas Papadimitriou
312 e1a80257 Sofia Papagiannaki
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
313 a96c495d Olga Brani
                                  null=True, blank=True, help_text = _( 'test' ))
314 e1a80257 Sofia Papagiannaki
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
315 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
316 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
317 6c736ed7 Kostas Papadimitriou
318 e1a80257 Sofia Papagiannaki
    updated = models.DateTimeField(_('Update date'))
319 e1a80257 Sofia Papagiannaki
    is_verified = models.BooleanField(_('Is verified?'), default=False)
320 6c736ed7 Kostas Papadimitriou
321 e1a80257 Sofia Papagiannaki
    email_verified = models.BooleanField(_('Email verified?'), default=False)
322 6c736ed7 Kostas Papadimitriou
323 e1a80257 Sofia Papagiannaki
    has_credits = models.BooleanField(_('Has credits?'), default=False)
324 5ce3ce4f Sofia Papagiannaki
    has_signed_terms = models.BooleanField(
325 e1a80257 Sofia Papagiannaki
        _('I agree with the terms'), default=False)
326 5ce3ce4f Sofia Papagiannaki
    date_signed_terms = models.DateTimeField(
327 e1a80257 Sofia Papagiannaki
        _('Signed terms date'), null=True, blank=True)
328 5ce3ce4f Sofia Papagiannaki
329 5ce3ce4f Sofia Papagiannaki
    activation_sent = models.DateTimeField(
330 e1a80257 Sofia Papagiannaki
        _('Activation sent data'), null=True, blank=True)
331 5ce3ce4f Sofia Papagiannaki
332 5ce3ce4f Sofia Papagiannaki
    policy = models.ManyToManyField(
333 5ce3ce4f Sofia Papagiannaki
        Resource, null=True, through='AstakosUserQuota')
334 5ce3ce4f Sofia Papagiannaki
335 836a0fb0 Kostas Papadimitriou
    uuid = models.CharField(max_length=255, null=True, blank=False, unique=True)
336 836a0fb0 Kostas Papadimitriou
337 18ffbee1 Sofia Papagiannaki
    __has_signed_terms = False
338 e1a80257 Sofia Papagiannaki
    disturbed_quota = models.BooleanField(_('Needs quotaholder syncing'),
339 9a06d96f Olga Brani
                                           default=False, db_index=True)
340 d2633501 Kostas Papadimitriou
341 d2633501 Kostas Papadimitriou
    objects = AstakosUserManager()
342 fbaa4f3c Kostas Papadimitriou
343 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
344 18ffbee1 Sofia Papagiannaki
        super(AstakosUser, self).__init__(*args, **kwargs)
345 18ffbee1 Sofia Papagiannaki
        self.__has_signed_terms = self.has_signed_terms
346 a3637508 Sofia Papagiannaki
        if not self.id:
347 18ffbee1 Sofia Papagiannaki
            self.is_active = False
348 5ce3ce4f Sofia Papagiannaki
349 0905ccd2 Sofia Papagiannaki
    @property
350 0905ccd2 Sofia Papagiannaki
    def realname(self):
351 5ce3ce4f Sofia Papagiannaki
        return '%s %s' % (self.first_name, self.last_name)
352 6c736ed7 Kostas Papadimitriou
353 0905ccd2 Sofia Papagiannaki
    @realname.setter
354 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
355 0905ccd2 Sofia Papagiannaki
        parts = value.split(' ')
356 0905ccd2 Sofia Papagiannaki
        if len(parts) == 2:
357 0905ccd2 Sofia Papagiannaki
            self.first_name = parts[0]
358 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[1]
359 0905ccd2 Sofia Papagiannaki
        else:
360 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[0]
361 6c736ed7 Kostas Papadimitriou
362 9a06d96f Olga Brani
    def add_permission(self, pname):
363 9a06d96f Olga Brani
        if self.has_perm(pname):
364 9a06d96f Olga Brani
            return
365 e65c21df Georgios D. Tsoukalas
        p, created = Permission.objects.get_or_create(
366 e65c21df Georgios D. Tsoukalas
                                    codename=pname,
367 e65c21df Georgios D. Tsoukalas
                                    name=pname.capitalize(),
368 e65c21df Georgios D. Tsoukalas
                                    content_type=get_content_type())
369 9a06d96f Olga Brani
        self.user_permissions.add(p)
370 9a06d96f Olga Brani
371 9a06d96f Olga Brani
    def remove_permission(self, pname):
372 9a06d96f Olga Brani
        if self.has_perm(pname):
373 9a06d96f Olga Brani
            return
374 9a06d96f Olga Brani
        p = Permission.objects.get(codename=pname,
375 e65c21df Georgios D. Tsoukalas
                                   content_type=get_content_type())
376 9a06d96f Olga Brani
        self.user_permissions.remove(p)
377 9a06d96f Olga Brani
378 64cd4730 Antony Chazapis
    @property
379 64cd4730 Antony Chazapis
    def invitation(self):
380 64cd4730 Antony Chazapis
        try:
381 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
382 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
383 64cd4730 Antony Chazapis
            return None
384 6c736ed7 Kostas Papadimitriou
385 0514bcc7 Giorgos Korfiatis
    def initial_quotas(self):
386 0514bcc7 Giorgos Korfiatis
        quotas = dict(get_default_quota())
387 0514bcc7 Giorgos Korfiatis
        for user_quota in self.policies:
388 0514bcc7 Giorgos Korfiatis
            resource = user_quota.resource.full_name()
389 0514bcc7 Giorgos Korfiatis
            quotas[resource] = user_quota.quota_values()
390 0514bcc7 Giorgos Korfiatis
        return quotas
391 0514bcc7 Giorgos Korfiatis
392 0514bcc7 Giorgos Korfiatis
    def all_quotas(self):
393 0514bcc7 Giorgos Korfiatis
        quotas = self.initial_quotas()
394 0514bcc7 Giorgos Korfiatis
395 0514bcc7 Giorgos Korfiatis
        objects = self.projectmembership_set.select_related()
396 0514bcc7 Giorgos Korfiatis
        memberships = objects.filter(is_active=True)
397 0514bcc7 Giorgos Korfiatis
        for membership in memberships:
398 0514bcc7 Giorgos Korfiatis
            application = membership.application
399 0514bcc7 Giorgos Korfiatis
400 0514bcc7 Giorgos Korfiatis
            grants = application.projectresourcegrant_set.all()
401 0514bcc7 Giorgos Korfiatis
            for grant in grants:
402 0514bcc7 Giorgos Korfiatis
                resource = grant.resource.full_name()
403 0514bcc7 Giorgos Korfiatis
                prev = quotas.get(resource, 0)
404 0514bcc7 Giorgos Korfiatis
                new = add_quota_values(prev, grant.member_quota_values())
405 0514bcc7 Giorgos Korfiatis
                quotas[resource] = new
406 0514bcc7 Giorgos Korfiatis
        return quotas
407 5ce3ce4f Sofia Papagiannaki
408 9a06d96f Olga Brani
    @property
409 9a06d96f Olga Brani
    def policies(self):
410 9a06d96f Olga Brani
        return self.astakosuserquota_set.select_related().all()
411 9a06d96f Olga Brani
412 9a06d96f Olga Brani
    @policies.setter
413 9a06d96f Olga Brani
    def policies(self, policies):
414 9a06d96f Olga Brani
        for p in policies:
415 6c997921 Sofia Papagiannaki
            p.setdefault('resource', '')
416 6c997921 Sofia Papagiannaki
            p.setdefault('capacity', 0)
417 6c997921 Sofia Papagiannaki
            p.setdefault('quantity', 0)
418 6c997921 Sofia Papagiannaki
            p.setdefault('import_limit', 0)
419 6c997921 Sofia Papagiannaki
            p.setdefault('export_limit', 0)
420 6c997921 Sofia Papagiannaki
            p.setdefault('update', True)
421 6c997921 Sofia Papagiannaki
            self.add_resource_policy(**p)
422 6c997921 Sofia Papagiannaki
423 6c997921 Sofia Papagiannaki
    def add_resource_policy(
424 6c997921 Sofia Papagiannaki
            self, resource, capacity, quantity, import_limit,
425 6c997921 Sofia Papagiannaki
            export_limit, update=True):
426 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
427 6c997921 Sofia Papagiannaki
        s, sep, r = resource.partition(RESOURCE_SEPARATOR)
428 6c997921 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=s, name=r)
429 9a06d96f Olga Brani
        if update:
430 6c997921 Sofia Papagiannaki
            AstakosUserQuota.objects.update_or_create(
431 6c997921 Sofia Papagiannaki
                user=self, resource=resource, defaults={
432 6c997921 Sofia Papagiannaki
                    'capacity':capacity,
433 6c997921 Sofia Papagiannaki
                    'quantity': quantity,
434 6c997921 Sofia Papagiannaki
                    'import_limit':import_limit,
435 6c997921 Sofia Papagiannaki
                    'export_limit':export_limit})
436 9a06d96f Olga Brani
        else:
437 9a06d96f Olga Brani
            q = self.astakosuserquota_set
438 6c997921 Sofia Papagiannaki
            q.create(
439 6c997921 Sofia Papagiannaki
                resource=resource, capacity=capacity, quanity=quantity,
440 6c997921 Sofia Papagiannaki
                import_limit=import_limit, export_limit=export_limit)
441 9a06d96f Olga Brani
442 6c997921 Sofia Papagiannaki
    def remove_resource_policy(self, service, resource):
443 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
444 9a06d96f Olga Brani
        resource = Resource.objects.get(service__name=service, name=resource)
445 9a06d96f Olga Brani
        q = self.policies.get(resource=resource).delete()
446 9a06d96f Olga Brani
447 836a0fb0 Kostas Papadimitriou
    def update_uuid(self):
448 836a0fb0 Kostas Papadimitriou
        while not self.uuid:
449 836a0fb0 Kostas Papadimitriou
            uuid_val =  str(uuid.uuid4())
450 836a0fb0 Kostas Papadimitriou
            try:
451 836a0fb0 Kostas Papadimitriou
                AstakosUser.objects.get(uuid=uuid_val)
452 836a0fb0 Kostas Papadimitriou
            except AstakosUser.DoesNotExist, e:
453 836a0fb0 Kostas Papadimitriou
                self.uuid = uuid_val
454 836a0fb0 Kostas Papadimitriou
        return self.uuid
455 836a0fb0 Kostas Papadimitriou
456 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
457 64cd4730 Antony Chazapis
        if update_timestamps:
458 64cd4730 Antony Chazapis
            if not self.id:
459 0905ccd2 Sofia Papagiannaki
                self.date_joined = datetime.now()
460 64cd4730 Antony Chazapis
            self.updated = datetime.now()
461 5ce3ce4f Sofia Papagiannaki
462 18ffbee1 Sofia Papagiannaki
        # update date_signed_terms if necessary
463 18ffbee1 Sofia Papagiannaki
        if self.__has_signed_terms != self.has_signed_terms:
464 18ffbee1 Sofia Papagiannaki
            self.date_signed_terms = datetime.now()
465 5ce3ce4f Sofia Papagiannaki
466 836a0fb0 Kostas Papadimitriou
        self.update_uuid()
467 836a0fb0 Kostas Papadimitriou
468 836a0fb0 Kostas Papadimitriou
        if self.username != self.email.lower():
469 9c01d9d1 Sofia Papagiannaki
            # set username
470 e5966bd9 Kostas Papadimitriou
            self.username = self.email.lower()
471 fbaa4f3c Kostas Papadimitriou
472 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
473 2e90e3ec Kostas Papadimitriou
474 bf0c6de5 Sofia Papagiannaki
    def renew_token(self, flush_sessions=False, current_key=None):
475 64cd4730 Antony Chazapis
        md5 = hashlib.md5()
476 8f8c43b2 Sofia Papagiannaki
        md5.update(settings.SECRET_KEY)
477 0905ccd2 Sofia Papagiannaki
        md5.update(self.username)
478 64cd4730 Antony Chazapis
        md5.update(self.realname.encode('ascii', 'ignore'))
479 64cd4730 Antony Chazapis
        md5.update(asctime())
480 2e90e3ec Kostas Papadimitriou
481 64cd4730 Antony Chazapis
        self.auth_token = b64encode(md5.digest())
482 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
483 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
484 92defad4 Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
485 bf0c6de5 Sofia Papagiannaki
        if flush_sessions:
486 bf0c6de5 Sofia Papagiannaki
            self.flush_sessions(current_key)
487 111f3da6 Sofia Papagiannaki
        msg = 'Token renewed for %s' % self.email
488 aab4d540 Sofia Papagiannaki
        logger.log(LOGGING_LEVEL, msg)
489 6c736ed7 Kostas Papadimitriou
490 bf0c6de5 Sofia Papagiannaki
    def flush_sessions(self, current_key=None):
491 bf0c6de5 Sofia Papagiannaki
        q = self.sessions
492 bf0c6de5 Sofia Papagiannaki
        if current_key:
493 bf0c6de5 Sofia Papagiannaki
            q = q.exclude(session_key=current_key)
494 2e90e3ec Kostas Papadimitriou
495 bf0c6de5 Sofia Papagiannaki
        keys = q.values_list('session_key', flat=True)
496 bf0c6de5 Sofia Papagiannaki
        if keys:
497 bf0c6de5 Sofia Papagiannaki
            msg = 'Flushing sessions: %s' % ','.join(keys)
498 c0b26605 Sofia Papagiannaki
            logger.log(LOGGING_LEVEL, msg, [])
499 bf0c6de5 Sofia Papagiannaki
        engine = import_module(settings.SESSION_ENGINE)
500 bf0c6de5 Sofia Papagiannaki
        for k in keys:
501 bf0c6de5 Sofia Papagiannaki
            s = engine.SessionStore(k)
502 bf0c6de5 Sofia Papagiannaki
            s.flush()
503 bf0c6de5 Sofia Papagiannaki
504 64cd4730 Antony Chazapis
    def __unicode__(self):
505 3abf6c78 Sofia Papagiannaki
        return '%s (%s)' % (self.realname, self.email)
506 5ce3ce4f Sofia Papagiannaki
507 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
508 5ce3ce4f Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username=self.username)
509 789a5951 Sofia Papagiannaki
        q = q.filter(email__iexact=self.email)
510 0a569195 Sofia Papagiannaki
        if q.count() != 0:
511 0a569195 Sofia Papagiannaki
            return True
512 0a569195 Sofia Papagiannaki
        return False
513 5ce3ce4f Sofia Papagiannaki
514 34a76cdb Kostas Papadimitriou
    def email_change_is_pending(self):
515 34a76cdb Kostas Papadimitriou
        return self.emailchanges.count() > 0
516 34a76cdb Kostas Papadimitriou
517 0712a55c Kostas Papadimitriou
    def email_change_is_pending(self):
518 0712a55c Kostas Papadimitriou
        return self.emailchanges.count() > 0
519 0712a55c Kostas Papadimitriou
520 fcf90160 Sofia Papagiannaki
    @property
521 09e7393c Sofia Papagiannaki
    def signed_terms(self):
522 09e7393c Sofia Papagiannaki
        term = get_latest_terms()
523 09e7393c Sofia Papagiannaki
        if not term:
524 09e7393c Sofia Papagiannaki
            return True
525 09e7393c Sofia Papagiannaki
        if not self.has_signed_terms:
526 09e7393c Sofia Papagiannaki
            return False
527 09e7393c Sofia Papagiannaki
        if not self.date_signed_terms:
528 09e7393c Sofia Papagiannaki
            return False
529 09e7393c Sofia Papagiannaki
        if self.date_signed_terms < term.date:
530 09e7393c Sofia Papagiannaki
            self.has_signed_terms = False
531 f0f92965 Sofia Papagiannaki
            self.date_signed_terms = None
532 09e7393c Sofia Papagiannaki
            self.save()
533 09e7393c Sofia Papagiannaki
            return False
534 09e7393c Sofia Papagiannaki
        return True
535 09e7393c Sofia Papagiannaki
536 d2633501 Kostas Papadimitriou
    def set_invitations_level(self):
537 d2633501 Kostas Papadimitriou
        """
538 d2633501 Kostas Papadimitriou
        Update user invitation level
539 d2633501 Kostas Papadimitriou
        """
540 d2633501 Kostas Papadimitriou
        level = self.invitation.inviter.level + 1
541 d2633501 Kostas Papadimitriou
        self.level = level
542 d2633501 Kostas Papadimitriou
        self.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
543 d2633501 Kostas Papadimitriou
544 d2633501 Kostas Papadimitriou
    def can_login_with_auth_provider(self, provider):
545 d2633501 Kostas Papadimitriou
        if not self.has_auth_provider(provider):
546 d2633501 Kostas Papadimitriou
            return False
547 d2633501 Kostas Papadimitriou
        else:
548 d2633501 Kostas Papadimitriou
            return auth_providers.get_provider(provider).is_available_for_login()
549 d2633501 Kostas Papadimitriou
550 8eb36753 Kostas Papadimitriou
    def can_add_auth_provider(self, provider, include_unverified=False, **kwargs):
551 d2633501 Kostas Papadimitriou
        provider_settings = auth_providers.get_provider(provider)
552 222d8e52 Kostas Papadimitriou
553 222d8e52 Kostas Papadimitriou
        if not provider_settings.is_available_for_add():
554 d2633501 Kostas Papadimitriou
            return False
555 f432088a Kostas Papadimitriou
556 d2633501 Kostas Papadimitriou
        if self.has_auth_provider(provider) and \
557 d2633501 Kostas Papadimitriou
           provider_settings.one_per_user:
558 d2633501 Kostas Papadimitriou
            return False
559 f432088a Kostas Papadimitriou
560 c630fee6 Kostas Papadimitriou
        if 'provider_info' in kwargs:
561 c630fee6 Kostas Papadimitriou
            kwargs.pop('provider_info')
562 c630fee6 Kostas Papadimitriou
563 f432088a Kostas Papadimitriou
        if 'identifier' in kwargs:
564 f432088a Kostas Papadimitriou
            try:
565 f432088a Kostas Papadimitriou
                # provider with specified params already exist
566 8eb36753 Kostas Papadimitriou
                if not include_unverified:
567 8eb36753 Kostas Papadimitriou
                    kwargs['user__email_verified'] = True
568 f432088a Kostas Papadimitriou
                existing_user = AstakosUser.objects.get_auth_provider_user(provider,
569 f432088a Kostas Papadimitriou
                                                                   **kwargs)
570 f432088a Kostas Papadimitriou
            except AstakosUser.DoesNotExist:
571 f432088a Kostas Papadimitriou
                return True
572 f432088a Kostas Papadimitriou
            else:
573 f432088a Kostas Papadimitriou
                return False
574 f432088a Kostas Papadimitriou
575 d2633501 Kostas Papadimitriou
        return True
576 d2633501 Kostas Papadimitriou
577 63836eda Kostas Papadimitriou
    def can_remove_auth_provider(self, module):
578 63836eda Kostas Papadimitriou
        provider = auth_providers.get_provider(module)
579 63836eda Kostas Papadimitriou
        existing = self.get_active_auth_providers()
580 63836eda Kostas Papadimitriou
        existing_for_provider = self.get_active_auth_providers(module=module)
581 63836eda Kostas Papadimitriou
582 63836eda Kostas Papadimitriou
        if len(existing) <= 1:
583 63836eda Kostas Papadimitriou
            return False
584 63836eda Kostas Papadimitriou
585 63836eda Kostas Papadimitriou
        if len(existing_for_provider) == 1 and provider.is_required():
586 d2633501 Kostas Papadimitriou
            return False
587 63836eda Kostas Papadimitriou
588 d2633501 Kostas Papadimitriou
        return True
589 d2633501 Kostas Papadimitriou
590 d2633501 Kostas Papadimitriou
    def can_change_password(self):
591 d2633501 Kostas Papadimitriou
        return self.has_auth_provider('local', auth_backend='astakos')
592 d2633501 Kostas Papadimitriou
593 649f2d36 Kostas Papadimitriou
    def can_change_email(self):
594 649f2d36 Kostas Papadimitriou
        non_astakos_local = self.get_auth_providers().filter(module='local')
595 649f2d36 Kostas Papadimitriou
        non_astakos_local = non_astakos_local.exclude(auth_backend='astakos')
596 649f2d36 Kostas Papadimitriou
        return non_astakos_local.count() == 0
597 649f2d36 Kostas Papadimitriou
598 63836eda Kostas Papadimitriou
    def has_required_auth_providers(self):
599 63836eda Kostas Papadimitriou
        required = auth_providers.REQUIRED_PROVIDERS
600 63836eda Kostas Papadimitriou
        for provider in required:
601 63836eda Kostas Papadimitriou
            if not self.has_auth_provider(provider):
602 63836eda Kostas Papadimitriou
                return False
603 63836eda Kostas Papadimitriou
        return True
604 63836eda Kostas Papadimitriou
605 d2633501 Kostas Papadimitriou
    def has_auth_provider(self, provider, **kwargs):
606 4d0bdd3b Kostas Papadimitriou
        return bool(self.get_auth_providers().filter(module=provider,
607 d2633501 Kostas Papadimitriou
                                               **kwargs).count())
608 d2633501 Kostas Papadimitriou
609 d2633501 Kostas Papadimitriou
    def add_auth_provider(self, provider, **kwargs):
610 3a72a5d4 Kostas Papadimitriou
        info_data = ''
611 3a72a5d4 Kostas Papadimitriou
        if 'provider_info' in kwargs:
612 c630fee6 Kostas Papadimitriou
            info_data = kwargs.pop('provider_info')
613 c630fee6 Kostas Papadimitriou
            if isinstance(info_data, dict):
614 c630fee6 Kostas Papadimitriou
                info_data = json.dumps(info_data)
615 3a72a5d4 Kostas Papadimitriou
616 f432088a Kostas Papadimitriou
        if self.can_add_auth_provider(provider, **kwargs):
617 8eb36753 Kostas Papadimitriou
            if 'identifier' in kwargs:
618 8eb36753 Kostas Papadimitriou
                # clean up third party pending for activation users of the same
619 8eb36753 Kostas Papadimitriou
                # identifier
620 8eb36753 Kostas Papadimitriou
                AstakosUserAuthProvider.objects.remove_unverified_providers(provider,
621 564a2292 Kostas Papadimitriou
                                                                **kwargs)
622 3a72a5d4 Kostas Papadimitriou
            self.auth_providers.create(module=provider, active=True,
623 3a72a5d4 Kostas Papadimitriou
                                       info_data=info_data,
624 3a72a5d4 Kostas Papadimitriou
                                       **kwargs)
625 f432088a Kostas Papadimitriou
        else:
626 f432088a Kostas Papadimitriou
            raise Exception('Cannot add provider')
627 d2633501 Kostas Papadimitriou
628 d2633501 Kostas Papadimitriou
    def add_pending_auth_provider(self, pending):
629 d2633501 Kostas Papadimitriou
        """
630 d2633501 Kostas Papadimitriou
        Convert PendingThirdPartyUser object to AstakosUserAuthProvider entry for
631 d2633501 Kostas Papadimitriou
        the current user.
632 d2633501 Kostas Papadimitriou
        """
633 d2633501 Kostas Papadimitriou
        if not isinstance(pending, PendingThirdPartyUser):
634 d2633501 Kostas Papadimitriou
            pending = PendingThirdPartyUser.objects.get(token=pending)
635 d2633501 Kostas Papadimitriou
636 d2633501 Kostas Papadimitriou
        provider = self.add_auth_provider(pending.provider,
637 c630fee6 Kostas Papadimitriou
                               identifier=pending.third_party_identifier,
638 c630fee6 Kostas Papadimitriou
                                affiliation=pending.affiliation,
639 c630fee6 Kostas Papadimitriou
                                          provider_info=pending.info)
640 d2633501 Kostas Papadimitriou
641 fbaa4f3c Kostas Papadimitriou
        if email_re.match(pending.email or '') and pending.email != self.email:
642 d2633501 Kostas Papadimitriou
            self.additionalmail_set.get_or_create(email=pending.email)
643 d2633501 Kostas Papadimitriou
644 d2633501 Kostas Papadimitriou
        pending.delete()
645 d2633501 Kostas Papadimitriou
        return provider
646 d2633501 Kostas Papadimitriou
647 d2633501 Kostas Papadimitriou
    def remove_auth_provider(self, provider, **kwargs):
648 4d0bdd3b Kostas Papadimitriou
        self.get_auth_providers().get(module=provider, **kwargs).delete()
649 d2633501 Kostas Papadimitriou
650 d2633501 Kostas Papadimitriou
    # user urls
651 d2633501 Kostas Papadimitriou
    def get_resend_activation_url(self):
652 c630fee6 Kostas Papadimitriou
        return reverse('send_activation', kwargs={'user_id': self.pk})
653 c630fee6 Kostas Papadimitriou
654 c630fee6 Kostas Papadimitriou
    def get_provider_remove_url(self, module, **kwargs):
655 c630fee6 Kostas Papadimitriou
        return reverse('remove_auth_provider', kwargs={
656 4d0bdd3b Kostas Papadimitriou
            'pk': self.get_auth_providers().get(module=module, **kwargs).pk})
657 d2633501 Kostas Papadimitriou
658 d2633501 Kostas Papadimitriou
    def get_activation_url(self, nxt=False):
659 d2633501 Kostas Papadimitriou
        url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
660 d2633501 Kostas Papadimitriou
                                 quote(self.auth_token))
661 d2633501 Kostas Papadimitriou
        if nxt:
662 d2633501 Kostas Papadimitriou
            url += "&next=%s" % quote(nxt)
663 d2633501 Kostas Papadimitriou
        return url
664 d2633501 Kostas Papadimitriou
665 d2633501 Kostas Papadimitriou
    def get_password_reset_url(self, token_generator=default_token_generator):
666 d2633501 Kostas Papadimitriou
        return reverse('django.contrib.auth.views.password_reset_confirm',
667 d2633501 Kostas Papadimitriou
                          kwargs={'uidb36':int_to_base36(self.id),
668 d2633501 Kostas Papadimitriou
                                  'token':token_generator.make_token(self)})
669 d2633501 Kostas Papadimitriou
670 d2633501 Kostas Papadimitriou
    def get_auth_providers(self):
671 4d0bdd3b Kostas Papadimitriou
        return self.auth_providers
672 d2633501 Kostas Papadimitriou
673 d2633501 Kostas Papadimitriou
    def get_available_auth_providers(self):
674 d2633501 Kostas Papadimitriou
        """
675 d2633501 Kostas Papadimitriou
        Returns a list of providers available for user to connect to.
676 d2633501 Kostas Papadimitriou
        """
677 d2633501 Kostas Papadimitriou
        providers = []
678 d2633501 Kostas Papadimitriou
        for module, provider_settings in auth_providers.PROVIDERS.iteritems():
679 f432088a Kostas Papadimitriou
            if self.can_add_auth_provider(module):
680 d2633501 Kostas Papadimitriou
                providers.append(provider_settings(self))
681 d2633501 Kostas Papadimitriou
682 d2633501 Kostas Papadimitriou
        return providers
683 d2633501 Kostas Papadimitriou
684 63836eda Kostas Papadimitriou
    def get_active_auth_providers(self, **filters):
685 d2633501 Kostas Papadimitriou
        providers = []
686 4d0bdd3b Kostas Papadimitriou
        for provider in self.get_auth_providers().active(**filters):
687 d2633501 Kostas Papadimitriou
            if auth_providers.get_provider(provider.module).is_available_for_login():
688 d2633501 Kostas Papadimitriou
                providers.append(provider)
689 d2633501 Kostas Papadimitriou
        return providers
690 d2633501 Kostas Papadimitriou
691 b778b6fa Kostas Papadimitriou
    @property
692 b778b6fa Kostas Papadimitriou
    def auth_providers_display(self):
693 4d0bdd3b Kostas Papadimitriou
        return ",".join(map(lambda x:unicode(x), self.get_auth_providers().active()))
694 b778b6fa Kostas Papadimitriou
695 c4b1a172 Kostas Papadimitriou
    def get_inactive_message(self):
696 c4b1a172 Kostas Papadimitriou
        msg_extra = ''
697 c4b1a172 Kostas Papadimitriou
        message = ''
698 c4b1a172 Kostas Papadimitriou
        if self.activation_sent:
699 c4b1a172 Kostas Papadimitriou
            if self.email_verified:
700 c4b1a172 Kostas Papadimitriou
                message = _(astakos_messages.ACCOUNT_INACTIVE)
701 c4b1a172 Kostas Papadimitriou
            else:
702 c4b1a172 Kostas Papadimitriou
                message = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION)
703 fc655b6f Kostas Papadimitriou
                if astakos_settings.MODERATION_ENABLED:
704 c4b1a172 Kostas Papadimitriou
                    msg_extra = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP)
705 c4b1a172 Kostas Papadimitriou
                else:
706 c4b1a172 Kostas Papadimitriou
                    url = self.get_resend_activation_url()
707 c4b1a172 Kostas Papadimitriou
                    msg_extra = mark_safe(_(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP) + \
708 a15a19b2 Kostas Papadimitriou
                                u' ' + \
709 c4b1a172 Kostas Papadimitriou
                                _('<a href="%s">%s?</a>') % (url,
710 c4b1a172 Kostas Papadimitriou
                                _(astakos_messages.ACCOUNT_RESEND_ACTIVATION_PROMPT)))
711 c4b1a172 Kostas Papadimitriou
        else:
712 fc655b6f Kostas Papadimitriou
            if astakos_settings.MODERATION_ENABLED:
713 c4b1a172 Kostas Papadimitriou
                message = _(astakos_messages.ACCOUNT_PENDING_MODERATION)
714 c4b1a172 Kostas Papadimitriou
            else:
715 c4b1a172 Kostas Papadimitriou
                message = astakos_messages.ACCOUNT_PENDING_ACTIVATION
716 c4b1a172 Kostas Papadimitriou
                url = self.get_resend_activation_url()
717 c4b1a172 Kostas Papadimitriou
                msg_extra = mark_safe(_('<a href="%s">%s?</a>') % (url,
718 c4b1a172 Kostas Papadimitriou
                            _(astakos_messages.ACCOUNT_RESEND_ACTIVATION_PROMPT)))
719 c4b1a172 Kostas Papadimitriou
720 a15a19b2 Kostas Papadimitriou
        return mark_safe(message + u' '+ msg_extra)
721 c4b1a172 Kostas Papadimitriou
722 e87bbb41 Kostas Papadimitriou
    def owns_project(self, project):
723 db9a498c Kostas Papadimitriou
        return project.owner == self
724 e87bbb41 Kostas Papadimitriou
725 05617ab9 Kostas Papadimitriou
    def is_project_member(self, project_or_application):
726 05617ab9 Kostas Papadimitriou
        return self.get_status_in_project(project_or_application) in \
727 05617ab9 Kostas Papadimitriou
                                        ProjectMembership.ASSOCIATED_STATES
728 e87bbb41 Kostas Papadimitriou
729 05617ab9 Kostas Papadimitriou
    def is_project_accepted_member(self, project_or_application):
730 05617ab9 Kostas Papadimitriou
        return self.get_status_in_project(project_or_application) in \
731 05617ab9 Kostas Papadimitriou
                                            ProjectMembership.ACCEPTED_STATES
732 05617ab9 Kostas Papadimitriou
733 05617ab9 Kostas Papadimitriou
    def get_status_in_project(self, project_or_application):
734 05617ab9 Kostas Papadimitriou
        application = project_or_application
735 05617ab9 Kostas Papadimitriou
        if isinstance(project_or_application, Project):
736 05617ab9 Kostas Papadimitriou
            application = project_or_application.project
737 05617ab9 Kostas Papadimitriou
        return application.user_status(self)
738 480ce06b Sofia Papagiannaki
739 d2633501 Kostas Papadimitriou
740 d2633501 Kostas Papadimitriou
class AstakosUserAuthProviderManager(models.Manager):
741 d2633501 Kostas Papadimitriou
742 63836eda Kostas Papadimitriou
    def active(self, **filters):
743 63836eda Kostas Papadimitriou
        return self.filter(active=True, **filters)
744 d2633501 Kostas Papadimitriou
745 564a2292 Kostas Papadimitriou
    def remove_unverified_providers(self, provider, **filters):
746 564a2292 Kostas Papadimitriou
        try:
747 564a2292 Kostas Papadimitriou
            existing = self.filter(module=provider, user__email_verified=False, **filters)
748 564a2292 Kostas Papadimitriou
            for p in existing:
749 564a2292 Kostas Papadimitriou
                p.user.delete()
750 564a2292 Kostas Papadimitriou
        except:
751 564a2292 Kostas Papadimitriou
            pass
752 564a2292 Kostas Papadimitriou
753 564a2292 Kostas Papadimitriou
754 d2633501 Kostas Papadimitriou
755 d2633501 Kostas Papadimitriou
class AstakosUserAuthProvider(models.Model):
756 d2633501 Kostas Papadimitriou
    """
757 d2633501 Kostas Papadimitriou
    Available user authentication methods.
758 d2633501 Kostas Papadimitriou
    """
759 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
760 d2633501 Kostas Papadimitriou
                                   null=True, default=None)
761 d2633501 Kostas Papadimitriou
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
762 e1a80257 Sofia Papagiannaki
    module = models.CharField(_('Provider'), max_length=255, blank=False,
763 d2633501 Kostas Papadimitriou
                                default='local')
764 e1a80257 Sofia Papagiannaki
    identifier = models.CharField(_('Third-party identifier'),
765 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
766 d2633501 Kostas Papadimitriou
                                              blank=True)
767 d2633501 Kostas Papadimitriou
    active = models.BooleanField(default=True)
768 e1a80257 Sofia Papagiannaki
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
769 d2633501 Kostas Papadimitriou
                                   default='astakos')
770 3a72a5d4 Kostas Papadimitriou
    info_data = models.TextField(default="", null=True, blank=True)
771 c630fee6 Kostas Papadimitriou
    created = models.DateTimeField('Creation date', auto_now_add=True)
772 d2633501 Kostas Papadimitriou
773 d2633501 Kostas Papadimitriou
    objects = AstakosUserAuthProviderManager()
774 d2633501 Kostas Papadimitriou
775 d2633501 Kostas Papadimitriou
    class Meta:
776 d2633501 Kostas Papadimitriou
        unique_together = (('identifier', 'module', 'user'), )
777 c630fee6 Kostas Papadimitriou
        ordering = ('module', 'created')
778 d2633501 Kostas Papadimitriou
779 3a72a5d4 Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
780 3a72a5d4 Kostas Papadimitriou
        super(AstakosUserAuthProvider, self).__init__(*args, **kwargs)
781 3a72a5d4 Kostas Papadimitriou
        try:
782 3a72a5d4 Kostas Papadimitriou
            self.info = json.loads(self.info_data)
783 c630fee6 Kostas Papadimitriou
            if not self.info:
784 c630fee6 Kostas Papadimitriou
                self.info = {}
785 c630fee6 Kostas Papadimitriou
        except Exception, e:
786 3a72a5d4 Kostas Papadimitriou
            self.info = {}
787 c630fee6 Kostas Papadimitriou
788 3a72a5d4 Kostas Papadimitriou
        for key,value in self.info.iteritems():
789 3a72a5d4 Kostas Papadimitriou
            setattr(self, 'info_%s' % key, value)
790 3a72a5d4 Kostas Papadimitriou
791 d2633501 Kostas Papadimitriou
792 d2633501 Kostas Papadimitriou
    @property
793 d2633501 Kostas Papadimitriou
    def settings(self):
794 d2633501 Kostas Papadimitriou
        return auth_providers.get_provider(self.module)
795 d2633501 Kostas Papadimitriou
796 d2633501 Kostas Papadimitriou
    @property
797 d2633501 Kostas Papadimitriou
    def details_display(self):
798 c630fee6 Kostas Papadimitriou
        try:
799 c630fee6 Kostas Papadimitriou
          return self.settings.get_details_tpl_display % self.__dict__
800 c630fee6 Kostas Papadimitriou
        except:
801 c630fee6 Kostas Papadimitriou
          return ''
802 3a72a5d4 Kostas Papadimitriou
803 3a72a5d4 Kostas Papadimitriou
    @property
804 3a72a5d4 Kostas Papadimitriou
    def title_display(self):
805 3a72a5d4 Kostas Papadimitriou
        title_tpl = self.settings.get_title_display
806 3a72a5d4 Kostas Papadimitriou
        try:
807 3a72a5d4 Kostas Papadimitriou
            if self.settings.get_user_title_display:
808 3a72a5d4 Kostas Papadimitriou
                title_tpl = self.settings.get_user_title_display
809 3a72a5d4 Kostas Papadimitriou
        except Exception, e:
810 3a72a5d4 Kostas Papadimitriou
            pass
811 c630fee6 Kostas Papadimitriou
        try:
812 c630fee6 Kostas Papadimitriou
          return title_tpl % self.__dict__
813 c630fee6 Kostas Papadimitriou
        except:
814 c630fee6 Kostas Papadimitriou
          return self.settings.get_title_display % self.__dict__
815 d2633501 Kostas Papadimitriou
816 d2633501 Kostas Papadimitriou
    def can_remove(self):
817 d2633501 Kostas Papadimitriou
        return self.user.can_remove_auth_provider(self.module)
818 d2633501 Kostas Papadimitriou
819 d2633501 Kostas Papadimitriou
    def delete(self, *args, **kwargs):
820 d2633501 Kostas Papadimitriou
        ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs)
821 5156e663 Kostas Papadimitriou
        if self.module == 'local':
822 5156e663 Kostas Papadimitriou
            self.user.set_unusable_password()
823 5156e663 Kostas Papadimitriou
            self.user.save()
824 d2633501 Kostas Papadimitriou
        return ret
825 d2633501 Kostas Papadimitriou
826 f432088a Kostas Papadimitriou
    def __repr__(self):
827 f432088a Kostas Papadimitriou
        return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
828 f432088a Kostas Papadimitriou
829 b778b6fa Kostas Papadimitriou
    def __unicode__(self):
830 b778b6fa Kostas Papadimitriou
        if self.identifier:
831 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.identifier)
832 b778b6fa Kostas Papadimitriou
        if self.auth_backend:
833 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.auth_backend)
834 b778b6fa Kostas Papadimitriou
        return self.module
835 b778b6fa Kostas Papadimitriou
836 3a72a5d4 Kostas Papadimitriou
    def save(self, *args, **kwargs):
837 3a72a5d4 Kostas Papadimitriou
        self.info_data = json.dumps(self.info)
838 3a72a5d4 Kostas Papadimitriou
        return super(AstakosUserAuthProvider, self).save(*args, **kwargs)
839 b778b6fa Kostas Papadimitriou
840 d2633501 Kostas Papadimitriou
841 e1a80257 Sofia Papagiannaki
class ExtendedManager(models.Manager):
842 9a06d96f Olga Brani
    def _update_or_create(self, **kwargs):
843 9a06d96f Olga Brani
        assert kwargs, \
844 9a06d96f Olga Brani
            'update_or_create() must be passed at least one keyword argument'
845 9a06d96f Olga Brani
        obj, created = self.get_or_create(**kwargs)
846 9a06d96f Olga Brani
        defaults = kwargs.pop('defaults', {})
847 9a06d96f Olga Brani
        if created:
848 9a06d96f Olga Brani
            return obj, True, False
849 9a06d96f Olga Brani
        else:
850 9a06d96f Olga Brani
            try:
851 9a06d96f Olga Brani
                params = dict(
852 9a06d96f Olga Brani
                    [(k, v) for k, v in kwargs.items() if '__' not in k])
853 9a06d96f Olga Brani
                params.update(defaults)
854 9a06d96f Olga Brani
                for attr, val in params.items():
855 9a06d96f Olga Brani
                    if hasattr(obj, attr):
856 9a06d96f Olga Brani
                        setattr(obj, attr, val)
857 9a06d96f Olga Brani
                sid = transaction.savepoint()
858 9a06d96f Olga Brani
                obj.save(force_update=True)
859 9a06d96f Olga Brani
                transaction.savepoint_commit(sid)
860 9a06d96f Olga Brani
                return obj, False, True
861 9a06d96f Olga Brani
            except IntegrityError, e:
862 9a06d96f Olga Brani
                transaction.savepoint_rollback(sid)
863 9a06d96f Olga Brani
                try:
864 9a06d96f Olga Brani
                    return self.get(**kwargs), False, False
865 9a06d96f Olga Brani
                except self.model.DoesNotExist:
866 9a06d96f Olga Brani
                    raise e
867 9a06d96f Olga Brani
868 9a06d96f Olga Brani
    update_or_create = _update_or_create
869 5ce3ce4f Sofia Papagiannaki
870 8e45d6fd Sofia Papagiannaki
871 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
872 e1a80257 Sofia Papagiannaki
    objects = ExtendedManager()
873 9deadfcd Georgios D. Tsoukalas
    capacity = intDecimalField()
874 9deadfcd Georgios D. Tsoukalas
    quantity = intDecimalField(default=0)
875 9deadfcd Georgios D. Tsoukalas
    export_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
876 9deadfcd Georgios D. Tsoukalas
    import_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
877 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
878 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
879 5ce3ce4f Sofia Papagiannaki
880 8e45d6fd Sofia Papagiannaki
    class Meta:
881 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
882 09e7393c Sofia Papagiannaki
883 8989f446 Sofia Papagiannaki
    def quota_values(self):
884 0514bcc7 Giorgos Korfiatis
        return QuotaValues(
885 0514bcc7 Giorgos Korfiatis
            quantity = self.quantity,
886 0514bcc7 Giorgos Korfiatis
            capacity = self.capacity,
887 0514bcc7 Giorgos Korfiatis
            import_limit = self.import_limit,
888 0514bcc7 Giorgos Korfiatis
            export_limit = self.export_limit)
889 0514bcc7 Giorgos Korfiatis
890 5ce3ce4f Sofia Papagiannaki
891 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
892 270dd48d Sofia Papagiannaki
    """
893 270dd48d Sofia Papagiannaki
    Model for approval terms
894 270dd48d Sofia Papagiannaki
    """
895 6c736ed7 Kostas Papadimitriou
896 5ce3ce4f Sofia Papagiannaki
    date = models.DateTimeField(
897 3c638f72 Giorgos Korfiatis
        _('Issue date'), db_index=True, auto_now_add=True)
898 e1a80257 Sofia Papagiannaki
    location = models.CharField(_('Terms location'), max_length=255)
899 270dd48d Sofia Papagiannaki
900 5ce3ce4f Sofia Papagiannaki
901 64cd4730 Antony Chazapis
class Invitation(models.Model):
902 890b0eaf Sofia Papagiannaki
    """
903 890b0eaf Sofia Papagiannaki
    Model for registring invitations
904 890b0eaf Sofia Papagiannaki
    """
905 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
906 64cd4730 Antony Chazapis
                                null=True)
907 e1a80257 Sofia Papagiannaki
    realname = models.CharField(_('Real name'), max_length=255)
908 e1a80257 Sofia Papagiannaki
    username = models.CharField(_('Unique ID'), max_length=255, unique=True)
909 e1a80257 Sofia Papagiannaki
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
910 e1a80257 Sofia Papagiannaki
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
911 e1a80257 Sofia Papagiannaki
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
912 e1a80257 Sofia Papagiannaki
    consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True)
913 5ce3ce4f Sofia Papagiannaki
914 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
915 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
916 8f5a3a06 Sofia Papagiannaki
        if not self.id:
917 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
918 5ce3ce4f Sofia Papagiannaki
919 64cd4730 Antony Chazapis
    def consume(self):
920 64cd4730 Antony Chazapis
        self.is_consumed = True
921 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
922 64cd4730 Antony Chazapis
        self.save()
923 6c736ed7 Kostas Papadimitriou
924 64cd4730 Antony Chazapis
    def __unicode__(self):
925 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
926 9c01d9d1 Sofia Papagiannaki
927 49790d9d Sofia Papagiannaki
928 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
929 34a76cdb Kostas Papadimitriou
930 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
931 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
932 49790d9d Sofia Papagiannaki
        """
933 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
934 49790d9d Sofia Papagiannaki
        ``User`` if valid.
935 49790d9d Sofia Papagiannaki

936 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
937 49790d9d Sofia Papagiannaki
        after activating.
938 49790d9d Sofia Papagiannaki

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

941 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
942 49790d9d Sofia Papagiannaki
        return ``None``.
943 49790d9d Sofia Papagiannaki

944 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
945 49790d9d Sofia Papagiannaki

946 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
947 49790d9d Sofia Papagiannaki
        """
948 49790d9d Sofia Papagiannaki
        try:
949 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
950 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
951 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
952 49790d9d Sofia Papagiannaki
                email_change.delete()
953 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
954 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
955 49790d9d Sofia Papagiannaki
            try:
956 789a5951 Sofia Papagiannaki
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
957 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
958 49790d9d Sofia Papagiannaki
                pass
959 49790d9d Sofia Papagiannaki
            else:
960 73fbaec4 Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
961 49790d9d Sofia Papagiannaki
            # update user
962 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
963 34a76cdb Kostas Papadimitriou
            old_email = user.email
964 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
965 49790d9d Sofia Papagiannaki
            user.save()
966 49790d9d Sofia Papagiannaki
            email_change.delete()
967 34a76cdb Kostas Papadimitriou
            msg = "User %d changed email from %s to %s" % (user.pk, old_email,
968 34a76cdb Kostas Papadimitriou
                                                          user.email)
969 34a76cdb Kostas Papadimitriou
            logger.log(LOGGING_LEVEL, msg)
970 49790d9d Sofia Papagiannaki
            return user
971 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
972 73fbaec4 Sofia Papagiannaki
            raise ValueError(_('Invalid activation key.'))
973 49790d9d Sofia Papagiannaki
974 49790d9d Sofia Papagiannaki
975 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
976 73fbaec4 Sofia Papagiannaki
    new_email_address = models.EmailField(
977 73fbaec4 Sofia Papagiannaki
        _(u'new e-mail address'),
978 73fbaec4 Sofia Papagiannaki
        help_text=_('Your old email address will be used until you verify your new one.'))
979 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
980 34a76cdb Kostas Papadimitriou
        AstakosUser, unique=True, related_name='emailchanges')
981 3c638f72 Giorgos Korfiatis
    requested_at = models.DateTimeField(auto_now_add=True)
982 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
983 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
984 49790d9d Sofia Papagiannaki
985 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
986 49790d9d Sofia Papagiannaki
987 34a76cdb Kostas Papadimitriou
    def get_url(self):
988 34a76cdb Kostas Papadimitriou
        return reverse('email_change_confirm',
989 34a76cdb Kostas Papadimitriou
                      kwargs={'activation_key': self.activation_key})
990 34a76cdb Kostas Papadimitriou
991 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
992 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
993 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
994 ff9290ec Sofia Papagiannaki
995 6b03a847 Sofia Papagiannaki
996 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
997 ca828a10 Sofia Papagiannaki
    """
998 ca828a10 Sofia Papagiannaki
    Model for registring invitations
999 ca828a10 Sofia Papagiannaki
    """
1000 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
1001 1eec103a Sofia Papagiannaki
    email = models.EmailField()
1002 ca828a10 Sofia Papagiannaki
1003 5ce3ce4f Sofia Papagiannaki
1004 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
1005 fc1e2f02 Sofia Papagiannaki
    while True:
1006 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
1007 fc1e2f02 Sofia Papagiannaki
        try:
1008 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
1009 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
1010 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
1011 fc1e2f02 Sofia Papagiannaki
            return code
1012 fc1e2f02 Sofia Papagiannaki
1013 5ce3ce4f Sofia Papagiannaki
1014 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
1015 fc1e2f02 Sofia Papagiannaki
    try:
1016 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
1017 fc1e2f02 Sofia Papagiannaki
        return term
1018 fc1e2f02 Sofia Papagiannaki
    except IndexError:
1019 fc1e2f02 Sofia Papagiannaki
        pass
1020 fc1e2f02 Sofia Papagiannaki
    return None
1021 fc1e2f02 Sofia Papagiannaki
1022 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
1023 ef20ea07 Sofia Papagiannaki
    """
1024 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
1025 ef20ea07 Sofia Papagiannaki
    """
1026 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
1027 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
1028 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
1029 564a2292 Kostas Papadimitriou
    first_name = models.CharField(_('first name'), max_length=30, blank=True,
1030 564a2292 Kostas Papadimitriou
                                  null=True)
1031 564a2292 Kostas Papadimitriou
    last_name = models.CharField(_('last name'), max_length=30, blank=True,
1032 564a2292 Kostas Papadimitriou
                                 null=True)
1033 564a2292 Kostas Papadimitriou
    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
1034 564a2292 Kostas Papadimitriou
                                   null=True)
1035 ef20ea07 Sofia Papagiannaki
    username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
1036 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
1037 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
1038 c630fee6 Kostas Papadimitriou
    info = models.TextField(default="", null=True, blank=True)
1039 d2633501 Kostas Papadimitriou
1040 678b2236 Sofia Papagiannaki
    class Meta:
1041 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
1042 ef20ea07 Sofia Papagiannaki
1043 c630fee6 Kostas Papadimitriou
    def get_user_instance(self):
1044 c630fee6 Kostas Papadimitriou
        d = self.__dict__
1045 c630fee6 Kostas Papadimitriou
        d.pop('_state', None)
1046 c630fee6 Kostas Papadimitriou
        d.pop('id', None)
1047 c630fee6 Kostas Papadimitriou
        d.pop('token', None)
1048 c630fee6 Kostas Papadimitriou
        d.pop('created', None)
1049 c630fee6 Kostas Papadimitriou
        d.pop('info', None)
1050 c630fee6 Kostas Papadimitriou
        user = AstakosUser(**d)
1051 c630fee6 Kostas Papadimitriou
1052 c630fee6 Kostas Papadimitriou
        return user
1053 c630fee6 Kostas Papadimitriou
1054 ef20ea07 Sofia Papagiannaki
    @property
1055 ef20ea07 Sofia Papagiannaki
    def realname(self):
1056 ef20ea07 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
1057 ef20ea07 Sofia Papagiannaki
1058 ef20ea07 Sofia Papagiannaki
    @realname.setter
1059 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
1060 ef20ea07 Sofia Papagiannaki
        parts = value.split(' ')
1061 ef20ea07 Sofia Papagiannaki
        if len(parts) == 2:
1062 ef20ea07 Sofia Papagiannaki
            self.first_name = parts[0]
1063 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[1]
1064 ef20ea07 Sofia Papagiannaki
        else:
1065 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[0]
1066 2e90e3ec Kostas Papadimitriou
1067 ef20ea07 Sofia Papagiannaki
    def save(self, **kwargs):
1068 ef20ea07 Sofia Papagiannaki
        if not self.id:
1069 ef20ea07 Sofia Papagiannaki
            # set username
1070 ef20ea07 Sofia Papagiannaki
            while not self.username:
1071 ef20ea07 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
1072 ef20ea07 Sofia Papagiannaki
                try:
1073 ef20ea07 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
1074 ef20ea07 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
1075 ef20ea07 Sofia Papagiannaki
                    self.username = username
1076 ef20ea07 Sofia Papagiannaki
        super(PendingThirdPartyUser, self).save(**kwargs)
1077 ef20ea07 Sofia Papagiannaki
1078 d2633501 Kostas Papadimitriou
    def generate_token(self):
1079 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1080 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1081 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1082 d2633501 Kostas Papadimitriou
1083 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1084 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1085 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1086 bf0c6de5 Sofia Papagiannaki
1087 fcc1e93f Sofia Papagiannaki
1088 fcc1e93f Sofia Papagiannaki
### PROJECTS ###
1089 fcc1e93f Sofia Papagiannaki
################
1090 fcc1e93f Sofia Papagiannaki
1091 e546df49 Georgios D. Tsoukalas
def synced_model_metaclass(class_name, class_parents, class_attributes):
1092 65360c65 Georgios D. Tsoukalas
1093 e546df49 Georgios D. Tsoukalas
    new_attributes = {}
1094 e546df49 Georgios D. Tsoukalas
    sync_attributes = {}
1095 65360c65 Georgios D. Tsoukalas
1096 e546df49 Georgios D. Tsoukalas
    for name, value in class_attributes.iteritems():
1097 e546df49 Georgios D. Tsoukalas
        sync, underscore, rest = name.partition('_')
1098 e546df49 Georgios D. Tsoukalas
        if sync == 'sync' and underscore == '_':
1099 e546df49 Georgios D. Tsoukalas
            sync_attributes[rest] = value
1100 e546df49 Georgios D. Tsoukalas
        else:
1101 e546df49 Georgios D. Tsoukalas
            new_attributes[name] = value
1102 65360c65 Georgios D. Tsoukalas
1103 e546df49 Georgios D. Tsoukalas
    if 'prefix' not in sync_attributes:
1104 e546df49 Georgios D. Tsoukalas
        m = ("you did not specify a 'sync_prefix' attribute "
1105 e546df49 Georgios D. Tsoukalas
             "in class '%s'" % (class_name,))
1106 e546df49 Georgios D. Tsoukalas
        raise ValueError(m)
1107 65360c65 Georgios D. Tsoukalas
1108 e546df49 Georgios D. Tsoukalas
    prefix = sync_attributes.pop('prefix')
1109 e546df49 Georgios D. Tsoukalas
    class_name = sync_attributes.pop('classname', prefix + '_model')
1110 65360c65 Georgios D. Tsoukalas
1111 e546df49 Georgios D. Tsoukalas
    for name, value in sync_attributes.iteritems():
1112 e546df49 Georgios D. Tsoukalas
        newname = prefix + '_' + name
1113 e546df49 Georgios D. Tsoukalas
        if newname in new_attributes:
1114 e546df49 Georgios D. Tsoukalas
            m = ("class '%s' was specified with prefix '%s' "
1115 e546df49 Georgios D. Tsoukalas
                 "but it already has an attribute named '%s'"
1116 e546df49 Georgios D. Tsoukalas
                 % (class_name, prefix, newname))
1117 e546df49 Georgios D. Tsoukalas
            raise ValueError(m)
1118 65360c65 Georgios D. Tsoukalas
1119 e546df49 Georgios D. Tsoukalas
        new_attributes[newname] = value
1120 e546df49 Georgios D. Tsoukalas
1121 e546df49 Georgios D. Tsoukalas
    newclass = type(class_name, class_parents, new_attributes)
1122 e546df49 Georgios D. Tsoukalas
    return newclass
1123 e546df49 Georgios D. Tsoukalas
1124 e546df49 Georgios D. Tsoukalas
1125 e546df49 Georgios D. Tsoukalas
def make_synced(prefix='sync', name='SyncedState'):
1126 e546df49 Georgios D. Tsoukalas
1127 e546df49 Georgios D. Tsoukalas
    the_name = name
1128 e546df49 Georgios D. Tsoukalas
    the_prefix = prefix
1129 e546df49 Georgios D. Tsoukalas
1130 e546df49 Georgios D. Tsoukalas
    class SyncedState(models.Model):
1131 e546df49 Georgios D. Tsoukalas
1132 e546df49 Georgios D. Tsoukalas
        sync_classname      = the_name
1133 e546df49 Georgios D. Tsoukalas
        sync_prefix         = the_prefix
1134 e546df49 Georgios D. Tsoukalas
        __metaclass__       = synced_model_metaclass
1135 e546df49 Georgios D. Tsoukalas
1136 e546df49 Georgios D. Tsoukalas
        sync_new_state      = models.BigIntegerField(null=True)
1137 e546df49 Georgios D. Tsoukalas
        sync_synced_state   = models.BigIntegerField(null=True)
1138 e546df49 Georgios D. Tsoukalas
        STATUS_SYNCED       = 0
1139 e546df49 Georgios D. Tsoukalas
        STATUS_PENDING      = 1
1140 e546df49 Georgios D. Tsoukalas
        sync_status         = models.IntegerField(db_index=True)
1141 e546df49 Georgios D. Tsoukalas
1142 e546df49 Georgios D. Tsoukalas
        class Meta:
1143 e546df49 Georgios D. Tsoukalas
            abstract = True
1144 e546df49 Georgios D. Tsoukalas
1145 e546df49 Georgios D. Tsoukalas
        class NotSynced(Exception):
1146 e546df49 Georgios D. Tsoukalas
            pass
1147 e546df49 Georgios D. Tsoukalas
1148 e546df49 Georgios D. Tsoukalas
        def sync_init_state(self, state):
1149 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = state
1150 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = state
1151 65360c65 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1152 65360c65 Georgios D. Tsoukalas
1153 e546df49 Georgios D. Tsoukalas
        def sync_get_status(self):
1154 e546df49 Georgios D. Tsoukalas
            return self.sync_status
1155 65360c65 Georgios D. Tsoukalas
1156 e546df49 Georgios D. Tsoukalas
        def sync_set_status(self):
1157 e546df49 Georgios D. Tsoukalas
            if self.sync_new_state != self.sync_synced_state:
1158 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_PENDING
1159 e546df49 Georgios D. Tsoukalas
            else:
1160 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_SYNCED
1161 65360c65 Georgios D. Tsoukalas
1162 e546df49 Georgios D. Tsoukalas
        def sync_set_synced(self):
1163 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = self.sync_new_state
1164 e546df49 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1165 65360c65 Georgios D. Tsoukalas
1166 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_state(self):
1167 e546df49 Georgios D. Tsoukalas
            return self.sync_synced_state
1168 65360c65 Georgios D. Tsoukalas
1169 e546df49 Georgios D. Tsoukalas
        def sync_set_new_state(self, new_state):
1170 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = new_state
1171 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1172 65360c65 Georgios D. Tsoukalas
1173 e546df49 Georgios D. Tsoukalas
        def sync_get_new_state(self):
1174 e546df49 Georgios D. Tsoukalas
            return self.sync_new_state
1175 65360c65 Georgios D. Tsoukalas
1176 e546df49 Georgios D. Tsoukalas
        def sync_set_synced_state(self, synced_state):
1177 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = synced_state
1178 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1179 65360c65 Georgios D. Tsoukalas
1180 e546df49 Georgios D. Tsoukalas
        def sync_get_pending_objects(self):
1181 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_PENDING))
1182 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1183 65360c65 Georgios D. Tsoukalas
1184 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_objects(self):
1185 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_SYNCED))
1186 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1187 65360c65 Georgios D. Tsoukalas
1188 e546df49 Georgios D. Tsoukalas
        def sync_verify_get_synced_state(self):
1189 e546df49 Georgios D. Tsoukalas
            status = self.sync_get_status()
1190 e546df49 Georgios D. Tsoukalas
            state = self.sync_get_synced_state()
1191 e546df49 Georgios D. Tsoukalas
            verified = (status == self.STATUS_SYNCED)
1192 e546df49 Georgios D. Tsoukalas
            return state, verified
1193 e546df49 Georgios D. Tsoukalas
1194 e546df49 Georgios D. Tsoukalas
        def sync_is_synced(self):
1195 e546df49 Georgios D. Tsoukalas
            state, verified = self.sync_verify_get_synced_state()
1196 e546df49 Georgios D. Tsoukalas
            return verified
1197 e546df49 Georgios D. Tsoukalas
1198 e546df49 Georgios D. Tsoukalas
    return SyncedState
1199 e546df49 Georgios D. Tsoukalas
1200 e546df49 Georgios D. Tsoukalas
SyncedState = make_synced(prefix='sync', name='SyncedState')
1201 425e2e95 Sofia Papagiannaki
1202 425e2e95 Sofia Papagiannaki
1203 6dcf53eb Kostas Papadimitriou
class ProjectApplicationManager(ForUpdateManager):
1204 5550bcfb Kostas Papadimitriou
1205 05617ab9 Kostas Papadimitriou
    def user_visible_projects(self, *filters, **kw_filters):
1206 05617ab9 Kostas Papadimitriou
        return self.filter(Q(state=ProjectApplication.PENDING)|\
1207 05617ab9 Kostas Papadimitriou
                           Q(state=ProjectApplication.APPROVED))
1208 05617ab9 Kostas Papadimitriou
1209 a3530159 Georgios D. Tsoukalas
    def user_visible_by_chain(self, *filters, **kw_filters):
1210 a3530159 Georgios D. Tsoukalas
        Q_PENDING = Q(state=ProjectApplication.PENDING)
1211 a3530159 Georgios D. Tsoukalas
        Q_APPROVED = Q(state=ProjectApplication.APPROVED)
1212 a3530159 Georgios D. Tsoukalas
        pending = self.filter(Q_PENDING).values_list('chain')
1213 a3530159 Georgios D. Tsoukalas
        approved = self.filter(Q_APPROVED).values_list('chain')
1214 a3530159 Georgios D. Tsoukalas
        by_chain = dict(pending.annotate(models.Max('id')))
1215 a3530159 Georgios D. Tsoukalas
        by_chain.update(approved.annotate(models.Max('id')))
1216 a3530159 Georgios D. Tsoukalas
        return self.filter(id__in=by_chain.values())
1217 05617ab9 Kostas Papadimitriou
1218 05617ab9 Kostas Papadimitriou
    def user_accessible_projects(self, user):
1219 5550bcfb Kostas Papadimitriou
        """
1220 5550bcfb Kostas Papadimitriou
        Return projects accessed by specified user.
1221 5550bcfb Kostas Papadimitriou
        """
1222 05617ab9 Kostas Papadimitriou
        participates_filters = Q(owner=user) | Q(applicant=user) | \
1223 9b32e2fb Kostas Papadimitriou
                               Q(project__projectmembership__person=user)
1224 05617ab9 Kostas Papadimitriou
1225 a3530159 Georgios D. Tsoukalas
        return self.user_visible_by_chain(participates_filters).order_by('issue_date').distinct()
1226 5550bcfb Kostas Papadimitriou
1227 a5cef8d0 Kostas Papadimitriou
    def search_by_name(self, *search_strings):
1228 a5cef8d0 Kostas Papadimitriou
        q = Q()
1229 a5cef8d0 Kostas Papadimitriou
        for s in search_strings:
1230 a5cef8d0 Kostas Papadimitriou
            q = q | Q(name__icontains=s)
1231 a5cef8d0 Kostas Papadimitriou
        return self.filter(q)
1232 a5cef8d0 Kostas Papadimitriou
1233 a5cef8d0 Kostas Papadimitriou
1234 db9a498c Kostas Papadimitriou
USER_STATUS_DISPLAY = {
1235 db9a498c Kostas Papadimitriou
      0: _('Join requested'),
1236 bd9af366 Kostas Papadimitriou
      1: _('Accepted member'),
1237 bd9af366 Kostas Papadimitriou
     10: _('Suspended'),
1238 bd9af366 Kostas Papadimitriou
    100: _('Terminated'),
1239 bd9af366 Kostas Papadimitriou
    200: _('Removed'),
1240 db9a498c Kostas Papadimitriou
     -1: _('Not a member'),
1241 db9a498c Kostas Papadimitriou
}
1242 db9a498c Kostas Papadimitriou
1243 bd9af366 Kostas Papadimitriou
1244 a9ba418f Giorgos Korfiatis
class Chain(models.Model):
1245 a9ba418f Giorgos Korfiatis
    chain  =   models.AutoField(primary_key=True)
1246 a9ba418f Giorgos Korfiatis
1247 a9ba418f Giorgos Korfiatis
def new_chain():
1248 a9ba418f Giorgos Korfiatis
    c = Chain.objects.create()
1249 a9ba418f Giorgos Korfiatis
    chain = c.chain
1250 a9ba418f Giorgos Korfiatis
    c.delete()
1251 a9ba418f Giorgos Korfiatis
    return chain
1252 a9ba418f Giorgos Korfiatis
1253 a9ba418f Giorgos Korfiatis
1254 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1255 425e2e95 Sofia Papagiannaki
    applicant               =   models.ForeignKey(
1256 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1257 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_applied',
1258 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1259 d6fdc91e Georgios D. Tsoukalas
1260 d0e78bbe Giorgos Korfiatis
    PENDING     =    0
1261 d0e78bbe Giorgos Korfiatis
    APPROVED    =    1
1262 d0e78bbe Giorgos Korfiatis
    REPLACED    =    2
1263 d0e78bbe Giorgos Korfiatis
    DENIED      =    3
1264 3c638f72 Giorgos Korfiatis
    DISMISSED   =    4
1265 3c638f72 Giorgos Korfiatis
    CANCELLED   =    5
1266 d0e78bbe Giorgos Korfiatis
1267 d0e78bbe Giorgos Korfiatis
    state                   =   models.IntegerField(default=PENDING)
1268 d6fdc91e Georgios D. Tsoukalas
1269 425e2e95 Sofia Papagiannaki
    owner                   =   models.ForeignKey(
1270 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1271 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_owned',
1272 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1273 d6fdc91e Georgios D. Tsoukalas
1274 c4892cd2 Sofia Papagiannaki
    chain                   =   models.IntegerField()
1275 3c638f72 Giorgos Korfiatis
    precursor_application   =   models.ForeignKey('ProjectApplication',
1276 3c638f72 Giorgos Korfiatis
                                                  null=True,
1277 3c638f72 Giorgos Korfiatis
                                                  blank=True)
1278 425e2e95 Sofia Papagiannaki
1279 67980f56 Georgios D. Tsoukalas
    name                    =   models.CharField(max_length=80)
1280 67980f56 Georgios D. Tsoukalas
    homepage                =   models.URLField(max_length=255, null=True)
1281 67980f56 Georgios D. Tsoukalas
    description             =   models.TextField(null=True, blank=True)
1282 e729f165 Kostas Papadimitriou
    start_date              =   models.DateTimeField(null=True, blank=True)
1283 67980f56 Georgios D. Tsoukalas
    end_date                =   models.DateTimeField()
1284 272cf735 Sofia Papagiannaki
    member_join_policy      =   models.IntegerField()
1285 272cf735 Sofia Papagiannaki
    member_leave_policy     =   models.IntegerField()
1286 67980f56 Georgios D. Tsoukalas
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1287 425e2e95 Sofia Papagiannaki
    resource_grants         =   models.ManyToManyField(
1288 425e2e95 Sofia Papagiannaki
                                    Resource,
1289 425e2e95 Sofia Papagiannaki
                                    null=True,
1290 425e2e95 Sofia Papagiannaki
                                    blank=True,
1291 d6fdc91e Georgios D. Tsoukalas
                                    through='ProjectResourceGrant')
1292 425e2e95 Sofia Papagiannaki
    comments                =   models.TextField(null=True, blank=True)
1293 3c638f72 Giorgos Korfiatis
    issue_date              =   models.DateTimeField(auto_now_add=True)
1294 3c638f72 Giorgos Korfiatis
    response_date           =   models.DateTimeField(null=True, blank=True)
1295 6dcf53eb Kostas Papadimitriou
1296 5550bcfb Kostas Papadimitriou
    objects                 =   ProjectApplicationManager()
1297 7729e9cc Giorgos Korfiatis
1298 c4892cd2 Sofia Papagiannaki
    class Meta:
1299 c4892cd2 Sofia Papagiannaki
        unique_together = ("chain", "id")
1300 c4892cd2 Sofia Papagiannaki
1301 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1302 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1303 f3a45fc6 Kostas Papadimitriou
1304 d0e78bbe Giorgos Korfiatis
    # TODO: Move to a more suitable place
1305 d0e78bbe Giorgos Korfiatis
    PROJECT_STATE_DISPLAY = {
1306 3c638f72 Giorgos Korfiatis
        PENDING  : _('Pending review'),
1307 3c638f72 Giorgos Korfiatis
        APPROVED : _('Active'),
1308 3c638f72 Giorgos Korfiatis
        REPLACED : _('Replaced'),
1309 3c638f72 Giorgos Korfiatis
        DENIED   : _('Denied'),
1310 3c638f72 Giorgos Korfiatis
        DISMISSED: _('Dismissed'),
1311 3c638f72 Giorgos Korfiatis
        CANCELLED: _('Cancelled')
1312 bd9af366 Kostas Papadimitriou
    }
1313 d0e78bbe Giorgos Korfiatis
1314 a3530159 Georgios D. Tsoukalas
    def get_project(self):
1315 a3530159 Georgios D. Tsoukalas
        try:
1316 a3530159 Georgios D. Tsoukalas
            project = Project.objects.get(id=self.chain, state=Project.APPROVED)
1317 a3530159 Georgios D. Tsoukalas
            return Project
1318 a3530159 Georgios D. Tsoukalas
        except Project.DoesNotExist, e:
1319 a3530159 Georgios D. Tsoukalas
            return None
1320 a3530159 Georgios D. Tsoukalas
1321 db9a498c Kostas Papadimitriou
    def state_display(self):
1322 d0e78bbe Giorgos Korfiatis
        return self.PROJECT_STATE_DISPLAY.get(self.state, _('Unknown'))
1323 db9a498c Kostas Papadimitriou
1324 a7aba804 Sofia Papagiannaki
    def add_resource_policy(self, service, resource, uplimit):
1325 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1326 a7aba804 Sofia Papagiannaki
        q = self.projectresourcegrant_set
1327 e1a80257 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=service, name=resource)
1328 a7aba804 Sofia Papagiannaki
        q.create(resource=resource, member_capacity=uplimit)
1329 e1a80257 Sofia Papagiannaki
1330 e87bbb41 Kostas Papadimitriou
    def user_status(self, user):
1331 db9a498c Kostas Papadimitriou
        try:
1332 05617ab9 Kostas Papadimitriou
            project = self.get_project()
1333 05617ab9 Kostas Papadimitriou
            if not project:
1334 05617ab9 Kostas Papadimitriou
                return -1
1335 bd9af366 Kostas Papadimitriou
            membership = project.projectmembership_set
1336 05617ab9 Kostas Papadimitriou
            membership = membership.exclude(state=ProjectMembership.REMOVED)
1337 bd9af366 Kostas Papadimitriou
            membership = membership.get(person=user)
1338 db9a498c Kostas Papadimitriou
            status = membership.state
1339 db9a498c Kostas Papadimitriou
        except ProjectMembership.DoesNotExist:
1340 f5991951 Kostas Papadimitriou
            return -1
1341 5550bcfb Kostas Papadimitriou
1342 5550bcfb Kostas Papadimitriou
        return status
1343 5550bcfb Kostas Papadimitriou
1344 db9a498c Kostas Papadimitriou
    def user_status_display(self, user):
1345 db9a498c Kostas Papadimitriou
        return USER_STATUS_DISPLAY.get(self.user_status(user), _('Unknown'))
1346 db9a498c Kostas Papadimitriou
1347 5550bcfb Kostas Papadimitriou
    def members_count(self):
1348 5550bcfb Kostas Papadimitriou
        return self.project.approved_memberships.count()
1349 5550bcfb Kostas Papadimitriou
1350 669cfe19 Olga Brani
    @property
1351 669cfe19 Olga Brani
    def grants(self):
1352 3d6dade7 Sofia Papagiannaki
        return self.projectresourcegrant_set.values(
1353 3d6dade7 Sofia Papagiannaki
            'member_capacity', 'resource__name', 'resource__service__name')
1354 5550bcfb Kostas Papadimitriou
1355 e1a80257 Sofia Papagiannaki
    @property
1356 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1357 f3342849 Sofia Papagiannaki
        return self.projectresourcegrant_set.all()
1358 e1a80257 Sofia Papagiannaki
1359 e1a80257 Sofia Papagiannaki
    @resource_policies.setter
1360 e1a80257 Sofia Papagiannaki
    def resource_policies(self, policies):
1361 e1a80257 Sofia Papagiannaki
        for p in policies:
1362 e1a80257 Sofia Papagiannaki
            service = p.get('service', None)
1363 e1a80257 Sofia Papagiannaki
            resource = p.get('resource', None)
1364 e1a80257 Sofia Papagiannaki
            uplimit = p.get('uplimit', 0)
1365 a7aba804 Sofia Papagiannaki
            self.add_resource_policy(service, resource, uplimit)
1366 425e2e95 Sofia Papagiannaki
1367 ccab6eb5 Sofia Papagiannaki
    @property
1368 ccab6eb5 Sofia Papagiannaki
    def follower(self):
1369 ccab6eb5 Sofia Papagiannaki
        try:
1370 ccab6eb5 Sofia Papagiannaki
            return ProjectApplication.objects.get(precursor_application=self)
1371 ccab6eb5 Sofia Papagiannaki
        except ProjectApplication.DoesNotExist:
1372 ccab6eb5 Sofia Papagiannaki
            return
1373 ccab6eb5 Sofia Papagiannaki
1374 9b32e2fb Kostas Papadimitriou
    def followers(self):
1375 efc58b65 Kostas Papadimitriou
        followers = self.chained_applications()
1376 efc58b65 Kostas Papadimitriou
        followers = followers.exclude(id=self.pk).filter(state=self.PENDING)
1377 efc58b65 Kostas Papadimitriou
        followers = followers.order_by('id')
1378 efc58b65 Kostas Papadimitriou
        return followers
1379 ece3b66e Giorgos Korfiatis
1380 9b32e2fb Kostas Papadimitriou
    def last_follower(self):
1381 9b32e2fb Kostas Papadimitriou
        try:
1382 efc58b65 Kostas Papadimitriou
            return self.followers().order_by('-id')[0]
1383 9b32e2fb Kostas Papadimitriou
        except IndexError:
1384 05617ab9 Kostas Papadimitriou
            return None
1385 05617ab9 Kostas Papadimitriou
1386 efc58b65 Kostas Papadimitriou
    def is_modification(self):
1387 efc58b65 Kostas Papadimitriou
        parents = self.chained_applications().filter(id__lt=self.id)
1388 efc58b65 Kostas Papadimitriou
        parents = parents.filter(state__in=[self.APPROVED])
1389 efc58b65 Kostas Papadimitriou
        return parents.count() > 0
1390 efc58b65 Kostas Papadimitriou
1391 efc58b65 Kostas Papadimitriou
    def chained_applications(self):
1392 efc58b65 Kostas Papadimitriou
        return ProjectApplication.objects.filter(chain=self.chain)
1393 efc58b65 Kostas Papadimitriou
1394 05617ab9 Kostas Papadimitriou
    def has_pending_modifications(self):
1395 05617ab9 Kostas Papadimitriou
        return bool(self.last_follower())
1396 05617ab9 Kostas Papadimitriou
1397 05617ab9 Kostas Papadimitriou
    def get_project(self):
1398 05617ab9 Kostas Papadimitriou
        try:
1399 05617ab9 Kostas Papadimitriou
            return Project.objects.get(id=self.chain)
1400 05617ab9 Kostas Papadimitriou
        except Project.DoesNotExist:
1401 9b32e2fb Kostas Papadimitriou
            return None
1402 4f22664f Georgios D. Tsoukalas
1403 b6fe8bb8 Giorgos Korfiatis
    def _get_project_for_update(self):
1404 a9ba418f Giorgos Korfiatis
        try:
1405 a9ba418f Giorgos Korfiatis
            objects = Project.objects.select_for_update()
1406 a9ba418f Giorgos Korfiatis
            project = objects.get(id=self.chain)
1407 a9ba418f Giorgos Korfiatis
            return project
1408 a9ba418f Giorgos Korfiatis
        except Project.DoesNotExist:
1409 a9ba418f Giorgos Korfiatis
            return None
1410 4f22664f Georgios D. Tsoukalas
1411 3c638f72 Giorgos Korfiatis
    def cancel(self):
1412 3c638f72 Giorgos Korfiatis
        if self.state != self.PENDING:
1413 3c638f72 Giorgos Korfiatis
            m = _("cannot cancel: application '%s' in state '%s'") % (
1414 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1415 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1416 3c638f72 Giorgos Korfiatis
1417 3c638f72 Giorgos Korfiatis
        self.state = self.CANCELLED
1418 3c638f72 Giorgos Korfiatis
        self.save()
1419 3c638f72 Giorgos Korfiatis
1420 3c638f72 Giorgos Korfiatis
    def dismiss(self):
1421 3c638f72 Giorgos Korfiatis
        if self.state != self.DENIED:
1422 3c638f72 Giorgos Korfiatis
            m = _("cannot dismiss: application '%s' in state '%s'") % (
1423 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1424 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1425 3c638f72 Giorgos Korfiatis
1426 3c638f72 Giorgos Korfiatis
        self.state = self.DISMISSED
1427 3c638f72 Giorgos Korfiatis
        self.save()
1428 3c638f72 Giorgos Korfiatis
1429 19eb3ee6 Giorgos Korfiatis
    def deny(self):
1430 19eb3ee6 Giorgos Korfiatis
        if self.state != self.PENDING:
1431 19eb3ee6 Giorgos Korfiatis
            m = _("cannot deny: application '%s' in state '%s'") % (
1432 19eb3ee6 Giorgos Korfiatis
                    self.id, self.state)
1433 19eb3ee6 Giorgos Korfiatis
            raise AssertionError(m)
1434 19eb3ee6 Giorgos Korfiatis
1435 19eb3ee6 Giorgos Korfiatis
        self.state = self.DENIED
1436 3c638f72 Giorgos Korfiatis
        self.response_date = datetime.now()
1437 19eb3ee6 Giorgos Korfiatis
        self.save()
1438 19eb3ee6 Giorgos Korfiatis
1439 ccab6eb5 Sofia Papagiannaki
    def approve(self, approval_user=None):
1440 ccab6eb5 Sofia Papagiannaki
        """
1441 ccab6eb5 Sofia Papagiannaki
        If approval_user then during owner membership acceptance
1442 ccab6eb5 Sofia Papagiannaki
        it is checked whether the request_user is eligible.
1443 262e04c6 Giorgos Korfiatis

1444 2553efae Sofia Papagiannaki
        Raises:
1445 b8f05f8d Sofia Papagiannaki
            PermissionDenied
1446 ccab6eb5 Sofia Papagiannaki
        """
1447 4f22664f Georgios D. Tsoukalas
1448 4f22664f Georgios D. Tsoukalas
        if not transaction.is_managed():
1449 4f22664f Georgios D. Tsoukalas
            raise AssertionError("NOPE")
1450 4f22664f Georgios D. Tsoukalas
1451 73fbaec4 Sofia Papagiannaki
        new_project_name = self.name
1452 85d444db Sofia Papagiannaki
        if self.state != self.PENDING:
1453 65360c65 Georgios D. Tsoukalas
            m = _("cannot approve: project '%s' in state '%s'") % (
1454 65360c65 Georgios D. Tsoukalas
                    new_project_name, self.state)
1455 4f22664f Georgios D. Tsoukalas
            raise PermissionDenied(m) # invalid argument
1456 262e04c6 Giorgos Korfiatis
1457 fdafae27 Giorgos Korfiatis
        now = datetime.now()
1458 b6fe8bb8 Giorgos Korfiatis
        project = self._get_project_for_update()
1459 3cc9637a Giorgos Korfiatis
1460 99463445 Giorgos Korfiatis
        try:
1461 99463445 Giorgos Korfiatis
            q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
1462 99463445 Giorgos Korfiatis
            conflicting_project = Project.objects.get(q)
1463 99463445 Giorgos Korfiatis
            if (conflicting_project != project):
1464 3cc9637a Giorgos Korfiatis
                m = (_("cannot approve: project with name '%s' "
1465 3cc9637a Giorgos Korfiatis
                       "already exists (serial: %s)") % (
1466 3cc9637a Giorgos Korfiatis
                        new_project_name, conflicting_project.id))
1467 3cc9637a Giorgos Korfiatis
                raise PermissionDenied(m) # invalid argument
1468 99463445 Giorgos Korfiatis
        except Project.DoesNotExist:
1469 99463445 Giorgos Korfiatis
            pass
1470 3cc9637a Giorgos Korfiatis
1471 4bf02ea5 Giorgos Korfiatis
        new_project = False
1472 4f22664f Georgios D. Tsoukalas
        if project is None:
1473 4bf02ea5 Giorgos Korfiatis
            new_project = True
1474 3c638f72 Giorgos Korfiatis
            project = Project(id=self.chain)
1475 fdafae27 Giorgos Korfiatis
1476 3cc9637a Giorgos Korfiatis
        project.name = new_project_name
1477 ee45eb81 Giorgos Korfiatis
        project.application = self
1478 4bf02ea5 Giorgos Korfiatis
        project.last_approval_date = now
1479 a769d7ba Sofia Papagiannaki
        if not new_project:
1480 a769d7ba Sofia Papagiannaki
            project.is_modified = True
1481 4bf02ea5 Giorgos Korfiatis
1482 a769d7ba Sofia Papagiannaki
        project.save()
1483 425e2e95 Sofia Papagiannaki
1484 85d444db Sofia Papagiannaki
        self.state = self.APPROVED
1485 3c638f72 Giorgos Korfiatis
        self.response_date = now
1486 bfe23b13 Sofia Papagiannaki
        self.save()
1487 262e04c6 Giorgos Korfiatis
1488 15ca2bea Giorgos Korfiatis
def submit_application(**kw):
1489 15ca2bea Giorgos Korfiatis
1490 15ca2bea Giorgos Korfiatis
    resource_policies = kw.pop('resource_policies', None)
1491 15ca2bea Giorgos Korfiatis
    application = ProjectApplication(**kw)
1492 15ca2bea Giorgos Korfiatis
1493 15ca2bea Giorgos Korfiatis
    precursor = kw['precursor_application']
1494 15ca2bea Giorgos Korfiatis
1495 0801358a Giorgos Korfiatis
    if precursor is None:
1496 a9ba418f Giorgos Korfiatis
        application.chain = new_chain()
1497 0801358a Giorgos Korfiatis
    else:
1498 0801358a Giorgos Korfiatis
        application.chain = precursor.chain
1499 0801358a Giorgos Korfiatis
        if precursor.state == ProjectApplication.PENDING:
1500 0801358a Giorgos Korfiatis
            precursor.state = ProjectApplication.REPLACED
1501 0801358a Giorgos Korfiatis
            precursor.save()
1502 15ca2bea Giorgos Korfiatis
1503 15ca2bea Giorgos Korfiatis
    application.save()
1504 15ca2bea Giorgos Korfiatis
    application.resource_policies = resource_policies
1505 15ca2bea Giorgos Korfiatis
    return application
1506 ccab6eb5 Sofia Papagiannaki
1507 73fbaec4 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1508 e1a80257 Sofia Papagiannaki
1509 425e2e95 Sofia Papagiannaki
    resource                =   models.ForeignKey(Resource)
1510 425e2e95 Sofia Papagiannaki
    project_application     =   models.ForeignKey(ProjectApplication,
1511 5200e864 Sofia Papagiannaki
                                                  null=True)
1512 c11dc0ce Giorgos Korfiatis
    project_capacity        =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1513 c11dc0ce Giorgos Korfiatis
    project_import_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1514 c11dc0ce Giorgos Korfiatis
    project_export_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1515 c11dc0ce Giorgos Korfiatis
    member_capacity         =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1516 c11dc0ce Giorgos Korfiatis
    member_import_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1517 c11dc0ce Giorgos Korfiatis
    member_export_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1518 73fbaec4 Sofia Papagiannaki
1519 73fbaec4 Sofia Papagiannaki
    objects = ExtendedManager()
1520 73fbaec4 Sofia Papagiannaki
1521 73fbaec4 Sofia Papagiannaki
    class Meta:
1522 73fbaec4 Sofia Papagiannaki
        unique_together = ("resource", "project_application")
1523 8327782d Sofia Papagiannaki
1524 0514bcc7 Giorgos Korfiatis
    def member_quota_values(self):
1525 0514bcc7 Giorgos Korfiatis
        return QuotaValues(
1526 0514bcc7 Giorgos Korfiatis
            quantity = 0,
1527 0514bcc7 Giorgos Korfiatis
            capacity = self.member_capacity,
1528 0514bcc7 Giorgos Korfiatis
            import_limit = self.member_import_limit,
1529 0514bcc7 Giorgos Korfiatis
            export_limit = self.member_export_limit)
1530 0514bcc7 Giorgos Korfiatis
1531 e546df49 Georgios D. Tsoukalas
1532 123be68a Giorgos Korfiatis
class ProjectManager(ForUpdateManager):
1533 123be68a Giorgos Korfiatis
1534 123be68a Giorgos Korfiatis
    def _q_terminated(self):
1535 b6fe8bb8 Giorgos Korfiatis
        return Q(state=Project.TERMINATED)
1536 db99f198 Giorgos Korfiatis
    def _q_suspended(self):
1537 db99f198 Giorgos Korfiatis
        return Q(state=Project.SUSPENDED)
1538 db99f198 Giorgos Korfiatis
    def _q_deactivated(self):
1539 db99f198 Giorgos Korfiatis
        return self._q_terminated() | self._q_suspended()
1540 123be68a Giorgos Korfiatis
1541 123be68a Giorgos Korfiatis
    def terminated_projects(self):
1542 123be68a Giorgos Korfiatis
        q = self._q_terminated()
1543 123be68a Giorgos Korfiatis
        return self.filter(q)
1544 123be68a Giorgos Korfiatis
1545 123be68a Giorgos Korfiatis
    def not_terminated_projects(self):
1546 123be68a Giorgos Korfiatis
        q = ~self._q_terminated()
1547 123be68a Giorgos Korfiatis
        return self.filter(q)
1548 123be68a Giorgos Korfiatis
1549 b6fe8bb8 Giorgos Korfiatis
    def terminating_projects(self):
1550 b6fe8bb8 Giorgos Korfiatis
        q = self._q_terminated() & Q(is_active=True)
1551 b6fe8bb8 Giorgos Korfiatis
        return self.filter(q)
1552 b6fe8bb8 Giorgos Korfiatis
1553 db99f198 Giorgos Korfiatis
    def deactivated_projects(self):
1554 db99f198 Giorgos Korfiatis
        q = self._q_deactivated()
1555 db99f198 Giorgos Korfiatis
        return self.filter(q)
1556 db99f198 Giorgos Korfiatis
1557 db99f198 Giorgos Korfiatis
    def deactivating_projects(self):
1558 db99f198 Giorgos Korfiatis
        q = self._q_deactivated() & Q(is_active=True)
1559 db99f198 Giorgos Korfiatis
        return self.filter(q)
1560 db99f198 Giorgos Korfiatis
1561 b6fe8bb8 Giorgos Korfiatis
    def modified_projects(self):
1562 b6fe8bb8 Giorgos Korfiatis
        return self.filter(is_modified=True)
1563 b6fe8bb8 Giorgos Korfiatis
1564 db99f198 Giorgos Korfiatis
    def reactivating_projects(self):
1565 db99f198 Giorgos Korfiatis
        return self.filter(state=Project.APPROVED, is_active=False)
1566 b6fe8bb8 Giorgos Korfiatis
1567 d6fdc91e Georgios D. Tsoukalas
class Project(models.Model):
1568 e546df49 Georgios D. Tsoukalas
1569 ee45eb81 Giorgos Korfiatis
    application                 =   models.OneToOneField(
1570 4f22664f Georgios D. Tsoukalas
                                            ProjectApplication,
1571 782d9118 Giorgos Korfiatis
                                            related_name='project')
1572 4f22664f Georgios D. Tsoukalas
    last_approval_date          =   models.DateTimeField(null=True)
1573 4f22664f Georgios D. Tsoukalas
1574 4f22664f Georgios D. Tsoukalas
    members                     =   models.ManyToManyField(
1575 4f22664f Georgios D. Tsoukalas
                                            AstakosUser,
1576 4f22664f Georgios D. Tsoukalas
                                            through='ProjectMembership')
1577 4f22664f Georgios D. Tsoukalas
1578 5b9e9530 Giorgos Korfiatis
    deactivation_reason         =   models.CharField(max_length=255, null=True)
1579 5b9e9530 Giorgos Korfiatis
    deactivation_date           =   models.DateTimeField(null=True)
1580 4f22664f Georgios D. Tsoukalas
1581 3c638f72 Giorgos Korfiatis
    creation_date               =   models.DateTimeField(auto_now_add=True)
1582 4f22664f Georgios D. Tsoukalas
    name                        =   models.CharField(
1583 4f22664f Georgios D. Tsoukalas
                                            max_length=80,
1584 4f22664f Georgios D. Tsoukalas
                                            db_index=True,
1585 4f22664f Georgios D. Tsoukalas
                                            unique=True)
1586 425e2e95 Sofia Papagiannaki
1587 b6fe8bb8 Giorgos Korfiatis
    APPROVED    = 1
1588 b6fe8bb8 Giorgos Korfiatis
    SUSPENDED   = 10
1589 b6fe8bb8 Giorgos Korfiatis
    TERMINATED  = 100
1590 5b9e9530 Giorgos Korfiatis
1591 b6fe8bb8 Giorgos Korfiatis
    is_modified                 =   models.BooleanField(default=False,
1592 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1593 b6fe8bb8 Giorgos Korfiatis
    is_active                   =   models.BooleanField(default=True,
1594 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1595 b6fe8bb8 Giorgos Korfiatis
    state                       =   models.IntegerField(default=APPROVED,
1596 123be68a Giorgos Korfiatis
                                                        db_index=True)
1597 123be68a Giorgos Korfiatis
1598 123be68a Giorgos Korfiatis
    objects     =   ProjectManager()
1599 7729e9cc Giorgos Korfiatis
1600 8c7b8bb8 Giorgos Korfiatis
    def __str__(self):
1601 8c7b8bb8 Giorgos Korfiatis
        return _("<project %s '%s'>") % (self.id, self.application.name)
1602 8c7b8bb8 Giorgos Korfiatis
1603 8c7b8bb8 Giorgos Korfiatis
    __repr__ = __str__
1604 8c7b8bb8 Giorgos Korfiatis
1605 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated(self, reason=None):
1606 b6fe8bb8 Giorgos Korfiatis
        if reason is not None:
1607 b6fe8bb8 Giorgos Korfiatis
            return self.state == reason
1608 425e2e95 Sofia Papagiannaki
1609 b6fe8bb8 Giorgos Korfiatis
        return self.state != self.APPROVED
1610 123be68a Giorgos Korfiatis
1611 123be68a Giorgos Korfiatis
    def is_deactivating(self, reason=None):
1612 b6fe8bb8 Giorgos Korfiatis
        if not self.is_active:
1613 b6fe8bb8 Giorgos Korfiatis
            return False
1614 123be68a Giorgos Korfiatis
1615 b6fe8bb8 Giorgos Korfiatis
        return self.is_deactivated(reason)
1616 123be68a Giorgos Korfiatis
1617 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated_strict(self, reason=None):
1618 b6fe8bb8 Giorgos Korfiatis
        if self.is_active:
1619 b6fe8bb8 Giorgos Korfiatis
            return False
1620 123be68a Giorgos Korfiatis
1621 b6fe8bb8 Giorgos Korfiatis
        return self.is_deactivated(reason)
1622 425e2e95 Sofia Papagiannaki
1623 123be68a Giorgos Korfiatis
    ### Deactivation calls
1624 425e2e95 Sofia Papagiannaki
1625 123be68a Giorgos Korfiatis
    def deactivate(self):
1626 b6fe8bb8 Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1627 b6fe8bb8 Giorgos Korfiatis
        self.is_active = False
1628 425e2e95 Sofia Papagiannaki
1629 db99f198 Giorgos Korfiatis
    def reactivate(self):
1630 db99f198 Giorgos Korfiatis
        self.deactivation_date = None
1631 db99f198 Giorgos Korfiatis
        self.is_active = True
1632 db99f198 Giorgos Korfiatis
1633 123be68a Giorgos Korfiatis
    def terminate(self):
1634 123be68a Giorgos Korfiatis
        self.deactivation_reason = 'TERMINATED'
1635 b6fe8bb8 Giorgos Korfiatis
        self.state = self.TERMINATED
1636 123be68a Giorgos Korfiatis
        self.save()
1637 8aed306c Giorgos Korfiatis
1638 db99f198 Giorgos Korfiatis
    def suspend(self):
1639 db99f198 Giorgos Korfiatis
        self.deactivation_reason = 'SUSPENDED'
1640 db99f198 Giorgos Korfiatis
        self.state = self.SUSPENDED
1641 db99f198 Giorgos Korfiatis
        self.save()
1642 db99f198 Giorgos Korfiatis
1643 db99f198 Giorgos Korfiatis
    def resume(self):
1644 db99f198 Giorgos Korfiatis
        self.deactivation_reason = None
1645 db99f198 Giorgos Korfiatis
        self.state = self.APPROVED
1646 db99f198 Giorgos Korfiatis
        self.save()
1647 123be68a Giorgos Korfiatis
1648 123be68a Giorgos Korfiatis
    ### Logical checks
1649 425e2e95 Sofia Papagiannaki
1650 e1a80257 Sofia Papagiannaki
    def is_inconsistent(self):
1651 e1a80257 Sofia Papagiannaki
        now = datetime.now()
1652 5b9e9530 Giorgos Korfiatis
        dates = [self.creation_date,
1653 5b9e9530 Giorgos Korfiatis
                 self.last_approval_date,
1654 5b9e9530 Giorgos Korfiatis
                 self.deactivation_date]
1655 5b9e9530 Giorgos Korfiatis
        return any([date > now for date in dates])
1656 5b9e9530 Giorgos Korfiatis
1657 b6fe8bb8 Giorgos Korfiatis
    def is_active_strict(self):
1658 b6fe8bb8 Giorgos Korfiatis
        return self.is_active and self.state == self.APPROVED
1659 5b9e9530 Giorgos Korfiatis
1660 db99f198 Giorgos Korfiatis
    def is_approved(self):
1661 db99f198 Giorgos Korfiatis
        return self.state == self.APPROVED
1662 db99f198 Giorgos Korfiatis
1663 123be68a Giorgos Korfiatis
    @property
1664 123be68a Giorgos Korfiatis
    def is_alive(self):
1665 b6fe8bb8 Giorgos Korfiatis
        return self.is_active_strict()
1666 123be68a Giorgos Korfiatis
1667 123be68a Giorgos Korfiatis
    @property
1668 123be68a Giorgos Korfiatis
    def is_terminated(self):
1669 123be68a Giorgos Korfiatis
        return self.is_deactivated(self.TERMINATED)
1670 123be68a Giorgos Korfiatis
1671 123be68a Giorgos Korfiatis
    @property
1672 123be68a Giorgos Korfiatis
    def is_suspended(self):
1673 db99f198 Giorgos Korfiatis
        return self.is_deactivated(self.SUSPENDED)
1674 5b9e9530 Giorgos Korfiatis
1675 5b9e9530 Giorgos Korfiatis
    def violates_resource_grants(self):
1676 e1a80257 Sofia Papagiannaki
        return False
1677 65360c65 Georgios D. Tsoukalas
1678 5b9e9530 Giorgos Korfiatis
    def violates_members_limit(self, adding=0):
1679 5b9e9530 Giorgos Korfiatis
        application = self.application
1680 943d5554 Giorgos Korfiatis
        limit = application.limit_on_members_number
1681 022c61cd Sofia Papagiannaki
        if limit is None:
1682 022c61cd Sofia Papagiannaki
            return False
1683 022c61cd Sofia Papagiannaki
        return (len(self.approved_members) + adding > limit)
1684 5b9e9530 Giorgos Korfiatis
1685 123be68a Giorgos Korfiatis
1686 123be68a Giorgos Korfiatis
    ### Other
1687 5b9e9530 Giorgos Korfiatis
1688 7db8c163 Georgios D. Tsoukalas
    def count_pending_memberships(self):
1689 7db8c163 Georgios D. Tsoukalas
        memb_set = self.projectmembership_set
1690 7db8c163 Georgios D. Tsoukalas
        memb_count = memb_set.filter(state=ProjectMembership.REQUESTED).count()
1691 7db8c163 Georgios D. Tsoukalas
        return memb_count
1692 7db8c163 Georgios D. Tsoukalas
1693 425e2e95 Sofia Papagiannaki
    @property
1694 425e2e95 Sofia Papagiannaki
    def approved_memberships(self):
1695 5b9e9530 Giorgos Korfiatis
        query = ProjectMembership.query_approved()
1696 5b9e9530 Giorgos Korfiatis
        return self.projectmembership_set.filter(query)
1697 4f22664f Georgios D. Tsoukalas
1698 425e2e95 Sofia Papagiannaki
    @property
1699 425e2e95 Sofia Papagiannaki
    def approved_members(self):
1700 425e2e95 Sofia Papagiannaki
        return [m.person for m in self.approved_memberships]
1701 4f22664f Georgios D. Tsoukalas
1702 4f22664f Georgios D. Tsoukalas
    def add_member(self, user):
1703 bfe23b13 Sofia Papagiannaki
        """
1704 bfe23b13 Sofia Papagiannaki
        Raises:
1705 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1706 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1707 bfe23b13 Sofia Papagiannaki
        """
1708 2a965273 Sofia Papagiannaki
        if isinstance(user, int):
1709 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1710 4f22664f Georgios D. Tsoukalas
1711 ccab6eb5 Sofia Papagiannaki
        m, created = ProjectMembership.objects.get_or_create(
1712 ccab6eb5 Sofia Papagiannaki
            person=user, project=self
1713 2a965273 Sofia Papagiannaki
        )
1714 4f22664f Georgios D. Tsoukalas
        m.accept()
1715 ccab6eb5 Sofia Papagiannaki
1716 4f22664f Georgios D. Tsoukalas
    def remove_member(self, user):
1717 bfe23b13 Sofia Papagiannaki
        """
1718 bfe23b13 Sofia Papagiannaki
        Raises:
1719 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1720 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1721 bfe23b13 Sofia Papagiannaki
            astakos.im.models.ProjectMembership.DoesNotExist
1722 bfe23b13 Sofia Papagiannaki
        """
1723 ccab6eb5 Sofia Papagiannaki
        if isinstance(user, int):
1724 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1725 4f22664f Georgios D. Tsoukalas
1726 bfe23b13 Sofia Papagiannaki
        m = ProjectMembership.objects.get(person=user, project=self)
1727 bfe23b13 Sofia Papagiannaki
        m.remove()
1728 4f22664f Georgios D. Tsoukalas
1729 425e2e95 Sofia Papagiannaki
1730 b6fe8bb8 Giorgos Korfiatis
class PendingMembershipError(Exception):
1731 b6fe8bb8 Giorgos Korfiatis
    pass
1732 b22de10a Sofia Papagiannaki
1733 4f22664f Georgios D. Tsoukalas
1734 db99f198 Giorgos Korfiatis
class ProjectMembershipManager(ForUpdateManager):
1735 db99f198 Giorgos Korfiatis
    pass
1736 db99f198 Giorgos Korfiatis
1737 d6fdc91e Georgios D. Tsoukalas
class ProjectMembership(models.Model):
1738 4f22664f Georgios D. Tsoukalas
1739 425e2e95 Sofia Papagiannaki
    person              =   models.ForeignKey(AstakosUser)
1740 3c638f72 Giorgos Korfiatis
    request_date        =   models.DateField(auto_now_add=True)
1741 d6fdc91e Georgios D. Tsoukalas
    project             =   models.ForeignKey(Project)
1742 d6fdc91e Georgios D. Tsoukalas
1743 db99f198 Giorgos Korfiatis
    REQUESTED           =   0
1744 db99f198 Giorgos Korfiatis
    ACCEPTED            =   1
1745 db99f198 Giorgos Korfiatis
    # User deactivation
1746 db99f198 Giorgos Korfiatis
    USER_SUSPENDED      =   10
1747 db99f198 Giorgos Korfiatis
    # Project deactivation
1748 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED =   100
1749 b6fe8bb8 Giorgos Korfiatis
1750 db99f198 Giorgos Korfiatis
    REMOVED             =   200
1751 db99f198 Giorgos Korfiatis
1752 db99f198 Giorgos Korfiatis
    ASSOCIATED_STATES   =   set([REQUESTED,
1753 db99f198 Giorgos Korfiatis
                                 ACCEPTED,
1754 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1755 db99f198 Giorgos Korfiatis
                                 PROJECT_DEACTIVATED])
1756 db99f198 Giorgos Korfiatis
1757 db99f198 Giorgos Korfiatis
    ACCEPTED_STATES     =   set([ACCEPTED,
1758 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1759 db99f198 Giorgos Korfiatis
                                 PROJECT_DEACTIVATED])
1760 05617ab9 Kostas Papadimitriou
1761 b6fe8bb8 Giorgos Korfiatis
    state               =   models.IntegerField(default=REQUESTED,
1762 b6fe8bb8 Giorgos Korfiatis
                                                db_index=True)
1763 b6fe8bb8 Giorgos Korfiatis
    is_pending          =   models.BooleanField(default=False, db_index=True)
1764 b6fe8bb8 Giorgos Korfiatis
    is_active           =   models.BooleanField(default=False, db_index=True)
1765 5200e864 Sofia Papagiannaki
    application         =   models.ForeignKey(
1766 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1767 5200e864 Sofia Papagiannaki
                                null=True,
1768 5200e864 Sofia Papagiannaki
                                related_name='memberships')
1769 5200e864 Sofia Papagiannaki
    pending_application =   models.ForeignKey(
1770 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1771 5200e864 Sofia Papagiannaki
                                null=True,
1772 5200e864 Sofia Papagiannaki
                                related_name='pending_memebrships')
1773 d6fdc91e Georgios D. Tsoukalas
    pending_serial      =   models.BigIntegerField(null=True, db_index=True)
1774 425e2e95 Sofia Papagiannaki
1775 425e2e95 Sofia Papagiannaki
    acceptance_date     =   models.DateField(null=True, db_index=True)
1776 425e2e95 Sofia Papagiannaki
    leave_request_date  =   models.DateField(null=True)
1777 2a965273 Sofia Papagiannaki
1778 db99f198 Giorgos Korfiatis
    objects     =   ProjectMembershipManager()
1779 ee45eb81 Giorgos Korfiatis
1780 5b9e9530 Giorgos Korfiatis
1781 b6fe8bb8 Giorgos Korfiatis
    def get_combined_state(self):
1782 b6fe8bb8 Giorgos Korfiatis
        return self.state, self.is_active, self.is_pending
1783 5b9e9530 Giorgos Korfiatis
1784 5b9e9530 Giorgos Korfiatis
    @classmethod
1785 5b9e9530 Giorgos Korfiatis
    def query_approved(cls):
1786 b6fe8bb8 Giorgos Korfiatis
        return (~Q(state=cls.REQUESTED) &
1787 b6fe8bb8 Giorgos Korfiatis
                ~Q(state=cls.REMOVED))
1788 65360c65 Georgios D. Tsoukalas
1789 0cc22d47 Sofia Papagiannaki
    class Meta:
1790 0cc22d47 Sofia Papagiannaki
        unique_together = ("person", "project")
1791 d6fdc91e Georgios D. Tsoukalas
        #index_together = [["project", "state"]]
1792 bfe23b13 Sofia Papagiannaki
1793 65360c65 Georgios D. Tsoukalas
    def __str__(self):
1794 8c7b8bb8 Giorgos Korfiatis
        return _("<'%s' membership in '%s'>") % (
1795 8c7b8bb8 Giorgos Korfiatis
                self.person.username, self.project)
1796 65360c65 Georgios D. Tsoukalas
1797 65360c65 Georgios D. Tsoukalas
    __repr__ = __str__
1798 65360c65 Georgios D. Tsoukalas
1799 65360c65 Georgios D. Tsoukalas
    def __init__(self, *args, **kwargs):
1800 ee45eb81 Giorgos Korfiatis
        self.state = self.REQUESTED
1801 65360c65 Georgios D. Tsoukalas
        super(ProjectMembership, self).__init__(*args, **kwargs)
1802 65360c65 Georgios D. Tsoukalas
1803 4f22664f Georgios D. Tsoukalas
    def _set_history_item(self, reason, date=None):
1804 4f22664f Georgios D. Tsoukalas
        if isinstance(reason, basestring):
1805 4f22664f Georgios D. Tsoukalas
            reason = ProjectMembershipHistory.reasons.get(reason, -1)
1806 4f22664f Georgios D. Tsoukalas
1807 4f22664f Georgios D. Tsoukalas
        history_item = ProjectMembershipHistory(
1808 4f22664f Georgios D. Tsoukalas
                            serial=self.id,
1809 d0e78bbe Giorgos Korfiatis
                            person=self.person_id,
1810 02d2519e Giorgos Korfiatis
                            project=self.project_id,
1811 8f975b72 Sofia Papagiannaki
                            date=date or datetime.now(),
1812 4f22664f Georgios D. Tsoukalas
                            reason=reason)
1813 4f22664f Georgios D. Tsoukalas
        history_item.save()
1814 4f22664f Georgios D. Tsoukalas
        serial = history_item.id
1815 4f22664f Georgios D. Tsoukalas
1816 4f22664f Georgios D. Tsoukalas
    def accept(self):
1817 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
1818 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to accept while is pending") % (self,)
1819 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1820 b6fe8bb8 Giorgos Korfiatis
1821 5200e864 Sofia Papagiannaki
        state = self.state
1822 65360c65 Georgios D. Tsoukalas
        if state != self.REQUESTED:
1823 5b9e9530 Giorgos Korfiatis
            m = _("%s: attempt to accept in state '%s'") % (self, state)
1824 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1825 4f22664f Georgios D. Tsoukalas
1826 65360c65 Georgios D. Tsoukalas
        now = datetime.now()
1827 65360c65 Georgios D. Tsoukalas
        self.acceptance_date = now
1828 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='ACCEPT', date=now)
1829 db99f198 Giorgos Korfiatis
        if self.project.is_approved():
1830 b6fe8bb8 Giorgos Korfiatis
            self.state = self.ACCEPTED
1831 b6fe8bb8 Giorgos Korfiatis
            self.is_pending = True
1832 b6fe8bb8 Giorgos Korfiatis
        else:
1833 db99f198 Giorgos Korfiatis
            self.state = self.PROJECT_DEACTIVATED
1834 b6fe8bb8 Giorgos Korfiatis
1835 65360c65 Georgios D. Tsoukalas
        self.save()
1836 4f22664f Georgios D. Tsoukalas
1837 65360c65 Georgios D. Tsoukalas
    def remove(self):
1838 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
1839 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to remove while is pending") % (self,)
1840 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1841 b6fe8bb8 Giorgos Korfiatis
1842 974ee6a6 Sofia Papagiannaki
        state = self.state
1843 db99f198 Giorgos Korfiatis
        if state not in self.ACCEPTED_STATES:
1844 65360c65 Georgios D. Tsoukalas
            m = _("%s: attempt to remove in state '%s'") % (self, state)
1845 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1846 4f22664f Georgios D. Tsoukalas
1847 ee45eb81 Giorgos Korfiatis
        self._set_history_item(reason='REMOVE')
1848 b6fe8bb8 Giorgos Korfiatis
        self.state = self.REMOVED
1849 b6fe8bb8 Giorgos Korfiatis
        self.is_pending = True
1850 0cc22d47 Sofia Papagiannaki
        self.save()
1851 b8f05f8d Sofia Papagiannaki
1852 65360c65 Georgios D. Tsoukalas
    def reject(self):
1853 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
1854 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to reject while is pending") % (self,)
1855 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1856 b6fe8bb8 Giorgos Korfiatis
1857 974ee6a6 Sofia Papagiannaki
        state = self.state
1858 65360c65 Georgios D. Tsoukalas
        if state != self.REQUESTED:
1859 5b9e9530 Giorgos Korfiatis
            m = _("%s: attempt to reject in state '%s'") % (self, state)
1860 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1861 65360c65 Georgios D. Tsoukalas
1862 65360c65 Georgios D. Tsoukalas
        # rejected requests don't need sync,
1863 65360c65 Georgios D. Tsoukalas
        # because they were never effected
1864 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='REJECT')
1865 0cc22d47 Sofia Papagiannaki
        self.delete()
1866 b8f05f8d Sofia Papagiannaki
1867 b6fe8bb8 Giorgos Korfiatis
    def get_diff_quotas(self, sub_list=None, add_list=None):
1868 d2b32360 Giorgos Korfiatis
        if sub_list is None:
1869 d2b32360 Giorgos Korfiatis
            sub_list = []
1870 d2b32360 Giorgos Korfiatis
1871 d2b32360 Giorgos Korfiatis
        if add_list is None:
1872 d2b32360 Giorgos Korfiatis
            add_list = []
1873 d6fdc91e Georgios D. Tsoukalas
1874 d2b32360 Giorgos Korfiatis
        sub_append = sub_list.append
1875 d2b32360 Giorgos Korfiatis
        add_append = add_list.append
1876 d75c432e Sofia Papagiannaki
        holder = self.person.uuid
1877 d6fdc91e Georgios D. Tsoukalas
1878 d6fdc91e Georgios D. Tsoukalas
        synced_application = self.application
1879 d6fdc91e Georgios D. Tsoukalas
        if synced_application is not None:
1880 5f2e4042 Sofia Papagiannaki
            cur_grants = synced_application.projectresourcegrant_set.all()
1881 d6fdc91e Georgios D. Tsoukalas
            for grant in cur_grants:
1882 d2b32360 Giorgos Korfiatis
                sub_append(QuotaLimits(
1883 d2b32360 Giorgos Korfiatis
                               holder       = holder,
1884 f3e93707 Sofia Papagiannaki
                               resource     = str(grant.resource),
1885 d2b32360 Giorgos Korfiatis
                               capacity     = grant.member_capacity,
1886 d2b32360 Giorgos Korfiatis
                               import_limit = grant.member_import_limit,
1887 d2b32360 Giorgos Korfiatis
                               export_limit = grant.member_export_limit))
1888 d6fdc91e Georgios D. Tsoukalas
1889 b6fe8bb8 Giorgos Korfiatis
        pending_application = self.pending_application
1890 b6fe8bb8 Giorgos Korfiatis
        if pending_application is not None:
1891 b6fe8bb8 Giorgos Korfiatis
            new_grants = pending_application.projectresourcegrant_set.all()
1892 d6fdc91e Georgios D. Tsoukalas
            for new_grant in new_grants:
1893 d2b32360 Giorgos Korfiatis
                add_append(QuotaLimits(
1894 d2b32360 Giorgos Korfiatis
                               holder       = holder,
1895 f3e93707 Sofia Papagiannaki
                               resource     = str(new_grant.resource),
1896 974ee6a6 Sofia Papagiannaki
                               capacity     = new_grant.member_capacity,
1897 974ee6a6 Sofia Papagiannaki
                               import_limit = new_grant.member_import_limit,
1898 974ee6a6 Sofia Papagiannaki
                               export_limit = new_grant.member_export_limit))
1899 d6fdc91e Georgios D. Tsoukalas
1900 d2b32360 Giorgos Korfiatis
        return (sub_list, add_list)
1901 65360c65 Georgios D. Tsoukalas
1902 ee45eb81 Giorgos Korfiatis
    def set_sync(self):
1903 b6fe8bb8 Giorgos Korfiatis
        if not self.is_pending:
1904 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to sync a non pending membership") % (self,)
1905 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1906 b6fe8bb8 Giorgos Korfiatis
1907 ee45eb81 Giorgos Korfiatis
        state = self.state
1908 b6fe8bb8 Giorgos Korfiatis
        if state == self.ACCEPTED:
1909 ee45eb81 Giorgos Korfiatis
            pending_application = self.pending_application
1910 ee45eb81 Giorgos Korfiatis
            if pending_application is None:
1911 ee45eb81 Giorgos Korfiatis
                m = _("%s: attempt to sync an empty pending application") % (
1912 8c7b8bb8 Giorgos Korfiatis
                    self,)
1913 ee45eb81 Giorgos Korfiatis
                raise AssertionError(m)
1914 b6fe8bb8 Giorgos Korfiatis
1915 ee45eb81 Giorgos Korfiatis
            self.application = pending_application
1916 b6fe8bb8 Giorgos Korfiatis
            self.is_active = True
1917 b6fe8bb8 Giorgos Korfiatis
1918 ee45eb81 Giorgos Korfiatis
            self.pending_application = None
1919 ee45eb81 Giorgos Korfiatis
            self.pending_serial = None
1920 ee45eb81 Giorgos Korfiatis
1921 ee45eb81 Giorgos Korfiatis
            # project.application may have changed in the meantime,
1922 ee45eb81 Giorgos Korfiatis
            # in which case we stay PENDING;
1923 ee45eb81 Giorgos Korfiatis
            # we are safe to check due to select_for_update
1924 ee45eb81 Giorgos Korfiatis
            if self.application == self.project.application:
1925 b6fe8bb8 Giorgos Korfiatis
                self.is_pending = False
1926 ee45eb81 Giorgos Korfiatis
            self.save()
1927 b6fe8bb8 Giorgos Korfiatis
1928 db99f198 Giorgos Korfiatis
        elif state == self.PROJECT_DEACTIVATED:
1929 5b9e9530 Giorgos Korfiatis
            if self.pending_application:
1930 5b9e9530 Giorgos Korfiatis
                m = _("%s: attempt to sync in state '%s' "
1931 5b9e9530 Giorgos Korfiatis
                      "with a pending application") % (self, state)
1932 5b9e9530 Giorgos Korfiatis
                raise AssertionError(m)
1933 b6fe8bb8 Giorgos Korfiatis
1934 5b9e9530 Giorgos Korfiatis
            self.application = None
1935 5b9e9530 Giorgos Korfiatis
            self.pending_serial = None
1936 b6fe8bb8 Giorgos Korfiatis
            self.is_pending = False
1937 5b9e9530 Giorgos Korfiatis
            self.save()
1938 b6fe8bb8 Giorgos Korfiatis
1939 b6fe8bb8 Giorgos Korfiatis
        elif state == self.REMOVED:
1940 ee45eb81 Giorgos Korfiatis
            self.delete()
1941 b6fe8bb8 Giorgos Korfiatis
1942 ee45eb81 Giorgos Korfiatis
        else:
1943 ee45eb81 Giorgos Korfiatis
            m = _("%s: attempt to sync in state '%s'") % (self, state)
1944 ee45eb81 Giorgos Korfiatis
            raise AssertionError(m)
1945 ee45eb81 Giorgos Korfiatis
1946 49b74233 Georgios D. Tsoukalas
    def reset_sync(self):
1947 b6fe8bb8 Giorgos Korfiatis
        if not self.is_pending:
1948 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to reset a non pending membership") % (self,)
1949 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1950 b6fe8bb8 Giorgos Korfiatis
1951 49b74233 Georgios D. Tsoukalas
        state = self.state
1952 db99f198 Giorgos Korfiatis
        if state in [self.ACCEPTED, self.PROJECT_DEACTIVATED, self.REMOVED]:
1953 49b74233 Georgios D. Tsoukalas
            self.pending_application = None
1954 49b74233 Georgios D. Tsoukalas
            self.pending_serial = None
1955 49b74233 Georgios D. Tsoukalas
            self.save()
1956 49b74233 Georgios D. Tsoukalas
        else:
1957 49b74233 Georgios D. Tsoukalas
            m = _("%s: attempt to reset sync in state '%s'") % (self, state)
1958 49b74233 Georgios D. Tsoukalas
            raise AssertionError(m)
1959 49b74233 Georgios D. Tsoukalas
1960 ee45eb81 Giorgos Korfiatis
class Serial(models.Model):
1961 ee45eb81 Giorgos Korfiatis
    serial  =   models.AutoField(primary_key=True)
1962 ee45eb81 Giorgos Korfiatis
1963 ee45eb81 Giorgos Korfiatis
def new_serial():
1964 5200e864 Sofia Papagiannaki
    s = Serial.objects.create()
1965 c70968bd Giorgos Korfiatis
    serial = s.serial
1966 c70968bd Giorgos Korfiatis
    s.delete()
1967 c70968bd Giorgos Korfiatis
    return serial
1968 82d7e9ef Georgios D. Tsoukalas
1969 333f6a72 Sofia Papagiannaki
def sync_finish_serials(serials_to_ack=None):
1970 333f6a72 Sofia Papagiannaki
    if serials_to_ack is None:
1971 333f6a72 Sofia Papagiannaki
        serials_to_ack = qh_query_serials([])
1972 333f6a72 Sofia Papagiannaki
1973 333f6a72 Sofia Papagiannaki
    serials_to_ack = set(serials_to_ack)
1974 d6fdc91e Georgios D. Tsoukalas
    sfu = ProjectMembership.objects.select_for_update()
1975 333f6a72 Sofia Papagiannaki
    memberships = list(sfu.filter(pending_serial__isnull=False))
1976 333f6a72 Sofia Papagiannaki
1977 60ca2f6f Giorgos Korfiatis
    if memberships:
1978 60ca2f6f Giorgos Korfiatis
        for membership in memberships:
1979 60ca2f6f Giorgos Korfiatis
            serial = membership.pending_serial
1980 60ca2f6f Giorgos Korfiatis
            if serial in serials_to_ack:
1981 60ca2f6f Giorgos Korfiatis
                membership.set_sync()
1982 60ca2f6f Giorgos Korfiatis
            else:
1983 60ca2f6f Giorgos Korfiatis
                membership.reset_sync()
1984 60ca2f6f Giorgos Korfiatis
1985 60ca2f6f Giorgos Korfiatis
        transaction.commit()
1986 60ca2f6f Giorgos Korfiatis
1987 ee45eb81 Giorgos Korfiatis
    qh_ack_serials(list(serials_to_ack))
1988 333f6a72 Sofia Papagiannaki
    return len(memberships)
1989 82d7e9ef Georgios D. Tsoukalas
1990 b6fe8bb8 Giorgos Korfiatis
def pre_sync():
1991 b6fe8bb8 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
1992 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
1993 b6fe8bb8 Giorgos Korfiatis
    psfu = Project.objects.select_for_update()
1994 b6fe8bb8 Giorgos Korfiatis
1995 b6fe8bb8 Giorgos Korfiatis
    modified = psfu.modified_projects()
1996 b6fe8bb8 Giorgos Korfiatis
    for project in modified:
1997 b6fe8bb8 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
1998 b6fe8bb8 Giorgos Korfiatis
1999 b6fe8bb8 Giorgos Korfiatis
        memberships = objects.filter(state=ACCEPTED)
2000 b6fe8bb8 Giorgos Korfiatis
        for membership in memberships:
2001 b6fe8bb8 Giorgos Korfiatis
            membership.is_pending = True
2002 b6fe8bb8 Giorgos Korfiatis
            membership.save()
2003 82d7e9ef Georgios D. Tsoukalas
2004 db99f198 Giorgos Korfiatis
    reactivating = psfu.reactivating_projects()
2005 db99f198 Giorgos Korfiatis
    for project in reactivating:
2006 db99f198 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2007 db99f198 Giorgos Korfiatis
        memberships = objects.filter(state=PROJECT_DEACTIVATED)
2008 db99f198 Giorgos Korfiatis
        for membership in memberships:
2009 db99f198 Giorgos Korfiatis
            membership.is_pending = True
2010 db99f198 Giorgos Korfiatis
            membership.state = ACCEPTED
2011 db99f198 Giorgos Korfiatis
            membership.save()
2012 db99f198 Giorgos Korfiatis
2013 db99f198 Giorgos Korfiatis
    deactivating = psfu.deactivating_projects()
2014 db99f198 Giorgos Korfiatis
    for project in deactivating:
2015 b6fe8bb8 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2016 b6fe8bb8 Giorgos Korfiatis
2017 db99f198 Giorgos Korfiatis
        # Note: we keep a user-level deactivation (e.g. USER_SUSPENDED) intact
2018 b6fe8bb8 Giorgos Korfiatis
        memberships = objects.filter(state=ACCEPTED)
2019 b6fe8bb8 Giorgos Korfiatis
        for membership in memberships:
2020 b6fe8bb8 Giorgos Korfiatis
            membership.is_pending = True
2021 db99f198 Giorgos Korfiatis
            membership.state = PROJECT_DEACTIVATED
2022 b6fe8bb8 Giorgos Korfiatis
            membership.save()
2023 b6fe8bb8 Giorgos Korfiatis
2024 b6fe8bb8 Giorgos Korfiatis
def do_sync():
2025 82d7e9ef Georgios D. Tsoukalas
2026 b6fe8bb8 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
2027 d6fdc91e Georgios D. Tsoukalas
    objects = ProjectMembership.objects.select_for_update()
2028 82d7e9ef Georgios D. Tsoukalas
2029 d2b32360 Giorgos Korfiatis
    sub_quota, add_quota = [], []
2030 65360c65 Georgios D. Tsoukalas
2031 ee45eb81 Giorgos Korfiatis
    serial = new_serial()
2032 d6fdc91e Georgios D. Tsoukalas
2033 b6fe8bb8 Giorgos Korfiatis
    pending = objects.filter(is_pending=True)
2034 d6fdc91e Georgios D. Tsoukalas
    for membership in pending:
2035 d6fdc91e Georgios D. Tsoukalas
2036 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_application:
2037 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_application is not None (%s)" % (
2038 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_application)
2039 d6fdc91e Georgios D. Tsoukalas
            raise AssertionError(m)
2040 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_serial:
2041 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_serial is not None (%s)" % (
2042 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_serial)
2043 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2044 b8f05f8d Sofia Papagiannaki
2045 b6fe8bb8 Giorgos Korfiatis
        if membership.state == ACCEPTED:
2046 b6fe8bb8 Giorgos Korfiatis
            membership.pending_application = membership.project.application
2047 d6fdc91e Georgios D. Tsoukalas
2048 d6fdc91e Georgios D. Tsoukalas
        membership.pending_serial = serial
2049 b6fe8bb8 Giorgos Korfiatis
        membership.get_diff_quotas(sub_quota, add_quota)
2050 d6fdc91e Georgios D. Tsoukalas
        membership.save()
2051 d6fdc91e Georgios D. Tsoukalas
2052 d6fdc91e Georgios D. Tsoukalas
    transaction.commit()
2053 ee45eb81 Giorgos Korfiatis
    # ProjectApplication.approve() unblocks here
2054 ee45eb81 Giorgos Korfiatis
    # and can set PENDING an already PENDING membership
2055 ee45eb81 Giorgos Korfiatis
    # which has been scheduled to sync with the old project.application
2056 ee45eb81 Giorgos Korfiatis
    # Need to check in ProjectMembership.set_sync()
2057 ee45eb81 Giorgos Korfiatis
2058 5cfd4acb Sofia Papagiannaki
    r = qh_add_quota(serial, sub_quota, add_quota)
2059 333f6a72 Sofia Papagiannaki
    if r:
2060 333f6a72 Sofia Papagiannaki
        m = "cannot sync serial: %d" % serial
2061 333f6a72 Sofia Papagiannaki
        raise RuntimeError(m)
2062 333f6a72 Sofia Papagiannaki
2063 b6fe8bb8 Giorgos Korfiatis
    return serial
2064 5b9e9530 Giorgos Korfiatis
2065 b6fe8bb8 Giorgos Korfiatis
def post_sync():
2066 5b9e9530 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
2067 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
2068 5b9e9530 Giorgos Korfiatis
    psfu = Project.objects.select_for_update()
2069 5b9e9530 Giorgos Korfiatis
2070 b6fe8bb8 Giorgos Korfiatis
    modified = psfu.modified_projects()
2071 b6fe8bb8 Giorgos Korfiatis
    for project in modified:
2072 5b9e9530 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2073 5b9e9530 Giorgos Korfiatis
2074 b6fe8bb8 Giorgos Korfiatis
        memberships = list(objects.filter(state=ACCEPTED, is_pending=True))
2075 b6fe8bb8 Giorgos Korfiatis
        if not memberships:
2076 b6fe8bb8 Giorgos Korfiatis
            project.is_modified = False
2077 b6fe8bb8 Giorgos Korfiatis
            project.save()
2078 5b9e9530 Giorgos Korfiatis
2079 db99f198 Giorgos Korfiatis
    reactivating = psfu.reactivating_projects()
2080 db99f198 Giorgos Korfiatis
    for project in reactivating:
2081 db99f198 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2082 db99f198 Giorgos Korfiatis
        memberships = list(objects.filter(Q(state=PROJECT_DEACTIVATED) |
2083 db99f198 Giorgos Korfiatis
                                          Q(is_pending=True)))
2084 db99f198 Giorgos Korfiatis
        if not memberships:
2085 db99f198 Giorgos Korfiatis
            project.reactivate()
2086 db99f198 Giorgos Korfiatis
            project.save()
2087 db99f198 Giorgos Korfiatis
2088 db99f198 Giorgos Korfiatis
    deactivating = psfu.deactivating_projects()
2089 db99f198 Giorgos Korfiatis
    for project in deactivating:
2090 5b9e9530 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2091 b6fe8bb8 Giorgos Korfiatis
2092 5b9e9530 Giorgos Korfiatis
        memberships = list(objects.filter(Q(state=ACCEPTED) |
2093 b6fe8bb8 Giorgos Korfiatis
                                          Q(is_pending=True)))
2094 5b9e9530 Giorgos Korfiatis
        if not memberships:
2095 b6fe8bb8 Giorgos Korfiatis
            project.deactivate()
2096 5b9e9530 Giorgos Korfiatis
            project.save()
2097 5b9e9530 Giorgos Korfiatis
2098 5b9e9530 Giorgos Korfiatis
    transaction.commit()
2099 5b9e9530 Giorgos Korfiatis
2100 5b9e9530 Giorgos Korfiatis
def sync_projects():
2101 b6fe8bb8 Giorgos Korfiatis
    sync_finish_serials()
2102 b6fe8bb8 Giorgos Korfiatis
    pre_sync()
2103 b6fe8bb8 Giorgos Korfiatis
    serial = do_sync()
2104 b6fe8bb8 Giorgos Korfiatis
    sync_finish_serials([serial])
2105 b6fe8bb8 Giorgos Korfiatis
    post_sync()
2106 d6fdc91e Georgios D. Tsoukalas
2107 d6fdc91e Georgios D. Tsoukalas
def trigger_sync(retries=3, retry_wait=1.0):
2108 14695557 Giorgos Korfiatis
    transaction.commit()
2109 14695557 Giorgos Korfiatis
2110 d6fdc91e Georgios D. Tsoukalas
    cursor = connection.cursor()
2111 d6fdc91e Georgios D. Tsoukalas
    locked = True
2112 d6fdc91e Georgios D. Tsoukalas
    try:
2113 d6fdc91e Georgios D. Tsoukalas
        while 1:
2114 d6fdc91e Georgios D. Tsoukalas
            cursor.execute("SELECT pg_try_advisory_lock(1)")
2115 d6fdc91e Georgios D. Tsoukalas
            r = cursor.fetchone()
2116 d6fdc91e Georgios D. Tsoukalas
            if r is None:
2117 d6fdc91e Georgios D. Tsoukalas
                m = "Impossible"
2118 d6fdc91e Georgios D. Tsoukalas
                raise AssertionError(m)
2119 d6fdc91e Georgios D. Tsoukalas
            locked = r[0]
2120 d6fdc91e Georgios D. Tsoukalas
            if locked:
2121 d6fdc91e Georgios D. Tsoukalas
                break
2122 d6fdc91e Georgios D. Tsoukalas
2123 d6fdc91e Georgios D. Tsoukalas
            retries -= 1
2124 d6fdc91e Georgios D. Tsoukalas
            if retries <= 0:
2125 d6fdc91e Georgios D. Tsoukalas
                return False
2126 d6fdc91e Georgios D. Tsoukalas
            sleep(retry_wait)
2127 d6fdc91e Georgios D. Tsoukalas
2128 d6fdc91e Georgios D. Tsoukalas
        sync_projects()
2129 d6fdc91e Georgios D. Tsoukalas
        return True
2130 d6fdc91e Georgios D. Tsoukalas
2131 d6fdc91e Georgios D. Tsoukalas
    finally:
2132 d6fdc91e Georgios D. Tsoukalas
        if locked:
2133 d6fdc91e Georgios D. Tsoukalas
            cursor.execute("SELECT pg_advisory_unlock(1)")
2134 d6fdc91e Georgios D. Tsoukalas
            cursor.fetchall()
2135 4f22664f Georgios D. Tsoukalas
2136 e1a80257 Sofia Papagiannaki
2137 0cc22d47 Sofia Papagiannaki
class ProjectMembershipHistory(models.Model):
2138 425e2e95 Sofia Papagiannaki
    reasons_list    =   ['ACCEPT', 'REJECT', 'REMOVE']
2139 425e2e95 Sofia Papagiannaki
    reasons         =   dict((k, v) for v, k in enumerate(reasons_list))
2140 425e2e95 Sofia Papagiannaki
2141 d0e78bbe Giorgos Korfiatis
    person  =   models.BigIntegerField()
2142 02d2519e Giorgos Korfiatis
    project =   models.BigIntegerField()
2143 3c638f72 Giorgos Korfiatis
    date    =   models.DateField(auto_now_add=True)
2144 425e2e95 Sofia Papagiannaki
    reason  =   models.IntegerField()
2145 425e2e95 Sofia Papagiannaki
    serial  =   models.BigIntegerField()
2146 fc655b6f Kostas Papadimitriou
2147 fcc1e93f Sofia Papagiannaki
### SIGNALS ###
2148 fcc1e93f Sofia Papagiannaki
################
2149 b22de10a Sofia Papagiannaki
2150 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
2151 ff9290ec Sofia Papagiannaki
    try:
2152 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
2153 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
2154 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
2155 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
2156 ff9290ec Sofia Papagiannaki
        extended_user.save()
2157 67be1883 Olga Brani
        if not extended_user.has_auth_provider('local'):
2158 67be1883 Olga Brani
            extended_user.add_auth_provider('local')
2159 fc1e2f02 Sofia Papagiannaki
    except BaseException, e:
2160 fc1e2f02 Sofia Papagiannaki
        logger.exception(e)
2161 ff9290ec Sofia Papagiannaki
2162 5ce3ce4f Sofia Papagiannaki
2163 fc1e2f02 Sofia Papagiannaki
def fix_superusers(sender, **kwargs):
2164 fc1e2f02 Sofia Papagiannaki
    # Associate superusers with AstakosUser
2165 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
2166 ff9290ec Sofia Papagiannaki
    for u in admins:
2167 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
2168 bfe23b13 Sofia Papagiannaki
post_syncdb.connect(fix_superusers)
2169 ff9290ec Sofia Papagiannaki
2170 ff9290ec Sofia Papagiannaki
2171 fc1e2f02 Sofia Papagiannaki
def user_post_save(sender, instance, created, **kwargs):
2172 aa4109d4 Sofia Papagiannaki
    if not created:
2173 aa4109d4 Sofia Papagiannaki
        return
2174 fc1e2f02 Sofia Papagiannaki
    create_astakos_user(instance)
2175 bfe23b13 Sofia Papagiannaki
post_save.connect(user_post_save, sender=User)
2176 ff9290ec Sofia Papagiannaki
2177 fc1e2f02 Sofia Papagiannaki
def astakosuser_post_save(sender, instance, created, **kwargs):
2178 21e0fdad Giorgos Korfiatis
    pass
2179 21e0fdad Giorgos Korfiatis
2180 bfe23b13 Sofia Papagiannaki
post_save.connect(astakosuser_post_save, sender=AstakosUser)
2181 fc1e2f02 Sofia Papagiannaki
2182 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
2183 0514bcc7 Giorgos Korfiatis
    pass
2184 0514bcc7 Giorgos Korfiatis
2185 bfe23b13 Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
2186 bfe23b13 Sofia Papagiannaki
2187 bfe23b13 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
2188 bfe23b13 Sofia Papagiannaki
    if not instance.auth_token:
2189 bfe23b13 Sofia Papagiannaki
        instance.renew_token()
2190 bf0c6de5 Sofia Papagiannaki
pre_save.connect(renew_token, sender=AstakosUser)
2191 2e90e3ec Kostas Papadimitriou
pre_save.connect(renew_token, sender=Service)