Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (69.7 kB)

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

1071 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1072 49790d9d Sofia Papagiannaki
        after activating.
1073 49790d9d Sofia Papagiannaki

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

1076 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1077 49790d9d Sofia Papagiannaki
        return ``None``.
1078 49790d9d Sofia Papagiannaki

1079 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1080 49790d9d Sofia Papagiannaki

1081 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
1082 49790d9d Sofia Papagiannaki
        """
1083 49790d9d Sofia Papagiannaki
        try:
1084 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
1085 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
1086 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
1087 49790d9d Sofia Papagiannaki
                email_change.delete()
1088 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
1089 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
1090 49790d9d Sofia Papagiannaki
            try:
1091 8fb8d0cf Giorgos Korfiatis
                AstakosUser.objects.get(
1092 8fb8d0cf Giorgos Korfiatis
                    email__iexact=email_change.new_email_address)
1093 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
1094 49790d9d Sofia Papagiannaki
                pass
1095 49790d9d Sofia Papagiannaki
            else:
1096 73fbaec4 Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
1097 49790d9d Sofia Papagiannaki
            # update user
1098 f6ff3033 Giorgos Korfiatis
            user = AstakosUser.objects.select_for_update().\
1099 f6ff3033 Giorgos Korfiatis
                get(pk=email_change.user_id)
1100 34a76cdb Kostas Papadimitriou
            old_email = user.email
1101 1808f7bc Giorgos Korfiatis
            user.set_email(email_change.new_email_address)
1102 49790d9d Sofia Papagiannaki
            user.save()
1103 49790d9d Sofia Papagiannaki
            email_change.delete()
1104 d59f5608 Sofia Papagiannaki
            msg = "User %s changed email from %s to %s"
1105 72313b77 Giorgos Korfiatis
            logger.log(astakos_settings.LOGGING_LEVEL, msg, user.log_display,
1106 72313b77 Giorgos Korfiatis
                       old_email, user.email)
1107 49790d9d Sofia Papagiannaki
            return user
1108 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
1109 73fbaec4 Sofia Papagiannaki
            raise ValueError(_('Invalid activation key.'))
1110 49790d9d Sofia Papagiannaki
1111 49790d9d Sofia Papagiannaki
1112 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
1113 73fbaec4 Sofia Papagiannaki
    new_email_address = models.EmailField(
1114 73fbaec4 Sofia Papagiannaki
        _(u'new e-mail address'),
1115 b8b8fdde Constantinos Venetsanopoulos
        help_text=_('Provide a new email address. Until you verify the new '
1116 b8b8fdde Constantinos Venetsanopoulos
                    'address by following the activation link that will be '
1117 b8b8fdde Constantinos Venetsanopoulos
                    'sent to it, your old email address will remain active.'))
1118 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
1119 34a76cdb Kostas Papadimitriou
        AstakosUser, unique=True, related_name='emailchanges')
1120 3c638f72 Giorgos Korfiatis
    requested_at = models.DateTimeField(auto_now_add=True)
1121 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
1122 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
1123 49790d9d Sofia Papagiannaki
1124 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
1125 49790d9d Sofia Papagiannaki
1126 34a76cdb Kostas Papadimitriou
    def get_url(self):
1127 34a76cdb Kostas Papadimitriou
        return reverse('email_change_confirm',
1128 8fb8d0cf Giorgos Korfiatis
                       kwargs={'activation_key': self.activation_key})
1129 34a76cdb Kostas Papadimitriou
1130 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
1131 8fb8d0cf Giorgos Korfiatis
        expiration_date = timedelta(
1132 8fb8d0cf Giorgos Korfiatis
            days=astakos_settings.EMAILCHANGE_ACTIVATION_DAYS)
1133 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
1134 ff9290ec Sofia Papagiannaki
1135 6b03a847 Sofia Papagiannaki
1136 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
1137 ca828a10 Sofia Papagiannaki
    """
1138 ca828a10 Sofia Papagiannaki
    Model for registring invitations
1139 ca828a10 Sofia Papagiannaki
    """
1140 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
1141 1eec103a Sofia Papagiannaki
    email = models.EmailField()
1142 ca828a10 Sofia Papagiannaki
1143 5ce3ce4f Sofia Papagiannaki
1144 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
1145 fc1e2f02 Sofia Papagiannaki
    while True:
1146 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
1147 fc1e2f02 Sofia Papagiannaki
        try:
1148 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
1149 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
1150 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
1151 fc1e2f02 Sofia Papagiannaki
            return code
1152 fc1e2f02 Sofia Papagiannaki
1153 5ce3ce4f Sofia Papagiannaki
1154 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
1155 fc1e2f02 Sofia Papagiannaki
    try:
1156 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
1157 fc1e2f02 Sofia Papagiannaki
        return term
1158 fc1e2f02 Sofia Papagiannaki
    except IndexError:
1159 fc1e2f02 Sofia Papagiannaki
        pass
1160 fc1e2f02 Sofia Papagiannaki
    return None
1161 fc1e2f02 Sofia Papagiannaki
1162 9d20fe23 Kostas Papadimitriou
1163 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
1164 ef20ea07 Sofia Papagiannaki
    """
1165 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
1166 ef20ea07 Sofia Papagiannaki
    """
1167 8fb8d0cf Giorgos Korfiatis
    third_party_identifier = models.CharField(
1168 8fb8d0cf Giorgos Korfiatis
        _('Third-party identifier'), max_length=255, null=True, blank=True)
1169 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
1170 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
1171 564a2292 Kostas Papadimitriou
    first_name = models.CharField(_('first name'), max_length=30, blank=True,
1172 564a2292 Kostas Papadimitriou
                                  null=True)
1173 564a2292 Kostas Papadimitriou
    last_name = models.CharField(_('last name'), max_length=30, blank=True,
1174 564a2292 Kostas Papadimitriou
                                 null=True)
1175 564a2292 Kostas Papadimitriou
    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
1176 564a2292 Kostas Papadimitriou
                                   null=True)
1177 8fb8d0cf Giorgos Korfiatis
    username = models.CharField(
1178 8fb8d0cf Giorgos Korfiatis
        _('username'), max_length=30, unique=True,
1179 8fb8d0cf Giorgos Korfiatis
        help_text=_("Required. 30 characters or fewer. "
1180 8fb8d0cf Giorgos Korfiatis
                    "Letters, numbers and @/./+/-/_ characters"))
1181 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
1182 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
1183 c630fee6 Kostas Papadimitriou
    info = models.TextField(default="", null=True, blank=True)
1184 d2633501 Kostas Papadimitriou
1185 678b2236 Sofia Papagiannaki
    class Meta:
1186 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
1187 ef20ea07 Sofia Papagiannaki
1188 c630fee6 Kostas Papadimitriou
    def get_user_instance(self):
1189 e7cb4085 Kostas Papadimitriou
        """
1190 e7cb4085 Kostas Papadimitriou
        Create a new AstakosUser instance based on details provided when user
1191 e7cb4085 Kostas Papadimitriou
        initially signed up.
