Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 890c2065

History | View | Annotate | Download (79.7 kB)

1 aba1e498 Antony Chazapis
# Copyright 2011-2012 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 64cd4730 Antony Chazapis
import hashlib
35 15efc749 Sofia Papagiannaki
import uuid
36 18ffbee1 Sofia Papagiannaki
import logging
37 3a72a5d4 Kostas Papadimitriou
import json
38 b98e1df0 Sofia Papagiannaki
import math
39 64cd4730 Antony Chazapis
40 d6fdc91e Georgios D. Tsoukalas
from time import asctime, sleep
41 64cd4730 Antony Chazapis
from datetime import datetime, timedelta
42 64cd4730 Antony Chazapis
from base64 import b64encode
43 ef20ea07 Sofia Papagiannaki
from urlparse import urlparse
44 d2633501 Kostas Papadimitriou
from urllib import quote
45 8f5a3a06 Sofia Papagiannaki
from random import randint
46 65360c65 Georgios D. Tsoukalas
from collections import defaultdict, namedtuple
47 64cd4730 Antony Chazapis
48 d6fdc91e Georgios D. Tsoukalas
from django.db import models, IntegrityError, transaction, connection
49 9a06d96f Olga Brani
from django.contrib.auth.models import User, UserManager, Group, Permission
50 0a569195 Sofia Papagiannaki
from django.utils.translation import ugettext as _
51 49790d9d Sofia Papagiannaki
from django.db import transaction
52 0a569195 Sofia Papagiannaki
from django.core.exceptions import ValidationError
53 c0b26605 Sofia Papagiannaki
from django.db.models.signals import (
54 73fbaec4 Sofia Papagiannaki
    pre_save, post_save, post_syncdb, post_delete)
55 9a06d96f Olga Brani
from django.contrib.contenttypes.models import ContentType
56 9a06d96f Olga Brani
57 fc1e2f02 Sofia Papagiannaki
from django.dispatch import Signal
58 e6759494 Sofia Papagiannaki
from django.db.models import Q
59 d2633501 Kostas Papadimitriou
from django.core.urlresolvers import reverse
60 d2633501 Kostas Papadimitriou
from django.utils.http import int_to_base36
61 d2633501 Kostas Papadimitriou
from django.contrib.auth.tokens import default_token_generator
62 8f8c43b2 Sofia Papagiannaki
from django.conf import settings
63 bf0c6de5 Sofia Papagiannaki
from django.utils.importlib import import_module
64 c4b1a172 Kostas Papadimitriou
from django.utils.safestring import mark_safe
65 d2633501 Kostas Papadimitriou
from django.core.validators import email_re
66 73fbaec4 Sofia Papagiannaki
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
67 64cd4730 Antony Chazapis
68 e1a80257 Sofia Papagiannaki
from astakos.im.settings import (
69 e1a80257 Sofia Papagiannaki
    DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
70 c7b82fdc Sofia Papagiannaki
    AUTH_TOKEN_DURATION, EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL,
71 b98e1df0 Sofia Papagiannaki
    SITENAME, SERVICES, MODERATION_ENABLED, RESOURCES_PRESENTATION_DATA,
72 b98e1df0 Sofia Papagiannaki
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES)
73 c4b1a172 Kostas Papadimitriou
from astakos.im import settings as astakos_settings
74 c0b26605 Sofia Papagiannaki
from astakos.im.endpoints.qh import (
75 5aac5dc4 Giorgos Korfiatis
    register_users, send_quotas, qh_check_users, qh_get_quota_limits,
76 0514bcc7 Giorgos Korfiatis
    register_services, register_resources, qh_add_quota, QuotaLimits,
77 0514bcc7 Giorgos Korfiatis
    qh_query_serials, qh_ack_serials,
78 0514bcc7 Giorgos Korfiatis
    QuotaValues, add_quota_values)
79 d2633501 Kostas Papadimitriou
from astakos.im import auth_providers
80 9c01d9d1 Sofia Papagiannaki
81 ae497612 Olga Brani
import astakos.im.messages as astakos_messages
82 ee45eb81 Giorgos Korfiatis
from .managers import ForUpdateManager
83 64cd4730 Antony Chazapis
84 c11dc0ce Giorgos Korfiatis
from synnefo.lib.quotaholder.api import QH_PRACTICALLY_INFINITE
85 c11dc0ce Giorgos Korfiatis
from synnefo.lib.db.intdecimalfield import intDecimalField
86 c11dc0ce Giorgos Korfiatis
87 18ffbee1 Sofia Papagiannaki
logger = logging.getLogger(__name__)
88 18ffbee1 Sofia Papagiannaki
89 9a06d96f Olga Brani
DEFAULT_CONTENT_TYPE = None
90 e65c21df Georgios D. Tsoukalas
_content_type = None
91 e65c21df Georgios D. Tsoukalas
92 e65c21df Georgios D. Tsoukalas
def get_content_type():
93 e65c21df Georgios D. Tsoukalas
    global _content_type
94 e65c21df Georgios D. Tsoukalas
    if _content_type is not None:
95 e65c21df Georgios D. Tsoukalas
        return _content_type
96 e65c21df Georgios D. Tsoukalas
97 e65c21df Georgios D. Tsoukalas
    try:
98 e65c21df Georgios D. Tsoukalas
        content_type = ContentType.objects.get(app_label='im', model='astakosuser')
99 e65c21df Georgios D. Tsoukalas
    except:
100 e65c21df Georgios D. Tsoukalas
        content_type = DEFAULT_CONTENT_TYPE
101 e65c21df Georgios D. Tsoukalas
    _content_type = content_type
102 e65c21df Georgios D. Tsoukalas
    return content_type
103 9a06d96f Olga Brani
104 9a06d96f Olga Brani
RESOURCE_SEPARATOR = '.'
105 9a06d96f Olga Brani
106 9ee0c6a2 Sofia Papagiannaki
inf = float('inf')
107 5ce3ce4f Sofia Papagiannaki
108 8e45d6fd Sofia Papagiannaki
class Service(models.Model):
109 e1a80257 Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
110 8e45d6fd Sofia Papagiannaki
    url = models.FilePathField()
111 8e45d6fd Sofia Papagiannaki
    icon = models.FilePathField(blank=True)
112 e1a80257 Sofia Papagiannaki
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
113 8e45d6fd Sofia Papagiannaki
                                  null=True, blank=True)
114 e1a80257 Sofia Papagiannaki
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
115 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
116 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
117 7795764b Kostas Papadimitriou
    order = models.PositiveIntegerField(default=0)
118 7795764b Kostas Papadimitriou
119 7795764b Kostas Papadimitriou
    class Meta:
120 7795764b Kostas Papadimitriou
        ordering = ('order', )
121 5ce3ce4f Sofia Papagiannaki
122 08494423 Sofia Papagiannaki
    def renew_token(self, expiration_date=None):
123 8e45d6fd Sofia Papagiannaki
        md5 = hashlib.md5()
124 8e45d6fd Sofia Papagiannaki
        md5.update(self.name.encode('ascii', 'ignore'))
125 8e45d6fd Sofia Papagiannaki
        md5.update(self.url.encode('ascii', 'ignore'))
126 8e45d6fd Sofia Papagiannaki
        md5.update(asctime())
127 8e45d6fd Sofia Papagiannaki
128 8e45d6fd Sofia Papagiannaki
        self.auth_token = b64encode(md5.digest())
129 8e45d6fd Sofia Papagiannaki
        self.auth_token_created = datetime.now()
130 08494423 Sofia Papagiannaki
        if expiration_date:
131 08494423 Sofia Papagiannaki
            self.auth_token_expires = expiration_date
132 08494423 Sofia Papagiannaki
        else:
133 08494423 Sofia Papagiannaki
            self.auth_token_expires = None
134 5ce3ce4f Sofia Papagiannaki
135 8e45d6fd Sofia Papagiannaki
    def __str__(self):
136 8e45d6fd Sofia Papagiannaki
        return self.name
137 8e45d6fd Sofia Papagiannaki
138 9a06d96f Olga Brani
    @property
139 9a06d96f Olga Brani
    def resources(self):
140 9a06d96f Olga Brani
        return self.resource_set.all()
141 9a06d96f Olga Brani
142 9a06d96f Olga Brani
    @resources.setter
143 9a06d96f Olga Brani
    def resources(self, resources):
144 9a06d96f Olga Brani
        for s in resources:
145 9a06d96f Olga Brani
            self.resource_set.create(**s)
146 2e90e3ec Kostas Papadimitriou
147 5ce3ce4f Sofia Papagiannaki
148 8e45d6fd Sofia Papagiannaki
class ResourceMetadata(models.Model):
149 e1a80257 Sofia Papagiannaki
    key = models.CharField(_('Name'), max_length=255, unique=True, db_index=True)
150 e1a80257 Sofia Papagiannaki
    value = models.CharField(_('Value'), max_length=255)
151 8e45d6fd Sofia Papagiannaki
152 8bc397e8 Sofia Papagiannaki
_presentation_data = {}
153 8bc397e8 Sofia Papagiannaki
def get_presentation(resource):
154 8bc397e8 Sofia Papagiannaki
    global _presentation_data
155 8bc397e8 Sofia Papagiannaki
    presentation = _presentation_data.get(resource, {})
156 8bc397e8 Sofia Papagiannaki
    if not presentation:
157 8bc397e8 Sofia Papagiannaki
        resource_presentation = RESOURCES_PRESENTATION_DATA.get('resources', {})
158 8bc397e8 Sofia Papagiannaki
        presentation = resource_presentation.get(resource, {})
159 8bc397e8 Sofia Papagiannaki
        _presentation_data[resource] = presentation
160 3c7528c9 Kostas Papadimitriou
    return presentation
161 5ce3ce4f Sofia Papagiannaki
162 8e45d6fd Sofia Papagiannaki
class Resource(models.Model):
163 43e09b6c Sofia Papagiannaki
    name = models.CharField(_('Name'), max_length=255)
164 8e45d6fd Sofia Papagiannaki
    meta = models.ManyToManyField(ResourceMetadata)
165 8e45d6fd Sofia Papagiannaki
    service = models.ForeignKey(Service)
166 e1a80257 Sofia Papagiannaki
    desc = models.TextField(_('Description'), null=True)
167 e1a80257 Sofia Papagiannaki
    unit = models.CharField(_('Name'), null=True, max_length=255)
168 e1a80257 Sofia Papagiannaki
    group = models.CharField(_('Group'), null=True, max_length=255)
169 0514bcc7 Giorgos Korfiatis
    uplimit = intDecimalField(default=0)
170 425e2e95 Sofia Papagiannaki
171 43e09b6c Sofia Papagiannaki
    class Meta:
172 0514bcc7 Giorgos Korfiatis
        unique_together = ("service", "name")
173 5ce3ce4f Sofia Papagiannaki
174 8e45d6fd Sofia Papagiannaki
    def __str__(self):
175 9a06d96f Olga Brani
        return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
176 8e45d6fd Sofia Papagiannaki
177 0514bcc7 Giorgos Korfiatis
    def full_name(self):
178 0514bcc7 Giorgos Korfiatis
        return str(self)
179 0514bcc7 Giorgos Korfiatis
180 8bc397e8 Sofia Papagiannaki
    @property
181 8bc397e8 Sofia Papagiannaki
    def help_text(self):
182 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('help_text', '')
183 3c7528c9 Kostas Papadimitriou
184 8bc397e8 Sofia Papagiannaki
    @property
185 8bc397e8 Sofia Papagiannaki
    def help_text_input_each(self):
186 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('help_text_input_each', '')
187 8bc397e8 Sofia Papagiannaki
188 8bc397e8 Sofia Papagiannaki
    @property
189 8bc397e8 Sofia Papagiannaki
    def is_abbreviation(self):
190 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('is_abbreviation', False)
191 8bc397e8 Sofia Papagiannaki
192 8bc397e8 Sofia Papagiannaki
    @property
193 8bc397e8 Sofia Papagiannaki
    def report_desc(self):
194 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('report_desc', '')
195 8bc397e8 Sofia Papagiannaki
196 8bc397e8 Sofia Papagiannaki
    @property
197 8bc397e8 Sofia Papagiannaki
    def placeholder(self):
198 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('placeholder', '')
199 8bc397e8 Sofia Papagiannaki
200 8bc397e8 Sofia Papagiannaki
    @property
201 8bc397e8 Sofia Papagiannaki
    def verbose_name(self):
202 8bc397e8 Sofia Papagiannaki
        return get_presentation(str(self)).get('verbose_name', '')
203 8bc397e8 Sofia Papagiannaki
204 b98e1df0 Sofia Papagiannaki
    @property
205 b98e1df0 Sofia Papagiannaki
    def display_name(self):
206 b98e1df0 Sofia Papagiannaki
        name = self.verbose_name
207 b98e1df0 Sofia Papagiannaki
        if self.is_abbreviation:
208 b98e1df0 Sofia Papagiannaki
            name = name.upper()
209 b98e1df0 Sofia Papagiannaki
        return name
210 b98e1df0 Sofia Papagiannaki
211 b98e1df0 Sofia Papagiannaki
    @property
212 b98e1df0 Sofia Papagiannaki
    def pluralized_display_name(self):
213 b98e1df0 Sofia Papagiannaki
        if not self.unit:
214 b98e1df0 Sofia Papagiannaki
            return '%ss' % self.display_name
215 b98e1df0 Sofia Papagiannaki
        return self.display_name
216 b98e1df0 Sofia Papagiannaki
217 8bc397e8 Sofia Papagiannaki
218 0514bcc7 Giorgos Korfiatis
def load_service_resources():
219 0514bcc7 Giorgos Korfiatis
    ss = []
220 0514bcc7 Giorgos Korfiatis
    rs = []
221 0514bcc7 Giorgos Korfiatis
    for service_name, data in SERVICES.iteritems():
222 0514bcc7 Giorgos Korfiatis
        url = data.get('url')
223 0514bcc7 Giorgos Korfiatis
        resources = data.get('resources') or ()
224 0514bcc7 Giorgos Korfiatis
        service, created = Service.objects.get_or_create(
225 0514bcc7 Giorgos Korfiatis
            name=service_name,
226 0514bcc7 Giorgos Korfiatis
            defaults={'url': url}
227 b4789608 Sofia Papagiannaki
        )
228 0514bcc7 Giorgos Korfiatis
        ss.append(service)
229 0514bcc7 Giorgos Korfiatis
230 0514bcc7 Giorgos Korfiatis
        for resource in resources:
231 0514bcc7 Giorgos Korfiatis
            try:
232 0514bcc7 Giorgos Korfiatis
                resource_name = resource.pop('name', '')
233 0514bcc7 Giorgos Korfiatis
                r, created = Resource.objects.get_or_create(
234 0514bcc7 Giorgos Korfiatis
                    service=service,
235 0514bcc7 Giorgos Korfiatis
                    name=resource_name,
236 0514bcc7 Giorgos Korfiatis
                    defaults=resource)
237 0514bcc7 Giorgos Korfiatis
                rs.append(r)
238 0514bcc7 Giorgos Korfiatis
239 0514bcc7 Giorgos Korfiatis
            except Exception, e:
240 0514bcc7 Giorgos Korfiatis
                print "Cannot create resource ", resource_name
241 0514bcc7 Giorgos Korfiatis
                continue
242 0514bcc7 Giorgos Korfiatis
    register_services(ss)
243 0514bcc7 Giorgos Korfiatis
    register_resources(rs)
244 0514bcc7 Giorgos Korfiatis
245 0514bcc7 Giorgos Korfiatis
def _quota_values(capacity):
246 0514bcc7 Giorgos Korfiatis
    return QuotaValues(
247 0514bcc7 Giorgos Korfiatis
        quantity = 0,
248 0514bcc7 Giorgos Korfiatis
        capacity = capacity,
249 0514bcc7 Giorgos Korfiatis
        import_limit = QH_PRACTICALLY_INFINITE,
250 0514bcc7 Giorgos Korfiatis
        export_limit = QH_PRACTICALLY_INFINITE)
251 0514bcc7 Giorgos Korfiatis
252 0514bcc7 Giorgos Korfiatis
def get_default_quota():
253 0514bcc7 Giorgos Korfiatis
    _DEFAULT_QUOTA = {}
254 0514bcc7 Giorgos Korfiatis
    resources = Resource.objects.all()
255 0514bcc7 Giorgos Korfiatis
    for resource in resources:
256 0514bcc7 Giorgos Korfiatis
        capacity = resource.uplimit
257 0514bcc7 Giorgos Korfiatis
        limits = _quota_values(capacity)
258 0514bcc7 Giorgos Korfiatis
        _DEFAULT_QUOTA[resource.full_name()] = limits
259 0514bcc7 Giorgos Korfiatis
260 0514bcc7 Giorgos Korfiatis
    return _DEFAULT_QUOTA
261 0514bcc7 Giorgos Korfiatis
262 0514bcc7 Giorgos Korfiatis
def get_resource_names():
263 0514bcc7 Giorgos Korfiatis
    _RESOURCE_NAMES = []
264 0514bcc7 Giorgos Korfiatis
    resources = Resource.objects.all()
265 0514bcc7 Giorgos Korfiatis
    _RESOURCE_NAMES = [resource.full_name() for resource in resources]
266 0514bcc7 Giorgos Korfiatis
    return _RESOURCE_NAMES
267 d2633501 Kostas Papadimitriou
268 43332a76 Kostas Papadimitriou
269 6b9a334b Sofia Papagiannaki
class AstakosUserManager(UserManager):
270 d2633501 Kostas Papadimitriou
271 d2633501 Kostas Papadimitriou
    def get_auth_provider_user(self, provider, **kwargs):
272 d2633501 Kostas Papadimitriou
        """
273 d2633501 Kostas Papadimitriou
        Retrieve AstakosUser instance associated with the specified third party
274 d2633501 Kostas Papadimitriou
        id.
275 d2633501 Kostas Papadimitriou
        """
276 d2633501 Kostas Papadimitriou
        kwargs = dict(map(lambda x: ('auth_providers__%s' % x[0], x[1]),
277 d2633501 Kostas Papadimitriou
                          kwargs.iteritems()))
278 d2633501 Kostas Papadimitriou
        return self.get(auth_providers__module=provider, **kwargs)
279 d2633501 Kostas Papadimitriou
280 c630fee6 Kostas Papadimitriou
    def get_by_email(self, email):
281 c630fee6 Kostas Papadimitriou
        return self.get(email=email)
282 c630fee6 Kostas Papadimitriou
283 e5966bd9 Kostas Papadimitriou
    def get_by_identifier(self, email_or_username, **kwargs):
284 e5966bd9 Kostas Papadimitriou
        try:
285 e5966bd9 Kostas Papadimitriou
            return self.get(email__iexact=email_or_username, **kwargs)
286 e5966bd9 Kostas Papadimitriou
        except AstakosUser.DoesNotExist:
287 e5966bd9 Kostas Papadimitriou
            return self.get(username__iexact=email_or_username, **kwargs)
288 e5966bd9 Kostas Papadimitriou
289 e5966bd9 Kostas Papadimitriou
    def user_exists(self, email_or_username, **kwargs):
290 e5966bd9 Kostas Papadimitriou
        qemail = Q(email__iexact=email_or_username)
291 e5966bd9 Kostas Papadimitriou
        qusername = Q(username__iexact=email_or_username)
292 43332a76 Kostas Papadimitriou
        qextra = Q(**kwargs)
293 43332a76 Kostas Papadimitriou
        return self.filter((qemail | qusername) & qextra).exists()
294 43332a76 Kostas Papadimitriou
295 43332a76 Kostas Papadimitriou
    def verified_user_exists(self, email_or_username):
296 43332a76 Kostas Papadimitriou
        return self.user_exists(email_or_username, email_verified=True)
297 43332a76 Kostas Papadimitriou
298 43332a76 Kostas Papadimitriou
    def verified(self):
299 43332a76 Kostas Papadimitriou
        return self.filter(email_verified=True)
300 e5966bd9 Kostas Papadimitriou
301 61a1b2d2 Georgios D. Tsoukalas
    def verified(self):
302 61a1b2d2 Georgios D. Tsoukalas
        return self.filter(email_verified=True)
303 e5966bd9 Kostas Papadimitriou
304 890c2065 Sofia Papagiannaki
    def uuid_catalog(self, l=None):
305 890c2065 Sofia Papagiannaki
        """
306 890c2065 Sofia Papagiannaki
        Returns a uuid to username mapping for the uuids appearing in l.
307 890c2065 Sofia Papagiannaki
        If l is None returns the mapping for all existing users.
308 890c2065 Sofia Papagiannaki
        """
309 890c2065 Sofia Papagiannaki
        q = self.filter(uuid__in=l) if l != None else self
310 890c2065 Sofia Papagiannaki
        return dict(q.values_list('uuid', 'username'))
311 890c2065 Sofia Papagiannaki
312 890c2065 Sofia Papagiannaki
    def displayname_catalog(self, l=None):
313 890c2065 Sofia Papagiannaki
        """
314 890c2065 Sofia Papagiannaki
        Returns a username to uuid mapping for the usernames appearing in l.
315 890c2065 Sofia Papagiannaki
        If l is None returns the mapping for all existing users.
316 890c2065 Sofia Papagiannaki
        """
