Statistics
| Branch: | Tag: | Revision:

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

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 9d20fe23 Kostas Papadimitriou
        return ",".join(["%s:%s" % (p.module, p.get_username_msg) 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 9d20fe23 Kostas Papadimitriou
            return self.get(module=provider, user__email_verified=False,
843 9d20fe23 Kostas Papadimitriou
                            **filters).settings
844 9d20fe23 Kostas Papadimitriou
        except AstakosUserAuthProvider.DoesNotExist:
845 9d20fe23 Kostas Papadimitriou
            return None
846 9d20fe23 Kostas Papadimitriou
847 9d20fe23 Kostas Papadimitriou
    def verified(self, provider, **filters):
848 9d20fe23 Kostas Papadimitriou
        try:
849 9d20fe23 Kostas Papadimitriou
            return self.get(module=provider, user__email_verified=True,
850 9d20fe23 Kostas Papadimitriou
                            **filters).settings
851 9d20fe23 Kostas Papadimitriou
        except AstakosUserAuthProvider.DoesNotExist:
852 9d20fe23 Kostas Papadimitriou
            return None
853 9d20fe23 Kostas Papadimitriou
854 9d20fe23 Kostas Papadimitriou
855 9d20fe23 Kostas Papadimitriou
class AuthProviderPolicyProfileManager(models.Manager):
856 9d20fe23 Kostas Papadimitriou
857 9d20fe23 Kostas Papadimitriou
    def active(self):
858 9d20fe23 Kostas Papadimitriou
        return self.filter(active=True)
859 9d20fe23 Kostas Papadimitriou
860 9d20fe23 Kostas Papadimitriou
    def for_user(self, user, provider):
861 9d20fe23 Kostas Papadimitriou
        policies = {}
862 9d20fe23 Kostas Papadimitriou
        exclusive_q1 = Q(provider=provider) & Q(is_exclusive=False)
863 9d20fe23 Kostas Papadimitriou
        exclusive_q2 = ~Q(provider=provider) & Q(is_exclusive=True)
864 9d20fe23 Kostas Papadimitriou
        exclusive_q = exclusive_q1 | exclusive_q2
865 9d20fe23 Kostas Papadimitriou
866 9d20fe23 Kostas Papadimitriou
        for profile in user.authpolicy_profiles.active().filter(exclusive_q):
867 9d20fe23 Kostas Papadimitriou
            policies.update(profile.policies)
868 9d20fe23 Kostas Papadimitriou
869 9d20fe23 Kostas Papadimitriou
        user_groups = user.groups.all().values('pk')
870 9d20fe23 Kostas Papadimitriou
        for profile in self.active().filter(groups__in=user_groups).filter(
871 9d20fe23 Kostas Papadimitriou
                exclusive_q):
872 9d20fe23 Kostas Papadimitriou
            policies.update(profile.policies)
873 9d20fe23 Kostas Papadimitriou
        return policies
874 9d20fe23 Kostas Papadimitriou
875 9d20fe23 Kostas Papadimitriou
    def add_policy(self, name, provider, group_or_user, exclusive=False,
876 9d20fe23 Kostas Papadimitriou
                   **policies):
877 9d20fe23 Kostas Papadimitriou
        is_group = isinstance(group_or_user, Group)
878 9d20fe23 Kostas Papadimitriou
        profile, created = self.get_or_create(name=name, provider=provider,
879 9d20fe23 Kostas Papadimitriou
                                              is_exclusive=exclusive)
880 9d20fe23 Kostas Papadimitriou
        profile.is_exclusive = exclusive
881 9d20fe23 Kostas Papadimitriou
        profile.save()
882 9d20fe23 Kostas Papadimitriou
        if is_group:
883 9d20fe23 Kostas Papadimitriou
            profile.groups.add(group_or_user)
884 9d20fe23 Kostas Papadimitriou
        else:
885 9d20fe23 Kostas Papadimitriou
            profile.users.add(group_or_user)
886 9d20fe23 Kostas Papadimitriou
        profile.set_policies(policies)
887 9d20fe23 Kostas Papadimitriou
        profile.save()
888 9d20fe23 Kostas Papadimitriou
        return profile
889 9d20fe23 Kostas Papadimitriou
890 9d20fe23 Kostas Papadimitriou
891 9d20fe23 Kostas Papadimitriou
class AuthProviderPolicyProfile(models.Model):
892 9d20fe23 Kostas Papadimitriou
    name = models.CharField(_('Name'), max_length=255, blank=False,
893 9d20fe23 Kostas Papadimitriou
                            null=False, db_index=True)
894 9d20fe23 Kostas Papadimitriou
    provider = models.CharField(_('Provider'), max_length=255, blank=False,
895 9d20fe23 Kostas Papadimitriou
                                null=False)
896 9d20fe23 Kostas Papadimitriou
897 9d20fe23 Kostas Papadimitriou
    # apply policies to all providers excluding the one set in provider field
898 9d20fe23 Kostas Papadimitriou
    is_exclusive = models.BooleanField(default=False)
899 9d20fe23 Kostas Papadimitriou
900 9d20fe23 Kostas Papadimitriou
    policy_add = models.NullBooleanField(null=True, default=None)
901 9d20fe23 Kostas Papadimitriou
    policy_remove = models.NullBooleanField(null=True, default=None)
902 9d20fe23 Kostas Papadimitriou
    policy_create = models.NullBooleanField(null=True, default=None)
903 9d20fe23 Kostas Papadimitriou
    policy_login = models.NullBooleanField(null=True, default=None)
904 9d20fe23 Kostas Papadimitriou
    policy_limit = models.IntegerField(null=True, default=None)
905 9d20fe23 Kostas Papadimitriou
    policy_required = models.NullBooleanField(null=True, default=None)
906 9d20fe23 Kostas Papadimitriou
    policy_automoderate = models.NullBooleanField(null=True, default=None)
907 9d20fe23 Kostas Papadimitriou
    policy_switch = models.NullBooleanField(null=True, default=None)
908 9d20fe23 Kostas Papadimitriou
909 9d20fe23 Kostas Papadimitriou
    POLICY_FIELDS = ('add', 'remove', 'create', 'login', 'limit', 'required',
910 9d20fe23 Kostas Papadimitriou
                     'automoderate')
911 9d20fe23 Kostas Papadimitriou
912 9d20fe23 Kostas Papadimitriou
    priority = models.IntegerField(null=False, default=1)
913 9d20fe23 Kostas Papadimitriou
    groups = models.ManyToManyField(Group, related_name='authpolicy_profiles')
914 9d20fe23 Kostas Papadimitriou
    users = models.ManyToManyField(AstakosUser,
915 9d20fe23 Kostas Papadimitriou
                                   related_name='authpolicy_profiles')
916 9d20fe23 Kostas Papadimitriou
    active = models.BooleanField(default=True)
917 9d20fe23 Kostas Papadimitriou
918 9d20fe23 Kostas Papadimitriou
    objects = AuthProviderPolicyProfileManager()
919 9d20fe23 Kostas Papadimitriou
920 9d20fe23 Kostas Papadimitriou
    class Meta:
921 9d20fe23 Kostas Papadimitriou
        ordering = ['priority']
922 9d20fe23 Kostas Papadimitriou
923 9d20fe23 Kostas Papadimitriou
    @property
924 9d20fe23 Kostas Papadimitriou
    def policies(self):
925 9d20fe23 Kostas Papadimitriou
        policies = {}
926 9d20fe23 Kostas Papadimitriou
        for pkey in self.POLICY_FIELDS:
927 9d20fe23 Kostas Papadimitriou
            value = getattr(self, 'policy_%s' % pkey, None)
928 9d20fe23 Kostas Papadimitriou
            if value is None:
929 9d20fe23 Kostas Papadimitriou
                continue
930 9d20fe23 Kostas Papadimitriou
            policies[pkey] = value
931 9d20fe23 Kostas Papadimitriou
        return policies
932 9d20fe23 Kostas Papadimitriou
933 9d20fe23 Kostas Papadimitriou
    def set_policies(self, policies_dict):
934 9d20fe23 Kostas Papadimitriou
        for key, value in policies_dict.iteritems():
935 9d20fe23 Kostas Papadimitriou
            if key in self.POLICY_FIELDS:
936 9d20fe23 Kostas Papadimitriou
                setattr(self, 'policy_%s' % key, value)
937 9d20fe23 Kostas Papadimitriou
        return self.policies
938 564a2292 Kostas Papadimitriou
939 d2633501 Kostas Papadimitriou
940 d2633501 Kostas Papadimitriou
class AstakosUserAuthProvider(models.Model):
941 d2633501 Kostas Papadimitriou
    """
942 d2633501 Kostas Papadimitriou
    Available user authentication methods.
943 d2633501 Kostas Papadimitriou
    """
944 8fb8d0cf Giorgos Korfiatis
    affiliation = models.CharField(_('Affiliation'), max_length=255,
945 8fb8d0cf Giorgos Korfiatis
                                   blank=True, null=True, default=None)
946 d2633501 Kostas Papadimitriou
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
947 e1a80257 Sofia Papagiannaki
    module = models.CharField(_('Provider'), max_length=255, blank=False,
948 8fb8d0cf Giorgos Korfiatis
                              default='local')
949 e1a80257 Sofia Papagiannaki
    identifier = models.CharField(_('Third-party identifier'),
950 8fb8d0cf Giorgos Korfiatis
                                  max_length=255, null=True,
951 8fb8d0cf Giorgos Korfiatis
                                  blank=True)
952 d2633501 Kostas Papadimitriou
    active = models.BooleanField(default=True)
953 e1a80257 Sofia Papagiannaki
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
954 8fb8d0cf Giorgos Korfiatis
                                    default='astakos')
955 3a72a5d4 Kostas Papadimitriou
    info_data = models.TextField(default="", null=True, blank=True)
956 c630fee6 Kostas Papadimitriou
    created = models.DateTimeField('Creation date', auto_now_add=True)
957 d2633501 Kostas Papadimitriou
958 d2633501 Kostas Papadimitriou
    objects = AstakosUserAuthProviderManager()
959 d2633501 Kostas Papadimitriou
960 d2633501 Kostas Papadimitriou
    class Meta:
961 d2633501 Kostas Papadimitriou
        unique_together = (('identifier', 'module', 'user'), )
962 c630fee6 Kostas Papadimitriou
        ordering = ('module', 'created')
963 d2633501 Kostas Papadimitriou
964 3a72a5d4 Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
965 3a72a5d4 Kostas Papadimitriou
        super(AstakosUserAuthProvider, self).__init__(*args, **kwargs)
966 3a72a5d4 Kostas Papadimitriou
        try:
967 3a72a5d4 Kostas Papadimitriou
            self.info = json.loads(self.info_data)
968 c630fee6 Kostas Papadimitriou
            if not self.info:
969 c630fee6 Kostas Papadimitriou
                self.info = {}
970 51db2da2 Giorgos Korfiatis
        except Exception:
971 3a72a5d4 Kostas Papadimitriou
            self.info = {}
972 c630fee6 Kostas Papadimitriou
973 8fb8d0cf Giorgos Korfiatis
        for key, value in self.info.iteritems():
974 3a72a5d4 Kostas Papadimitriou
            setattr(self, 'info_%s' % key, value)
975 3a72a5d4 Kostas Papadimitriou
976 d2633501 Kostas Papadimitriou
    @property
977 d2633501 Kostas Papadimitriou
    def settings(self):
978 9d20fe23 Kostas Papadimitriou
        extra_data = {}
979 d2633501 Kostas Papadimitriou
980 9d20fe23 Kostas Papadimitriou
        info_data = {}
981 9d20fe23 Kostas Papadimitriou
        if self.info_data:
982 9d20fe23 Kostas Papadimitriou
            info_data = json.loads(self.info_data)
983 3a72a5d4 Kostas Papadimitriou
984 9d20fe23 Kostas Papadimitriou
        extra_data['info'] = info_data
985 d2633501 Kostas Papadimitriou
986 9d20fe23 Kostas Papadimitriou
        for key in ['active', 'auth_backend', 'created', 'pk', 'affiliation']:
987 9d20fe23 Kostas Papadimitriou
            extra_data[key] = getattr(self, key)
988 d2633501 Kostas Papadimitriou
989 9d20fe23 Kostas Papadimitriou
        extra_data['instance'] = self
990 9d20fe23 Kostas Papadimitriou
        return auth.get_provider(self.module, self.user,
991 8fb8d0cf Giorgos Korfiatis
                                 self.identifier, **extra_data)
992 d2633501 Kostas Papadimitriou
993 f432088a Kostas Papadimitriou
    def __repr__(self):
994 8fb8d0cf Giorgos Korfiatis
        return '<AstakosUserAuthProvider %s:%s>' % (
995 8fb8d0cf Giorgos Korfiatis
            self.module, self.identifier)
996 f432088a Kostas Papadimitriou
997 b778b6fa Kostas Papadimitriou
    def __unicode__(self):
998 b778b6fa Kostas Papadimitriou
        if self.identifier:
999 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.identifier)
1000 b778b6fa Kostas Papadimitriou
        if self.auth_backend:
