Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 26551b92

History | View | Annotate | Download (73.9 kB)

1 f557d10a Giorgos Korfiatis
# Copyright 2011, 2012, 2013 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 b98e1df0 Sofia Papagiannaki
import math
39 a989b48e Giorgos Korfiatis
import copy
40 64cd4730 Antony Chazapis
41 57f5ea5c Giorgos Korfiatis
from time import asctime
42 64cd4730 Antony Chazapis
from datetime import datetime, timedelta
43 64cd4730 Antony Chazapis
from base64 import b64encode
44 ef20ea07 Sofia Papagiannaki
from urlparse import urlparse
45 d2633501 Kostas Papadimitriou
from urllib import quote
46 8f5a3a06 Sofia Papagiannaki
from random import randint
47 65360c65 Georgios D. Tsoukalas
from collections import defaultdict, namedtuple
48 64cd4730 Antony Chazapis
49 57f5ea5c Giorgos Korfiatis
from django.db import models, IntegrityError, transaction
50 9a06d96f Olga Brani
from django.contrib.auth.models import User, UserManager, Group, Permission
51 0a569195 Sofia Papagiannaki
from django.utils.translation import ugettext as _
52 0a569195 Sofia Papagiannaki
from django.core.exceptions import ValidationError
53 c0b26605 Sofia Papagiannaki
from django.db.models.signals import (
54 73fbaec4 Sofia Papagiannaki
    pre_save, post_save, post_syncdb, post_delete)
55 9a06d96f Olga Brani
from django.contrib.contenttypes.models import ContentType
56 9a06d96f Olga Brani
57 fc1e2f02 Sofia Papagiannaki
from django.dispatch import Signal
58 4391de3d Giorgos Korfiatis
from django.db.models import Q, Max
59 d2633501 Kostas Papadimitriou
from django.core.urlresolvers import reverse
60 d2633501 Kostas Papadimitriou
from django.utils.http import int_to_base36
61 d2633501 Kostas Papadimitriou
from django.contrib.auth.tokens import default_token_generator
62 8f8c43b2 Sofia Papagiannaki
from django.conf import settings
63 bf0c6de5 Sofia Papagiannaki
from django.utils.importlib import import_module
64 c4b1a172 Kostas Papadimitriou
from django.utils.safestring import mark_safe
65 d2633501 Kostas Papadimitriou
from django.core.validators import email_re
66 73fbaec4 Sofia Papagiannaki
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
67 64cd4730 Antony Chazapis
68 e1a80257 Sofia Papagiannaki
from astakos.im.settings import (
69 e1a80257 Sofia Papagiannaki
    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
70 c7b82fdc Sofia Papagiannaki
    AUTH_TOKEN_DURATION, EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
71 4161cb70 Giorgos Korfiatis
    SITENAME, MODERATION_ENABLED,
72 8e1a5af5 Georgios D. Tsoukalas
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES, PROJECT_ADMINS)
73 c4b1a172 Kostas Papadimitriou
from astakos.im import settings as astakos_settings
74 9d20fe23 Kostas Papadimitriou
from astakos.im import auth_providers as auth
75 9c01d9d1 Sofia Papagiannaki
76 ae497612 Olga Brani
import astakos.im.messages as astakos_messages
77 a6a152d6 Giorgos Korfiatis
from synnefo.lib.db.managers import ForUpdateManager
78 64cd4730 Antony Chazapis
79 f77363c2 Giorgos Korfiatis
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
80 c11dc0ce Giorgos Korfiatis
from synnefo.lib.db.intdecimalfield import intDecimalField
81 b6eaca30 Giorgos Korfiatis
from synnefo.util.text import uenc, udec
82 4161cb70 Giorgos Korfiatis
from astakos.im.presentation import RESOURCES_PRESENTATION_DATA
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 9ee0c6a2 Sofia Papagiannaki
inf = float('inf')
102 5ce3ce4f Sofia Papagiannaki
103 8e45d6fd Sofia Papagiannaki
class Service(models.Model):
104 e1a80257 Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
105 8e45d6fd Sofia Papagiannaki
    url = models.FilePathField()
106 8e45d6fd Sofia Papagiannaki
    icon = models.FilePathField(blank=True)
107 e1a80257 Sofia Papagiannaki
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
108 8e45d6fd Sofia Papagiannaki
                                  null=True, blank=True)
109 e1a80257 Sofia Papagiannaki
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
110 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
111 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
112 7795764b Kostas Papadimitriou
    order = models.PositiveIntegerField(default=0)
113 7795764b Kostas Papadimitriou
114 7795764b Kostas Papadimitriou
    class Meta:
115 7795764b Kostas Papadimitriou
        ordering = ('order', )
116 5ce3ce4f Sofia Papagiannaki
117 08494423 Sofia Papagiannaki
    def renew_token(self, expiration_date=None):
118 8e45d6fd Sofia Papagiannaki
        md5 = hashlib.md5()
119 8e45d6fd Sofia Papagiannaki
        md5.update(self.name.encode('ascii', 'ignore'))
120 8e45d6fd Sofia Papagiannaki
        md5.update(self.url.encode('ascii', 'ignore'))
121 8e45d6fd Sofia Papagiannaki
        md5.update(asctime())
122 8e45d6fd Sofia Papagiannaki
123 8e45d6fd Sofia Papagiannaki
        self.auth_token = b64encode(md5.digest())
124 8e45d6fd Sofia Papagiannaki
        self.auth_token_created = datetime.now()
125 08494423 Sofia Papagiannaki
        if expiration_date:
126 08494423 Sofia Papagiannaki
            self.auth_token_expires = expiration_date
127 08494423 Sofia Papagiannaki
        else:
128 08494423 Sofia Papagiannaki
            self.auth_token_expires = None
129 5ce3ce4f Sofia Papagiannaki
130 8e45d6fd Sofia Papagiannaki
    def __str__(self):
131 8e45d6fd Sofia Papagiannaki
        return self.name
132 8e45d6fd Sofia Papagiannaki
133 5ce3ce4f Sofia Papagiannaki
134 8bc397e8 Sofia Papagiannaki
_presentation_data = {}
135 8bc397e8 Sofia Papagiannaki
def get_presentation(resource):
136 8bc397e8 Sofia Papagiannaki
    global _presentation_data
137 8bc397e8 Sofia Papagiannaki
    presentation = _presentation_data.get(resource, {})
138 8bc397e8 Sofia Papagiannaki
    if not presentation:
139 8bc397e8 Sofia Papagiannaki
        resource_presentation = RESOURCES_PRESENTATION_DATA.get('resources', {})
140 8bc397e8 Sofia Papagiannaki
        presentation = resource_presentation.get(resource, {})
141 8bc397e8 Sofia Papagiannaki
        _presentation_data[resource] = presentation
142 3c7528c9 Kostas Papadimitriou
    return presentation
143 5ce3ce4f Sofia Papagiannaki
144 8e45d6fd Sofia Papagiannaki
class Resource(models.Model):
145 34d3883a Giorgos Korfiatis
    name = models.CharField(_('Name'), max_length=255, unique=True)
146 e1a80257 Sofia Papagiannaki
    desc = models.TextField(_('Description'), null=True)
147 26551b92 Kostas Papadimitriou
    service = models.CharField(_('Service identifier'), max_length=255,
148 26551b92 Kostas Papadimitriou
                               null=True)
149 26551b92 Kostas Papadimitriou
    unit = models.CharField(_('Unit'), null=True, max_length=255)
150 0514bcc7 Giorgos Korfiatis
    uplimit = intDecimalField(default=0)
151 425e2e95 Sofia Papagiannaki
152 b1ea24f3 Giorgos Korfiatis
    objects = ForUpdateManager()
153 b1ea24f3 Giorgos Korfiatis
154 8e45d6fd Sofia Papagiannaki
    def __str__(self):
155 34d3883a Giorgos Korfiatis
        return self.name
156 8e45d6fd Sofia Papagiannaki
157 0514bcc7 Giorgos Korfiatis
    def full_name(self):
158 0514bcc7 Giorgos Korfiatis
        return str(self)
159 0514bcc7 Giorgos Korfiatis
160 1028e568 Giorgos Korfiatis
    def get_info(self):
161 1028e568 Giorgos Korfiatis
        return {'service': str(self.service),
162 1028e568 Giorgos Korfiatis
                'description': self.desc,
163 1028e568 Giorgos Korfiatis
                'unit': self.unit,
164 1028e568 Giorgos Korfiatis
                }
165 1028e568 Giorgos Korfiatis
166 8bc397e8 Sofia Papagiannaki
    @property
167 26551b92 Kostas Papadimitriou
    def group(self):
168 26551b92 Kostas Papadimitriou
        default = self.name
169 26551b92 Kostas Papadimitriou
        return get_presentation(str(self)).get('group', default)
170 26551b92 Kostas Papadimitriou
171 26551b92 Kostas Papadimitriou
    @property
172 8bc397e8 Sofia Papagiannaki
    def help_text(self):
173 26551b92 Kostas Papadimitriou
        default = "%s resource" % self.name
174 26551b92 Kostas Papadimitriou
        return get_presentation(str(self)).get('help_text', default)
175 3c7528c9 Kostas Papadimitriou
176 8bc397e8 Sofia Papagiannaki
    @property
177 8bc397e8 Sofia Papagiannaki
    def help_text_input_each(self):
178 26551b92 Kostas Papadimitriou
        default = "%s resource" % self.name
179 26551b92 Kostas Papadimitriou
        return get_presentation(str(self)).get('help_text_input_each', default)
180 8bc397e8 Sofia Papagiannaki
181 8bc397e8 Sofia Papagiannaki
    @property
182 8bc397e8 Sofia Papagiannaki
    def is_abbreviation(self):
183 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('is_abbreviation', False)
184 8bc397e8 Sofia Papagiannaki
185 8bc397e8 Sofia Papagiannaki
    @property
186 8bc397e8 Sofia Papagiannaki
    def report_desc(self):
187 26551b92 Kostas Papadimitriou
        default = "%s resource" % self.name
188 26551b92 Kostas Papadimitriou
        return get_presentation(str(self)).get('report_desc', default)
189 8bc397e8 Sofia Papagiannaki
190 8bc397e8 Sofia Papagiannaki
    @property
191 8bc397e8 Sofia Papagiannaki
    def placeholder(self):
192 26551b92 Kostas Papadimitriou
        return get_presentation(str(self)).get('placeholder', self.unit)
193 8bc397e8 Sofia Papagiannaki
194 8bc397e8 Sofia Papagiannaki
    @property
195 8bc397e8 Sofia Papagiannaki
    def verbose_name(self):
196 26551b92 Kostas Papadimitriou
        return get_presentation(str(self)).get('verbose_name', self.name)
197 8bc397e8 Sofia Papagiannaki
198 b98e1df0 Sofia Papagiannaki
    @property
199 b98e1df0 Sofia Papagiannaki
    def display_name(self):
200 b98e1df0 Sofia Papagiannaki
        name = self.verbose_name
201 b98e1df0 Sofia Papagiannaki
        if self.is_abbreviation:
202 b98e1df0 Sofia Papagiannaki
            name = name.upper()
203 b98e1df0 Sofia Papagiannaki
        return name
204 b98e1df0 Sofia Papagiannaki
205 b98e1df0 Sofia Papagiannaki
    @property
206 b98e1df0 Sofia Papagiannaki
    def pluralized_display_name(self):
207 b98e1df0 Sofia Papagiannaki
        if not self.unit:
208 b98e1df0 Sofia Papagiannaki
            return '%ss' % self.display_name
209 b98e1df0 Sofia Papagiannaki
        return self.display_name
210 b98e1df0 Sofia Papagiannaki
211 0514bcc7 Giorgos Korfiatis
def get_resource_names():
212 0514bcc7 Giorgos Korfiatis
    _RESOURCE_NAMES = []
213 a989b48e Giorgos Korfiatis
    resources = Resource.objects.select_related('service').all()
214 0514bcc7 Giorgos Korfiatis
    _RESOURCE_NAMES = [resource.full_name() for resource in resources]
215 0514bcc7 Giorgos Korfiatis
    return _RESOURCE_NAMES
216 d2633501 Kostas Papadimitriou
217 43332a76 Kostas Papadimitriou
218 6b9a334b Sofia Papagiannaki
class AstakosUserManager(UserManager):
219 d2633501 Kostas Papadimitriou
220 d2633501 Kostas Papadimitriou
    def get_auth_provider_user(self, provider, **kwargs):
221 d2633501 Kostas Papadimitriou
        """
222 d2633501 Kostas Papadimitriou
        Retrieve AstakosUser instance associated with the specified third party
223 d2633501 Kostas Papadimitriou
        id.
224 d2633501 Kostas Papadimitriou
        """
225 d2633501 Kostas Papadimitriou
        kwargs = dict(map(lambda x: ('auth_providers__%s' % x[0], x[1]),
226 d2633501 Kostas Papadimitriou
                          kwargs.iteritems()))
227 d2633501 Kostas Papadimitriou
        return self.get(auth_providers__module=provider, **kwargs)
228 d2633501 Kostas Papadimitriou
229 c630fee6 Kostas Papadimitriou
    def get_by_email(self, email):
230 c630fee6 Kostas Papadimitriou
        return self.get(email=email)
231 c630fee6 Kostas Papadimitriou
232 e5966bd9 Kostas Papadimitriou
    def get_by_identifier(self, email_or_username, **kwargs):
233 e5966bd9 Kostas Papadimitriou
        try:
234 e5966bd9 Kostas Papadimitriou
            return self.get(email__iexact=email_or_username, **kwargs)
235 e5966bd9 Kostas Papadimitriou
        except AstakosUser.DoesNotExist:
236 e5966bd9 Kostas Papadimitriou
            return self.get(username__iexact=email_or_username, **kwargs)
237 e5966bd9 Kostas Papadimitriou
238 e5966bd9 Kostas Papadimitriou
    def user_exists(self, email_or_username, **kwargs):
239 e5966bd9 Kostas Papadimitriou
        qemail = Q(email__iexact=email_or_username)
240 e5966bd9 Kostas Papadimitriou
        qusername = Q(username__iexact=email_or_username)
241 43332a76 Kostas Papadimitriou
        qextra = Q(**kwargs)
242 43332a76 Kostas Papadimitriou
        return self.filter((qemail | qusername) & qextra).exists()
243 43332a76 Kostas Papadimitriou
244 43332a76 Kostas Papadimitriou
    def verified_user_exists(self, email_or_username):
245 43332a76 Kostas Papadimitriou
        return self.user_exists(email_or_username, email_verified=True)
246 43332a76 Kostas Papadimitriou
247 43332a76 Kostas Papadimitriou
    def verified(self):
248 43332a76 Kostas Papadimitriou
        return self.filter(email_verified=True)
249 e5966bd9 Kostas Papadimitriou
250 890c2065 Sofia Papagiannaki
    def uuid_catalog(self, l=None):
251 890c2065 Sofia Papagiannaki
        """
252 890c2065 Sofia Papagiannaki
        Returns a uuid to username mapping for the uuids appearing in l.
253 890c2065 Sofia Papagiannaki
        If l is None returns the mapping for all existing users.
254 890c2065 Sofia Papagiannaki
        """
255 890c2065 Sofia Papagiannaki
        q = self.filter(uuid__in=l) if l != None else self
256 890c2065 Sofia Papagiannaki
        return dict(q.values_list('uuid', 'username'))
257 890c2065 Sofia Papagiannaki
258 890c2065 Sofia Papagiannaki
    def displayname_catalog(self, l=None):
259 890c2065 Sofia Papagiannaki
        """
260 890c2065 Sofia Papagiannaki
        Returns a username to uuid mapping for the usernames appearing in l.
261 890c2065 Sofia Papagiannaki
        If l is None returns the mapping for all existing users.
262 890c2065 Sofia Papagiannaki
        """
263 ea05c568 Sofia Papagiannaki
        if l is not None:
264 ea05c568 Sofia Papagiannaki
            lmap = dict((x.lower(), x) for x in l)
265 ea05c568 Sofia Papagiannaki
            q = self.filter(username__in=lmap.keys())
266 ea05c568 Sofia Papagiannaki
            values = ((lmap[n], u) for n, u in q.values_list('username', 'uuid'))
267 ea05c568 Sofia Papagiannaki
        else:
268 ea05c568 Sofia Papagiannaki
            q = self
269 ea05c568 Sofia Papagiannaki
            values = self.values_list('username', 'uuid')
270 ea05c568 Sofia Papagiannaki
        return dict(values)
271 890c2065 Sofia Papagiannaki
272 890c2065 Sofia Papagiannaki
273 e5966bd9 Kostas Papadimitriou
274 0905ccd2 Sofia Papagiannaki
class AstakosUser(User):
275 890b0eaf Sofia Papagiannaki
    """
276 890b0eaf Sofia Papagiannaki
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
277 890b0eaf Sofia Papagiannaki
    """
278 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
279 d2633501 Kostas Papadimitriou
                                   null=True)
280 d2633501 Kostas Papadimitriou
281 d2633501 Kostas Papadimitriou
    # DEPRECATED FIELDS: provider, third_party_identifier moved in
282 d2633501 Kostas Papadimitriou
    #                    AstakosUserProvider model.
283 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True,
284 d2633501 Kostas Papadimitriou
                                null=True)
285 d2633501 Kostas Papadimitriou
    # ex. screen_name for twitter, eppn for shibboleth
286 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'),
287 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
288 d2633501 Kostas Papadimitriou
                                              blank=True)
289 d2633501 Kostas Papadimitriou
290 6c736ed7 Kostas Papadimitriou
291 64cd4730 Antony Chazapis
    #for invitations
292 92defad4 Sofia Papagiannaki
    user_level = DEFAULT_USER_LEVEL
293 e1a80257 Sofia Papagiannaki
    level = models.IntegerField(_('Inviter level'), default=user_level)
294 5ce3ce4f Sofia Papagiannaki
    invitations = models.IntegerField(
295 e1a80257 Sofia Papagiannaki
        _('Invitations left'), default=INVITATIONS_PER_LEVEL.get(user_level, 0))
296 6c736ed7 Kostas Papadimitriou
297 9d20fe23 Kostas Papadimitriou
    auth_token = models.CharField(_('Authentication Token'),
298 d6ea9b3d Olga Brani
                                  max_length=32,
299 9d20fe23 Kostas Papadimitriou
                                  null=True,
300 9d20fe23 Kostas Papadimitriou
                                  blank=True,
301 c9a6e558 Constantinos Venetsanopoulos
                                  help_text = _('Renew your authentication '
302 c9a6e558 Constantinos Venetsanopoulos
                                                'token. Make sure to set the new '
303 c9a6e558 Constantinos Venetsanopoulos
                                                'token in any client you may be '
304 c9a6e558 Constantinos Venetsanopoulos
                                                'using, to preserve its '
305 c9a6e558 Constantinos Venetsanopoulos
                                                'functionality.'))
306 9d20fe23 Kostas Papadimitriou
    auth_token_created = models.DateTimeField(_('Token creation date'),
307 d6ea9b3d Olga Brani
                                              null=True)
308 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
309 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
310 6c736ed7 Kostas Papadimitriou
311 e1a80257 Sofia Papagiannaki
    updated = models.DateTimeField(_('Update date'))
