Statistics
| Branch: | Tag: | Revision:

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

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

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

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

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

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

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