Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 5e3c112a

History | View | Annotate | Download (66.2 kB)

1 f557d10a Giorgos Korfiatis
# Copyright 2011, 2012, 2013 GRNET S.A. All rights reserved.
2 6c736ed7 Kostas Papadimitriou
#
3 64cd4730 Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 64cd4730 Antony Chazapis
# without modification, are permitted provided that the following
5 64cd4730 Antony Chazapis
# conditions are met:
6 6c736ed7 Kostas Papadimitriou
#
7 64cd4730 Antony Chazapis
#   1. Redistributions of source code must retain the above
8 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
9 64cd4730 Antony Chazapis
#      disclaimer.
10 6c736ed7 Kostas Papadimitriou
#
11 64cd4730 Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
13 64cd4730 Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 64cd4730 Antony Chazapis
#      provided with the distribution.
15 6c736ed7 Kostas Papadimitriou
#
16 64cd4730 Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 64cd4730 Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 64cd4730 Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 64cd4730 Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 64cd4730 Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 64cd4730 Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 64cd4730 Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 64cd4730 Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 64cd4730 Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 64cd4730 Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 64cd4730 Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 64cd4730 Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 6c736ed7 Kostas Papadimitriou
#
29 64cd4730 Antony Chazapis
# The views and conclusions contained in the software and
30 64cd4730 Antony Chazapis
# documentation are those of the authors and should not be
31 64cd4730 Antony Chazapis
# interpreted as representing official policies, either expressed
32 64cd4730 Antony Chazapis
# or implied, of GRNET S.A.
33 64cd4730 Antony Chazapis
34 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 d2633501 Kostas Papadimitriou
    objects = AstakosUserManager()
473 fbaa4f3c Kostas Papadimitriou
474 0905ccd2 Sofia Papagiannaki
    @property
475 0905ccd2 Sofia Papagiannaki
    def realname(self):
476 5ce3ce4f Sofia Papagiannaki
        return '%s %s' % (self.first_name, self.last_name)
477 6c736ed7 Kostas Papadimitriou
478 5df4c364 Kostas Papadimitriou
    @property
479 5df4c364 Kostas Papadimitriou
    def log_display(self):
480 5df4c364 Kostas Papadimitriou
        """
481 5df4c364 Kostas Papadimitriou
        Should be used in all logger.* calls that refer to a user so that
482 5df4c364 Kostas Papadimitriou
        user display is consistent across log entries.
483 5df4c364 Kostas Papadimitriou
        """
484 5d5ce247 Kostas Papadimitriou
        return '%s::%s' % (self.uuid, self.email)
485 5df4c364 Kostas Papadimitriou
486 0905ccd2 Sofia Papagiannaki
    @realname.setter
487 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
488 e066eedb Giorgos Korfiatis
        first, last = split_realname(value)
489 e066eedb Giorgos Korfiatis
        self.first_name = first
490 e066eedb Giorgos Korfiatis
        self.last_name = last
491 6c736ed7 Kostas Papadimitriou
492 9a06d96f Olga Brani
    def add_permission(self, pname):
493 9a06d96f Olga Brani
        if self.has_perm(pname):
494 9a06d96f Olga Brani
            return
495 e65c21df Georgios D. Tsoukalas
        p, created = Permission.objects.get_or_create(
496 8fb8d0cf Giorgos Korfiatis
            codename=pname,
497 8fb8d0cf Giorgos Korfiatis
            name=pname.capitalize(),
498 8fb8d0cf Giorgos Korfiatis
            content_type=get_content_type())
499 9a06d96f Olga Brani
        self.user_permissions.add(p)
500 9a06d96f Olga Brani
501 9a06d96f Olga Brani
    def remove_permission(self, pname):
502 9a06d96f Olga Brani
        if self.has_perm(pname):
503 9a06d96f Olga Brani
            return
504 9a06d96f Olga Brani
        p = Permission.objects.get(codename=pname,
505 e65c21df Georgios D. Tsoukalas
                                   content_type=get_content_type())
506 9a06d96f Olga Brani
        self.user_permissions.remove(p)
507 9a06d96f Olga Brani
508 b095201e Sofia Papagiannaki
    def add_group(self, gname):
509 b095201e Sofia Papagiannaki
        group, _ = Group.objects.get_or_create(name=gname)
510 b095201e Sofia Papagiannaki
        self.groups.add(group)
511 b095201e Sofia Papagiannaki
512 3848b521 Giorgos Korfiatis
    def is_accepted(self):
513 3848b521 Giorgos Korfiatis
        return self.moderated and not self.is_rejected
514 3848b521 Giorgos Korfiatis
515 8e1a5af5 Georgios D. Tsoukalas
    def is_project_admin(self, application_id=None):
516 450c7fb0 Kostas Papadimitriou
        return self.uuid in astakos_settings.PROJECT_ADMINS
517 8e1a5af5 Georgios D. Tsoukalas
518 64cd4730 Antony Chazapis
    @property
519 64cd4730 Antony Chazapis
    def invitation(self):
520 64cd4730 Antony Chazapis
        try:
521 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
522 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
523 64cd4730 Antony Chazapis
            return None
524 6c736ed7 Kostas Papadimitriou
525 9a06d96f Olga Brani
    @property
526 9a06d96f Olga Brani
    def policies(self):
527 9a06d96f Olga Brani
        return self.astakosuserquota_set.select_related().all()
528 9a06d96f Olga Brani
529 8d1636b5 Giorgos Korfiatis
    def get_resource_policy(self, resource):
530 f72ba65d Giorgos Korfiatis
        return AstakosUserQuota.objects.select_related("resource").\
531 f72ba65d Giorgos Korfiatis
            get(user=self, resource__name=resource)
532 8d1636b5 Giorgos Korfiatis
533 1808f7bc Giorgos Korfiatis
    def fix_username(self):
534 1808f7bc Giorgos Korfiatis
        self.username = self.email.lower()