1001 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.auth_backend)
1002 b778b6fa Kostas Papadimitriou
        return self.module
1003 b778b6fa Kostas Papadimitriou
1004 3a72a5d4 Kostas Papadimitriou
    def save(self, *args, **kwargs):
1005 3a72a5d4 Kostas Papadimitriou
        self.info_data = json.dumps(self.info)
1006 3a72a5d4 Kostas Papadimitriou
        return super(AstakosUserAuthProvider, self).save(*args, **kwargs)
1007 b778b6fa Kostas Papadimitriou
1008 d2633501 Kostas Papadimitriou
1009 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
1010 41e92ef2 Giorgos Korfiatis
    capacity = models.BigIntegerField()
1011 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
1012 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
1013 5ce3ce4f Sofia Papagiannaki
1014 8e45d6fd Sofia Papagiannaki
    class Meta:
1015 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
1016 09e7393c Sofia Papagiannaki
1017 5ce3ce4f Sofia Papagiannaki
1018 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
1019 270dd48d Sofia Papagiannaki
    """
1020 270dd48d Sofia Papagiannaki
    Model for approval terms
1021 270dd48d Sofia Papagiannaki
    """
1022 6c736ed7 Kostas Papadimitriou
1023 5ce3ce4f Sofia Papagiannaki
    date = models.DateTimeField(
1024 3c638f72 Giorgos Korfiatis
        _('Issue date'), db_index=True, auto_now_add=True)
