Statistics
| Branch: | Tag: | Revision:

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

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

1082 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1083 49790d9d Sofia Papagiannaki
        after activating.
1084 49790d9d Sofia Papagiannaki

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

1087 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1088 49790d9d Sofia Papagiannaki
        return ``None``.
1089 49790d9d Sofia Papagiannaki

1090 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1091 49790d9d Sofia Papagiannaki

1092 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
1093 49790d9d Sofia Papagiannaki
        """
1094 49790d9d Sofia Papagiannaki
        try:
1095 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
1096 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
1097 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
1098 49790d9d Sofia Papagiannaki
                email_change.delete()
1099 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
1100 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
1101 49790d9d Sofia Papagiannaki
            try:
1102 789a5951 Sofia Papagiannaki
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
1103 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
1104 49790d9d Sofia Papagiannaki
                pass
1105 49790d9d Sofia Papagiannaki
            else:
1106 73fbaec4 Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
1107 49790d9d Sofia Papagiannaki
            # update user
1108 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
1109 34a76cdb Kostas Papadimitriou
            old_email = user.email
1110 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
1111 49790d9d Sofia Papagiannaki
            user.save()
1112 49790d9d Sofia Papagiannaki
            email_change.delete()
1113 5d5ce247 Kostas Papadimitriou
            msg = "User %s changed email from %s to %s" % (user.log_display,
1114 5d5ce247 Kostas Papadimitriou
                                                           old_email,
1115 5d5ce247 Kostas Papadimitriou
                                                           user.email)
1116 8998f09a Sofia Papagiannaki
            logger.log(astakos_settings.LOGGING_LEVEL, msg)
1117 49790d9d Sofia Papagiannaki
            return user
1118 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
1119 73fbaec4 Sofia Papagiannaki
            raise ValueError(_('Invalid activation key.'))
1120 49790d9d Sofia Papagiannaki
1121 49790d9d Sofia Papagiannaki
1122 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
1123 73fbaec4 Sofia Papagiannaki
    new_email_address = models.EmailField(
1124 73fbaec4 Sofia Papagiannaki
        _(u'new e-mail address'),
1125 b8b8fdde Constantinos Venetsanopoulos
        help_text=_('Provide a new email address. Until you verify the new '
1126 b8b8fdde Constantinos Venetsanopoulos
                    'address by following the activation link that will be '
1127 b8b8fdde Constantinos Venetsanopoulos
                    'sent to it, your old email address will remain active.'))
1128 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
1129 34a76cdb Kostas Papadimitriou
        AstakosUser, unique=True, related_name='emailchanges')
1130 3c638f72 Giorgos Korfiatis
    requested_at = models.DateTimeField(auto_now_add=True)
1131 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
1132 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
1133 49790d9d Sofia Papagiannaki
1134 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
1135 49790d9d Sofia Papagiannaki
1136 34a76cdb Kostas Papadimitriou
    def get_url(self):
1137 34a76cdb Kostas Papadimitriou
        return reverse('email_change_confirm',
1138 34a76cdb Kostas Papadimitriou
                      kwargs={'activation_key': self.activation_key})
1139 34a76cdb Kostas Papadimitriou
1140 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
1141 8998f09a Sofia Papagiannaki
        expiration_date = timedelta(days=astakos_settings.EMAILCHANGE_ACTIVATION_DAYS)
1142 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
1143 ff9290ec Sofia Papagiannaki
1144 6b03a847 Sofia Papagiannaki
1145 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
1146 ca828a10 Sofia Papagiannaki
    """
1147 ca828a10 Sofia Papagiannaki
    Model for registring invitations
1148 ca828a10 Sofia Papagiannaki
    """
1149 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
1150 1eec103a Sofia Papagiannaki
    email = models.EmailField()
1151 ca828a10 Sofia Papagiannaki
1152 5ce3ce4f Sofia Papagiannaki
1153 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
1154 fc1e2f02 Sofia Papagiannaki
    while True:
1155 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
1156 fc1e2f02 Sofia Papagiannaki
        try:
1157 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
1158 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
1159 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
1160 fc1e2f02 Sofia Papagiannaki
            return code
1161 fc1e2f02 Sofia Papagiannaki
1162 5ce3ce4f Sofia Papagiannaki
1163 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
1164 fc1e2f02 Sofia Papagiannaki
    try:
1165 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
1166 fc1e2f02 Sofia Papagiannaki
        return term
1167 fc1e2f02 Sofia Papagiannaki
    except IndexError:
1168 fc1e2f02 Sofia Papagiannaki
        pass
1169 fc1e2f02 Sofia Papagiannaki
    return None
1170 fc1e2f02 Sofia Papagiannaki
1171 9d20fe23 Kostas Papadimitriou
1172 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
1173 ef20ea07 Sofia Papagiannaki
    """
1174 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
1175 ef20ea07 Sofia Papagiannaki
    """
1176 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
1177 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
1178 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
1179 564a2292 Kostas Papadimitriou
    first_name = models.CharField(_('first name'), max_length=30, blank=True,
1180 564a2292 Kostas Papadimitriou
                                  null=True)
1181 564a2292 Kostas Papadimitriou
    last_name = models.CharField(_('last name'), max_length=30, blank=True,
1182 564a2292 Kostas Papadimitriou
                                 null=True)
1183 564a2292 Kostas Papadimitriou
    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
1184 564a2292 Kostas Papadimitriou
                                   null=True)
1185 9d20fe23 Kostas Papadimitriou
    username = models.CharField(_('username'), max_length=30, unique=True,
1186 d6ea9b3d Olga Brani
                                help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
1187 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
1188 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
1189 c630fee6 Kostas Papadimitriou
    info = models.TextField(default="", null=True, blank=True)
1190 d2633501 Kostas Papadimitriou
1191 678b2236 Sofia Papagiannaki
    class Meta:
1192 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
1193 ef20ea07 Sofia Papagiannaki
1194 c630fee6 Kostas Papadimitriou
    def get_user_instance(self):
1195 e7cb4085 Kostas Papadimitriou
        """
1196 e7cb4085 Kostas Papadimitriou
        Create a new AstakosUser instance based on details provided when user
1197 e7cb4085 Kostas Papadimitriou
        initially signed up.