312 e1a80257 Sofia Papagiannaki
    is_verified = models.BooleanField(_('Is verified?'), default=False)
313 6c736ed7 Kostas Papadimitriou
314 e1a80257 Sofia Papagiannaki
    email_verified = models.BooleanField(_('Email verified?'), default=False)
315 6c736ed7 Kostas Papadimitriou
316 e1a80257 Sofia Papagiannaki
    has_credits = models.BooleanField(_('Has credits?'), default=False)
317 5ce3ce4f Sofia Papagiannaki
    has_signed_terms = models.BooleanField(
318 e1a80257 Sofia Papagiannaki
        _('I agree with the terms'), default=False)
319 5ce3ce4f Sofia Papagiannaki
    date_signed_terms = models.DateTimeField(
320 e1a80257 Sofia Papagiannaki
        _('Signed terms date'), null=True, blank=True)
321 5ce3ce4f Sofia Papagiannaki
322 5ce3ce4f Sofia Papagiannaki
    activation_sent = models.DateTimeField(
323 e1a80257 Sofia Papagiannaki
        _('Activation sent data'), null=True, blank=True)
324 5ce3ce4f Sofia Papagiannaki
325 5ce3ce4f Sofia Papagiannaki
    policy = models.ManyToManyField(
326 5ce3ce4f Sofia Papagiannaki
        Resource, null=True, through='AstakosUserQuota')
327 5ce3ce4f Sofia Papagiannaki
328 836a0fb0 Kostas Papadimitriou
    uuid = models.CharField(max_length=255, null=True, blank=False, unique=True)
329 836a0fb0 Kostas Papadimitriou
330 18ffbee1 Sofia Papagiannaki
    __has_signed_terms = False
331 e1a80257 Sofia Papagiannaki
    disturbed_quota = models.BooleanField(_('Needs quotaholder syncing'),
332 9a06d96f Olga Brani
                                           default=False, db_index=True)
333 d2633501 Kostas Papadimitriou
334 d2633501 Kostas Papadimitriou
    objects = AstakosUserManager()
335 fbaa4f3c Kostas Papadimitriou
336 8cbea11d Giorgos Korfiatis
    forupdate = ForUpdateManager()
337 8cbea11d Giorgos Korfiatis
338 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
339 18ffbee1 Sofia Papagiannaki
        super(AstakosUser, self).__init__(*args, **kwargs)
340 18ffbee1 Sofia Papagiannaki
        self.__has_signed_terms = self.has_signed_terms
341 a3637508 Sofia Papagiannaki
        if not self.id:
342 18ffbee1 Sofia Papagiannaki
            self.is_active = False
343 5ce3ce4f Sofia Papagiannaki
344 0905ccd2 Sofia Papagiannaki
    @property
345 0905ccd2 Sofia Papagiannaki
    def realname(self):
346 5ce3ce4f Sofia Papagiannaki
        return '%s %s' % (self.first_name, self.last_name)
347 6c736ed7 Kostas Papadimitriou
348 5df4c364 Kostas Papadimitriou
    @property
349 5df4c364 Kostas Papadimitriou
    def log_display(self):
350 5df4c364 Kostas Papadimitriou
        """
351 5df4c364 Kostas Papadimitriou
        Should be used in all logger.* calls that refer to a user so that
352 5df4c364 Kostas Papadimitriou
        user display is consistent across log entries.
353 5df4c364 Kostas Papadimitriou
        """
354 5d5ce247 Kostas Papadimitriou
        return '%s::%s' % (self.uuid, self.email)
355 5df4c364 Kostas Papadimitriou
356 0905ccd2 Sofia Papagiannaki
    @realname.setter
357 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
358 0905ccd2 Sofia Papagiannaki
        parts = value.split(' ')
359 0905ccd2 Sofia Papagiannaki
        if len(parts) == 2:
360 0905ccd2 Sofia Papagiannaki
            self.first_name = parts[0]
361 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[1]
362 0905ccd2 Sofia Papagiannaki
        else:
363 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[0]
364 6c736ed7 Kostas Papadimitriou
365 9a06d96f Olga Brani
    def add_permission(self, pname):
366 9a06d96f Olga Brani
        if self.has_perm(pname):
367 9a06d96f Olga Brani
            return
368 e65c21df Georgios D. Tsoukalas
        p, created = Permission.objects.get_or_create(
369 e65c21df Georgios D. Tsoukalas
                                    codename=pname,
370 e65c21df Georgios D. Tsoukalas
                                    name=pname.capitalize(),
371 e65c21df Georgios D. Tsoukalas
                                    content_type=get_content_type())
372 9a06d96f Olga Brani
        self.user_permissions.add(p)
373 9a06d96f Olga Brani
374 9a06d96f Olga Brani
    def remove_permission(self, pname):
375 9a06d96f Olga Brani
        if self.has_perm(pname):
376 9a06d96f Olga Brani
            return
377 9a06d96f Olga Brani
        p = Permission.objects.get(codename=pname,
378 e65c21df Georgios D. Tsoukalas
                                   content_type=get_content_type())
379 9a06d96f Olga Brani
        self.user_permissions.remove(p)
380 9a06d96f Olga Brani
381 8e1a5af5 Georgios D. Tsoukalas
    def is_project_admin(self, application_id=None):
382 8e1a5af5 Georgios D. Tsoukalas
        return self.uuid in PROJECT_ADMINS
383 8e1a5af5 Georgios D. Tsoukalas
384 64cd4730 Antony Chazapis
    @property
385 64cd4730 Antony Chazapis
    def invitation(self):
386 64cd4730 Antony Chazapis
        try:
387 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
388 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
389 64cd4730 Antony Chazapis
            return None
390 6c736ed7 Kostas Papadimitriou
391 9a06d96f Olga Brani
    @property
392 9a06d96f Olga Brani
    def policies(self):
393 9a06d96f Olga Brani
        return self.astakosuserquota_set.select_related().all()
394 9a06d96f Olga Brani
395 9a06d96f Olga Brani
    @policies.setter
396 9a06d96f Olga Brani
    def policies(self, policies):
397 9a06d96f Olga Brani
        for p in policies:
398 6c997921 Sofia Papagiannaki
            p.setdefault('resource', '')
399 6c997921 Sofia Papagiannaki
            p.setdefault('capacity', 0)
400 6c997921 Sofia Papagiannaki
            p.setdefault('quantity', 0)
401 6c997921 Sofia Papagiannaki
            p.setdefault('update', True)
402 6c997921 Sofia Papagiannaki
            self.add_resource_policy(**p)
403 6c997921 Sofia Papagiannaki
404 6c997921 Sofia Papagiannaki
    def add_resource_policy(
405 e496f888 Giorgos Korfiatis
            self, resource, capacity,
406 14a58548 Giorgos Korfiatis
            update=True):
407 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
408 26551b92 Kostas Papadimitriou
        resource = Resource.objects.get(name=resource)
409 9a06d96f Olga Brani
        if update:
410 6c997921 Sofia Papagiannaki
            AstakosUserQuota.objects.update_or_create(
411 6c997921 Sofia Papagiannaki
                user=self, resource=resource, defaults={
412 6c997921 Sofia Papagiannaki
                    'capacity':capacity,
413 14a58548 Giorgos Korfiatis
                    })
414 9a06d96f Olga Brani
        else:
415 9a06d96f Olga Brani
            q = self.astakosuserquota_set
416 6c997921 Sofia Papagiannaki
            q.create(
417 6c997921 Sofia Papagiannaki
                resource=resource, capacity=capacity, quanity=quantity,
418 14a58548 Giorgos Korfiatis
                )
419 9a06d96f Olga Brani
420 8d1636b5 Giorgos Korfiatis
    def get_resource_policy(self, resource):
421 26551b92 Kostas Papadimitriou
        resource = Resource.objects.get(name=resource)
422 88f4d3b3 Georgios D. Tsoukalas
        default_capacity = resource.uplimit
423 8d1636b5 Giorgos Korfiatis
        try:
424 88f4d3b3 Georgios D. Tsoukalas
            policy = AstakosUserQuota.objects.get(user=self, resource=resource)
425 88f4d3b3 Georgios D. Tsoukalas
            return policy, default_capacity
426 8d1636b5 Giorgos Korfiatis
        except AstakosUserQuota.DoesNotExist:
427 88f4d3b3 Georgios D. Tsoukalas
            return None, default_capacity
428 8d1636b5 Giorgos Korfiatis
429 6c997921 Sofia Papagiannaki
    def remove_resource_policy(self, service, resource):
430 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
431 26551b92 Kostas Papadimitriou
        resource = Resource.objects.get(name=resource)
432 9a06d96f Olga Brani
        q = self.policies.get(resource=resource).delete()
433 9a06d96f Olga Brani
434 836a0fb0 Kostas Papadimitriou
    def update_uuid(self):
435 836a0fb0 Kostas Papadimitriou
        while not self.uuid:
436 836a0fb0 Kostas Papadimitriou
            uuid_val =  str(uuid.uuid4())
437 836a0fb0 Kostas Papadimitriou
            try:
438 836a0fb0 Kostas Papadimitriou
                AstakosUser.objects.get(uuid=uuid_val)
439 836a0fb0 Kostas Papadimitriou
            except AstakosUser.DoesNotExist, e:
440 836a0fb0 Kostas Papadimitriou
                self.uuid = uuid_val
441 836a0fb0 Kostas Papadimitriou
        return self.uuid
442 836a0fb0 Kostas Papadimitriou
443 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
444 64cd4730 Antony Chazapis
        if update_timestamps:
445 64cd4730 Antony Chazapis
            if not self.id:
446 0905ccd2 Sofia Papagiannaki
                self.date_joined = datetime.now()
447 64cd4730 Antony Chazapis
            self.updated = datetime.now()
448 5ce3ce4f Sofia Papagiannaki
449 18ffbee1 Sofia Papagiannaki
        # update date_signed_terms if necessary
450 18ffbee1 Sofia Papagiannaki
        if self.__has_signed_terms != self.has_signed_terms:
451 18ffbee1 Sofia Papagiannaki
            self.date_signed_terms = datetime.now()
452 5ce3ce4f Sofia Papagiannaki
453 836a0fb0 Kostas Papadimitriou
        self.update_uuid()
454 836a0fb0 Kostas Papadimitriou
455 836a0fb0 Kostas Papadimitriou
        if self.username != self.email.lower():
456 9c01d9d1 Sofia Papagiannaki
            # set username
457 e5966bd9 Kostas Papadimitriou
            self.username = self.email.lower()
458 fbaa4f3c Kostas Papadimitriou
459 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
460 2e90e3ec Kostas Papadimitriou
461 bf0c6de5 Sofia Papagiannaki
    def renew_token(self, flush_sessions=False, current_key=None):
462 64cd4730 Antony Chazapis
        md5 = hashlib.md5()
463 8f8c43b2 Sofia Papagiannaki
        md5.update(settings.SECRET_KEY)
464 0905ccd2 Sofia Papagiannaki
        md5.update(self.username)
465 64cd4730 Antony Chazapis
        md5.update(self.realname.encode('ascii', 'ignore'))
466 64cd4730 Antony Chazapis
        md5.update(asctime())
467 2e90e3ec Kostas Papadimitriou
468 64cd4730 Antony Chazapis
        self.auth_token = b64encode(md5.digest())
469 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
470 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
471 92defad4 Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
472 bf0c6de5 Sofia Papagiannaki
        if flush_sessions:
473 bf0c6de5 Sofia Papagiannaki
            self.flush_sessions(current_key)
474 111f3da6 Sofia Papagiannaki
        msg = 'Token renewed for %s' % self.email
475 aab4d540 Sofia Papagiannaki
        logger.log(LOGGING_LEVEL, msg)
476 6c736ed7 Kostas Papadimitriou
477 bf0c6de5 Sofia Papagiannaki
    def flush_sessions(self, current_key=None):
478 bf0c6de5 Sofia Papagiannaki
        q = self.sessions
479 bf0c6de5 Sofia Papagiannaki
        if current_key:
480 bf0c6de5 Sofia Papagiannaki
            q = q.exclude(session_key=current_key)
481 2e90e3ec Kostas Papadimitriou
482 bf0c6de5 Sofia Papagiannaki
        keys = q.values_list('session_key', flat=True)
483 bf0c6de5 Sofia Papagiannaki
        if keys:
484 bf0c6de5 Sofia Papagiannaki
            msg = 'Flushing sessions: %s' % ','.join(keys)
485 c0b26605 Sofia Papagiannaki
            logger.log(LOGGING_LEVEL, msg, [])
486 bf0c6de5 Sofia Papagiannaki
        engine = import_module(settings.SESSION_ENGINE)
487 bf0c6de5 Sofia Papagiannaki
        for k in keys:
488 bf0c6de5 Sofia Papagiannaki
            s = engine.SessionStore(k)
489 bf0c6de5 Sofia Papagiannaki
            s.flush()
490 bf0c6de5 Sofia Papagiannaki
491 64cd4730 Antony Chazapis
    def __unicode__(self):
492 3abf6c78 Sofia Papagiannaki
        return '%s (%s)' % (self.realname, self.email)
493 5ce3ce4f Sofia Papagiannaki
494 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
495 5ce3ce4f Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username=self.username)
496 789a5951 Sofia Papagiannaki
        q = q.filter(email__iexact=self.email)
497 0a569195 Sofia Papagiannaki
        if q.count() != 0:
498 0a569195 Sofia Papagiannaki
            return True
499 0a569195 Sofia Papagiannaki
        return False
500 5ce3ce4f Sofia Papagiannaki
501 34a76cdb Kostas Papadimitriou
    def email_change_is_pending(self):
502 34a76cdb Kostas Papadimitriou
        return self.emailchanges.count() > 0
503 34a76cdb Kostas Papadimitriou
504 fcf90160 Sofia Papagiannaki
    @property
505 09e7393c Sofia Papagiannaki
    def signed_terms(self):
506 09e7393c Sofia Papagiannaki
        term = get_latest_terms()
507 09e7393c Sofia Papagiannaki
        if not term:
508 09e7393c Sofia Papagiannaki
            return True
509 09e7393c Sofia Papagiannaki
        if not self.has_signed_terms:
510 09e7393c Sofia Papagiannaki
            return False
511 09e7393c Sofia Papagiannaki
        if not self.date_signed_terms:
512 09e7393c Sofia Papagiannaki
            return False
513 09e7393c Sofia Papagiannaki
        if self.date_signed_terms < term.date:
514 09e7393c Sofia Papagiannaki
            self.has_signed_terms = False
515 f0f92965 Sofia Papagiannaki
            self.date_signed_terms = None
516 09e7393c Sofia Papagiannaki
            self.save()
517 09e7393c Sofia Papagiannaki
            return False
518 09e7393c Sofia Papagiannaki
        return True
519 09e7393c Sofia Papagiannaki
520 d2633501 Kostas Papadimitriou
    def set_invitations_level(self):
521 d2633501 Kostas Papadimitriou
        """
522 d2633501 Kostas Papadimitriou
        Update user invitation level
523 d2633501 Kostas Papadimitriou
        """
524 d2633501 Kostas Papadimitriou
        level = self.invitation.inviter.level + 1
525 d2633501 Kostas Papadimitriou
        self.level = level
526 d2633501 Kostas Papadimitriou
        self.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
527 d2633501 Kostas Papadimitriou
528 9d20fe23 Kostas Papadimitriou
    def can_change_password(self):
529 9d20fe23 Kostas Papadimitriou
        return self.has_auth_provider('local', auth_backend='astakos')
530 d2633501 Kostas Papadimitriou
531 9d20fe23 Kostas Papadimitriou
    def can_change_email(self):
532 9d20fe23 Kostas Papadimitriou
        if not self.has_auth_provider('local'):
533 9d20fe23 Kostas Papadimitriou
            return True
534 63836eda Kostas Papadimitriou
535 9d20fe23 Kostas Papadimitriou
        local = self.get_auth_provider('local')._instance
536 9d20fe23 Kostas Papadimitriou
        return local.auth_backend == 'astakos'
537 63836eda Kostas Papadimitriou
538 9d20fe23 Kostas Papadimitriou
    # Auth providers related methods
539 9d20fe23 Kostas Papadimitriou
    def get_auth_provider(self, module=None, identifier=None, **filters):
540 9d20fe23 Kostas Papadimitriou
        if not module:
541 9d20fe23 Kostas Papadimitriou
            return self.auth_providers.active()[0].settings
542 63836eda Kostas Papadimitriou
543 97246b51 Kostas Papadimitriou
        params = {'module': module}
544 97246b51 Kostas Papadimitriou
        if identifier:
545 97246b51 Kostas Papadimitriou
            params['identifier'] = identifier
546 97246b51 Kostas Papadimitriou
        params.update(filters)
547 97246b51 Kostas Papadimitriou
        return self.auth_providers.active().get(**params).settings
548 d2633501 Kostas Papadimitriou
549 9d20fe23 Kostas Papadimitriou
    def has_auth_provider(self, provider, **kwargs):
550 9d20fe23 Kostas Papadimitriou
        return bool(self.auth_providers.active().filter(module=provider,
551 9d20fe23 Kostas Papadimitriou
                                                        **kwargs).count())
552 d2633501 Kostas Papadimitriou
553 9d20fe23 Kostas Papadimitriou
    def get_required_providers(self, **kwargs):
554 9d20fe23 Kostas Papadimitriou
        return auth.REQUIRED_PROVIDERS.keys()
555 649f2d36 Kostas Papadimitriou
556 9d20fe23 Kostas Papadimitriou
    def missing_required_providers(self):
557 9d20fe23 Kostas Papadimitriou
        required = self.get_required_providers()
558 9d20fe23 Kostas Papadimitriou
        missing = []
559 63836eda Kostas Papadimitriou
        for provider in required:
560 63836eda Kostas Papadimitriou
            if not self.has_auth_provider(provider):
561 9d20fe23 Kostas Papadimitriou
                missing.append(auth.get_provider(provider, self))
562 9d20fe23 Kostas Papadimitriou
        return missing
563 63836eda Kostas Papadimitriou
564 9d20fe23 Kostas Papadimitriou
    def get_available_auth_providers(self, **filters):
565 d2633501 Kostas Papadimitriou
        """
566 9d20fe23 Kostas Papadimitriou
        Returns a list of providers available for add by the user.
567 d2633501 Kostas Papadimitriou
        """
568 9d20fe23 Kostas Papadimitriou
        modules = astakos_settings.IM_MODULES
569 9d20fe23 Kostas Papadimitriou
        providers = []
570 9d20fe23 Kostas Papadimitriou
        for p in modules:
571 9d20fe23 Kostas Papadimitriou
            providers.append(auth.get_provider(p, self))
572 9d20fe23 Kostas Papadimitriou
        available = []
573 9d20fe23 Kostas Papadimitriou
574 9d20fe23 Kostas Papadimitriou
        for p in providers:
575 9d20fe23 Kostas Papadimitriou
            if p.get_add_policy:
576 9d20fe23 Kostas Papadimitriou
                available.append(p)
577 9d20fe23 Kostas Papadimitriou
        return available
578 9d20fe23 Kostas Papadimitriou
579 9d20fe23 Kostas Papadimitriou
    def get_disabled_auth_providers(self, **filters):