535 1808f7bc Giorgos Korfiatis
536 1808f7bc Giorgos Korfiatis
    def set_email(self, email):
537 1808f7bc Giorgos Korfiatis
        self.email = email
538 1808f7bc Giorgos Korfiatis
        self.fix_username()
539 836a0fb0 Kostas Papadimitriou
540 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
541 64cd4730 Antony Chazapis
        if update_timestamps:
542 64cd4730 Antony Chazapis
            self.updated = datetime.now()
543 5ce3ce4f Sofia Papagiannaki
544 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
545 2e90e3ec Kostas Papadimitriou
546 e7cb4085 Kostas Papadimitriou
    def renew_verification_code(self):
547 e7cb4085 Kostas Papadimitriou
        self.verification_code = str(uuid.uuid4())
548 e7cb4085 Kostas Papadimitriou
        logger.info("Verification code renewed for %s" % self.log_display)
549 e7cb4085 Kostas Papadimitriou
550 bf0c6de5 Sofia Papagiannaki
    def renew_token(self, flush_sessions=False, current_key=None):
551 7b0f970f Giorgos Korfiatis
        for i in range(10):
552 6d4190ba Giorgos Korfiatis
            new_token = generate_token()
553 7b0f970f Giorgos Korfiatis
            count = AstakosUser.objects.filter(auth_token=new_token).count()
554 7b0f970f Giorgos Korfiatis
            if count == 0:
555 7b0f970f Giorgos Korfiatis
                break
556 7b0f970f Giorgos Korfiatis
            continue
557 7b0f970f Giorgos Korfiatis
        else:
558 7b0f970f Giorgos Korfiatis
            raise ValueError('Could not generate a token')
559 2e90e3ec Kostas Papadimitriou
560 7b0f970f Giorgos Korfiatis
        self.auth_token = new_token
561 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
562 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
563 8fb8d0cf Giorgos Korfiatis
            timedelta(hours=astakos_settings.AUTH_TOKEN_DURATION)
564 bf0c6de5 Sofia Papagiannaki
        if flush_sessions:
565 bf0c6de5 Sofia Papagiannaki
            self.flush_sessions(current_key)
566 5764728a Sofia Papagiannaki
        self.delete_online_access_tokens()
567 d59f5608 Sofia Papagiannaki
        msg = 'Token renewed for %s'
568 d59f5608 Sofia Papagiannaki
        logger.log(astakos_settings.LOGGING_LEVEL, msg, self.log_display)
569 6c736ed7 Kostas Papadimitriou
570 67920ea0 Giorgos Korfiatis
    def token_expired(self):
571 67920ea0 Giorgos Korfiatis
        return self.auth_token_expires < datetime.now()
572 6c736ed7 Kostas Papadimitriou
573 bf0c6de5 Sofia Papagiannaki
    def flush_sessions(self, current_key=None):
574 bf0c6de5 Sofia Papagiannaki
        q = self.sessions
575 bf0c6de5 Sofia Papagiannaki
        if current_key:
576 bf0c6de5 Sofia Papagiannaki
            q = q.exclude(session_key=current_key)
577 2e90e3ec Kostas Papadimitriou
578 bf0c6de5 Sofia Papagiannaki
        keys = q.values_list('session_key', flat=True)
579 bf0c6de5 Sofia Papagiannaki
        if keys:
580 d59f5608 Sofia Papagiannaki
            msg = 'Flushing sessions: %s'
581 d59f5608 Sofia Papagiannaki
            logger.log(astakos_settings.LOGGING_LEVEL, msg, ','.join(keys))
582 bf0c6de5 Sofia Papagiannaki
        engine = import_module(settings.SESSION_ENGINE)
583 bf0c6de5 Sofia Papagiannaki
        for k in keys:
584 bf0c6de5 Sofia Papagiannaki
            s = engine.SessionStore(k)
585 bf0c6de5 Sofia Papagiannaki
            s.flush()
586 bf0c6de5 Sofia Papagiannaki
587 64cd4730 Antony Chazapis
    def __unicode__(self):
588 3abf6c78 Sofia Papagiannaki
        return '%s (%s)' % (self.realname, self.email)
589 5ce3ce4f Sofia Papagiannaki
590 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
591 5ce3ce4f Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username=self.username)
592 789a5951 Sofia Papagiannaki
        q = q.filter(email__iexact=self.email)
593 0a569195 Sofia Papagiannaki
        if q.count() != 0:
594 0a569195 Sofia Papagiannaki
            return True
595 0a569195 Sofia Papagiannaki
        return False
596 5ce3ce4f Sofia Papagiannaki
597 34a76cdb Kostas Papadimitriou
    def email_change_is_pending(self):
598 34a76cdb Kostas Papadimitriou
        return self.emailchanges.count() > 0
599 34a76cdb Kostas Papadimitriou
600 fcf90160 Sofia Papagiannaki
    @property
601 25769d1b Kostas Papadimitriou
    def status_display(self):
602 25769d1b Kostas Papadimitriou
        msg = ""
603 25769d1b Kostas Papadimitriou
        if self.is_active:
604 25769d1b Kostas Papadimitriou
            msg = "Accepted/Active"
605 25769d1b Kostas Papadimitriou
        if self.is_rejected:
606 25769d1b Kostas Papadimitriou
            msg = "Rejected"
607 25769d1b Kostas Papadimitriou
            if self.rejected_reason:
608 25769d1b Kostas Papadimitriou
                msg += " (%s)" % self.rejected_reason
609 25769d1b Kostas Papadimitriou
        if not self.email_verified:
610 25769d1b Kostas Papadimitriou
            msg = "Pending email verification"
611 25769d1b Kostas Papadimitriou
        if not self.moderated:
612 25769d1b Kostas Papadimitriou
            msg = "Pending moderation"
