Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 764d99c4

History | View | Annotate | Download (71.9 kB)

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

1004 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1005 49790d9d Sofia Papagiannaki
        after activating.
1006 49790d9d Sofia Papagiannaki

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

1009 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1010 49790d9d Sofia Papagiannaki
        return ``None``.
1011 49790d9d Sofia Papagiannaki

1012 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1013 49790d9d Sofia Papagiannaki

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