1192 e7cb4085 Kostas Papadimitriou
        """
1193 3dfb68fe Kostas Papadimitriou
        d = copy.copy(self.__dict__)
1194 c630fee6 Kostas Papadimitriou
        d.pop('_state', None)
1195 c630fee6 Kostas Papadimitriou
        d.pop('id', None)
1196 c630fee6 Kostas Papadimitriou
        d.pop('token', None)
1197 c630fee6 Kostas Papadimitriou
        d.pop('created', None)
1198 c630fee6 Kostas Papadimitriou
        d.pop('info', None)
1199 3dfb68fe Kostas Papadimitriou
        d.pop('affiliation', None)
1200 3dfb68fe Kostas Papadimitriou
        d.pop('provider', None)
1201 3dfb68fe Kostas Papadimitriou
        d.pop('third_party_identifier', None)
1202 c630fee6 Kostas Papadimitriou
        user = AstakosUser(**d)
1203 c630fee6 Kostas Papadimitriou
1204 c630fee6 Kostas Papadimitriou
        return user
1205 c630fee6 Kostas Papadimitriou
1206 ef20ea07 Sofia Papagiannaki
    @property
1207 ef20ea07 Sofia Papagiannaki
    def realname(self):
1208 8fb8d0cf Giorgos Korfiatis
        return '%s %s' % (self.first_name, self.last_name)
1209 ef20ea07 Sofia Papagiannaki
1210 ef20ea07 Sofia Papagiannaki
    @realname.setter
1211 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
1212 e066eedb Giorgos Korfiatis
        first, last = split_realname(value)
1213 e066eedb Giorgos Korfiatis
        self.first_name = first
1214 e066eedb Giorgos Korfiatis
        self.last_name = last
1215 2e90e3ec Kostas Papadimitriou
1216 60b59fed Ilias Tsitsimpis
    def save(self, *args, **kwargs):
1217 ef20ea07 Sofia Papagiannaki
        if not self.id:
1218 ef20ea07 Sofia Papagiannaki
            # set username
1219 ef20ea07 Sofia Papagiannaki
            while not self.username:
1220 8fb8d0cf Giorgos Korfiatis
                username = uuid.uuid4().hex[:30]
1221 ef20ea07 Sofia Papagiannaki
                try:
1222 8fb8d0cf Giorgos Korfiatis
                    AstakosUser.objects.get(username=username)
1223 51db2da2 Giorgos Korfiatis
                except AstakosUser.DoesNotExist:
1224 ef20ea07 Sofia Papagiannaki
                    self.username = username
1225 60b59fed Ilias Tsitsimpis
        super(PendingThirdPartyUser, self).save(*args, **kwargs)
1226 ef20ea07 Sofia Papagiannaki
1227 d2633501 Kostas Papadimitriou
    def generate_token(self):
1228 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1229 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1230 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1231 d2633501 Kostas Papadimitriou
1232 606dea8d Kostas Papadimitriou
    def existing_user(self):
1233 8fb8d0cf Giorgos Korfiatis
        return AstakosUser.objects.filter(
1234 8fb8d0cf Giorgos Korfiatis
            auth_providers__module=self.provider,
1235 8fb8d0cf Giorgos Korfiatis
            auth_providers__identifier=self.third_party_identifier)
1236 606dea8d Kostas Papadimitriou
1237 9d20fe23 Kostas Papadimitriou
    def get_provider(self, user):
1238 9d20fe23 Kostas Papadimitriou
        params = {
1239 9d20fe23 Kostas Papadimitriou
            'info_data': self.info,
1240 9d20fe23 Kostas Papadimitriou
            'affiliation': self.affiliation
1241 9d20fe23 Kostas Papadimitriou
        }
1242 9d20fe23 Kostas Papadimitriou
        return auth.get_provider(self.provider, user,
1243 9d20fe23 Kostas Papadimitriou
                                 self.third_party_identifier, **params)
1244 9d20fe23 Kostas Papadimitriou
1245 8fb8d0cf Giorgos Korfiatis
1246 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1247 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1248 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1249 bf0c6de5 Sofia Papagiannaki
1250 fcc1e93f Sofia Papagiannaki
1251 c7c0ec58 Giorgos Korfiatis
class UserSetting(models.Model):
1252 c7c0ec58 Giorgos Korfiatis
    user = models.ForeignKey(AstakosUser)
1253 c7c0ec58 Giorgos Korfiatis
    setting = models.CharField(max_length=255)
1254 c7c0ec58 Giorgos Korfiatis
    value = models.IntegerField()
1255 c7c0ec58 Giorgos Korfiatis
1256 c7c0ec58 Giorgos Korfiatis
    class Meta:
1257 c7c0ec58 Giorgos Korfiatis
        unique_together = ("user", "setting")
1258 c7c0ec58 Giorgos Korfiatis
1259 c7c0ec58 Giorgos Korfiatis
1260 fcc1e93f Sofia Papagiannaki
### PROJECTS ###
1261 fcc1e93f Sofia Papagiannaki
################
1262 fcc1e93f Sofia Papagiannaki
1263 033f2822 Giorgos Korfiatis
class Chain(models.Model):
1264 8fb8d0cf Giorgos Korfiatis
    chain = models.AutoField(primary_key=True)
1265 033f2822 Giorgos Korfiatis
1266 033f2822 Giorgos Korfiatis
    def __str__(self):
1267 033f2822 Giorgos Korfiatis
        return "%s" % (self.chain,)
1268 033f2822 Giorgos Korfiatis
1269 4391de3d Giorgos Korfiatis
1270 033f2822 Giorgos Korfiatis
def new_chain():
1271 033f2822 Giorgos Korfiatis
    c = Chain.objects.create()
1272 033f2822 Giorgos Korfiatis
    return c
1273 033f2822 Giorgos Korfiatis
1274 033f2822 Giorgos Korfiatis
1275 c7e03d20 Giorgos Korfiatis
class ProjectApplicationManager(models.Manager):
1276 b10ceccd Giorgos Korfiatis
1277 b10ceccd Giorgos Korfiatis
    def pending_per_project(self, projects):
1278 b10ceccd Giorgos Korfiatis
        apps = self.filter(state=self.model.PENDING,
1279 b10ceccd Giorgos Korfiatis
                           chain__in=projects).order_by('chain', '-id')
1280 b10ceccd Giorgos Korfiatis
        checked_chain = None
1281 b10ceccd Giorgos Korfiatis
        projs = {}
1282 b10ceccd Giorgos Korfiatis
        for app in apps:
1283 b10ceccd Giorgos Korfiatis
            chain = app.chain_id
1284 b10ceccd Giorgos Korfiatis
            if chain != checked_chain:
1285 b10ceccd Giorgos Korfiatis
                checked_chain = chain
1286 b10ceccd Giorgos Korfiatis
                projs[chain] = app
1287 b10ceccd Giorgos Korfiatis
        return projs
1288 a5cef8d0 Kostas Papadimitriou
1289 a9ba418f Giorgos Korfiatis
1290 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1291 8fb8d0cf Giorgos Korfiatis
    applicant = models.ForeignKey(
1292 8fb8d0cf Giorgos Korfiatis
        AstakosUser,
1293 8fb8d0cf Giorgos Korfiatis
        related_name='projects_applied',
1294 8fb8d0cf Giorgos Korfiatis
        db_index=True)
1295 8fb8d0cf Giorgos Korfiatis
1296 8fb8d0cf Giorgos Korfiatis
    PENDING = 0
1297 8fb8d0cf Giorgos Korfiatis
    APPROVED = 1
1298 8fb8d0cf Giorgos Korfiatis
    REPLACED = 2
1299 8fb8d0cf Giorgos Korfiatis
    DENIED = 3
1300 8fb8d0cf Giorgos Korfiatis
    DISMISSED = 4
1301 8fb8d0cf Giorgos Korfiatis
    CANCELLED = 5
1302 8fb8d0cf Giorgos Korfiatis
1303 8fb8d0cf Giorgos Korfiatis
    state = models.IntegerField(default=PENDING,
1304 8fb8d0cf Giorgos Korfiatis
                                db_index=True)
1305 8fb8d0cf Giorgos Korfiatis
    owner = models.ForeignKey(
1306 8fb8d0cf Giorgos Korfiatis
        AstakosUser,
1307 8fb8d0cf Giorgos Korfiatis
        related_name='projects_owned',
1308 2ffe077d Giorgos Korfiatis
        null=True,
1309 8fb8d0cf Giorgos Korfiatis
        db_index=True)
1310 6d583e07 Giorgos Korfiatis
    chain = models.ForeignKey('Project',
1311 8fb8d0cf Giorgos Korfiatis
                              related_name='chained_apps',
1312 8fb8d0cf Giorgos Korfiatis
                              db_column='chain')
1313 2ffe077d Giorgos Korfiatis
    name = models.CharField(max_length=80, null=True)
1314 8fb8d0cf Giorgos Korfiatis
    homepage = models.URLField(max_length=255, null=True,
1315 8fb8d0cf Giorgos Korfiatis
                               verify_exists=False)
1316 8fb8d0cf Giorgos Korfiatis
    description = models.TextField(null=True, blank=True)
1317 8fb8d0cf Giorgos Korfiatis
    start_date = models.DateTimeField(null=True, blank=True)
1318 2ffe077d Giorgos Korfiatis
    end_date = models.DateTimeField(null=True)
1319 2ffe077d Giorgos Korfiatis
    member_join_policy = models.IntegerField(null=True)
1320 2ffe077d Giorgos Korfiatis
    member_leave_policy = models.IntegerField(null=True)
1321 8af74daf Giorgos Korfiatis
    limit_on_members_number = models.BigIntegerField(null=True)
1322 8fb8d0cf Giorgos Korfiatis
    resource_grants = models.ManyToManyField(
1323 8fb8d0cf Giorgos Korfiatis
        Resource,
1324 8fb8d0cf Giorgos Korfiatis
        null=True,
1325 8fb8d0cf Giorgos Korfiatis
        blank=True,
1326 8fb8d0cf Giorgos Korfiatis
        through='ProjectResourceGrant')
1327 8fb8d0cf Giorgos Korfiatis
    comments = models.TextField(null=True, blank=True)
1328 8fb8d0cf Giorgos Korfiatis
    issue_date = models.DateTimeField(auto_now_add=True)
1329 8fb8d0cf Giorgos Korfiatis
    response_date = models.DateTimeField(null=True, blank=True)
1330 8fb8d0cf Giorgos Korfiatis
    response = models.TextField(null=True, blank=True)
1331 88beea39 Giorgos Korfiatis
    response_actor = models.ForeignKey(AstakosUser, null=True,
1332 88beea39 Giorgos Korfiatis
                                       related_name='responded_apps')
1333 88beea39 Giorgos Korfiatis
    waive_date = models.DateTimeField(null=True, blank=True)
1334 88beea39 Giorgos Korfiatis
    waive_reason = models.TextField(null=True, blank=True)
1335 88beea39 Giorgos Korfiatis
    waive_actor = models.ForeignKey(AstakosUser, null=True,
1336 88beea39 Giorgos Korfiatis
                                    related_name='waived_apps')
1337 2ffe077d Giorgos Korfiatis
    private = models.NullBooleanField(default=False)
1338 8fb8d0cf Giorgos Korfiatis
1339 8fb8d0cf Giorgos Korfiatis
    objects = ProjectApplicationManager()
1340 7729e9cc Giorgos Korfiatis
1341 689226c3 Giorgos Korfiatis
    # Compiled queries
1342 8fb8d0cf Giorgos Korfiatis
    Q_PENDING = Q(state=PENDING)
1343 689226c3 Giorgos Korfiatis
    Q_APPROVED = Q(state=APPROVED)
1344 8fb8d0cf Giorgos Korfiatis
    Q_DENIED = Q(state=DENIED)
1345 689226c3 Giorgos Korfiatis
1346 c4892cd2 Sofia Papagiannaki
    class Meta:
1347 c4892cd2 Sofia Papagiannaki
        unique_together = ("chain", "id")
1348 c4892cd2 Sofia Papagiannaki
1349 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1350 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1351 f3a45fc6 Kostas Papadimitriou
1352 d0e78bbe Giorgos Korfiatis
    # TODO: Move to a more suitable place
1353 9307cd46 Giorgos Korfiatis
    APPLICATION_STATE_DISPLAY = {
1354 8fb8d0cf Giorgos Korfiatis
        PENDING:   _('Pending review'),
1355 8fb8d0cf Giorgos Korfiatis
        APPROVED:  _('Approved'),
1356 8fb8d0cf Giorgos Korfiatis
        REPLACED:  _('Replaced'),
1357 8fb8d0cf Giorgos Korfiatis
        DENIED:    _('Denied'),
1358 3c638f72 Giorgos Korfiatis
        DISMISSED: _('Dismissed'),
1359 3c638f72 Giorgos Korfiatis
        CANCELLED: _('Cancelled')
1360 bd9af366 Kostas Papadimitriou
    }
1361 d0e78bbe Giorgos Korfiatis
1362 f30f0170 Giorgos Korfiatis
    @property
1363 f30f0170 Giorgos Korfiatis
    def log_display(self):
1364 f30f0170 Giorgos Korfiatis
        return "application %s (%s) for project %s" % (
1365 f30f0170 Giorgos Korfiatis
            self.id, self.name, self.chain)
1366 f30f0170 Giorgos Korfiatis
1367 db9a498c Kostas Papadimitriou
    def state_display(self):
1368 9307cd46 Giorgos Korfiatis
        return self.APPLICATION_STATE_DISPLAY.get(self.state, _('Unknown'))
1369 db9a498c Kostas Papadimitriou
1370 669cfe19 Olga Brani
    @property
1371 9efd0075 Kostas Papadimitriou
    def resource_set(self):
1372 9efd0075 Kostas Papadimitriou
        return self.projectresourcegrant_set.order_by('resource__name')
1373 5550bcfb Kostas Papadimitriou
1374 e1a80257 Sofia Papagiannaki
    @property
1375 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1376 b98e1df0 Sofia Papagiannaki
        return [str(rp) for rp in self.projectresourcegrant_set.all()]
1377 e1a80257 Sofia Papagiannaki
1378 efc58b65 Kostas Papadimitriou
    def is_modification(self):
1379 d4660e00 Giorgos Korfiatis
        # if self.state != self.PENDING:
1380 d4660e00 Giorgos Korfiatis
        #     return False
1381 efc58b65 Kostas Papadimitriou
        parents = self.chained_applications().filter(id__lt=self.id)
1382 efc58b65 Kostas Papadimitriou
        parents = parents.filter(state__in=[self.APPROVED])
1383 efc58b65 Kostas Papadimitriou
        return parents.count() > 0
1384 efc58b65 Kostas Papadimitriou
1385 efc58b65 Kostas Papadimitriou
    def chained_applications(self):
1386 efc58b65 Kostas Papadimitriou
        return ProjectApplication.objects.filter(chain=self.chain)
1387 efc58b65 Kostas Papadimitriou
1388 022cc8e2 Giorgos Korfiatis
    def denied_modifications(self):
1389 022cc8e2 Giorgos Korfiatis
        q = self.chained_applications()
1390 022cc8e2 Giorgos Korfiatis
        q = q.filter(Q(state=self.DENIED))
1391 022cc8e2 Giorgos Korfiatis
        q = q.filter(~Q(id=self.id))
1392 022cc8e2 Giorgos Korfiatis
        return q
1393 022cc8e2 Giorgos Korfiatis
1394 022cc8e2 Giorgos Korfiatis
    def last_denied(self):
1395 022cc8e2 Giorgos Korfiatis
        try:
1396 022cc8e2 Giorgos Korfiatis
            return self.denied_modifications().order_by('-id')[0]
1397 022cc8e2 Giorgos Korfiatis
        except IndexError:
1398 022cc8e2 Giorgos Korfiatis
            return None
1399 022cc8e2 Giorgos Korfiatis
1400 022cc8e2 Giorgos Korfiatis
    def has_denied_modifications(self):
1401 022cc8e2 Giorgos Korfiatis
        return bool(self.last_denied())
1402 022cc8e2 Giorgos Korfiatis
1403 01bdbd17 Giorgos Korfiatis
    def can_cancel(self):
1404 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1405 01bdbd17 Giorgos Korfiatis
1406 88beea39 Giorgos Korfiatis
    def cancel(self, actor=None, reason=None):
1407 01bdbd17 Giorgos Korfiatis
        if not self.can_cancel():
1408 3c638f72 Giorgos Korfiatis
            m = _("cannot cancel: application '%s' in state '%s'") % (
1409 8fb8d0cf Giorgos Korfiatis
                self.id, self.state)
1410 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1411 3c638f72 Giorgos Korfiatis
1412 3c638f72 Giorgos Korfiatis
        self.state = self.CANCELLED
1413 88beea39 Giorgos Korfiatis
        self.waive_date = datetime.now()
1414 88beea39 Giorgos Korfiatis
        self.waive_reason = reason
1415 88beea39 Giorgos Korfiatis
        self.waive_actor = actor
1416 3c638f72 Giorgos Korfiatis
        self.save()
1417 3c638f72 Giorgos Korfiatis
1418 01bdbd17 Giorgos Korfiatis
    def can_dismiss(self):
1419 01bdbd17 Giorgos Korfiatis
        return self.state == self.DENIED
1420 01bdbd17 Giorgos Korfiatis
1421 88beea39 Giorgos Korfiatis
    def dismiss(self, actor=None, reason=None):
1422 01bdbd17 Giorgos Korfiatis
        if not self.can_dismiss():
1423 3c638f72 Giorgos Korfiatis
            m = _("cannot dismiss: application '%s' in state '%s'") % (
1424 8fb8d0cf Giorgos Korfiatis
                self.id, self.state)
1425 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1426 3c638f72 Giorgos Korfiatis
1427 3c638f72 Giorgos Korfiatis
        self.state = self.DISMISSED
1428 88beea39 Giorgos Korfiatis
        self.waive_date = datetime.now()
1429 88beea39 Giorgos Korfiatis
        self.waive_reason = reason
1430 88beea39 Giorgos Korfiatis
        self.waive_actor = actor
1431 3c638f72 Giorgos Korfiatis
        self.save()
1432 3c638f72 Giorgos Korfiatis
1433 01bdbd17 Giorgos Korfiatis
    def can_deny(self):
1434 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1435 01bdbd17 Giorgos Korfiatis
1436 88beea39 Giorgos Korfiatis
    def deny(self, actor=None, reason=None):
1437 01bdbd17 Giorgos Korfiatis
        if not self.can_deny():
1438 19eb3ee6 Giorgos Korfiatis
            m = _("cannot deny: application '%s' in state '%s'") % (
1439 8fb8d0cf Giorgos Korfiatis
                self.id, self.state)
1440 19eb3ee6 Giorgos Korfiatis
            raise AssertionError(m)
1441 19eb3ee6 Giorgos Korfiatis
1442 19eb3ee6 Giorgos Korfiatis
        self.state = self.DENIED
1443 3c638f72 Giorgos Korfiatis
        self.response_date = datetime.now()
1444 2b745492 Giorgos Korfiatis
        self.response = reason
1445 88beea39 Giorgos Korfiatis
        self.response_actor = actor
1446 19eb3ee6 Giorgos Korfiatis
        self.save()
1447 19eb3ee6 Giorgos Korfiatis
1448 01bdbd17 Giorgos Korfiatis
    def can_approve(self):
1449 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1450 01bdbd17 Giorgos Korfiatis
1451 88beea39 Giorgos Korfiatis
    def approve(self, actor=None, reason=None):
1452 01bdbd17 Giorgos Korfiatis
        if not self.can_approve():
1453 65360c65 Georgios D. Tsoukalas
            m = _("cannot approve: project '%s' in state '%s'") % (
1454 8fb8d0cf Giorgos Korfiatis
                self.name, self.state)
1455 8fb8d0cf Giorgos Korfiatis
            raise AssertionError(m)  # invalid argument
1456 262e04c6 Giorgos Korfiatis
1457 fdafae27 Giorgos Korfiatis
        now = datetime.now()
1458 3c22bad0 Giorgos Korfiatis
        self.state = self.APPROVED
1459 3c22bad0 Giorgos Korfiatis
        self.response_date = now
1460 3c22bad0 Giorgos Korfiatis
        self.response = reason
1461 88beea39 Giorgos Korfiatis
        self.response_actor = actor
1462 3c22bad0 Giorgos Korfiatis
        self.save()
1463 3cc9637a Giorgos Korfiatis
1464 b98e1df0 Sofia Papagiannaki
    @property
1465 b98e1df0 Sofia Papagiannaki
    def member_join_policy_display(self):
1466 251b83be Giorgos Korfiatis
        policy = self.member_join_policy
1467 251b83be Giorgos Korfiatis
        return presentation.PROJECT_MEMBER_JOIN_POLICIES.get(policy)
1468 b98e1df0 Sofia Papagiannaki
1469 b98e1df0 Sofia Papagiannaki
    @property
1470 b98e1df0 Sofia Papagiannaki
    def member_leave_policy_display(self):
1471 251b83be Giorgos Korfiatis
        policy = self.member_leave_policy
1472 251b83be Giorgos Korfiatis
        return presentation.PROJECT_MEMBER_LEAVE_POLICIES.get(policy)
1473 b98e1df0 Sofia Papagiannaki
1474 8fb8d0cf Giorgos Korfiatis
1475 2556cf45 Giorgos Korfiatis
class ProjectResourceGrantManager(models.Manager):
1476 2556cf45 Giorgos Korfiatis
    def grants_per_app(self, applications):
1477 2556cf45 Giorgos Korfiatis
        app_ids = [app.id for app in applications]
1478 2556cf45 Giorgos Korfiatis
        grants = self.filter(
1479 2556cf45 Giorgos Korfiatis
            project_application__in=app_ids).select_related("resource")
1480 2556cf45 Giorgos Korfiatis
        return _partition_by(lambda g: g.project_application_id, grants)
1481 2556cf45 Giorgos Korfiatis
1482 2556cf45 Giorgos Korfiatis
1483 73fbaec4 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1484 e1a80257 Sofia Papagiannaki
1485 8fb8d0cf Giorgos Korfiatis
    resource = models.ForeignKey(Resource)
1486 c3763b39 Giorgos Korfiatis
    project_application = models.ForeignKey(ProjectApplication)
1487 c3763b39 Giorgos Korfiatis
    project_capacity = models.BigIntegerField()
1488 c3763b39 Giorgos Korfiatis
    member_capacity = models.BigIntegerField()
1489 73fbaec4 Sofia Papagiannaki
1490 2556cf45 Giorgos Korfiatis
    objects = ProjectResourceGrantManager()
1491 2556cf45 Giorgos Korfiatis
1492 73fbaec4 Sofia Papagiannaki
    class Meta:
1493 73fbaec4 Sofia Papagiannaki
        unique_together = ("resource", "project_application")
1494 8327782d Sofia Papagiannaki
1495 b98e1df0 Sofia Papagiannaki
    def display_member_capacity(self):
1496 3e87075a Giorgos Korfiatis
        return units.show(self.member_capacity, self.resource.unit)
1497 b98e1df0 Sofia Papagiannaki
1498 9efd0075 Kostas Papadimitriou
    def display_project_capacity(self):
1499 9efd0075 Kostas Papadimitriou
        return units.show(self.project_capacity, self.resource.unit)
1500 9efd0075 Kostas Papadimitriou
1501 9efd0075 Kostas Papadimitriou
    def project_diffs(self):
1502 9efd0075 Kostas Papadimitriou
        project = self.project_application.chain
1503 9efd0075 Kostas Papadimitriou
        try:
1504 9efd0075 Kostas Papadimitriou
            project_resource = project.resource_set.get(resource=self.resource)
1505 9efd0075 Kostas Papadimitriou
        except ProjectResourceQuota.DoesNotExist:
1506 9efd0075 Kostas Papadimitriou
            return [self.project_capacity, self.member_capacity]
1507 9efd0075 Kostas Papadimitriou
1508 9efd0075 Kostas Papadimitriou
        project_diff = \
1509 9efd0075 Kostas Papadimitriou
                self.project_capacity - project_resource.project_capacity
1510 9efd0075 Kostas Papadimitriou
        member_diff = self.member_capacity - project_resource.member_capacity
1511 9efd0075 Kostas Papadimitriou
        return [project_diff, member_diff]
1512 9efd0075 Kostas Papadimitriou
1513 9efd0075 Kostas Papadimitriou
    def display_project_diff(self):
1514 9efd0075 Kostas Papadimitriou
        proj, member = self.project_diffs()
1515 9efd0075 Kostas Papadimitriou
        proj_abs, member_abs = abs(proj), abs(member)
1516 9efd0075 Kostas Papadimitriou
        unit = self.resource.unit
1517 9efd0075 Kostas Papadimitriou
1518 9efd0075 Kostas Papadimitriou
        def disp(v):
1519 9efd0075 Kostas Papadimitriou
            sign = u'+' if v >= 0 else u'-'
1520 9efd0075 Kostas Papadimitriou
            return sign + unicode(units.show(v, unit))
1521 9efd0075 Kostas Papadimitriou
        return map(disp, [proj_abs, member_abs])
1522 9efd0075 Kostas Papadimitriou
1523 b98e1df0 Sofia Papagiannaki
    def __str__(self):
1524 b98e1df0 Sofia Papagiannaki
        return 'Max %s per user: %s' % (self.resource.pluralized_display_name,
1525 b98e1df0 Sofia Papagiannaki
                                        self.display_member_capacity())
1526 b98e1df0 Sofia Papagiannaki
1527 e546df49 Georgios D. Tsoukalas
1528 c7e03d20 Giorgos Korfiatis
class ProjectManager(models.Manager):
1529 7eadc230 Giorgos Korfiatis
    def expired_projects(self):
1530 6d583e07 Giorgos Korfiatis
        model = self.model
1531 362dadaa Giorgos Korfiatis
        q = (Q(state__in=[model.NORMAL, model.SUSPENDED]) &
1532 362dadaa Giorgos Korfiatis
             Q(end_date__lt=datetime.now()))
1533 7eadc230 Giorgos Korfiatis
        return self.filter(q)
1534 7eadc230 Giorgos Korfiatis
1535 f243d667 Giorgos Korfiatis
    def user_accessible_projects(self, user):
1536 f243d667 Giorgos Korfiatis
        """
