Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 72313b77

History | View | Annotate | Download (67.3 kB)

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

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

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

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

1086 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1087 49790d9d Sofia Papagiannaki

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