1025 e1a80257 Sofia Papagiannaki
    location = models.CharField(_('Terms location'), max_length=255)
1026 270dd48d Sofia Papagiannaki
1027 5ce3ce4f Sofia Papagiannaki
1028 64cd4730 Antony Chazapis
class Invitation(models.Model):
1029 890b0eaf Sofia Papagiannaki
    """
1030 890b0eaf Sofia Papagiannaki
    Model for registring invitations
1031 890b0eaf Sofia Papagiannaki
    """
1032 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
1033 64cd4730 Antony Chazapis
                                null=True)
1034 e1a80257 Sofia Papagiannaki
    realname = models.CharField(_('Real name'), max_length=255)
1035 e1a80257 Sofia Papagiannaki
    username = models.CharField(_('Unique ID'), max_length=255, unique=True)
1036 e1a80257 Sofia Papagiannaki
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
1037 e1a80257 Sofia Papagiannaki
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
1038 e1a80257 Sofia Papagiannaki
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
1039 8fb8d0cf Giorgos Korfiatis
    consumed = models.DateTimeField(_('Consumption date'),
1040 8fb8d0cf Giorgos Korfiatis
                                    null=True, blank=True)
1041 5ce3ce4f Sofia Papagiannaki
1042 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
1043 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
1044 8f5a3a06 Sofia Papagiannaki
        if not self.id:
1045 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
1046 5ce3ce4f Sofia Papagiannaki
1047 64cd4730 Antony Chazapis
    def consume(self):
1048 64cd4730 Antony Chazapis
        self.is_consumed = True
1049 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
1050 64cd4730 Antony Chazapis
        self.save()
1051 6c736ed7 Kostas Papadimitriou
1052 64cd4730 Antony Chazapis
    def __unicode__(self):
1053 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
1054 9c01d9d1 Sofia Papagiannaki
1055 49790d9d Sofia Papagiannaki
1056 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
1057 34a76cdb Kostas Papadimitriou
1058 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
1059 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
1060 49790d9d Sofia Papagiannaki
        """
1061 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
1062 49790d9d Sofia Papagiannaki
        ``User`` if valid.
1063 49790d9d Sofia Papagiannaki

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

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

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

1072 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1073 49790d9d Sofia Papagiannaki

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