580 9d20fe23 Kostas Papadimitriou
        providers = self.get_auth_providers(**filters)
581 9d20fe23 Kostas Papadimitriou
        disabled = []
582 9d20fe23 Kostas Papadimitriou
        for p in providers:
583 9d20fe23 Kostas Papadimitriou
            if not p.get_login_policy:
584 9d20fe23 Kostas Papadimitriou
                disabled.append(p)
585 9d20fe23 Kostas Papadimitriou
        return disabled
586 9d20fe23 Kostas Papadimitriou
587 9d20fe23 Kostas Papadimitriou
    def get_enabled_auth_providers(self, **filters):
588 9d20fe23 Kostas Papadimitriou
        providers = self.get_auth_providers(**filters)
589 9d20fe23 Kostas Papadimitriou
        enabled = []
590 9d20fe23 Kostas Papadimitriou
        for p in providers:
591 9d20fe23 Kostas Papadimitriou
            if p.get_login_policy:
592 9d20fe23 Kostas Papadimitriou
                enabled.append(p)
593 9d20fe23 Kostas Papadimitriou
        return enabled
594 9d20fe23 Kostas Papadimitriou
595 9d20fe23 Kostas Papadimitriou
    def get_auth_providers(self, **filters):
596 9d20fe23 Kostas Papadimitriou
        providers = []
597 9d20fe23 Kostas Papadimitriou
        for provider in self.auth_providers.active(**filters):
598 9d20fe23 Kostas Papadimitriou
            if provider.settings.module_enabled:
599 9d20fe23 Kostas Papadimitriou
                providers.append(provider.settings)
600 d2633501 Kostas Papadimitriou
601 9d20fe23 Kostas Papadimitriou
        modules = astakos_settings.IM_MODULES
602 d2633501 Kostas Papadimitriou
603 9d20fe23 Kostas Papadimitriou
        def key(p):
604 9d20fe23 Kostas Papadimitriou
            if not p.module in modules:
605 9d20fe23 Kostas Papadimitriou
                return 100
606 9d20fe23 Kostas Papadimitriou
            return modules.index(p.module)
607 9d20fe23 Kostas Papadimitriou
608 9d20fe23 Kostas Papadimitriou
        providers = sorted(providers, key=key)
609 9d20fe23 Kostas Papadimitriou
        return providers
610 d2633501 Kostas Papadimitriou
611 9d20fe23 Kostas Papadimitriou
    # URL methods
612 9d20fe23 Kostas Papadimitriou
    @property
613 9d20fe23 Kostas Papadimitriou
    def auth_providers_display(self):
614 9d20fe23 Kostas Papadimitriou
        return ",".join(["%s:%s" % (p.module, p.get_username_msg) for p in
615 9d20fe23 Kostas Papadimitriou
                         self.get_enabled_auth_providers()])
616 d2633501 Kostas Papadimitriou
617 9d20fe23 Kostas Papadimitriou
    def add_auth_provider(self, module='local', identifier=None, **params):
618 9d20fe23 Kostas Papadimitriou
        provider = auth.get_provider(module, self, identifier, **params)
619 9d20fe23 Kostas Papadimitriou
        provider.add_to_user()
620 d2633501 Kostas Papadimitriou
621 d2633501 Kostas Papadimitriou
    def get_resend_activation_url(self):
622 c630fee6 Kostas Papadimitriou
        return reverse('send_activation', kwargs={'user_id': self.pk})
623 c630fee6 Kostas Papadimitriou
624 d2633501 Kostas Papadimitriou
    def get_activation_url(self, nxt=False):
625 d2633501 Kostas Papadimitriou
        url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
626 d2633501 Kostas Papadimitriou
                                 quote(self.auth_token))
627 d2633501 Kostas Papadimitriou
        if nxt:
628 d2633501 Kostas Papadimitriou
            url += "&next=%s" % quote(nxt)
629 d2633501 Kostas Papadimitriou
        return url
630 d2633501 Kostas Papadimitriou
631 d2633501 Kostas Papadimitriou
    def get_password_reset_url(self, token_generator=default_token_generator):
632 d2633501 Kostas Papadimitriou
        return reverse('django.contrib.auth.views.password_reset_confirm',
633 d2633501 Kostas Papadimitriou
                          kwargs={'uidb36':int_to_base36(self.id),
634 d2633501 Kostas Papadimitriou
                                  'token':token_generator.make_token(self)})
635 d2633501 Kostas Papadimitriou
636 9d20fe23 Kostas Papadimitriou
    def get_inactive_message(self, provider_module, identifier=None):
637 9d20fe23 Kostas Papadimitriou
        provider = self.get_auth_provider(provider_module, identifier)
638 d2633501 Kostas Papadimitriou
639 c4b1a172 Kostas Papadimitriou
        msg_extra = ''
640 c4b1a172 Kostas Papadimitriou
        message = ''
641 9d20fe23 Kostas Papadimitriou
642 9d20fe23 Kostas Papadimitriou
        msg_inactive = provider.get_account_inactive_msg
643 9d20fe23 Kostas Papadimitriou
        msg_pending = provider.get_pending_activation_msg
644 9d20fe23 Kostas Papadimitriou
        msg_pending_help = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP)
645 9d20fe23 Kostas Papadimitriou
        #msg_resend_prompt = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION)
646 9d20fe23 Kostas Papadimitriou
        msg_pending_mod = provider.get_pending_moderation_msg
647 9d20fe23 Kostas Papadimitriou
        msg_resend = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION)
648 9d20fe23 Kostas Papadimitriou
649 c4b1a172 Kostas Papadimitriou
        if self.activation_sent:
650 c4b1a172 Kostas Papadimitriou
            if self.email_verified:
651 9d20fe23 Kostas Papadimitriou
                message = msg_inactive
652 c4b1a172 Kostas Papadimitriou
            else:
653 9d20fe23 Kostas Papadimitriou
                message = msg_pending
654 9d20fe23 Kostas Papadimitriou
                url = self.get_resend_activation_url()
655 9d20fe23 Kostas Papadimitriou
                msg_extra = msg_pending_help + \
656 9d20fe23 Kostas Papadimitriou
                            u' ' + \
657 9d20fe23 Kostas Papadimitriou
                            '<a href="%s">%s?</a>' % (url, msg_resend)
658 c4b1a172 Kostas Papadimitriou
        else:
659 fc655b6f Kostas Papadimitriou
            if astakos_settings.MODERATION_ENABLED:
660 9d20fe23 Kostas Papadimitriou
                message = msg_pending_mod
661 c4b1a172 Kostas Papadimitriou
            else:
662 9d20fe23 Kostas Papadimitriou
                message = msg_pending
663 c4b1a172 Kostas Papadimitriou
                url = self.get_resend_activation_url()
664 9d20fe23 Kostas Papadimitriou
                msg_extra = '<a href="%s">%s?</a>' % (url, \
665 9d20fe23 Kostas Papadimitriou
                                msg_resend)
666 c4b1a172 Kostas Papadimitriou
667 a15a19b2 Kostas Papadimitriou
        return mark_safe(message + u' '+ msg_extra)
668 c4b1a172 Kostas Papadimitriou
669 7184f408 Giorgos Korfiatis
    def owns_application(self, application):
670 7184f408 Giorgos Korfiatis
        return application.owner == self
671 7184f408 Giorgos Korfiatis
672 e87bbb41 Kostas Papadimitriou
    def owns_project(self, project):
673 7184f408 Giorgos Korfiatis
        return project.application.owner == self
674 e87bbb41 Kostas Papadimitriou
675 d4660e00 Giorgos Korfiatis
    def is_associated(self, project):
676 d4660e00 Giorgos Korfiatis
        try:
677 d4660e00 Giorgos Korfiatis
            m = ProjectMembership.objects.get(person=self, project=project)
678 d4660e00 Giorgos Korfiatis
            return m.state in ProjectMembership.ASSOCIATED_STATES
679 d4660e00 Giorgos Korfiatis
        except ProjectMembership.DoesNotExist:
680 d4660e00 Giorgos Korfiatis
            return False
681 d4660e00 Giorgos Korfiatis
682 aad0e329 Giorgos Korfiatis
    def get_membership(self, project):
683 aad0e329 Giorgos Korfiatis
        try:
684 aad0e329 Giorgos Korfiatis
            return ProjectMembership.objects.get(
685 aad0e329 Giorgos Korfiatis
                project=project,
686 aad0e329 Giorgos Korfiatis
                person=self)
687 aad0e329 Giorgos Korfiatis
        except ProjectMembership.DoesNotExist:
688 aad0e329 Giorgos Korfiatis
            return None
689 d2633501 Kostas Papadimitriou
690 d4660e00 Giorgos Korfiatis
    def membership_display(self, project):
691 d4660e00 Giorgos Korfiatis
        m = self.get_membership(project)
692 d4660e00 Giorgos Korfiatis
        if m is None:
693 d4660e00 Giorgos Korfiatis
            return _('Not a member')
694 d4660e00 Giorgos Korfiatis
        else:
695 d4660e00 Giorgos Korfiatis
            return m.user_friendly_state_display()
696 d4660e00 Giorgos Korfiatis
697 7f31a7a3 Giorgos Korfiatis
    def non_owner_can_view(self, maybe_project):
698 8e1a5af5 Georgios D. Tsoukalas
        if self.is_project_admin():
699 8e1a5af5 Georgios D. Tsoukalas
            return True
700 7f31a7a3 Giorgos Korfiatis
        if maybe_project is None:
701 7f31a7a3 Giorgos Korfiatis
            return False
702 7f31a7a3 Giorgos Korfiatis
        project = maybe_project
703 7f31a7a3 Giorgos Korfiatis
        if self.is_associated(project):
704 7f31a7a3 Giorgos Korfiatis
            return True
705 7f31a7a3 Giorgos Korfiatis
        if project.is_deactivated():
706 7f31a7a3 Giorgos Korfiatis
            return False
707 7f31a7a3 Giorgos Korfiatis
        return True
708 7f31a7a3 Giorgos Korfiatis
709 c7c0ec58 Giorgos Korfiatis
    def settings(self):
710 c7c0ec58 Giorgos Korfiatis
        return UserSetting.objects.filter(user=self)
711 c7c0ec58 Giorgos Korfiatis
712 a989b48e Giorgos Korfiatis
713 d2633501 Kostas Papadimitriou
class AstakosUserAuthProviderManager(models.Manager):
714 d2633501 Kostas Papadimitriou
715 63836eda Kostas Papadimitriou
    def active(self, **filters):
716 63836eda Kostas Papadimitriou
        return self.filter(active=True, **filters)
717 d2633501 Kostas Papadimitriou
718 564a2292 Kostas Papadimitriou
    def remove_unverified_providers(self, provider, **filters):
719 564a2292 Kostas Papadimitriou
        try:
720 9d20fe23 Kostas Papadimitriou
            existing = self.filter(module=provider, user__email_verified=False,
721 9d20fe23 Kostas Papadimitriou
                                   **filters)
722 564a2292 Kostas Papadimitriou
            for p in existing:
723 564a2292 Kostas Papadimitriou
                p.user.delete()
724 564a2292 Kostas Papadimitriou
        except:
725 564a2292 Kostas Papadimitriou
            pass
726 564a2292 Kostas Papadimitriou
727 9d20fe23 Kostas Papadimitriou
    def unverified(self, provider, **filters):
728 9d20fe23 Kostas Papadimitriou
        try:
729 9d20fe23 Kostas Papadimitriou
            return self.get(module=provider, user__email_verified=False,
730 9d20fe23 Kostas Papadimitriou
                            **filters).settings
731 9d20fe23 Kostas Papadimitriou
        except AstakosUserAuthProvider.DoesNotExist:
732 9d20fe23 Kostas Papadimitriou
            return None
733 9d20fe23 Kostas Papadimitriou
734 9d20fe23 Kostas Papadimitriou
    def verified(self, provider, **filters):
735 9d20fe23 Kostas Papadimitriou
        try:
736 9d20fe23 Kostas Papadimitriou
            return self.get(module=provider, user__email_verified=True,
737 9d20fe23 Kostas Papadimitriou
                            **filters).settings
738 9d20fe23 Kostas Papadimitriou
        except AstakosUserAuthProvider.DoesNotExist:
739 9d20fe23 Kostas Papadimitriou
            return None
740 9d20fe23 Kostas Papadimitriou
741 9d20fe23 Kostas Papadimitriou
742 9d20fe23 Kostas Papadimitriou
class AuthProviderPolicyProfileManager(models.Manager):
743 9d20fe23 Kostas Papadimitriou
744 9d20fe23 Kostas Papadimitriou
    def active(self):
745 9d20fe23 Kostas Papadimitriou
        return self.filter(active=True)
746 9d20fe23 Kostas Papadimitriou
747 9d20fe23 Kostas Papadimitriou
    def for_user(self, user, provider):
748 9d20fe23 Kostas Papadimitriou
        policies = {}
749 9d20fe23 Kostas Papadimitriou
        exclusive_q1 = Q(provider=provider) & Q(is_exclusive=False)
750 9d20fe23 Kostas Papadimitriou
        exclusive_q2 = ~Q(provider=provider) & Q(is_exclusive=True)
751 9d20fe23 Kostas Papadimitriou
        exclusive_q = exclusive_q1 | exclusive_q2
752 9d20fe23 Kostas Papadimitriou
753 9d20fe23 Kostas Papadimitriou
        for profile in user.authpolicy_profiles.active().filter(exclusive_q):
754 9d20fe23 Kostas Papadimitriou
            policies.update(profile.policies)
755 9d20fe23 Kostas Papadimitriou
756 9d20fe23 Kostas Papadimitriou
        user_groups = user.groups.all().values('pk')
757 9d20fe23 Kostas Papadimitriou
        for profile in self.active().filter(groups__in=user_groups).filter(
758 9d20fe23 Kostas Papadimitriou
                exclusive_q):
759 9d20fe23 Kostas Papadimitriou
            policies.update(profile.policies)
760 9d20fe23 Kostas Papadimitriou
        return policies
761 9d20fe23 Kostas Papadimitriou
762 9d20fe23 Kostas Papadimitriou
    def add_policy(self, name, provider, group_or_user, exclusive=False,
763 9d20fe23 Kostas Papadimitriou
                   **policies):
764 9d20fe23 Kostas Papadimitriou
        is_group = isinstance(group_or_user, Group)
765 9d20fe23 Kostas Papadimitriou
        profile, created = self.get_or_create(name=name, provider=provider,
766 9d20fe23 Kostas Papadimitriou
                                              is_exclusive=exclusive)
767 9d20fe23 Kostas Papadimitriou
        profile.is_exclusive = exclusive
768 9d20fe23 Kostas Papadimitriou
        profile.save()
769 9d20fe23 Kostas Papadimitriou
        if is_group:
770 9d20fe23 Kostas Papadimitriou
            profile.groups.add(group_or_user)
771 9d20fe23 Kostas Papadimitriou
        else:
772 9d20fe23 Kostas Papadimitriou
            profile.users.add(group_or_user)
773 9d20fe23 Kostas Papadimitriou
        profile.set_policies(policies)
774 9d20fe23 Kostas Papadimitriou
        profile.save()
775 9d20fe23 Kostas Papadimitriou
        return profile
776 9d20fe23 Kostas Papadimitriou
777 9d20fe23 Kostas Papadimitriou
778 9d20fe23 Kostas Papadimitriou
class AuthProviderPolicyProfile(models.Model):
779 9d20fe23 Kostas Papadimitriou
    name = models.CharField(_('Name'), max_length=255, blank=False,
780 9d20fe23 Kostas Papadimitriou
                            null=False, db_index=True)
781 9d20fe23 Kostas Papadimitriou
    provider = models.CharField(_('Provider'), max_length=255, blank=False,
782 9d20fe23 Kostas Papadimitriou
                                null=False)
783 9d20fe23 Kostas Papadimitriou
784 9d20fe23 Kostas Papadimitriou
    # apply policies to all providers excluding the one set in provider field
785 9d20fe23 Kostas Papadimitriou
    is_exclusive = models.BooleanField(default=False)
786 9d20fe23 Kostas Papadimitriou
787 9d20fe23 Kostas Papadimitriou
    policy_add = models.NullBooleanField(null=True, default=None)
788 9d20fe23 Kostas Papadimitriou
    policy_remove = models.NullBooleanField(null=True, default=None)
789 9d20fe23 Kostas Papadimitriou
    policy_create = models.NullBooleanField(null=True, default=None)
790 9d20fe23 Kostas Papadimitriou
    policy_login = models.NullBooleanField(null=True, default=None)
791 9d20fe23 Kostas Papadimitriou
    policy_limit = models.IntegerField(null=True, default=None)
792 9d20fe23 Kostas Papadimitriou
    policy_required = models.NullBooleanField(null=True, default=None)
793 9d20fe23 Kostas Papadimitriou
    policy_automoderate = models.NullBooleanField(null=True, default=None)
794 9d20fe23 Kostas Papadimitriou
    policy_switch = models.NullBooleanField(null=True, default=None)
795 9d20fe23 Kostas Papadimitriou
796 9d20fe23 Kostas Papadimitriou
    POLICY_FIELDS = ('add', 'remove', 'create', 'login', 'limit', 'required',
797 9d20fe23 Kostas Papadimitriou
                     'automoderate')
798 9d20fe23 Kostas Papadimitriou
799 9d20fe23 Kostas Papadimitriou
    priority = models.IntegerField(null=False, default=1)
800 9d20fe23 Kostas Papadimitriou
    groups = models.ManyToManyField(Group, related_name='authpolicy_profiles')
801 9d20fe23 Kostas Papadimitriou
    users = models.ManyToManyField(AstakosUser,
802 9d20fe23 Kostas Papadimitriou
                                   related_name='authpolicy_profiles')
803 9d20fe23 Kostas Papadimitriou
    active = models.BooleanField(default=True)
804 9d20fe23 Kostas Papadimitriou
805 9d20fe23 Kostas Papadimitriou
    objects = AuthProviderPolicyProfileManager()
806 9d20fe23 Kostas Papadimitriou
807 9d20fe23 Kostas Papadimitriou
    class Meta:
808 9d20fe23 Kostas Papadimitriou
        ordering = ['priority']
809 9d20fe23 Kostas Papadimitriou
810 9d20fe23 Kostas Papadimitriou
    @property
811 9d20fe23 Kostas Papadimitriou
    def policies(self):
812 9d20fe23 Kostas Papadimitriou
        policies = {}
813 9d20fe23 Kostas Papadimitriou
        for pkey in self.POLICY_FIELDS:
814 9d20fe23 Kostas Papadimitriou
            value = getattr(self, 'policy_%s' % pkey, None)
815 9d20fe23 Kostas Papadimitriou
            if value is None:
816 9d20fe23 Kostas Papadimitriou
                continue
817 9d20fe23 Kostas Papadimitriou
            policies[pkey] = value
818 9d20fe23 Kostas Papadimitriou
        return policies
819 9d20fe23 Kostas Papadimitriou
820 9d20fe23 Kostas Papadimitriou
    def set_policies(self, policies_dict):
821 9d20fe23 Kostas Papadimitriou
        for key, value in policies_dict.iteritems():