613 25769d1b Kostas Papadimitriou
        if not self.is_active and self.email_verified:
614 25769d1b Kostas Papadimitriou
            msg = "Accepted/Inactive"
615 25769d1b Kostas Papadimitriou
            if self.deactivated_reason:
616 25769d1b Kostas Papadimitriou
                msg += " (%s)" % (self.deactivated_reason)
617 25769d1b Kostas Papadimitriou
618 25769d1b Kostas Papadimitriou
        if self.moderated and not self.is_rejected:
619 25769d1b Kostas Papadimitriou
            if self.accepted_policy == 'manual':
620 25769d1b Kostas Papadimitriou
                msg += " (manually accepted)"
621 25769d1b Kostas Papadimitriou
            else:
622 25769d1b Kostas Papadimitriou
                msg += " (accepted policy: %s)" % \
623 8fb8d0cf Giorgos Korfiatis
                    self.accepted_policy
624 25769d1b Kostas Papadimitriou
        return msg
625 25769d1b Kostas Papadimitriou
626 25769d1b Kostas Papadimitriou
    @property
627 09e7393c Sofia Papagiannaki
    def signed_terms(self):
628 ef1fb98b Giorgos Korfiatis
        return self.has_signed_terms
629 09e7393c Sofia Papagiannaki
630 d2633501 Kostas Papadimitriou
    def set_invitations_level(self):
631 d2633501 Kostas Papadimitriou
        """
632 d2633501 Kostas Papadimitriou
        Update user invitation level
633 d2633501 Kostas Papadimitriou
        """
634 d2633501 Kostas Papadimitriou
        level = self.invitation.inviter.level + 1
635 d2633501 Kostas Papadimitriou
        self.level = level
636 8998f09a Sofia Papagiannaki
        self.invitations = astakos_settings.INVITATIONS_PER_LEVEL.get(level, 0)
637 d2633501 Kostas Papadimitriou
638 9d20fe23 Kostas Papadimitriou
    def can_change_password(self):
639 9d20fe23 Kostas Papadimitriou
        return self.has_auth_provider('local', auth_backend='astakos')
640 d2633501 Kostas Papadimitriou
641 9d20fe23 Kostas Papadimitriou
    def can_change_email(self):
642 9d20fe23 Kostas Papadimitriou
        if not self.has_auth_provider('local'):
643 9d20fe23 Kostas Papadimitriou
            return True
644 63836eda Kostas Papadimitriou
645 9d20fe23 Kostas Papadimitriou
        local = self.get_auth_provider('local')._instance
646 9d20fe23 Kostas Papadimitriou
        return local.auth_backend == 'astakos'
647 63836eda Kostas Papadimitriou
648 9d20fe23 Kostas Papadimitriou
    # Auth providers related methods
649 9d20fe23 Kostas Papadimitriou
    def get_auth_provider(self, module=None, identifier=None, **filters):
650 9d20fe23 Kostas Papadimitriou
        if not module:
651 9d20fe23 Kostas Papadimitriou
            return self.auth_providers.active()[0].settings
652 63836eda Kostas Papadimitriou
653 97246b51 Kostas Papadimitriou
        params = {'module': module}
654 97246b51 Kostas Papadimitriou
        if identifier:
655 97246b51 Kostas Papadimitriou
            params['identifier'] = identifier
656 97246b51 Kostas Papadimitriou
        params.update(filters)
657 97246b51 Kostas Papadimitriou
        return self.auth_providers.active().get(**params).settings
658 d2633501 Kostas Papadimitriou
659 9d20fe23 Kostas Papadimitriou
    def has_auth_provider(self, provider, **kwargs):
660 9d20fe23 Kostas Papadimitriou
        return bool(self.auth_providers.active().filter(module=provider,
661 9d20fe23 Kostas Papadimitriou
                                                        **kwargs).count())
662 d2633501 Kostas Papadimitriou
663 9d20fe23 Kostas Papadimitriou
    def get_required_providers(self, **kwargs):
664 9d20fe23 Kostas Papadimitriou
        return auth.REQUIRED_PROVIDERS.keys()
665 649f2d36 Kostas Papadimitriou
666 9d20fe23 Kostas Papadimitriou
    def missing_required_providers(self):
667 9d20fe23 Kostas Papadimitriou
        required = self.get_required_providers()
668 9d20fe23 Kostas Papadimitriou
        missing = []
669 63836eda Kostas Papadimitriou
        for provider in required:
670 63836eda Kostas Papadimitriou
            if not self.has_auth_provider(provider):
671 9d20fe23 Kostas Papadimitriou
                missing.append(auth.get_provider(provider, self))
672 9d20fe23 Kostas Papadimitriou
        return missing
673 63836eda Kostas Papadimitriou
674 9d20fe23 Kostas Papadimitriou
    def get_available_auth_providers(self, **filters):
675 d2633501 Kostas Papadimitriou
        """
676 9d20fe23 Kostas Papadimitriou
        Returns a list of providers available for add by the user.
677 d2633501 Kostas Papadimitriou
        """
678 9d20fe23 Kostas Papadimitriou
        modules = astakos_settings.IM_MODULES
679 9d20fe23 Kostas Papadimitriou
        providers = []
680 9d20fe23 Kostas Papadimitriou
        for p in modules:
681 9d20fe23 Kostas Papadimitriou
            providers.append(auth.get_provider(p, self))
682 9d20fe23 Kostas Papadimitriou
        available = []
683 9d20fe23 Kostas Papadimitriou
684 9d20fe23 Kostas Papadimitriou
        for p in providers:
685 9d20fe23 Kostas Papadimitriou
            if p.get_add_policy:
686 9d20fe23 Kostas Papadimitriou
                available.append(p)
687 9d20fe23 Kostas Papadimitriou
        return available
688 9d20fe23 Kostas Papadimitriou
689 9d20fe23 Kostas Papadimitriou
    def get_disabled_auth_providers(self, **filters):
