Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 428c4e0a

History | View | Annotate | Download (73.6 kB)

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

1050 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1051 49790d9d Sofia Papagiannaki
        after activating.
1052 49790d9d Sofia Papagiannaki

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

1055 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1056 49790d9d Sofia Papagiannaki
        return ``None``.
1057 49790d9d Sofia Papagiannaki

1058 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1059 49790d9d Sofia Papagiannaki

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