822 9d20fe23 Kostas Papadimitriou
            if key in self.POLICY_FIELDS:
823 9d20fe23 Kostas Papadimitriou
                setattr(self, 'policy_%s' % key, value)
824 9d20fe23 Kostas Papadimitriou
        return self.policies
825 564a2292 Kostas Papadimitriou
826 d2633501 Kostas Papadimitriou
827 d2633501 Kostas Papadimitriou
class AstakosUserAuthProvider(models.Model):
828 d2633501 Kostas Papadimitriou
    """
829 d2633501 Kostas Papadimitriou
    Available user authentication methods.
830 d2633501 Kostas Papadimitriou
    """
831 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
832 d2633501 Kostas Papadimitriou
                                   null=True, default=None)
833 d2633501 Kostas Papadimitriou
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
834 e1a80257 Sofia Papagiannaki
    module = models.CharField(_('Provider'), max_length=255, blank=False,
835 d2633501 Kostas Papadimitriou
                                default='local')
836 e1a80257 Sofia Papagiannaki
    identifier = models.CharField(_('Third-party identifier'),
837 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
838 d2633501 Kostas Papadimitriou
                                              blank=True)
839 d2633501 Kostas Papadimitriou
    active = models.BooleanField(default=True)
840 e1a80257 Sofia Papagiannaki
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
841 d2633501 Kostas Papadimitriou
                                   default='astakos')
842 3a72a5d4 Kostas Papadimitriou
    info_data = models.TextField(default="", null=True, blank=True)
843 c630fee6 Kostas Papadimitriou
    created = models.DateTimeField('Creation date', auto_now_add=True)
844 d2633501 Kostas Papadimitriou
845 d2633501 Kostas Papadimitriou
    objects = AstakosUserAuthProviderManager()
846 d2633501 Kostas Papadimitriou
847 d2633501 Kostas Papadimitriou
    class Meta:
848 d2633501 Kostas Papadimitriou
        unique_together = (('identifier', 'module', 'user'), )
849 c630fee6 Kostas Papadimitriou
        ordering = ('module', 'created')
850 d2633501 Kostas Papadimitriou
851 3a72a5d4 Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
852 3a72a5d4 Kostas Papadimitriou
        super(AstakosUserAuthProvider, self).__init__(*args, **kwargs)
853 3a72a5d4 Kostas Papadimitriou
        try:
854 3a72a5d4 Kostas Papadimitriou
            self.info = json.loads(self.info_data)
855 c630fee6 Kostas Papadimitriou
            if not self.info:
856 c630fee6 Kostas Papadimitriou
                self.info = {}
857 c630fee6 Kostas Papadimitriou
        except Exception, e:
858 3a72a5d4 Kostas Papadimitriou
            self.info = {}
859 c630fee6 Kostas Papadimitriou
860 3a72a5d4 Kostas Papadimitriou
        for key,value in self.info.iteritems():
861 3a72a5d4 Kostas Papadimitriou
            setattr(self, 'info_%s' % key, value)
862 3a72a5d4 Kostas Papadimitriou
863 d2633501 Kostas Papadimitriou
    @property
864 d2633501 Kostas Papadimitriou
    def settings(self):
865 9d20fe23 Kostas Papadimitriou
        extra_data = {}
866 d2633501 Kostas Papadimitriou
867 9d20fe23 Kostas Papadimitriou
        info_data = {}
868 9d20fe23 Kostas Papadimitriou
        if self.info_data:
869 9d20fe23 Kostas Papadimitriou
            info_data = json.loads(self.info_data)
870 3a72a5d4 Kostas Papadimitriou
871 9d20fe23 Kostas Papadimitriou
        extra_data['info'] = info_data
872 d2633501 Kostas Papadimitriou
873 9d20fe23 Kostas Papadimitriou
        for key in ['active', 'auth_backend', 'created', 'pk', 'affiliation']:
874 9d20fe23 Kostas Papadimitriou
            extra_data[key] = getattr(self, key)
875 d2633501 Kostas Papadimitriou
876 9d20fe23 Kostas Papadimitriou
        extra_data['instance'] = self
877 9d20fe23 Kostas Papadimitriou
        return auth.get_provider(self.module, self.user,
878 9d20fe23 Kostas Papadimitriou
                                           self.identifier, **extra_data)
879 d2633501 Kostas Papadimitriou
880 f432088a Kostas Papadimitriou
    def __repr__(self):
881 f432088a Kostas Papadimitriou
        return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
882 f432088a Kostas Papadimitriou
883 b778b6fa Kostas Papadimitriou
    def __unicode__(self):
884 b778b6fa Kostas Papadimitriou
        if self.identifier:
885 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.identifier)
886 b778b6fa Kostas Papadimitriou
        if self.auth_backend:
887 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.auth_backend)
888 b778b6fa Kostas Papadimitriou
        return self.module
889 b778b6fa Kostas Papadimitriou
890 3a72a5d4 Kostas Papadimitriou
    def save(self, *args, **kwargs):
891 3a72a5d4 Kostas Papadimitriou
        self.info_data = json.dumps(self.info)
892 3a72a5d4 Kostas Papadimitriou
        return super(AstakosUserAuthProvider, self).save(*args, **kwargs)
893 b778b6fa Kostas Papadimitriou
894 d2633501 Kostas Papadimitriou
895 e1a80257 Sofia Papagiannaki
class ExtendedManager(models.Manager):
896 9a06d96f Olga Brani
    def _update_or_create(self, **kwargs):
897 9a06d96f Olga Brani
        assert kwargs, \
898 9a06d96f Olga Brani
            'update_or_create() must be passed at least one keyword argument'
899 9a06d96f Olga Brani
        obj, created = self.get_or_create(**kwargs)
900 9a06d96f Olga Brani
        defaults = kwargs.pop('defaults', {})
901 9a06d96f Olga Brani
        if created:
902 9a06d96f Olga Brani
            return obj, True, False
903 9a06d96f Olga Brani
        else:
904 9a06d96f Olga Brani
            try:
905 9a06d96f Olga Brani
                params = dict(
906 9a06d96f Olga Brani
                    [(k, v) for k, v in kwargs.items() if '__' not in k])
907 9a06d96f Olga Brani
                params.update(defaults)
908 9a06d96f Olga Brani
                for attr, val in params.items():
909 9a06d96f Olga Brani
                    if hasattr(obj, attr):
910 9a06d96f Olga Brani
                        setattr(obj, attr, val)
911 9a06d96f Olga Brani
                sid = transaction.savepoint()
912 9a06d96f Olga Brani
                obj.save(force_update=True)
913 9a06d96f Olga Brani
                transaction.savepoint_commit(sid)
914 9a06d96f Olga Brani
                return obj, False, True
915 9a06d96f Olga Brani
            except IntegrityError, e:
916 9a06d96f Olga Brani
                transaction.savepoint_rollback(sid)
917 9a06d96f Olga Brani
                try:
918 9a06d96f Olga Brani
                    return self.get(**kwargs), False, False
919 9a06d96f Olga Brani
                except self.model.DoesNotExist:
920 9a06d96f Olga Brani
                    raise e
921 9a06d96f Olga Brani
922 9a06d96f Olga Brani
    update_or_create = _update_or_create
923 5ce3ce4f Sofia Papagiannaki
924 8e45d6fd Sofia Papagiannaki
925 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
926 e1a80257 Sofia Papagiannaki
    objects = ExtendedManager()
927 9deadfcd Georgios D. Tsoukalas
    capacity = intDecimalField()
928 9deadfcd Georgios D. Tsoukalas
    quantity = intDecimalField(default=0)
929 9deadfcd Georgios D. Tsoukalas
    export_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
930 9deadfcd Georgios D. Tsoukalas
    import_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
931 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
932 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
933 5ce3ce4f Sofia Papagiannaki
934 8e45d6fd Sofia Papagiannaki
    class Meta:
935 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
936 09e7393c Sofia Papagiannaki
937 5ce3ce4f Sofia Papagiannaki
938 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
939 270dd48d Sofia Papagiannaki
    """
940 270dd48d Sofia Papagiannaki
    Model for approval terms
941 270dd48d Sofia Papagiannaki
    """
942 6c736ed7 Kostas Papadimitriou
943 5ce3ce4f Sofia Papagiannaki
    date = models.DateTimeField(
944 3c638f72 Giorgos Korfiatis
        _('Issue date'), db_index=True, auto_now_add=True)
945 e1a80257 Sofia Papagiannaki
    location = models.CharField(_('Terms location'), max_length=255)
946 270dd48d Sofia Papagiannaki
947 5ce3ce4f Sofia Papagiannaki
948 64cd4730 Antony Chazapis
class Invitation(models.Model):
949 890b0eaf Sofia Papagiannaki
    """
950 890b0eaf Sofia Papagiannaki
    Model for registring invitations
951 890b0eaf Sofia Papagiannaki
    """
952 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
953 64cd4730 Antony Chazapis
                                null=True)
954 e1a80257 Sofia Papagiannaki
    realname = models.CharField(_('Real name'), max_length=255)
955 e1a80257 Sofia Papagiannaki
    username = models.CharField(_('Unique ID'), max_length=255, unique=True)
956 e1a80257 Sofia Papagiannaki
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
957 e1a80257 Sofia Papagiannaki
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
958 e1a80257 Sofia Papagiannaki
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
959 e1a80257 Sofia Papagiannaki
    consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True)
960 5ce3ce4f Sofia Papagiannaki
961 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
962 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
963 8f5a3a06 Sofia Papagiannaki
        if not self.id:
964 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
965 5ce3ce4f Sofia Papagiannaki
966 64cd4730 Antony Chazapis
    def consume(self):
967 64cd4730 Antony Chazapis
        self.is_consumed = True
968 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
969 64cd4730 Antony Chazapis
        self.save()
970 6c736ed7 Kostas Papadimitriou
971 64cd4730 Antony Chazapis
    def __unicode__(self):
972 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
973 9c01d9d1 Sofia Papagiannaki
974 49790d9d Sofia Papagiannaki
975 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
976 34a76cdb Kostas Papadimitriou
977 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
978 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
979 49790d9d Sofia Papagiannaki
        """
980 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
981 49790d9d Sofia Papagiannaki
        ``User`` if valid.
982 49790d9d Sofia Papagiannaki

983 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
984 49790d9d Sofia Papagiannaki
        after activating.
985 49790d9d Sofia Papagiannaki

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

988 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
989 49790d9d Sofia Papagiannaki
        return ``None``.
990 49790d9d Sofia Papagiannaki

991 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
992 49790d9d Sofia Papagiannaki

993 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
994 49790d9d Sofia Papagiannaki
        """
995 49790d9d Sofia Papagiannaki
        try:
996 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
997 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
998 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
999 49790d9d Sofia Papagiannaki
                email_change.delete()
1000 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
1001 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
1002 49790d9d Sofia Papagiannaki
            try:
1003 789a5951 Sofia Papagiannaki
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
1004 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
1005 49790d9d Sofia Papagiannaki
                pass
1006 49790d9d Sofia Papagiannaki
            else:
1007 73fbaec4 Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
1008 49790d9d Sofia Papagiannaki
            # update user
1009 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
1010 34a76cdb Kostas Papadimitriou
            old_email = user.email
1011 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
1012 49790d9d Sofia Papagiannaki
            user.save()
1013 49790d9d Sofia Papagiannaki
            email_change.delete()
1014 5d5ce247 Kostas Papadimitriou
            msg = "User %s changed email from %s to %s" % (user.log_display,
1015 5d5ce247 Kostas Papadimitriou
                                                           old_email,
1016 5d5ce247 Kostas Papadimitriou
                                                           user.email)
1017 34a76cdb Kostas Papadimitriou
            logger.log(LOGGING_LEVEL, msg)
1018 49790d9d Sofia Papagiannaki
            return user
1019 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
1020 73fbaec4 Sofia Papagiannaki
            raise ValueError(_('Invalid activation key.'))
1021 49790d9d Sofia Papagiannaki
1022 49790d9d Sofia Papagiannaki
1023 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
1024 73fbaec4 Sofia Papagiannaki
    new_email_address = models.EmailField(
1025 73fbaec4 Sofia Papagiannaki
        _(u'new e-mail address'),
1026 b8b8fdde Constantinos Venetsanopoulos
        help_text=_('Provide a new email address. Until you verify the new '
1027 b8b8fdde Constantinos Venetsanopoulos
                    'address by following the activation link that will be '
1028 b8b8fdde Constantinos Venetsanopoulos
                    'sent to it, your old email address will remain active.'))
1029 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
1030 34a76cdb Kostas Papadimitriou
        AstakosUser, unique=True, related_name='emailchanges')
1031 3c638f72 Giorgos Korfiatis
    requested_at = models.DateTimeField(auto_now_add=True)
1032 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
1033 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
1034 49790d9d Sofia Papagiannaki
1035 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
1036 49790d9d Sofia Papagiannaki
1037 34a76cdb Kostas Papadimitriou
    def get_url(self):
1038 34a76cdb Kostas Papadimitriou
        return reverse('email_change_confirm',
1039 34a76cdb Kostas Papadimitriou
                      kwargs={'activation_key': self.activation_key})
1040 34a76cdb Kostas Papadimitriou
1041 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
1042 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
1043 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
1044 ff9290ec Sofia Papagiannaki
1045 6b03a847 Sofia Papagiannaki
1046 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
1047 ca828a10 Sofia Papagiannaki
    """
1048 ca828a10 Sofia Papagiannaki
    Model for registring invitations
1049 ca828a10 Sofia Papagiannaki
    """
1050 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
1051 1eec103a Sofia Papagiannaki
    email = models.EmailField()
1052 ca828a10 Sofia Papagiannaki
1053 5ce3ce4f Sofia Papagiannaki
1054 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
1055 fc1e2f02 Sofia Papagiannaki
    while True:
1056 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
1057 fc1e2f02 Sofia Papagiannaki
        try:
1058 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
1059 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
1060 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
1061 fc1e2f02 Sofia Papagiannaki
            return code
1062 fc1e2f02 Sofia Papagiannaki
1063 5ce3ce4f Sofia Papagiannaki
1064 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
1065 fc1e2f02 Sofia Papagiannaki
    try:
1066 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
1067 fc1e2f02 Sofia Papagiannaki
        return term
1068 fc1e2f02 Sofia Papagiannaki
    except IndexError:
1069 fc1e2f02 Sofia Papagiannaki
        pass
1070 fc1e2f02 Sofia Papagiannaki
    return None
1071 fc1e2f02 Sofia Papagiannaki
1072 9d20fe23 Kostas Papadimitriou
1073 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
1074 ef20ea07 Sofia Papagiannaki
    """
1075 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
1076 ef20ea07 Sofia Papagiannaki
    """
1077 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
1078 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
1079 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
1080 564a2292 Kostas Papadimitriou
    first_name = models.CharField(_('first name'), max_length=30, blank=True,
1081 564a2292 Kostas Papadimitriou
                                  null=True)
1082 564a2292 Kostas Papadimitriou
    last_name = models.CharField(_('last name'), max_length=30, blank=True,
1083 564a2292 Kostas Papadimitriou
                                 null=True)
1084 564a2292 Kostas Papadimitriou
    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
1085 564a2292 Kostas Papadimitriou
                                   null=True)