317 890c2065 Sofia Papagiannaki
        q = self.filter(uuid__in=l) if l != None else self
318 890c2065 Sofia Papagiannaki
        q = self.filter(username__in=l) if l != None else self
319 890c2065 Sofia Papagiannaki
        return dict(q.values_list('username', 'uuid'))
320 890c2065 Sofia Papagiannaki
321 890c2065 Sofia Papagiannaki
322 e5966bd9 Kostas Papadimitriou
323 0905ccd2 Sofia Papagiannaki
class AstakosUser(User):
324 890b0eaf Sofia Papagiannaki
    """
325 890b0eaf Sofia Papagiannaki
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
326 890b0eaf Sofia Papagiannaki
    """
327 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
328 d2633501 Kostas Papadimitriou
                                   null=True)
329 d2633501 Kostas Papadimitriou
330 d2633501 Kostas Papadimitriou
    # DEPRECATED FIELDS: provider, third_party_identifier moved in
331 d2633501 Kostas Papadimitriou
    #                    AstakosUserProvider model.
332 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True,
333 d2633501 Kostas Papadimitriou
                                null=True)
334 d2633501 Kostas Papadimitriou
    # ex. screen_name for twitter, eppn for shibboleth
335 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'),
336 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
337 d2633501 Kostas Papadimitriou
                                              blank=True)
338 d2633501 Kostas Papadimitriou
339 6c736ed7 Kostas Papadimitriou
340 64cd4730 Antony Chazapis
    #for invitations
341 92defad4 Sofia Papagiannaki
    user_level = DEFAULT_USER_LEVEL
342 e1a80257 Sofia Papagiannaki
    level = models.IntegerField(_('Inviter level'), default=user_level)
343 5ce3ce4f Sofia Papagiannaki
    invitations = models.IntegerField(
344 e1a80257 Sofia Papagiannaki
        _('Invitations left'), default=INVITATIONS_PER_LEVEL.get(user_level, 0))
345 6c736ed7 Kostas Papadimitriou
346 e1a80257 Sofia Papagiannaki
    auth_token = models.CharField(_('Authentication Token'), max_length=32,
347 a96c495d Olga Brani
                                  null=True, blank=True, help_text = _( 'test' ))
348 e1a80257 Sofia Papagiannaki
    auth_token_created = models.DateTimeField(_('Token creation date'), null=True)
349 5ce3ce4f Sofia Papagiannaki
    auth_token_expires = models.DateTimeField(
350 e1a80257 Sofia Papagiannaki
        _('Token expiration date'), null=True)
351 6c736ed7 Kostas Papadimitriou
352 e1a80257 Sofia Papagiannaki
    updated = models.DateTimeField(_('Update date'))
353 e1a80257 Sofia Papagiannaki
    is_verified = models.BooleanField(_('Is verified?'), default=False)
354 6c736ed7 Kostas Papadimitriou
355 e1a80257 Sofia Papagiannaki
    email_verified = models.BooleanField(_('Email verified?'), default=False)
356 6c736ed7 Kostas Papadimitriou
357 e1a80257 Sofia Papagiannaki
    has_credits = models.BooleanField(_('Has credits?'), default=False)
358 5ce3ce4f Sofia Papagiannaki
    has_signed_terms = models.BooleanField(
359 e1a80257 Sofia Papagiannaki
        _('I agree with the terms'), default=False)
360 5ce3ce4f Sofia Papagiannaki
    date_signed_terms = models.DateTimeField(
361 e1a80257 Sofia Papagiannaki
        _('Signed terms date'), null=True, blank=True)
362 5ce3ce4f Sofia Papagiannaki
363 5ce3ce4f Sofia Papagiannaki
    activation_sent = models.DateTimeField(
364 e1a80257 Sofia Papagiannaki
        _('Activation sent data'), null=True, blank=True)
365 5ce3ce4f Sofia Papagiannaki
366 5ce3ce4f Sofia Papagiannaki
    policy = models.ManyToManyField(
367 5ce3ce4f Sofia Papagiannaki
        Resource, null=True, through='AstakosUserQuota')
368 5ce3ce4f Sofia Papagiannaki
369 836a0fb0 Kostas Papadimitriou
    uuid = models.CharField(max_length=255, null=True, blank=False, unique=True)
370 836a0fb0 Kostas Papadimitriou
371 18ffbee1 Sofia Papagiannaki
    __has_signed_terms = False
372 e1a80257 Sofia Papagiannaki
    disturbed_quota = models.BooleanField(_('Needs quotaholder syncing'),
373 9a06d96f Olga Brani
                                           default=False, db_index=True)
374 d2633501 Kostas Papadimitriou
375 d2633501 Kostas Papadimitriou
    objects = AstakosUserManager()
376 fbaa4f3c Kostas Papadimitriou
377 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
378 18ffbee1 Sofia Papagiannaki
        super(AstakosUser, self).__init__(*args, **kwargs)
379 18ffbee1 Sofia Papagiannaki
        self.__has_signed_terms = self.has_signed_terms
380 a3637508 Sofia Papagiannaki
        if not self.id:
381 18ffbee1 Sofia Papagiannaki
            self.is_active = False
382 5ce3ce4f Sofia Papagiannaki
383 0905ccd2 Sofia Papagiannaki
    @property
384 0905ccd2 Sofia Papagiannaki
    def realname(self):
385 5ce3ce4f Sofia Papagiannaki
        return '%s %s' % (self.first_name, self.last_name)
386 6c736ed7 Kostas Papadimitriou
387 0905ccd2 Sofia Papagiannaki
    @realname.setter
388 0905ccd2 Sofia Papagiannaki
    def realname(self, value):
389 0905ccd2 Sofia Papagiannaki
        parts = value.split(' ')
390 0905ccd2 Sofia Papagiannaki
        if len(parts) == 2:
391 0905ccd2 Sofia Papagiannaki
            self.first_name = parts[0]
392 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[1]
393 0905ccd2 Sofia Papagiannaki
        else:
394 0905ccd2 Sofia Papagiannaki
            self.last_name = parts[0]
395 6c736ed7 Kostas Papadimitriou
396 9a06d96f Olga Brani
    def add_permission(self, pname):
397 9a06d96f Olga Brani
        if self.has_perm(pname):
398 9a06d96f Olga Brani
            return
399 e65c21df Georgios D. Tsoukalas
        p, created = Permission.objects.get_or_create(
400 e65c21df Georgios D. Tsoukalas
                                    codename=pname,
401 e65c21df Georgios D. Tsoukalas
                                    name=pname.capitalize(),
402 e65c21df Georgios D. Tsoukalas
                                    content_type=get_content_type())
403 9a06d96f Olga Brani
        self.user_permissions.add(p)
404 9a06d96f Olga Brani
405 9a06d96f Olga Brani
    def remove_permission(self, pname):
406 9a06d96f Olga Brani
        if self.has_perm(pname):
407 9a06d96f Olga Brani
            return
408 9a06d96f Olga Brani
        p = Permission.objects.get(codename=pname,
409 e65c21df Georgios D. Tsoukalas
                                   content_type=get_content_type())
410 9a06d96f Olga Brani
        self.user_permissions.remove(p)
411 9a06d96f Olga Brani
412 64cd4730 Antony Chazapis
    @property
413 64cd4730 Antony Chazapis
    def invitation(self):
414 64cd4730 Antony Chazapis
        try:
415 9fb8e808 Sofia Papagiannaki
            return Invitation.objects.get(username=self.email)
416 64cd4730 Antony Chazapis
        except Invitation.DoesNotExist:
417 64cd4730 Antony Chazapis
            return None
418 6c736ed7 Kostas Papadimitriou
419 0514bcc7 Giorgos Korfiatis
    def initial_quotas(self):
420 0514bcc7 Giorgos Korfiatis
        quotas = dict(get_default_quota())
421 0514bcc7 Giorgos Korfiatis
        for user_quota in self.policies:
422 0514bcc7 Giorgos Korfiatis
            resource = user_quota.resource.full_name()
423 0514bcc7 Giorgos Korfiatis
            quotas[resource] = user_quota.quota_values()
424 0514bcc7 Giorgos Korfiatis
        return quotas
425 0514bcc7 Giorgos Korfiatis
426 0514bcc7 Giorgos Korfiatis
    def all_quotas(self):
427 0514bcc7 Giorgos Korfiatis
        quotas = self.initial_quotas()
428 0514bcc7 Giorgos Korfiatis
429 0514bcc7 Giorgos Korfiatis
        objects = self.projectmembership_set.select_related()
430 0514bcc7 Giorgos Korfiatis
        memberships = objects.filter(is_active=True)
431 0514bcc7 Giorgos Korfiatis
        for membership in memberships:
432 0514bcc7 Giorgos Korfiatis
            application = membership.application
433 e5bc7bc9 Giorgos Korfiatis
            if application is None:
434 e5bc7bc9 Giorgos Korfiatis
                m = _("missing application for active membership %s"
435 e5bc7bc9 Giorgos Korfiatis
                      % (membership,))
436 e5bc7bc9 Giorgos Korfiatis
                raise AssertionError(m)
437 0514bcc7 Giorgos Korfiatis
438 0514bcc7 Giorgos Korfiatis
            grants = application.projectresourcegrant_set.all()
439 0514bcc7 Giorgos Korfiatis
            for grant in grants:
440 0514bcc7 Giorgos Korfiatis
                resource = grant.resource.full_name()
441 0514bcc7 Giorgos Korfiatis
                prev = quotas.get(resource, 0)
442 0514bcc7 Giorgos Korfiatis
                new = add_quota_values(prev, grant.member_quota_values())
443 0514bcc7 Giorgos Korfiatis
                quotas[resource] = new
444 0514bcc7 Giorgos Korfiatis
        return quotas
445 5ce3ce4f Sofia Papagiannaki
446 9a06d96f Olga Brani
    @property
447 9a06d96f Olga Brani
    def policies(self):
448 9a06d96f Olga Brani
        return self.astakosuserquota_set.select_related().all()
449 9a06d96f Olga Brani
450 9a06d96f Olga Brani
    @policies.setter
451 9a06d96f Olga Brani
    def policies(self, policies):
452 9a06d96f Olga Brani
        for p in policies:
453 6c997921 Sofia Papagiannaki
            p.setdefault('resource', '')
454 6c997921 Sofia Papagiannaki
            p.setdefault('capacity', 0)
455 6c997921 Sofia Papagiannaki
            p.setdefault('quantity', 0)
456 6c997921 Sofia Papagiannaki
            p.setdefault('import_limit', 0)
457 6c997921 Sofia Papagiannaki
            p.setdefault('export_limit', 0)
458 6c997921 Sofia Papagiannaki
            p.setdefault('update', True)
459 6c997921 Sofia Papagiannaki
            self.add_resource_policy(**p)
460 6c997921 Sofia Papagiannaki
461 6c997921 Sofia Papagiannaki
    def add_resource_policy(
462 6c997921 Sofia Papagiannaki
            self, resource, capacity, quantity, import_limit,
463 6c997921 Sofia Papagiannaki
            export_limit, update=True):
464 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
465 6c997921 Sofia Papagiannaki
        s, sep, r = resource.partition(RESOURCE_SEPARATOR)
466 6c997921 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=s, name=r)
467 9a06d96f Olga Brani
        if update:
468 6c997921 Sofia Papagiannaki
            AstakosUserQuota.objects.update_or_create(
469 6c997921 Sofia Papagiannaki
                user=self, resource=resource, defaults={
470 6c997921 Sofia Papagiannaki
                    'capacity':capacity,
471 6c997921 Sofia Papagiannaki
                    'quantity': quantity,
472 6c997921 Sofia Papagiannaki
                    'import_limit':import_limit,
473 6c997921 Sofia Papagiannaki
                    'export_limit':export_limit})
474 9a06d96f Olga Brani
        else:
475 9a06d96f Olga Brani
            q = self.astakosuserquota_set
476 6c997921 Sofia Papagiannaki
            q.create(
477 6c997921 Sofia Papagiannaki
                resource=resource, capacity=capacity, quanity=quantity,
478 6c997921 Sofia Papagiannaki
                import_limit=import_limit, export_limit=export_limit)
479 9a06d96f Olga Brani
480 6c997921 Sofia Papagiannaki
    def remove_resource_policy(self, service, resource):
481 9a06d96f Olga Brani
        """Raises ObjectDoesNotExist, IntegrityError"""
482 9a06d96f Olga Brani
        resource = Resource.objects.get(service__name=service, name=resource)
483 9a06d96f Olga Brani
        q = self.policies.get(resource=resource).delete()
484 9a06d96f Olga Brani
485 836a0fb0 Kostas Papadimitriou
    def update_uuid(self):
486 836a0fb0 Kostas Papadimitriou
        while not self.uuid:
487 836a0fb0 Kostas Papadimitriou
            uuid_val =  str(uuid.uuid4())
488 836a0fb0 Kostas Papadimitriou
            try:
489 836a0fb0 Kostas Papadimitriou
                AstakosUser.objects.get(uuid=uuid_val)
490 836a0fb0 Kostas Papadimitriou
            except AstakosUser.DoesNotExist, e:
491 836a0fb0 Kostas Papadimitriou
                self.uuid = uuid_val
492 836a0fb0 Kostas Papadimitriou
        return self.uuid
493 836a0fb0 Kostas Papadimitriou
494 64cd4730 Antony Chazapis
    def save(self, update_timestamps=True, **kwargs):
495 64cd4730 Antony Chazapis
        if update_timestamps:
496 64cd4730 Antony Chazapis
            if not self.id:
497 0905ccd2 Sofia Papagiannaki
                self.date_joined = datetime.now()
498 64cd4730 Antony Chazapis
            self.updated = datetime.now()
499 5ce3ce4f Sofia Papagiannaki
500 18ffbee1 Sofia Papagiannaki
        # update date_signed_terms if necessary
501 18ffbee1 Sofia Papagiannaki
        if self.__has_signed_terms != self.has_signed_terms:
502 18ffbee1 Sofia Papagiannaki
            self.date_signed_terms = datetime.now()
503 5ce3ce4f Sofia Papagiannaki
504 836a0fb0 Kostas Papadimitriou
        self.update_uuid()
505 836a0fb0 Kostas Papadimitriou
506 836a0fb0 Kostas Papadimitriou
        if self.username != self.email.lower():
507 9c01d9d1 Sofia Papagiannaki
            # set username
508 e5966bd9 Kostas Papadimitriou
            self.username = self.email.lower()
509 fbaa4f3c Kostas Papadimitriou
510 0905ccd2 Sofia Papagiannaki
        super(AstakosUser, self).save(**kwargs)
511 2e90e3ec Kostas Papadimitriou
512 bf0c6de5 Sofia Papagiannaki
    def renew_token(self, flush_sessions=False, current_key=None):
513 64cd4730 Antony Chazapis
        md5 = hashlib.md5()
514 8f8c43b2 Sofia Papagiannaki
        md5.update(settings.SECRET_KEY)
515 0905ccd2 Sofia Papagiannaki
        md5.update(self.username)
516 64cd4730 Antony Chazapis
        md5.update(self.realname.encode('ascii', 'ignore'))
517 64cd4730 Antony Chazapis
        md5.update(asctime())
518 2e90e3ec Kostas Papadimitriou
519 64cd4730 Antony Chazapis
        self.auth_token = b64encode(md5.digest())
520 64cd4730 Antony Chazapis
        self.auth_token_created = datetime.now()
521 64cd4730 Antony Chazapis
        self.auth_token_expires = self.auth_token_created + \
522 92defad4 Sofia Papagiannaki
                                  timedelta(hours=AUTH_TOKEN_DURATION)
523 bf0c6de5 Sofia Papagiannaki
        if flush_sessions:
524 bf0c6de5 Sofia Papagiannaki
            self.flush_sessions(current_key)
525 111f3da6 Sofia Papagiannaki
        msg = 'Token renewed for %s' % self.email
526 aab4d540 Sofia Papagiannaki
        logger.log(LOGGING_LEVEL, msg)
527 6c736ed7 Kostas Papadimitriou
528 bf0c6de5 Sofia Papagiannaki
    def flush_sessions(self, current_key=None):
529 bf0c6de5 Sofia Papagiannaki
        q = self.sessions
530 bf0c6de5 Sofia Papagiannaki
        if current_key:
531 bf0c6de5 Sofia Papagiannaki
            q = q.exclude(session_key=current_key)
532 2e90e3ec Kostas Papadimitriou
533 bf0c6de5 Sofia Papagiannaki
        keys = q.values_list('session_key', flat=True)
534 bf0c6de5 Sofia Papagiannaki
        if keys:
535 bf0c6de5 Sofia Papagiannaki
            msg = 'Flushing sessions: %s' % ','.join(keys)
536 c0b26605 Sofia Papagiannaki
            logger.log(LOGGING_LEVEL, msg, [])
537 bf0c6de5 Sofia Papagiannaki
        engine = import_module(settings.SESSION_ENGINE)
538 bf0c6de5 Sofia Papagiannaki
        for k in keys:
539 bf0c6de5 Sofia Papagiannaki
            s = engine.SessionStore(k)
540 bf0c6de5 Sofia Papagiannaki
            s.flush()
541 bf0c6de5 Sofia Papagiannaki
542 64cd4730 Antony Chazapis
    def __unicode__(self):
543 3abf6c78 Sofia Papagiannaki
        return '%s (%s)' % (self.realname, self.email)
544 5ce3ce4f Sofia Papagiannaki
545 0a569195 Sofia Papagiannaki
    def conflicting_email(self):
546 5ce3ce4f Sofia Papagiannaki
        q = AstakosUser.objects.exclude(username=self.username)
547 789a5951 Sofia Papagiannaki
        q = q.filter(email__iexact=self.email)
548 0a569195 Sofia Papagiannaki
        if q.count() != 0:
549 0a569195 Sofia Papagiannaki
            return True
550 0a569195 Sofia Papagiannaki
        return False
551 5ce3ce4f Sofia Papagiannaki
552 34a76cdb Kostas Papadimitriou
    def email_change_is_pending(self):
553 34a76cdb Kostas Papadimitriou
        return self.emailchanges.count() > 0
554 34a76cdb Kostas Papadimitriou
555 0712a55c Kostas Papadimitriou
    def email_change_is_pending(self):
556 0712a55c Kostas Papadimitriou
        return self.emailchanges.count() > 0
557 0712a55c Kostas Papadimitriou
558 fcf90160 Sofia Papagiannaki
    @property
559 09e7393c Sofia Papagiannaki
    def signed_terms(self):
560 09e7393c Sofia Papagiannaki
        term = get_latest_terms()
561 09e7393c Sofia Papagiannaki
        if not term:
562 09e7393c Sofia Papagiannaki
            return True
563 09e7393c Sofia Papagiannaki
        if not self.has_signed_terms:
564 09e7393c Sofia Papagiannaki
            return False
565 09e7393c Sofia Papagiannaki
        if not self.date_signed_terms:
566 09e7393c Sofia Papagiannaki
            return False
567 09e7393c Sofia Papagiannaki
        if self.date_signed_terms < term.date:
568 09e7393c Sofia Papagiannaki
            self.has_signed_terms = False
569 f0f92965 Sofia Papagiannaki
            self.date_signed_terms = None
570 09e7393c Sofia Papagiannaki
            self.save()
571 09e7393c Sofia Papagiannaki
            return False
572 09e7393c Sofia Papagiannaki
        return True
573 09e7393c Sofia Papagiannaki
574 d2633501 Kostas Papadimitriou
    def set_invitations_level(self):
575 d2633501 Kostas Papadimitriou
        """
576 d2633501 Kostas Papadimitriou
        Update user invitation level
577 d2633501 Kostas Papadimitriou
        """
578 d2633501 Kostas Papadimitriou
        level = self.invitation.inviter.level + 1
579 d2633501 Kostas Papadimitriou
        self.level = level
580 d2633501 Kostas Papadimitriou
        self.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
581 d2633501 Kostas Papadimitriou
582 d2633501 Kostas Papadimitriou
    def can_login_with_auth_provider(self, provider):
583 d2633501 Kostas Papadimitriou
        if not self.has_auth_provider(provider):
584 d2633501 Kostas Papadimitriou
            return False
585 d2633501 Kostas Papadimitriou
        else:
586 d2633501 Kostas Papadimitriou
            return auth_providers.get_provider(provider).is_available_for_login()
587 d2633501 Kostas Papadimitriou
588 8eb36753 Kostas Papadimitriou
    def can_add_auth_provider(self, provider, include_unverified=False, **kwargs):
589 d2633501 Kostas Papadimitriou
        provider_settings = auth_providers.get_provider(provider)
590 222d8e52 Kostas Papadimitriou
591 222d8e52 Kostas Papadimitriou
        if not provider_settings.is_available_for_add():
592 d2633501 Kostas Papadimitriou
            return False
593 f432088a Kostas Papadimitriou
594 d2633501 Kostas Papadimitriou
        if self.has_auth_provider(provider) and \
595 d2633501 Kostas Papadimitriou
           provider_settings.one_per_user:
596 d2633501 Kostas Papadimitriou
            return False
597 f432088a Kostas Papadimitriou
598 c630fee6 Kostas Papadimitriou
        if 'provider_info' in kwargs:
599 c630fee6 Kostas Papadimitriou
            kwargs.pop('provider_info')
600 c630fee6 Kostas Papadimitriou
601 f432088a Kostas Papadimitriou
        if 'identifier' in kwargs:
602 f432088a Kostas Papadimitriou
            try:
603 f432088a Kostas Papadimitriou
                # provider with specified params already exist
604 8eb36753 Kostas Papadimitriou
                if not include_unverified:
605 8eb36753 Kostas Papadimitriou
                    kwargs['user__email_verified'] = True
606 f432088a Kostas Papadimitriou
                existing_user = AstakosUser.objects.get_auth_provider_user(provider,
607 f432088a Kostas Papadimitriou
                                                                   **kwargs)
608 f432088a Kostas Papadimitriou
            except AstakosUser.DoesNotExist:
609 f432088a Kostas Papadimitriou
                return True
610 f432088a Kostas Papadimitriou
            else:
611 f432088a Kostas Papadimitriou
                return False
612 f432088a Kostas Papadimitriou
613 d2633501 Kostas Papadimitriou
        return True
614 d2633501 Kostas Papadimitriou
615 63836eda Kostas Papadimitriou
    def can_remove_auth_provider(self, module):
616 63836eda Kostas Papadimitriou
        provider = auth_providers.get_provider(module)
617 63836eda Kostas Papadimitriou
        existing = self.get_active_auth_providers()
618 63836eda Kostas Papadimitriou
        existing_for_provider = self.get_active_auth_providers(module=module)
619 63836eda Kostas Papadimitriou
620 63836eda Kostas Papadimitriou
        if len(existing) <= 1:
621 63836eda Kostas Papadimitriou
            return False
622 63836eda Kostas Papadimitriou
623 63836eda Kostas Papadimitriou
        if len(existing_for_provider) == 1 and provider.is_required():
624 d2633501 Kostas Papadimitriou
            return False
625 63836eda Kostas Papadimitriou
626 d2633501 Kostas Papadimitriou
        return True
627 d2633501 Kostas Papadimitriou
628 d2633501 Kostas Papadimitriou
    def can_change_password(self):
629 d2633501 Kostas Papadimitriou
        return self.has_auth_provider('local', auth_backend='astakos')
630 d2633501 Kostas Papadimitriou
631 649f2d36 Kostas Papadimitriou
    def can_change_email(self):
632 649f2d36 Kostas Papadimitriou
        non_astakos_local = self.get_auth_providers().filter(module='local')
633 649f2d36 Kostas Papadimitriou
        non_astakos_local = non_astakos_local.exclude(auth_backend='astakos')
634 649f2d36 Kostas Papadimitriou
        return non_astakos_local.count() == 0
635 649f2d36 Kostas Papadimitriou
636 63836eda Kostas Papadimitriou
    def has_required_auth_providers(self):
637 63836eda Kostas Papadimitriou
        required = auth_providers.REQUIRED_PROVIDERS
638 63836eda Kostas Papadimitriou
        for provider in required:
639 63836eda Kostas Papadimitriou
            if not self.has_auth_provider(provider):
640 63836eda Kostas Papadimitriou
                return False
641 63836eda Kostas Papadimitriou
        return True
642 63836eda Kostas Papadimitriou
643 d2633501 Kostas Papadimitriou
    def has_auth_provider(self, provider, **kwargs):
644 4d0bdd3b Kostas Papadimitriou
        return bool(self.get_auth_providers().filter(module=provider,
645 d2633501 Kostas Papadimitriou
                                               **kwargs).count())
646 d2633501 Kostas Papadimitriou
647 d2633501 Kostas Papadimitriou
    def add_auth_provider(self, provider, **kwargs):
648 3a72a5d4 Kostas Papadimitriou
        info_data = ''
649 3a72a5d4 Kostas Papadimitriou
        if 'provider_info' in kwargs:
650 c630fee6 Kostas Papadimitriou
            info_data = kwargs.pop('provider_info')
651 c630fee6 Kostas Papadimitriou
            if isinstance(info_data, dict):
652 c630fee6 Kostas Papadimitriou
                info_data = json.dumps(info_data)
653 3a72a5d4 Kostas Papadimitriou
654 f432088a Kostas Papadimitriou
        if self.can_add_auth_provider(provider, **kwargs):
655 8eb36753 Kostas Papadimitriou
            if 'identifier' in kwargs:
656 8eb36753 Kostas Papadimitriou
                # clean up third party pending for activation users of the same
657 8eb36753 Kostas Papadimitriou
                # identifier
658 8eb36753 Kostas Papadimitriou
                AstakosUserAuthProvider.objects.remove_unverified_providers(provider,
659 564a2292 Kostas Papadimitriou
                                                                **kwargs)
660 3a72a5d4 Kostas Papadimitriou
            self.auth_providers.create(module=provider, active=True,
661 3a72a5d4 Kostas Papadimitriou
                                       info_data=info_data,
662 3a72a5d4 Kostas Papadimitriou
                                       **kwargs)
663 f432088a Kostas Papadimitriou
        else:
664 f432088a Kostas Papadimitriou
            raise Exception('Cannot add provider')
665 d2633501 Kostas Papadimitriou
666 d2633501 Kostas Papadimitriou
    def add_pending_auth_provider(self, pending):
667 d2633501 Kostas Papadimitriou
        """
668 d2633501 Kostas Papadimitriou
        Convert PendingThirdPartyUser object to AstakosUserAuthProvider entry for
669 d2633501 Kostas Papadimitriou
        the current user.
670 d2633501 Kostas Papadimitriou
        """
671 d2633501 Kostas Papadimitriou
        if not isinstance(pending, PendingThirdPartyUser):
672 d2633501 Kostas Papadimitriou
            pending = PendingThirdPartyUser.objects.get(token=pending)
673 d2633501 Kostas Papadimitriou
674 d2633501 Kostas Papadimitriou
        provider = self.add_auth_provider(pending.provider,
675 c630fee6 Kostas Papadimitriou
                               identifier=pending.third_party_identifier,
676 c630fee6 Kostas Papadimitriou
                                affiliation=pending.affiliation,
677 c630fee6 Kostas Papadimitriou
                                          provider_info=pending.info)
678 d2633501 Kostas Papadimitriou
679 fbaa4f3c Kostas Papadimitriou
        if email_re.match(pending.email or '') and pending.email != self.email:
680 d2633501 Kostas Papadimitriou
            self.additionalmail_set.get_or_create(email=pending.email)
681 d2633501 Kostas Papadimitriou
682 d2633501 Kostas Papadimitriou
        pending.delete()
683 d2633501 Kostas Papadimitriou
        return provider
684 d2633501 Kostas Papadimitriou
685 d2633501 Kostas Papadimitriou
    def remove_auth_provider(self, provider, **kwargs):
686 4d0bdd3b Kostas Papadimitriou
        self.get_auth_providers().get(module=provider, **kwargs).delete()
687 d2633501 Kostas Papadimitriou
688 d2633501 Kostas Papadimitriou
    # user urls
689 d2633501 Kostas Papadimitriou
    def get_resend_activation_url(self):
690 c630fee6 Kostas Papadimitriou
        return reverse('send_activation', kwargs={'user_id': self.pk})
691 c630fee6 Kostas Papadimitriou
692 c630fee6 Kostas Papadimitriou
    def get_provider_remove_url(self, module, **kwargs):
693 c630fee6 Kostas Papadimitriou
        return reverse('remove_auth_provider', kwargs={
694 4d0bdd3b Kostas Papadimitriou
            'pk': self.get_auth_providers().get(module=module, **kwargs).pk})
695 d2633501 Kostas Papadimitriou
696 d2633501 Kostas Papadimitriou
    def get_activation_url(self, nxt=False):
697 d2633501 Kostas Papadimitriou
        url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
698 d2633501 Kostas Papadimitriou
                                 quote(self.auth_token))
699 d2633501 Kostas Papadimitriou
        if nxt:
700 d2633501 Kostas Papadimitriou
            url += "&next=%s" % quote(nxt)
701 d2633501 Kostas Papadimitriou
        return url
702 d2633501 Kostas Papadimitriou
703 d2633501 Kostas Papadimitriou
    def get_password_reset_url(self, token_generator=default_token_generator):
704 d2633501 Kostas Papadimitriou
        return reverse('django.contrib.auth.views.password_reset_confirm',
705 d2633501 Kostas Papadimitriou
                          kwargs={'uidb36':int_to_base36(self.id),
706 d2633501 Kostas Papadimitriou
                                  'token':token_generator.make_token(self)})
707 d2633501 Kostas Papadimitriou
708 d2633501 Kostas Papadimitriou
    def get_auth_providers(self):
709 4d0bdd3b Kostas Papadimitriou
        return self.auth_providers
710 d2633501 Kostas Papadimitriou
711 d2633501 Kostas Papadimitriou
    def get_available_auth_providers(self):
712 d2633501 Kostas Papadimitriou
        """
713 d2633501 Kostas Papadimitriou
        Returns a list of providers available for user to connect to.
714 d2633501 Kostas Papadimitriou
        """
715 d2633501 Kostas Papadimitriou
        providers = []
716 d2633501 Kostas Papadimitriou
        for module, provider_settings in auth_providers.PROVIDERS.iteritems():
717 f432088a Kostas Papadimitriou
            if self.can_add_auth_provider(module):
718 d2633501 Kostas Papadimitriou
                providers.append(provider_settings(self))
719 d2633501 Kostas Papadimitriou
720 d2633501 Kostas Papadimitriou
        return providers
721 d2633501 Kostas Papadimitriou
722 63836eda Kostas Papadimitriou
    def get_active_auth_providers(self, **filters):
723 d2633501 Kostas Papadimitriou
        providers = []
724 4d0bdd3b Kostas Papadimitriou
        for provider in self.get_auth_providers().active(**filters):
725 d2633501 Kostas Papadimitriou
            if auth_providers.get_provider(provider.module).is_available_for_login():
726 d2633501 Kostas Papadimitriou
                providers.append(provider)
727 d2633501 Kostas Papadimitriou
        return providers
728 d2633501 Kostas Papadimitriou
729 b778b6fa Kostas Papadimitriou
    @property
730 b778b6fa Kostas Papadimitriou
    def auth_providers_display(self):
731 4d0bdd3b Kostas Papadimitriou
        return ",".join(map(lambda x:unicode(x), self.get_auth_providers().active()))
732 b778b6fa Kostas Papadimitriou
733 c4b1a172 Kostas Papadimitriou
    def get_inactive_message(self):
734 c4b1a172 Kostas Papadimitriou
        msg_extra = ''
735 c4b1a172 Kostas Papadimitriou
        message = ''
736 c4b1a172 Kostas Papadimitriou
        if self.activation_sent:
737 c4b1a172 Kostas Papadimitriou
            if self.email_verified:
738 c4b1a172 Kostas Papadimitriou
                message = _(astakos_messages.ACCOUNT_INACTIVE)
739 c4b1a172 Kostas Papadimitriou
            else:
740 c4b1a172 Kostas Papadimitriou
                message = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION)
741 fc655b6f Kostas Papadimitriou
                if astakos_settings.MODERATION_ENABLED:
742 c4b1a172 Kostas Papadimitriou
                    msg_extra = _(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP)
743 c4b1a172 Kostas Papadimitriou
                else:
744 c4b1a172 Kostas Papadimitriou
                    url = self.get_resend_activation_url()
745 c4b1a172 Kostas Papadimitriou
                    msg_extra = mark_safe(_(astakos_messages.ACCOUNT_PENDING_ACTIVATION_HELP) + \
746 a15a19b2 Kostas Papadimitriou
                                u' ' + \
747 c4b1a172 Kostas Papadimitriou
                                _('<a href="%s">%s?</a>') % (url,
748 c4b1a172 Kostas Papadimitriou
                                _(astakos_messages.ACCOUNT_RESEND_ACTIVATION_PROMPT)))
749 c4b1a172 Kostas Papadimitriou
        else:
750 fc655b6f Kostas Papadimitriou
            if astakos_settings.MODERATION_ENABLED:
751 c4b1a172 Kostas Papadimitriou
                message = _(astakos_messages.ACCOUNT_PENDING_MODERATION)
752 c4b1a172 Kostas Papadimitriou
            else:
753 c4b1a172 Kostas Papadimitriou
                message = astakos_messages.ACCOUNT_PENDING_ACTIVATION
754 c4b1a172 Kostas Papadimitriou
                url = self.get_resend_activation_url()
755 c4b1a172 Kostas Papadimitriou
                msg_extra = mark_safe(_('<a href="%s">%s?</a>') % (url,
756 c4b1a172 Kostas Papadimitriou
                            _(astakos_messages.ACCOUNT_RESEND_ACTIVATION_PROMPT)))
757 c4b1a172 Kostas Papadimitriou
758 a15a19b2 Kostas Papadimitriou
        return mark_safe(message + u' '+ msg_extra)
759 c4b1a172 Kostas Papadimitriou
760 7184f408 Giorgos Korfiatis
    def owns_application(self, application):
761 7184f408 Giorgos Korfiatis
        return application.owner == self
762 7184f408 Giorgos Korfiatis
763 e87bbb41 Kostas Papadimitriou
    def owns_project(self, project):
764 7184f408 Giorgos Korfiatis
        return project.application.owner == self
765 e87bbb41 Kostas Papadimitriou
766 05617ab9 Kostas Papadimitriou
    def is_project_member(self, project_or_application):
767 05617ab9 Kostas Papadimitriou
        return self.get_status_in_project(project_or_application) in \
768 05617ab9 Kostas Papadimitriou
                                        ProjectMembership.ASSOCIATED_STATES
769 e87bbb41 Kostas Papadimitriou
770 05617ab9 Kostas Papadimitriou
    def is_project_accepted_member(self, project_or_application):
771 05617ab9 Kostas Papadimitriou
        return self.get_status_in_project(project_or_application) in \
772 05617ab9 Kostas Papadimitriou
                                            ProjectMembership.ACCEPTED_STATES
773 05617ab9 Kostas Papadimitriou
774 05617ab9 Kostas Papadimitriou
    def get_status_in_project(self, project_or_application):
775 05617ab9 Kostas Papadimitriou
        application = project_or_application
776 05617ab9 Kostas Papadimitriou
        if isinstance(project_or_application, Project):
777 05617ab9 Kostas Papadimitriou
            application = project_or_application.project
778 05617ab9 Kostas Papadimitriou
        return application.user_status(self)
779 480ce06b Sofia Papagiannaki
780 aad0e329 Giorgos Korfiatis
    def get_membership(self, project):
781 aad0e329 Giorgos Korfiatis
        try:
782 aad0e329 Giorgos Korfiatis
            return ProjectMembership.objects.get(
783 aad0e329 Giorgos Korfiatis
                project=project,
784 aad0e329 Giorgos Korfiatis
                person=self)
785 aad0e329 Giorgos Korfiatis
        except ProjectMembership.DoesNotExist:
786 aad0e329 Giorgos Korfiatis
            return None
787 d2633501 Kostas Papadimitriou
788 d2633501 Kostas Papadimitriou
class AstakosUserAuthProviderManager(models.Manager):
789 d2633501 Kostas Papadimitriou
790 63836eda Kostas Papadimitriou
    def active(self, **filters):
791 63836eda Kostas Papadimitriou
        return self.filter(active=True, **filters)
792 d2633501 Kostas Papadimitriou
793 564a2292 Kostas Papadimitriou
    def remove_unverified_providers(self, provider, **filters):
794 564a2292 Kostas Papadimitriou
        try:
795 564a2292 Kostas Papadimitriou
            existing = self.filter(module=provider, user__email_verified=False, **filters)
796 564a2292 Kostas Papadimitriou
            for p in existing:
797 564a2292 Kostas Papadimitriou
                p.user.delete()
798 564a2292 Kostas Papadimitriou
        except:
799 564a2292 Kostas Papadimitriou
            pass
800 564a2292 Kostas Papadimitriou
801 564a2292 Kostas Papadimitriou
802 d2633501 Kostas Papadimitriou
803 d2633501 Kostas Papadimitriou
class AstakosUserAuthProvider(models.Model):
804 d2633501 Kostas Papadimitriou
    """
805 d2633501 Kostas Papadimitriou
    Available user authentication methods.
806 d2633501 Kostas Papadimitriou
    """
807 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
808 d2633501 Kostas Papadimitriou
                                   null=True, default=None)
809 d2633501 Kostas Papadimitriou
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
810 e1a80257 Sofia Papagiannaki
    module = models.CharField(_('Provider'), max_length=255, blank=False,
811 d2633501 Kostas Papadimitriou
                                default='local')
812 e1a80257 Sofia Papagiannaki
    identifier = models.CharField(_('Third-party identifier'),
813 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
814 d2633501 Kostas Papadimitriou
                                              blank=True)
815 d2633501 Kostas Papadimitriou
    active = models.BooleanField(default=True)
816 e1a80257 Sofia Papagiannaki
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
817 d2633501 Kostas Papadimitriou
                                   default='astakos')
818 3a72a5d4 Kostas Papadimitriou
    info_data = models.TextField(default="", null=True, blank=True)
819 c630fee6 Kostas Papadimitriou
    created = models.DateTimeField('Creation date', auto_now_add=True)
820 d2633501 Kostas Papadimitriou
821 d2633501 Kostas Papadimitriou
    objects = AstakosUserAuthProviderManager()
822 d2633501 Kostas Papadimitriou
823 d2633501 Kostas Papadimitriou
    class Meta:
824 d2633501 Kostas Papadimitriou
        unique_together = (('identifier', 'module', 'user'), )
825 c630fee6 Kostas Papadimitriou
        ordering = ('module', 'created')
826 d2633501 Kostas Papadimitriou
827 3a72a5d4 Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
828 3a72a5d4 Kostas Papadimitriou
        super(AstakosUserAuthProvider, self).__init__(*args, **kwargs)
829 3a72a5d4 Kostas Papadimitriou
        try:
830 3a72a5d4 Kostas Papadimitriou
            self.info = json.loads(self.info_data)
831 c630fee6 Kostas Papadimitriou
            if not self.info:
832 c630fee6 Kostas Papadimitriou
                self.info = {}
833 c630fee6 Kostas Papadimitriou
        except Exception, e:
834 3a72a5d4 Kostas Papadimitriou
            self.info = {}
835 c630fee6 Kostas Papadimitriou
836 3a72a5d4 Kostas Papadimitriou
        for key,value in self.info.iteritems():
837 3a72a5d4 Kostas Papadimitriou
            setattr(self, 'info_%s' % key, value)
838 3a72a5d4 Kostas Papadimitriou
839 d2633501 Kostas Papadimitriou
840 d2633501 Kostas Papadimitriou
    @property
841 d2633501 Kostas Papadimitriou
    def settings(self):
842 d2633501 Kostas Papadimitriou
        return auth_providers.get_provider(self.module)
843 d2633501 Kostas Papadimitriou
844 d2633501 Kostas Papadimitriou
    @property
845 d2633501 Kostas Papadimitriou
    def details_display(self):
846 c630fee6 Kostas Papadimitriou
        try:
847 c630fee6 Kostas Papadimitriou
          return self.settings.get_details_tpl_display % self.__dict__
848 c630fee6 Kostas Papadimitriou
        except:
849 c630fee6 Kostas Papadimitriou
          return ''
850 3a72a5d4 Kostas Papadimitriou
851 3a72a5d4 Kostas Papadimitriou
    @property
852 3a72a5d4 Kostas Papadimitriou
    def title_display(self):
853 3a72a5d4 Kostas Papadimitriou
        title_tpl = self.settings.get_title_display
854 3a72a5d4 Kostas Papadimitriou
        try:
855 3a72a5d4 Kostas Papadimitriou
            if self.settings.get_user_title_display:
856 3a72a5d4 Kostas Papadimitriou
                title_tpl = self.settings.get_user_title_display
857 3a72a5d4 Kostas Papadimitriou
        except Exception, e:
858 3a72a5d4 Kostas Papadimitriou
            pass
859 c630fee6 Kostas Papadimitriou
        try:
860 c630fee6 Kostas Papadimitriou
          return title_tpl % self.__dict__
861 c630fee6 Kostas Papadimitriou
        except:
862 c630fee6 Kostas Papadimitriou
          return self.settings.get_title_display % self.__dict__
863 d2633501 Kostas Papadimitriou
864 d2633501 Kostas Papadimitriou
    def can_remove(self):
865 d2633501 Kostas Papadimitriou
        return self.user.can_remove_auth_provider(self.module)
866 d2633501 Kostas Papadimitriou
867 d2633501 Kostas Papadimitriou
    def delete(self, *args, **kwargs):
868 d2633501 Kostas Papadimitriou
        ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs)
869 5156e663 Kostas Papadimitriou
        if self.module == 'local':
870 5156e663 Kostas Papadimitriou
            self.user.set_unusable_password()
871 5156e663 Kostas Papadimitriou
            self.user.save()