690 9d20fe23 Kostas Papadimitriou
        providers = self.get_auth_providers(**filters)
691 9d20fe23 Kostas Papadimitriou
        disabled = []
692 9d20fe23 Kostas Papadimitriou
        for p in providers:
693 9d20fe23 Kostas Papadimitriou
            if not p.get_login_policy:
694 9d20fe23 Kostas Papadimitriou
                disabled.append(p)
695 9d20fe23 Kostas Papadimitriou
        return disabled
696 9d20fe23 Kostas Papadimitriou
697 9d20fe23 Kostas Papadimitriou
    def get_enabled_auth_providers(self, **filters):
698 9d20fe23 Kostas Papadimitriou
        providers = self.get_auth_providers(**filters)
699 9d20fe23 Kostas Papadimitriou
        enabled = []
700 9d20fe23 Kostas Papadimitriou
        for p in providers:
701 9d20fe23 Kostas Papadimitriou
            if p.get_login_policy:
702 9d20fe23 Kostas Papadimitriou
                enabled.append(p)
703 9d20fe23 Kostas Papadimitriou
        return enabled
704 9d20fe23 Kostas Papadimitriou
705 9d20fe23 Kostas Papadimitriou
    def get_auth_providers(self, **filters):
706 9d20fe23 Kostas Papadimitriou
        providers = []
707 9d20fe23 Kostas Papadimitriou
        for provider in self.auth_providers.active(**filters):
708 9d20fe23 Kostas Papadimitriou
            if provider.settings.module_enabled:
709 9d20fe23 Kostas Papadimitriou
                providers.append(provider.settings)
710 d2633501 Kostas Papadimitriou
711 9d20fe23 Kostas Papadimitriou
        modules = astakos_settings.IM_MODULES
712 d2633501 Kostas Papadimitriou
713 9d20fe23 Kostas Papadimitriou
        def key(p):
714 9d20fe23 Kostas Papadimitriou
            if not p.module in modules:
715 9d20fe23 Kostas Papadimitriou
                return 100
716 9d20fe23 Kostas Papadimitriou
            return modules.index(p.module)
717 9d20fe23 Kostas Papadimitriou
718 9d20fe23 Kostas Papadimitriou
        providers = sorted(providers, key=key)
719 9d20fe23 Kostas Papadimitriou
        return providers
720 d2633501 Kostas Papadimitriou
721 9d20fe23 Kostas Papadimitriou
    # URL methods
722 9d20fe23 Kostas Papadimitriou
    @property
723 9d20fe23 Kostas Papadimitriou
    def auth_providers_display(self):
724 9255938e Kostas Papadimitriou
        return ",".join(["%s:%s" % (p.module, p.identifier) for p in
725 9d20fe23 Kostas Papadimitriou
                         self.get_enabled_auth_providers()])
726 d2633501 Kostas Papadimitriou
727 9d20fe23 Kostas Papadimitriou
    def add_auth_provider(self, module='local', identifier=None, **params):
728 9d20fe23 Kostas Papadimitriou
        provider = auth.get_provider(module, self, identifier, **params)
729 9d20fe23 Kostas Papadimitriou
        provider.add_to_user()
730 d2633501 Kostas Papadimitriou
731 d2633501 Kostas Papadimitriou
    def get_resend_activation_url(self):
732 c630fee6 Kostas Papadimitriou
        return reverse('send_activation', kwargs={'user_id': self.pk})
733 c630fee6 Kostas Papadimitriou
734 d2633501 Kostas Papadimitriou
    def get_activation_url(self, nxt=False):
735 d2633501 Kostas Papadimitriou
        url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
736 8fb8d0cf Giorgos Korfiatis
                              quote(self.verification_code))
737 d2633501 Kostas Papadimitriou
        if nxt:
738 d2633501 Kostas Papadimitriou
            url += "&next=%s" % quote(nxt)
739 d2633501 Kostas Papadimitriou
        return url
740 d2633501 Kostas Papadimitriou
741 d2633501 Kostas Papadimitriou
    def get_password_reset_url(self, token_generator=default_token_generator):
742 70e11eaa Sofia Papagiannaki
        return reverse('astakos.im.views.target.local.password_reset_confirm',
743 8fb8d0cf Giorgos Korfiatis
                       kwargs={'uidb36': int_to_base36(self.id),
744 8fb8d0cf Giorgos Korfiatis
                               'token': token_generator.make_token(self)})
745 d2633501 Kostas Papadimitriou
746 9d20fe23 Kostas Papadimitriou
    def get_inactive_message(self, provider_module, identifier=None):
747 d0d3646d Kostas Papadimitriou
        try:
748 d0d3646d Kostas Papadimitriou
            provider = self.get_auth_provider(provider_module, identifier)
749 d0d3646d Kostas Papadimitriou
        except AstakosUserAuthProvider.DoesNotExist:
750 d0d3646d Kostas Papadimitriou
            provider = auth.get_provider(provider_module, self)
751 d2633501 Kostas Papadimitriou
752 c4b1a172 Kostas Papadimitriou
        msg_extra = ''
753 c4b1a172 Kostas Papadimitriou
        message = ''
754 9d20fe23 Kostas Papadimitriou
755 9d20fe23 Kostas Papadimitriou
        msg_inactive = provider.get_account_inactive_msg
756 9d20fe23 Kostas Papadimitriou
        msg_pending = provider.get_pending_activation_msg
757 9d20fe23 Kostas Papadimitriou
        msg_pending_help = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP)
758 9d20fe23 Kostas Papadimitriou
        #msg_resend_prompt = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION)
759 9d20fe23 Kostas Papadimitriou
        msg_pending_mod = provider.get_pending_moderation_msg
760 e7cb4085 Kostas Papadimitriou
        msg_rejected = _(astakos_messages.ACCOUNT_REJECTED)