1198 e7cb4085 Kostas Papadimitriou
        """
1199 3dfb68fe Kostas Papadimitriou
        d = copy.copy(self.__dict__)
1200 c630fee6 Kostas Papadimitriou
        d.pop('_state', None)
1201 c630fee6 Kostas Papadimitriou
        d.pop('id', None)
1202 c630fee6 Kostas Papadimitriou
        d.pop('token', None)
1203 c630fee6 Kostas Papadimitriou
        d.pop('created', None)
1204 c630fee6 Kostas Papadimitriou
        d.pop('info', None)
1205 3dfb68fe Kostas Papadimitriou
        d.pop('affiliation', None)
1206 3dfb68fe Kostas Papadimitriou
        d.pop('provider', None)
1207 3dfb68fe Kostas Papadimitriou
        d.pop('third_party_identifier', None)
1208 c630fee6 Kostas Papadimitriou
        user = AstakosUser(**d)
1209 c630fee6 Kostas Papadimitriou
1210 c630fee6 Kostas Papadimitriou
        return user
1211 c630fee6 Kostas Papadimitriou
1212 ef20ea07 Sofia Papagiannaki
    @property
1213 ef20ea07 Sofia Papagiannaki
    def realname(self):
1214 ef20ea07 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
1215 ef20ea07 Sofia Papagiannaki
1216 ef20ea07 Sofia Papagiannaki
    @realname.setter
1217 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
1218 ef20ea07 Sofia Papagiannaki
        parts = value.split(' ')
1219 ef20ea07 Sofia Papagiannaki
        if len(parts) == 2:
1220 ef20ea07 Sofia Papagiannaki
            self.first_name = parts[0]
1221 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[1]
1222 ef20ea07 Sofia Papagiannaki
        else:
1223 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[0]
1224 2e90e3ec Kostas Papadimitriou
1225 ef20ea07 Sofia Papagiannaki
    def save(self, **kwargs):
1226 ef20ea07 Sofia Papagiannaki
        if not self.id:
1227 ef20ea07 Sofia Papagiannaki
            # set username
1228 ef20ea07 Sofia Papagiannaki
            while not self.username:
1229 ef20ea07 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
1230 ef20ea07 Sofia Papagiannaki
                try:
1231 ef20ea07 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
1232 ef20ea07 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
1233 ef20ea07 Sofia Papagiannaki
                    self.username = username
1234 ef20ea07 Sofia Papagiannaki
        super(PendingThirdPartyUser, self).save(**kwargs)
1235 ef20ea07 Sofia Papagiannaki
1236 d2633501 Kostas Papadimitriou
    def generate_token(self):
1237 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1238 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1239 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1240 d2633501 Kostas Papadimitriou
1241 606dea8d Kostas Papadimitriou
    def existing_user(self):
1242 606dea8d Kostas Papadimitriou
        return AstakosUser.objects.filter(auth_providers__module=self.provider,
1243 606dea8d Kostas Papadimitriou
                                         auth_providers__identifier=self.third_party_identifier)
1244 606dea8d Kostas Papadimitriou
1245 9d20fe23 Kostas Papadimitriou
    def get_provider(self, user):
1246 9d20fe23 Kostas Papadimitriou
        params = {
1247 9d20fe23 Kostas Papadimitriou
            'info_data': self.info,
1248 9d20fe23 Kostas Papadimitriou
            'affiliation': self.affiliation
1249 9d20fe23 Kostas Papadimitriou
        }
1250 9d20fe23 Kostas Papadimitriou
        return auth.get_provider(self.provider, user,
1251 9d20fe23 Kostas Papadimitriou
                                 self.third_party_identifier, **params)
1252 9d20fe23 Kostas Papadimitriou
1253 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1254 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1255 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1256 bf0c6de5 Sofia Papagiannaki
1257 fcc1e93f Sofia Papagiannaki
1258 c7c0ec58 Giorgos Korfiatis
class UserSetting(models.Model):
1259 c7c0ec58 Giorgos Korfiatis
    user = models.ForeignKey(AstakosUser)
1260 c7c0ec58 Giorgos Korfiatis
    setting = models.CharField(max_length=255)
1261 c7c0ec58 Giorgos Korfiatis
    value = models.IntegerField()
1262 c7c0ec58 Giorgos Korfiatis
1263 c7c0ec58 Giorgos Korfiatis
    objects = ForUpdateManager()
1264 c7c0ec58 Giorgos Korfiatis
1265 c7c0ec58 Giorgos Korfiatis
    class Meta:
1266 c7c0ec58 Giorgos Korfiatis
        unique_together = ("user", "setting")
1267 c7c0ec58 Giorgos Korfiatis
1268 c7c0ec58 Giorgos Korfiatis
1269 fcc1e93f Sofia Papagiannaki
### PROJECTS ###
1270 fcc1e93f Sofia Papagiannaki
################
1271 fcc1e93f Sofia Papagiannaki
1272 2529745f Giorgos Korfiatis
class ChainManager(ForUpdateManager):
1273 2529745f Giorgos Korfiatis
1274 2529745f Giorgos Korfiatis
    def search_by_name(self, *search_strings):
1275 2529745f Giorgos Korfiatis
        projects = Project.objects.search_by_name(*search_strings)
1276 2529745f Giorgos Korfiatis
        chains = [p.id for p in projects]
1277 2529745f Giorgos Korfiatis
        apps  = ProjectApplication.objects.search_by_name(*search_strings)
1278 2529745f Giorgos Korfiatis
        apps = (app for app in apps if app.is_latest())
1279 2529745f Giorgos Korfiatis
        app_chains = [app.chain for app in apps if app.chain not in chains]
1280 2529745f Giorgos Korfiatis
        return chains + app_chains
1281 2529745f Giorgos Korfiatis
1282 2529745f Giorgos Korfiatis
    def all_full_state(self):
1283 2529745f Giorgos Korfiatis
        chains = self.all()
1284 4391de3d Giorgos Korfiatis
        cids = [c.chain for c in chains]
1285 4391de3d Giorgos Korfiatis
        projects = Project.objects.select_related('application').in_bulk(cids)
1286 4391de3d Giorgos Korfiatis
1287 4391de3d Giorgos Korfiatis
        objs = Chain.objects.annotate(latest=Max('chained_apps__id'))
1288 4391de3d Giorgos Korfiatis
        chain_latest = dict(objs.values_list('chain', 'latest'))
1289 4391de3d Giorgos Korfiatis
1290 4391de3d Giorgos Korfiatis
        objs = ProjectApplication.objects.select_related('applicant')
1291 4391de3d Giorgos Korfiatis
        apps = objs.in_bulk(chain_latest.values())
1292 4391de3d Giorgos Korfiatis
1293 4391de3d Giorgos Korfiatis
        d = {}
1294 2529745f Giorgos Korfiatis
        for chain in chains:
1295 4391de3d Giorgos Korfiatis
            pk = chain.pk
1296 4391de3d Giorgos Korfiatis
            project = projects.get(pk, None)
1297 4391de3d Giorgos Korfiatis
            app = apps[chain_latest[pk]]
1298 4391de3d Giorgos Korfiatis
            d[chain.pk] = chain.get_state(project, app)
1299 4391de3d Giorgos Korfiatis
1300 2529745f Giorgos Korfiatis
        return d
1301 2529745f Giorgos Korfiatis
1302 2529745f Giorgos Korfiatis
    def of_project(self, project):
1303 2529745f Giorgos Korfiatis
        if project is None:
1304 2529745f Giorgos Korfiatis
            return None
1305 2529745f Giorgos Korfiatis
        try:
1306 2529745f Giorgos Korfiatis
            return self.get(chain=project.id)
1307 2529745f Giorgos Korfiatis
        except Chain.DoesNotExist:
1308 2529745f Giorgos Korfiatis
            raise AssertionError('project with no chain')
1309 2529745f Giorgos Korfiatis
1310 2529745f Giorgos Korfiatis
1311 033f2822 Giorgos Korfiatis
class Chain(models.Model):
1312 033f2822 Giorgos Korfiatis
    chain  =   models.AutoField(primary_key=True)
1313 033f2822 Giorgos Korfiatis
1314 033f2822 Giorgos Korfiatis
    def __str__(self):
1315 033f2822 Giorgos Korfiatis
        return "%s" % (self.chain,)
1316 033f2822 Giorgos Korfiatis
1317 2529745f Giorgos Korfiatis
    objects = ChainManager()
1318 2529745f Giorgos Korfiatis
1319 2529745f Giorgos Korfiatis
    PENDING            = 0
1320 2529745f Giorgos Korfiatis
    DENIED             = 3
1321 2529745f Giorgos Korfiatis
    DISMISSED          = 4
1322 2529745f Giorgos Korfiatis
    CANCELLED          = 5
1323 2529745f Giorgos Korfiatis
1324 2529745f Giorgos Korfiatis
    APPROVED           = 10
1325 2529745f Giorgos Korfiatis
    APPROVED_PENDING   = 11
1326 2529745f Giorgos Korfiatis
    SUSPENDED          = 12
1327 2529745f Giorgos Korfiatis
    SUSPENDED_PENDING  = 13
1328 2529745f Giorgos Korfiatis
    TERMINATED         = 14
1329 2529745f Giorgos Korfiatis
    TERMINATED_PENDING = 15
1330 2529745f Giorgos Korfiatis
1331 2529745f Giorgos Korfiatis
    PENDING_STATES = [PENDING,
1332 2529745f Giorgos Korfiatis
                      APPROVED_PENDING,
1333 2529745f Giorgos Korfiatis
                      SUSPENDED_PENDING,
1334 2529745f Giorgos Korfiatis
                      TERMINATED_PENDING,
1335 2529745f Giorgos Korfiatis
                      ]
1336 2529745f Giorgos Korfiatis
1337 f557d10a Giorgos Korfiatis
    MODIFICATION_STATES = [APPROVED_PENDING,
1338 f557d10a Giorgos Korfiatis
                           SUSPENDED_PENDING,
1339 f557d10a Giorgos Korfiatis
                           TERMINATED_PENDING,
1340 f557d10a Giorgos Korfiatis
                           ]
1341 f557d10a Giorgos Korfiatis
1342 f557d10a Giorgos Korfiatis
    RELEVANT_STATES = [PENDING,
1343 f557d10a Giorgos Korfiatis
                       DENIED,
1344 f557d10a Giorgos Korfiatis
                       APPROVED,
1345 f557d10a Giorgos Korfiatis
                       APPROVED_PENDING,
1346 f557d10a Giorgos Korfiatis
                       SUSPENDED,
1347 f557d10a Giorgos Korfiatis
                       SUSPENDED_PENDING,
1348 f557d10a Giorgos Korfiatis
                       TERMINATED_PENDING,
1349 f557d10a Giorgos Korfiatis
                       ]
1350 f557d10a Giorgos Korfiatis
1351 2529745f Giorgos Korfiatis
    SKIP_STATES = [DISMISSED,
1352 2529745f Giorgos Korfiatis
                   CANCELLED,
1353 2529745f Giorgos Korfiatis
                   TERMINATED]
1354 2529745f Giorgos Korfiatis
1355 2529745f Giorgos Korfiatis
    STATE_DISPLAY = {
1356 5d209685 Giorgos Korfiatis
        PENDING            : _("Pending"),
1357 2529745f Giorgos Korfiatis
        DENIED             : _("Denied"),
1358 2529745f Giorgos Korfiatis
        DISMISSED          : _("Dismissed"),
1359 2529745f Giorgos Korfiatis
        CANCELLED          : _("Cancelled"),
1360 2529745f Giorgos Korfiatis
        APPROVED           : _("Active"),
1361 2529745f Giorgos Korfiatis
        APPROVED_PENDING   : _("Active - Pending"),
1362 2529745f Giorgos Korfiatis
        SUSPENDED          : _("Suspended"),
1363 2529745f Giorgos Korfiatis
        SUSPENDED_PENDING  : _("Suspended - Pending"),
1364 2529745f Giorgos Korfiatis
        TERMINATED         : _("Terminated"),
1365 2529745f Giorgos Korfiatis
        TERMINATED_PENDING : _("Terminated - Pending"),
1366 2529745f Giorgos Korfiatis
        }
1367 2529745f Giorgos Korfiatis
1368 2529745f Giorgos Korfiatis
1369 2529745f Giorgos Korfiatis
    @classmethod
1370 2529745f Giorgos Korfiatis
    def _chain_state(cls, project_state, app_state):
1371 2529745f Giorgos Korfiatis
        s = CHAIN_STATE.get((project_state, app_state), None)
1372 2529745f Giorgos Korfiatis
        if s is None:
1373 2529745f Giorgos Korfiatis
            raise AssertionError('inconsistent chain state')
1374 2529745f Giorgos Korfiatis
        return s
1375 2529745f Giorgos Korfiatis
1376 2529745f Giorgos Korfiatis
    @classmethod
1377 2529745f Giorgos Korfiatis
    def chain_state(cls, project, app):
1378 2529745f Giorgos Korfiatis
        p_state = project.state if project else None
1379 2529745f Giorgos Korfiatis
        return cls._chain_state(p_state, app.state)
1380 2529745f Giorgos Korfiatis
1381 2529745f Giorgos Korfiatis
    @classmethod
1382 2529745f Giorgos Korfiatis
    def state_display(cls, s):
1383 2529745f Giorgos Korfiatis
        if s is None:
1384 2529745f Giorgos Korfiatis
            return _("Unknown")
1385 2529745f Giorgos Korfiatis
        return cls.STATE_DISPLAY.get(s, _("Inconsistent"))
1386 2529745f Giorgos Korfiatis
1387 2529745f Giorgos Korfiatis
    def last_application(self):
1388 2529745f Giorgos Korfiatis
        return self.chained_apps.order_by('-id')[0]
1389 2529745f Giorgos Korfiatis
1390 2529745f Giorgos Korfiatis
    def get_project(self):
1391 2529745f Giorgos Korfiatis
        try:
1392 2529745f Giorgos Korfiatis
            return self.chained_project
1393 2529745f Giorgos Korfiatis
        except Project.DoesNotExist:
1394 2529745f Giorgos Korfiatis
            return None
1395 2529745f Giorgos Korfiatis
1396 2529745f Giorgos Korfiatis
    def get_elements(self):
1397 2529745f Giorgos Korfiatis
        project = self.get_project()
1398 2529745f Giorgos Korfiatis
        app = self.last_application()
1399 2529745f Giorgos Korfiatis
        return project, app
1400 2529745f Giorgos Korfiatis
1401 4391de3d Giorgos Korfiatis
    def get_state(self, project, app):
1402 2529745f Giorgos Korfiatis
        s = self.chain_state(project, app)
1403 2529745f Giorgos Korfiatis
        return s, project, app
1404 2529745f Giorgos Korfiatis
1405 4391de3d Giorgos Korfiatis
    def full_state(self):
1406 4391de3d Giorgos Korfiatis
        project, app = self.get_elements()
1407 4391de3d Giorgos Korfiatis
        return self.get_state(project, app)
1408 4391de3d Giorgos Korfiatis
1409 4391de3d Giorgos Korfiatis
1410 033f2822 Giorgos Korfiatis
def new_chain():
1411 033f2822 Giorgos Korfiatis
    c = Chain.objects.create()
1412 033f2822 Giorgos Korfiatis
    return c
1413 033f2822 Giorgos Korfiatis
1414 033f2822 Giorgos Korfiatis
1415 6dcf53eb Kostas Papadimitriou
class ProjectApplicationManager(ForUpdateManager):
1416 5550bcfb Kostas Papadimitriou
1417 05617ab9 Kostas Papadimitriou
    def user_visible_projects(self, *filters, **kw_filters):
1418 689226c3 Giorgos Korfiatis
        model = self.model
1419 689226c3 Giorgos Korfiatis
        return self.filter(model.Q_PENDING | model.Q_APPROVED)
1420 05617ab9 Kostas Papadimitriou
1421 7184f408 Giorgos Korfiatis
    def user_visible_by_chain(self, flt):
1422 689226c3 Giorgos Korfiatis
        model = self.model
1423 3e3743f2 Giorgos Korfiatis
        pending = self.filter(model.Q_PENDING | model.Q_DENIED).values_list('chain')
1424 689226c3 Giorgos Korfiatis
        approved = self.filter(model.Q_APPROVED).values_list('chain')
1425 a3530159 Georgios D. Tsoukalas
        by_chain = dict(pending.annotate(models.Max('id')))
1426 a3530159 Georgios D. Tsoukalas
        by_chain.update(approved.annotate(models.Max('id')))
1427 7184f408 Giorgos Korfiatis
        return self.filter(flt, id__in=by_chain.values())
1428 05617ab9 Kostas Papadimitriou
1429 05617ab9 Kostas Papadimitriou
    def user_accessible_projects(self, user):
1430 5550bcfb Kostas Papadimitriou
        """