872 d2633501 Kostas Papadimitriou
        return ret
873 d2633501 Kostas Papadimitriou
874 f432088a Kostas Papadimitriou
    def __repr__(self):
875 f432088a Kostas Papadimitriou
        return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
876 f432088a Kostas Papadimitriou
877 b778b6fa Kostas Papadimitriou
    def __unicode__(self):
878 b778b6fa Kostas Papadimitriou
        if self.identifier:
879 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.identifier)
880 b778b6fa Kostas Papadimitriou
        if self.auth_backend:
881 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.auth_backend)
882 b778b6fa Kostas Papadimitriou
        return self.module
883 b778b6fa Kostas Papadimitriou
884 3a72a5d4 Kostas Papadimitriou
    def save(self, *args, **kwargs):
885 3a72a5d4 Kostas Papadimitriou
        self.info_data = json.dumps(self.info)
886 3a72a5d4 Kostas Papadimitriou
        return super(AstakosUserAuthProvider, self).save(*args, **kwargs)
887 b778b6fa Kostas Papadimitriou
888 d2633501 Kostas Papadimitriou
889 e1a80257 Sofia Papagiannaki
class ExtendedManager(models.Manager):
890 9a06d96f Olga Brani
    def _update_or_create(self, **kwargs):
891 9a06d96f Olga Brani
        assert kwargs, \
892 9a06d96f Olga Brani
            'update_or_create() must be passed at least one keyword argument'
893 9a06d96f Olga Brani
        obj, created = self.get_or_create(**kwargs)
894 9a06d96f Olga Brani
        defaults = kwargs.pop('defaults', {})
895 9a06d96f Olga Brani
        if created:
896 9a06d96f Olga Brani
            return obj, True, False
897 9a06d96f Olga Brani
        else:
898 9a06d96f Olga Brani
            try:
899 9a06d96f Olga Brani
                params = dict(
900 9a06d96f Olga Brani
                    [(k, v) for k, v in kwargs.items() if '__' not in k])
901 9a06d96f Olga Brani
                params.update(defaults)
902 9a06d96f Olga Brani
                for attr, val in params.items():
903 9a06d96f Olga Brani
                    if hasattr(obj, attr):
904 9a06d96f Olga Brani
                        setattr(obj, attr, val)
905 9a06d96f Olga Brani
                sid = transaction.savepoint()
906 9a06d96f Olga Brani
                obj.save(force_update=True)
907 9a06d96f Olga Brani
                transaction.savepoint_commit(sid)
908 9a06d96f Olga Brani
                return obj, False, True
909 9a06d96f Olga Brani
            except IntegrityError, e:
910 9a06d96f Olga Brani
                transaction.savepoint_rollback(sid)
911 9a06d96f Olga Brani
                try:
912 9a06d96f Olga Brani
                    return self.get(**kwargs), False, False
913 9a06d96f Olga Brani
                except self.model.DoesNotExist:
914 9a06d96f Olga Brani
                    raise e
915 9a06d96f Olga Brani
916 9a06d96f Olga Brani
    update_or_create = _update_or_create
917 5ce3ce4f Sofia Papagiannaki
918 8e45d6fd Sofia Papagiannaki
919 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
920 e1a80257 Sofia Papagiannaki
    objects = ExtendedManager()
921 9deadfcd Georgios D. Tsoukalas
    capacity = intDecimalField()
922 9deadfcd Georgios D. Tsoukalas
    quantity = intDecimalField(default=0)
923 9deadfcd Georgios D. Tsoukalas
    export_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
924 9deadfcd Georgios D. Tsoukalas
    import_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
925 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
926 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
927 5ce3ce4f Sofia Papagiannaki
928 8e45d6fd Sofia Papagiannaki
    class Meta:
929 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
930 09e7393c Sofia Papagiannaki
931 8989f446 Sofia Papagiannaki
    def quota_values(self):
932 0514bcc7 Giorgos Korfiatis
        return QuotaValues(
933 0514bcc7 Giorgos Korfiatis
            quantity = self.quantity,
934 0514bcc7 Giorgos Korfiatis
            capacity = self.capacity,
935 0514bcc7 Giorgos Korfiatis
            import_limit = self.import_limit,
936 0514bcc7 Giorgos Korfiatis
            export_limit = self.export_limit)
937 0514bcc7 Giorgos Korfiatis
938 5ce3ce4f Sofia Papagiannaki
939 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
940 270dd48d Sofia Papagiannaki
    """
941 270dd48d Sofia Papagiannaki
    Model for approval terms
942 270dd48d Sofia Papagiannaki
    """
943 6c736ed7 Kostas Papadimitriou
944 5ce3ce4f Sofia Papagiannaki
    date = models.DateTimeField(
945 3c638f72 Giorgos Korfiatis
        _('Issue date'), db_index=True, auto_now_add=True)
946 e1a80257 Sofia Papagiannaki
    location = models.CharField(_('Terms location'), max_length=255)
947 270dd48d Sofia Papagiannaki
948 5ce3ce4f Sofia Papagiannaki
949 64cd4730 Antony Chazapis
class Invitation(models.Model):
950 890b0eaf Sofia Papagiannaki
    """
951 890b0eaf Sofia Papagiannaki
    Model for registring invitations
952 890b0eaf Sofia Papagiannaki
    """
953 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
954 64cd4730 Antony Chazapis
                                null=True)
955 e1a80257 Sofia Papagiannaki
    realname = models.CharField(_('Real name'), max_length=255)
956 e1a80257 Sofia Papagiannaki
    username = models.CharField(_('Unique ID'), max_length=255, unique=True)
957 e1a80257 Sofia Papagiannaki
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
958 e1a80257 Sofia Papagiannaki
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
959 e1a80257 Sofia Papagiannaki
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
960 e1a80257 Sofia Papagiannaki
    consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True)
961 5ce3ce4f Sofia Papagiannaki
962 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
963 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
964 8f5a3a06 Sofia Papagiannaki
        if not self.id:
965 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
966 5ce3ce4f Sofia Papagiannaki
967 64cd4730 Antony Chazapis
    def consume(self):
968 64cd4730 Antony Chazapis
        self.is_consumed = True
969 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
970 64cd4730 Antony Chazapis
        self.save()
971 6c736ed7 Kostas Papadimitriou
972 64cd4730 Antony Chazapis
    def __unicode__(self):
973 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
974 9c01d9d1 Sofia Papagiannaki
975 49790d9d Sofia Papagiannaki
976 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
977 34a76cdb Kostas Papadimitriou
978 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
979 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
980 49790d9d Sofia Papagiannaki
        """
981 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
982 49790d9d Sofia Papagiannaki
        ``User`` if valid.
983 49790d9d Sofia Papagiannaki

984 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
985 49790d9d Sofia Papagiannaki
        after activating.
986 49790d9d Sofia Papagiannaki

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

989 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
990 49790d9d Sofia Papagiannaki
        return ``None``.
991 49790d9d Sofia Papagiannaki

992 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
993 49790d9d Sofia Papagiannaki

994 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
995 49790d9d Sofia Papagiannaki
        """
996 49790d9d Sofia Papagiannaki
        try:
997 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
998 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
999 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
1000 49790d9d Sofia Papagiannaki
                email_change.delete()
1001 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
1002 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
1003 49790d9d Sofia Papagiannaki
            try:
1004 789a5951 Sofia Papagiannaki
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
1005 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
1006 49790d9d Sofia Papagiannaki
                pass
1007 49790d9d Sofia Papagiannaki
            else:
1008 73fbaec4 Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
1009 49790d9d Sofia Papagiannaki
            # update user
1010 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
1011 34a76cdb Kostas Papadimitriou
            old_email = user.email
1012 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
1013 49790d9d Sofia Papagiannaki
            user.save()
1014 49790d9d Sofia Papagiannaki
            email_change.delete()
1015 34a76cdb Kostas Papadimitriou
            msg = "User %d changed email from %s to %s" % (user.pk, old_email,
1016 34a76cdb Kostas Papadimitriou
                                                          user.email)
1017 34a76cdb Kostas Papadimitriou
            logger.log(LOGGING_LEVEL, msg)
1018 49790d9d Sofia Papagiannaki
            return user
1019 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
1020 73fbaec4 Sofia Papagiannaki
            raise ValueError(_('Invalid activation key.'))
1021 49790d9d Sofia Papagiannaki
1022 49790d9d Sofia Papagiannaki
1023 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
1024 73fbaec4 Sofia Papagiannaki
    new_email_address = models.EmailField(
1025 73fbaec4 Sofia Papagiannaki
        _(u'new e-mail address'),
1026 73fbaec4 Sofia Papagiannaki
        help_text=_('Your old email address will be used until you verify your new one.'))
1027 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
1028 34a76cdb Kostas Papadimitriou
        AstakosUser, unique=True, related_name='emailchanges')
1029 3c638f72 Giorgos Korfiatis
    requested_at = models.DateTimeField(auto_now_add=True)
1030 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
1031 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
1032 49790d9d Sofia Papagiannaki
1033 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
1034 49790d9d Sofia Papagiannaki
1035 34a76cdb Kostas Papadimitriou
    def get_url(self):
1036 34a76cdb Kostas Papadimitriou
        return reverse('email_change_confirm',
1037 34a76cdb Kostas Papadimitriou
                      kwargs={'activation_key': self.activation_key})
1038 34a76cdb Kostas Papadimitriou
1039 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
1040 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
1041 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
1042 ff9290ec Sofia Papagiannaki
1043 6b03a847 Sofia Papagiannaki
1044 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
1045 ca828a10 Sofia Papagiannaki
    """
1046 ca828a10 Sofia Papagiannaki
    Model for registring invitations
1047 ca828a10 Sofia Papagiannaki
    """
1048 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
1049 1eec103a Sofia Papagiannaki
    email = models.EmailField()
1050 ca828a10 Sofia Papagiannaki
1051 5ce3ce4f Sofia Papagiannaki
1052 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
1053 fc1e2f02 Sofia Papagiannaki
    while True:
1054 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
1055 fc1e2f02 Sofia Papagiannaki
        try:
1056 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
1057 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
1058 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
1059 fc1e2f02 Sofia Papagiannaki
            return code
1060 fc1e2f02 Sofia Papagiannaki
1061 5ce3ce4f Sofia Papagiannaki
1062 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
1063 fc1e2f02 Sofia Papagiannaki
    try:
1064 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
1065 fc1e2f02 Sofia Papagiannaki
        return term
1066 fc1e2f02 Sofia Papagiannaki
    except IndexError:
1067 fc1e2f02 Sofia Papagiannaki
        pass
1068 fc1e2f02 Sofia Papagiannaki
    return None
1069 fc1e2f02 Sofia Papagiannaki
1070 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
1071 ef20ea07 Sofia Papagiannaki
    """
1072 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
1073 ef20ea07 Sofia Papagiannaki
    """
1074 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
1075 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
1076 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
1077 564a2292 Kostas Papadimitriou
    first_name = models.CharField(_('first name'), max_length=30, blank=True,
1078 564a2292 Kostas Papadimitriou
                                  null=True)
1079 564a2292 Kostas Papadimitriou
    last_name = models.CharField(_('last name'), max_length=30, blank=True,
1080 564a2292 Kostas Papadimitriou
                                 null=True)
1081 564a2292 Kostas Papadimitriou
    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
1082 564a2292 Kostas Papadimitriou
                                   null=True)
1083 ef20ea07 Sofia Papagiannaki
    username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
1084 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
1085 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
1086 c630fee6 Kostas Papadimitriou
    info = models.TextField(default="", null=True, blank=True)
1087 d2633501 Kostas Papadimitriou
1088 678b2236 Sofia Papagiannaki
    class Meta:
1089 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
1090 ef20ea07 Sofia Papagiannaki
1091 c630fee6 Kostas Papadimitriou
    def get_user_instance(self):
1092 c630fee6 Kostas Papadimitriou
        d = self.__dict__
1093 c630fee6 Kostas Papadimitriou
        d.pop('_state', None)
1094 c630fee6 Kostas Papadimitriou
        d.pop('id', None)
1095 c630fee6 Kostas Papadimitriou
        d.pop('token', None)
1096 c630fee6 Kostas Papadimitriou
        d.pop('created', None)
1097 c630fee6 Kostas Papadimitriou
        d.pop('info', None)
1098 c630fee6 Kostas Papadimitriou
        user = AstakosUser(**d)
1099 c630fee6 Kostas Papadimitriou
1100 c630fee6 Kostas Papadimitriou
        return user
1101 c630fee6 Kostas Papadimitriou
1102 ef20ea07 Sofia Papagiannaki
    @property
1103 ef20ea07 Sofia Papagiannaki
    def realname(self):
1104 ef20ea07 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
1105 ef20ea07 Sofia Papagiannaki
1106 ef20ea07 Sofia Papagiannaki
    @realname.setter
1107 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
1108 ef20ea07 Sofia Papagiannaki
        parts = value.split(' ')
1109 ef20ea07 Sofia Papagiannaki
        if len(parts) == 2:
1110 ef20ea07 Sofia Papagiannaki
            self.first_name = parts[0]
1111 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[1]
1112 ef20ea07 Sofia Papagiannaki
        else:
1113 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[0]
1114 2e90e3ec Kostas Papadimitriou
1115 ef20ea07 Sofia Papagiannaki
    def save(self, **kwargs):
1116 ef20ea07 Sofia Papagiannaki
        if not self.id:
1117 ef20ea07 Sofia Papagiannaki
            # set username
1118 ef20ea07 Sofia Papagiannaki
            while not self.username:
1119 ef20ea07 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
1120 ef20ea07 Sofia Papagiannaki
                try:
1121 ef20ea07 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
1122 ef20ea07 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
1123 ef20ea07 Sofia Papagiannaki
                    self.username = username
1124 ef20ea07 Sofia Papagiannaki
        super(PendingThirdPartyUser, self).save(**kwargs)
1125 ef20ea07 Sofia Papagiannaki
1126 d2633501 Kostas Papadimitriou
    def generate_token(self):
1127 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1128 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1129 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1130 d2633501 Kostas Papadimitriou
1131 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1132 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1133 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1134 bf0c6de5 Sofia Papagiannaki
1135 fcc1e93f Sofia Papagiannaki
1136 fcc1e93f Sofia Papagiannaki
### PROJECTS ###
1137 fcc1e93f Sofia Papagiannaki
################
1138 fcc1e93f Sofia Papagiannaki
1139 e546df49 Georgios D. Tsoukalas
def synced_model_metaclass(class_name, class_parents, class_attributes):
1140 65360c65 Georgios D. Tsoukalas
1141 e546df49 Georgios D. Tsoukalas
    new_attributes = {}
1142 e546df49 Georgios D. Tsoukalas
    sync_attributes = {}
1143 65360c65 Georgios D. Tsoukalas
1144 e546df49 Georgios D. Tsoukalas
    for name, value in class_attributes.iteritems():
1145 e546df49 Georgios D. Tsoukalas
        sync, underscore, rest = name.partition('_')
1146 e546df49 Georgios D. Tsoukalas
        if sync == 'sync' and underscore == '_':
1147 e546df49 Georgios D. Tsoukalas
            sync_attributes[rest] = value
1148 e546df49 Georgios D. Tsoukalas
        else:
1149 e546df49 Georgios D. Tsoukalas
            new_attributes[name] = value
1150 65360c65 Georgios D. Tsoukalas
1151 e546df49 Georgios D. Tsoukalas
    if 'prefix' not in sync_attributes:
1152 e546df49 Georgios D. Tsoukalas
        m = ("you did not specify a 'sync_prefix' attribute "
1153 e546df49 Georgios D. Tsoukalas
             "in class '%s'" % (class_name,))
1154 e546df49 Georgios D. Tsoukalas
        raise ValueError(m)
1155 65360c65 Georgios D. Tsoukalas
1156 e546df49 Georgios D. Tsoukalas
    prefix = sync_attributes.pop('prefix')
1157 e546df49 Georgios D. Tsoukalas
    class_name = sync_attributes.pop('classname', prefix + '_model')
1158 65360c65 Georgios D. Tsoukalas
1159 e546df49 Georgios D. Tsoukalas
    for name, value in sync_attributes.iteritems():
1160 e546df49 Georgios D. Tsoukalas
        newname = prefix + '_' + name
1161 e546df49 Georgios D. Tsoukalas
        if newname in new_attributes:
1162 e546df49 Georgios D. Tsoukalas
            m = ("class '%s' was specified with prefix '%s' "
1163 e546df49 Georgios D. Tsoukalas
                 "but it already has an attribute named '%s'"
1164 e546df49 Georgios D. Tsoukalas
                 % (class_name, prefix, newname))
1165 e546df49 Georgios D. Tsoukalas
            raise ValueError(m)
1166 65360c65 Georgios D. Tsoukalas
1167 e546df49 Georgios D. Tsoukalas
        new_attributes[newname] = value
1168 e546df49 Georgios D. Tsoukalas
1169 e546df49 Georgios D. Tsoukalas
    newclass = type(class_name, class_parents, new_attributes)
1170 e546df49 Georgios D. Tsoukalas
    return newclass
1171 e546df49 Georgios D. Tsoukalas
1172 e546df49 Georgios D. Tsoukalas
1173 e546df49 Georgios D. Tsoukalas
def make_synced(prefix='sync', name='SyncedState'):
1174 e546df49 Georgios D. Tsoukalas
1175 e546df49 Georgios D. Tsoukalas
    the_name = name
1176 e546df49 Georgios D. Tsoukalas
    the_prefix = prefix
1177 e546df49 Georgios D. Tsoukalas
1178 e546df49 Georgios D. Tsoukalas
    class SyncedState(models.Model):
1179 e546df49 Georgios D. Tsoukalas
1180 e546df49 Georgios D. Tsoukalas
        sync_classname      = the_name
1181 e546df49 Georgios D. Tsoukalas
        sync_prefix         = the_prefix
1182 e546df49 Georgios D. Tsoukalas
        __metaclass__       = synced_model_metaclass
1183 e546df49 Georgios D. Tsoukalas
1184 e546df49 Georgios D. Tsoukalas
        sync_new_state      = models.BigIntegerField(null=True)
1185 e546df49 Georgios D. Tsoukalas
        sync_synced_state   = models.BigIntegerField(null=True)
1186 e546df49 Georgios D. Tsoukalas
        STATUS_SYNCED       = 0
1187 e546df49 Georgios D. Tsoukalas
        STATUS_PENDING      = 1
1188 e546df49 Georgios D. Tsoukalas
        sync_status         = models.IntegerField(db_index=True)
1189 e546df49 Georgios D. Tsoukalas
1190 e546df49 Georgios D. Tsoukalas
        class Meta:
1191 e546df49 Georgios D. Tsoukalas
            abstract = True
1192 e546df49 Georgios D. Tsoukalas
1193 e546df49 Georgios D. Tsoukalas
        class NotSynced(Exception):
1194 e546df49 Georgios D. Tsoukalas
            pass
1195 e546df49 Georgios D. Tsoukalas
1196 e546df49 Georgios D. Tsoukalas
        def sync_init_state(self, state):
1197 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = state
1198 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = state
1199 65360c65 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1200 65360c65 Georgios D. Tsoukalas
1201 e546df49 Georgios D. Tsoukalas
        def sync_get_status(self):
1202 e546df49 Georgios D. Tsoukalas
            return self.sync_status
1203 65360c65 Georgios D. Tsoukalas
1204 e546df49 Georgios D. Tsoukalas
        def sync_set_status(self):
1205 e546df49 Georgios D. Tsoukalas
            if self.sync_new_state != self.sync_synced_state:
1206 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_PENDING
1207 e546df49 Georgios D. Tsoukalas
            else:
1208 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_SYNCED
1209 65360c65 Georgios D. Tsoukalas
1210 e546df49 Georgios D. Tsoukalas
        def sync_set_synced(self):
1211 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = self.sync_new_state
1212 e546df49 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1213 65360c65 Georgios D. Tsoukalas
1214 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_state(self):
1215 e546df49 Georgios D. Tsoukalas
            return self.sync_synced_state
1216 65360c65 Georgios D. Tsoukalas
1217 e546df49 Georgios D. Tsoukalas
        def sync_set_new_state(self, new_state):
1218 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = new_state
1219 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1220 65360c65 Georgios D. Tsoukalas
1221 e546df49 Georgios D. Tsoukalas
        def sync_get_new_state(self):
1222 e546df49 Georgios D. Tsoukalas
            return self.sync_new_state
1223 65360c65 Georgios D. Tsoukalas
1224 e546df49 Georgios D. Tsoukalas
        def sync_set_synced_state(self, synced_state):
1225 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = synced_state
1226 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1227 65360c65 Georgios D. Tsoukalas
1228 e546df49 Georgios D. Tsoukalas
        def sync_get_pending_objects(self):
1229 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_PENDING))
1230 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1231 65360c65 Georgios D. Tsoukalas
1232 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_objects(self):
1233 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_SYNCED))
1234 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1235 65360c65 Georgios D. Tsoukalas
1236 e546df49 Georgios D. Tsoukalas
        def sync_verify_get_synced_state(self):
1237 e546df49 Georgios D. Tsoukalas
            status = self.sync_get_status()