761 9d20fe23 Kostas Papadimitriou
        msg_resend = _(astakos_messages.ACCOUNT_RESEND_ACTIVATION)
762 9d20fe23 Kostas Papadimitriou
763 e7cb4085 Kostas Papadimitriou
        if not self.email_verified:
764 e7cb4085 Kostas Papadimitriou
            message = msg_pending
765 e7cb4085 Kostas Papadimitriou
            url = self.get_resend_activation_url()
766 e7cb4085 Kostas Papadimitriou
            msg_extra = msg_pending_help + \
767 8fb8d0cf Giorgos Korfiatis
                u' ' + \
768 8fb8d0cf Giorgos Korfiatis
                '<a href="%s">%s?</a>' % (url, msg_resend)
769 c4b1a172 Kostas Papadimitriou
        else:
770 e7cb4085 Kostas Papadimitriou
            if not self.moderated:
771 9d20fe23 Kostas Papadimitriou
                message = msg_pending_mod
772 c4b1a172 Kostas Papadimitriou
            else:
773 e7cb4085 Kostas Papadimitriou
                if self.is_rejected:
774 e7cb4085 Kostas Papadimitriou
                    message = msg_rejected
775 e7cb4085 Kostas Papadimitriou
                else:
776 e7cb4085 Kostas Papadimitriou
                    message = msg_inactive
777 c4b1a172 Kostas Papadimitriou
778 e7cb4085 Kostas Papadimitriou
        return mark_safe(message + u' ' + msg_extra)
779 c4b1a172 Kostas Papadimitriou
780 7184f408 Giorgos Korfiatis
    def owns_application(self, application):
781 7184f408 Giorgos Korfiatis
        return application.owner == self
782 7184f408 Giorgos Korfiatis
783 e87bbb41 Kostas Papadimitriou
    def owns_project(self, project):
784 7184f408 Giorgos Korfiatis
        return project.application.owner == self
785 e87bbb41 Kostas Papadimitriou
786 d4660e00 Giorgos Korfiatis
    def is_associated(self, project):
787 d4660e00 Giorgos Korfiatis
        try:
788 d4660e00 Giorgos Korfiatis
            m = ProjectMembership.objects.get(person=self, project=project)
789 d4660e00 Giorgos Korfiatis
            return m.state in ProjectMembership.ASSOCIATED_STATES
790 d4660e00 Giorgos Korfiatis
        except ProjectMembership.DoesNotExist:
791 d4660e00 Giorgos Korfiatis
            return False
792 d4660e00 Giorgos Korfiatis
793 aad0e329 Giorgos Korfiatis
    def get_membership(self, project):
794 aad0e329 Giorgos Korfiatis
        try:
795 aad0e329 Giorgos Korfiatis
            return ProjectMembership.objects.get(
796 aad0e329 Giorgos Korfiatis
                project=project,
797 aad0e329 Giorgos Korfiatis
                person=self)
798 aad0e329 Giorgos Korfiatis
        except ProjectMembership.DoesNotExist:
799 aad0e329 Giorgos Korfiatis
            return None
800 d2633501 Kostas Papadimitriou
801 d4660e00 Giorgos Korfiatis
    def membership_display(self, project):
802 d4660e00 Giorgos Korfiatis
        m = self.get_membership(project)
803 d4660e00 Giorgos Korfiatis
        if m is None:
804 d4660e00 Giorgos Korfiatis
            return _('Not a member')
805 d4660e00 Giorgos Korfiatis
        else:
806 d4660e00 Giorgos Korfiatis
            return m.user_friendly_state_display()
807 d4660e00 Giorgos Korfiatis
808 7f31a7a3 Giorgos Korfiatis
    def non_owner_can_view(self, maybe_project):
809 8e1a5af5 Georgios D. Tsoukalas
        if self.is_project_admin():
810 8e1a5af5 Georgios D. Tsoukalas
            return True
811 7f31a7a3 Giorgos Korfiatis
        if maybe_project is None:
812 7f31a7a3 Giorgos Korfiatis
            return False
813 7f31a7a3 Giorgos Korfiatis
        project = maybe_project
814 7f31a7a3 Giorgos Korfiatis
        if self.is_associated(project):
815 7f31a7a3 Giorgos Korfiatis
            return True
816 7f31a7a3 Giorgos Korfiatis
        if project.is_deactivated():
817 7f31a7a3 Giorgos Korfiatis
            return False
818 7f31a7a3 Giorgos Korfiatis
        return True
819 7f31a7a3 Giorgos Korfiatis
820 5764728a Sofia Papagiannaki
    def delete_online_access_tokens(self):
821 5764728a Sofia Papagiannaki
        offline_tokens = self.token_set.filter(access_token='online')
822 5764728a Sofia Papagiannaki
        logger.info('The following access tokens will be deleted: %s',
823 5764728a Sofia Papagiannaki
                    offline_tokens)
824 5764728a Sofia Papagiannaki
        offline_tokens.delete()
825 5764728a Sofia Papagiannaki
826 a989b48e Giorgos Korfiatis
827 d2633501 Kostas Papadimitriou
class AstakosUserAuthProviderManager(models.Manager):
828 d2633501 Kostas Papadimitriou
829 63836eda Kostas Papadimitriou
    def active(self, **filters):
830 63836eda Kostas Papadimitriou
        return self.filter(active=True, **filters)
831 d2633501 Kostas Papadimitriou
832 564a2292 Kostas Papadimitriou
    def remove_unverified_providers(self, provider, **filters):
833 564a2292 Kostas Papadimitriou
        try:
834 9d20fe23 Kostas Papadimitriou
            existing = self.filter(module=provider, user__email_verified=False,
835 9d20fe23 Kostas Papadimitriou
                                   **filters)
836 564a2292 Kostas Papadimitriou
            for p in existing:
837 564a2292 Kostas Papadimitriou
                p.user.delete()