1086 9d20fe23 Kostas Papadimitriou
    username = models.CharField(_('username'), max_length=30, unique=True,
1087 d6ea9b3d Olga Brani
                                help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
1088 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
1089 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
1090 c630fee6 Kostas Papadimitriou
    info = models.TextField(default="", null=True, blank=True)
1091 d2633501 Kostas Papadimitriou
1092 678b2236 Sofia Papagiannaki
    class Meta:
1093 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
1094 ef20ea07 Sofia Papagiannaki
1095 c630fee6 Kostas Papadimitriou
    def get_user_instance(self):
1096 c630fee6 Kostas Papadimitriou
        d = self.__dict__
1097 c630fee6 Kostas Papadimitriou
        d.pop('_state', None)
1098 c630fee6 Kostas Papadimitriou
        d.pop('id', None)
1099 c630fee6 Kostas Papadimitriou
        d.pop('token', None)
1100 c630fee6 Kostas Papadimitriou
        d.pop('created', None)
1101 c630fee6 Kostas Papadimitriou
        d.pop('info', None)
1102 c630fee6 Kostas Papadimitriou
        user = AstakosUser(**d)
1103 c630fee6 Kostas Papadimitriou
1104 c630fee6 Kostas Papadimitriou
        return user
1105 c630fee6 Kostas Papadimitriou
1106 ef20ea07 Sofia Papagiannaki
    @property
1107 ef20ea07 Sofia Papagiannaki
    def realname(self):
1108 ef20ea07 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
1109 ef20ea07 Sofia Papagiannaki
1110 ef20ea07 Sofia Papagiannaki
    @realname.setter
1111 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
1112 ef20ea07 Sofia Papagiannaki
        parts = value.split(' ')
1113 ef20ea07 Sofia Papagiannaki
        if len(parts) == 2:
1114 ef20ea07 Sofia Papagiannaki
            self.first_name = parts[0]
1115 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[1]
1116 ef20ea07 Sofia Papagiannaki
        else:
1117 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[0]
1118 2e90e3ec Kostas Papadimitriou
1119 ef20ea07 Sofia Papagiannaki
    def save(self, **kwargs):
1120 ef20ea07 Sofia Papagiannaki
        if not self.id:
1121 ef20ea07 Sofia Papagiannaki
            # set username
1122 ef20ea07 Sofia Papagiannaki
            while not self.username:
1123 ef20ea07 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
1124 ef20ea07 Sofia Papagiannaki
                try:
1125 ef20ea07 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
1126 ef20ea07 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
1127 ef20ea07 Sofia Papagiannaki
                    self.username = username
1128 ef20ea07 Sofia Papagiannaki
        super(PendingThirdPartyUser, self).save(**kwargs)
1129 ef20ea07 Sofia Papagiannaki
1130 d2633501 Kostas Papadimitriou
    def generate_token(self):
1131 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1132 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1133 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1134 d2633501 Kostas Papadimitriou
1135 606dea8d Kostas Papadimitriou
    def existing_user(self):
1136 606dea8d Kostas Papadimitriou
        return AstakosUser.objects.filter(auth_providers__module=self.provider,
1137 606dea8d Kostas Papadimitriou
                                         auth_providers__identifier=self.third_party_identifier)
1138 606dea8d Kostas Papadimitriou
1139 9d20fe23 Kostas Papadimitriou
    def get_provider(self, user):
1140 9d20fe23 Kostas Papadimitriou
        params = {
1141 9d20fe23 Kostas Papadimitriou
            'info_data': self.info,
1142 9d20fe23 Kostas Papadimitriou
            'affiliation': self.affiliation
1143 9d20fe23 Kostas Papadimitriou
        }
1144 9d20fe23 Kostas Papadimitriou
        return auth.get_provider(self.provider, user,
1145 9d20fe23 Kostas Papadimitriou
                                 self.third_party_identifier, **params)
1146 9d20fe23 Kostas Papadimitriou
1147 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1148 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1149 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1150 bf0c6de5 Sofia Papagiannaki
1151 fcc1e93f Sofia Papagiannaki
1152 c7c0ec58 Giorgos Korfiatis
class UserSetting(models.Model):
1153 c7c0ec58 Giorgos Korfiatis
    user = models.ForeignKey(AstakosUser)
1154 c7c0ec58 Giorgos Korfiatis
    setting = models.CharField(max_length=255)
1155 c7c0ec58 Giorgos Korfiatis
    value = models.IntegerField()
1156 c7c0ec58 Giorgos Korfiatis
1157 c7c0ec58 Giorgos Korfiatis
    objects = ForUpdateManager()
1158 c7c0ec58 Giorgos Korfiatis
1159 c7c0ec58 Giorgos Korfiatis
    class Meta:
1160 c7c0ec58 Giorgos Korfiatis
        unique_together = ("user", "setting")
1161 c7c0ec58 Giorgos Korfiatis
1162 c7c0ec58 Giorgos Korfiatis
1163 fcc1e93f Sofia Papagiannaki
### PROJECTS ###
1164 fcc1e93f Sofia Papagiannaki
################
1165 fcc1e93f Sofia Papagiannaki
1166 2529745f Giorgos Korfiatis
class ChainManager(ForUpdateManager):
1167 2529745f Giorgos Korfiatis
1168 2529745f Giorgos Korfiatis
    def search_by_name(self, *search_strings):
1169 2529745f Giorgos Korfiatis
        projects = Project.objects.search_by_name(*search_strings)
1170 2529745f Giorgos Korfiatis
        chains = [p.id for p in projects]
1171 2529745f Giorgos Korfiatis
        apps  = ProjectApplication.objects.search_by_name(*search_strings)
1172 2529745f Giorgos Korfiatis
        apps = (app for app in apps if app.is_latest())
1173 2529745f Giorgos Korfiatis
        app_chains = [app.chain for app in apps if app.chain not in chains]
1174 2529745f Giorgos Korfiatis
        return chains + app_chains
1175 2529745f Giorgos Korfiatis
1176 2529745f Giorgos Korfiatis
    def all_full_state(self):
1177 2529745f Giorgos Korfiatis
        chains = self.all()
1178 4391de3d Giorgos Korfiatis
        cids = [c.chain for c in chains]
1179 4391de3d Giorgos Korfiatis
        projects = Project.objects.select_related('application').in_bulk(cids)
1180 4391de3d Giorgos Korfiatis
1181 4391de3d Giorgos Korfiatis
        objs = Chain.objects.annotate(latest=Max('chained_apps__id'))
1182 4391de3d Giorgos Korfiatis
        chain_latest = dict(objs.values_list('chain', 'latest'))
1183 4391de3d Giorgos Korfiatis
1184 4391de3d Giorgos Korfiatis
        objs = ProjectApplication.objects.select_related('applicant')
1185 4391de3d Giorgos Korfiatis
        apps = objs.in_bulk(chain_latest.values())
1186 4391de3d Giorgos Korfiatis
1187 4391de3d Giorgos Korfiatis
        d = {}
1188 2529745f Giorgos Korfiatis
        for chain in chains:
1189 4391de3d Giorgos Korfiatis
            pk = chain.pk
1190 4391de3d Giorgos Korfiatis
            project = projects.get(pk, None)
1191 4391de3d Giorgos Korfiatis
            app = apps[chain_latest[pk]]
1192 4391de3d Giorgos Korfiatis
            d[chain.pk] = chain.get_state(project, app)
1193 4391de3d Giorgos Korfiatis
1194 2529745f Giorgos Korfiatis
        return d
1195 2529745f Giorgos Korfiatis
1196 2529745f Giorgos Korfiatis
    def of_project(self, project):
1197 2529745f Giorgos Korfiatis
        if project is None:
1198 2529745f Giorgos Korfiatis
            return None
1199 2529745f Giorgos Korfiatis
        try:
1200 2529745f Giorgos Korfiatis
            return self.get(chain=project.id)
1201 2529745f Giorgos Korfiatis
        except Chain.DoesNotExist:
1202 2529745f Giorgos Korfiatis
            raise AssertionError('project with no chain')
1203 2529745f Giorgos Korfiatis
1204 2529745f Giorgos Korfiatis
1205 033f2822 Giorgos Korfiatis
class Chain(models.Model):
1206 033f2822 Giorgos Korfiatis
    chain  =   models.AutoField(primary_key=True)
1207 033f2822 Giorgos Korfiatis
1208 033f2822 Giorgos Korfiatis
    def __str__(self):
1209 033f2822 Giorgos Korfiatis
        return "%s" % (self.chain,)
1210 033f2822 Giorgos Korfiatis
1211 2529745f Giorgos Korfiatis
    objects = ChainManager()
1212 2529745f Giorgos Korfiatis
1213 2529745f Giorgos Korfiatis
    PENDING            = 0
1214 2529745f Giorgos Korfiatis
    DENIED             = 3
1215 2529745f Giorgos Korfiatis
    DISMISSED          = 4
1216 2529745f Giorgos Korfiatis
    CANCELLED          = 5
1217 2529745f Giorgos Korfiatis
1218 2529745f Giorgos Korfiatis
    APPROVED           = 10
1219 2529745f Giorgos Korfiatis
    APPROVED_PENDING   = 11
1220 2529745f Giorgos Korfiatis
    SUSPENDED          = 12
1221 2529745f Giorgos Korfiatis
    SUSPENDED_PENDING  = 13
1222 2529745f Giorgos Korfiatis
    TERMINATED         = 14
1223 2529745f Giorgos Korfiatis
    TERMINATED_PENDING = 15
1224 2529745f Giorgos Korfiatis
1225 2529745f Giorgos Korfiatis
    PENDING_STATES = [PENDING,
1226 2529745f Giorgos Korfiatis
                      APPROVED_PENDING,
1227 2529745f Giorgos Korfiatis
                      SUSPENDED_PENDING,
1228 2529745f Giorgos Korfiatis
                      TERMINATED_PENDING,
1229 2529745f Giorgos Korfiatis
                      ]
1230 2529745f Giorgos Korfiatis
1231 f557d10a Giorgos Korfiatis
    MODIFICATION_STATES = [APPROVED_PENDING,
1232 f557d10a Giorgos Korfiatis
                           SUSPENDED_PENDING,
1233 f557d10a Giorgos Korfiatis
                           TERMINATED_PENDING,
1234 f557d10a Giorgos Korfiatis
                           ]
1235 f557d10a Giorgos Korfiatis
1236 f557d10a Giorgos Korfiatis
    RELEVANT_STATES = [PENDING,
1237 f557d10a Giorgos Korfiatis
                       DENIED,
1238 f557d10a Giorgos Korfiatis
                       APPROVED,
1239 f557d10a Giorgos Korfiatis
                       APPROVED_PENDING,
1240 f557d10a Giorgos Korfiatis
                       SUSPENDED,
1241 f557d10a Giorgos Korfiatis
                       SUSPENDED_PENDING,
1242 f557d10a Giorgos Korfiatis
                       TERMINATED_PENDING,
1243 f557d10a Giorgos Korfiatis
                       ]
1244 f557d10a Giorgos Korfiatis
1245 2529745f Giorgos Korfiatis
    SKIP_STATES = [DISMISSED,
1246 2529745f Giorgos Korfiatis
                   CANCELLED,
1247 2529745f Giorgos Korfiatis
                   TERMINATED]
1248 2529745f Giorgos Korfiatis
1249 2529745f Giorgos Korfiatis
    STATE_DISPLAY = {
1250 5d209685 Giorgos Korfiatis
        PENDING            : _("Pending"),
1251 2529745f Giorgos Korfiatis
        DENIED             : _("Denied"),
1252 2529745f Giorgos Korfiatis
        DISMISSED          : _("Dismissed"),
1253 2529745f Giorgos Korfiatis
        CANCELLED          : _("Cancelled"),
1254 2529745f Giorgos Korfiatis
        APPROVED           : _("Active"),
1255 2529745f Giorgos Korfiatis
        APPROVED_PENDING   : _("Active - Pending"),
1256 2529745f Giorgos Korfiatis
        SUSPENDED          : _("Suspended"),
1257 2529745f Giorgos Korfiatis
        SUSPENDED_PENDING  : _("Suspended - Pending"),
1258 2529745f Giorgos Korfiatis
        TERMINATED         : _("Terminated"),
1259 2529745f Giorgos Korfiatis
        TERMINATED_PENDING : _("Terminated - Pending"),
1260 2529745f Giorgos Korfiatis
        }
1261 2529745f Giorgos Korfiatis
1262 2529745f Giorgos Korfiatis
1263 2529745f Giorgos Korfiatis
    @classmethod
1264 2529745f Giorgos Korfiatis
    def _chain_state(cls, project_state, app_state):
1265 2529745f Giorgos Korfiatis
        s = CHAIN_STATE.get((project_state, app_state), None)
1266 2529745f Giorgos Korfiatis
        if s is None:
1267 2529745f Giorgos Korfiatis
            raise AssertionError('inconsistent chain state')
1268 2529745f Giorgos Korfiatis
        return s
1269 2529745f Giorgos Korfiatis
1270 2529745f Giorgos Korfiatis
    @classmethod
1271 2529745f Giorgos Korfiatis
    def chain_state(cls, project, app):
1272 2529745f Giorgos Korfiatis
        p_state = project.state if project else None
1273 2529745f Giorgos Korfiatis
        return cls._chain_state(p_state, app.state)
1274 2529745f Giorgos Korfiatis
1275 2529745f Giorgos Korfiatis
    @classmethod
1276 2529745f Giorgos Korfiatis
    def state_display(cls, s):
1277 2529745f Giorgos Korfiatis
        if s is None:
1278 2529745f Giorgos Korfiatis
            return _("Unknown")
1279 2529745f Giorgos Korfiatis
        return cls.STATE_DISPLAY.get(s, _("Inconsistent"))
1280 2529745f Giorgos Korfiatis
1281 2529745f Giorgos Korfiatis
    def last_application(self):
1282 2529745f Giorgos Korfiatis
        return self.chained_apps.order_by('-id')[0]
1283 2529745f Giorgos Korfiatis
1284 2529745f Giorgos Korfiatis
    def get_project(self):
1285 2529745f Giorgos Korfiatis
        try:
1286 2529745f Giorgos Korfiatis
            return self.chained_project
1287 2529745f Giorgos Korfiatis
        except Project.DoesNotExist:
1288 2529745f Giorgos Korfiatis
            return None
1289 2529745f Giorgos Korfiatis
1290 2529745f Giorgos Korfiatis
    def get_elements(self):
1291 2529745f Giorgos Korfiatis
        project = self.get_project()
1292 2529745f Giorgos Korfiatis
        app = self.last_application()
1293 2529745f Giorgos Korfiatis
        return project, app
1294 2529745f Giorgos Korfiatis
1295 4391de3d Giorgos Korfiatis
    def get_state(self, project, app):
1296 2529745f Giorgos Korfiatis
        s = self.chain_state(project, app)
1297 2529745f Giorgos Korfiatis
        return s, project, app
1298 2529745f Giorgos Korfiatis
1299 4391de3d Giorgos Korfiatis
    def full_state(self):
1300 4391de3d Giorgos Korfiatis
        project, app = self.get_elements()
1301 4391de3d Giorgos Korfiatis
        return self.get_state(project, app)
1302 4391de3d Giorgos Korfiatis
1303 4391de3d Giorgos Korfiatis
1304 033f2822 Giorgos Korfiatis
def new_chain():
1305 033f2822 Giorgos Korfiatis
    c = Chain.objects.create()
1306 033f2822 Giorgos Korfiatis
    return c
1307 033f2822 Giorgos Korfiatis
1308 033f2822 Giorgos Korfiatis
1309 6dcf53eb Kostas Papadimitriou
class ProjectApplicationManager(ForUpdateManager):
1310 5550bcfb Kostas Papadimitriou
1311 05617ab9 Kostas Papadimitriou
    def user_visible_projects(self, *filters, **kw_filters):
1312 689226c3 Giorgos Korfiatis
        model = self.model
1313 689226c3 Giorgos Korfiatis
        return self.filter(model.Q_PENDING | model.Q_APPROVED)
1314 05617ab9 Kostas Papadimitriou
1315 7184f408 Giorgos Korfiatis
    def user_visible_by_chain(self, flt):
1316 689226c3 Giorgos Korfiatis
        model = self.model
1317 3e3743f2 Giorgos Korfiatis
        pending = self.filter(model.Q_PENDING | model.Q_DENIED).values_list('chain')
1318 689226c3 Giorgos Korfiatis
        approved = self.filter(model.Q_APPROVED).values_list('chain')
1319 a3530159 Georgios D. Tsoukalas
        by_chain = dict(pending.annotate(models.Max('id')))
1320 a3530159 Georgios D. Tsoukalas
        by_chain.update(approved.annotate(models.Max('id')))
1321 7184f408 Giorgos Korfiatis
        return self.filter(flt, id__in=by_chain.values())
1322 05617ab9 Kostas Papadimitriou
1323 05617ab9 Kostas Papadimitriou
    def user_accessible_projects(self, user):
1324 5550bcfb Kostas Papadimitriou
        """
1325 5550bcfb Kostas Papadimitriou
        Return projects accessed by specified user.
1326 5550bcfb Kostas Papadimitriou
        """
1327 8e1a5af5 Georgios D. Tsoukalas
        if user.is_project_admin():
1328 8e1a5af5 Georgios D. Tsoukalas
            participates_filters = Q()
1329 8e1a5af5 Georgios D. Tsoukalas
        else:
1330 8e1a5af5 Georgios D. Tsoukalas
            participates_filters = Q(owner=user) | Q(applicant=user) | \
1331 8e1a5af5 Georgios D. Tsoukalas
                                   Q(project__projectmembership__person=user)
1332 05617ab9 Kostas Papadimitriou
1333 a3530159 Georgios D. Tsoukalas
        return self.user_visible_by_chain(participates_filters).order_by('issue_date').distinct()
1334 5550bcfb Kostas Papadimitriou
1335 a5cef8d0 Kostas Papadimitriou
    def search_by_name(self, *search_strings):
1336 a5cef8d0 Kostas Papadimitriou
        q = Q()
1337 a5cef8d0 Kostas Papadimitriou
        for s in search_strings:
1338 a5cef8d0 Kostas Papadimitriou
            q = q | Q(name__icontains=s)
1339 a5cef8d0 Kostas Papadimitriou
        return self.filter(q)
1340 a5cef8d0 Kostas Papadimitriou
1341 ff67242a Giorgos Korfiatis
    def latest_of_chain(self, chain_id):
1342 ff67242a Giorgos Korfiatis
        try:
1343 ff67242a Giorgos Korfiatis
            return self.filter(chain=chain_id).order_by('-id')[0]
1344 ff67242a Giorgos Korfiatis
        except IndexError:
1345 ff67242a Giorgos Korfiatis
            return None
1346 a5cef8d0 Kostas Papadimitriou
1347 a9ba418f Giorgos Korfiatis
1348 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1349 425e2e95 Sofia Papagiannaki
    applicant               =   models.ForeignKey(
1350 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1351 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_applied',
1352 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1353 d6fdc91e Georgios D. Tsoukalas
1354 d0e78bbe Giorgos Korfiatis
    PENDING     =    0
1355 d0e78bbe Giorgos Korfiatis
    APPROVED    =    1
1356 d0e78bbe Giorgos Korfiatis
    REPLACED    =    2
1357 d0e78bbe Giorgos Korfiatis
    DENIED      =    3
1358 3c638f72 Giorgos Korfiatis
    DISMISSED   =    4
1359 3c638f72 Giorgos Korfiatis
    CANCELLED   =    5
1360 d0e78bbe Giorgos Korfiatis
1361 69ab4df9 Giorgos Korfiatis
    state                   =   models.IntegerField(default=PENDING,
1362 69ab4df9 Giorgos Korfiatis
                                                    db_index=True)
1363 d6fdc91e Georgios D. Tsoukalas
1364 425e2e95 Sofia Papagiannaki
    owner                   =   models.ForeignKey(
1365 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1366 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_owned',
1367 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1368 d6fdc91e Georgios D. Tsoukalas
1369 5195c0e9 Giorgos Korfiatis
    chain                   =   models.ForeignKey(Chain,
1370 5195c0e9 Giorgos Korfiatis
                                                  related_name='chained_apps',
1371 5195c0e9 Giorgos Korfiatis
                                                  db_column='chain')
1372 3c638f72 Giorgos Korfiatis
    precursor_application   =   models.ForeignKey('ProjectApplication',
1373 3c638f72 Giorgos Korfiatis
                                                  null=True,
1374 3c638f72 Giorgos Korfiatis
                                                  blank=True)
1375 425e2e95 Sofia Papagiannaki
1376 67980f56 Georgios D. Tsoukalas
    name                    =   models.CharField(max_length=80)
1377 94e49e22 Kostas Papadimitriou
    homepage                =   models.URLField(max_length=255, null=True,
1378 94e49e22 Kostas Papadimitriou
                                                verify_exists=False)
1379 67980f56 Georgios D. Tsoukalas
    description             =   models.TextField(null=True, blank=True)
1380 e729f165 Kostas Papadimitriou
    start_date              =   models.DateTimeField(null=True, blank=True)
1381 67980f56 Georgios D. Tsoukalas
    end_date                =   models.DateTimeField()
1382 272cf735 Sofia Papagiannaki
    member_join_policy      =   models.IntegerField()
1383 272cf735 Sofia Papagiannaki
    member_leave_policy     =   models.IntegerField()
1384 67980f56 Georgios D. Tsoukalas
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1385 425e2e95 Sofia Papagiannaki
    resource_grants         =   models.ManyToManyField(
1386 425e2e95 Sofia Papagiannaki
                                    Resource,
1387 425e2e95 Sofia Papagiannaki
                                    null=True,
1388 425e2e95 Sofia Papagiannaki
                                    blank=True,
1389 d6fdc91e Georgios D. Tsoukalas
                                    through='ProjectResourceGrant')
1390 425e2e95 Sofia Papagiannaki
    comments                =   models.TextField(null=True, blank=True)
1391 3c638f72 Giorgos Korfiatis
    issue_date              =   models.DateTimeField(auto_now_add=True)
1392 3c638f72 Giorgos Korfiatis
    response_date           =   models.DateTimeField(null=True, blank=True)
1393 2b745492 Giorgos Korfiatis
    response                =   models.TextField(null=True, blank=True)
1394 6dcf53eb Kostas Papadimitriou
1395 5550bcfb Kostas Papadimitriou
    objects                 =   ProjectApplicationManager()
1396 7729e9cc Giorgos Korfiatis
1397 689226c3 Giorgos Korfiatis
    # Compiled queries
1398 689226c3 Giorgos Korfiatis
    Q_PENDING  = Q(state=PENDING)
1399 689226c3 Giorgos Korfiatis
    Q_APPROVED = Q(state=APPROVED)
1400 3e3743f2 Giorgos Korfiatis
    Q_DENIED   = Q(state=DENIED)
1401 689226c3 Giorgos Korfiatis
1402 c4892cd2 Sofia Papagiannaki
    class Meta:
1403 c4892cd2 Sofia Papagiannaki
        unique_together = ("chain", "id")
1404 c4892cd2 Sofia Papagiannaki
1405 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1406 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1407 f3a45fc6 Kostas Papadimitriou
1408 d0e78bbe Giorgos Korfiatis
    # TODO: Move to a more suitable place
1409 9307cd46 Giorgos Korfiatis
    APPLICATION_STATE_DISPLAY = {
1410 3c638f72 Giorgos Korfiatis
        PENDING  : _('Pending review'),
1411 d77b32f2 Giorgos Korfiatis
        APPROVED : _('Approved'),
1412 3c638f72 Giorgos Korfiatis
        REPLACED : _('Replaced'),
1413 3c638f72 Giorgos Korfiatis
        DENIED   : _('Denied'),
1414 3c638f72 Giorgos Korfiatis
        DISMISSED: _('Dismissed'),
1415 3c638f72 Giorgos Korfiatis
        CANCELLED: _('Cancelled')
1416 bd9af366 Kostas Papadimitriou
    }
1417 d0e78bbe Giorgos Korfiatis
1418 f30f0170 Giorgos Korfiatis
    @property
1419 f30f0170 Giorgos Korfiatis
    def log_display(self):
1420 f30f0170 Giorgos Korfiatis
        return "application %s (%s) for project %s" % (
1421 f30f0170 Giorgos Korfiatis
            self.id, self.name, self.chain)
1422 f30f0170 Giorgos Korfiatis
1423 a3530159 Georgios D. Tsoukalas
    def get_project(self):
1424 a3530159 Georgios D. Tsoukalas
        try:
1425 a3530159 Georgios D. Tsoukalas
            project = Project.objects.get(id=self.chain, state=Project.APPROVED)
1426 a3530159 Georgios D. Tsoukalas
            return Project
1427 a3530159 Georgios D. Tsoukalas
        except Project.DoesNotExist, e:
1428 a3530159 Georgios D. Tsoukalas
            return None
1429 a3530159 Georgios D. Tsoukalas
1430 db9a498c Kostas Papadimitriou
    def state_display(self):
1431 9307cd46 Giorgos Korfiatis
        return self.APPLICATION_STATE_DISPLAY.get(self.state, _('Unknown'))
1432 db9a498c Kostas Papadimitriou
1433 d4660e00 Giorgos Korfiatis
    def project_state_display(self):
1434 d4660e00 Giorgos Korfiatis
        try:
1435 d4660e00 Giorgos Korfiatis
            project = self.project
1436 d4660e00 Giorgos Korfiatis
            return project.state_display()
1437 d4660e00 Giorgos Korfiatis
        except Project.DoesNotExist:
1438 d4660e00 Giorgos Korfiatis
            return self.state_display()
1439 d4660e00 Giorgos Korfiatis
1440 a7aba804 Sofia Papagiannaki
    def add_resource_policy(self, service, resource, uplimit):
1441 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1442 a7aba804 Sofia Papagiannaki
        q = self.projectresourcegrant_set
1443 26551b92 Kostas Papadimitriou
        resource = Resource.objects.get(name=resource)
1444 a7aba804 Sofia Papagiannaki
        q.create(resource=resource, member_capacity=uplimit)
1445 e1a80257 Sofia Papagiannaki
1446 5550bcfb Kostas Papadimitriou
    def members_count(self):
1447 5550bcfb Kostas Papadimitriou
        return self.project.approved_memberships.count()
1448 5550bcfb Kostas Papadimitriou
1449 669cfe19 Olga Brani
    @property
1450 669cfe19 Olga Brani
    def grants(self):
1451 26551b92 Kostas Papadimitriou
        return self.projectresourcegrant_set.values('member_capacity',
1452 26551b92 Kostas Papadimitriou
                                                    'resource__name')
1453 5550bcfb Kostas Papadimitriou
1454 e1a80257 Sofia Papagiannaki
    @property
1455 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1456 b98e1df0 Sofia Papagiannaki
        return [str(rp) for rp in self.projectresourcegrant_set.all()]
1457 e1a80257 Sofia Papagiannaki
1458 e1a80257 Sofia Papagiannaki
    @resource_policies.setter
1459 e1a80257 Sofia Papagiannaki
    def resource_policies(self, policies):
1460 e1a80257 Sofia Papagiannaki
        for p in policies:
1461 e1a80257 Sofia Papagiannaki
            service = p.get('service', None)
1462 e1a80257 Sofia Papagiannaki
            resource = p.get('resource', None)
1463 e1a80257 Sofia Papagiannaki
            uplimit = p.get('uplimit', 0)
1464 a7aba804 Sofia Papagiannaki
            self.add_resource_policy(service, resource, uplimit)
1465 425e2e95 Sofia Papagiannaki
1466 a75dbd7b Giorgos Korfiatis
    def pending_modifications_incl_me(self):
1467 3e3743f2 Giorgos Korfiatis
        q = self.chained_applications()
1468 a75dbd7b Giorgos Korfiatis
        q = q.filter(Q(state=self.PENDING))
1469 3e3743f2 Giorgos Korfiatis
        return q
1470 ece3b66e Giorgos Korfiatis
1471 a75dbd7b Giorgos Korfiatis
    def last_pending_incl_me(self):
1472 a75dbd7b Giorgos Korfiatis
        try:
1473 a75dbd7b Giorgos Korfiatis
            return self.pending_modifications_incl_me().order_by('-id')[0]
1474 a75dbd7b Giorgos Korfiatis
        except IndexError:
1475 a75dbd7b Giorgos Korfiatis
            return None
1476 a75dbd7b Giorgos Korfiatis
1477 a75dbd7b Giorgos Korfiatis
    def pending_modifications(self):
1478 a75dbd7b Giorgos Korfiatis
        return self.pending_modifications_incl_me().filter(~Q(id=self.id))
1479 a75dbd7b Giorgos Korfiatis
1480 3e3743f2 Giorgos Korfiatis
    def last_pending(self):
1481 9b32e2fb Kostas Papadimitriou
        try:
1482 3e3743f2 Giorgos Korfiatis
            return self.pending_modifications().order_by('-id')[0]
1483 9b32e2fb Kostas Papadimitriou
        except IndexError:
1484 05617ab9 Kostas Papadimitriou
            return None
1485 05617ab9 Kostas Papadimitriou
1486 efc58b65 Kostas Papadimitriou
    def is_modification(self):
1487 d4660e00 Giorgos Korfiatis
        # if self.state != self.PENDING:
1488 d4660e00 Giorgos Korfiatis
        #     return False
1489 efc58b65 Kostas Papadimitriou
        parents = self.chained_applications().filter(id__lt=self.id)
1490 efc58b65 Kostas Papadimitriou
        parents = parents.filter(state__in=[self.APPROVED])
1491 efc58b65 Kostas Papadimitriou
        return parents.count() > 0
1492 efc58b65 Kostas Papadimitriou
1493 efc58b65 Kostas Papadimitriou
    def chained_applications(self):
1494 efc58b65 Kostas Papadimitriou
        return ProjectApplication.objects.filter(chain=self.chain)
1495 efc58b65 Kostas Papadimitriou
1496 2529745f Giorgos Korfiatis
    def is_latest(self):
1497 2529745f Giorgos Korfiatis
        return self.chained_applications().order_by('-id')[0] == self
1498 2529745f Giorgos Korfiatis
1499 05617ab9 Kostas Papadimitriou
    def has_pending_modifications(self):
1500 3e3743f2 Giorgos Korfiatis
        return bool(self.last_pending())
1501 05617ab9 Kostas Papadimitriou
1502 022cc8e2 Giorgos Korfiatis
    def denied_modifications(self):
1503 022cc8e2 Giorgos Korfiatis
        q = self.chained_applications()
1504 022cc8e2 Giorgos Korfiatis
        q = q.filter(Q(state=self.DENIED))
1505 022cc8e2 Giorgos Korfiatis
        q = q.filter(~Q(id=self.id))
1506 022cc8e2 Giorgos Korfiatis
        return q
1507 022cc8e2 Giorgos Korfiatis
1508 022cc8e2 Giorgos Korfiatis
    def last_denied(self):
1509 022cc8e2 Giorgos Korfiatis
        try:
1510 022cc8e2 Giorgos Korfiatis
            return self.denied_modifications().order_by('-id')[0]
1511 022cc8e2 Giorgos Korfiatis
        except IndexError:
1512 022cc8e2 Giorgos Korfiatis
            return None
1513 022cc8e2 Giorgos Korfiatis
1514 022cc8e2 Giorgos Korfiatis
    def has_denied_modifications(self):
1515 022cc8e2 Giorgos Korfiatis
        return bool(self.last_denied())
1516 022cc8e2 Giorgos Korfiatis
1517 2529745f Giorgos Korfiatis
    def is_applied(self):
1518 2529745f Giorgos Korfiatis
        try:
1519 2529745f Giorgos Korfiatis
            self.project
1520 2529745f Giorgos Korfiatis
            return True
1521 2529745f Giorgos Korfiatis
        except Project.DoesNotExist:
1522 2529745f Giorgos Korfiatis
            return False
1523 2529745f Giorgos Korfiatis
1524 05617ab9 Kostas Papadimitriou
    def get_project(self):
1525 05617ab9 Kostas Papadimitriou
        try:
1526 05617ab9 Kostas Papadimitriou
            return Project.objects.get(id=self.chain)
1527 05617ab9 Kostas Papadimitriou
        except Project.DoesNotExist:
1528 9b32e2fb Kostas Papadimitriou
            return None
1529 4f22664f Georgios D. Tsoukalas
1530 d74111be Giorgos Korfiatis
    def project_exists(self):
1531 d74111be Giorgos Korfiatis
        return self.get_project() is not None
1532 d74111be Giorgos Korfiatis
1533 b6fe8bb8 Giorgos Korfiatis
    def _get_project_for_update(self):
1534 a9ba418f Giorgos Korfiatis
        try:
1535 ea1e5d9f Giorgos Korfiatis
            objects = Project.objects
1536 ea1e5d9f Giorgos Korfiatis
            project = objects.get_for_update(id=self.chain)
1537 a9ba418f Giorgos Korfiatis
            return project
1538 a9ba418f Giorgos Korfiatis
        except Project.DoesNotExist:
1539 a9ba418f Giorgos Korfiatis
            return None
1540 4f22664f Georgios D. Tsoukalas
1541 01bdbd17 Giorgos Korfiatis
    def can_cancel(self):
1542 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1543 01bdbd17 Giorgos Korfiatis
1544 3c638f72 Giorgos Korfiatis
    def cancel(self):
1545 01bdbd17 Giorgos Korfiatis
        if not self.can_cancel():
1546 3c638f72 Giorgos Korfiatis
            m = _("cannot cancel: application '%s' in state '%s'") % (
1547 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1548 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1549 3c638f72 Giorgos Korfiatis
1550 3c638f72 Giorgos Korfiatis
        self.state = self.CANCELLED
1551 3c638f72 Giorgos Korfiatis
        self.save()
1552 3c638f72 Giorgos Korfiatis
1553 01bdbd17 Giorgos Korfiatis
    def can_dismiss(self):
1554 01bdbd17 Giorgos Korfiatis
        return self.state == self.DENIED
1555 01bdbd17 Giorgos Korfiatis
1556 3c638f72 Giorgos Korfiatis
    def dismiss(self):
1557 01bdbd17 Giorgos Korfiatis
        if not self.can_dismiss():
1558 3c638f72 Giorgos Korfiatis
            m = _("cannot dismiss: application '%s' in state '%s'") % (
1559 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1560 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1561 3c638f72 Giorgos Korfiatis
1562 3c638f72 Giorgos Korfiatis
        self.state = self.DISMISSED
1563 3c638f72 Giorgos Korfiatis
        self.save()
1564 3c638f72 Giorgos Korfiatis
1565 01bdbd17 Giorgos Korfiatis
    def can_deny(self):
1566 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1567 01bdbd17 Giorgos Korfiatis
1568 2b745492 Giorgos Korfiatis
    def deny(self, reason):
1569 01bdbd17 Giorgos Korfiatis
        if not self.can_deny():
1570 19eb3ee6 Giorgos Korfiatis
            m = _("cannot deny: application '%s' in state '%s'") % (
1571 19eb3ee6 Giorgos Korfiatis
                    self.id, self.state)
1572 19eb3ee6 Giorgos Korfiatis
            raise AssertionError(m)
1573 19eb3ee6 Giorgos Korfiatis
1574 19eb3ee6 Giorgos Korfiatis
        self.state = self.DENIED
1575 3c638f72 Giorgos Korfiatis
        self.response_date = datetime.now()
1576 2b745492 Giorgos Korfiatis
        self.response = reason
1577 19eb3ee6 Giorgos Korfiatis
        self.save()
1578 19eb3ee6 Giorgos Korfiatis
1579 01bdbd17 Giorgos Korfiatis
    def can_approve(self):
1580 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1581 01bdbd17 Giorgos Korfiatis
1582 ccab6eb5 Sofia Papagiannaki
    def approve(self, approval_user=None):
1583 ccab6eb5 Sofia Papagiannaki
        """
1584 ccab6eb5 Sofia Papagiannaki
        If approval_user then during owner membership acceptance
1585 ccab6eb5 Sofia Papagiannaki
        it is checked whether the request_user is eligible.
1586 262e04c6 Giorgos Korfiatis

1587 2553efae Sofia Papagiannaki
        Raises:
1588 b8f05f8d Sofia Papagiannaki
            PermissionDenied
1589 ccab6eb5 Sofia Papagiannaki
        """
1590 4f22664f Georgios D. Tsoukalas
1591 4f22664f Georgios D. Tsoukalas
        if not transaction.is_managed():
1592 4f22664f Georgios D. Tsoukalas
            raise AssertionError("NOPE")
1593 4f22664f Georgios D. Tsoukalas
1594 73fbaec4 Sofia Papagiannaki
        new_project_name = self.name
1595 01bdbd17 Giorgos Korfiatis
        if not self.can_approve():
1596 65360c65 Georgios D. Tsoukalas
            m = _("cannot approve: project '%s' in state '%s'") % (
1597 65360c65 Georgios D. Tsoukalas
                    new_project_name, self.state)
1598 01bdbd17 Giorgos Korfiatis
            raise AssertionError(m) # invalid argument
1599 262e04c6 Giorgos Korfiatis
1600 fdafae27 Giorgos Korfiatis
        now = datetime.now()
1601 b6fe8bb8 Giorgos Korfiatis
        project = self._get_project_for_update()
1602 3cc9637a Giorgos Korfiatis
1603 99463445 Giorgos Korfiatis
        try:
1604 99463445 Giorgos Korfiatis
            q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
1605 99463445 Giorgos Korfiatis
            conflicting_project = Project.objects.get(q)
1606 99463445 Giorgos Korfiatis
            if (conflicting_project != project):
1607 3cc9637a Giorgos Korfiatis
                m = (_("cannot approve: project with name '%s' "
1608 e1017df9 Giorgos Korfiatis
                       "already exists (id: %s)") % (
1609 3cc9637a Giorgos Korfiatis
                        new_project_name, conflicting_project.id))
1610 3cc9637a Giorgos Korfiatis
                raise PermissionDenied(m) # invalid argument
1611 99463445 Giorgos Korfiatis
        except Project.DoesNotExist:
1612 99463445 Giorgos Korfiatis
            pass
1613 3cc9637a Giorgos Korfiatis
1614 4bf02ea5 Giorgos Korfiatis
        new_project = False
1615 4f22664f Georgios D. Tsoukalas
        if project is None:
1616 4bf02ea5 Giorgos Korfiatis
            new_project = True
1617 3c638f72 Giorgos Korfiatis
            project = Project(id=self.chain)
1618 fdafae27 Giorgos Korfiatis
1619 3cc9637a Giorgos Korfiatis
        project.name = new_project_name
1620 ee45eb81 Giorgos Korfiatis
        project.application = self
1621 4bf02ea5 Giorgos Korfiatis
        project.last_approval_date = now
1622 a769d7ba Sofia Papagiannaki
        if not new_project:
1623 a769d7ba Sofia Papagiannaki
            project.is_modified = True
1624 4bf02ea5 Giorgos Korfiatis
1625 a769d7ba Sofia Papagiannaki
        project.save()
1626 425e2e95 Sofia Papagiannaki
1627 85d444db Sofia Papagiannaki
        self.state = self.APPROVED
1628 3c638f72 Giorgos Korfiatis
        self.response_date = now
1629 bfe23b13 Sofia Papagiannaki
        self.save()
1630 570015d2 Giorgos Korfiatis
        return project
1631 262e04c6 Giorgos Korfiatis
1632 b98e1df0 Sofia Papagiannaki
    @property
1633 b98e1df0 Sofia Papagiannaki
    def member_join_policy_display(self):
1634 b98e1df0 Sofia Papagiannaki
        return PROJECT_MEMBER_JOIN_POLICIES.get(str(self.member_join_policy))
1635 b98e1df0 Sofia Papagiannaki
1636 b98e1df0 Sofia Papagiannaki
    @property
1637 b98e1df0 Sofia Papagiannaki
    def member_leave_policy_display(self):
1638 b98e1df0 Sofia Papagiannaki
        return PROJECT_MEMBER_LEAVE_POLICIES.get(str(self.member_leave_policy))
1639 b98e1df0 Sofia Papagiannaki
1640 73fbaec4 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1641 e1a80257 Sofia Papagiannaki
1642 425e2e95 Sofia Papagiannaki
    resource                =   models.ForeignKey(Resource)
1643 425e2e95 Sofia Papagiannaki
    project_application     =   models.ForeignKey(ProjectApplication,
1644 5200e864 Sofia Papagiannaki
                                                  null=True)
1645 c11dc0ce Giorgos Korfiatis
    project_capacity        =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1646 c11dc0ce Giorgos Korfiatis
    project_import_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1647 c11dc0ce Giorgos Korfiatis
    project_export_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1648 c11dc0ce Giorgos Korfiatis
    member_capacity         =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1649 c11dc0ce Giorgos Korfiatis
    member_import_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1650 c11dc0ce Giorgos Korfiatis
    member_export_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1651 73fbaec4 Sofia Papagiannaki
1652 73fbaec4 Sofia Papagiannaki
    objects = ExtendedManager()
1653 73fbaec4 Sofia Papagiannaki
1654 73fbaec4 Sofia Papagiannaki
    class Meta:
1655 73fbaec4 Sofia Papagiannaki
        unique_together = ("resource", "project_application")
1656 8327782d Sofia Papagiannaki
1657 b98e1df0 Sofia Papagiannaki
    def display_member_capacity(self):
1658 b98e1df0 Sofia Papagiannaki
        if self.member_capacity:
1659 b98e1df0 Sofia Papagiannaki
            if self.resource.unit:
1660 b98e1df0 Sofia Papagiannaki
                return ProjectResourceGrant.display_filesize(
1661 b98e1df0 Sofia Papagiannaki
                    self.member_capacity)
1662 b98e1df0 Sofia Papagiannaki
            else:
1663 b98e1df0 Sofia Papagiannaki
                if math.isinf(self.member_capacity):
1664 b98e1df0 Sofia Papagiannaki
                    return 'Unlimited'
1665 b98e1df0 Sofia Papagiannaki
                else:
1666 b98e1df0 Sofia Papagiannaki
                    return self.member_capacity
1667 b98e1df0 Sofia Papagiannaki
        else:
1668 b98e1df0 Sofia Papagiannaki
            return 'Unlimited'
1669 b98e1df0 Sofia Papagiannaki
1670 b98e1df0 Sofia Papagiannaki
    def __str__(self):
1671 b98e1df0 Sofia Papagiannaki
        return 'Max %s per user: %s' % (self.resource.pluralized_display_name,
1672 b98e1df0 Sofia Papagiannaki
                                        self.display_member_capacity())
1673 b98e1df0 Sofia Papagiannaki
1674 b98e1df0 Sofia Papagiannaki
    @classmethod
1675 b98e1df0 Sofia Papagiannaki
    def display_filesize(cls, value):
1676 b98e1df0 Sofia Papagiannaki
        try:
1677 b98e1df0 Sofia Papagiannaki
            value = float(value)
1678 b98e1df0 Sofia Papagiannaki
        except:
1679 b98e1df0 Sofia Papagiannaki
            return
1680 b98e1df0 Sofia Papagiannaki
        else:
1681 b98e1df0 Sofia Papagiannaki
            if math.isinf(value):
1682 b98e1df0 Sofia Papagiannaki
                return 'Unlimited'
1683 b98e1df0 Sofia Papagiannaki
            if value > 1:
1684 b98e1df0 Sofia Papagiannaki
                unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'],
1685 b98e1df0 Sofia Papagiannaki
                                [0, 0, 0, 0, 0, 0])
1686 b98e1df0 Sofia Papagiannaki
                exponent = min(int(math.log(value, 1024)), len(unit_list) - 1)
1687 b98e1df0 Sofia Papagiannaki
                quotient = float(value) / 1024**exponent
1688 b98e1df0 Sofia Papagiannaki
                unit, value_decimals = unit_list[exponent]
1689 b98e1df0 Sofia Papagiannaki
                format_string = '{0:.%sf} {1}' % (value_decimals)
1690 b98e1df0 Sofia Papagiannaki
                return format_string.format(quotient, unit)
1691 b98e1df0 Sofia Papagiannaki
            if value == 0:
1692 b98e1df0 Sofia Papagiannaki
                return '0 bytes'
1693 b98e1df0 Sofia Papagiannaki
            if value == 1:
1694 b98e1df0 Sofia Papagiannaki
                return '1 byte'
1695 b98e1df0 Sofia Papagiannaki
            else:
1696 b98e1df0 Sofia Papagiannaki
               return '0'
1697 b98e1df0 Sofia Papagiannaki
1698 e546df49 Georgios D. Tsoukalas
1699 123be68a Giorgos Korfiatis
class ProjectManager(ForUpdateManager):
1700 123be68a Giorgos Korfiatis
1701 123be68a Giorgos Korfiatis
    def terminated_projects(self):
1702 689226c3 Giorgos Korfiatis
        q = self.model.Q_TERMINATED
1703 123be68a Giorgos Korfiatis
        return self.filter(q)
1704 123be68a Giorgos Korfiatis
1705 123be68a Giorgos Korfiatis
    def not_terminated_projects(self):
1706 689226c3 Giorgos Korfiatis
        q = ~self.model.Q_TERMINATED
1707 123be68a Giorgos Korfiatis
        return self.filter(q)
1708 123be68a Giorgos Korfiatis
1709 db99f198 Giorgos Korfiatis
    def deactivated_projects(self):
1710 689226c3 Giorgos Korfiatis
        q = self.model.Q_DEACTIVATED
1711 db99f198 Giorgos Korfiatis
        return self.filter(q)
1712 db99f198 Giorgos Korfiatis
1713 b6fe8bb8 Giorgos Korfiatis
    def modified_projects(self):
1714 b6fe8bb8 Giorgos Korfiatis
        return self.filter(is_modified=True)
1715 b6fe8bb8 Giorgos Korfiatis
1716 7eadc230 Giorgos Korfiatis
    def expired_projects(self):
1717 7eadc230 Giorgos Korfiatis
        q = (~Q(state=Project.TERMINATED) &
1718 7eadc230 Giorgos Korfiatis
              Q(application__end_date__lt=datetime.now()))
1719 7eadc230 Giorgos Korfiatis
        return self.filter(q)
1720 7eadc230 Giorgos Korfiatis
1721 d77b32f2 Giorgos Korfiatis
    def search_by_name(self, *search_strings):
1722 d77b32f2 Giorgos Korfiatis
        q = Q()
1723 d77b32f2 Giorgos Korfiatis
        for s in search_strings:
1724 d77b32f2 Giorgos Korfiatis
            q = q | Q(name__icontains=s)
1725 d77b32f2 Giorgos Korfiatis
        return self.filter(q)
1726 d77b32f2 Giorgos Korfiatis
1727 7eadc230 Giorgos Korfiatis
1728 d6fdc91e Georgios D. Tsoukalas
class Project(models.Model):
1729 e546df49 Georgios D. Tsoukalas
1730 5195c0e9 Giorgos Korfiatis
    id                          =   models.OneToOneField(Chain,
1731 5195c0e9 Giorgos Korfiatis
                                                      related_name='chained_project',
1732 5195c0e9 Giorgos Korfiatis
                                                      db_column='id',
1733 5195c0e9 Giorgos Korfiatis
                                                      primary_key=True)
1734 5195c0e9 Giorgos Korfiatis
1735 ee45eb81 Giorgos Korfiatis
    application                 =   models.OneToOneField(
1736 4f22664f Georgios D. Tsoukalas
                                            ProjectApplication,
1737 782d9118 Giorgos Korfiatis
                                            related_name='project')
1738 4f22664f Georgios D. Tsoukalas
    last_approval_date          =   models.DateTimeField(null=True)
1739 4f22664f Georgios D. Tsoukalas
1740 4f22664f Georgios D. Tsoukalas
    members                     =   models.ManyToManyField(
1741 4f22664f Georgios D. Tsoukalas
                                            AstakosUser,
1742 4f22664f Georgios D. Tsoukalas
                                            through='ProjectMembership')
1743 4f22664f Georgios D. Tsoukalas
1744 5b9e9530 Giorgos Korfiatis
    deactivation_reason         =   models.CharField(max_length=255, null=True)
1745 5b9e9530 Giorgos Korfiatis
    deactivation_date           =   models.DateTimeField(null=True)
1746 4f22664f Georgios D. Tsoukalas
1747 3c638f72 Giorgos Korfiatis
    creation_date               =   models.DateTimeField(auto_now_add=True)
1748 4f22664f Georgios D. Tsoukalas
    name                        =   models.CharField(
1749 4f22664f Georgios D. Tsoukalas
                                            max_length=80,
1750 e1017df9 Giorgos Korfiatis
                                            null=True,
1751 4f22664f Georgios D. Tsoukalas
                                            db_index=True,
1752 4f22664f Georgios D. Tsoukalas
                                            unique=True)
1753 425e2e95 Sofia Papagiannaki
1754 b6fe8bb8 Giorgos Korfiatis
    APPROVED    = 1
1755 b6fe8bb8 Giorgos Korfiatis
    SUSPENDED   = 10
1756 b6fe8bb8 Giorgos Korfiatis
    TERMINATED  = 100
1757 5b9e9530 Giorgos Korfiatis
1758 b6fe8bb8 Giorgos Korfiatis
    is_modified                 =   models.BooleanField(default=False,
1759 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1760 b6fe8bb8 Giorgos Korfiatis
    is_active                   =   models.BooleanField(default=True,
1761 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1762 b6fe8bb8 Giorgos Korfiatis
    state                       =   models.IntegerField(default=APPROVED,
1763 123be68a Giorgos Korfiatis
                                                        db_index=True)
1764 123be68a Giorgos Korfiatis
1765 123be68a Giorgos Korfiatis
    objects     =   ProjectManager()
1766 7729e9cc Giorgos Korfiatis
1767 689226c3 Giorgos Korfiatis
    # Compiled queries
1768 689226c3 Giorgos Korfiatis
    Q_TERMINATED  = Q(state=TERMINATED)
1769 689226c3 Giorgos Korfiatis
    Q_SUSPENDED   = Q(state=SUSPENDED)
1770 689226c3 Giorgos Korfiatis
    Q_DEACTIVATED = Q_TERMINATED | Q_SUSPENDED
1771 689226c3 Giorgos Korfiatis
1772 8c7b8bb8 Giorgos Korfiatis
    def __str__(self):
1773 b6eaca30 Giorgos Korfiatis
        return uenc(_("<project %s '%s'>") %
1774 b6eaca30 Giorgos Korfiatis
                    (self.id, udec(self.application.name)))
1775 8c7b8bb8 Giorgos Korfiatis
1776 8c7b8bb8 Giorgos Korfiatis
    __repr__ = __str__
1777 8c7b8bb8 Giorgos Korfiatis
1778 b6eaca30 Giorgos Korfiatis
    def __unicode__(self):
1779 b6eaca30 Giorgos Korfiatis
        return _("<project %s '%s'>") % (self.id, self.application.name)
1780 b6eaca30 Giorgos Korfiatis
1781 e1f31e63 Giorgos Korfiatis
    STATE_DISPLAY = {
1782 d77b32f2 Giorgos Korfiatis
        APPROVED   : 'Active',
1783 d77b32f2 Giorgos Korfiatis
        SUSPENDED  : 'Suspended',
1784 d77b32f2 Giorgos Korfiatis
        TERMINATED : 'Terminated'
1785 e1f31e63 Giorgos Korfiatis
        }
1786 e1f31e63 Giorgos Korfiatis
1787 e1f31e63 Giorgos Korfiatis
    def state_display(self):
1788 e1f31e63 Giorgos Korfiatis
        return self.STATE_DISPLAY.get(self.state, _('Unknown'))
1789 e1f31e63 Giorgos Korfiatis
1790 7eadc230 Giorgos Korfiatis
    def expiration_info(self):
1791 7eadc230 Giorgos Korfiatis
        return (str(self.id), self.name, self.state_display(),
1792 7eadc230 Giorgos Korfiatis
                str(self.application.end_date))
1793 7eadc230 Giorgos Korfiatis
1794 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated(self, reason=None):
1795 b6fe8bb8 Giorgos Korfiatis
        if reason is not None:
1796 b6fe8bb8 Giorgos Korfiatis
            return self.state == reason
1797 425e2e95 Sofia Papagiannaki
1798 b6fe8bb8 Giorgos Korfiatis
        return self.state != self.APPROVED
1799 123be68a Giorgos Korfiatis
1800 123be68a Giorgos Korfiatis
    ### Deactivation calls
1801 425e2e95 Sofia Papagiannaki
1802 123be68a Giorgos Korfiatis
    def terminate(self):
1803 123be68a Giorgos Korfiatis
        self.deactivation_reason = 'TERMINATED'
1804 3d4cef9e Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1805 b6fe8bb8 Giorgos Korfiatis
        self.state = self.TERMINATED
1806 e1017df9 Giorgos Korfiatis
        self.name = None
1807 123be68a Giorgos Korfiatis
        self.save()
1808 8aed306c Giorgos Korfiatis
1809 db99f198 Giorgos Korfiatis
    def suspend(self):
1810 db99f198 Giorgos Korfiatis
        self.deactivation_reason = 'SUSPENDED'
1811 3d4cef9e Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1812 db99f198 Giorgos Korfiatis
        self.state = self.SUSPENDED
1813 db99f198 Giorgos Korfiatis
        self.save()
1814 db99f198 Giorgos Korfiatis
1815 db99f198 Giorgos Korfiatis
    def resume(self):
1816 db99f198 Giorgos Korfiatis
        self.deactivation_reason = None
1817 3d4cef9e Giorgos Korfiatis
        self.deactivation_date = None
1818 db99f198 Giorgos Korfiatis
        self.state = self.APPROVED
1819 db99f198 Giorgos Korfiatis
        self.save()
1820 123be68a Giorgos Korfiatis
1821 123be68a Giorgos Korfiatis
    ### Logical checks
1822 425e2e95 Sofia Papagiannaki
1823 e1a80257 Sofia Papagiannaki
    def is_inconsistent(self):
1824 e1a80257 Sofia Papagiannaki
        now = datetime.now()
1825 5b9e9530 Giorgos Korfiatis
        dates = [self.creation_date,
1826 5b9e9530 Giorgos Korfiatis
                 self.last_approval_date,
1827 5b9e9530 Giorgos Korfiatis
                 self.deactivation_date]
1828 5b9e9530 Giorgos Korfiatis
        return any([date > now for date in dates])
1829 5b9e9530 Giorgos Korfiatis
1830 b6fe8bb8 Giorgos Korfiatis
    def is_active_strict(self):
1831 b6fe8bb8 Giorgos Korfiatis
        return self.is_active and self.state == self.APPROVED
1832 5b9e9530 Giorgos Korfiatis
1833 db99f198 Giorgos Korfiatis
    def is_approved(self):
1834 db99f198 Giorgos Korfiatis
        return self.state == self.APPROVED
1835 db99f198 Giorgos Korfiatis
1836 123be68a Giorgos Korfiatis
    @property
1837 123be68a Giorgos Korfiatis
    def is_alive(self):
1838 72a6e1e8 Giorgos Korfiatis
        return not self.is_terminated
1839 123be68a Giorgos Korfiatis
1840 123be68a Giorgos Korfiatis
    @property
1841 123be68a Giorgos Korfiatis
    def is_terminated(self):
1842 123be68a Giorgos Korfiatis
        return self.is_deactivated(self.TERMINATED)
1843 123be68a Giorgos Korfiatis
1844 123be68a Giorgos Korfiatis
    @property
1845 123be68a Giorgos Korfiatis
    def is_suspended(self):
1846 db99f198 Giorgos Korfiatis
        return self.is_deactivated(self.SUSPENDED)
1847 5b9e9530 Giorgos Korfiatis
1848 5b9e9530 Giorgos Korfiatis
    def violates_resource_grants(self):
1849 e1a80257 Sofia Papagiannaki
        return False
1850 65360c65 Georgios D. Tsoukalas
1851 5b9e9530 Giorgos Korfiatis
    def violates_members_limit(self, adding=0):
1852 5b9e9530 Giorgos Korfiatis
        application = self.application
1853 943d5554 Giorgos Korfiatis
        limit = application.limit_on_members_number
1854 022c61cd Sofia Papagiannaki
        if limit is None:
1855 022c61cd Sofia Papagiannaki
            return False
1856 022c61cd Sofia Papagiannaki
        return (len(self.approved_members) + adding > limit)
1857 5b9e9530 Giorgos Korfiatis
1858 123be68a Giorgos Korfiatis
1859 123be68a Giorgos Korfiatis
    ### Other
1860 5b9e9530 Giorgos Korfiatis
1861 7db8c163 Georgios D. Tsoukalas
    def count_pending_memberships(self):
1862 7db8c163 Georgios D. Tsoukalas
        memb_set = self.projectmembership_set
1863 7db8c163 Georgios D. Tsoukalas
        memb_count = memb_set.filter(state=ProjectMembership.REQUESTED).count()
1864 7db8c163 Georgios D. Tsoukalas
        return memb_count
1865 7db8c163 Georgios D. Tsoukalas
1866 d77b32f2 Giorgos Korfiatis
    def members_count(self):
1867 d77b32f2 Giorgos Korfiatis
        return self.approved_memberships.count()
1868 d77b32f2 Giorgos Korfiatis
1869 425e2e95 Sofia Papagiannaki
    @property
1870 425e2e95 Sofia Papagiannaki
    def approved_memberships(self):
1871 689226c3 Giorgos Korfiatis
        query = ProjectMembership.Q_ACCEPTED_STATES
1872 5b9e9530 Giorgos Korfiatis
        return self.projectmembership_set.filter(query)
1873 4f22664f Georgios D. Tsoukalas
1874 425e2e95 Sofia Papagiannaki
    @property
1875 425e2e95 Sofia Papagiannaki
    def approved_members(self):
1876 425e2e95 Sofia Papagiannaki
        return [m.person for m in self.approved_memberships]
1877 4f22664f Georgios D. Tsoukalas
1878 4f22664f Georgios D. Tsoukalas
    def add_member(self, user):
1879 bfe23b13 Sofia Papagiannaki
        """
1880 bfe23b13 Sofia Papagiannaki
        Raises:
1881 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1882 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1883 bfe23b13 Sofia Papagiannaki
        """
1884 8c7229a8 Giorgos Korfiatis
        if isinstance(user, (int, long)):
1885 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1886 4f22664f Georgios D. Tsoukalas
1887 ccab6eb5 Sofia Papagiannaki
        m, created = ProjectMembership.objects.get_or_create(
1888 ccab6eb5 Sofia Papagiannaki
            person=user, project=self
1889 2a965273 Sofia Papagiannaki
        )
1890 4f22664f Georgios D. Tsoukalas
        m.accept()
1891 ccab6eb5 Sofia Papagiannaki
1892 4f22664f Georgios D. Tsoukalas
    def remove_member(self, user):
1893 bfe23b13 Sofia Papagiannaki
        """
1894 bfe23b13 Sofia Papagiannaki
        Raises:
1895 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1896 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1897 bfe23b13 Sofia Papagiannaki
            astakos.im.models.ProjectMembership.DoesNotExist
1898 bfe23b13 Sofia Papagiannaki
        """
1899 8c7229a8 Giorgos Korfiatis
        if isinstance(user, (int, long)):
1900 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1901 4f22664f Georgios D. Tsoukalas
1902 bfe23b13 Sofia Papagiannaki
        m = ProjectMembership.objects.get(person=user, project=self)
1903 bfe23b13 Sofia Papagiannaki
        m.remove()
1904 4f22664f Georgios D. Tsoukalas
1905 425e2e95 Sofia Papagiannaki
1906 2529745f Giorgos Korfiatis
CHAIN_STATE = {
1907 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.PENDING)  : Chain.APPROVED_PENDING,
1908 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.APPROVED) : Chain.APPROVED,
1909 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.DENIED)   : Chain.APPROVED,
1910 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.DISMISSED): Chain.APPROVED,
1911 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.CANCELLED): Chain.APPROVED,
1912 2529745f Giorgos Korfiatis
1913 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.PENDING)  : Chain.SUSPENDED_PENDING,
1914 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.APPROVED) : Chain.SUSPENDED,
1915 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.DENIED)   : Chain.SUSPENDED,
1916 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.DISMISSED): Chain.SUSPENDED,
1917 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.CANCELLED): Chain.SUSPENDED,
1918 2529745f Giorgos Korfiatis
1919 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.PENDING)  : Chain.TERMINATED_PENDING,
1920 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.APPROVED) : Chain.TERMINATED,
1921 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.DENIED)   : Chain.TERMINATED,
1922 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.DISMISSED): Chain.TERMINATED,
1923 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.CANCELLED): Chain.TERMINATED,
1924 2529745f Giorgos Korfiatis
1925 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.PENDING)  : Chain.PENDING,
1926 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.DENIED)   : Chain.DENIED,
1927 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.DISMISSED): Chain.DISMISSED,
1928 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.CANCELLED): Chain.CANCELLED,
1929 2529745f Giorgos Korfiatis
    }