1238 e546df49 Georgios D. Tsoukalas
            state = self.sync_get_synced_state()
1239 e546df49 Georgios D. Tsoukalas
            verified = (status == self.STATUS_SYNCED)
1240 e546df49 Georgios D. Tsoukalas
            return state, verified
1241 e546df49 Georgios D. Tsoukalas
1242 e546df49 Georgios D. Tsoukalas
        def sync_is_synced(self):
1243 e546df49 Georgios D. Tsoukalas
            state, verified = self.sync_verify_get_synced_state()
1244 e546df49 Georgios D. Tsoukalas
            return verified
1245 e546df49 Georgios D. Tsoukalas
1246 e546df49 Georgios D. Tsoukalas
    return SyncedState
1247 e546df49 Georgios D. Tsoukalas
1248 e546df49 Georgios D. Tsoukalas
SyncedState = make_synced(prefix='sync', name='SyncedState')
1249 425e2e95 Sofia Papagiannaki
1250 425e2e95 Sofia Papagiannaki
1251 6dcf53eb Kostas Papadimitriou
class ProjectApplicationManager(ForUpdateManager):
1252 5550bcfb Kostas Papadimitriou
1253 05617ab9 Kostas Papadimitriou
    def user_visible_projects(self, *filters, **kw_filters):
1254 689226c3 Giorgos Korfiatis
        model = self.model
1255 689226c3 Giorgos Korfiatis
        return self.filter(model.Q_PENDING | model.Q_APPROVED)
1256 05617ab9 Kostas Papadimitriou
1257 7184f408 Giorgos Korfiatis
    def user_visible_by_chain(self, flt):
1258 689226c3 Giorgos Korfiatis
        model = self.model
1259 3e3743f2 Giorgos Korfiatis
        pending = self.filter(model.Q_PENDING | model.Q_DENIED).values_list('chain')
1260 689226c3 Giorgos Korfiatis
        approved = self.filter(model.Q_APPROVED).values_list('chain')
1261 a3530159 Georgios D. Tsoukalas
        by_chain = dict(pending.annotate(models.Max('id')))
1262 a3530159 Georgios D. Tsoukalas
        by_chain.update(approved.annotate(models.Max('id')))
1263 7184f408 Giorgos Korfiatis
        return self.filter(flt, id__in=by_chain.values())
1264 05617ab9 Kostas Papadimitriou
1265 05617ab9 Kostas Papadimitriou
    def user_accessible_projects(self, user):
1266 5550bcfb Kostas Papadimitriou
        """
1267 5550bcfb Kostas Papadimitriou
        Return projects accessed by specified user.
1268 5550bcfb Kostas Papadimitriou
        """
1269 05617ab9 Kostas Papadimitriou
        participates_filters = Q(owner=user) | Q(applicant=user) | \
1270 9b32e2fb Kostas Papadimitriou
                               Q(project__projectmembership__person=user)
1271 05617ab9 Kostas Papadimitriou
1272 a3530159 Georgios D. Tsoukalas
        return self.user_visible_by_chain(participates_filters).order_by('issue_date').distinct()
1273 5550bcfb Kostas Papadimitriou
1274 a5cef8d0 Kostas Papadimitriou
    def search_by_name(self, *search_strings):
1275 a5cef8d0 Kostas Papadimitriou
        q = Q()
1276 a5cef8d0 Kostas Papadimitriou
        for s in search_strings:
1277 a5cef8d0 Kostas Papadimitriou
            q = q | Q(name__icontains=s)
1278 a5cef8d0 Kostas Papadimitriou
        return self.filter(q)
1279 a5cef8d0 Kostas Papadimitriou
1280 ff67242a Giorgos Korfiatis
    def latest_of_chain(self, chain_id):
1281 ff67242a Giorgos Korfiatis
        try:
1282 ff67242a Giorgos Korfiatis
            return self.filter(chain=chain_id).order_by('-id')[0]
1283 ff67242a Giorgos Korfiatis
        except IndexError:
1284 ff67242a Giorgos Korfiatis
            return None
1285 a5cef8d0 Kostas Papadimitriou
1286 db9a498c Kostas Papadimitriou
USER_STATUS_DISPLAY = {
1287 db9a498c Kostas Papadimitriou
      0: _('Join requested'),
1288 bd9af366 Kostas Papadimitriou
      1: _('Accepted member'),
1289 bd9af366 Kostas Papadimitriou
     10: _('Suspended'),
1290 bd9af366 Kostas Papadimitriou
    100: _('Terminated'),
1291 bd9af366 Kostas Papadimitriou
    200: _('Removed'),
1292 db9a498c Kostas Papadimitriou
     -1: _('Not a member'),
1293 db9a498c Kostas Papadimitriou
}
1294 db9a498c Kostas Papadimitriou
1295 bd9af366 Kostas Papadimitriou
1296 a9ba418f Giorgos Korfiatis
class Chain(models.Model):
1297 a9ba418f Giorgos Korfiatis
    chain  =   models.AutoField(primary_key=True)
1298 a9ba418f Giorgos Korfiatis
1299 a9ba418f Giorgos Korfiatis
def new_chain():
1300 a9ba418f Giorgos Korfiatis
    c = Chain.objects.create()
1301 a9ba418f Giorgos Korfiatis
    chain = c.chain
1302 a9ba418f Giorgos Korfiatis
    c.delete()
1303 a9ba418f Giorgos Korfiatis
    return chain
