Statistics
| Branch: | Tag: | Revision:

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

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

1076 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1077 49790d9d Sofia Papagiannaki
        after activating.
1078 49790d9d Sofia Papagiannaki

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

1081 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1082 49790d9d Sofia Papagiannaki
        return ``None``.
1083 49790d9d Sofia Papagiannaki

1084 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1085 49790d9d Sofia Papagiannaki

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