1930 2529745f Giorgos Korfiatis
1931 2529745f Giorgos Korfiatis
1932 db99f198 Giorgos Korfiatis
class ProjectMembershipManager(ForUpdateManager):
1933 d77b32f2 Giorgos Korfiatis
1934 d77b32f2 Giorgos Korfiatis
    def any_accepted(self):
1935 96efed67 Giorgos Korfiatis
        q = self.model.Q_ACTUALLY_ACCEPTED
1936 d77b32f2 Giorgos Korfiatis
        return self.filter(q)
1937 d77b32f2 Giorgos Korfiatis
1938 c1007621 Giorgos Korfiatis
    def actually_accepted(self):
1939 c1007621 Giorgos Korfiatis
        q = self.model.Q_ACTUALLY_ACCEPTED
1940 c1007621 Giorgos Korfiatis
        return self.filter(q)
1941 c1007621 Giorgos Korfiatis
1942 d77b32f2 Giorgos Korfiatis
    def requested(self):
1943 d77b32f2 Giorgos Korfiatis
        return self.filter(state=ProjectMembership.REQUESTED)
1944 d77b32f2 Giorgos Korfiatis
1945 d77b32f2 Giorgos Korfiatis
    def suspended(self):
1946 d77b32f2 Giorgos Korfiatis
        return self.filter(state=ProjectMembership.USER_SUSPENDED)