838 564a2292 Kostas Papadimitriou
        except:
839 564a2292 Kostas Papadimitriou
            pass
840 564a2292 Kostas Papadimitriou
841 9d20fe23 Kostas Papadimitriou
    def unverified(self, provider, **filters):
842 9d20fe23 Kostas Papadimitriou
        try:
843 db34e6ad Kostas Papadimitriou
844 db34e6ad Kostas Papadimitriou
            return self.select_for_update().get(module=provider,
845 db34e6ad Kostas Papadimitriou
                                                user__email_verified=False,
846 db34e6ad Kostas Papadimitriou
                                                **filters).settings
847 9d20fe23 Kostas Papadimitriou
        except AstakosUserAuthProvider.DoesNotExist:
848 9d20fe23 Kostas Papadimitriou
            return None
849 9d20fe23 Kostas Papadimitriou
850 9d20fe23 Kostas Papadimitriou
    def verified(self, provider, **filters):
851 9d20fe23 Kostas Papadimitriou
        try:
852 9d20fe23 Kostas Papadimitriou
            return self.get(module=provider, user__email_verified=True,
853 9d20fe23 Kostas Papadimitriou
                            **filters).settings
854 9d20fe23 Kostas Papadimitriou
        except AstakosUserAuthProvider.DoesNotExist:
855 9d20fe23 Kostas Papadimitriou
            return None
856 9d20fe23 Kostas Papadimitriou
857 9d20fe23 Kostas Papadimitriou
858 9d20fe23 Kostas Papadimitriou
class AuthProviderPolicyProfileManager(models.Manager):
859 9d20fe23 Kostas Papadimitriou
860 9d20fe23 Kostas Papadimitriou
    def active(self):
861 9d20fe23 Kostas Papadimitriou
        return self.filter(active=True)
862 9d20fe23 Kostas Papadimitriou
863 9d20fe23 Kostas Papadimitriou
    def for_user(self, user, provider):
864 9d20fe23 Kostas Papadimitriou
        policies = {}
865 9d20fe23 Kostas Papadimitriou
        exclusive_q1 = Q(provider=provider) & Q(is_exclusive=False)
866 9d20fe23 Kostas Papadimitriou
        exclusive_q2 = ~Q(provider=provider) & Q(is_exclusive=True)
867 9d20fe23 Kostas Papadimitriou
        exclusive_q = exclusive_q1 | exclusive_q2
868 9d20fe23 Kostas Papadimitriou
869 9d20fe23 Kostas Papadimitriou
        for profile in user.authpolicy_profiles.active().filter(exclusive_q):
870 9d20fe23 Kostas Papadimitriou
            policies.update(profile.policies)
871 9d20fe23 Kostas Papadimitriou
872 9d20fe23 Kostas Papadimitriou
        user_groups = user.groups.all().values('pk')
873 9d20fe23 Kostas Papadimitriou
        for profile in self.active().filter(groups__in=user_groups).filter(
874 9d20fe23 Kostas Papadimitriou
                exclusive_q):
875 9d20fe23 Kostas Papadimitriou
            policies.update(profile.policies)
876 9d20fe23 Kostas Papadimitriou
        return policies
877 9d20fe23 Kostas Papadimitriou
878 9d20fe23 Kostas Papadimitriou
    def add_policy(self, name, provider, group_or_user, exclusive=False,
879 9d20fe23 Kostas Papadimitriou
                   **policies):
880 9d20fe23 Kostas Papadimitriou
        is_group = isinstance(group_or_user, Group)
881 9d20fe23 Kostas Papadimitriou
        profile, created = self.get_or_create(name=name, provider=provider,
882 9d20fe23 Kostas Papadimitriou
                                              is_exclusive=exclusive)
883 9d20fe23 Kostas Papadimitriou
        profile.is_exclusive = exclusive
884 9d20fe23 Kostas Papadimitriou
        profile.save()
885 9d20fe23 Kostas Papadimitriou
        if is_group:
886 9d20fe23 Kostas Papadimitriou
            profile.groups.add(group_or_user)
887 9d20fe23 Kostas Papadimitriou
        else:
888 9d20fe23 Kostas Papadimitriou
            profile.users.add(group_or_user)
889 9d20fe23 Kostas Papadimitriou
        profile.set_policies(policies)
890 9d20fe23 Kostas Papadimitriou
        profile.save()
891 9d20fe23 Kostas Papadimitriou
        return profile
892 9d20fe23 Kostas Papadimitriou
893 9d20fe23 Kostas Papadimitriou
894 9d20fe23 Kostas Papadimitriou
class AuthProviderPolicyProfile(models.Model):
895 9d20fe23 Kostas Papadimitriou
    name = models.CharField(_('Name'), max_length=255, blank=False,
896 9d20fe23 Kostas Papadimitriou
                            null=False, db_index=True)
897 9d20fe23 Kostas Papadimitriou
    provider = models.CharField(_('Provider'), max_length=255, blank=False,
898 9d20fe23 Kostas Papadimitriou
                                null=False)
899 9d20fe23 Kostas Papadimitriou
900 9d20fe23 Kostas Papadimitriou
    # apply policies to all providers excluding the one set in provider field
901 9d20fe23 Kostas Papadimitriou
    is_exclusive = models.BooleanField(default=False)
902 9d20fe23 Kostas Papadimitriou
903 9d20fe23 Kostas Papadimitriou
    policy_add = models.NullBooleanField(null=True, default=None)
904 9d20fe23 Kostas Papadimitriou
    policy_remove = models.NullBooleanField(null=True, default=None)
905 9d20fe23 Kostas Papadimitriou
    policy_create = models.NullBooleanField(null=True, default=None)
906 9d20fe23 Kostas Papadimitriou
    policy_login = models.NullBooleanField(null=True, default=None)