1431 5550bcfb Kostas Papadimitriou
        Return projects accessed by specified user.
1432 5550bcfb Kostas Papadimitriou
        """
1433 8e1a5af5 Georgios D. Tsoukalas
        if user.is_project_admin():
1434 8e1a5af5 Georgios D. Tsoukalas
            participates_filters = Q()
1435 8e1a5af5 Georgios D. Tsoukalas
        else:
1436 8e1a5af5 Georgios D. Tsoukalas
            participates_filters = Q(owner=user) | Q(applicant=user) | \
1437 8e1a5af5 Georgios D. Tsoukalas
                                   Q(project__projectmembership__person=user)
1438 05617ab9 Kostas Papadimitriou
1439 a3530159 Georgios D. Tsoukalas
        return self.user_visible_by_chain(participates_filters).order_by('issue_date').distinct()
1440 5550bcfb Kostas Papadimitriou
1441 a5cef8d0 Kostas Papadimitriou
    def search_by_name(self, *search_strings):
1442 a5cef8d0 Kostas Papadimitriou
        q = Q()
1443 a5cef8d0 Kostas Papadimitriou
        for s in search_strings:
1444 a5cef8d0 Kostas Papadimitriou
            q = q | Q(name__icontains=s)
1445 a5cef8d0 Kostas Papadimitriou
        return self.filter(q)
1446 a5cef8d0 Kostas Papadimitriou
1447 ff67242a Giorgos Korfiatis
    def latest_of_chain(self, chain_id):
1448 ff67242a Giorgos Korfiatis
        try:
1449 ff67242a Giorgos Korfiatis
            return self.filter(chain=chain_id).order_by('-id')[0]
1450 ff67242a Giorgos Korfiatis
        except IndexError:
1451 ff67242a Giorgos Korfiatis
            return None
1452 a5cef8d0 Kostas Papadimitriou
1453 a9ba418f Giorgos Korfiatis
1454 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1455 425e2e95 Sofia Papagiannaki
    applicant               =   models.ForeignKey(
1456 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1457 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_applied',
1458 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1459 d6fdc91e Georgios D. Tsoukalas
1460 d0e78bbe Giorgos Korfiatis
    PENDING     =    0
1461 d0e78bbe Giorgos Korfiatis
    APPROVED    =    1
1462 d0e78bbe Giorgos Korfiatis
    REPLACED    =    2
1463 d0e78bbe Giorgos Korfiatis
    DENIED      =    3
1464 3c638f72 Giorgos Korfiatis
    DISMISSED   =    4
1465 3c638f72 Giorgos Korfiatis
    CANCELLED   =    5
1466 d0e78bbe Giorgos Korfiatis
1467 69ab4df9 Giorgos Korfiatis
    state                   =   models.IntegerField(default=PENDING,
1468 69ab4df9 Giorgos Korfiatis
                                                    db_index=True)
1469 d6fdc91e Georgios D. Tsoukalas
1470 425e2e95 Sofia Papagiannaki
    owner                   =   models.ForeignKey(
1471 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1472 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_owned',
1473 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1474 d6fdc91e Georgios D. Tsoukalas
1475 5195c0e9 Giorgos Korfiatis
    chain                   =   models.ForeignKey(Chain,
1476 5195c0e9 Giorgos Korfiatis
                                                  related_name='chained_apps',
1477 5195c0e9 Giorgos Korfiatis
                                                  db_column='chain')
1478 3c638f72 Giorgos Korfiatis
    precursor_application   =   models.ForeignKey('ProjectApplication',
1479 3c638f72 Giorgos Korfiatis
                                                  null=True,
1480 3c638f72 Giorgos Korfiatis
                                                  blank=True)
1481 425e2e95 Sofia Papagiannaki
1482 67980f56 Georgios D. Tsoukalas
    name                    =   models.CharField(max_length=80)
1483 94e49e22 Kostas Papadimitriou
    homepage                =   models.URLField(max_length=255, null=True,
1484 94e49e22 Kostas Papadimitriou
                                                verify_exists=False)
1485 67980f56 Georgios D. Tsoukalas
    description             =   models.TextField(null=True, blank=True)
1486 e729f165 Kostas Papadimitriou
    start_date              =   models.DateTimeField(null=True, blank=True)
1487 67980f56 Georgios D. Tsoukalas
    end_date                =   models.DateTimeField()
1488 272cf735 Sofia Papagiannaki
    member_join_policy      =   models.IntegerField()
1489 272cf735 Sofia Papagiannaki
    member_leave_policy     =   models.IntegerField()
1490 67980f56 Georgios D. Tsoukalas
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1491 425e2e95 Sofia Papagiannaki
    resource_grants         =   models.ManyToManyField(
1492 425e2e95 Sofia Papagiannaki
                                    Resource,
1493 425e2e95 Sofia Papagiannaki
                                    null=True,
1494 425e2e95 Sofia Papagiannaki
                                    blank=True,
1495 d6fdc91e Georgios D. Tsoukalas
                                    through='ProjectResourceGrant')
1496 425e2e95 Sofia Papagiannaki
    comments                =   models.TextField(null=True, blank=True)
1497 3c638f72 Giorgos Korfiatis
    issue_date              =   models.DateTimeField(auto_now_add=True)
1498 3c638f72 Giorgos Korfiatis
    response_date           =   models.DateTimeField(null=True, blank=True)
1499 2b745492 Giorgos Korfiatis
    response                =   models.TextField(null=True, blank=True)
1500 6dcf53eb Kostas Papadimitriou
1501 5550bcfb Kostas Papadimitriou
    objects                 =   ProjectApplicationManager()
1502 7729e9cc Giorgos Korfiatis
1503 689226c3 Giorgos Korfiatis
    # Compiled queries
1504 689226c3 Giorgos Korfiatis
    Q_PENDING  = Q(state=PENDING)
1505 689226c3 Giorgos Korfiatis
    Q_APPROVED = Q(state=APPROVED)
1506 3e3743f2 Giorgos Korfiatis
    Q_DENIED   = Q(state=DENIED)
1507 689226c3 Giorgos Korfiatis
1508 c4892cd2 Sofia Papagiannaki
    class Meta:
1509 c4892cd2 Sofia Papagiannaki
        unique_together = ("chain", "id")
1510 c4892cd2 Sofia Papagiannaki
1511 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1512 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1513 f3a45fc6 Kostas Papadimitriou
1514 d0e78bbe Giorgos Korfiatis
    # TODO: Move to a more suitable place
1515 9307cd46 Giorgos Korfiatis
    APPLICATION_STATE_DISPLAY = {
1516 3c638f72 Giorgos Korfiatis
        PENDING  : _('Pending review'),
1517 d77b32f2 Giorgos Korfiatis
        APPROVED : _('Approved'),
1518 3c638f72 Giorgos Korfiatis
        REPLACED : _('Replaced'),
1519 3c638f72 Giorgos Korfiatis
        DENIED   : _('Denied'),
1520 3c638f72 Giorgos Korfiatis
        DISMISSED: _('Dismissed'),
1521 3c638f72 Giorgos Korfiatis
        CANCELLED: _('Cancelled')
1522 bd9af366 Kostas Papadimitriou
    }
1523 d0e78bbe Giorgos Korfiatis
1524 f30f0170 Giorgos Korfiatis
    @property
1525 f30f0170 Giorgos Korfiatis
    def log_display(self):
1526 f30f0170 Giorgos Korfiatis
        return "application %s (%s) for project %s" % (
1527 f30f0170 Giorgos Korfiatis
            self.id, self.name, self.chain)
1528 f30f0170 Giorgos Korfiatis
1529 db9a498c Kostas Papadimitriou
    def state_display(self):
1530 9307cd46 Giorgos Korfiatis
        return self.APPLICATION_STATE_DISPLAY.get(self.state, _('Unknown'))
1531 db9a498c Kostas Papadimitriou
1532 d4660e00 Giorgos Korfiatis
    def project_state_display(self):
1533 d4660e00 Giorgos Korfiatis
        try:
1534 d4660e00 Giorgos Korfiatis
            project = self.project
1535 d4660e00 Giorgos Korfiatis
            return project.state_display()
1536 d4660e00 Giorgos Korfiatis
        except Project.DoesNotExist:
1537 d4660e00 Giorgos Korfiatis
            return self.state_display()
1538 d4660e00 Giorgos Korfiatis
1539 eee9ec4d Giorgos Korfiatis
    def add_resource_policy(self, resource, uplimit):
1540 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1541 a7aba804 Sofia Papagiannaki
        q = self.projectresourcegrant_set
1542 26551b92 Kostas Papadimitriou
        resource = Resource.objects.get(name=resource)
1543 a7aba804 Sofia Papagiannaki
        q.create(resource=resource, member_capacity=uplimit)
1544 e1a80257 Sofia Papagiannaki
1545 5550bcfb Kostas Papadimitriou
    def members_count(self):
1546 5550bcfb Kostas Papadimitriou
        return self.project.approved_memberships.count()
1547 5550bcfb Kostas Papadimitriou
1548 669cfe19 Olga Brani
    @property
1549 669cfe19 Olga Brani
    def grants(self):
1550 26551b92 Kostas Papadimitriou
        return self.projectresourcegrant_set.values('member_capacity',
1551 26551b92 Kostas Papadimitriou
                                                    'resource__name')
1552 5550bcfb Kostas Papadimitriou
1553 e1a80257 Sofia Papagiannaki
    @property
1554 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1555 b98e1df0 Sofia Papagiannaki
        return [str(rp) for rp in self.projectresourcegrant_set.all()]
1556 e1a80257 Sofia Papagiannaki
1557 eee9ec4d Giorgos Korfiatis
    def set_resource_policies(self, policies):
1558 eee9ec4d Giorgos Korfiatis
        for resource, uplimit in policies:
1559 eee9ec4d Giorgos Korfiatis
            self.add_resource_policy(resource, uplimit)
1560 425e2e95 Sofia Papagiannaki
1561 a75dbd7b Giorgos Korfiatis
    def pending_modifications_incl_me(self):
1562 3e3743f2 Giorgos Korfiatis
        q = self.chained_applications()
1563 a75dbd7b Giorgos Korfiatis
        q = q.filter(Q(state=self.PENDING))
1564 3e3743f2 Giorgos Korfiatis
        return q
1565 ece3b66e Giorgos Korfiatis
1566 a75dbd7b Giorgos Korfiatis
    def last_pending_incl_me(self):
1567 a75dbd7b Giorgos Korfiatis
        try:
1568 a75dbd7b Giorgos Korfiatis
            return self.pending_modifications_incl_me().order_by('-id')[0]
1569 a75dbd7b Giorgos Korfiatis
        except IndexError:
1570 a75dbd7b Giorgos Korfiatis
            return None
1571 a75dbd7b Giorgos Korfiatis
1572 a75dbd7b Giorgos Korfiatis
    def pending_modifications(self):
1573 a75dbd7b Giorgos Korfiatis
        return self.pending_modifications_incl_me().filter(~Q(id=self.id))
1574 a75dbd7b Giorgos Korfiatis
1575 3e3743f2 Giorgos Korfiatis
    def last_pending(self):
1576 9b32e2fb Kostas Papadimitriou
        try:
1577 3e3743f2 Giorgos Korfiatis
            return self.pending_modifications().order_by('-id')[0]
1578 9b32e2fb Kostas Papadimitriou
        except IndexError:
1579 05617ab9 Kostas Papadimitriou
            return None
1580 05617ab9 Kostas Papadimitriou
1581 efc58b65 Kostas Papadimitriou
    def is_modification(self):
1582 d4660e00 Giorgos Korfiatis
        # if self.state != self.PENDING:
1583 d4660e00 Giorgos Korfiatis
        #     return False
1584 efc58b65 Kostas Papadimitriou
        parents = self.chained_applications().filter(id__lt=self.id)
1585 efc58b65 Kostas Papadimitriou
        parents = parents.filter(state__in=[self.APPROVED])
1586 efc58b65 Kostas Papadimitriou
        return parents.count() > 0
1587 efc58b65 Kostas Papadimitriou
1588 efc58b65 Kostas Papadimitriou
    def chained_applications(self):
1589 efc58b65 Kostas Papadimitriou
        return ProjectApplication.objects.filter(chain=self.chain)
1590 efc58b65 Kostas Papadimitriou
1591 2529745f Giorgos Korfiatis
    def is_latest(self):
1592 2529745f Giorgos Korfiatis
        return self.chained_applications().order_by('-id')[0] == self
1593 2529745f Giorgos Korfiatis
1594 05617ab9 Kostas Papadimitriou
    def has_pending_modifications(self):
1595 3e3743f2 Giorgos Korfiatis
        return bool(self.last_pending())
1596 05617ab9 Kostas Papadimitriou
1597 022cc8e2 Giorgos Korfiatis
    def denied_modifications(self):
1598 022cc8e2 Giorgos Korfiatis
        q = self.chained_applications()
1599 022cc8e2 Giorgos Korfiatis
        q = q.filter(Q(state=self.DENIED))
1600 022cc8e2 Giorgos Korfiatis
        q = q.filter(~Q(id=self.id))
1601 022cc8e2 Giorgos Korfiatis
        return q
1602 022cc8e2 Giorgos Korfiatis
1603 022cc8e2 Giorgos Korfiatis
    def last_denied(self):
1604 022cc8e2 Giorgos Korfiatis
        try:
1605 022cc8e2 Giorgos Korfiatis
            return self.denied_modifications().order_by('-id')[0]
1606 022cc8e2 Giorgos Korfiatis
        except IndexError:
1607 022cc8e2 Giorgos Korfiatis
            return None
1608 022cc8e2 Giorgos Korfiatis
1609 022cc8e2 Giorgos Korfiatis
    def has_denied_modifications(self):
1610 022cc8e2 Giorgos Korfiatis
        return bool(self.last_denied())
1611 022cc8e2 Giorgos Korfiatis
1612 2529745f Giorgos Korfiatis
    def is_applied(self):
1613 2529745f Giorgos Korfiatis
        try:
1614 2529745f Giorgos Korfiatis
            self.project
1615 2529745f Giorgos Korfiatis
            return True
1616 2529745f Giorgos Korfiatis
        except Project.DoesNotExist:
1617 2529745f Giorgos Korfiatis
            return False
1618 2529745f Giorgos Korfiatis
1619 05617ab9 Kostas Papadimitriou
    def get_project(self):
1620 05617ab9 Kostas Papadimitriou
        try:
1621 05617ab9 Kostas Papadimitriou
            return Project.objects.get(id=self.chain)
1622 05617ab9 Kostas Papadimitriou
        except Project.DoesNotExist:
1623 9b32e2fb Kostas Papadimitriou
            return None
1624 4f22664f Georgios D. Tsoukalas
1625 d74111be Giorgos Korfiatis
    def project_exists(self):
1626 d74111be Giorgos Korfiatis
        return self.get_project() is not None
1627 d74111be Giorgos Korfiatis
1628 01bdbd17 Giorgos Korfiatis
    def can_cancel(self):
1629 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1630 01bdbd17 Giorgos Korfiatis
1631 3c638f72 Giorgos Korfiatis
    def cancel(self):
1632 01bdbd17 Giorgos Korfiatis
        if not self.can_cancel():
1633 3c638f72 Giorgos Korfiatis
            m = _("cannot cancel: application '%s' in state '%s'") % (
1634 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1635 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1636 3c638f72 Giorgos Korfiatis
1637 3c638f72 Giorgos Korfiatis
        self.state = self.CANCELLED
1638 3c638f72 Giorgos Korfiatis
        self.save()
1639 3c638f72 Giorgos Korfiatis
1640 01bdbd17 Giorgos Korfiatis
    def can_dismiss(self):
1641 01bdbd17 Giorgos Korfiatis
        return self.state == self.DENIED
1642 01bdbd17 Giorgos Korfiatis
1643 3c638f72 Giorgos Korfiatis
    def dismiss(self):
1644 01bdbd17 Giorgos Korfiatis
        if not self.can_dismiss():
1645 3c638f72 Giorgos Korfiatis
            m = _("cannot dismiss: application '%s' in state '%s'") % (
1646 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1647 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1648 3c638f72 Giorgos Korfiatis
1649 3c638f72 Giorgos Korfiatis
        self.state = self.DISMISSED
1650 3c638f72 Giorgos Korfiatis
        self.save()
1651 3c638f72 Giorgos Korfiatis
1652 01bdbd17 Giorgos Korfiatis
    def can_deny(self):
1653 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1654 01bdbd17 Giorgos Korfiatis
1655 2b745492 Giorgos Korfiatis
    def deny(self, reason):
1656 01bdbd17 Giorgos Korfiatis
        if not self.can_deny():
1657 19eb3ee6 Giorgos Korfiatis
            m = _("cannot deny: application '%s' in state '%s'") % (
1658 19eb3ee6 Giorgos Korfiatis
                    self.id, self.state)
1659 19eb3ee6 Giorgos Korfiatis
            raise AssertionError(m)
1660 19eb3ee6 Giorgos Korfiatis
1661 19eb3ee6 Giorgos Korfiatis
        self.state = self.DENIED
1662 3c638f72 Giorgos Korfiatis
        self.response_date = datetime.now()
1663 2b745492 Giorgos Korfiatis
        self.response = reason
1664 19eb3ee6 Giorgos Korfiatis
        self.save()
1665 19eb3ee6 Giorgos Korfiatis
1666 01bdbd17 Giorgos Korfiatis
    def can_approve(self):
1667 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1668 01bdbd17 Giorgos Korfiatis
1669 64d0c13e Giorgos Korfiatis
    def approve(self, reason):
1670 01bdbd17 Giorgos Korfiatis
        if not self.can_approve():
1671 65360c65 Georgios D. Tsoukalas
            m = _("cannot approve: project '%s' in state '%s'") % (
1672 3c22bad0 Giorgos Korfiatis
                    self.name, self.state)
1673 01bdbd17 Giorgos Korfiatis
            raise AssertionError(m) # invalid argument
1674 262e04c6 Giorgos Korfiatis
1675 fdafae27 Giorgos Korfiatis
        now = datetime.now()
1676 3c22bad0 Giorgos Korfiatis
        self.state = self.APPROVED
1677 3c22bad0 Giorgos Korfiatis
        self.response_date = now
1678 3c22bad0 Giorgos Korfiatis
        self.response = reason
1679 3c22bad0 Giorgos Korfiatis
        self.save()
1680 3cc9637a Giorgos Korfiatis
1681 3c22bad0 Giorgos Korfiatis
        project = self.get_project()
1682 4f22664f Georgios D. Tsoukalas
        if project is None:
1683 3c638f72 Giorgos Korfiatis
            project = Project(id=self.chain)
1684 fdafae27 Giorgos Korfiatis
1685 3c22bad0 Giorgos Korfiatis
        project.name = self.name
1686 ee45eb81 Giorgos Korfiatis
        project.application = self
1687 4bf02ea5 Giorgos Korfiatis
        project.last_approval_date = now
1688 a769d7ba Sofia Papagiannaki
        project.save()
1689 cfe52787 Giorgos Korfiatis
        if project.is_deactivated():
1690 cfe52787 Giorgos Korfiatis
            project.resume()
1691 570015d2 Giorgos Korfiatis
        return project
1692 262e04c6 Giorgos Korfiatis
1693 b98e1df0 Sofia Papagiannaki
    @property
1694 b98e1df0 Sofia Papagiannaki
    def member_join_policy_display(self):
1695 251b83be Giorgos Korfiatis
        policy = self.member_join_policy
1696 251b83be Giorgos Korfiatis
        return presentation.PROJECT_MEMBER_JOIN_POLICIES.get(policy)
1697 b98e1df0 Sofia Papagiannaki
1698 b98e1df0 Sofia Papagiannaki
    @property
1699 b98e1df0 Sofia Papagiannaki
    def member_leave_policy_display(self):
1700 251b83be Giorgos Korfiatis
        policy = self.member_leave_policy
1701 251b83be Giorgos Korfiatis
        return presentation.PROJECT_MEMBER_LEAVE_POLICIES.get(policy)
1702 b98e1df0 Sofia Papagiannaki
1703 73fbaec4 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1704 e1a80257 Sofia Papagiannaki
1705 425e2e95 Sofia Papagiannaki
    resource                =   models.ForeignKey(Resource)
1706 425e2e95 Sofia Papagiannaki
    project_application     =   models.ForeignKey(ProjectApplication,
1707 5200e864 Sofia Papagiannaki
                                                  null=True)
1708 0cdc5cc5 Giorgos Korfiatis
    project_capacity        =   intDecimalField(null=True)
1709 0cdc5cc5 Giorgos Korfiatis
    member_capacity         =   intDecimalField(default=0)
1710 73fbaec4 Sofia Papagiannaki
1711 73fbaec4 Sofia Papagiannaki
    objects = ExtendedManager()
1712 73fbaec4 Sofia Papagiannaki
1713 73fbaec4 Sofia Papagiannaki
    class Meta:
1714 73fbaec4 Sofia Papagiannaki
        unique_together = ("resource", "project_application")
1715 8327782d Sofia Papagiannaki
1716 b98e1df0 Sofia Papagiannaki
    def display_member_capacity(self):
1717 b98e1df0 Sofia Papagiannaki
        if self.member_capacity:
1718 b98e1df0 Sofia Papagiannaki
            if self.resource.unit:
1719 b98e1df0 Sofia Papagiannaki
                return ProjectResourceGrant.display_filesize(
1720 b98e1df0 Sofia Papagiannaki
                    self.member_capacity)
1721 b98e1df0 Sofia Papagiannaki
            else:
1722 b98e1df0 Sofia Papagiannaki
                if math.isinf(self.member_capacity):
1723 b98e1df0 Sofia Papagiannaki
                    return 'Unlimited'
1724 b98e1df0 Sofia Papagiannaki
                else:
1725 b98e1df0 Sofia Papagiannaki
                    return self.member_capacity
1726 b98e1df0 Sofia Papagiannaki
        else:
1727 b98e1df0 Sofia Papagiannaki
            return 'Unlimited'
1728 b98e1df0 Sofia Papagiannaki
1729 b98e1df0 Sofia Papagiannaki
    def __str__(self):
1730 b98e1df0 Sofia Papagiannaki
        return 'Max %s per user: %s' % (self.resource.pluralized_display_name,
1731 b98e1df0 Sofia Papagiannaki
                                        self.display_member_capacity())
1732 b98e1df0 Sofia Papagiannaki
1733 b98e1df0 Sofia Papagiannaki
    @classmethod
1734 b98e1df0 Sofia Papagiannaki
    def display_filesize(cls, value):
1735 b98e1df0 Sofia Papagiannaki
        try:
1736 b98e1df0 Sofia Papagiannaki
            value = float(value)
1737 b98e1df0 Sofia Papagiannaki
        except:
1738 b98e1df0 Sofia Papagiannaki
            return
1739 b98e1df0 Sofia Papagiannaki
        else:
1740 b98e1df0 Sofia Papagiannaki
            if math.isinf(value):
1741 b98e1df0 Sofia Papagiannaki
                return 'Unlimited'
1742 b98e1df0 Sofia Papagiannaki
            if value > 1:
1743 b98e1df0 Sofia Papagiannaki
                unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'],
1744 b98e1df0 Sofia Papagiannaki
                                [0, 0, 0, 0, 0, 0])
1745 b98e1df0 Sofia Papagiannaki
                exponent = min(int(math.log(value, 1024)), len(unit_list) - 1)
1746 b98e1df0 Sofia Papagiannaki
                quotient = float(value) / 1024**exponent
1747 b98e1df0 Sofia Papagiannaki
                unit, value_decimals = unit_list[exponent]
1748 b98e1df0 Sofia Papagiannaki
                format_string = '{0:.%sf} {1}' % (value_decimals)
1749 b98e1df0 Sofia Papagiannaki
                return format_string.format(quotient, unit)
1750 b98e1df0 Sofia Papagiannaki
            if value == 0:
1751 b98e1df0 Sofia Papagiannaki
                return '0 bytes'
1752 b98e1df0 Sofia Papagiannaki
            if value == 1:
1753 b98e1df0 Sofia Papagiannaki
                return '1 byte'
1754 b98e1df0 Sofia Papagiannaki
            else:
1755 b98e1df0 Sofia Papagiannaki
               return '0'
1756 b98e1df0 Sofia Papagiannaki
1757 e546df49 Georgios D. Tsoukalas
1758 123be68a Giorgos Korfiatis
class ProjectManager(ForUpdateManager):
1759 123be68a Giorgos Korfiatis
1760 123be68a Giorgos Korfiatis
    def terminated_projects(self):
1761 689226c3 Giorgos Korfiatis
        q = self.model.Q_TERMINATED
1762 123be68a Giorgos Korfiatis
        return self.filter(q)
1763 123be68a Giorgos Korfiatis
1764 123be68a Giorgos Korfiatis
    def not_terminated_projects(self):
1765 689226c3 Giorgos Korfiatis
        q = ~self.model.Q_TERMINATED
1766 123be68a Giorgos Korfiatis
        return self.filter(q)
1767 123be68a Giorgos Korfiatis
1768 db99f198 Giorgos Korfiatis
    def deactivated_projects(self):
1769 689226c3 Giorgos Korfiatis
        q = self.model.Q_DEACTIVATED
1770 db99f198 Giorgos Korfiatis
        return self.filter(q)
1771 db99f198 Giorgos Korfiatis
1772 7eadc230 Giorgos Korfiatis
    def expired_projects(self):
1773 7eadc230 Giorgos Korfiatis
        q = (~Q(state=Project.TERMINATED) &
1774 7eadc230 Giorgos Korfiatis
              Q(application__end_date__lt=datetime.now()))
1775 7eadc230 Giorgos Korfiatis
        return self.filter(q)
1776 7eadc230 Giorgos Korfiatis
1777 d77b32f2 Giorgos Korfiatis
    def search_by_name(self, *search_strings):
1778 d77b32f2 Giorgos Korfiatis
        q = Q()
1779 d77b32f2 Giorgos Korfiatis
        for s in search_strings:
1780 d77b32f2 Giorgos Korfiatis
            q = q | Q(name__icontains=s)
1781 d77b32f2 Giorgos Korfiatis
        return self.filter(q)
1782 d77b32f2 Giorgos Korfiatis
1783 7eadc230 Giorgos Korfiatis
1784 d6fdc91e Georgios D. Tsoukalas
class Project(models.Model):
1785 e546df49 Georgios D. Tsoukalas
1786 5195c0e9 Giorgos Korfiatis
    id                          =   models.OneToOneField(Chain,
1787 5195c0e9 Giorgos Korfiatis
                                                      related_name='chained_project',
1788 5195c0e9 Giorgos Korfiatis
                                                      db_column='id',
1789 5195c0e9 Giorgos Korfiatis
                                                      primary_key=True)
1790 5195c0e9 Giorgos Korfiatis
1791 ee45eb81 Giorgos Korfiatis
    application                 =   models.OneToOneField(
1792 4f22664f Georgios D. Tsoukalas
                                            ProjectApplication,
1793 782d9118 Giorgos Korfiatis
                                            related_name='project')
1794 4f22664f Georgios D. Tsoukalas
    last_approval_date          =   models.DateTimeField(null=True)
1795 4f22664f Georgios D. Tsoukalas
1796 4f22664f Georgios D. Tsoukalas
    members                     =   models.ManyToManyField(
1797 4f22664f Georgios D. Tsoukalas
                                            AstakosUser,
1798 4f22664f Georgios D. Tsoukalas
                                            through='ProjectMembership')
1799 4f22664f Georgios D. Tsoukalas
1800 5b9e9530 Giorgos Korfiatis
    deactivation_reason         =   models.CharField(max_length=255, null=True)
1801 5b9e9530 Giorgos Korfiatis
    deactivation_date           =   models.DateTimeField(null=True)
1802 4f22664f Georgios D. Tsoukalas
1803 3c638f72 Giorgos Korfiatis
    creation_date               =   models.DateTimeField(auto_now_add=True)
1804 4f22664f Georgios D. Tsoukalas
    name                        =   models.CharField(
1805 4f22664f Georgios D. Tsoukalas
                                            max_length=80,
1806 e1017df9 Giorgos Korfiatis
                                            null=True,
1807 4f22664f Georgios D. Tsoukalas
                                            db_index=True,
1808 4f22664f Georgios D. Tsoukalas
                                            unique=True)
1809 425e2e95 Sofia Papagiannaki
1810 b6fe8bb8 Giorgos Korfiatis
    APPROVED    = 1
1811 b6fe8bb8 Giorgos Korfiatis
    SUSPENDED   = 10
1812 b6fe8bb8 Giorgos Korfiatis
    TERMINATED  = 100
1813 5b9e9530 Giorgos Korfiatis
1814 b6fe8bb8 Giorgos Korfiatis
    state                       =   models.IntegerField(default=APPROVED,
1815 123be68a Giorgos Korfiatis
                                                        db_index=True)
1816 123be68a Giorgos Korfiatis
1817 123be68a Giorgos Korfiatis
    objects     =   ProjectManager()
1818 7729e9cc Giorgos Korfiatis
1819 689226c3 Giorgos Korfiatis
    # Compiled queries
1820 689226c3 Giorgos Korfiatis
    Q_TERMINATED  = Q(state=TERMINATED)
1821 689226c3 Giorgos Korfiatis
    Q_SUSPENDED   = Q(state=SUSPENDED)
1822 689226c3 Giorgos Korfiatis
    Q_DEACTIVATED = Q_TERMINATED | Q_SUSPENDED
1823 689226c3 Giorgos Korfiatis
1824 8c7b8bb8 Giorgos Korfiatis
    def __str__(self):
1825 b6eaca30 Giorgos Korfiatis
        return uenc(_("<project %s '%s'>") %
1826 b6eaca30 Giorgos Korfiatis
                    (self.id, udec(self.application.name)))
1827 8c7b8bb8 Giorgos Korfiatis
1828 8c7b8bb8 Giorgos Korfiatis
    __repr__ = __str__
1829 8c7b8bb8 Giorgos Korfiatis
1830 b6eaca30 Giorgos Korfiatis
    def __unicode__(self):
1831 b6eaca30 Giorgos Korfiatis
        return _("<project %s '%s'>") % (self.id, self.application.name)
1832 b6eaca30 Giorgos Korfiatis
1833 e1f31e63 Giorgos Korfiatis
    STATE_DISPLAY = {
1834 d77b32f2 Giorgos Korfiatis
        APPROVED   : 'Active',
1835 d77b32f2 Giorgos Korfiatis
        SUSPENDED  : 'Suspended',
1836 d77b32f2 Giorgos Korfiatis
        TERMINATED : 'Terminated'
1837 e1f31e63 Giorgos Korfiatis
        }
1838 e1f31e63 Giorgos Korfiatis
1839 e1f31e63 Giorgos Korfiatis
    def state_display(self):
1840 e1f31e63 Giorgos Korfiatis
        return self.STATE_DISPLAY.get(self.state, _('Unknown'))
1841 e1f31e63 Giorgos Korfiatis
1842 7eadc230 Giorgos Korfiatis
    def expiration_info(self):
1843 7eadc230 Giorgos Korfiatis
        return (str(self.id), self.name, self.state_display(),
1844 7eadc230 Giorgos Korfiatis
                str(self.application.end_date))
1845 7eadc230 Giorgos Korfiatis
1846 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated(self, reason=None):
1847 b6fe8bb8 Giorgos Korfiatis
        if reason is not None:
1848 b6fe8bb8 Giorgos Korfiatis
            return self.state == reason
1849 425e2e95 Sofia Papagiannaki
1850 b6fe8bb8 Giorgos Korfiatis
        return self.state != self.APPROVED
1851 123be68a Giorgos Korfiatis
1852 123be68a Giorgos Korfiatis
    ### Deactivation calls
1853 425e2e95 Sofia Papagiannaki
1854 123be68a Giorgos Korfiatis
    def terminate(self):
1855 123be68a Giorgos Korfiatis
        self.deactivation_reason = 'TERMINATED'
1856 3d4cef9e Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1857 b6fe8bb8 Giorgos Korfiatis
        self.state = self.TERMINATED
1858 e1017df9 Giorgos Korfiatis
        self.name = None
1859 123be68a Giorgos Korfiatis
        self.save()
1860 8aed306c Giorgos Korfiatis
1861 db99f198 Giorgos Korfiatis
    def suspend(self):
1862 db99f198 Giorgos Korfiatis
        self.deactivation_reason = 'SUSPENDED'
1863 3d4cef9e Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1864 db99f198 Giorgos Korfiatis
        self.state = self.SUSPENDED
1865 db99f198 Giorgos Korfiatis
        self.save()
1866 db99f198 Giorgos Korfiatis
1867 db99f198 Giorgos Korfiatis
    def resume(self):
1868 db99f198 Giorgos Korfiatis
        self.deactivation_reason = None
1869 3d4cef9e Giorgos Korfiatis
        self.deactivation_date = None
1870 db99f198 Giorgos Korfiatis
        self.state = self.APPROVED
1871 db99f198 Giorgos Korfiatis
        self.save()
1872 123be68a Giorgos Korfiatis
1873 123be68a Giorgos Korfiatis
    ### Logical checks
1874 425e2e95 Sofia Papagiannaki
1875 e1a80257 Sofia Papagiannaki
    def is_inconsistent(self):
1876 e1a80257 Sofia Papagiannaki
        now = datetime.now()
1877 5b9e9530 Giorgos Korfiatis
        dates = [self.creation_date,
1878 5b9e9530 Giorgos Korfiatis
                 self.last_approval_date,
1879 5b9e9530 Giorgos Korfiatis
                 self.deactivation_date]
1880 5b9e9530 Giorgos Korfiatis
        return any([date > now for date in dates])
1881 5b9e9530 Giorgos Korfiatis
1882 db99f198 Giorgos Korfiatis
    def is_approved(self):
1883 db99f198 Giorgos Korfiatis
        return self.state == self.APPROVED
1884 db99f198 Giorgos Korfiatis
1885 123be68a Giorgos Korfiatis
    @property
1886 123be68a Giorgos Korfiatis
    def is_alive(self):
1887 72a6e1e8 Giorgos Korfiatis
        return not self.is_terminated
1888 123be68a Giorgos Korfiatis
1889 123be68a Giorgos Korfiatis
    @property
1890 123be68a Giorgos Korfiatis
    def is_terminated(self):
1891 123be68a Giorgos Korfiatis
        return self.is_deactivated(self.TERMINATED)
1892 123be68a Giorgos Korfiatis
1893 123be68a Giorgos Korfiatis
    @property
1894 123be68a Giorgos Korfiatis
    def is_suspended(self):
1895 db99f198 Giorgos Korfiatis
        return self.is_deactivated(self.SUSPENDED)
1896 5b9e9530 Giorgos Korfiatis
1897 5b9e9530 Giorgos Korfiatis
    def violates_resource_grants(self):
1898 e1a80257 Sofia Papagiannaki
        return False
1899 65360c65 Georgios D. Tsoukalas
1900 5b9e9530 Giorgos Korfiatis
    def violates_members_limit(self, adding=0):
1901 5b9e9530 Giorgos Korfiatis
        application = self.application
1902 943d5554 Giorgos Korfiatis
        limit = application.limit_on_members_number
1903 022c61cd Sofia Papagiannaki
        if limit is None:
1904 022c61cd Sofia Papagiannaki
            return False
1905 022c61cd Sofia Papagiannaki
        return (len(self.approved_members) + adding > limit)
1906 5b9e9530 Giorgos Korfiatis
1907 123be68a Giorgos Korfiatis
1908 123be68a Giorgos Korfiatis
    ### Other
1909 5b9e9530 Giorgos Korfiatis
1910 7db8c163 Georgios D. Tsoukalas
    def count_pending_memberships(self):
1911 d895de37 Giorgos Korfiatis
        return self.projectmembership_set.requested().count()
1912 61edd5cd Olga Brani
1913 d77b32f2 Giorgos Korfiatis
    def members_count(self):
1914 d77b32f2 Giorgos Korfiatis
        return self.approved_memberships.count()
1915 d77b32f2 Giorgos Korfiatis
1916 425e2e95 Sofia Papagiannaki
    @property
1917 425e2e95 Sofia Papagiannaki
    def approved_memberships(self):
1918 689226c3 Giorgos Korfiatis
        query = ProjectMembership.Q_ACCEPTED_STATES
1919 5b9e9530 Giorgos Korfiatis
        return self.projectmembership_set.filter(query)
1920 4f22664f Georgios D. Tsoukalas
1921 425e2e95 Sofia Papagiannaki
    @property
1922 425e2e95 Sofia Papagiannaki
    def approved_members(self):
1923 425e2e95 Sofia Papagiannaki
        return [m.person for m in self.approved_memberships]
1924 4f22664f Georgios D. Tsoukalas
1925 425e2e95 Sofia Papagiannaki
1926 2529745f Giorgos Korfiatis
CHAIN_STATE = {
1927 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.PENDING)  : Chain.APPROVED_PENDING,
1928 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.APPROVED) : Chain.APPROVED,
1929 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.DENIED)   : Chain.APPROVED,
1930 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.DISMISSED): Chain.APPROVED,
1931 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.CANCELLED): Chain.APPROVED,
1932 2529745f Giorgos Korfiatis
1933 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.PENDING)  : Chain.SUSPENDED_PENDING,
1934 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.APPROVED) : Chain.SUSPENDED,
1935 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.DENIED)   : Chain.SUSPENDED,
1936 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.DISMISSED): Chain.SUSPENDED,
1937 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.CANCELLED): Chain.SUSPENDED,
1938 2529745f Giorgos Korfiatis
1939 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.PENDING)  : Chain.TERMINATED_PENDING,
1940 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.APPROVED) : Chain.TERMINATED,
1941 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.DENIED)   : Chain.TERMINATED,
1942 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.DISMISSED): Chain.TERMINATED,
1943 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.CANCELLED): Chain.TERMINATED,
1944 2529745f Giorgos Korfiatis
1945 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.PENDING)  : Chain.PENDING,
1946 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.DENIED)   : Chain.DENIED,
1947 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.DISMISSED): Chain.DISMISSED,
1948 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.CANCELLED): Chain.CANCELLED,
1949 2529745f Giorgos Korfiatis
    }
1950 2529745f Giorgos Korfiatis
1951 2529745f Giorgos Korfiatis
1952 db99f198 Giorgos Korfiatis
class ProjectMembershipManager(ForUpdateManager):
1953 d77b32f2 Giorgos Korfiatis
1954 d77b32f2 Giorgos Korfiatis
    def any_accepted(self):
1955 d895de37 Giorgos Korfiatis
        q = self.model.Q_ACCEPTED_STATES
1956 d77b32f2 Giorgos Korfiatis
        return self.filter(q)
1957 d77b32f2 Giorgos Korfiatis
1958 c1007621 Giorgos Korfiatis
    def actually_accepted(self):
1959 c1007621 Giorgos Korfiatis
        q = self.model.Q_ACTUALLY_ACCEPTED
1960 c1007621 Giorgos Korfiatis
        return self.filter(q)
1961 c1007621 Giorgos Korfiatis
1962 d77b32f2 Giorgos Korfiatis
    def requested(self):
1963 d77b32f2 Giorgos Korfiatis
        return self.filter(state=ProjectMembership.REQUESTED)
1964 d77b32f2 Giorgos Korfiatis
1965 d77b32f2 Giorgos Korfiatis
    def suspended(self):
1966 d77b32f2 Giorgos Korfiatis
        return self.filter(state=ProjectMembership.USER_SUSPENDED)
1967 db99f198 Giorgos Korfiatis
1968 d6fdc91e Georgios D. Tsoukalas
class ProjectMembership(models.Model):
1969 4f22664f Georgios D. Tsoukalas
1970 425e2e95 Sofia Papagiannaki
    person              =   models.ForeignKey(AstakosUser)
1971 95f33116 Giorgos Korfiatis
    request_date        =   models.DateTimeField(auto_now_add=True)
1972 d6fdc91e Georgios D. Tsoukalas
    project             =   models.ForeignKey(Project)
1973 d6fdc91e Georgios D. Tsoukalas
1974 db99f198 Giorgos Korfiatis
    REQUESTED           =   0
1975 db99f198 Giorgos Korfiatis
    ACCEPTED            =   1
1976 c1007621 Giorgos Korfiatis
    LEAVE_REQUESTED     =   5
1977 db99f198 Giorgos Korfiatis
    # User deactivation
1978 db99f198 Giorgos Korfiatis
    USER_SUSPENDED      =   10
1979 b6fe8bb8 Giorgos Korfiatis
1980 db99f198 Giorgos Korfiatis
    REMOVED             =   200
1981 db99f198 Giorgos Korfiatis
1982 db99f198 Giorgos Korfiatis
    ASSOCIATED_STATES   =   set([REQUESTED,
1983 db99f198 Giorgos Korfiatis
                                 ACCEPTED,
1984 c1007621 Giorgos Korfiatis
                                 LEAVE_REQUESTED,
1985 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1986 96efed67 Giorgos Korfiatis
                                 ])
1987 db99f198 Giorgos Korfiatis
1988 db99f198 Giorgos Korfiatis
    ACCEPTED_STATES     =   set([ACCEPTED,
1989 c1007621 Giorgos Korfiatis
                                 LEAVE_REQUESTED,
1990 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1991 96efed67 Giorgos Korfiatis
                                 ])
1992 05617ab9 Kostas Papadimitriou
1993 c1007621 Giorgos Korfiatis
    ACTUALLY_ACCEPTED   =   set([ACCEPTED, LEAVE_REQUESTED])
1994 c1007621 Giorgos Korfiatis
1995 b6fe8bb8 Giorgos Korfiatis
    state               =   models.IntegerField(default=REQUESTED,
1996 b6fe8bb8 Giorgos Korfiatis
                                                db_index=True)
1997 95f33116 Giorgos Korfiatis
    acceptance_date     =   models.DateTimeField(null=True, db_index=True)
1998 95f33116 Giorgos Korfiatis
    leave_request_date  =   models.DateTimeField(null=True)
1999 2a965273 Sofia Papagiannaki
2000 db99f198 Giorgos Korfiatis
    objects     =   ProjectMembershipManager()
2001 ee45eb81 Giorgos Korfiatis
2002 689226c3 Giorgos Korfiatis
    # Compiled queries
2003 d895de37 Giorgos Korfiatis
    Q_ACCEPTED_STATES = Q(state__in=ACCEPTED_STATES)
2004 c1007621 Giorgos Korfiatis
    Q_ACTUALLY_ACCEPTED = Q(state=ACCEPTED) | Q(state=LEAVE_REQUESTED)
2005 5b9e9530 Giorgos Korfiatis
2006 d77b32f2 Giorgos Korfiatis
    MEMBERSHIP_STATE_DISPLAY = {
2007 d4660e00 Giorgos Korfiatis
        REQUESTED           : _('Requested'),
2008 d4660e00 Giorgos Korfiatis
        ACCEPTED            : _('Accepted'),
2009 c1007621 Giorgos Korfiatis
        LEAVE_REQUESTED     : _('Leave Requested'),
2010 d4660e00 Giorgos Korfiatis
        USER_SUSPENDED      : _('Suspended'),
2011 d4660e00 Giorgos Korfiatis
        REMOVED             : _('Pending removal'),
2012 d4660e00 Giorgos Korfiatis
        }
2013 d4660e00 Giorgos Korfiatis
2014 d4660e00 Giorgos Korfiatis
    USER_FRIENDLY_STATE_DISPLAY = {
2015 d4660e00 Giorgos Korfiatis
        REQUESTED           : _('Join requested'),
2016 d4660e00 Giorgos Korfiatis
        ACCEPTED            : _('Accepted member'),
2017 c1007621 Giorgos Korfiatis
        LEAVE_REQUESTED     : _('Requested to leave'),
2018 d4660e00 Giorgos Korfiatis
        USER_SUSPENDED      : _('Suspended member'),
2019 d4660e00 Giorgos Korfiatis
        REMOVED             : _('Pending removal'),
2020 d77b32f2 Giorgos Korfiatis
        }
2021 d77b32f2 Giorgos Korfiatis
2022 d77b32f2 Giorgos Korfiatis
    def state_display(self):
2023 d77b32f2 Giorgos Korfiatis
        return self.MEMBERSHIP_STATE_DISPLAY.get(self.state, _('Unknown'))
2024 d77b32f2 Giorgos Korfiatis
2025 d4660e00 Giorgos Korfiatis
    def user_friendly_state_display(self):
2026 d4660e00 Giorgos Korfiatis
        return self.USER_FRIENDLY_STATE_DISPLAY.get(self.state, _('Unknown'))
2027 d4660e00 Giorgos Korfiatis
2028 0cc22d47 Sofia Papagiannaki
    class Meta:
2029 0cc22d47 Sofia Papagiannaki
        unique_together = ("person", "project")
2030 d6fdc91e Georgios D. Tsoukalas
        #index_together = [["project", "state"]]
2031 bfe23b13 Sofia Papagiannaki
2032 65360c65 Georgios D. Tsoukalas
    def __str__(self):
2033 b6eaca30 Giorgos Korfiatis
        return uenc(_("<'%s' membership in '%s'>") % (
2034 b6eaca30 Giorgos Korfiatis
                self.person.username, self.project))
2035 65360c65 Georgios D. Tsoukalas
2036 65360c65 Georgios D. Tsoukalas
    __repr__ = __str__
2037 65360c65 Georgios D. Tsoukalas
2038 65360c65 Georgios D. Tsoukalas
    def __init__(self, *args, **kwargs):
2039 ee45eb81 Giorgos Korfiatis
        self.state = self.REQUESTED
2040 65360c65 Georgios D. Tsoukalas
        super(ProjectMembership, self).__init__(*args, **kwargs)
2041 65360c65 Georgios D. Tsoukalas
2042 4f22664f Georgios D. Tsoukalas
    def _set_history_item(self, reason, date=None):
2043 4f22664f Georgios D. Tsoukalas
        if isinstance(reason, basestring):
2044 4f22664f Georgios D. Tsoukalas
            reason = ProjectMembershipHistory.reasons.get(reason, -1)
2045 4f22664f Georgios D. Tsoukalas
2046 4f22664f Georgios D. Tsoukalas
        history_item = ProjectMembershipHistory(
2047 4f22664f Georgios D. Tsoukalas
                            serial=self.id,
2048 d0e78bbe Giorgos Korfiatis
                            person=self.person_id,
2049 02d2519e Giorgos Korfiatis
                            project=self.project_id,
2050 8f975b72 Sofia Papagiannaki
                            date=date or datetime.now(),
2051 4f22664f Georgios D. Tsoukalas
                            reason=reason)
2052 4f22664f Georgios D. Tsoukalas
        history_item.save()
2053 4f22664f Georgios D. Tsoukalas
        serial = history_item.id
2054 4f22664f Georgios D. Tsoukalas
2055 14f7f6a5 Giorgos Korfiatis
    def can_accept(self):
2056 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
2057 14f7f6a5 Giorgos Korfiatis
2058 4f22664f Georgios D. Tsoukalas
    def accept(self):
2059 14f7f6a5 Giorgos Korfiatis
        if not self.can_accept():
2060 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to accept in state '%s'") % (self, self.state)
2061 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2062 4f22664f Georgios D. Tsoukalas
2063 65360c65 Georgios D. Tsoukalas
        now = datetime.now()
2064 65360c65 Georgios D. Tsoukalas
        self.acceptance_date = now
2065 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='ACCEPT', date=now)
2066 96efed67 Giorgos Korfiatis
        self.state = self.ACCEPTED
2067 65360c65 Georgios D. Tsoukalas
        self.save()
2068 4f22664f Georgios D. Tsoukalas
2069 14f7f6a5 Giorgos Korfiatis
    def can_leave(self):
2070 c1007621 Giorgos Korfiatis
        return self.state in self.ACCEPTED_STATES
2071 c1007621 Giorgos Korfiatis
2072 c1007621 Giorgos Korfiatis
    def leave_request(self):
2073 c1007621 Giorgos Korfiatis
        if not self.can_leave():
2074 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to request to leave in state '%s'") % (
2075 c1007621 Giorgos Korfiatis
                self, self.state)
2076 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2077 c1007621 Giorgos Korfiatis
2078 c1007621 Giorgos Korfiatis
        self.leave_request_date = datetime.now()
2079 c1007621 Giorgos Korfiatis
        self.state = self.LEAVE_REQUESTED
2080 c1007621 Giorgos Korfiatis
        self.save()
2081 c1007621 Giorgos Korfiatis
2082 c1007621 Giorgos Korfiatis
    def can_deny_leave(self):
2083 c1007621 Giorgos Korfiatis
        return self.state == self.LEAVE_REQUESTED
2084 c1007621 Giorgos Korfiatis
2085 c1007621 Giorgos Korfiatis
    def leave_request_deny(self):
2086 c1007621 Giorgos Korfiatis
        if not self.can_deny_leave():
2087 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to deny leave request in state '%s'") % (
2088 c1007621 Giorgos Korfiatis
                self, self.state)
2089 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2090 c1007621 Giorgos Korfiatis
2091 c1007621 Giorgos Korfiatis
        self.leave_request_date = None
2092 c1007621 Giorgos Korfiatis
        self.state = self.ACCEPTED
2093 c1007621 Giorgos Korfiatis
        self.save()
2094 c1007621 Giorgos Korfiatis
2095 c1007621 Giorgos Korfiatis
    def can_cancel_leave(self):
2096 c1007621 Giorgos Korfiatis
        return self.state == self.LEAVE_REQUESTED
2097 c1007621 Giorgos Korfiatis
2098 c1007621 Giorgos Korfiatis
    def leave_request_cancel(self):
2099 c1007621 Giorgos Korfiatis
        if not self.can_cancel_leave():
2100 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to cancel leave request in state '%s'") % (
2101 c1007621 Giorgos Korfiatis
                self, self.state)
2102 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2103 c1007621 Giorgos Korfiatis
2104 c1007621 Giorgos Korfiatis
        self.leave_request_date = None
2105 c1007621 Giorgos Korfiatis
        self.state = self.ACCEPTED
2106 c1007621 Giorgos Korfiatis
        self.save()
2107 14f7f6a5 Giorgos Korfiatis
2108 14f7f6a5 Giorgos Korfiatis
    def can_remove(self):
2109 14f7f6a5 Giorgos Korfiatis
        return self.state in self.ACCEPTED_STATES
2110 14f7f6a5 Giorgos Korfiatis
2111 65360c65 Georgios D. Tsoukalas
    def remove(self):
2112 14f7f6a5 Giorgos Korfiatis
        if not self.can_remove():
2113 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to remove in state '%s'") % (self, self.state)
2114 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2115 4f22664f Georgios D. Tsoukalas
2116 ee45eb81 Giorgos Korfiatis
        self._set_history_item(reason='REMOVE')
2117 8cbea11d Giorgos Korfiatis
        self.delete()
2118 b8f05f8d Sofia Papagiannaki
2119 14f7f6a5 Giorgos Korfiatis
    def can_reject(self):
2120 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
2121 14f7f6a5 Giorgos Korfiatis
2122 65360c65 Georgios D. Tsoukalas
    def reject(self):
2123 14f7f6a5 Giorgos Korfiatis
        if not self.can_reject():
2124 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to reject in state '%s'") % (self, self.state)
2125 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2126 65360c65 Georgios D. Tsoukalas
2127 65360c65 Georgios D. Tsoukalas
        # rejected requests don't need sync,
2128 65360c65 Georgios D. Tsoukalas
        # because they were never effected
2129 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='REJECT')
2130 0cc22d47 Sofia Papagiannaki
        self.delete()
2131 b8f05f8d Sofia Papagiannaki
2132 aad0e329 Giorgos Korfiatis
    def can_cancel(self):
2133 aad0e329 Giorgos Korfiatis
        return self.state == self.REQUESTED
2134 aad0e329 Giorgos Korfiatis
2135 aad0e329 Giorgos Korfiatis
    def cancel(self):
2136 aad0e329 Giorgos Korfiatis
        if not self.can_cancel():
2137 aad0e329 Giorgos Korfiatis
            m = _("%s: attempt to cancel in state '%s'") % (self, self.state)
2138 aad0e329 Giorgos Korfiatis
            raise AssertionError(m)
2139 aad0e329 Giorgos Korfiatis
2140 aad0e329 Giorgos Korfiatis
        # rejected requests don't need sync,
2141 aad0e329 Giorgos Korfiatis
        # because they were never effected
2142 aad0e329 Giorgos Korfiatis
        self._set_history_item(reason='CANCEL')
2143 aad0e329 Giorgos Korfiatis
        self.delete()
2144 aad0e329 Giorgos Korfiatis
2145 49b74233 Georgios D. Tsoukalas
2146 ee45eb81 Giorgos Korfiatis
class Serial(models.Model):
2147 ee45eb81 Giorgos Korfiatis
    serial  =   models.AutoField(primary_key=True)
2148 ee45eb81 Giorgos Korfiatis
2149 e1a80257 Sofia Papagiannaki
2150 0cc22d47 Sofia Papagiannaki
class ProjectMembershipHistory(models.Model):
2151 425e2e95 Sofia Papagiannaki
    reasons_list    =   ['ACCEPT', 'REJECT', 'REMOVE']
2152 425e2e95 Sofia Papagiannaki
    reasons         =   dict((k, v) for v, k in enumerate(reasons_list))
2153 425e2e95 Sofia Papagiannaki
2154 d0e78bbe Giorgos Korfiatis
    person  =   models.BigIntegerField()
2155 02d2519e Giorgos Korfiatis
    project =   models.BigIntegerField()
2156 95f33116 Giorgos Korfiatis
    date    =   models.DateTimeField(auto_now_add=True)
2157 425e2e95 Sofia Papagiannaki
    reason  =   models.IntegerField()
2158 425e2e95 Sofia Papagiannaki
    serial  =   models.BigIntegerField()
2159 fc655b6f Kostas Papadimitriou
2160 fcc1e93f Sofia Papagiannaki
### SIGNALS ###
2161 fcc1e93f Sofia Papagiannaki
################
2162 b22de10a Sofia Papagiannaki
2163 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
2164 ff9290ec Sofia Papagiannaki
    try:
2165 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
2166 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
2167 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
2168 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
2169 ff9290ec Sofia Papagiannaki
        extended_user.save()
2170 67be1883 Olga Brani
        if not extended_user.has_auth_provider('local'):
2171 67be1883 Olga Brani
            extended_user.add_auth_provider('local')
2172 fc1e2f02 Sofia Papagiannaki
    except BaseException, e:
2173 fc1e2f02 Sofia Papagiannaki
        logger.exception(e)
2174 ff9290ec Sofia Papagiannaki
2175 a7752e95 Sofia Papagiannaki
def fix_superusers():
2176 fc1e2f02 Sofia Papagiannaki
    # Associate superusers with AstakosUser
2177 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
2178 ff9290ec Sofia Papagiannaki
    for u in admins:
2179 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
2180 ff9290ec Sofia Papagiannaki
2181 fc1e2f02 Sofia Papagiannaki
def user_post_save(sender, instance, created, **kwargs):
2182 aa4109d4 Sofia Papagiannaki
    if not created:
2183 aa4109d4 Sofia Papagiannaki
        return
2184 fc1e2f02 Sofia Papagiannaki
    create_astakos_user(instance)
2185 bfe23b13 Sofia Papagiannaki
post_save.connect(user_post_save, sender=User)
2186 ff9290ec Sofia Papagiannaki
2187 fc1e2f02 Sofia Papagiannaki
def astakosuser_post_save(sender, instance, created, **kwargs):
2188 21e0fdad Giorgos Korfiatis
    pass
2189 21e0fdad Giorgos Korfiatis
2190 bfe23b13 Sofia Papagiannaki
post_save.connect(astakosuser_post_save, sender=AstakosUser)
2191 fc1e2f02 Sofia Papagiannaki
2192 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
2193 0514bcc7 Giorgos Korfiatis
    pass
2194 0514bcc7 Giorgos Korfiatis
2195 bfe23b13 Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
2196 bfe23b13 Sofia Papagiannaki
2197 bfe23b13 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
2198 bfe23b13 Sofia Papagiannaki
    if not instance.auth_token:
2199 bfe23b13 Sofia Papagiannaki
        instance.renew_token()
2200 bf0c6de5 Sofia Papagiannaki
pre_save.connect(renew_token, sender=AstakosUser)
2201 bea584e1 Giorgos Korfiatis
pre_save.connect(renew_token, sender=Component)