1947 db99f198 Giorgos Korfiatis
1948 d6fdc91e Georgios D. Tsoukalas
class ProjectMembership(models.Model):
1949 4f22664f Georgios D. Tsoukalas
1950 425e2e95 Sofia Papagiannaki
    person              =   models.ForeignKey(AstakosUser)
1951 3c638f72 Giorgos Korfiatis
    request_date        =   models.DateField(auto_now_add=True)
1952 d6fdc91e Georgios D. Tsoukalas
    project             =   models.ForeignKey(Project)
1953 d6fdc91e Georgios D. Tsoukalas
1954 db99f198 Giorgos Korfiatis
    REQUESTED           =   0
1955 db99f198 Giorgos Korfiatis
    ACCEPTED            =   1
1956 c1007621 Giorgos Korfiatis
    LEAVE_REQUESTED     =   5
1957 db99f198 Giorgos Korfiatis
    # User deactivation
1958 db99f198 Giorgos Korfiatis
    USER_SUSPENDED      =   10
1959 b6fe8bb8 Giorgos Korfiatis
1960 db99f198 Giorgos Korfiatis
    REMOVED             =   200
1961 db99f198 Giorgos Korfiatis
1962 db99f198 Giorgos Korfiatis
    ASSOCIATED_STATES   =   set([REQUESTED,
1963 db99f198 Giorgos Korfiatis
                                 ACCEPTED,
1964 c1007621 Giorgos Korfiatis
                                 LEAVE_REQUESTED,
1965 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1966 96efed67 Giorgos Korfiatis
                                 ])