907 9d20fe23 Kostas Papadimitriou
    policy_limit = models.IntegerField(null=True, default=None)
908 9d20fe23 Kostas Papadimitriou
    policy_required = models.NullBooleanField(null=True, default=None)
909 9d20fe23 Kostas Papadimitriou
    policy_automoderate = models.NullBooleanField(null=True, default=None)
910 9d20fe23 Kostas Papadimitriou
    policy_switch = models.NullBooleanField(null=True, default=None)
911 9d20fe23 Kostas Papadimitriou
912 9d20fe23 Kostas Papadimitriou
    POLICY_FIELDS = ('add', 'remove', 'create', 'login', 'limit', 'required',
913 9d20fe23 Kostas Papadimitriou
                     'automoderate')
914 9d20fe23 Kostas Papadimitriou
915 9d20fe23 Kostas Papadimitriou
    priority = models.IntegerField(null=False, default=1)
916 9d20fe23 Kostas Papadimitriou
    groups = models.ManyToManyField(Group, related_name='authpolicy_profiles')
917 9d20fe23 Kostas Papadimitriou
    users = models.ManyToManyField(AstakosUser,
918 9d20fe23 Kostas Papadimitriou
                                   related_name='authpolicy_profiles')
919 9d20fe23 Kostas Papadimitriou
    active = models.BooleanField(default=True)
920 9d20fe23 Kostas Papadimitriou
921 9d20fe23 Kostas Papadimitriou
    objects = AuthProviderPolicyProfileManager()
922 9d20fe23 Kostas Papadimitriou
923 9d20fe23 Kostas Papadimitriou
    class Meta:
924 9d20fe23 Kostas Papadimitriou
        ordering = ['priority']
925 9d20fe23 Kostas Papadimitriou
926 9d20fe23 Kostas Papadimitriou
    @property
927 9d20fe23 Kostas Papadimitriou
    def policies(self):
928 9d20fe23 Kostas Papadimitriou
        policies = {}
929 9d20fe23 Kostas Papadimitriou
        for pkey in self.POLICY_FIELDS:
930 9d20fe23 Kostas Papadimitriou
            value = getattr(self, 'policy_%s' % pkey, None)
931 9d20fe23 Kostas Papadimitriou
            if value is None:
932 9d20fe23 Kostas Papadimitriou
                continue
933 9d20fe23 Kostas Papadimitriou
            policies[pkey] = value
934 9d20fe23 Kostas Papadimitriou
        return policies
935 9d20fe23 Kostas Papadimitriou
936 9d20fe23 Kostas Papadimitriou
    def set_policies(self, policies_dict):
937 9d20fe23 Kostas Papadimitriou
        for key, value in policies_dict.iteritems():
938 9d20fe23 Kostas Papadimitriou
            if key in self.POLICY_FIELDS:
939 9d20fe23 Kostas Papadimitriou
                setattr(self, 'policy_%s' % key, value)
940 9d20fe23 Kostas Papadimitriou
        return self.policies
941 564a2292 Kostas Papadimitriou
942 d2633501 Kostas Papadimitriou
943 d2633501 Kostas Papadimitriou
class AstakosUserAuthProvider(models.Model):
944 d2633501 Kostas Papadimitriou
    """
945 d2633501 Kostas Papadimitriou
    Available user authentication methods.
946 d2633501 Kostas Papadimitriou
    """
947 8fb8d0cf Giorgos Korfiatis
    affiliation = models.CharField(_('Affiliation'), max_length=255,
948 8fb8d0cf Giorgos Korfiatis
                                   blank=True, null=True, default=None)
949 d2633501 Kostas Papadimitriou
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
950 e1a80257 Sofia Papagiannaki
    module = models.CharField(_('Provider'), max_length=255, blank=False,
951 8fb8d0cf Giorgos Korfiatis
                              default='local')
952 e1a80257 Sofia Papagiannaki
    identifier = models.CharField(_('Third-party identifier'),
953 8fb8d0cf Giorgos Korfiatis
                                  max_length=255, null=True,
954 8fb8d0cf Giorgos Korfiatis
                                  blank=True)
955 d2633501 Kostas Papadimitriou
    active = models.BooleanField(default=True)
956 e1a80257 Sofia Papagiannaki
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
957 8fb8d0cf Giorgos Korfiatis
                                    default='astakos')
958 3a72a5d4 Kostas Papadimitriou
    info_data = models.TextField(default="", null=True, blank=True)
959 c630fee6 Kostas Papadimitriou
    created = models.DateTimeField('Creation date', auto_now_add=True)
960 d2633501 Kostas Papadimitriou
961 d2633501 Kostas Papadimitriou
    objects = AstakosUserAuthProviderManager()
962 d2633501 Kostas Papadimitriou
963 d2633501 Kostas Papadimitriou
    class Meta:
964 d2633501 Kostas Papadimitriou
        unique_together = (('identifier', 'module', 'user'), )
965 c630fee6 Kostas Papadimitriou
        ordering = ('module', 'created')
966 d2633501 Kostas Papadimitriou
967 3a72a5d4 Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
968 3a72a5d4 Kostas Papadimitriou
        super(AstakosUserAuthProvider, self).__init__(*args, **kwargs)
969 3a72a5d4 Kostas Papadimitriou
        try:
970 3a72a5d4 Kostas Papadimitriou
            self.info = json.loads(self.info_data)
971 c630fee6 Kostas Papadimitriou
            if not self.info:
972 c630fee6 Kostas Papadimitriou
                self.info = {}
973 51db2da2 Giorgos Korfiatis
        except Exception:
974 3a72a5d4 Kostas Papadimitriou
            self.info = {}
975 c630fee6 Kostas Papadimitriou
976 8fb8d0cf Giorgos Korfiatis
        for key, value in self.info.iteritems():
977 3a72a5d4 Kostas Papadimitriou
            setattr(self, 'info_%s' % key, value)