1304 a9ba418f Giorgos Korfiatis
1305 a9ba418f Giorgos Korfiatis
1306 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1307 425e2e95 Sofia Papagiannaki
    applicant               =   models.ForeignKey(
1308 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1309 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_applied',
1310 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1311 d6fdc91e Georgios D. Tsoukalas
1312 d0e78bbe Giorgos Korfiatis
    PENDING     =    0
1313 d0e78bbe Giorgos Korfiatis
    APPROVED    =    1
1314 d0e78bbe Giorgos Korfiatis
    REPLACED    =    2
1315 d0e78bbe Giorgos Korfiatis
    DENIED      =    3
1316 3c638f72 Giorgos Korfiatis
    DISMISSED   =    4
1317 3c638f72 Giorgos Korfiatis
    CANCELLED   =    5
1318 d0e78bbe Giorgos Korfiatis
1319 69ab4df9 Giorgos Korfiatis
    state                   =   models.IntegerField(default=PENDING,
1320 69ab4df9 Giorgos Korfiatis
                                                    db_index=True)
1321 d6fdc91e Georgios D. Tsoukalas
1322 425e2e95 Sofia Papagiannaki
    owner                   =   models.ForeignKey(
1323 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1324 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_owned',
1325 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1326 d6fdc91e Georgios D. Tsoukalas
1327 c4892cd2 Sofia Papagiannaki
    chain                   =   models.IntegerField()
1328 3c638f72 Giorgos Korfiatis
    precursor_application   =   models.ForeignKey('ProjectApplication',
1329 3c638f72 Giorgos Korfiatis
                                                  null=True,
1330 3c638f72 Giorgos Korfiatis
                                                  blank=True)
1331 425e2e95 Sofia Papagiannaki
1332 67980f56 Georgios D. Tsoukalas
    name                    =   models.CharField(max_length=80)
1333 67980f56 Georgios D. Tsoukalas
    homepage                =   models.URLField(max_length=255, null=True)
1334 67980f56 Georgios D. Tsoukalas
    description             =   models.TextField(null=True, blank=True)
1335 e729f165 Kostas Papadimitriou
    start_date              =   models.DateTimeField(null=True, blank=True)
1336 67980f56 Georgios D. Tsoukalas
    end_date                =   models.DateTimeField()
1337 272cf735 Sofia Papagiannaki
    member_join_policy      =   models.IntegerField()
1338 272cf735 Sofia Papagiannaki
    member_leave_policy     =   models.IntegerField()
1339 67980f56 Georgios D. Tsoukalas
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1340 425e2e95 Sofia Papagiannaki
    resource_grants         =   models.ManyToManyField(
1341 425e2e95 Sofia Papagiannaki
                                    Resource,
1342 425e2e95 Sofia Papagiannaki
                                    null=True,
1343 425e2e95 Sofia Papagiannaki
                                    blank=True,
1344 d6fdc91e Georgios D. Tsoukalas
                                    through='ProjectResourceGrant')
1345 425e2e95 Sofia Papagiannaki
    comments                =   models.TextField(null=True, blank=True)
1346 3c638f72 Giorgos Korfiatis
    issue_date              =   models.DateTimeField(auto_now_add=True)
1347 3c638f72 Giorgos Korfiatis
    response_date           =   models.DateTimeField(null=True, blank=True)
1348 6dcf53eb Kostas Papadimitriou
1349 5550bcfb Kostas Papadimitriou
    objects                 =   ProjectApplicationManager()
1350 7729e9cc Giorgos Korfiatis
1351 689226c3 Giorgos Korfiatis
    # Compiled queries
1352 689226c3 Giorgos Korfiatis
    Q_PENDING  = Q(state=PENDING)
1353 689226c3 Giorgos Korfiatis
    Q_APPROVED = Q(state=APPROVED)
1354 3e3743f2 Giorgos Korfiatis
    Q_DENIED   = Q(state=DENIED)
1355 689226c3 Giorgos Korfiatis
1356 c4892cd2 Sofia Papagiannaki
    class Meta:
1357 c4892cd2 Sofia Papagiannaki
        unique_together = ("chain", "id")
1358 c4892cd2 Sofia Papagiannaki
1359 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1360 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1361 f3a45fc6 Kostas Papadimitriou
1362 d0e78bbe Giorgos Korfiatis
    # TODO: Move to a more suitable place
1363 9307cd46 Giorgos Korfiatis
    APPLICATION_STATE_DISPLAY = {
1364 3c638f72 Giorgos Korfiatis
        PENDING  : _('Pending review'),
1365 3c638f72 Giorgos Korfiatis
        APPROVED : _('Active'),
1366 3c638f72 Giorgos Korfiatis
        REPLACED : _('Replaced'),
1367 3c638f72 Giorgos Korfiatis
        DENIED   : _('Denied'),
1368 3c638f72 Giorgos Korfiatis
        DISMISSED: _('Dismissed'),
1369 3c638f72 Giorgos Korfiatis
        CANCELLED: _('Cancelled')
1370 bd9af366 Kostas Papadimitriou
    }
1371 d0e78bbe Giorgos Korfiatis
1372 a3530159 Georgios D. Tsoukalas
    def get_project(self):
1373 a3530159 Georgios D. Tsoukalas
        try:
1374 a3530159 Georgios D. Tsoukalas
            project = Project.objects.get(id=self.chain, state=Project.APPROVED)
1375 a3530159 Georgios D. Tsoukalas
            return Project
1376 a3530159 Georgios D. Tsoukalas
        except Project.DoesNotExist, e:
1377 a3530159 Georgios D. Tsoukalas
            return None
1378 a3530159 Georgios D. Tsoukalas
1379 db9a498c Kostas Papadimitriou
    def state_display(self):
1380 9307cd46 Giorgos Korfiatis
        return self.APPLICATION_STATE_DISPLAY.get(self.state, _('Unknown'))
1381 db9a498c Kostas Papadimitriou
1382 a7aba804 Sofia Papagiannaki
    def add_resource_policy(self, service, resource, uplimit):
1383 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1384 a7aba804 Sofia Papagiannaki
        q = self.projectresourcegrant_set
1385 e1a80257 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=service, name=resource)
1386 a7aba804 Sofia Papagiannaki
        q.create(resource=resource, member_capacity=uplimit)
1387 e1a80257 Sofia Papagiannaki
1388 e87bbb41 Kostas Papadimitriou
    def user_status(self, user):
1389 db9a498c Kostas Papadimitriou
        try:
1390 05617ab9 Kostas Papadimitriou
            project = self.get_project()
1391 05617ab9 Kostas Papadimitriou
            if not project:
1392 05617ab9 Kostas Papadimitriou
                return -1
1393 bd9af366 Kostas Papadimitriou
            membership = project.projectmembership_set
1394 05617ab9 Kostas Papadimitriou
            membership = membership.exclude(state=ProjectMembership.REMOVED)
1395 bd9af366 Kostas Papadimitriou
            membership = membership.get(person=user)
1396 db9a498c Kostas Papadimitriou
            status = membership.state
1397 db9a498c Kostas Papadimitriou
        except ProjectMembership.DoesNotExist:
1398 f5991951 Kostas Papadimitriou
            return -1
1399 5550bcfb Kostas Papadimitriou
1400 5550bcfb Kostas Papadimitriou
        return status
1401 5550bcfb Kostas Papadimitriou
1402 db9a498c Kostas Papadimitriou
    def user_status_display(self, user):
1403 db9a498c Kostas Papadimitriou
        return USER_STATUS_DISPLAY.get(self.user_status(user), _('Unknown'))
1404 db9a498c Kostas Papadimitriou
1405 5550bcfb Kostas Papadimitriou
    def members_count(self):
1406 5550bcfb Kostas Papadimitriou
        return self.project.approved_memberships.count()
1407 5550bcfb Kostas Papadimitriou
1408 669cfe19 Olga Brani
    @property
1409 669cfe19 Olga Brani
    def grants(self):
1410 3d6dade7 Sofia Papagiannaki
        return self.projectresourcegrant_set.values(
1411 3d6dade7 Sofia Papagiannaki
            'member_capacity', 'resource__name', 'resource__service__name')
1412 5550bcfb Kostas Papadimitriou
1413 e1a80257 Sofia Papagiannaki
    @property
1414 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1415 b98e1df0 Sofia Papagiannaki
        return [str(rp) for rp in self.projectresourcegrant_set.all()]
1416 e1a80257 Sofia Papagiannaki
1417 e1a80257 Sofia Papagiannaki
    @resource_policies.setter
1418 e1a80257 Sofia Papagiannaki
    def resource_policies(self, policies):
1419 e1a80257 Sofia Papagiannaki
        for p in policies:
1420 e1a80257 Sofia Papagiannaki
            service = p.get('service', None)
1421 e1a80257 Sofia Papagiannaki
            resource = p.get('resource', None)
1422 e1a80257 Sofia Papagiannaki
            uplimit = p.get('uplimit', 0)
1423 a7aba804 Sofia Papagiannaki
            self.add_resource_policy(service, resource, uplimit)
1424 425e2e95 Sofia Papagiannaki
1425 3e3743f2 Giorgos Korfiatis
    def pending_modifications(self):
1426 3e3743f2 Giorgos Korfiatis
        q = self.chained_applications()
1427 3e3743f2 Giorgos Korfiatis
        q = q.filter(~Q(id=self.id) & Q(state=self.PENDING))
1428 3e3743f2 Giorgos Korfiatis
        q = q.order_by('id')
1429 3e3743f2 Giorgos Korfiatis
        return q
1430 ece3b66e Giorgos Korfiatis
1431 3e3743f2 Giorgos Korfiatis
    def last_pending(self):
1432 9b32e2fb Kostas Papadimitriou
        try:
1433 3e3743f2 Giorgos Korfiatis
            return self.pending_modifications().order_by('-id')[0]
1434 9b32e2fb Kostas Papadimitriou
        except IndexError:
1435 05617ab9 Kostas Papadimitriou
            return None
1436 05617ab9 Kostas Papadimitriou
1437 efc58b65 Kostas Papadimitriou
    def is_modification(self):
1438 3e3743f2 Giorgos Korfiatis
        if self.state != self.PENDING:
1439 3e3743f2 Giorgos Korfiatis
            return False
1440 efc58b65 Kostas Papadimitriou
        parents = self.chained_applications().filter(id__lt=self.id)
1441 efc58b65 Kostas Papadimitriou
        parents = parents.filter(state__in=[self.APPROVED])
1442 efc58b65 Kostas Papadimitriou
        return parents.count() > 0
1443 efc58b65 Kostas Papadimitriou
1444 efc58b65 Kostas Papadimitriou
    def chained_applications(self):
1445 efc58b65 Kostas Papadimitriou
        return ProjectApplication.objects.filter(chain=self.chain)
1446 efc58b65 Kostas Papadimitriou
1447 05617ab9 Kostas Papadimitriou
    def has_pending_modifications(self):
1448 3e3743f2 Giorgos Korfiatis
        return bool(self.last_pending())
1449 05617ab9 Kostas Papadimitriou
1450 05617ab9 Kostas Papadimitriou
    def get_project(self):
1451 05617ab9 Kostas Papadimitriou
        try:
1452 05617ab9 Kostas Papadimitriou
            return Project.objects.get(id=self.chain)
1453 05617ab9 Kostas Papadimitriou
        except Project.DoesNotExist:
1454 9b32e2fb Kostas Papadimitriou
            return None
1455 4f22664f Georgios D. Tsoukalas
1456 b6fe8bb8 Giorgos Korfiatis
    def _get_project_for_update(self):
1457 a9ba418f Giorgos Korfiatis
        try:
1458 a9ba418f Giorgos Korfiatis
            objects = Project.objects.select_for_update()
1459 a9ba418f Giorgos Korfiatis
            project = objects.get(id=self.chain)
1460 a9ba418f Giorgos Korfiatis
            return project
1461 a9ba418f Giorgos Korfiatis
        except Project.DoesNotExist:
1462 a9ba418f Giorgos Korfiatis
            return None
1463 4f22664f Georgios D. Tsoukalas
1464 01bdbd17 Giorgos Korfiatis
    def can_cancel(self):
1465 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1466 01bdbd17 Giorgos Korfiatis
1467 3c638f72 Giorgos Korfiatis
    def cancel(self):
1468 01bdbd17 Giorgos Korfiatis
        if not self.can_cancel():
1469 3c638f72 Giorgos Korfiatis
            m = _("cannot cancel: application '%s' in state '%s'") % (
1470 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1471 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1472 3c638f72 Giorgos Korfiatis
1473 3c638f72 Giorgos Korfiatis
        self.state = self.CANCELLED
1474 3c638f72 Giorgos Korfiatis
        self.save()
1475 3c638f72 Giorgos Korfiatis
1476 01bdbd17 Giorgos Korfiatis
    def can_dismiss(self):
1477 01bdbd17 Giorgos Korfiatis
        return self.state == self.DENIED
1478 01bdbd17 Giorgos Korfiatis
1479 3c638f72 Giorgos Korfiatis
    def dismiss(self):
1480 01bdbd17 Giorgos Korfiatis
        if not self.can_dismiss():
1481 3c638f72 Giorgos Korfiatis
            m = _("cannot dismiss: application '%s' in state '%s'") % (
1482 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1483 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1484 3c638f72 Giorgos Korfiatis
1485 3c638f72 Giorgos Korfiatis
        self.state = self.DISMISSED
1486 3c638f72 Giorgos Korfiatis
        self.save()
1487 3c638f72 Giorgos Korfiatis
1488 01bdbd17 Giorgos Korfiatis
    def can_deny(self):
1489 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1490 01bdbd17 Giorgos Korfiatis
1491 19eb3ee6 Giorgos Korfiatis
    def deny(self):
1492 01bdbd17 Giorgos Korfiatis
        if not self.can_deny():
1493 19eb3ee6 Giorgos Korfiatis
            m = _("cannot deny: application '%s' in state '%s'") % (
1494 19eb3ee6 Giorgos Korfiatis
                    self.id, self.state)
1495 19eb3ee6 Giorgos Korfiatis
            raise AssertionError(m)
1496 19eb3ee6 Giorgos Korfiatis
1497 19eb3ee6 Giorgos Korfiatis
        self.state = self.DENIED
1498 3c638f72 Giorgos Korfiatis
        self.response_date = datetime.now()
1499 19eb3ee6 Giorgos Korfiatis
        self.save()
1500 19eb3ee6 Giorgos Korfiatis
1501 01bdbd17 Giorgos Korfiatis
    def can_approve(self):
1502 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1503 01bdbd17 Giorgos Korfiatis
1504 ccab6eb5 Sofia Papagiannaki
    def approve(self, approval_user=None):
1505 ccab6eb5 Sofia Papagiannaki
        """
1506 ccab6eb5 Sofia Papagiannaki
        If approval_user then during owner membership acceptance
1507 ccab6eb5 Sofia Papagiannaki
        it is checked whether the request_user is eligible.
1508 262e04c6 Giorgos Korfiatis

1509 2553efae Sofia Papagiannaki
        Raises:
1510 b8f05f8d Sofia Papagiannaki
            PermissionDenied
1511 ccab6eb5 Sofia Papagiannaki
        """
1512 4f22664f Georgios D. Tsoukalas
1513 4f22664f Georgios D. Tsoukalas
        if not transaction.is_managed():
1514 4f22664f Georgios D. Tsoukalas
            raise AssertionError("NOPE")
1515 4f22664f Georgios D. Tsoukalas
1516 73fbaec4 Sofia Papagiannaki
        new_project_name = self.name
1517 01bdbd17 Giorgos Korfiatis
        if not self.can_approve():
1518 65360c65 Georgios D. Tsoukalas
            m = _("cannot approve: project '%s' in state '%s'") % (
1519 65360c65 Georgios D. Tsoukalas
                    new_project_name, self.state)
1520 01bdbd17 Giorgos Korfiatis
            raise AssertionError(m) # invalid argument
1521 262e04c6 Giorgos Korfiatis
1522 fdafae27 Giorgos Korfiatis
        now = datetime.now()
1523 b6fe8bb8 Giorgos Korfiatis
        project = self._get_project_for_update()
1524 3cc9637a Giorgos Korfiatis
1525 99463445 Giorgos Korfiatis
        try:
1526 99463445 Giorgos Korfiatis
            q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
1527 99463445 Giorgos Korfiatis
            conflicting_project = Project.objects.get(q)
1528 99463445 Giorgos Korfiatis
            if (conflicting_project != project):
1529 3cc9637a Giorgos Korfiatis
                m = (_("cannot approve: project with name '%s' "
1530 3cc9637a Giorgos Korfiatis
                       "already exists (serial: %s)") % (
1531 3cc9637a Giorgos Korfiatis
                        new_project_name, conflicting_project.id))
1532 3cc9637a Giorgos Korfiatis
                raise PermissionDenied(m) # invalid argument
1533 99463445 Giorgos Korfiatis
        except Project.DoesNotExist:
1534 99463445 Giorgos Korfiatis
            pass
1535 3cc9637a Giorgos Korfiatis
1536 4bf02ea5 Giorgos Korfiatis
        new_project = False
1537 4f22664f Georgios D. Tsoukalas
        if project is None:
1538 4bf02ea5 Giorgos Korfiatis
            new_project = True
1539 3c638f72 Giorgos Korfiatis
            project = Project(id=self.chain)
1540 fdafae27 Giorgos Korfiatis
1541 3cc9637a Giorgos Korfiatis
        project.name = new_project_name
1542 ee45eb81 Giorgos Korfiatis
        project.application = self
1543 4bf02ea5 Giorgos Korfiatis
        project.last_approval_date = now
1544 a769d7ba Sofia Papagiannaki
        if not new_project:
1545 a769d7ba Sofia Papagiannaki
            project.is_modified = True
1546 4bf02ea5 Giorgos Korfiatis
1547 a769d7ba Sofia Papagiannaki
        project.save()
1548 425e2e95 Sofia Papagiannaki
1549 85d444db Sofia Papagiannaki
        self.state = self.APPROVED
1550 3c638f72 Giorgos Korfiatis
        self.response_date = now
1551 bfe23b13 Sofia Papagiannaki
        self.save()
1552 262e04c6 Giorgos Korfiatis
1553 b98e1df0 Sofia Papagiannaki
    @property
1554 b98e1df0 Sofia Papagiannaki
    def member_join_policy_display(self):
1555 b98e1df0 Sofia Papagiannaki
        return PROJECT_MEMBER_JOIN_POLICIES.get(str(self.member_join_policy))
1556 b98e1df0 Sofia Papagiannaki
1557 b98e1df0 Sofia Papagiannaki
    @property
1558 b98e1df0 Sofia Papagiannaki
    def member_leave_policy_display(self):
1559 b98e1df0 Sofia Papagiannaki
        return PROJECT_MEMBER_LEAVE_POLICIES.get(str(self.member_leave_policy))
1560 b98e1df0 Sofia Papagiannaki
1561 73fbaec4 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1562 e1a80257 Sofia Papagiannaki
1563 425e2e95 Sofia Papagiannaki
    resource                =   models.ForeignKey(Resource)
1564 425e2e95 Sofia Papagiannaki
    project_application     =   models.ForeignKey(ProjectApplication,
1565 5200e864 Sofia Papagiannaki
                                                  null=True)
1566 c11dc0ce Giorgos Korfiatis
    project_capacity        =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1567 c11dc0ce Giorgos Korfiatis
    project_import_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1568 c11dc0ce Giorgos Korfiatis
    project_export_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1569 c11dc0ce Giorgos Korfiatis
    member_capacity         =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1570 c11dc0ce Giorgos Korfiatis
    member_import_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1571 c11dc0ce Giorgos Korfiatis
    member_export_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1572 73fbaec4 Sofia Papagiannaki
1573 73fbaec4 Sofia Papagiannaki
    objects = ExtendedManager()
1574 73fbaec4 Sofia Papagiannaki
1575 73fbaec4 Sofia Papagiannaki
    class Meta:
1576 73fbaec4 Sofia Papagiannaki
        unique_together = ("resource", "project_application")
1577 8327782d Sofia Papagiannaki
1578 0514bcc7 Giorgos Korfiatis
    def member_quota_values(self):
1579 0514bcc7 Giorgos Korfiatis
        return QuotaValues(
1580 0514bcc7 Giorgos Korfiatis
            quantity = 0,
1581 0514bcc7 Giorgos Korfiatis
            capacity = self.member_capacity,
1582 0514bcc7 Giorgos Korfiatis
            import_limit = self.member_import_limit,
1583 0514bcc7 Giorgos Korfiatis
            export_limit = self.member_export_limit)
1584 0514bcc7 Giorgos Korfiatis
1585 b98e1df0 Sofia Papagiannaki
    def display_member_capacity(self):
1586 b98e1df0 Sofia Papagiannaki
        if self.member_capacity:
1587 b98e1df0 Sofia Papagiannaki
            if self.resource.unit:
1588 b98e1df0 Sofia Papagiannaki
                return ProjectResourceGrant.display_filesize(
1589 b98e1df0 Sofia Papagiannaki
                    self.member_capacity)
1590 b98e1df0 Sofia Papagiannaki
            else:
1591 b98e1df0 Sofia Papagiannaki
                if math.isinf(self.member_capacity):
1592 b98e1df0 Sofia Papagiannaki
                    return 'Unlimited'
1593 b98e1df0 Sofia Papagiannaki
                else:
1594 b98e1df0 Sofia Papagiannaki
                    return self.member_capacity
1595 b98e1df0 Sofia Papagiannaki
        else:
1596 b98e1df0 Sofia Papagiannaki
            return 'Unlimited'
1597 b98e1df0 Sofia Papagiannaki
1598 b98e1df0 Sofia Papagiannaki
    def __str__(self):
1599 b98e1df0 Sofia Papagiannaki
        return 'Max %s per user: %s' % (self.resource.pluralized_display_name,
1600 b98e1df0 Sofia Papagiannaki
                                        self.display_member_capacity())
1601 b98e1df0 Sofia Papagiannaki
1602 b98e1df0 Sofia Papagiannaki
    @classmethod
1603 b98e1df0 Sofia Papagiannaki
    def display_filesize(cls, value):
1604 b98e1df0 Sofia Papagiannaki
        try:
1605 b98e1df0 Sofia Papagiannaki
            value = float(value)
1606 b98e1df0 Sofia Papagiannaki
        except:
1607 b98e1df0 Sofia Papagiannaki
            return
1608 b98e1df0 Sofia Papagiannaki
        else:
1609 b98e1df0 Sofia Papagiannaki
            if math.isinf(value):
1610 b98e1df0 Sofia Papagiannaki
                return 'Unlimited'
1611 b98e1df0 Sofia Papagiannaki
            if value > 1:
1612 b98e1df0 Sofia Papagiannaki
                unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'],
1613 b98e1df0 Sofia Papagiannaki
                                [0, 0, 0, 0, 0, 0])
1614 b98e1df0 Sofia Papagiannaki
                exponent = min(int(math.log(value, 1024)), len(unit_list) - 1)
1615 b98e1df0 Sofia Papagiannaki
                quotient = float(value) / 1024**exponent
1616 b98e1df0 Sofia Papagiannaki
                unit, value_decimals = unit_list[exponent]
1617 b98e1df0 Sofia Papagiannaki
                format_string = '{0:.%sf} {1}' % (value_decimals)
1618 b98e1df0 Sofia Papagiannaki
                return format_string.format(quotient, unit)
1619 b98e1df0 Sofia Papagiannaki
            if value == 0:
1620 b98e1df0 Sofia Papagiannaki
                return '0 bytes'
1621 b98e1df0 Sofia Papagiannaki
            if value == 1:
1622 b98e1df0 Sofia Papagiannaki
                return '1 byte'
1623 b98e1df0 Sofia Papagiannaki
            else:
1624 b98e1df0 Sofia Papagiannaki
               return '0'
1625 b98e1df0 Sofia Papagiannaki
1626 e546df49 Georgios D. Tsoukalas
1627 123be68a Giorgos Korfiatis
class ProjectManager(ForUpdateManager):
1628 123be68a Giorgos Korfiatis
1629 123be68a Giorgos Korfiatis
    def terminated_projects(self):
1630 689226c3 Giorgos Korfiatis
        q = self.model.Q_TERMINATED
1631 123be68a Giorgos Korfiatis
        return self.filter(q)
1632 123be68a Giorgos Korfiatis
1633 123be68a Giorgos Korfiatis
    def not_terminated_projects(self):
1634 689226c3 Giorgos Korfiatis
        q = ~self.model.Q_TERMINATED
1635 123be68a Giorgos Korfiatis
        return self.filter(q)
1636 123be68a Giorgos Korfiatis
1637 b6fe8bb8 Giorgos Korfiatis
    def terminating_projects(self):
1638 689226c3 Giorgos Korfiatis
        q = self.model.Q_TERMINATED & Q(is_active=True)
1639 b6fe8bb8 Giorgos Korfiatis
        return self.filter(q)
1640 b6fe8bb8 Giorgos Korfiatis
1641 db99f198 Giorgos Korfiatis
    def deactivated_projects(self):
1642 689226c3 Giorgos Korfiatis
        q = self.model.Q_DEACTIVATED
1643 db99f198 Giorgos Korfiatis
        return self.filter(q)
1644 db99f198 Giorgos Korfiatis
1645 db99f198 Giorgos Korfiatis
    def deactivating_projects(self):
1646 689226c3 Giorgos Korfiatis
        q = self.model.Q_DEACTIVATED & Q(is_active=True)
1647 db99f198 Giorgos Korfiatis
        return self.filter(q)
1648 db99f198 Giorgos Korfiatis
1649 b6fe8bb8 Giorgos Korfiatis
    def modified_projects(self):
1650 b6fe8bb8 Giorgos Korfiatis
        return self.filter(is_modified=True)
1651 b6fe8bb8 Giorgos Korfiatis
1652 db99f198 Giorgos Korfiatis
    def reactivating_projects(self):
1653 db99f198 Giorgos Korfiatis
        return self.filter(state=Project.APPROVED, is_active=False)
1654 b6fe8bb8 Giorgos Korfiatis
1655 7eadc230 Giorgos Korfiatis
    def expired_projects(self):
1656 7eadc230 Giorgos Korfiatis
        q = (~Q(state=Project.TERMINATED) &
1657 7eadc230 Giorgos Korfiatis
              Q(application__end_date__lt=datetime.now()))
1658 7eadc230 Giorgos Korfiatis
        return self.filter(q)
1659 7eadc230 Giorgos Korfiatis
1660 7eadc230 Giorgos Korfiatis
1661 d6fdc91e Georgios D. Tsoukalas
class Project(models.Model):
1662 e546df49 Georgios D. Tsoukalas
1663 ee45eb81 Giorgos Korfiatis
    application                 =   models.OneToOneField(
1664 4f22664f Georgios D. Tsoukalas
                                            ProjectApplication,
1665 782d9118 Giorgos Korfiatis
                                            related_name='project')
1666 4f22664f Georgios D. Tsoukalas
    last_approval_date          =   models.DateTimeField(null=True)
1667 4f22664f Georgios D. Tsoukalas
1668 4f22664f Georgios D. Tsoukalas
    members                     =   models.ManyToManyField(
1669 4f22664f Georgios D. Tsoukalas
                                            AstakosUser,
1670 4f22664f Georgios D. Tsoukalas
                                            through='ProjectMembership')
1671 4f22664f Georgios D. Tsoukalas
1672 5b9e9530 Giorgos Korfiatis
    deactivation_reason         =   models.CharField(max_length=255, null=True)
1673 5b9e9530 Giorgos Korfiatis
    deactivation_date           =   models.DateTimeField(null=True)
1674 4f22664f Georgios D. Tsoukalas
1675 3c638f72 Giorgos Korfiatis
    creation_date               =   models.DateTimeField(auto_now_add=True)
1676 4f22664f Georgios D. Tsoukalas
    name                        =   models.CharField(
1677 4f22664f Georgios D. Tsoukalas
                                            max_length=80,
1678 4f22664f Georgios D. Tsoukalas
                                            db_index=True,
1679 4f22664f Georgios D. Tsoukalas
                                            unique=True)
1680 425e2e95 Sofia Papagiannaki
1681 b6fe8bb8 Giorgos Korfiatis
    APPROVED    = 1
1682 b6fe8bb8 Giorgos Korfiatis
    SUSPENDED   = 10
1683 b6fe8bb8 Giorgos Korfiatis
    TERMINATED  = 100
1684 5b9e9530 Giorgos Korfiatis
1685 b6fe8bb8 Giorgos Korfiatis
    is_modified                 =   models.BooleanField(default=False,
1686 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1687 b6fe8bb8 Giorgos Korfiatis
    is_active                   =   models.BooleanField(default=True,
1688 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1689 b6fe8bb8 Giorgos Korfiatis
    state                       =   models.IntegerField(default=APPROVED,
1690 123be68a Giorgos Korfiatis
                                                        db_index=True)
1691 123be68a Giorgos Korfiatis
1692 123be68a Giorgos Korfiatis
    objects     =   ProjectManager()
1693 7729e9cc Giorgos Korfiatis
1694 689226c3 Giorgos Korfiatis
    # Compiled queries
1695 689226c3 Giorgos Korfiatis
    Q_TERMINATED  = Q(state=TERMINATED)
1696 689226c3 Giorgos Korfiatis
    Q_SUSPENDED   = Q(state=SUSPENDED)
1697 689226c3 Giorgos Korfiatis
    Q_DEACTIVATED = Q_TERMINATED | Q_SUSPENDED
1698 689226c3 Giorgos Korfiatis
1699 8c7b8bb8 Giorgos Korfiatis
    def __str__(self):
1700 8c7b8bb8 Giorgos Korfiatis
        return _("<project %s '%s'>") % (self.id, self.application.name)
1701 8c7b8bb8 Giorgos Korfiatis
1702 8c7b8bb8 Giorgos Korfiatis
    __repr__ = __str__
1703 8c7b8bb8 Giorgos Korfiatis
1704 e1f31e63 Giorgos Korfiatis
    STATE_DISPLAY = {
1705 e1f31e63 Giorgos Korfiatis
        APPROVED   : 'APPROVED',
1706 e1f31e63 Giorgos Korfiatis
        SUSPENDED  : 'SUSPENDED',
1707 e1f31e63 Giorgos Korfiatis
        TERMINATED : 'TERMINATED'
1708 e1f31e63 Giorgos Korfiatis
        }
1709 e1f31e63 Giorgos Korfiatis
1710 e1f31e63 Giorgos Korfiatis
    def state_display(self):
1711 e1f31e63 Giorgos Korfiatis
        return self.STATE_DISPLAY.get(self.state, _('Unknown'))
1712 e1f31e63 Giorgos Korfiatis
1713 7eadc230 Giorgos Korfiatis
    def expiration_info(self):
1714 7eadc230 Giorgos Korfiatis
        return (str(self.id), self.name, self.state_display(),
1715 7eadc230 Giorgos Korfiatis
                str(self.application.end_date))
1716 7eadc230 Giorgos Korfiatis
1717 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated(self, reason=None):
1718 b6fe8bb8 Giorgos Korfiatis
        if reason is not None:
1719 b6fe8bb8 Giorgos Korfiatis
            return self.state == reason
1720 425e2e95 Sofia Papagiannaki
1721 b6fe8bb8 Giorgos Korfiatis
        return self.state != self.APPROVED
1722 123be68a Giorgos Korfiatis
1723 123be68a Giorgos Korfiatis
    def is_deactivating(self, reason=None):
1724 b6fe8bb8 Giorgos Korfiatis
        if not self.is_active:
1725 b6fe8bb8 Giorgos Korfiatis
            return False
1726 123be68a Giorgos Korfiatis
1727 b6fe8bb8 Giorgos Korfiatis
        return self.is_deactivated(reason)
1728 123be68a Giorgos Korfiatis
1729 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated_strict(self, reason=None):
1730 b6fe8bb8 Giorgos Korfiatis
        if self.is_active:
1731 b6fe8bb8 Giorgos Korfiatis
            return False
1732 123be68a Giorgos Korfiatis
1733 b6fe8bb8 Giorgos Korfiatis
        return self.is_deactivated(reason)
1734 425e2e95 Sofia Papagiannaki
1735 123be68a Giorgos Korfiatis
    ### Deactivation calls
1736 425e2e95 Sofia Papagiannaki
1737 123be68a Giorgos Korfiatis
    def deactivate(self):
1738 b6fe8bb8 Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1739 b6fe8bb8 Giorgos Korfiatis
        self.is_active = False
1740 425e2e95 Sofia Papagiannaki
1741 db99f198 Giorgos Korfiatis
    def reactivate(self):
1742 db99f198 Giorgos Korfiatis
        self.deactivation_date = None
1743 db99f198 Giorgos Korfiatis
        self.is_active = True
1744 db99f198 Giorgos Korfiatis
1745 123be68a Giorgos Korfiatis
    def terminate(self):
1746 123be68a Giorgos Korfiatis
        self.deactivation_reason = 'TERMINATED'
1747 b6fe8bb8 Giorgos Korfiatis
        self.state = self.TERMINATED
1748 123be68a Giorgos Korfiatis
        self.save()
1749 8aed306c Giorgos Korfiatis
1750 db99f198 Giorgos Korfiatis
    def suspend(self):
1751 db99f198 Giorgos Korfiatis
        self.deactivation_reason = 'SUSPENDED'
1752 db99f198 Giorgos Korfiatis
        self.state = self.SUSPENDED
1753 db99f198 Giorgos Korfiatis
        self.save()
1754 db99f198 Giorgos Korfiatis
1755 db99f198 Giorgos Korfiatis
    def resume(self):
1756 db99f198 Giorgos Korfiatis
        self.deactivation_reason = None
1757 db99f198 Giorgos Korfiatis
        self.state = self.APPROVED
1758 db99f198 Giorgos Korfiatis
        self.save()
1759 123be68a Giorgos Korfiatis
1760 123be68a Giorgos Korfiatis
    ### Logical checks
1761 425e2e95 Sofia Papagiannaki
1762 e1a80257 Sofia Papagiannaki
    def is_inconsistent(self):
1763 e1a80257 Sofia Papagiannaki
        now = datetime.now()
1764 5b9e9530 Giorgos Korfiatis
        dates = [self.creation_date,
1765 5b9e9530 Giorgos Korfiatis
                 self.last_approval_date,
1766 5b9e9530 Giorgos Korfiatis
                 self.deactivation_date]
1767 5b9e9530 Giorgos Korfiatis
        return any([date > now for date in dates])
1768 5b9e9530 Giorgos Korfiatis
1769 b6fe8bb8 Giorgos Korfiatis
    def is_active_strict(self):
1770 b6fe8bb8 Giorgos Korfiatis
        return self.is_active and self.state == self.APPROVED
1771 5b9e9530 Giorgos Korfiatis
1772 db99f198 Giorgos Korfiatis
    def is_approved(self):
1773 db99f198 Giorgos Korfiatis
        return self.state == self.APPROVED
1774 db99f198 Giorgos Korfiatis
1775 123be68a Giorgos Korfiatis
    @property
1776 123be68a Giorgos Korfiatis
    def is_alive(self):
1777 72a6e1e8 Giorgos Korfiatis
        return not self.is_terminated
1778 123be68a Giorgos Korfiatis
1779 123be68a Giorgos Korfiatis
    @property
1780 123be68a Giorgos Korfiatis
    def is_terminated(self):
1781 123be68a Giorgos Korfiatis
        return self.is_deactivated(self.TERMINATED)
1782 123be68a Giorgos Korfiatis
1783 123be68a Giorgos Korfiatis
    @property
1784 123be68a Giorgos Korfiatis
    def is_suspended(self):
1785 db99f198 Giorgos Korfiatis
        return self.is_deactivated(self.SUSPENDED)
1786 5b9e9530 Giorgos Korfiatis
1787 5b9e9530 Giorgos Korfiatis
    def violates_resource_grants(self):
1788 e1a80257 Sofia Papagiannaki
        return False
1789 65360c65 Georgios D. Tsoukalas
1790 5b9e9530 Giorgos Korfiatis
    def violates_members_limit(self, adding=0):
1791 5b9e9530 Giorgos Korfiatis
        application = self.application
1792 943d5554 Giorgos Korfiatis
        limit = application.limit_on_members_number
1793 022c61cd Sofia Papagiannaki
        if limit is None:
1794 022c61cd Sofia Papagiannaki
            return False
1795 022c61cd Sofia Papagiannaki
        return (len(self.approved_members) + adding > limit)
1796 5b9e9530 Giorgos Korfiatis
1797 123be68a Giorgos Korfiatis
1798 123be68a Giorgos Korfiatis
    ### Other
1799 5b9e9530 Giorgos Korfiatis
1800 7db8c163 Georgios D. Tsoukalas
    def count_pending_memberships(self):
1801 7db8c163 Georgios D. Tsoukalas
        memb_set = self.projectmembership_set
1802 7db8c163 Georgios D. Tsoukalas
        memb_count = memb_set.filter(state=ProjectMembership.REQUESTED).count()
1803 7db8c163 Georgios D. Tsoukalas
        return memb_count
1804 7db8c163 Georgios D. Tsoukalas
1805 425e2e95 Sofia Papagiannaki
    @property
1806 425e2e95 Sofia Papagiannaki
    def approved_memberships(self):
1807 689226c3 Giorgos Korfiatis
        query = ProjectMembership.Q_ACCEPTED_STATES
1808 5b9e9530 Giorgos Korfiatis
        return self.projectmembership_set.filter(query)
1809 4f22664f Georgios D. Tsoukalas
1810 425e2e95 Sofia Papagiannaki
    @property
1811 425e2e95 Sofia Papagiannaki
    def approved_members(self):
1812 425e2e95 Sofia Papagiannaki
        return [m.person for m in self.approved_memberships]
1813 4f22664f Georgios D. Tsoukalas
1814 4f22664f Georgios D. Tsoukalas
    def add_member(self, user):
1815 bfe23b13 Sofia Papagiannaki
        """
1816 bfe23b13 Sofia Papagiannaki
        Raises:
1817 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1818 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1819 bfe23b13 Sofia Papagiannaki
        """
1820 2a965273 Sofia Papagiannaki
        if isinstance(user, int):
1821 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1822 4f22664f Georgios D. Tsoukalas
1823 ccab6eb5 Sofia Papagiannaki
        m, created = ProjectMembership.objects.get_or_create(
1824 ccab6eb5 Sofia Papagiannaki
            person=user, project=self
1825 2a965273 Sofia Papagiannaki
        )
1826 4f22664f Georgios D. Tsoukalas
        m.accept()
1827 ccab6eb5 Sofia Papagiannaki
1828 4f22664f Georgios D. Tsoukalas
    def remove_member(self, user):
1829 bfe23b13 Sofia Papagiannaki
        """
1830 bfe23b13 Sofia Papagiannaki
        Raises:
1831 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1832 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1833 bfe23b13 Sofia Papagiannaki
            astakos.im.models.ProjectMembership.DoesNotExist
1834 bfe23b13 Sofia Papagiannaki
        """
1835 ccab6eb5 Sofia Papagiannaki
        if isinstance(user, int):
1836 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1837 4f22664f Georgios D. Tsoukalas
1838 bfe23b13 Sofia Papagiannaki
        m = ProjectMembership.objects.get(person=user, project=self)
1839 bfe23b13 Sofia Papagiannaki
        m.remove()
1840 4f22664f Georgios D. Tsoukalas
1841 425e2e95 Sofia Papagiannaki
1842 b6fe8bb8 Giorgos Korfiatis
class PendingMembershipError(Exception):
1843 b6fe8bb8 Giorgos Korfiatis
    pass
1844 b22de10a Sofia Papagiannaki
1845 4f22664f Georgios D. Tsoukalas
1846 db99f198 Giorgos Korfiatis
class ProjectMembershipManager(ForUpdateManager):
1847 db99f198 Giorgos Korfiatis
    pass
1848 db99f198 Giorgos Korfiatis
1849 d6fdc91e Georgios D. Tsoukalas
class ProjectMembership(models.Model):
1850 4f22664f Georgios D. Tsoukalas
1851 425e2e95 Sofia Papagiannaki
    person              =   models.ForeignKey(AstakosUser)
1852 3c638f72 Giorgos Korfiatis
    request_date        =   models.DateField(auto_now_add=True)
1853 d6fdc91e Georgios D. Tsoukalas
    project             =   models.ForeignKey(Project)
1854 d6fdc91e Georgios D. Tsoukalas
1855 db99f198 Giorgos Korfiatis
    REQUESTED           =   0
1856 db99f198 Giorgos Korfiatis
    ACCEPTED            =   1
1857 db99f198 Giorgos Korfiatis
    # User deactivation
1858 db99f198 Giorgos Korfiatis
    USER_SUSPENDED      =   10
1859 db99f198 Giorgos Korfiatis
    # Project deactivation
1860 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED =   100
1861 b6fe8bb8 Giorgos Korfiatis
1862 db99f198 Giorgos Korfiatis
    REMOVED             =   200
1863 db99f198 Giorgos Korfiatis
1864 db99f198 Giorgos Korfiatis
    ASSOCIATED_STATES   =   set([REQUESTED,
1865 db99f198 Giorgos Korfiatis
                                 ACCEPTED,
1866 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1867 db99f198 Giorgos Korfiatis
                                 PROJECT_DEACTIVATED])
1868 db99f198 Giorgos Korfiatis
1869 db99f198 Giorgos Korfiatis
    ACCEPTED_STATES     =   set([ACCEPTED,
1870 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
1871 db99f198 Giorgos Korfiatis
                                 PROJECT_DEACTIVATED])
1872 05617ab9 Kostas Papadimitriou
1873 b6fe8bb8 Giorgos Korfiatis
    state               =   models.IntegerField(default=REQUESTED,
1874 b6fe8bb8 Giorgos Korfiatis
                                                db_index=True)
1875 b6fe8bb8 Giorgos Korfiatis
    is_pending          =   models.BooleanField(default=False, db_index=True)
1876 b6fe8bb8 Giorgos Korfiatis
    is_active           =   models.BooleanField(default=False, db_index=True)
1877 5200e864 Sofia Papagiannaki
    application         =   models.ForeignKey(
1878 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1879 5200e864 Sofia Papagiannaki
                                null=True,
1880 5200e864 Sofia Papagiannaki
                                related_name='memberships')
1881 5200e864 Sofia Papagiannaki
    pending_application =   models.ForeignKey(
1882 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1883 5200e864 Sofia Papagiannaki
                                null=True,
1884 cd633c29 Giorgos Korfiatis
                                related_name='pending_memberships')
1885 d6fdc91e Georgios D. Tsoukalas
    pending_serial      =   models.BigIntegerField(null=True, db_index=True)
1886 425e2e95 Sofia Papagiannaki
1887 425e2e95 Sofia Papagiannaki
    acceptance_date     =   models.DateField(null=True, db_index=True)
1888 425e2e95 Sofia Papagiannaki
    leave_request_date  =   models.DateField(null=True)
1889 2a965273 Sofia Papagiannaki
1890 db99f198 Giorgos Korfiatis
    objects     =   ProjectMembershipManager()
1891 ee45eb81 Giorgos Korfiatis
1892 689226c3 Giorgos Korfiatis
    # Compiled queries
1893 689226c3 Giorgos Korfiatis
    Q_ACCEPTED_STATES = ~Q(state=REQUESTED) & ~Q(state=REMOVED)
1894 5b9e9530 Giorgos Korfiatis
1895 b6fe8bb8 Giorgos Korfiatis
    def get_combined_state(self):
1896 b6fe8bb8 Giorgos Korfiatis
        return self.state, self.is_active, self.is_pending
1897 5b9e9530 Giorgos Korfiatis
1898 0cc22d47 Sofia Papagiannaki
    class Meta:
1899 0cc22d47 Sofia Papagiannaki
        unique_together = ("person", "project")
1900 d6fdc91e Georgios D. Tsoukalas
        #index_together = [["project", "state"]]
1901 bfe23b13 Sofia Papagiannaki
1902 65360c65 Georgios D. Tsoukalas
    def __str__(self):
1903 8c7b8bb8 Giorgos Korfiatis
        return _("<'%s' membership in '%s'>") % (
1904 8c7b8bb8 Giorgos Korfiatis
                self.person.username, self.project)
1905 65360c65 Georgios D. Tsoukalas
1906 65360c65 Georgios D. Tsoukalas
    __repr__ = __str__
1907 65360c65 Georgios D. Tsoukalas
1908 65360c65 Georgios D. Tsoukalas
    def __init__(self, *args, **kwargs):
1909 ee45eb81 Giorgos Korfiatis
        self.state = self.REQUESTED
1910 65360c65 Georgios D. Tsoukalas
        super(ProjectMembership, self).__init__(*args, **kwargs)
1911 65360c65 Georgios D. Tsoukalas
1912 4f22664f Georgios D. Tsoukalas
    def _set_history_item(self, reason, date=None):
1913 4f22664f Georgios D. Tsoukalas
        if isinstance(reason, basestring):
1914 4f22664f Georgios D. Tsoukalas
            reason = ProjectMembershipHistory.reasons.get(reason, -1)
1915 4f22664f Georgios D. Tsoukalas
1916 4f22664f Georgios D. Tsoukalas
        history_item = ProjectMembershipHistory(
1917 4f22664f Georgios D. Tsoukalas
                            serial=self.id,
1918 d0e78bbe Giorgos Korfiatis
                            person=self.person_id,
1919 02d2519e Giorgos Korfiatis
                            project=self.project_id,
1920 8f975b72 Sofia Papagiannaki
                            date=date or datetime.now(),
1921 4f22664f Georgios D. Tsoukalas
                            reason=reason)
1922 4f22664f Georgios D. Tsoukalas
        history_item.save()
1923 4f22664f Georgios D. Tsoukalas
        serial = history_item.id
1924 4f22664f Georgios D. Tsoukalas
1925 14f7f6a5 Giorgos Korfiatis
    def can_accept(self):
1926 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
1927 14f7f6a5 Giorgos Korfiatis
1928 4f22664f Georgios D. Tsoukalas
    def accept(self):
1929 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
1930 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to accept while is pending") % (self,)
1931 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1932 b6fe8bb8 Giorgos Korfiatis
1933 14f7f6a5 Giorgos Korfiatis
        if not self.can_accept():
1934 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to accept in state '%s'") % (self, self.state)
1935 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1936 4f22664f Georgios D. Tsoukalas
1937 65360c65 Georgios D. Tsoukalas
        now = datetime.now()
1938 65360c65 Georgios D. Tsoukalas
        self.acceptance_date = now
1939 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='ACCEPT', date=now)
1940 db99f198 Giorgos Korfiatis
        if self.project.is_approved():
1941 b6fe8bb8 Giorgos Korfiatis
            self.state = self.ACCEPTED
1942 b6fe8bb8 Giorgos Korfiatis
            self.is_pending = True
1943 b6fe8bb8 Giorgos Korfiatis
        else:
1944 db99f198 Giorgos Korfiatis
            self.state = self.PROJECT_DEACTIVATED
1945 b6fe8bb8 Giorgos Korfiatis
1946 65360c65 Georgios D. Tsoukalas
        self.save()
1947 4f22664f Georgios D. Tsoukalas
1948 14f7f6a5 Giorgos Korfiatis
    def can_leave(self):
1949 14f7f6a5 Giorgos Korfiatis
        return self.can_remove()
1950 14f7f6a5 Giorgos Korfiatis
1951 14f7f6a5 Giorgos Korfiatis
    def can_remove(self):
1952 14f7f6a5 Giorgos Korfiatis
        return self.state in self.ACCEPTED_STATES
1953 14f7f6a5 Giorgos Korfiatis
1954 65360c65 Georgios D. Tsoukalas
    def remove(self):
1955 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
1956 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to remove while is pending") % (self,)
1957 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1958 b6fe8bb8 Giorgos Korfiatis
1959 14f7f6a5 Giorgos Korfiatis
        if not self.can_remove():
1960 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to remove in state '%s'") % (self, self.state)
1961 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1962 4f22664f Georgios D. Tsoukalas
1963 ee45eb81 Giorgos Korfiatis
        self._set_history_item(reason='REMOVE')
1964 b6fe8bb8 Giorgos Korfiatis
        self.state = self.REMOVED
1965 b6fe8bb8 Giorgos Korfiatis
        self.is_pending = True
1966 0cc22d47 Sofia Papagiannaki
        self.save()
1967 b8f05f8d Sofia Papagiannaki
1968 14f7f6a5 Giorgos Korfiatis
    def can_reject(self):
1969 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
1970 14f7f6a5 Giorgos Korfiatis
1971 65360c65 Georgios D. Tsoukalas
    def reject(self):
1972 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
1973 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to reject while is pending") % (self,)
1974 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
1975 b6fe8bb8 Giorgos Korfiatis
1976 14f7f6a5 Giorgos Korfiatis
        if not self.can_reject():
1977 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to reject in state '%s'") % (self, self.state)
1978 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1979 65360c65 Georgios D. Tsoukalas
1980 65360c65 Georgios D. Tsoukalas
        # rejected requests don't need sync,
1981 65360c65 Georgios D. Tsoukalas
        # because they were never effected
1982 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='REJECT')
1983 0cc22d47 Sofia Papagiannaki
        self.delete()
1984 b8f05f8d Sofia Papagiannaki
1985 aad0e329 Giorgos Korfiatis
    def can_cancel(self):
1986 aad0e329 Giorgos Korfiatis
        return self.state == self.REQUESTED
1987 aad0e329 Giorgos Korfiatis
1988 aad0e329 Giorgos Korfiatis
    def cancel(self):
1989 aad0e329 Giorgos Korfiatis
        if self.is_pending:
1990 aad0e329 Giorgos Korfiatis
            m = _("%s: attempt to cancel while is pending") % (self,)
1991 aad0e329 Giorgos Korfiatis
            raise AssertionError(m)
1992 aad0e329 Giorgos Korfiatis
1993 aad0e329 Giorgos Korfiatis
        if not self.can_cancel():
1994 aad0e329 Giorgos Korfiatis
            m = _("%s: attempt to cancel in state '%s'") % (self, self.state)
1995 aad0e329 Giorgos Korfiatis
            raise AssertionError(m)
1996 aad0e329 Giorgos Korfiatis
1997 aad0e329 Giorgos Korfiatis
        # rejected requests don't need sync,
1998 aad0e329 Giorgos Korfiatis
        # because they were never effected
1999 aad0e329 Giorgos Korfiatis
        self._set_history_item(reason='CANCEL')
2000 aad0e329 Giorgos Korfiatis
        self.delete()
2001 aad0e329 Giorgos Korfiatis
2002 b6fe8bb8 Giorgos Korfiatis
    def get_diff_quotas(self, sub_list=None, add_list=None):
2003 d2b32360 Giorgos Korfiatis
        if sub_list is None:
2004 d2b32360 Giorgos Korfiatis
            sub_list = []
2005 d2b32360 Giorgos Korfiatis
2006 d2b32360 Giorgos Korfiatis
        if add_list is None:
2007 d2b32360 Giorgos Korfiatis
            add_list = []
2008 d6fdc91e Georgios D. Tsoukalas
2009 d2b32360 Giorgos Korfiatis
        sub_append = sub_list.append
2010 d2b32360 Giorgos Korfiatis
        add_append = add_list.append
2011 d75c432e Sofia Papagiannaki
        holder = self.person.uuid
2012 d6fdc91e Georgios D. Tsoukalas
2013 d6fdc91e Georgios D. Tsoukalas
        synced_application = self.application
2014 d6fdc91e Georgios D. Tsoukalas
        if synced_application is not None:
2015 5f2e4042 Sofia Papagiannaki
            cur_grants = synced_application.projectresourcegrant_set.all()
2016 d6fdc91e Georgios D. Tsoukalas
            for grant in cur_grants:
2017 d2b32360 Giorgos Korfiatis
                sub_append(QuotaLimits(
2018 d2b32360 Giorgos Korfiatis
                               holder       = holder,
2019 f3e93707 Sofia Papagiannaki
                               resource     = str(grant.resource),
2020 d2b32360 Giorgos Korfiatis
                               capacity     = grant.member_capacity,
2021 d2b32360 Giorgos Korfiatis
                               import_limit = grant.member_import_limit,
2022 d2b32360 Giorgos Korfiatis
                               export_limit = grant.member_export_limit))
2023 d6fdc91e Georgios D. Tsoukalas
2024 b6fe8bb8 Giorgos Korfiatis
        pending_application = self.pending_application
2025 b6fe8bb8 Giorgos Korfiatis
        if pending_application is not None:
2026 b6fe8bb8 Giorgos Korfiatis
            new_grants = pending_application.projectresourcegrant_set.all()
2027 d6fdc91e Georgios D. Tsoukalas
            for new_grant in new_grants:
2028 d2b32360 Giorgos Korfiatis
                add_append(QuotaLimits(
2029 d2b32360 Giorgos Korfiatis
                               holder       = holder,
2030 f3e93707 Sofia Papagiannaki
                               resource     = str(new_grant.resource),
2031 974ee6a6 Sofia Papagiannaki
                               capacity     = new_grant.member_capacity,
2032 974ee6a6 Sofia Papagiannaki
                               import_limit = new_grant.member_import_limit,
2033 974ee6a6 Sofia Papagiannaki
                               export_limit = new_grant.member_export_limit))
2034 d6fdc91e Georgios D. Tsoukalas
2035 d2b32360 Giorgos Korfiatis
        return (sub_list, add_list)
2036 65360c65 Georgios D. Tsoukalas
2037 ee45eb81 Giorgos Korfiatis
    def set_sync(self):
2038 b6fe8bb8 Giorgos Korfiatis
        if not self.is_pending:
2039 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to sync a non pending membership") % (self,)
2040 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
2041 b6fe8bb8 Giorgos Korfiatis
2042 ee45eb81 Giorgos Korfiatis
        state = self.state
2043 b6fe8bb8 Giorgos Korfiatis
        if state == self.ACCEPTED:
2044 ee45eb81 Giorgos Korfiatis
            pending_application = self.pending_application
2045 ee45eb81 Giorgos Korfiatis
            if pending_application is None:
2046 ee45eb81 Giorgos Korfiatis
                m = _("%s: attempt to sync an empty pending application") % (
2047 8c7b8bb8 Giorgos Korfiatis
                    self,)
2048 ee45eb81 Giorgos Korfiatis
                raise AssertionError(m)
2049 b6fe8bb8 Giorgos Korfiatis
2050 ee45eb81 Giorgos Korfiatis
            self.application = pending_application
2051 b6fe8bb8 Giorgos Korfiatis
            self.is_active = True
2052 b6fe8bb8 Giorgos Korfiatis
2053 ee45eb81 Giorgos Korfiatis
            self.pending_application = None
2054 ee45eb81 Giorgos Korfiatis
            self.pending_serial = None
2055 ee45eb81 Giorgos Korfiatis
2056 ee45eb81 Giorgos Korfiatis
            # project.application may have changed in the meantime,
2057 ee45eb81 Giorgos Korfiatis
            # in which case we stay PENDING;
2058 ee45eb81 Giorgos Korfiatis
            # we are safe to check due to select_for_update
2059 ee45eb81 Giorgos Korfiatis
            if self.application == self.project.application:
2060 b6fe8bb8 Giorgos Korfiatis
                self.is_pending = False
2061 ee45eb81 Giorgos Korfiatis
            self.save()
2062 b6fe8bb8 Giorgos Korfiatis
2063 db99f198 Giorgos Korfiatis
        elif state == self.PROJECT_DEACTIVATED:
2064 5b9e9530 Giorgos Korfiatis
            if self.pending_application:
2065 5b9e9530 Giorgos Korfiatis
                m = _("%s: attempt to sync in state '%s' "
2066 5b9e9530 Giorgos Korfiatis
                      "with a pending application") % (self, state)
2067 5b9e9530 Giorgos Korfiatis
                raise AssertionError(m)
2068 b6fe8bb8 Giorgos Korfiatis
2069 5b9e9530 Giorgos Korfiatis
            self.application = None
2070 2a666c36 Giorgos Korfiatis
            self.is_active = False
2071 5b9e9530 Giorgos Korfiatis
            self.pending_serial = None
2072 b6fe8bb8 Giorgos Korfiatis
            self.is_pending = False
2073 5b9e9530 Giorgos Korfiatis
            self.save()
2074 b6fe8bb8 Giorgos Korfiatis
2075 b6fe8bb8 Giorgos Korfiatis
        elif state == self.REMOVED:
2076 ee45eb81 Giorgos Korfiatis
            self.delete()
2077 b6fe8bb8 Giorgos Korfiatis
2078 ee45eb81 Giorgos Korfiatis
        else:
2079 ee45eb81 Giorgos Korfiatis
            m = _("%s: attempt to sync in state '%s'") % (self, state)
2080 ee45eb81 Giorgos Korfiatis
            raise AssertionError(m)
2081 ee45eb81 Giorgos Korfiatis
2082 49b74233 Georgios D. Tsoukalas
    def reset_sync(self):
2083 b6fe8bb8 Giorgos Korfiatis
        if not self.is_pending:
2084 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to reset a non pending membership") % (self,)
2085 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
2086 b6fe8bb8 Giorgos Korfiatis
2087 49b74233 Georgios D. Tsoukalas
        state = self.state
2088 db99f198 Giorgos Korfiatis
        if state in [self.ACCEPTED, self.PROJECT_DEACTIVATED, self.REMOVED]:
2089 49b74233 Georgios D. Tsoukalas
            self.pending_application = None
2090 49b74233 Georgios D. Tsoukalas
            self.pending_serial = None
2091 49b74233 Georgios D. Tsoukalas
            self.save()
2092 49b74233 Georgios D. Tsoukalas
        else:
2093 49b74233 Georgios D. Tsoukalas
            m = _("%s: attempt to reset sync in state '%s'") % (self, state)
2094 49b74233 Georgios D. Tsoukalas
            raise AssertionError(m)
2095 49b74233 Georgios D. Tsoukalas
2096 ee45eb81 Giorgos Korfiatis
class Serial(models.Model):
2097 ee45eb81 Giorgos Korfiatis
    serial  =   models.AutoField(primary_key=True)
2098 ee45eb81 Giorgos Korfiatis
2099 ee45eb81 Giorgos Korfiatis
def new_serial():
2100 5200e864 Sofia Papagiannaki
    s = Serial.objects.create()
2101 c70968bd Giorgos Korfiatis
    serial = s.serial
2102 c70968bd Giorgos Korfiatis
    s.delete()
2103 c70968bd Giorgos Korfiatis
    return serial
2104 82d7e9ef Georgios D. Tsoukalas
2105 333f6a72 Sofia Papagiannaki
def sync_finish_serials(serials_to_ack=None):
2106 333f6a72 Sofia Papagiannaki
    if serials_to_ack is None:
2107 333f6a72 Sofia Papagiannaki
        serials_to_ack = qh_query_serials([])
2108 333f6a72 Sofia Papagiannaki
2109 333f6a72 Sofia Papagiannaki
    serials_to_ack = set(serials_to_ack)
2110 d6fdc91e Georgios D. Tsoukalas
    sfu = ProjectMembership.objects.select_for_update()
2111 333f6a72 Sofia Papagiannaki
    memberships = list(sfu.filter(pending_serial__isnull=False))
2112 333f6a72 Sofia Papagiannaki
2113 60ca2f6f Giorgos Korfiatis
    if memberships:
2114 60ca2f6f Giorgos Korfiatis
        for membership in memberships:
2115 60ca2f6f Giorgos Korfiatis
            serial = membership.pending_serial
2116 60ca2f6f Giorgos Korfiatis
            if serial in serials_to_ack:
2117 60ca2f6f Giorgos Korfiatis
                membership.set_sync()
2118 60ca2f6f Giorgos Korfiatis
            else:
2119 60ca2f6f Giorgos Korfiatis
                membership.reset_sync()
2120 60ca2f6f Giorgos Korfiatis
2121 60ca2f6f Giorgos Korfiatis
        transaction.commit()
2122 60ca2f6f Giorgos Korfiatis
2123 ee45eb81 Giorgos Korfiatis
    qh_ack_serials(list(serials_to_ack))
2124 333f6a72 Sofia Papagiannaki
    return len(memberships)
2125 82d7e9ef Georgios D. Tsoukalas
2126 762900a2 Giorgos Korfiatis
def pre_sync_projects(sync=True):
2127 b6fe8bb8 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
2128 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
2129 b6fe8bb8 Giorgos Korfiatis
    psfu = Project.objects.select_for_update()
2130 b6fe8bb8 Giorgos Korfiatis
2131 762900a2 Giorgos Korfiatis
    modified = list(psfu.modified_projects())
2132 762900a2 Giorgos Korfiatis
    if sync:
2133 762900a2 Giorgos Korfiatis
        for project in modified:
2134 762900a2 Giorgos Korfiatis
            objects = project.projectmembership_set.select_for_update()
2135 762900a2 Giorgos Korfiatis
2136 762900a2 Giorgos Korfiatis
            memberships = objects.filter(state=ACCEPTED)
2137 762900a2 Giorgos Korfiatis
            for membership in memberships:
2138 762900a2 Giorgos Korfiatis
                membership.is_pending = True
2139 762900a2 Giorgos Korfiatis
                membership.save()
2140 762900a2 Giorgos Korfiatis
2141 762900a2 Giorgos Korfiatis
    reactivating = list(psfu.reactivating_projects())
2142 762900a2 Giorgos Korfiatis
    if sync:
2143 762900a2 Giorgos Korfiatis
        for project in reactivating:
2144 762900a2 Giorgos Korfiatis
            objects = project.projectmembership_set.select_for_update()
2145 762900a2 Giorgos Korfiatis
2146 762900a2 Giorgos Korfiatis
            memberships = objects.filter(state=PROJECT_DEACTIVATED)
2147 762900a2 Giorgos Korfiatis
            for membership in memberships:
2148 762900a2 Giorgos Korfiatis
                membership.is_pending = True
2149 762900a2 Giorgos Korfiatis
                membership.state = ACCEPTED
2150 762900a2 Giorgos Korfiatis
                membership.save()
2151 762900a2 Giorgos Korfiatis
2152 762900a2 Giorgos Korfiatis
    deactivating = list(psfu.deactivating_projects())
2153 762900a2 Giorgos Korfiatis
    if sync:
2154 762900a2 Giorgos Korfiatis
        for project in deactivating:
2155 762900a2 Giorgos Korfiatis
            objects = project.projectmembership_set.select_for_update()
2156 762900a2 Giorgos Korfiatis
2157 762900a2 Giorgos Korfiatis
            # Note: we keep a user-level deactivation
2158 762900a2 Giorgos Korfiatis
            # (e.g. USER_SUSPENDED) intact
2159 762900a2 Giorgos Korfiatis
            memberships = objects.filter(state=ACCEPTED)
2160 762900a2 Giorgos Korfiatis
            for membership in memberships:
2161 762900a2 Giorgos Korfiatis
                membership.is_pending = True
2162 762900a2 Giorgos Korfiatis
                membership.state = PROJECT_DEACTIVATED
2163 762900a2 Giorgos Korfiatis
                membership.save()
2164 762900a2 Giorgos Korfiatis
2165 762900a2 Giorgos Korfiatis
    return (modified, reactivating, deactivating)
2166 b6fe8bb8 Giorgos Korfiatis
2167 84a3f701 Giorgos Korfiatis
def do_sync_projects():
2168 82d7e9ef Georgios D. Tsoukalas
2169 b6fe8bb8 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
2170 d6fdc91e Georgios D. Tsoukalas
    objects = ProjectMembership.objects.select_for_update()
2171 82d7e9ef Georgios D. Tsoukalas
2172 d2b32360 Giorgos Korfiatis
    sub_quota, add_quota = [], []
2173 65360c65 Georgios D. Tsoukalas
2174 ee45eb81 Giorgos Korfiatis
    serial = new_serial()
2175 d6fdc91e Georgios D. Tsoukalas
2176 b6fe8bb8 Giorgos Korfiatis
    pending = objects.filter(is_pending=True)
2177 d6fdc91e Georgios D. Tsoukalas
    for membership in pending:
2178 d6fdc91e Georgios D. Tsoukalas
2179 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_application:
2180 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_application is not None (%s)" % (
2181 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_application)
2182 d6fdc91e Georgios D. Tsoukalas
            raise AssertionError(m)
2183 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_serial:
2184 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_serial is not None (%s)" % (
2185 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_serial)
2186 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2187 b8f05f8d Sofia Papagiannaki
2188 b6fe8bb8 Giorgos Korfiatis
        if membership.state == ACCEPTED:
