Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 67cf14bf

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

1044 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1045 49790d9d Sofia Papagiannaki
        after activating.
1046 49790d9d Sofia Papagiannaki

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

1049 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1050 49790d9d Sofia Papagiannaki
        return ``None``.
1051 49790d9d Sofia Papagiannaki

1052 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1053 49790d9d Sofia Papagiannaki

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