1537 f243d667 Giorgos Korfiatis
        Return projects accessible by specified user.
1538 f243d667 Giorgos Korfiatis
        """
1539 f243d667 Giorgos Korfiatis
        model = self.model
1540 f243d667 Giorgos Korfiatis
        if user.is_project_admin():
1541 f243d667 Giorgos Korfiatis
            flt = Q()
1542 f243d667 Giorgos Korfiatis
        else:
1543 f243d667 Giorgos Korfiatis
            membs = user.projectmembership_set.associated()
1544 f243d667 Giorgos Korfiatis
            memb_projects = membs.values_list("project", flat=True)
1545 362dadaa Giorgos Korfiatis
            flt = (Q(owner=user) |
1546 362dadaa Giorgos Korfiatis
                   Q(last_application__applicant=user) |
1547 f243d667 Giorgos Korfiatis
                   Q(id__in=memb_projects))
1548 f243d667 Giorgos Korfiatis
1549 362dadaa Giorgos Korfiatis
        relevant = ~Q(state=model.DELETED)
1550 f243d667 Giorgos Korfiatis
        return self.filter(flt, relevant).order_by(
1551 362dadaa Giorgos Korfiatis
            'creation_date').select_related('last_application', 'owner')
1552 f243d667 Giorgos Korfiatis
1553 f243d667 Giorgos Korfiatis
    def search_by_name(self, *search_strings):
1554 f243d667 Giorgos Korfiatis
        q = Q()
1555 f243d667 Giorgos Korfiatis
        for s in search_strings:
1556 f243d667 Giorgos Korfiatis
            q = q | Q(name__icontains=s)
1557 f243d667 Giorgos Korfiatis
        return self.filter(q)
1558 f243d667 Giorgos Korfiatis
1559 362dadaa Giorgos Korfiatis
    def initialized(self, flt=None):
1560 362dadaa Giorgos Korfiatis
        q = Q(state__in=self.model.INITIALIZED_STATES)
1561 362dadaa Giorgos Korfiatis
        if flt is not None:
1562 362dadaa Giorgos Korfiatis
            q &= flt
1563 362dadaa Giorgos Korfiatis
        return self.filter(q)
1564 362dadaa Giorgos Korfiatis
1565 7eadc230 Giorgos Korfiatis
1566 d6fdc91e Georgios D. Tsoukalas
class Project(models.Model):
1567 e546df49 Georgios D. Tsoukalas
1568 6d583e07 Giorgos Korfiatis
    id = models.BigIntegerField(db_column='id', primary_key=True)
1569 5195c0e9 Giorgos Korfiatis
1570 2ffe077d Giorgos Korfiatis
    last_application = models.ForeignKey(ProjectApplication, null=True,
1571 2ffe077d Giorgos Korfiatis
                                         related_name='last_of_project')
1572 4f22664f Georgios D. Tsoukalas
1573 8fb8d0cf Giorgos Korfiatis
    members = models.ManyToManyField(
1574 8fb8d0cf Giorgos Korfiatis
        AstakosUser,
1575 8fb8d0cf Giorgos Korfiatis
        through='ProjectMembership')
1576 4f22664f Georgios D. Tsoukalas
1577 8fb8d0cf Giorgos Korfiatis
    creation_date = models.DateTimeField(auto_now_add=True)
1578 8fb8d0cf Giorgos Korfiatis
    name = models.CharField(
1579 8fb8d0cf Giorgos Korfiatis
        max_length=80,
1580 8fb8d0cf Giorgos Korfiatis
        null=True,
1581 8fb8d0cf Giorgos Korfiatis
        db_index=True,
1582 8fb8d0cf Giorgos Korfiatis
        unique=True)
1583 425e2e95 Sofia Papagiannaki
1584 2ffe077d Giorgos Korfiatis
    UNINITIALIZED = 0
1585 6d583e07 Giorgos Korfiatis
    NORMAL = 1
1586 8fb8d0cf Giorgos Korfiatis
    SUSPENDED = 10
1587 8fb8d0cf Giorgos Korfiatis
    TERMINATED = 100
1588 362dadaa Giorgos Korfiatis
    DELETED = 1000
1589 362dadaa Giorgos Korfiatis
1590 362dadaa Giorgos Korfiatis
    INITIALIZED_STATES = [NORMAL,
1591 362dadaa Giorgos Korfiatis
                          SUSPENDED,
1592 362dadaa Giorgos Korfiatis
                          TERMINATED,
1593 362dadaa Giorgos Korfiatis
                          ]
1594 362dadaa Giorgos Korfiatis
1595 362dadaa Giorgos Korfiatis
    ALIVE_STATES = [NORMAL,
1596 362dadaa Giorgos Korfiatis
                    SUSPENDED,
1597 362dadaa Giorgos Korfiatis
                    ]
1598 362dadaa Giorgos Korfiatis
1599 362dadaa Giorgos Korfiatis
    SKIP_STATES = [DELETED,
1600 362dadaa Giorgos Korfiatis
                   TERMINATED,
1601 362dadaa Giorgos Korfiatis
                   ]
1602 5b9e9530 Giorgos Korfiatis
1603 88beea39 Giorgos Korfiatis
    DEACTIVATED_STATES = [SUSPENDED, TERMINATED]
1604 88beea39 Giorgos Korfiatis
1605 362dadaa Giorgos Korfiatis
    state = models.IntegerField(default=UNINITIALIZED,
1606 8fb8d0cf Giorgos Korfiatis
                                db_index=True)
1607 88f5242e Giorgos Korfiatis
    uuid = models.CharField(max_length=255, unique=True)
1608 123be68a Giorgos Korfiatis
1609 2ffe077d Giorgos Korfiatis
    owner = models.ForeignKey(
1610 2ffe077d Giorgos Korfiatis
        AstakosUser,
1611 2ffe077d Giorgos Korfiatis
        related_name='projs_owned',
1612 2ffe077d Giorgos Korfiatis
        null=True,
1613 2ffe077d Giorgos Korfiatis
        db_index=True)
1614 2ffe077d Giorgos Korfiatis
    realname = models.CharField(max_length=80)
1615 2ffe077d Giorgos Korfiatis
    homepage = models.URLField(max_length=255, verify_exists=False)
1616 2ffe077d Giorgos Korfiatis
    description = models.TextField(blank=True)
1617 2ffe077d Giorgos Korfiatis
    end_date = models.DateTimeField()
1618 2ffe077d Giorgos Korfiatis
    member_join_policy = models.IntegerField()
1619 2ffe077d Giorgos Korfiatis
    member_leave_policy = models.IntegerField()
1620 2ffe077d Giorgos Korfiatis
    limit_on_members_number = models.BigIntegerField()
1621 2ffe077d Giorgos Korfiatis
    resource_grants = models.ManyToManyField(
1622 2ffe077d Giorgos Korfiatis
        Resource,
1623 2ffe077d Giorgos Korfiatis
        null=True,
1624 2ffe077d Giorgos Korfiatis
        blank=True,
1625 2ffe077d Giorgos Korfiatis
        through='ProjectResourceQuota')
1626 2ffe077d Giorgos Korfiatis
    private = models.BooleanField(default=False)
1627 2c1a5a15 Giorgos Korfiatis
    is_base = models.BooleanField(default=False)
1628 2ffe077d Giorgos Korfiatis
1629 8fb8d0cf Giorgos Korfiatis
    objects = ProjectManager()
1630 7729e9cc Giorgos Korfiatis
1631 8c7b8bb8 Giorgos Korfiatis
    def __str__(self):
1632 b6eaca30 Giorgos Korfiatis
        return uenc(_("<project %s '%s'>") %
1633 362dadaa Giorgos Korfiatis
                    (self.id, udec(self.realname)))
1634 8c7b8bb8 Giorgos Korfiatis
1635 8c7b8bb8 Giorgos Korfiatis
    __repr__ = __str__
1636 8c7b8bb8 Giorgos Korfiatis
1637 b6eaca30 Giorgos Korfiatis
    def __unicode__(self):
1638 362dadaa Giorgos Korfiatis
        return _("<project %s '%s'>") % (self.id, self.realname)
1639 b6eaca30 Giorgos Korfiatis
1640 362dadaa Giorgos Korfiatis
    O_UNINITIALIZED = -1
1641 6d583e07 Giorgos Korfiatis
    O_PENDING = 0
1642 6d583e07 Giorgos Korfiatis
    O_ACTIVE = 1
1643 362dadaa Giorgos Korfiatis
    O_ACTIVE_PENDING = 2
1644 6d583e07 Giorgos Korfiatis
    O_DENIED = 3
1645 6d583e07 Giorgos Korfiatis
    O_DISMISSED = 4
1646 6d583e07 Giorgos Korfiatis
    O_CANCELLED = 5
1647 6d583e07 Giorgos Korfiatis
    O_SUSPENDED = 10
1648 6d583e07 Giorgos Korfiatis
    O_TERMINATED = 100
1649 362dadaa Giorgos Korfiatis
    O_DELETED = 1000
1650 6d583e07 Giorgos Korfiatis
1651 6d583e07 Giorgos Korfiatis
    O_STATE_DISPLAY = {
1652 362dadaa Giorgos Korfiatis
        O_UNINITIALIZED: _("Uninitialized"),
1653 6d583e07 Giorgos Korfiatis
        O_PENDING:    _("Pending"),
1654 6d583e07 Giorgos Korfiatis
        O_ACTIVE:     _("Active"),
1655 6d583e07 Giorgos Korfiatis
        O_DENIED:     _("Denied"),
1656 6d583e07 Giorgos Korfiatis
        O_DISMISSED:  _("Dismissed"),
1657 6d583e07 Giorgos Korfiatis
        O_CANCELLED:  _("Cancelled"),
1658 6d583e07 Giorgos Korfiatis
        O_SUSPENDED:  _("Suspended"),
1659 6d583e07 Giorgos Korfiatis
        O_TERMINATED: _("Terminated"),
1660 362dadaa Giorgos Korfiatis
        O_DELETED:    _("Deleted"),
1661 8fb8d0cf Giorgos Korfiatis
    }
1662 e1f31e63 Giorgos Korfiatis
1663 362dadaa Giorgos Korfiatis
    O_STATE_UNINITIALIZED = {
1664 362dadaa Giorgos Korfiatis
        None: O_UNINITIALIZED,
1665 362dadaa Giorgos Korfiatis
        ProjectApplication.PENDING: O_PENDING,
1666 362dadaa Giorgos Korfiatis
        ProjectApplication.DENIED:  O_DENIED,
1667 362dadaa Giorgos Korfiatis
        }
1668 362dadaa Giorgos Korfiatis
    O_STATE_DELETED = {
1669 362dadaa Giorgos Korfiatis
        None: O_DELETED,
1670 362dadaa Giorgos Korfiatis
        ProjectApplication.DISMISSED: O_DISMISSED,
1671 362dadaa Giorgos Korfiatis
        ProjectApplication.CANCELLED: O_CANCELLED,
1672 362dadaa Giorgos Korfiatis
        }
1673 6d583e07 Giorgos Korfiatis
1674 362dadaa Giorgos Korfiatis
    OVERALL_STATE = {
1675 362dadaa Giorgos Korfiatis
        NORMAL: lambda app_state: Project.O_ACTIVE,
1676 362dadaa Giorgos Korfiatis
        UNINITIALIZED: lambda app_state: Project.O_STATE_UNINITIALIZED.get(
1677 362dadaa Giorgos Korfiatis
            app_state, None),
1678 362dadaa Giorgos Korfiatis
        DELETED: lambda app_state: Project.O_STATE_DELETED.get(
1679 362dadaa Giorgos Korfiatis
            app_state, None),
1680 362dadaa Giorgos Korfiatis
        SUSPENDED: lambda app_state: Project.O_SUSPENDED,
1681 362dadaa Giorgos Korfiatis
        TERMINATED: lambda app_state: Project.O_TERMINATED,
1682 362dadaa Giorgos Korfiatis
        }
1683 6d583e07 Giorgos Korfiatis
1684 6d583e07 Giorgos Korfiatis
    @classmethod
1685 6d583e07 Giorgos Korfiatis
    def _overall_state(cls, project_state, app_state):
1686 362dadaa Giorgos Korfiatis
        os = cls.OVERALL_STATE.get(project_state, None)
1687 362dadaa Giorgos Korfiatis
        if os is None:
1688 362dadaa Giorgos Korfiatis
            return None
1689 362dadaa Giorgos Korfiatis
        return os(app_state)
1690 6d583e07 Giorgos Korfiatis
1691 6d583e07 Giorgos Korfiatis
    def overall_state(self):
1692 362dadaa Giorgos Korfiatis
        app_state = (self.last_application.state
1693 362dadaa Giorgos Korfiatis
                     if self.last_application else None)
1694 362dadaa Giorgos Korfiatis
        return self._overall_state(self.state, app_state)
1695 6d583e07 Giorgos Korfiatis
1696 6d583e07 Giorgos Korfiatis
    def last_pending_application(self):
1697 362dadaa Giorgos Korfiatis
        app = self.last_application
1698 362dadaa Giorgos Korfiatis
        if app and app.state == ProjectApplication.PENDING:
1699 362dadaa Giorgos Korfiatis
            return app
1700 6d583e07 Giorgos Korfiatis
        return None
1701 6d583e07 Giorgos Korfiatis
1702 f243d667 Giorgos Korfiatis
    def last_pending_modification(self):
1703 f243d667 Giorgos Korfiatis
        last_pending = self.last_pending_application()
1704 362dadaa Giorgos Korfiatis
        if self.state != Project.UNINITIALIZED:
1705 362dadaa Giorgos Korfiatis
            return last_pending
1706 362dadaa Giorgos Korfiatis
        return None
1707 f243d667 Giorgos Korfiatis
1708 e1f31e63 Giorgos Korfiatis
    def state_display(self):
1709 6d583e07 Giorgos Korfiatis
        return self.O_STATE_DISPLAY.get(self.overall_state(), _('Unknown'))
1710 e1f31e63 Giorgos Korfiatis
1711 7eadc230 Giorgos Korfiatis
    def expiration_info(self):
1712 7eadc230 Giorgos Korfiatis
        return (str(self.id), self.name, self.state_display(),
1713 362dadaa Giorgos Korfiatis
                str(self.end_date))
1714 7eadc230 Giorgos Korfiatis
1715 88beea39 Giorgos Korfiatis
    def last_deactivation(self):
1716 88beea39 Giorgos Korfiatis
        objs = self.log.filter(to_state__in=self.DEACTIVATED_STATES)
1717 88beea39 Giorgos Korfiatis
        ls = objs.order_by("-date")
1718 88beea39 Giorgos Korfiatis
        if not ls:
1719 88beea39 Giorgos Korfiatis
            return None
1720 88beea39 Giorgos Korfiatis
        return ls[0]
1721 88beea39 Giorgos Korfiatis
1722 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated(self, reason=None):
1723 b6fe8bb8 Giorgos Korfiatis
        if reason is not None:
1724 b6fe8bb8 Giorgos Korfiatis
            return self.state == reason
1725 425e2e95 Sofia Papagiannaki
1726 6d583e07 Giorgos Korfiatis
        return self.state != self.NORMAL
1727 6d583e07 Giorgos Korfiatis
1728 6d583e07 Giorgos Korfiatis
    def is_active(self):
1729 362dadaa Giorgos Korfiatis
        return self.state == self.NORMAL
1730 6d583e07 Giorgos Korfiatis
1731 6d583e07 Giorgos Korfiatis
    def is_initialized(self):
1732 362dadaa Giorgos Korfiatis
        return self.state in self.INITIALIZED_STATES
1733 123be68a Giorgos Korfiatis
1734 123be68a Giorgos Korfiatis
    ### Deactivation calls
1735 425e2e95 Sofia Papagiannaki
1736 88beea39 Giorgos Korfiatis
    def _log_create(self, from_state, to_state, actor=None, reason=None,
1737 88beea39 Giorgos Korfiatis
                    comments=None):
1738 88beea39 Giorgos Korfiatis
        now = datetime.now()
1739 88beea39 Giorgos Korfiatis
        self.log.create(from_state=from_state, to_state=to_state, date=now,
1740 88beea39 Giorgos Korfiatis
                        actor=actor, reason=reason, comments=comments)
1741 8aed306c Giorgos Korfiatis
1742 88beea39 Giorgos Korfiatis
    def set_state(self, to_state, actor=None, reason=None, comments=None):
1743 88beea39 Giorgos Korfiatis
        self._log_create(self.state, to_state, actor=actor, reason=reason,
1744 88beea39 Giorgos Korfiatis
                         comments=comments)
1745 88beea39 Giorgos Korfiatis
        self.state = to_state
1746 db99f198 Giorgos Korfiatis
        self.save()
1747 db99f198 Giorgos Korfiatis
1748 88beea39 Giorgos Korfiatis
    def terminate(self, actor=None, reason=None):
1749 88beea39 Giorgos Korfiatis
        self.set_state(self.TERMINATED, actor=actor, reason=reason)
1750 88beea39 Giorgos Korfiatis
        self.name = None
1751 db99f198 Giorgos Korfiatis
        self.save()
1752 123be68a Giorgos Korfiatis
1753 88beea39 Giorgos Korfiatis
    def suspend(self, actor=None, reason=None):
1754 88beea39 Giorgos Korfiatis
        self.set_state(self.SUSPENDED, actor=actor, reason=reason)
1755 425e2e95 Sofia Papagiannaki
1756 88beea39 Giorgos Korfiatis
    def resume(self, actor=None, reason=None):
1757 88beea39 Giorgos Korfiatis
        self.set_state(self.NORMAL, actor=actor, reason=reason)
1758 1b52192e Giorgos Korfiatis
        if self.name is None:
1759 362dadaa Giorgos Korfiatis
            self.name = self.realname
1760 362dadaa Giorgos Korfiatis
            self.save()
1761 362dadaa Giorgos Korfiatis
1762 362dadaa Giorgos Korfiatis
    def activate(self, actor=None, reason=None):
1763 362dadaa Giorgos Korfiatis
        assert self.state != self.DELETED, \
1764 362dadaa Giorgos Korfiatis
            "cannot activate: %s is deleted" % self
1765 362dadaa Giorgos Korfiatis
        if self.state != self.NORMAL:
1766 362dadaa Giorgos Korfiatis
            self.set_state(self.NORMAL, actor=actor, reason=reason)
1767 362dadaa Giorgos Korfiatis
        if self.name != self.realname:
1768 362dadaa Giorgos Korfiatis
            self.name = self.realname
1769 1b52192e Giorgos Korfiatis
            self.save()
1770 88beea39 Giorgos Korfiatis
1771 362dadaa Giorgos Korfiatis
    def set_deleted(self, actor=None, reason=None):
1772 362dadaa Giorgos Korfiatis
        self.set_state(self.DELETED, actor=actor, reason=reason)
1773 362dadaa Giorgos Korfiatis
1774 9efd0075 Kostas Papadimitriou
    def can_modify(self):
1775 9efd0075 Kostas Papadimitriou
        return self.state not in [self.UNINITIALIZED, self.DELETED]
1776 5b9e9530 Giorgos Korfiatis
1777 9efd0075 Kostas Papadimitriou
    ### Logical checks
1778 123be68a Giorgos Korfiatis
    @property
1779 123be68a Giorgos Korfiatis
    def is_alive(self):
1780 362dadaa Giorgos Korfiatis
        return self.state in [self.NORMAL, self.SUSPENDED]
1781 123be68a Giorgos Korfiatis
1782 123be68a Giorgos Korfiatis
    @property
1783 123be68a Giorgos Korfiatis
    def is_terminated(self):
1784 123be68a Giorgos Korfiatis
        return self.is_deactivated(self.TERMINATED)
1785 123be68a Giorgos Korfiatis
1786 123be68a Giorgos Korfiatis
    @property
1787 123be68a Giorgos Korfiatis
    def is_suspended(self):
1788 db99f198 Giorgos Korfiatis
        return self.is_deactivated(self.SUSPENDED)
1789 5b9e9530 Giorgos Korfiatis
1790 5b9e9530 Giorgos Korfiatis
    def violates_members_limit(self, adding=0):
1791 362dadaa Giorgos Korfiatis
        limit = self.limit_on_members_number
1792 022c61cd Sofia Papagiannaki
        return (len(self.approved_members) + adding > limit)
1793 5b9e9530 Giorgos Korfiatis
1794 123be68a Giorgos Korfiatis
    ### Other
1795 5b9e9530 Giorgos Korfiatis
1796 7db8c163 Georgios D. Tsoukalas
    def count_pending_memberships(self):
1797 d895de37 Giorgos Korfiatis
        return self.projectmembership_set.requested().count()
1798 61edd5cd Olga Brani
1799 d77b32f2 Giorgos Korfiatis
    def members_count(self):
1800 d77b32f2 Giorgos Korfiatis
        return self.approved_memberships.count()
1801 d77b32f2 Giorgos Korfiatis
1802 425e2e95 Sofia Papagiannaki
    @property
1803 425e2e95 Sofia Papagiannaki
    def approved_memberships(self):
1804 689226c3 Giorgos Korfiatis
        query = ProjectMembership.Q_ACCEPTED_STATES
1805 5b9e9530 Giorgos Korfiatis
        return self.projectmembership_set.filter(query)
1806 4f22664f Georgios D. Tsoukalas
1807 425e2e95 Sofia Papagiannaki
    @property
1808 425e2e95 Sofia Papagiannaki
    def approved_members(self):
1809 425e2e95 Sofia Papagiannaki
        return [m.person for m in self.approved_memberships]
1810 4f22664f Georgios D. Tsoukalas
1811 362dadaa Giorgos Korfiatis
    @property
1812 362dadaa Giorgos Korfiatis
    def member_join_policy_display(self):
1813 362dadaa Giorgos Korfiatis
        policy = self.member_join_policy
1814 362dadaa Giorgos Korfiatis
        return presentation.PROJECT_MEMBER_JOIN_POLICIES.get(policy)
1815 362dadaa Giorgos Korfiatis
1816 362dadaa Giorgos Korfiatis
    @property
1817 362dadaa Giorgos Korfiatis
    def member_leave_policy_display(self):
1818 362dadaa Giorgos Korfiatis
        policy = self.member_leave_policy
1819 362dadaa Giorgos Korfiatis
        return presentation.PROJECT_MEMBER_LEAVE_POLICIES.get(policy)
1820 362dadaa Giorgos Korfiatis
1821 9efd0075 Kostas Papadimitriou
    @property
1822 9efd0075 Kostas Papadimitriou
    def resource_set(self):
1823 9efd0075 Kostas Papadimitriou
        return self.projectresourcequota_set.order_by('resource__name')
1824 9efd0075 Kostas Papadimitriou
1825 425e2e95 Sofia Papagiannaki
1826 88f5242e Giorgos Korfiatis
def create_project(**kwargs):
1827 88f5242e Giorgos Korfiatis
    if "uuid" not in kwargs:
1828 88f5242e Giorgos Korfiatis
        kwargs["uuid"] = str(uuid.uuid4())
1829 88f5242e Giorgos Korfiatis
    return Project.objects.create(**kwargs)
1830 88f5242e Giorgos Korfiatis
1831 88f5242e Giorgos Korfiatis
1832 2ffe077d Giorgos Korfiatis
class ProjectResourceQuotaManager(models.Manager):
1833 2ffe077d Giorgos Korfiatis
    def quotas_per_project(self, projects):
1834 2ffe077d Giorgos Korfiatis
        proj_ids = [proj.id for proj in projects]
1835 2ffe077d Giorgos Korfiatis
        quotas = self.filter(
1836 2ffe077d Giorgos Korfiatis
            project__in=proj_ids).select_related("resource")
1837 2ffe077d Giorgos Korfiatis
        return _partition_by(lambda g: g.project_id, quotas)
1838 2ffe077d Giorgos Korfiatis
1839 2ffe077d Giorgos Korfiatis
1840 2ffe077d Giorgos Korfiatis
class ProjectResourceQuota(models.Model):
1841 2ffe077d Giorgos Korfiatis
1842 2ffe077d Giorgos Korfiatis
    resource = models.ForeignKey(Resource)
1843 2ffe077d Giorgos Korfiatis
    project = models.ForeignKey(Project)
1844 2ffe077d Giorgos Korfiatis
    project_capacity = models.BigIntegerField(default=0)
1845 2ffe077d Giorgos Korfiatis
    member_capacity = models.BigIntegerField(default=0)
1846 2ffe077d Giorgos Korfiatis
1847 2ffe077d Giorgos Korfiatis
    objects = ProjectResourceQuotaManager()
1848 2ffe077d Giorgos Korfiatis
1849 2ffe077d Giorgos Korfiatis
    class Meta:
1850 2ffe077d Giorgos Korfiatis
        unique_together = ("resource", "project")
1851 2ffe077d Giorgos Korfiatis
1852 9efd0075 Kostas Papadimitriou
    def display_member_capacity(self):
1853 9efd0075 Kostas Papadimitriou
        return units.show(self.member_capacity, self.resource.unit)
1854 9efd0075 Kostas Papadimitriou
1855 9efd0075 Kostas Papadimitriou
    def display_project_capacity(self):
1856 9efd0075 Kostas Papadimitriou
        return units.show(self.project_capacity, self.resource.unit)
1857 9efd0075 Kostas Papadimitriou
1858 2ffe077d Giorgos Korfiatis
1859 88beea39 Giorgos Korfiatis
class ProjectLogManager(models.Manager):
1860 88beea39 Giorgos Korfiatis
    def last_deactivations(self, projects):
1861 88beea39 Giorgos Korfiatis
        logs = self.filter(
1862 88beea39 Giorgos Korfiatis
            project__in=projects,
1863 88beea39 Giorgos Korfiatis
            to_state__in=Project.DEACTIVATED_STATES).order_by("-date")
1864 88beea39 Giorgos Korfiatis
        return first_of_group(lambda l: l.project_id, logs)
1865 88beea39 Giorgos Korfiatis
1866 88beea39 Giorgos Korfiatis
1867 88beea39 Giorgos Korfiatis
class ProjectLog(models.Model):
1868 88beea39 Giorgos Korfiatis
    project = models.ForeignKey(Project, related_name="log")
1869 88beea39 Giorgos Korfiatis
    from_state = models.IntegerField(null=True)
1870 88beea39 Giorgos Korfiatis
    to_state = models.IntegerField()
1871 88beea39 Giorgos Korfiatis
    date = models.DateTimeField()
1872 88beea39 Giorgos Korfiatis
    actor = models.ForeignKey(AstakosUser, null=True)
1873 88beea39 Giorgos Korfiatis
    reason = models.TextField(null=True)
1874 88beea39 Giorgos Korfiatis
    comments = models.TextField(null=True)
1875 88beea39 Giorgos Korfiatis
1876 88beea39 Giorgos Korfiatis
    objects = ProjectLogManager()
1877 88beea39 Giorgos Korfiatis
1878 88beea39 Giorgos Korfiatis
1879 1b52192e Giorgos Korfiatis
class ProjectLock(models.Model):
1880 c7e03d20 Giorgos Korfiatis
    pass
1881 1b52192e Giorgos Korfiatis
1882 1b52192e Giorgos Korfiatis
1883 c7e03d20 Giorgos Korfiatis
class ProjectMembershipManager(models.Manager):
1884 d77b32f2 Giorgos Korfiatis
1885 d77b32f2 Giorgos Korfiatis
    def any_accepted(self):
1886 d895de37 Giorgos Korfiatis
        q = self.model.Q_ACCEPTED_STATES
1887 d77b32f2 Giorgos Korfiatis
        return self.filter(q)
1888 d77b32f2 Giorgos Korfiatis
1889 a53ec93b Giorgos Korfiatis
    def actually_accepted(self, projects=None):
1890 c1007621 Giorgos Korfiatis
        q = self.model.Q_ACTUALLY_ACCEPTED
1891 a53ec93b Giorgos Korfiatis
        if projects is not None:
1892 a53ec93b Giorgos Korfiatis
            q &= Q(project__in=projects)
1893 c1007621 Giorgos Korfiatis
        return self.filter(q)
1894 c1007621 Giorgos Korfiatis
1895 edc51a21 Giorgos Korfiatis
    def actually_accepted_and_active(self):
1896 edc51a21 Giorgos Korfiatis
        q = self.model.Q_ACTUALLY_ACCEPTED
1897 edc51a21 Giorgos Korfiatis
        q &= Q(project__state=Project.NORMAL)
1898 edc51a21 Giorgos Korfiatis
        return self.filter(q)
1899 edc51a21 Giorgos Korfiatis
1900 d1c95ad9 Giorgos Korfiatis
    def initialized(self, projects=None):
1901 d1c95ad9 Giorgos Korfiatis
        q = Q(initialized=True)
1902 d1c95ad9 Giorgos Korfiatis
        if projects is not None:
1903 d1c95ad9 Giorgos Korfiatis
            q &= Q(project__in=projects)
1904 d1c95ad9 Giorgos Korfiatis
        return self.filter(q)
1905 d1c95ad9 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 f243d667 Giorgos Korfiatis
    def associated(self):
1913 f243d667 Giorgos Korfiatis
        return self.filter(state__in=ProjectMembership.ASSOCIATED_STATES)
1914 f243d667 Giorgos Korfiatis
1915 b10ceccd Giorgos Korfiatis
    def any_accepted_per_project(self, projects):
1916 b10ceccd Giorgos Korfiatis
        ms = self.any_accepted().filter(project__in=projects)
1917 b10ceccd Giorgos Korfiatis
        return _partition_by(lambda m: m.project_id, ms)
1918 b10ceccd Giorgos Korfiatis
1919 b10ceccd Giorgos Korfiatis
    def requested_per_project(self, projects):
1920 b10ceccd Giorgos Korfiatis
        ms = self.requested().filter(project__in=projects)
1921 b10ceccd Giorgos Korfiatis
        return _partition_by(lambda m: m.project_id, ms)
1922 b10ceccd Giorgos Korfiatis
1923 b10ceccd Giorgos Korfiatis
    def one_per_project(self):
1924 b10ceccd Giorgos Korfiatis
        ms = self.all().select_related(
1925 b10ceccd Giorgos Korfiatis
            'project', 'project__application',
1926 b10ceccd Giorgos Korfiatis
            'project__application__owner', 'project_application__applicant',
1927 b10ceccd Giorgos Korfiatis
            'person')
1928 b10ceccd Giorgos Korfiatis
        m_per_p = {}
1929 b10ceccd Giorgos Korfiatis
        for m in ms:
1930 b10ceccd Giorgos Korfiatis
            m_per_p[m.project_id] = m
1931 b10ceccd Giorgos Korfiatis
        return m_per_p
1932 b10ceccd Giorgos Korfiatis
1933 8fb8d0cf Giorgos Korfiatis
1934 d6fdc91e Georgios D. Tsoukalas
class ProjectMembership(models.Model):
1935 4f22664f Georgios D. Tsoukalas
1936 8fb8d0cf Giorgos Korfiatis
    person = models.ForeignKey(AstakosUser)
1937 8fb8d0cf Giorgos Korfiatis
    project = models.ForeignKey(Project)
1938 d6fdc91e Georgios D. Tsoukalas
1939 8fb8d0cf Giorgos Korfiatis
    REQUESTED = 0
1940 8fb8d0cf Giorgos Korfiatis
    ACCEPTED = 1
1941 8fb8d0cf Giorgos Korfiatis
    LEAVE_REQUESTED = 5
1942 db99f198 Giorgos Korfiatis
    # User deactivation
1943 8fb8d0cf Giorgos Korfiatis
    USER_SUSPENDED = 10
1944 1a14083b Giorgos Korfiatis
    REJECTED = 100
1945 1a14083b Giorgos Korfiatis
    CANCELLED = 101
1946 8fb8d0cf Giorgos Korfiatis
    REMOVED = 200
1947 db99f198 Giorgos Korfiatis
1948 8fb8d0cf Giorgos Korfiatis
    ASSOCIATED_STATES = set([REQUESTED,
1949 8fb8d0cf Giorgos Korfiatis
                             ACCEPTED,
1950 8fb8d0cf Giorgos Korfiatis
                             LEAVE_REQUESTED,
1951 8fb8d0cf Giorgos Korfiatis
                             USER_SUSPENDED,
1952 8fb8d0cf Giorgos Korfiatis
                             ])
1953 db99f198 Giorgos Korfiatis
1954 8fb8d0cf Giorgos Korfiatis
    ACCEPTED_STATES = set([ACCEPTED,
1955 8fb8d0cf Giorgos Korfiatis
                           LEAVE_REQUESTED,
1956 8fb8d0cf Giorgos Korfiatis
                           USER_SUSPENDED,
1957 8fb8d0cf Giorgos Korfiatis
                           ])
1958 05617ab9 Kostas Papadimitriou
1959 8fb8d0cf Giorgos Korfiatis
    ACTUALLY_ACCEPTED = set([ACCEPTED, LEAVE_REQUESTED])
1960 c1007621 Giorgos Korfiatis
1961 8fb8d0cf Giorgos Korfiatis
    state = models.IntegerField(default=REQUESTED,
1962 8fb8d0cf Giorgos Korfiatis
                                db_index=True)
1963 2a965273 Sofia Papagiannaki
1964 d1c95ad9 Giorgos Korfiatis
    initialized = models.BooleanField(default=False)
1965 8fb8d0cf Giorgos Korfiatis
    objects = ProjectMembershipManager()
1966 ee45eb81 Giorgos Korfiatis
1967 689226c3 Giorgos Korfiatis
    # Compiled queries
1968 d895de37 Giorgos Korfiatis
    Q_ACCEPTED_STATES = Q(state__in=ACCEPTED_STATES)
1969 c1007621 Giorgos Korfiatis
    Q_ACTUALLY_ACCEPTED = Q(state=ACCEPTED) | Q(state=LEAVE_REQUESTED)
1970 5b9e9530 Giorgos Korfiatis
1971 d77b32f2 Giorgos Korfiatis
    MEMBERSHIP_STATE_DISPLAY = {
1972 8fb8d0cf Giorgos Korfiatis
        REQUESTED:       _('Requested'),
1973 8fb8d0cf Giorgos Korfiatis
        ACCEPTED:        _('Accepted'),
1974 8fb8d0cf Giorgos Korfiatis
        LEAVE_REQUESTED: _('Leave Requested'),
1975 8fb8d0cf Giorgos Korfiatis
        USER_SUSPENDED:  _('Suspended'),
1976 1a14083b Giorgos Korfiatis
        REJECTED:        _('Rejected'),
1977 1a14083b Giorgos Korfiatis
        CANCELLED:       _('Cancelled'),
1978 1a14083b Giorgos Korfiatis
        REMOVED:         _('Removed'),
1979 8fb8d0cf Giorgos Korfiatis
    }
1980 d4660e00 Giorgos Korfiatis
1981 d4660e00 Giorgos Korfiatis
    USER_FRIENDLY_STATE_DISPLAY = {
1982 8fb8d0cf Giorgos Korfiatis
        REQUESTED:       _('Join requested'),
1983 8fb8d0cf Giorgos Korfiatis
        ACCEPTED:        _('Accepted member'),
1984 8fb8d0cf Giorgos Korfiatis
        LEAVE_REQUESTED: _('Requested to leave'),
1985 8fb8d0cf Giorgos Korfiatis
        USER_SUSPENDED:  _('Suspended member'),
1986 1a14083b Giorgos Korfiatis
        REJECTED:        _('Request rejected'),
1987 1a14083b Giorgos Korfiatis
        CANCELLED:       _('Request cancelled'),
1988 1a14083b Giorgos Korfiatis
        REMOVED:         _('Removed member'),
1989 8fb8d0cf Giorgos Korfiatis
    }
1990 d77b32f2 Giorgos Korfiatis
1991 d77b32f2 Giorgos Korfiatis
    def state_display(self):
1992 d77b32f2 Giorgos Korfiatis
        return self.MEMBERSHIP_STATE_DISPLAY.get(self.state, _('Unknown'))
1993 d77b32f2 Giorgos Korfiatis
1994 d4660e00 Giorgos Korfiatis
    def user_friendly_state_display(self):
1995 d4660e00 Giorgos Korfiatis
        return self.USER_FRIENDLY_STATE_DISPLAY.get(self.state, _('Unknown'))
1996 d4660e00 Giorgos Korfiatis
1997 0cc22d47 Sofia Papagiannaki
    class Meta:
1998 0cc22d47 Sofia Papagiannaki
        unique_together = ("person", "project")
1999 d6fdc91e Georgios D. Tsoukalas
        #index_together = [["project", "state"]]
2000 bfe23b13 Sofia Papagiannaki
2001 65360c65 Georgios D. Tsoukalas
    def __str__(self):
2002 8fb8d0cf Giorgos Korfiatis
        return uenc(_("<'%s' membership in '%s'>") %
2003 8fb8d0cf Giorgos Korfiatis
                    (self.person.username, self.project))
2004 65360c65 Georgios D. Tsoukalas
2005 65360c65 Georgios D. Tsoukalas
    __repr__ = __str__
2006 65360c65 Georgios D. Tsoukalas
2007 1a14083b Giorgos Korfiatis
    def latest_log(self):
2008 1a14083b Giorgos Korfiatis
        logs = self.log.all()
2009 1a14083b Giorgos Korfiatis
        logs_d = _partition_by(lambda l: l.to_state, logs)
2010 1a14083b Giorgos Korfiatis
        for s, s_logs in logs_d.iteritems():
2011 1a14083b Giorgos Korfiatis
            logs_d[s] = max(s_logs, key=(lambda l: l.date))
2012 1a14083b Giorgos Korfiatis
        return logs_d
2013 1a14083b Giorgos Korfiatis
2014 1a14083b Giorgos Korfiatis
    def _log_create(self, from_state, to_state, actor=None, reason=None,
2015 1a14083b Giorgos Korfiatis
                    comments=None):
2016 1a14083b Giorgos Korfiatis
        now = datetime.now()
2017 1a14083b Giorgos Korfiatis
        self.log.create(from_state=from_state, to_state=to_state, date=now,
2018 1a14083b Giorgos Korfiatis
                        actor=actor, reason=reason, comments=comments)
2019 1a14083b Giorgos Korfiatis
2020 1a14083b Giorgos Korfiatis
    def set_state(self, to_state, actor=None, reason=None, comments=None):
2021 1a14083b Giorgos Korfiatis
        self._log_create(self.state, to_state, actor=actor, reason=reason,
2022 1a14083b Giorgos Korfiatis
                         comments=comments)
2023 1a14083b Giorgos Korfiatis
        self.state = to_state
2024 1a14083b Giorgos Korfiatis
        self.save()
2025 1a14083b Giorgos Korfiatis
2026 d1c95ad9 Giorgos Korfiatis
    def is_active(self):
2027 d1c95ad9 Giorgos Korfiatis
        return (self.project.state == Project.NORMAL and
2028 d1c95ad9 Giorgos Korfiatis
                self.state in self.ACTUALLY_ACCEPTED)
2029 d1c95ad9 Giorgos Korfiatis
2030 71c741dc Giorgos Korfiatis
    ACTION_CHECKS = {
2031 71c741dc Giorgos Korfiatis
        "join": lambda m: m.state not in m.ASSOCIATED_STATES,
2032 71c741dc Giorgos Korfiatis
        "accept": lambda m: m.state == m.REQUESTED,
2033 71c741dc Giorgos Korfiatis
        "enroll": lambda m: m.state not in m.ACCEPTED_STATES,
2034 71c741dc Giorgos Korfiatis
        "leave": lambda m: m.state in m.ACCEPTED_STATES,
2035 71c741dc Giorgos Korfiatis
        "leave_request": lambda m: m.state in m.ACCEPTED_STATES,
2036 71c741dc Giorgos Korfiatis
        "deny_leave": lambda m: m.state == m.LEAVE_REQUESTED,
2037 71c741dc Giorgos Korfiatis
        "cancel_leave": lambda m: m.state == m.LEAVE_REQUESTED,
2038 71c741dc Giorgos Korfiatis
        "remove": lambda m: m.state in m.ACCEPTED_STATES,
2039 71c741dc Giorgos Korfiatis
        "reject": lambda m: m.state == m.REQUESTED,
2040 71c741dc Giorgos Korfiatis
        "cancel": lambda m: m.state == m.REQUESTED,
2041 71c741dc Giorgos Korfiatis
    }
2042 b8f05f8d Sofia Papagiannaki
2043 71c741dc Giorgos Korfiatis
    ACTION_STATES = {
2044 71c741dc Giorgos Korfiatis
        "join":          REQUESTED,
2045 71c741dc Giorgos Korfiatis
        "accept":        ACCEPTED,
2046 a5aa11ee Giorgos Korfiatis
        "enroll":        ACCEPTED,
2047 71c741dc Giorgos Korfiatis
        "leave_request": LEAVE_REQUESTED,
2048 71c741dc Giorgos Korfiatis
        "deny_leave":    ACCEPTED,
2049 71c741dc Giorgos Korfiatis
        "cancel_leave":  ACCEPTED,
2050 71c741dc Giorgos Korfiatis
        "remove":        REMOVED,
2051 71c741dc Giorgos Korfiatis
        "reject":        REJECTED,
2052 71c741dc Giorgos Korfiatis
        "cancel":        CANCELLED,
2053 71c741dc Giorgos Korfiatis
    }
2054 aad0e329 Giorgos Korfiatis
2055 71c741dc Giorgos Korfiatis
    def check_action(self, action):
2056 71c741dc Giorgos Korfiatis
        try:
2057 71c741dc Giorgos Korfiatis
            check = self.ACTION_CHECKS[action]
2058 71c741dc Giorgos Korfiatis
        except KeyError:
2059 71c741dc Giorgos Korfiatis
            raise ValueError("No check found for action '%s'" % action)
2060 71c741dc Giorgos Korfiatis
        return check(self)
2061 71c741dc Giorgos Korfiatis
2062 da4ac460 Giorgos Korfiatis
    def perform_action(self, action, actor=None, reason=None):
2063 71c741dc Giorgos Korfiatis
        if not self.check_action(action):
2064 71c741dc Giorgos Korfiatis
            m = _("%s: attempted action '%s' in state '%s'") % (
2065 71c741dc Giorgos Korfiatis
                self, action, self.state)
2066 aad0e329 Giorgos Korfiatis
            raise AssertionError(m)
2067 71c741dc Giorgos Korfiatis
        try:
2068 71c741dc Giorgos Korfiatis
            s = self.ACTION_STATES[action]
2069 71c741dc Giorgos Korfiatis
        except KeyError:
2070 71c741dc Giorgos Korfiatis
            raise ValueError("No such action '%s'" % action)
2071 d1c95ad9 Giorgos Korfiatis
        if action == "accept":
2072 d1c95ad9 Giorgos Korfiatis
            self.initialized = True
2073 da4ac460 Giorgos Korfiatis
        return self.set_state(s, actor=actor, reason=reason)
2074 aad0e329 Giorgos Korfiatis
2075 49b74233 Georgios D. Tsoukalas
2076 1a14083b Giorgos Korfiatis
class ProjectMembershipLogManager(models.Manager):
2077 1a14083b Giorgos Korfiatis
    def last_logs(self, memberships):
2078 1a14083b Giorgos Korfiatis
        logs = self.filter(membership__in=memberships).order_by("-date")
2079 1a14083b Giorgos Korfiatis
        logs = _partition_by(lambda l: l.membership_id, logs)
2080 1a14083b Giorgos Korfiatis
2081 1a14083b Giorgos Korfiatis
        for memb_id, m_logs in logs.iteritems():
2082 1a14083b Giorgos Korfiatis
            logs[memb_id] = first_of_group(lambda l: l.to_state, m_logs)
2083 1a14083b Giorgos Korfiatis
        return logs
2084 1a14083b Giorgos Korfiatis
2085 1a14083b Giorgos Korfiatis
2086 1a14083b Giorgos Korfiatis
class ProjectMembershipLog(models.Model):
2087 1a14083b Giorgos Korfiatis
    membership = models.ForeignKey(ProjectMembership, related_name="log")
2088 1a14083b Giorgos Korfiatis
    from_state = models.IntegerField(null=True)
2089 1a14083b Giorgos Korfiatis
    to_state = models.IntegerField()
2090 1a14083b Giorgos Korfiatis
    date = models.DateTimeField()
2091 1a14083b Giorgos Korfiatis
    actor = models.ForeignKey(AstakosUser, null=True)
2092 1a14083b Giorgos Korfiatis
    reason = models.TextField(null=True)
2093 1a14083b Giorgos Korfiatis
    comments = models.TextField(null=True)
2094 8fb8d0cf Giorgos Korfiatis
2095 1a14083b Giorgos Korfiatis
    objects = ProjectMembershipLogManager()
2096 425e2e95 Sofia Papagiannaki
2097 fc655b6f Kostas Papadimitriou
2098 fcc1e93f Sofia Papagiannaki
### SIGNALS ###
2099 fcc1e93f Sofia Papagiannaki
################
2100 b22de10a Sofia Papagiannaki
2101 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
2102 0514bcc7 Giorgos Korfiatis
    pass
2103 0514bcc7 Giorgos Korfiatis
2104 bfe23b13 Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
2105 bfe23b13 Sofia Papagiannaki
2106 8fb8d0cf Giorgos Korfiatis
2107 bfe23b13 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
2108 bfe23b13 Sofia Papagiannaki
    if not instance.auth_token:
2109 bfe23b13 Sofia Papagiannaki
        instance.renew_token()
2110 bea584e1 Giorgos Korfiatis
pre_save.connect(renew_token, sender=Component)