1967 db99f198 Giorgos Korfiatis
1968 db99f198 Giorgos Korfiatis
    ACCEPTED_STATES     =   set([ACCEPTED,
1969 c1007621 Giorgos Korfiatis
                                 LEAVE_REQUESTED,
1970 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1971 96efed67 Giorgos Korfiatis
                                 ])
1972 05617ab9 Kostas Papadimitriou
1973 c1007621 Giorgos Korfiatis
    ACTUALLY_ACCEPTED   =   set([ACCEPTED, LEAVE_REQUESTED])
1974 c1007621 Giorgos Korfiatis
1975 b6fe8bb8 Giorgos Korfiatis
    state               =   models.IntegerField(default=REQUESTED,
1976 b6fe8bb8 Giorgos Korfiatis
                                                db_index=True)
1977 b6fe8bb8 Giorgos Korfiatis
    is_pending          =   models.BooleanField(default=False, db_index=True)
1978 b6fe8bb8 Giorgos Korfiatis
    is_active           =   models.BooleanField(default=False, db_index=True)
1979 5200e864 Sofia Papagiannaki
    application         =   models.ForeignKey(
1980 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1981 5200e864 Sofia Papagiannaki
                                null=True,
1982 5200e864 Sofia Papagiannaki
                                related_name='memberships')
1983 5200e864 Sofia Papagiannaki
    pending_application =   models.ForeignKey(
1984 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1985 5200e864 Sofia Papagiannaki
                                null=True,
1986 cd633c29 Giorgos Korfiatis
                                related_name='pending_memberships')
1987 d6fdc91e Georgios D. Tsoukalas
    pending_serial      =   models.BigIntegerField(null=True, db_index=True)
1988 425e2e95 Sofia Papagiannaki
1989 425e2e95 Sofia Papagiannaki
    acceptance_date     =   models.DateField(null=True, db_index=True)
1990 425e2e95 Sofia Papagiannaki
    leave_request_date  =   models.DateField(null=True)
1991 2a965273 Sofia Papagiannaki
1992 db99f198 Giorgos Korfiatis
    objects     =   ProjectMembershipManager()
1993 ee45eb81 Giorgos Korfiatis
1994 689226c3 Giorgos Korfiatis
    # Compiled queries
1995 689226c3 Giorgos Korfiatis
    Q_ACCEPTED_STATES = ~Q(state=REQUESTED) & ~Q(state=REMOVED)
1996 c1007621 Giorgos Korfiatis
    Q_ACTUALLY_ACCEPTED = Q(state=ACCEPTED) | Q(state=LEAVE_REQUESTED)
1997 5b9e9530 Giorgos Korfiatis
1998 d77b32f2 Giorgos Korfiatis
    MEMBERSHIP_STATE_DISPLAY = {
1999 d4660e00 Giorgos Korfiatis
        REQUESTED           : _('Requested'),
2000 d4660e00 Giorgos Korfiatis
        ACCEPTED            : _('Accepted'),
2001 c1007621 Giorgos Korfiatis
        LEAVE_REQUESTED     : _('Leave Requested'),
2002 d4660e00 Giorgos Korfiatis
        USER_SUSPENDED      : _('Suspended'),
2003 d4660e00 Giorgos Korfiatis
        REMOVED             : _('Pending removal'),
2004 d4660e00 Giorgos Korfiatis
        }
2005 d4660e00 Giorgos Korfiatis
2006 d4660e00 Giorgos Korfiatis
    USER_FRIENDLY_STATE_DISPLAY = {
2007 d4660e00 Giorgos Korfiatis
        REQUESTED           : _('Join requested'),
2008 d4660e00 Giorgos Korfiatis
        ACCEPTED            : _('Accepted member'),
2009 c1007621 Giorgos Korfiatis
        LEAVE_REQUESTED     : _('Requested to leave'),
2010 d4660e00 Giorgos Korfiatis
        USER_SUSPENDED      : _('Suspended member'),
2011 d4660e00 Giorgos Korfiatis
        REMOVED             : _('Pending removal'),
2012 d77b32f2 Giorgos Korfiatis
        }
2013 d77b32f2 Giorgos Korfiatis
2014 d77b32f2 Giorgos Korfiatis
    def state_display(self):
2015 d77b32f2 Giorgos Korfiatis
        return self.MEMBERSHIP_STATE_DISPLAY.get(self.state, _('Unknown'))
2016 d77b32f2 Giorgos Korfiatis
2017 d4660e00 Giorgos Korfiatis
    def user_friendly_state_display(self):
2018 d4660e00 Giorgos Korfiatis
        return self.USER_FRIENDLY_STATE_DISPLAY.get(self.state, _('Unknown'))
2019 d4660e00 Giorgos Korfiatis
2020 0cc22d47 Sofia Papagiannaki
    class Meta:
2021 0cc22d47 Sofia Papagiannaki
        unique_together = ("person", "project")
2022 d6fdc91e Georgios D. Tsoukalas
        #index_together = [["project", "state"]]
2023 bfe23b13 Sofia Papagiannaki
2024 65360c65 Georgios D. Tsoukalas
    def __str__(self):
2025 b6eaca30 Giorgos Korfiatis
        return uenc(_("<'%s' membership in '%s'>") % (
2026 b6eaca30 Giorgos Korfiatis
                self.person.username, self.project))
2027 65360c65 Georgios D. Tsoukalas
2028 65360c65 Georgios D. Tsoukalas
    __repr__ = __str__
2029 65360c65 Georgios D. Tsoukalas
2030 65360c65 Georgios D. Tsoukalas
    def __init__(self, *args, **kwargs):
2031 ee45eb81 Giorgos Korfiatis
        self.state = self.REQUESTED
2032 65360c65 Georgios D. Tsoukalas
        super(ProjectMembership, self).__init__(*args, **kwargs)
2033 65360c65 Georgios D. Tsoukalas
2034 4f22664f Georgios D. Tsoukalas
    def _set_history_item(self, reason, date=None):
2035 4f22664f Georgios D. Tsoukalas
        if isinstance(reason, basestring):
2036 4f22664f Georgios D. Tsoukalas
            reason = ProjectMembershipHistory.reasons.get(reason, -1)
2037 4f22664f Georgios D. Tsoukalas
2038 4f22664f Georgios D. Tsoukalas
        history_item = ProjectMembershipHistory(
2039 4f22664f Georgios D. Tsoukalas
                            serial=self.id,
2040 d0e78bbe Giorgos Korfiatis
                            person=self.person_id,
2041 02d2519e Giorgos Korfiatis
                            project=self.project_id,
2042 8f975b72 Sofia Papagiannaki
                            date=date or datetime.now(),
2043 4f22664f Georgios D. Tsoukalas
                            reason=reason)
2044 4f22664f Georgios D. Tsoukalas
        history_item.save()
2045 4f22664f Georgios D. Tsoukalas
        serial = history_item.id
2046 4f22664f Georgios D. Tsoukalas
2047 14f7f6a5 Giorgos Korfiatis
    def can_accept(self):
2048 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
2049 14f7f6a5 Giorgos Korfiatis
2050 4f22664f Georgios D. Tsoukalas
    def accept(self):
2051 14f7f6a5 Giorgos Korfiatis
        if not self.can_accept():
2052 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to accept in state '%s'") % (self, self.state)
2053 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2054 4f22664f Georgios D. Tsoukalas
2055 65360c65 Georgios D. Tsoukalas
        now = datetime.now()
2056 65360c65 Georgios D. Tsoukalas
        self.acceptance_date = now
2057 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='ACCEPT', date=now)
2058 96efed67 Giorgos Korfiatis
        self.state = self.ACCEPTED
2059 65360c65 Georgios D. Tsoukalas
        self.save()
2060 4f22664f Georgios D. Tsoukalas
2061 14f7f6a5 Giorgos Korfiatis
    def can_leave(self):
2062 c1007621 Giorgos Korfiatis
        return self.state in self.ACCEPTED_STATES
2063 c1007621 Giorgos Korfiatis
2064 c1007621 Giorgos Korfiatis
    def leave_request(self):
2065 c1007621 Giorgos Korfiatis
        if not self.can_leave():
2066 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to request to leave in state '%s'") % (
2067 c1007621 Giorgos Korfiatis
                self, self.state)
2068 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2069 c1007621 Giorgos Korfiatis
2070 c1007621 Giorgos Korfiatis
        self.leave_request_date = datetime.now()
2071 c1007621 Giorgos Korfiatis
        self.state = self.LEAVE_REQUESTED
2072 c1007621 Giorgos Korfiatis
        self.save()
2073 c1007621 Giorgos Korfiatis
2074 c1007621 Giorgos Korfiatis
    def can_deny_leave(self):
2075 c1007621 Giorgos Korfiatis
        return self.state == self.LEAVE_REQUESTED
2076 c1007621 Giorgos Korfiatis
2077 c1007621 Giorgos Korfiatis
    def leave_request_deny(self):
2078 c1007621 Giorgos Korfiatis
        if not self.can_deny_leave():
2079 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to deny leave request in state '%s'") % (
2080 c1007621 Giorgos Korfiatis
                self, self.state)
2081 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2082 c1007621 Giorgos Korfiatis
2083 c1007621 Giorgos Korfiatis
        self.leave_request_date = None
2084 c1007621 Giorgos Korfiatis
        self.state = self.ACCEPTED
2085 c1007621 Giorgos Korfiatis
        self.save()
2086 c1007621 Giorgos Korfiatis
2087 c1007621 Giorgos Korfiatis
    def can_cancel_leave(self):
2088 c1007621 Giorgos Korfiatis
        return self.state == self.LEAVE_REQUESTED
2089 c1007621 Giorgos Korfiatis
2090 c1007621 Giorgos Korfiatis
    def leave_request_cancel(self):
2091 c1007621 Giorgos Korfiatis
        if not self.can_cancel_leave():
2092 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to cancel leave request in state '%s'") % (
2093 c1007621 Giorgos Korfiatis
                self, self.state)
2094 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2095 c1007621 Giorgos Korfiatis
2096 c1007621 Giorgos Korfiatis
        self.leave_request_date = None
2097 c1007621 Giorgos Korfiatis
        self.state = self.ACCEPTED
2098 c1007621 Giorgos Korfiatis
        self.save()
2099 14f7f6a5 Giorgos Korfiatis
2100 14f7f6a5 Giorgos Korfiatis
    def can_remove(self):
2101 14f7f6a5 Giorgos Korfiatis
        return self.state in self.ACCEPTED_STATES
2102 14f7f6a5 Giorgos Korfiatis
2103 65360c65 Georgios D. Tsoukalas
    def remove(self):
2104 14f7f6a5 Giorgos Korfiatis
        if not self.can_remove():
2105 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to remove in state '%s'") % (self, self.state)
2106 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2107 4f22664f Georgios D. Tsoukalas
2108 ee45eb81 Giorgos Korfiatis
        self._set_history_item(reason='REMOVE')
2109 8cbea11d Giorgos Korfiatis
        self.delete()
2110 b8f05f8d Sofia Papagiannaki
2111 14f7f6a5 Giorgos Korfiatis
    def can_reject(self):
2112 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
2113 14f7f6a5 Giorgos Korfiatis
2114 65360c65 Georgios D. Tsoukalas
    def reject(self):
2115 14f7f6a5 Giorgos Korfiatis
        if not self.can_reject():
2116 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to reject in state '%s'") % (self, self.state)
2117 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2118 65360c65 Georgios D. Tsoukalas
2119 65360c65 Georgios D. Tsoukalas
        # rejected requests don't need sync,
2120 65360c65 Georgios D. Tsoukalas
        # because they were never effected
2121 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='REJECT')
2122 0cc22d47 Sofia Papagiannaki
        self.delete()
2123 b8f05f8d Sofia Papagiannaki
2124 aad0e329 Giorgos Korfiatis
    def can_cancel(self):
2125 aad0e329 Giorgos Korfiatis
        return self.state == self.REQUESTED
2126 aad0e329 Giorgos Korfiatis
2127 aad0e329 Giorgos Korfiatis
    def cancel(self):
2128 aad0e329 Giorgos Korfiatis
        if not self.can_cancel():
2129 aad0e329 Giorgos Korfiatis
            m = _("%s: attempt to cancel in state '%s'") % (self, self.state)
2130 aad0e329 Giorgos Korfiatis
            raise AssertionError(m)
2131 aad0e329 Giorgos Korfiatis
2132 aad0e329 Giorgos Korfiatis
        # rejected requests don't need sync,
2133 aad0e329 Giorgos Korfiatis
        # because they were never effected
2134 aad0e329 Giorgos Korfiatis
        self._set_history_item(reason='CANCEL')
2135 aad0e329 Giorgos Korfiatis
        self.delete()
2136 aad0e329 Giorgos Korfiatis
2137 49b74233 Georgios D. Tsoukalas
2138 ee45eb81 Giorgos Korfiatis
class Serial(models.Model):
2139 ee45eb81 Giorgos Korfiatis
    serial  =   models.AutoField(primary_key=True)
2140 ee45eb81 Giorgos Korfiatis
2141 60ca2f6f Giorgos Korfiatis
2142 0cc22d47 Sofia Papagiannaki
class ProjectMembershipHistory(models.Model):
2143 425e2e95 Sofia Papagiannaki
    reasons_list    =   ['ACCEPT', 'REJECT', 'REMOVE']
2144 425e2e95 Sofia Papagiannaki
    reasons         =   dict((k, v) for v, k in enumerate(reasons_list))
2145 425e2e95 Sofia Papagiannaki
2146 d0e78bbe Giorgos Korfiatis
    person  =   models.BigIntegerField()
2147 02d2519e Giorgos Korfiatis
    project =   models.BigIntegerField()
2148 3c638f72 Giorgos Korfiatis
    date    =   models.DateField(auto_now_add=True)
2149 425e2e95 Sofia Papagiannaki
    reason  =   models.IntegerField()
2150 425e2e95 Sofia Papagiannaki
    serial  =   models.BigIntegerField()
2151 fc655b6f Kostas Papadimitriou
2152 fcc1e93f Sofia Papagiannaki
### SIGNALS ###
2153 fcc1e93f Sofia Papagiannaki
################
2154 b22de10a Sofia Papagiannaki
2155 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
2156 ff9290ec Sofia Papagiannaki
    try:
2157 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
2158 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
2159 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
2160 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
2161 ff9290ec Sofia Papagiannaki
        extended_user.save()
2162 67be1883 Olga Brani
        if not extended_user.has_auth_provider('local'):
2163 67be1883 Olga Brani
            extended_user.add_auth_provider('local')
2164 fc1e2f02 Sofia Papagiannaki
    except BaseException, e:
2165 fc1e2f02 Sofia Papagiannaki
        logger.exception(e)
2166 ff9290ec Sofia Papagiannaki
2167 a7752e95 Sofia Papagiannaki
def fix_superusers():
2168 fc1e2f02 Sofia Papagiannaki
    # Associate superusers with AstakosUser
2169 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
2170 ff9290ec Sofia Papagiannaki
    for u in admins:
2171 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
2172 ff9290ec Sofia Papagiannaki
2173 fc1e2f02 Sofia Papagiannaki
def user_post_save(sender, instance, created, **kwargs):
2174 aa4109d4 Sofia Papagiannaki
    if not created:
2175 aa4109d4 Sofia Papagiannaki
        return
2176 fc1e2f02 Sofia Papagiannaki
    create_astakos_user(instance)
2177 bfe23b13 Sofia Papagiannaki
post_save.connect(user_post_save, sender=User)
2178 ff9290ec Sofia Papagiannaki
2179 fc1e2f02 Sofia Papagiannaki
def astakosuser_post_save(sender, instance, created, **kwargs):
2180 21e0fdad Giorgos Korfiatis
    pass
2181 21e0fdad Giorgos Korfiatis
2182 bfe23b13 Sofia Papagiannaki
post_save.connect(astakosuser_post_save, sender=AstakosUser)
2183 fc1e2f02 Sofia Papagiannaki
2184 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
2185 0514bcc7 Giorgos Korfiatis
    pass
2186 0514bcc7 Giorgos Korfiatis
2187 bfe23b13 Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
2188 bfe23b13 Sofia Papagiannaki
2189 bfe23b13 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
2190 bfe23b13 Sofia Papagiannaki
    if not instance.auth_token:
2191 bfe23b13 Sofia Papagiannaki
        instance.renew_token()
2192 bf0c6de5 Sofia Papagiannaki
pre_save.connect(renew_token, sender=AstakosUser)
2193 2e90e3ec Kostas Papadimitriou
pre_save.connect(renew_token, sender=Service)