Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (65.9 kB)

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

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

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

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

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

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