2189 b6fe8bb8 Giorgos Korfiatis
            membership.pending_application = membership.project.application
2190 d6fdc91e Georgios D. Tsoukalas
2191 d6fdc91e Georgios D. Tsoukalas
        membership.pending_serial = serial
2192 b6fe8bb8 Giorgos Korfiatis
        membership.get_diff_quotas(sub_quota, add_quota)
2193 d6fdc91e Georgios D. Tsoukalas
        membership.save()
2194 d6fdc91e Georgios D. Tsoukalas
2195 d6fdc91e Georgios D. Tsoukalas
    transaction.commit()
2196 ee45eb81 Giorgos Korfiatis
    # ProjectApplication.approve() unblocks here
2197 ee45eb81 Giorgos Korfiatis
    # and can set PENDING an already PENDING membership
2198 ee45eb81 Giorgos Korfiatis
    # which has been scheduled to sync with the old project.application
2199 ee45eb81 Giorgos Korfiatis
    # Need to check in ProjectMembership.set_sync()
2200 ee45eb81 Giorgos Korfiatis
2201 5cfd4acb Sofia Papagiannaki
    r = qh_add_quota(serial, sub_quota, add_quota)
2202 333f6a72 Sofia Papagiannaki
    if r:
2203 333f6a72 Sofia Papagiannaki
        m = "cannot sync serial: %d" % serial