978 3a72a5d4 Kostas Papadimitriou
979 d2633501 Kostas Papadimitriou
    @property
980 d2633501 Kostas Papadimitriou
    def settings(self):
981 9d20fe23 Kostas Papadimitriou
        extra_data = {}
982 d2633501 Kostas Papadimitriou
983 9d20fe23 Kostas Papadimitriou
        info_data = {}
984 9d20fe23 Kostas Papadimitriou
        if self.info_data:
985 9d20fe23 Kostas Papadimitriou
            info_data = json.loads(self.info_data)
986 3a72a5d4 Kostas Papadimitriou
987 9d20fe23 Kostas Papadimitriou
        extra_data['info'] = info_data
988 d2633501 Kostas Papadimitriou
989 9d20fe23 Kostas Papadimitriou
        for key in ['active', 'auth_backend', 'created', 'pk', 'affiliation']:
990 9d20fe23 Kostas Papadimitriou
            extra_data[key] = getattr(self, key)
991 d2633501 Kostas Papadimitriou
992 9d20fe23 Kostas Papadimitriou
        extra_data['instance'] = self
993 9d20fe23 Kostas Papadimitriou
        return auth.get_provider(self.module, self.user,
994 8fb8d0cf Giorgos Korfiatis
                                 self.identifier, **extra_data)
995 d2633501 Kostas Papadimitriou
996 f432088a Kostas Papadimitriou
    def __repr__(self):
997 8fb8d0cf Giorgos Korfiatis
        return '<AstakosUserAuthProvider %s:%s>' % (
998 8fb8d0cf Giorgos Korfiatis
            self.module, self.identifier)
999 f432088a Kostas Papadimitriou
1000 b778b6fa Kostas Papadimitriou
    def __unicode__(self):
1001 b778b6fa Kostas Papadimitriou
        if self.identifier:
1002 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.identifier)
1003 b778b6fa Kostas Papadimitriou
        if self.auth_backend:
1004 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.auth_backend)
1005 b778b6fa Kostas Papadimitriou
        return self.module
1006 b778b6fa Kostas Papadimitriou
1007 3a72a5d4 Kostas Papadimitriou
    def save(self, *args, **kwargs):
1008 3a72a5d4 Kostas Papadimitriou
        self.info_data = json.dumps(self.info)
1009 3a72a5d4 Kostas Papadimitriou
        return super(AstakosUserAuthProvider, self).save(*args, **kwargs)
1010 b778b6fa Kostas Papadimitriou
1011 d2633501 Kostas Papadimitriou
1012 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
1013 41e92ef2 Giorgos Korfiatis
    capacity = models.BigIntegerField()
1014 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
1015 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
1016 5ce3ce4f Sofia Papagiannaki
1017 8e45d6fd Sofia Papagiannaki
    class Meta:
1018 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
1019 09e7393c Sofia Papagiannaki
1020 5ce3ce4f Sofia Papagiannaki
1021 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
1022 270dd48d Sofia Papagiannaki
    """
1023 270dd48d Sofia Papagiannaki
    Model for approval terms
1024 270dd48d Sofia Papagiannaki
    """
1025 6c736ed7 Kostas Papadimitriou
1026 5ce3ce4f Sofia Papagiannaki
    date = models.DateTimeField(
1027 3c638f72 Giorgos Korfiatis
        _('Issue date'), db_index=True, auto_now_add=True)
1028 e1a80257 Sofia Papagiannaki
    location = models.CharField(_('Terms location'), max_length=255)
1029 270dd48d Sofia Papagiannaki
1030 5ce3ce4f Sofia Papagiannaki
1031 64cd4730 Antony Chazapis
class Invitation(models.Model):
1032 890b0eaf Sofia Papagiannaki
    """
1033 890b0eaf Sofia Papagiannaki
    Model for registring invitations
1034 890b0eaf Sofia Papagiannaki
    """
1035 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
1036 64cd4730 Antony Chazapis
                                null=True)
1037 e1a80257 Sofia Papagiannaki
    realname = models.CharField(_('Real name'), max_length=255)
1038 e1a80257 Sofia Papagiannaki
    username = models.CharField(_('Unique ID'), max_length=255, unique=True)
1039 e1a80257 Sofia Papagiannaki
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
1040 e1a80257 Sofia Papagiannaki
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
1041 e1a80257 Sofia Papagiannaki
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
1042 8fb8d0cf Giorgos Korfiatis
    consumed = models.DateTimeField(_('Consumption date'),
1043 8fb8d0cf Giorgos Korfiatis
                                    null=True, blank=True)
1044 5ce3ce4f Sofia Papagiannaki
1045 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
1046 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
1047 8f5a3a06 Sofia Papagiannaki
        if not self.id:
1048 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
1049 5ce3ce4f Sofia Papagiannaki
1050 64cd4730 Antony Chazapis
    def consume(self):
1051 64cd4730 Antony Chazapis
        self.is_consumed = True
1052 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
1053 64cd4730 Antony Chazapis
        self.save()
1054 6c736ed7 Kostas Papadimitriou
1055 64cd4730 Antony Chazapis
    def __unicode__(self):
1056 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
1057 9c01d9d1 Sofia Papagiannaki
1058 49790d9d Sofia Papagiannaki
1059 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
1060 34a76cdb Kostas Papadimitriou
1061 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
1062 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
1063 49790d9d Sofia Papagiannaki
        """
1064 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
1065 49790d9d Sofia Papagiannaki
        ``User`` if valid.
1066 49790d9d Sofia Papagiannaki

1067 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1068 49790d9d Sofia Papagiannaki
        after activating.
1069 49790d9d Sofia Papagiannaki

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

1072 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1073 49790d9d Sofia Papagiannaki
        return ``None``.
1074 49790d9d Sofia Papagiannaki

1075 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1076 49790d9d Sofia Papagiannaki

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