2204 333f6a72 Sofia Papagiannaki
        raise RuntimeError(m)
2205 333f6a72 Sofia Papagiannaki
2206 b6fe8bb8 Giorgos Korfiatis
    return serial
2207 5b9e9530 Giorgos Korfiatis
2208 84a3f701 Giorgos Korfiatis
def post_sync_projects():
2209 5b9e9530 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
2210 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
2211 5b9e9530 Giorgos Korfiatis
    psfu = Project.objects.select_for_update()
2212 5b9e9530 Giorgos Korfiatis
2213 b6fe8bb8 Giorgos Korfiatis
    modified = psfu.modified_projects()
2214 b6fe8bb8 Giorgos Korfiatis
    for project in modified:
2215 5b9e9530 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2216 5b9e9530 Giorgos Korfiatis
2217 b6fe8bb8 Giorgos Korfiatis
        memberships = list(objects.filter(state=ACCEPTED, is_pending=True))
2218 b6fe8bb8 Giorgos Korfiatis
        if not memberships:
2219 b6fe8bb8 Giorgos Korfiatis
            project.is_modified = False
2220 b6fe8bb8 Giorgos Korfiatis
            project.save()
2221 5b9e9530 Giorgos Korfiatis
2222 db99f198 Giorgos Korfiatis
    reactivating = psfu.reactivating_projects()
2223 db99f198 Giorgos Korfiatis
    for project in reactivating:
2224 db99f198 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2225 db99f198 Giorgos Korfiatis
        memberships = list(objects.filter(Q(state=PROJECT_DEACTIVATED) |
2226 db99f198 Giorgos Korfiatis
                                          Q(is_pending=True)))
2227 db99f198 Giorgos Korfiatis
        if not memberships:
2228 db99f198 Giorgos Korfiatis
            project.reactivate()
2229 db99f198 Giorgos Korfiatis
            project.save()
2230 db99f198 Giorgos Korfiatis
2231 db99f198 Giorgos Korfiatis
    deactivating = psfu.deactivating_projects()
2232 db99f198 Giorgos Korfiatis
    for project in deactivating:
2233 5b9e9530 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
2234 b6fe8bb8 Giorgos Korfiatis
2235 5b9e9530 Giorgos Korfiatis
        memberships = list(objects.filter(Q(state=ACCEPTED) |
2236 b6fe8bb8 Giorgos Korfiatis
                                          Q(is_pending=True)))
2237 5b9e9530 Giorgos Korfiatis
        if not memberships:
2238 b6fe8bb8 Giorgos Korfiatis
            project.deactivate()
2239 5b9e9530 Giorgos Korfiatis
            project.save()
2240 5b9e9530 Giorgos Korfiatis
2241 5b9e9530 Giorgos Korfiatis
    transaction.commit()
2242 5b9e9530 Giorgos Korfiatis
2243 140da2d1 Giorgos Korfiatis
def _sync_projects(sync):
2244 b6fe8bb8 Giorgos Korfiatis
    sync_finish_serials()
2245 762900a2 Giorgos Korfiatis
    # Informative only -- no select_for_update()
2246 93c4dc6b Giorgos Korfiatis
    pending = list(ProjectMembership.objects.filter(is_pending=True))
2247 762900a2 Giorgos Korfiatis
2248 762900a2 Giorgos Korfiatis
    projects_log = pre_sync_projects(sync)
2249 762900a2 Giorgos Korfiatis
    if sync:
2250 762900a2 Giorgos Korfiatis
        serial = do_sync_projects()
2251 762900a2 Giorgos Korfiatis
        sync_finish_serials([serial])
2252 762900a2 Giorgos Korfiatis
        post_sync_projects()
2253 762900a2 Giorgos Korfiatis
2254 762900a2 Giorgos Korfiatis
    return (pending, projects_log)
2255 84a3f701 Giorgos Korfiatis
2256 140da2d1 Giorgos Korfiatis
def sync_projects(sync=True, retries=3, retry_wait=1.0):
2257 84a3f701 Giorgos Korfiatis
    return lock_sync(_sync_projects,
2258 140da2d1 Giorgos Korfiatis
                     args=[sync],
2259 84a3f701 Giorgos Korfiatis
                     retries=retries,
2260 84a3f701 Giorgos Korfiatis
                     retry_wait=retry_wait)
2261 84a3f701 Giorgos Korfiatis
2262 5aac5dc4 Giorgos Korfiatis
2263 5aac5dc4 Giorgos Korfiatis
def all_users_quotas(users):
2264 5aac5dc4 Giorgos Korfiatis
    quotas = {}
2265 5aac5dc4 Giorgos Korfiatis
    for user in users:
2266 5aac5dc4 Giorgos Korfiatis
        quotas[user.uuid] = user.all_quotas()
2267 5aac5dc4 Giorgos Korfiatis
    return quotas
2268 5aac5dc4 Giorgos Korfiatis
2269 140da2d1 Giorgos Korfiatis
def _sync_users(users, sync):
2270 84a3f701 Giorgos Korfiatis
    sync_finish_serials()
2271 84a3f701 Giorgos Korfiatis
2272 84a3f701 Giorgos Korfiatis
    existing, nonexisting = qh_check_users(users)
2273 84a3f701 Giorgos Korfiatis
    resources = get_resource_names()
2274 5aac5dc4 Giorgos Korfiatis
    registered_quotas = qh_get_quota_limits(existing, resources)
2275 5aac5dc4 Giorgos Korfiatis
    astakos_quotas = all_users_quotas(users)
2276 d6fdc91e Georgios D. Tsoukalas
2277 140da2d1 Giorgos Korfiatis
    if sync:
2278 84a3f701 Giorgos Korfiatis
        r = register_users(nonexisting)
2279 5aac5dc4 Giorgos Korfiatis
        r = send_quotas(astakos_quotas)
2280 84a3f701 Giorgos Korfiatis
2281 5aac5dc4 Giorgos Korfiatis
    return (existing, nonexisting, registered_quotas, astakos_quotas)
2282 84a3f701 Giorgos Korfiatis
2283 140da2d1 Giorgos Korfiatis
def sync_users(users, sync=True, retries=3, retry_wait=1.0):
2284 84a3f701 Giorgos Korfiatis
    return lock_sync(_sync_users,
2285 140da2d1 Giorgos Korfiatis
                     args=[users, sync],
2286 84a3f701 Giorgos Korfiatis
                     retries=retries,
2287 84a3f701 Giorgos Korfiatis
                     retry_wait=retry_wait)
2288 84a3f701 Giorgos Korfiatis
2289 140da2d1 Giorgos Korfiatis
def sync_all_users(sync=True, retries=3, retry_wait=1.0):
2290 d5570293 Giorgos Korfiatis
    users = AstakosUser.objects.filter(is_active=True)
2291 140da2d1 Giorgos Korfiatis
    return sync_users(users, sync, retries=retries, retry_wait=retry_wait)
2292 84a3f701 Giorgos Korfiatis
2293 84a3f701 Giorgos Korfiatis
def lock_sync(func, args=[], kwargs={}, retries=3, retry_wait=1.0):
2294 14695557 Giorgos Korfiatis
    transaction.commit()
2295 14695557 Giorgos Korfiatis
2296 d6fdc91e Georgios D. Tsoukalas
    cursor = connection.cursor()
2297 d6fdc91e Georgios D. Tsoukalas
    locked = True
2298 d6fdc91e Georgios D. Tsoukalas
    try:
2299 d6fdc91e Georgios D. Tsoukalas
        while 1:
2300 d6fdc91e Georgios D. Tsoukalas
            cursor.execute("SELECT pg_try_advisory_lock(1)")
2301 d6fdc91e Georgios D. Tsoukalas
            r = cursor.fetchone()
2302 d6fdc91e Georgios D. Tsoukalas
            if r is None:
2303 d6fdc91e Georgios D. Tsoukalas
                m = "Impossible"
2304 d6fdc91e Georgios D. Tsoukalas
                raise AssertionError(m)
2305 d6fdc91e Georgios D. Tsoukalas
            locked = r[0]
2306 d6fdc91e Georgios D. Tsoukalas
            if locked:
2307 d6fdc91e Georgios D. Tsoukalas
                break
2308 d6fdc91e Georgios D. Tsoukalas
2309 d6fdc91e Georgios D. Tsoukalas
            retries -= 1
2310 d6fdc91e Georgios D. Tsoukalas
            if retries <= 0:
2311 d6fdc91e Georgios D. Tsoukalas
                return False
2312 d6fdc91e Georgios D. Tsoukalas
            sleep(retry_wait)
2313 d6fdc91e Georgios D. Tsoukalas
2314 84a3f701 Giorgos Korfiatis
        return func(*args, **kwargs)
2315 d6fdc91e Georgios D. Tsoukalas
2316 d6fdc91e Georgios D. Tsoukalas
    finally:
2317 d6fdc91e Georgios D. Tsoukalas
        if locked:
2318 d6fdc91e Georgios D. Tsoukalas
            cursor.execute("SELECT pg_advisory_unlock(1)")
2319 d6fdc91e Georgios D. Tsoukalas
            cursor.fetchall()
2320 4f22664f Georgios D. Tsoukalas
2321 e1a80257 Sofia Papagiannaki
2322 0cc22d47 Sofia Papagiannaki
class ProjectMembershipHistory(models.Model):
2323 425e2e95 Sofia Papagiannaki
    reasons_list    =   ['ACCEPT', 'REJECT', 'REMOVE']
2324 425e2e95 Sofia Papagiannaki
    reasons         =   dict((k, v) for v, k in enumerate(reasons_list))
2325 425e2e95 Sofia Papagiannaki
2326 d0e78bbe Giorgos Korfiatis
    person  =   models.BigIntegerField()
2327 02d2519e Giorgos Korfiatis
    project =   models.BigIntegerField()
2328 3c638f72 Giorgos Korfiatis
    date    =   models.DateField(auto_now_add=True)
2329 425e2e95 Sofia Papagiannaki
    reason  =   models.IntegerField()
2330 425e2e95 Sofia Papagiannaki
    serial  =   models.BigIntegerField()
2331 fc655b6f Kostas Papadimitriou
2332 fcc1e93f Sofia Papagiannaki
### SIGNALS ###
2333 fcc1e93f Sofia Papagiannaki
################
2334 b22de10a Sofia Papagiannaki
2335 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
2336 ff9290ec Sofia Papagiannaki
    try:
2337 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
2338 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
2339 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
2340 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
2341 ff9290ec Sofia Papagiannaki
        extended_user.save()
2342 67be1883 Olga Brani
        if not extended_user.has_auth_provider('local'):
2343 67be1883 Olga Brani
            extended_user.add_auth_provider('local')
2344 fc1e2f02 Sofia Papagiannaki
    except BaseException, e:
2345 fc1e2f02 Sofia Papagiannaki
        logger.exception(e)
2346 ff9290ec Sofia Papagiannaki
2347 5ce3ce4f Sofia Papagiannaki
2348 fc1e2f02 Sofia Papagiannaki
def fix_superusers(sender, **kwargs):
2349 fc1e2f02 Sofia Papagiannaki
    # Associate superusers with AstakosUser
2350 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
2351 ff9290ec Sofia Papagiannaki
    for u in admins:
2352 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
2353 bfe23b13 Sofia Papagiannaki
post_syncdb.connect(fix_superusers)
2354 ff9290ec Sofia Papagiannaki
2355 ff9290ec Sofia Papagiannaki
2356 fc1e2f02 Sofia Papagiannaki
def user_post_save(sender, instance, created, **kwargs):
2357 aa4109d4 Sofia Papagiannaki
    if not created:
2358 aa4109d4 Sofia Papagiannaki
        return
2359 fc1e2f02 Sofia Papagiannaki
    create_astakos_user(instance)
2360 bfe23b13 Sofia Papagiannaki
post_save.connect(user_post_save, sender=User)
2361 ff9290ec Sofia Papagiannaki
2362 fc1e2f02 Sofia Papagiannaki
def astakosuser_post_save(sender, instance, created, **kwargs):
2363 21e0fdad Giorgos Korfiatis
    pass
2364 21e0fdad Giorgos Korfiatis
2365 bfe23b13 Sofia Papagiannaki
post_save.connect(astakosuser_post_save, sender=AstakosUser)
2366 fc1e2f02 Sofia Papagiannaki
2367 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
2368 0514bcc7 Giorgos Korfiatis
    pass
2369 0514bcc7 Giorgos Korfiatis
2370 bfe23b13 Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
2371 bfe23b13 Sofia Papagiannaki
2372 bfe23b13 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
2373 bfe23b13 Sofia Papagiannaki
    if not instance.auth_token:
2374 bfe23b13 Sofia Papagiannaki
        instance.renew_token()
2375 bf0c6de5 Sofia Papagiannaki
pre_save.connect(renew_token, sender=AstakosUser)
2376 2e90e3ec Kostas Papadimitriou
pre_save.connect(renew_token, sender=Service)