Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 61a1b2d2

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

873 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
874 49790d9d Sofia Papagiannaki
        after activating.
875 49790d9d Sofia Papagiannaki

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

878 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
879 49790d9d Sofia Papagiannaki
        return ``None``.
880 49790d9d Sofia Papagiannaki

881 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
882 49790d9d Sofia Papagiannaki

883 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
884 49790d9d Sofia Papagiannaki
        """
885 49790d9d Sofia Papagiannaki
        try:
886 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
887 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
888 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
889 49790d9d Sofia Papagiannaki
                email_change.delete()
890 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
891 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
892 49790d9d Sofia Papagiannaki
            try:
893 789a5951 Sofia Papagiannaki
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
894 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
895 49790d9d Sofia Papagiannaki
                pass
896 49790d9d Sofia Papagiannaki
            else:
897 73fbaec4 Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
898 49790d9d Sofia Papagiannaki
            # update user
899 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
900 34a76cdb Kostas Papadimitriou
            old_email = user.email
901 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
902 49790d9d Sofia Papagiannaki
            user.save()
903 49790d9d Sofia Papagiannaki
            email_change.delete()
904 34a76cdb Kostas Papadimitriou
            msg = "User %d changed email from %s to %s" % (user.pk, old_email,
905 34a76cdb Kostas Papadimitriou
                                                          user.email)
906 34a76cdb Kostas Papadimitriou
            logger.log(LOGGING_LEVEL, msg)
907 49790d9d Sofia Papagiannaki
            return user
908 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
909 73fbaec4 Sofia Papagiannaki
            raise ValueError(_('Invalid activation key.'))
910 49790d9d Sofia Papagiannaki
911 49790d9d Sofia Papagiannaki
912 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
913 73fbaec4 Sofia Papagiannaki
    new_email_address = models.EmailField(
914 73fbaec4 Sofia Papagiannaki
        _(u'new e-mail address'),
915 73fbaec4 Sofia Papagiannaki
        help_text=_('Your old email address will be used until you verify your new one.'))
916 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
917 34a76cdb Kostas Papadimitriou
        AstakosUser, unique=True, related_name='emailchanges')
918 49790d9d Sofia Papagiannaki
    requested_at = models.DateTimeField(default=datetime.now())
919 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
920 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
921 49790d9d Sofia Papagiannaki
922 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
923 49790d9d Sofia Papagiannaki
924 34a76cdb Kostas Papadimitriou
    def get_url(self):
925 34a76cdb Kostas Papadimitriou
        return reverse('email_change_confirm',
926 34a76cdb Kostas Papadimitriou
                      kwargs={'activation_key': self.activation_key})
927 34a76cdb Kostas Papadimitriou
928 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
929 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
930 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
931 ff9290ec Sofia Papagiannaki
932 6b03a847 Sofia Papagiannaki
933 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
934 ca828a10 Sofia Papagiannaki
    """
935 ca828a10 Sofia Papagiannaki
    Model for registring invitations
936 ca828a10 Sofia Papagiannaki
    """
937 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
938 1eec103a Sofia Papagiannaki
    email = models.EmailField()
939 ca828a10 Sofia Papagiannaki
940 5ce3ce4f Sofia Papagiannaki
941 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
942 fc1e2f02 Sofia Papagiannaki
    while True:
943 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
944 fc1e2f02 Sofia Papagiannaki
        try:
945 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
946 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
947 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
948 fc1e2f02 Sofia Papagiannaki
            return code
949 fc1e2f02 Sofia Papagiannaki
950 5ce3ce4f Sofia Papagiannaki
951 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
952 fc1e2f02 Sofia Papagiannaki
    try:
953 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
954 fc1e2f02 Sofia Papagiannaki
        return term
955 fc1e2f02 Sofia Papagiannaki
    except IndexError:
956 fc1e2f02 Sofia Papagiannaki
        pass
957 fc1e2f02 Sofia Papagiannaki
    return None
958 fc1e2f02 Sofia Papagiannaki
959 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
960 ef20ea07 Sofia Papagiannaki
    """
961 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
962 ef20ea07 Sofia Papagiannaki
    """
963 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
964 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
965 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
966 ef20ea07 Sofia Papagiannaki
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
967 ef20ea07 Sofia Papagiannaki
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
968 ef20ea07 Sofia Papagiannaki
    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
969 ef20ea07 Sofia Papagiannaki
    username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
970 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
971 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
972 c630fee6 Kostas Papadimitriou
    info = models.TextField(default="", null=True, blank=True)
973 d2633501 Kostas Papadimitriou
974 678b2236 Sofia Papagiannaki
    class Meta:
975 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
976 ef20ea07 Sofia Papagiannaki
977 c630fee6 Kostas Papadimitriou
    def get_user_instance(self):
978 c630fee6 Kostas Papadimitriou
        d = self.__dict__
979 c630fee6 Kostas Papadimitriou
        d.pop('_state', None)
980 c630fee6 Kostas Papadimitriou
        d.pop('id', None)
981 c630fee6 Kostas Papadimitriou
        d.pop('token', None)
982 c630fee6 Kostas Papadimitriou
        d.pop('created', None)
983 c630fee6 Kostas Papadimitriou
        d.pop('info', None)
984 c630fee6 Kostas Papadimitriou
        user = AstakosUser(**d)
985 c630fee6 Kostas Papadimitriou
986 c630fee6 Kostas Papadimitriou
        return user
987 c630fee6 Kostas Papadimitriou
988 ef20ea07 Sofia Papagiannaki
    @property
989 ef20ea07 Sofia Papagiannaki
    def realname(self):
990 ef20ea07 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
991 ef20ea07 Sofia Papagiannaki
992 ef20ea07 Sofia Papagiannaki
    @realname.setter
993 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
994 ef20ea07 Sofia Papagiannaki
        parts = value.split(' ')
995 ef20ea07 Sofia Papagiannaki
        if len(parts) == 2:
996 ef20ea07 Sofia Papagiannaki
            self.first_name = parts[0]
997 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[1]
998 ef20ea07 Sofia Papagiannaki
        else:
999 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[0]
1000 2e90e3ec Kostas Papadimitriou
1001 ef20ea07 Sofia Papagiannaki
    def save(self, **kwargs):
1002 ef20ea07 Sofia Papagiannaki
        if not self.id:
1003 ef20ea07 Sofia Papagiannaki
            # set username
1004 ef20ea07 Sofia Papagiannaki
            while not self.username:
1005 ef20ea07 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
1006 ef20ea07 Sofia Papagiannaki
                try:
1007 ef20ea07 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
1008 ef20ea07 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
1009 ef20ea07 Sofia Papagiannaki
                    self.username = username
1010 ef20ea07 Sofia Papagiannaki
        super(PendingThirdPartyUser, self).save(**kwargs)
1011 ef20ea07 Sofia Papagiannaki
1012 d2633501 Kostas Papadimitriou
    def generate_token(self):
1013 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1014 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1015 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1016 d2633501 Kostas Papadimitriou
1017 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1018 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1019 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1020 bf0c6de5 Sofia Papagiannaki
1021 fcc1e93f Sofia Papagiannaki
1022 fcc1e93f Sofia Papagiannaki
### PROJECTS ###
1023 fcc1e93f Sofia Papagiannaki
################
1024 fcc1e93f Sofia Papagiannaki
1025 e546df49 Georgios D. Tsoukalas
def synced_model_metaclass(class_name, class_parents, class_attributes):
1026 65360c65 Georgios D. Tsoukalas
1027 e546df49 Georgios D. Tsoukalas
    new_attributes = {}
1028 e546df49 Georgios D. Tsoukalas
    sync_attributes = {}
1029 65360c65 Georgios D. Tsoukalas
1030 e546df49 Georgios D. Tsoukalas
    for name, value in class_attributes.iteritems():
1031 e546df49 Georgios D. Tsoukalas
        sync, underscore, rest = name.partition('_')
1032 e546df49 Georgios D. Tsoukalas
        if sync == 'sync' and underscore == '_':
1033 e546df49 Georgios D. Tsoukalas
            sync_attributes[rest] = value
1034 e546df49 Georgios D. Tsoukalas
        else:
1035 e546df49 Georgios D. Tsoukalas
            new_attributes[name] = value
1036 65360c65 Georgios D. Tsoukalas
1037 e546df49 Georgios D. Tsoukalas
    if 'prefix' not in sync_attributes:
1038 e546df49 Georgios D. Tsoukalas
        m = ("you did not specify a 'sync_prefix' attribute "
1039 e546df49 Georgios D. Tsoukalas
             "in class '%s'" % (class_name,))
1040 e546df49 Georgios D. Tsoukalas
        raise ValueError(m)
1041 65360c65 Georgios D. Tsoukalas
1042 e546df49 Georgios D. Tsoukalas
    prefix = sync_attributes.pop('prefix')
1043 e546df49 Georgios D. Tsoukalas
    class_name = sync_attributes.pop('classname', prefix + '_model')
1044 65360c65 Georgios D. Tsoukalas
1045 e546df49 Georgios D. Tsoukalas
    for name, value in sync_attributes.iteritems():
1046 e546df49 Georgios D. Tsoukalas
        newname = prefix + '_' + name
1047 e546df49 Georgios D. Tsoukalas
        if newname in new_attributes:
1048 e546df49 Georgios D. Tsoukalas
            m = ("class '%s' was specified with prefix '%s' "
1049 e546df49 Georgios D. Tsoukalas
                 "but it already has an attribute named '%s'"
1050 e546df49 Georgios D. Tsoukalas
                 % (class_name, prefix, newname))
1051 e546df49 Georgios D. Tsoukalas
            raise ValueError(m)
1052 65360c65 Georgios D. Tsoukalas
1053 e546df49 Georgios D. Tsoukalas
        new_attributes[newname] = value
1054 e546df49 Georgios D. Tsoukalas
1055 e546df49 Georgios D. Tsoukalas
    newclass = type(class_name, class_parents, new_attributes)
1056 e546df49 Georgios D. Tsoukalas
    return newclass
1057 e546df49 Georgios D. Tsoukalas
1058 e546df49 Georgios D. Tsoukalas
1059 e546df49 Georgios D. Tsoukalas
def make_synced(prefix='sync', name='SyncedState'):
1060 e546df49 Georgios D. Tsoukalas
1061 e546df49 Georgios D. Tsoukalas
    the_name = name
1062 e546df49 Georgios D. Tsoukalas
    the_prefix = prefix
1063 e546df49 Georgios D. Tsoukalas
1064 e546df49 Georgios D. Tsoukalas
    class SyncedState(models.Model):
1065 e546df49 Georgios D. Tsoukalas
1066 e546df49 Georgios D. Tsoukalas
        sync_classname      = the_name
1067 e546df49 Georgios D. Tsoukalas
        sync_prefix         = the_prefix
1068 e546df49 Georgios D. Tsoukalas
        __metaclass__       = synced_model_metaclass
1069 e546df49 Georgios D. Tsoukalas
1070 e546df49 Georgios D. Tsoukalas
        sync_new_state      = models.BigIntegerField(null=True)
1071 e546df49 Georgios D. Tsoukalas
        sync_synced_state   = models.BigIntegerField(null=True)
1072 e546df49 Georgios D. Tsoukalas
        STATUS_SYNCED       = 0
1073 e546df49 Georgios D. Tsoukalas
        STATUS_PENDING      = 1
1074 e546df49 Georgios D. Tsoukalas
        sync_status         = models.IntegerField(db_index=True)
1075 e546df49 Georgios D. Tsoukalas
1076 e546df49 Georgios D. Tsoukalas
        class Meta:
1077 e546df49 Georgios D. Tsoukalas
            abstract = True
1078 e546df49 Georgios D. Tsoukalas
1079 e546df49 Georgios D. Tsoukalas
        class NotSynced(Exception):
1080 e546df49 Georgios D. Tsoukalas
            pass
1081 e546df49 Georgios D. Tsoukalas
1082 e546df49 Georgios D. Tsoukalas
        def sync_init_state(self, state):
1083 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = state
1084 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = state
1085 65360c65 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1086 65360c65 Georgios D. Tsoukalas
1087 e546df49 Georgios D. Tsoukalas
        def sync_get_status(self):
1088 e546df49 Georgios D. Tsoukalas
            return self.sync_status
1089 65360c65 Georgios D. Tsoukalas
1090 e546df49 Georgios D. Tsoukalas
        def sync_set_status(self):
1091 e546df49 Georgios D. Tsoukalas
            if self.sync_new_state != self.sync_synced_state:
1092 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_PENDING
1093 e546df49 Georgios D. Tsoukalas
            else:
1094 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_SYNCED
1095 65360c65 Georgios D. Tsoukalas
1096 e546df49 Georgios D. Tsoukalas
        def sync_set_synced(self):
1097 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = self.sync_new_state
1098 e546df49 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1099 65360c65 Georgios D. Tsoukalas
1100 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_state(self):
1101 e546df49 Georgios D. Tsoukalas
            return self.sync_synced_state
1102 65360c65 Georgios D. Tsoukalas
1103 e546df49 Georgios D. Tsoukalas
        def sync_set_new_state(self, new_state):
1104 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = new_state
1105 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1106 65360c65 Georgios D. Tsoukalas
1107 e546df49 Georgios D. Tsoukalas
        def sync_get_new_state(self):
1108 e546df49 Georgios D. Tsoukalas
            return self.sync_new_state
1109 65360c65 Georgios D. Tsoukalas
1110 e546df49 Georgios D. Tsoukalas
        def sync_set_synced_state(self, synced_state):
1111 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = synced_state
1112 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1113 65360c65 Georgios D. Tsoukalas
1114 e546df49 Georgios D. Tsoukalas
        def sync_get_pending_objects(self):
1115 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_PENDING))
1116 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1117 65360c65 Georgios D. Tsoukalas
1118 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_objects(self):
1119 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_SYNCED))
1120 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1121 65360c65 Georgios D. Tsoukalas
1122 e546df49 Georgios D. Tsoukalas
        def sync_verify_get_synced_state(self):
1123 e546df49 Georgios D. Tsoukalas
            status = self.sync_get_status()
1124 e546df49 Georgios D. Tsoukalas
            state = self.sync_get_synced_state()
1125 e546df49 Georgios D. Tsoukalas
            verified = (status == self.STATUS_SYNCED)
1126 e546df49 Georgios D. Tsoukalas
            return state, verified
1127 e546df49 Georgios D. Tsoukalas
1128 e546df49 Georgios D. Tsoukalas
        def sync_is_synced(self):
1129 e546df49 Georgios D. Tsoukalas
            state, verified = self.sync_verify_get_synced_state()
1130 e546df49 Georgios D. Tsoukalas
            return verified
1131 e546df49 Georgios D. Tsoukalas
1132 e546df49 Georgios D. Tsoukalas
    return SyncedState
1133 e546df49 Georgios D. Tsoukalas
1134 e546df49 Georgios D. Tsoukalas
SyncedState = make_synced(prefix='sync', name='SyncedState')
1135 425e2e95 Sofia Papagiannaki
1136 425e2e95 Sofia Papagiannaki
1137 6dcf53eb Kostas Papadimitriou
class ProjectApplicationManager(ForUpdateManager):
1138 5550bcfb Kostas Papadimitriou
1139 5550bcfb Kostas Papadimitriou
    def user_projects(self, user):
1140 5550bcfb Kostas Papadimitriou
        """
1141 5550bcfb Kostas Papadimitriou
        Return projects accessed by specified user.
1142 5550bcfb Kostas Papadimitriou
        """
1143 9b32e2fb Kostas Papadimitriou
        participates_fitlers = Q(owner=user) | Q(applicant=user) | \
1144 9b32e2fb Kostas Papadimitriou
                               Q(project__projectmembership__person=user)
1145 9b32e2fb Kostas Papadimitriou
        state_filters = (Q(state=ProjectApplication.PENDING) & \
1146 9b32e2fb Kostas Papadimitriou
                        Q(precursor_application__isnull=True)) | \
1147 9b32e2fb Kostas Papadimitriou
                        Q(state=ProjectApplication.APPROVED)
1148 9b32e2fb Kostas Papadimitriou
        return self.filter(participates_fitlers & state_filters).order_by('issue_date').distinct()
1149 5550bcfb Kostas Papadimitriou
1150 a5cef8d0 Kostas Papadimitriou
    def search_by_name(self, *search_strings):
1151 a5cef8d0 Kostas Papadimitriou
        q = Q()
1152 a5cef8d0 Kostas Papadimitriou
        for s in search_strings:
1153 a5cef8d0 Kostas Papadimitriou
            q = q | Q(name__icontains=s)
1154 a5cef8d0 Kostas Papadimitriou
        return self.filter(q)
1155 a5cef8d0 Kostas Papadimitriou
1156 a5cef8d0 Kostas Papadimitriou
1157 db9a498c Kostas Papadimitriou
PROJECT_STATE_DISPLAY = {
1158 db9a498c Kostas Papadimitriou
    'Pending': _('Pending review'),
1159 db9a498c Kostas Papadimitriou
    'Approved': _('Active'),
1160 db9a498c Kostas Papadimitriou
    'Replaced': _('Replaced'),
1161 db9a498c Kostas Papadimitriou
    'Unknown': _('Unknown')
1162 db9a498c Kostas Papadimitriou
}
1163 db9a498c Kostas Papadimitriou
1164 db9a498c Kostas Papadimitriou
USER_STATUS_DISPLAY = {
1165 db9a498c Kostas Papadimitriou
    100: _('Owner'),
1166 db9a498c Kostas Papadimitriou
      0: _('Join requested'),
1167 db9a498c Kostas Papadimitriou
      1: _('Pending'),
1168 db9a498c Kostas Papadimitriou
      2: _('Accepted member'),
1169 db9a498c Kostas Papadimitriou
      3: _('Removing'),
1170 db9a498c Kostas Papadimitriou
      4: _('Removed'),
1171 db9a498c Kostas Papadimitriou
     -1: _('Not a member'),
1172 db9a498c Kostas Papadimitriou
}
1173 db9a498c Kostas Papadimitriou
1174 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1175 85d444db Sofia Papagiannaki
    PENDING, APPROVED, REPLACED, UNKNOWN = 'Pending', 'Approved', 'Replaced', 'Unknown'
1176 425e2e95 Sofia Papagiannaki
    applicant               =   models.ForeignKey(
1177 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1178 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_applied',
1179 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1180 d6fdc91e Georgios D. Tsoukalas
1181 d6fdc91e Georgios D. Tsoukalas
    state                   =   models.CharField(max_length=80,
1182 69c822cc Giorgos Korfiatis
                                                default=PENDING)
1183 d6fdc91e Georgios D. Tsoukalas
1184 425e2e95 Sofia Papagiannaki
    owner                   =   models.ForeignKey(
1185 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1186 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_owned',
1187 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1188 d6fdc91e Georgios D. Tsoukalas
1189 425e2e95 Sofia Papagiannaki
    precursor_application   =   models.OneToOneField('ProjectApplication',
1190 425e2e95 Sofia Papagiannaki
                                                     null=True,
1191 425e2e95 Sofia Papagiannaki
                                                     blank=True,
1192 d6fdc91e Georgios D. Tsoukalas
                                                     db_index=True)
1193 425e2e95 Sofia Papagiannaki
1194 67980f56 Georgios D. Tsoukalas
    name                    =   models.CharField(max_length=80)
1195 67980f56 Georgios D. Tsoukalas
    homepage                =   models.URLField(max_length=255, null=True)
1196 67980f56 Georgios D. Tsoukalas
    description             =   models.TextField(null=True, blank=True)
1197 e729f165 Kostas Papadimitriou
    start_date              =   models.DateTimeField(null=True, blank=True)
1198 67980f56 Georgios D. Tsoukalas
    end_date                =   models.DateTimeField()
1199 272cf735 Sofia Papagiannaki
    member_join_policy      =   models.IntegerField()
1200 272cf735 Sofia Papagiannaki
    member_leave_policy     =   models.IntegerField()
1201 67980f56 Georgios D. Tsoukalas
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1202 425e2e95 Sofia Papagiannaki
    resource_grants         =   models.ManyToManyField(
1203 425e2e95 Sofia Papagiannaki
                                    Resource,
1204 425e2e95 Sofia Papagiannaki
                                    null=True,
1205 425e2e95 Sofia Papagiannaki
                                    blank=True,
1206 d6fdc91e Georgios D. Tsoukalas
                                    through='ProjectResourceGrant')
1207 425e2e95 Sofia Papagiannaki
    comments                =   models.TextField(null=True, blank=True)
1208 69c822cc Giorgos Korfiatis
    issue_date              =   models.DateTimeField(default=datetime.now)
1209 425e2e95 Sofia Papagiannaki
1210 6dcf53eb Kostas Papadimitriou
1211 5550bcfb Kostas Papadimitriou
    objects                 =   ProjectApplicationManager()
1212 7729e9cc Giorgos Korfiatis
1213 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1214 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1215 f3a45fc6 Kostas Papadimitriou
1216 db9a498c Kostas Papadimitriou
    def state_display(self):
1217 db9a498c Kostas Papadimitriou
        return PROJECT_STATE_DISPLAY.get(self.state, _('Unknown'))
1218 db9a498c Kostas Papadimitriou
1219 a7aba804 Sofia Papagiannaki
    def add_resource_policy(self, service, resource, uplimit):
1220 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1221 a7aba804 Sofia Papagiannaki
        q = self.projectresourcegrant_set
1222 e1a80257 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=service, name=resource)
1223 a7aba804 Sofia Papagiannaki
        q.create(resource=resource, member_capacity=uplimit)
1224 e1a80257 Sofia Papagiannaki
1225 e87bbb41 Kostas Papadimitriou
    def user_status(self, user):
1226 e87bbb41 Kostas Papadimitriou
        """
1227 e87bbb41 Kostas Papadimitriou
        100 OWNER
1228 e87bbb41 Kostas Papadimitriou
        0   REQUESTED
1229 e87bbb41 Kostas Papadimitriou
        1   PENDING
1230 e87bbb41 Kostas Papadimitriou
        2   ACCEPTED
1231 e87bbb41 Kostas Papadimitriou
        3   REMOVING
1232 e87bbb41 Kostas Papadimitriou
        4   REMOVED
1233 e87bbb41 Kostas Papadimitriou
       -1   User has no association with the project
1234 e87bbb41 Kostas Papadimitriou
        """
1235 db9a498c Kostas Papadimitriou
        try:
1236 db9a498c Kostas Papadimitriou
            membership = self.project.projectmembership_set.get(person=user)
1237 db9a498c Kostas Papadimitriou
            status = membership.state
1238 db9a498c Kostas Papadimitriou
        except Project.DoesNotExist:
1239 db9a498c Kostas Papadimitriou
            status = -1
1240 db9a498c Kostas Papadimitriou
        except ProjectMembership.DoesNotExist:
1241 db9a498c Kostas Papadimitriou
            status = -1
1242 5550bcfb Kostas Papadimitriou
1243 5550bcfb Kostas Papadimitriou
        return status
1244 5550bcfb Kostas Papadimitriou
1245 db9a498c Kostas Papadimitriou
    def user_status_display(self, user):
1246 db9a498c Kostas Papadimitriou
        return USER_STATUS_DISPLAY.get(self.user_status(user), _('Unknown'))
1247 db9a498c Kostas Papadimitriou
1248 5550bcfb Kostas Papadimitriou
    def members_count(self):
1249 5550bcfb Kostas Papadimitriou
        return self.project.approved_memberships.count()
1250 5550bcfb Kostas Papadimitriou
1251 669cfe19 Olga Brani
    @property
1252 669cfe19 Olga Brani
    def grants(self):
1253 669cfe19 Olga Brani
        return self.projectresourcegrant_set.values('member_capacity', 'resource__name', 'resource__service__name')
1254 5550bcfb Kostas Papadimitriou
1255 e1a80257 Sofia Papagiannaki
    @property
1256 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1257 f3342849 Sofia Papagiannaki
        return self.projectresourcegrant_set.all()
1258 e1a80257 Sofia Papagiannaki
1259 e1a80257 Sofia Papagiannaki
    @resource_policies.setter
1260 e1a80257 Sofia Papagiannaki
    def resource_policies(self, policies):
1261 e1a80257 Sofia Papagiannaki
        for p in policies:
1262 e1a80257 Sofia Papagiannaki
            service = p.get('service', None)
1263 e1a80257 Sofia Papagiannaki
            resource = p.get('resource', None)
1264 e1a80257 Sofia Papagiannaki
            uplimit = p.get('uplimit', 0)
1265 a7aba804 Sofia Papagiannaki
            self.add_resource_policy(service, resource, uplimit)
1266 425e2e95 Sofia Papagiannaki
1267 ccab6eb5 Sofia Papagiannaki
    @property
1268 ccab6eb5 Sofia Papagiannaki
    def follower(self):
1269 ccab6eb5 Sofia Papagiannaki
        try:
1270 ccab6eb5 Sofia Papagiannaki
            return ProjectApplication.objects.get(precursor_application=self)
1271 ccab6eb5 Sofia Papagiannaki
        except ProjectApplication.DoesNotExist:
1272 ccab6eb5 Sofia Papagiannaki
            return
1273 ccab6eb5 Sofia Papagiannaki
1274 9b32e2fb Kostas Papadimitriou
    def followers(self):
1275 9b32e2fb Kostas Papadimitriou
        current = self
1276 9b32e2fb Kostas Papadimitriou
        try:
1277 9b32e2fb Kostas Papadimitriou
            while current.projectapplication:
1278 9b32e2fb Kostas Papadimitriou
                yield current.follower
1279 9b32e2fb Kostas Papadimitriou
                current = current.follower
1280 9b32e2fb Kostas Papadimitriou
        except:
1281 9b32e2fb Kostas Papadimitriou
            pass
1282 9b32e2fb Kostas Papadimitriou
1283 9b32e2fb Kostas Papadimitriou
    def last_follower(self):
1284 9b32e2fb Kostas Papadimitriou
        try:
1285 9b32e2fb Kostas Papadimitriou
            return list(self.followers())[-1]
1286 9b32e2fb Kostas Papadimitriou
        except IndexError:
1287 9b32e2fb Kostas Papadimitriou
            return None
1288 9b32e2fb Kostas Papadimitriou
1289 4f22664f Georgios D. Tsoukalas
    def _get_project(self):
1290 4f22664f Georgios D. Tsoukalas
        precursor = self
1291 4f22664f Georgios D. Tsoukalas
        while precursor:
1292 4f22664f Georgios D. Tsoukalas
            try:
1293 91d3d92a Giorgos Korfiatis
                objects = Project.objects.select_for_update()
1294 91d3d92a Giorgos Korfiatis
                project = objects.get(application=precursor)
1295 4f22664f Georgios D. Tsoukalas
                return project
1296 4f22664f Georgios D. Tsoukalas
            except Project.DoesNotExist:
1297 4f22664f Georgios D. Tsoukalas
                pass
1298 4f22664f Georgios D. Tsoukalas
            precursor = precursor.precursor_application
1299 4f22664f Georgios D. Tsoukalas
1300 4f22664f Georgios D. Tsoukalas
        return None
1301 4f22664f Georgios D. Tsoukalas
1302 ccab6eb5 Sofia Papagiannaki
    def approve(self, approval_user=None):
1303 ccab6eb5 Sofia Papagiannaki
        """
1304 ccab6eb5 Sofia Papagiannaki
        If approval_user then during owner membership acceptance
1305 ccab6eb5 Sofia Papagiannaki
        it is checked whether the request_user is eligible.
1306 262e04c6 Giorgos Korfiatis

1307 2553efae Sofia Papagiannaki
        Raises:
1308 b8f05f8d Sofia Papagiannaki
            PermissionDenied
1309 ccab6eb5 Sofia Papagiannaki
        """
1310 4f22664f Georgios D. Tsoukalas
1311 4f22664f Georgios D. Tsoukalas
        if not transaction.is_managed():
1312 4f22664f Georgios D. Tsoukalas
            raise AssertionError("NOPE")
1313 4f22664f Georgios D. Tsoukalas
1314 73fbaec4 Sofia Papagiannaki
        new_project_name = self.name
1315 85d444db Sofia Papagiannaki
        if self.state != self.PENDING:
1316 65360c65 Georgios D. Tsoukalas
            m = _("cannot approve: project '%s' in state '%s'") % (
1317 65360c65 Georgios D. Tsoukalas
                    new_project_name, self.state)
1318 4f22664f Georgios D. Tsoukalas
            raise PermissionDenied(m) # invalid argument
1319 262e04c6 Giorgos Korfiatis
1320 fdafae27 Giorgos Korfiatis
        now = datetime.now()
1321 4f22664f Georgios D. Tsoukalas
        project = self._get_project()
1322 3cc9637a Giorgos Korfiatis
1323 3cc9637a Giorgos Korfiatis
        try:
1324 3cc9637a Giorgos Korfiatis
            # needs SERIALIZABLE
1325 3cc9637a Giorgos Korfiatis
            conflicting_project = Project.objects.get(name=new_project_name)
1326 3cc9637a Giorgos Korfiatis
            if (conflicting_project.is_alive and
1327 37705b5f Giorgos Korfiatis
                conflicting_project != project):
1328 3cc9637a Giorgos Korfiatis
                m = (_("cannot approve: project with name '%s' "
1329 3cc9637a Giorgos Korfiatis
                       "already exists (serial: %s)") % (
1330 3cc9637a Giorgos Korfiatis
                        new_project_name, conflicting_project.id))
1331 3cc9637a Giorgos Korfiatis
                raise PermissionDenied(m) # invalid argument
1332 3cc9637a Giorgos Korfiatis
        except Project.DoesNotExist:
1333 3cc9637a Giorgos Korfiatis
            pass
1334 3cc9637a Giorgos Korfiatis
1335 4bf02ea5 Giorgos Korfiatis
        new_project = False
1336 4f22664f Georgios D. Tsoukalas
        if project is None:
1337 4bf02ea5 Giorgos Korfiatis
            new_project = True
1338 3cc9637a Giorgos Korfiatis
            project = Project(creation_date=now)
1339 fdafae27 Giorgos Korfiatis
1340 3cc9637a Giorgos Korfiatis
        project.name = new_project_name
1341 ee45eb81 Giorgos Korfiatis
        project.application = self
1342 4bf02ea5 Giorgos Korfiatis
        project.last_approval_date = now
1343 4bf02ea5 Giorgos Korfiatis
        project.save()
1344 4bf02ea5 Giorgos Korfiatis
1345 4bf02ea5 Giorgos Korfiatis
        if new_project:
1346 4bf02ea5 Giorgos Korfiatis
            project.add_member(self.owner)
1347 425e2e95 Sofia Papagiannaki
1348 ee45eb81 Giorgos Korfiatis
        # This will block while syncing,
1349 ee45eb81 Giorgos Korfiatis
        # but unblock before setting the membership state.
1350 ee45eb81 Giorgos Korfiatis
        # See ProjectMembership.set_sync()
1351 ee45eb81 Giorgos Korfiatis
        project.set_membership_pending_sync()
1352 425e2e95 Sofia Papagiannaki
1353 4f22664f Georgios D. Tsoukalas
        precursor = self.precursor_application
1354 4f22664f Georgios D. Tsoukalas
        while precursor:
1355 85d444db Sofia Papagiannaki
            precursor.state = self.REPLACED
1356 4f22664f Georgios D. Tsoukalas
            precursor.save()
1357 4f22664f Georgios D. Tsoukalas
            precursor = precursor.precursor_application
1358 262e04c6 Giorgos Korfiatis
1359 85d444db Sofia Papagiannaki
        self.state = self.APPROVED
1360 bfe23b13 Sofia Papagiannaki
        self.save()
1361 262e04c6 Giorgos Korfiatis
1362 15ca2bea Giorgos Korfiatis
def submit_application(**kw):
1363 15ca2bea Giorgos Korfiatis
1364 15ca2bea Giorgos Korfiatis
    resource_policies = kw.pop('resource_policies', None)
1365 15ca2bea Giorgos Korfiatis
    application = ProjectApplication(**kw)
1366 15ca2bea Giorgos Korfiatis
1367 15ca2bea Giorgos Korfiatis
    precursor = kw['precursor_application']
1368 15ca2bea Giorgos Korfiatis
1369 15ca2bea Giorgos Korfiatis
    if precursor is not None:
1370 15ca2bea Giorgos Korfiatis
        precursor.state = ProjectApplication.REPLACED
1371 15ca2bea Giorgos Korfiatis
        precursor.save()
1372 15ca2bea Giorgos Korfiatis
1373 15ca2bea Giorgos Korfiatis
    application.save()
1374 15ca2bea Giorgos Korfiatis
    application.resource_policies = resource_policies
1375 15ca2bea Giorgos Korfiatis
    return application
1376 ccab6eb5 Sofia Papagiannaki
1377 73fbaec4 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1378 e1a80257 Sofia Papagiannaki
1379 425e2e95 Sofia Papagiannaki
    resource                =   models.ForeignKey(Resource)
1380 425e2e95 Sofia Papagiannaki
    project_application     =   models.ForeignKey(ProjectApplication,
1381 5200e864 Sofia Papagiannaki
                                                  null=True)
1382 c11dc0ce Giorgos Korfiatis
    project_capacity        =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1383 c11dc0ce Giorgos Korfiatis
    project_import_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1384 c11dc0ce Giorgos Korfiatis
    project_export_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1385 c11dc0ce Giorgos Korfiatis
    member_capacity         =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1386 c11dc0ce Giorgos Korfiatis
    member_import_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1387 c11dc0ce Giorgos Korfiatis
    member_export_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1388 73fbaec4 Sofia Papagiannaki
1389 73fbaec4 Sofia Papagiannaki
    objects = ExtendedManager()
1390 73fbaec4 Sofia Papagiannaki
1391 73fbaec4 Sofia Papagiannaki
    class Meta:
1392 73fbaec4 Sofia Papagiannaki
        unique_together = ("resource", "project_application")
1393 8327782d Sofia Papagiannaki
1394 e546df49 Georgios D. Tsoukalas
1395 d6fdc91e Georgios D. Tsoukalas
class Project(models.Model):
1396 e546df49 Georgios D. Tsoukalas
1397 ee45eb81 Giorgos Korfiatis
    application                 =   models.OneToOneField(
1398 4f22664f Georgios D. Tsoukalas
                                            ProjectApplication,
1399 782d9118 Giorgos Korfiatis
                                            related_name='project')
1400 4f22664f Georgios D. Tsoukalas
    last_approval_date          =   models.DateTimeField(null=True)
1401 4f22664f Georgios D. Tsoukalas
1402 4f22664f Georgios D. Tsoukalas
    members                     =   models.ManyToManyField(
1403 4f22664f Georgios D. Tsoukalas
                                            AstakosUser,
1404 4f22664f Georgios D. Tsoukalas
                                            through='ProjectMembership')
1405 4f22664f Georgios D. Tsoukalas
1406 5b9e9530 Giorgos Korfiatis
    deactivation_reason         =   models.CharField(max_length=255, null=True)
1407 5b9e9530 Giorgos Korfiatis
    deactivation_start_date     =   models.DateTimeField(null=True)
1408 5b9e9530 Giorgos Korfiatis
    deactivation_date           =   models.DateTimeField(null=True)
1409 4f22664f Georgios D. Tsoukalas
1410 4f22664f Georgios D. Tsoukalas
    creation_date               =   models.DateTimeField()
1411 4f22664f Georgios D. Tsoukalas
    name                        =   models.CharField(
1412 4f22664f Georgios D. Tsoukalas
                                            max_length=80,
1413 4f22664f Georgios D. Tsoukalas
                                            db_index=True,
1414 4f22664f Georgios D. Tsoukalas
                                            unique=True)
1415 425e2e95 Sofia Papagiannaki
1416 5b9e9530 Giorgos Korfiatis
    TERMINATED  =   'TERMINATED'
1417 5b9e9530 Giorgos Korfiatis
    SUSPENDED   =   'SUSPENDED'
1418 5b9e9530 Giorgos Korfiatis
1419 7729e9cc Giorgos Korfiatis
    objects     =   ForUpdateManager()
1420 7729e9cc Giorgos Korfiatis
1421 8c7b8bb8 Giorgos Korfiatis
    def __str__(self):
1422 8c7b8bb8 Giorgos Korfiatis
        return _("<project %s '%s'>") % (self.id, self.application.name)
1423 8c7b8bb8 Giorgos Korfiatis
1424 8c7b8bb8 Giorgos Korfiatis
    __repr__ = __str__
1425 8c7b8bb8 Giorgos Korfiatis
1426 5b9e9530 Giorgos Korfiatis
    def is_deactivating(self):
1427 5b9e9530 Giorgos Korfiatis
        return bool(self.deactivation_start_date)
1428 425e2e95 Sofia Papagiannaki
1429 5b9e9530 Giorgos Korfiatis
    def is_deactivated_synced(self):
1430 5b9e9530 Giorgos Korfiatis
        return bool(self.deactivation_date)
1431 425e2e95 Sofia Papagiannaki
1432 5b9e9530 Giorgos Korfiatis
    def is_deactivated(self):
1433 5b9e9530 Giorgos Korfiatis
        return self.is_deactivated_synced() or self.is_deactivating()
1434 425e2e95 Sofia Papagiannaki
1435 8aed306c Giorgos Korfiatis
    def is_still_approved(self):
1436 8aed306c Giorgos Korfiatis
        return bool(self.last_approval_date)
1437 8aed306c Giorgos Korfiatis
1438 e1a80257 Sofia Papagiannaki
    def is_active(self):
1439 5b9e9530 Giorgos Korfiatis
        return not(self.is_deactivated())
1440 425e2e95 Sofia Papagiannaki
1441 e1a80257 Sofia Papagiannaki
    def is_inconsistent(self):
1442 e1a80257 Sofia Papagiannaki
        now = datetime.now()
1443 5b9e9530 Giorgos Korfiatis
        dates = [self.creation_date,
1444 5b9e9530 Giorgos Korfiatis
                 self.last_approval_date,
1445 5b9e9530 Giorgos Korfiatis
                 self.deactivation_start_date,
1446 5b9e9530 Giorgos Korfiatis
                 self.deactivation_date]
1447 5b9e9530 Giorgos Korfiatis
        return any([date > now for date in dates])
1448 5b9e9530 Giorgos Korfiatis
1449 5b9e9530 Giorgos Korfiatis
    def set_deactivation_start_date(self):
1450 5b9e9530 Giorgos Korfiatis
        self.deactivation_start_date = datetime.now()
1451 5b9e9530 Giorgos Korfiatis
1452 5b9e9530 Giorgos Korfiatis
    def set_deactivation_date(self):
1453 5b9e9530 Giorgos Korfiatis
        self.deactivation_start_date = None
1454 5b9e9530 Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1455 5b9e9530 Giorgos Korfiatis
1456 5b9e9530 Giorgos Korfiatis
    def violates_resource_grants(self):
1457 e1a80257 Sofia Papagiannaki
        return False
1458 65360c65 Georgios D. Tsoukalas
1459 5b9e9530 Giorgos Korfiatis
    def violates_members_limit(self, adding=0):
1460 5b9e9530 Giorgos Korfiatis
        application = self.application
1461 5b9e9530 Giorgos Korfiatis
        return (len(self.approved_members) + adding >
1462 5b9e9530 Giorgos Korfiatis
                application.limit_on_members_number)
1463 5b9e9530 Giorgos Korfiatis
1464 5b9e9530 Giorgos Korfiatis
    @property
1465 5b9e9530 Giorgos Korfiatis
    def is_alive(self):
1466 5b9e9530 Giorgos Korfiatis
        return self.is_active()
1467 5b9e9530 Giorgos Korfiatis
1468 425e2e95 Sofia Papagiannaki
    @property
1469 425e2e95 Sofia Papagiannaki
    def approved_memberships(self):
1470 5b9e9530 Giorgos Korfiatis
        query = ProjectMembership.query_approved()
1471 5b9e9530 Giorgos Korfiatis
        return self.projectmembership_set.filter(query)
1472 4f22664f Georgios D. Tsoukalas
1473 425e2e95 Sofia Papagiannaki
    @property
1474 425e2e95 Sofia Papagiannaki
    def approved_members(self):
1475 425e2e95 Sofia Papagiannaki
        return [m.person for m in self.approved_memberships]
1476 4f22664f Georgios D. Tsoukalas
1477 ee45eb81 Giorgos Korfiatis
    def set_membership_pending_sync(self):
1478 5b9e9530 Giorgos Korfiatis
        query = ProjectMembership.query_approved()
1479 ee45eb81 Giorgos Korfiatis
        sfu = self.projectmembership_set.select_for_update()
1480 5b9e9530 Giorgos Korfiatis
        members = sfu.filter(query)
1481 4f22664f Georgios D. Tsoukalas
1482 425e2e95 Sofia Papagiannaki
        for member in members:
1483 ee45eb81 Giorgos Korfiatis
            member.state = member.PENDING
1484 425e2e95 Sofia Papagiannaki
            member.save()
1485 4f22664f Georgios D. Tsoukalas
1486 4f22664f Georgios D. Tsoukalas
    def add_member(self, user):
1487 bfe23b13 Sofia Papagiannaki
        """
1488 bfe23b13 Sofia Papagiannaki
        Raises:
1489 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1490 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1491 bfe23b13 Sofia Papagiannaki
        """
1492 2a965273 Sofia Papagiannaki
        if isinstance(user, int):
1493 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1494 4f22664f Georgios D. Tsoukalas
1495 ccab6eb5 Sofia Papagiannaki
        m, created = ProjectMembership.objects.get_or_create(
1496 ccab6eb5 Sofia Papagiannaki
            person=user, project=self
1497 2a965273 Sofia Papagiannaki
        )
1498 4f22664f Georgios D. Tsoukalas
        m.accept()
1499 ccab6eb5 Sofia Papagiannaki
1500 4f22664f Georgios D. Tsoukalas
    def remove_member(self, user):
1501 bfe23b13 Sofia Papagiannaki
        """
1502 bfe23b13 Sofia Papagiannaki
        Raises:
1503 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
1504 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
1505 bfe23b13 Sofia Papagiannaki
            astakos.im.models.ProjectMembership.DoesNotExist
1506 bfe23b13 Sofia Papagiannaki
        """
1507 ccab6eb5 Sofia Papagiannaki
        if isinstance(user, int):
1508 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
1509 4f22664f Georgios D. Tsoukalas
1510 bfe23b13 Sofia Papagiannaki
        m = ProjectMembership.objects.get(person=user, project=self)
1511 bfe23b13 Sofia Papagiannaki
        m.remove()
1512 4f22664f Georgios D. Tsoukalas
1513 5b9e9530 Giorgos Korfiatis
    def terminate(self):
1514 5b9e9530 Giorgos Korfiatis
        self.set_deactivation_start_date()
1515 5b9e9530 Giorgos Korfiatis
        self.deactivation_reason = self.TERMINATED
1516 b22de10a Sofia Papagiannaki
        self.save()
1517 425e2e95 Sofia Papagiannaki
1518 5b9e9530 Giorgos Korfiatis
    @property
1519 5b9e9530 Giorgos Korfiatis
    def is_terminated(self):
1520 5b9e9530 Giorgos Korfiatis
        return (self.is_deactivated() and
1521 5b9e9530 Giorgos Korfiatis
                self.deactivation_reason == self.TERMINATED)
1522 b22de10a Sofia Papagiannaki
1523 5b9e9530 Giorgos Korfiatis
    @property
1524 5b9e9530 Giorgos Korfiatis
    def is_suspended(self):
1525 5b9e9530 Giorgos Korfiatis
        return False
1526 4f22664f Georgios D. Tsoukalas
1527 d6fdc91e Georgios D. Tsoukalas
class ProjectMembership(models.Model):
1528 4f22664f Georgios D. Tsoukalas
1529 425e2e95 Sofia Papagiannaki
    person              =   models.ForeignKey(AstakosUser)
1530 425e2e95 Sofia Papagiannaki
    request_date        =   models.DateField(default=datetime.now())
1531 d6fdc91e Georgios D. Tsoukalas
    project             =   models.ForeignKey(Project)
1532 d6fdc91e Georgios D. Tsoukalas
1533 d6fdc91e Georgios D. Tsoukalas
    state               =   models.IntegerField(default=0)
1534 5200e864 Sofia Papagiannaki
    application         =   models.ForeignKey(
1535 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1536 5200e864 Sofia Papagiannaki
                                null=True,
1537 5200e864 Sofia Papagiannaki
                                related_name='memberships')
1538 5200e864 Sofia Papagiannaki
    pending_application =   models.ForeignKey(
1539 5200e864 Sofia Papagiannaki
                                ProjectApplication,
1540 5200e864 Sofia Papagiannaki
                                null=True,
1541 5200e864 Sofia Papagiannaki
                                related_name='pending_memebrships')
1542 d6fdc91e Georgios D. Tsoukalas
    pending_serial      =   models.BigIntegerField(null=True, db_index=True)
1543 425e2e95 Sofia Papagiannaki
1544 425e2e95 Sofia Papagiannaki
    acceptance_date     =   models.DateField(null=True, db_index=True)
1545 425e2e95 Sofia Papagiannaki
    leave_request_date  =   models.DateField(null=True)
1546 2a965273 Sofia Papagiannaki
1547 ee45eb81 Giorgos Korfiatis
    objects     =   ForUpdateManager()
1548 ee45eb81 Giorgos Korfiatis
1549 65360c65 Georgios D. Tsoukalas
    REQUESTED   =   0
1550 d6fdc91e Georgios D. Tsoukalas
    PENDING     =   1
1551 d6fdc91e Georgios D. Tsoukalas
    ACCEPTED    =   2
1552 d6fdc91e Georgios D. Tsoukalas
    REMOVING    =   3
1553 d6fdc91e Georgios D. Tsoukalas
    REMOVED     =   4
1554 5b9e9530 Giorgos Korfiatis
    INACTIVE    =   5
1555 5b9e9530 Giorgos Korfiatis
1556 5b9e9530 Giorgos Korfiatis
    APPROVED_SET    =   [PENDING, ACCEPTED, INACTIVE]
1557 5b9e9530 Giorgos Korfiatis
1558 5b9e9530 Giorgos Korfiatis
    @classmethod
1559 5b9e9530 Giorgos Korfiatis
    def query_approved(cls):
1560 5b9e9530 Giorgos Korfiatis
        return (Q(state=cls.PENDING) |
1561 5b9e9530 Giorgos Korfiatis
                Q(state=cls.ACCEPTED) |
1562 5b9e9530 Giorgos Korfiatis
                Q(state=cls.INACTIVE))
1563 65360c65 Georgios D. Tsoukalas
1564 0cc22d47 Sofia Papagiannaki
    class Meta:
1565 0cc22d47 Sofia Papagiannaki
        unique_together = ("person", "project")
1566 d6fdc91e Georgios D. Tsoukalas
        #index_together = [["project", "state"]]
1567 bfe23b13 Sofia Papagiannaki
1568 65360c65 Georgios D. Tsoukalas
    def __str__(self):
1569 8c7b8bb8 Giorgos Korfiatis
        return _("<'%s' membership in '%s'>") % (
1570 8c7b8bb8 Giorgos Korfiatis
                self.person.username, self.project)
1571 65360c65 Georgios D. Tsoukalas
1572 65360c65 Georgios D. Tsoukalas
    __repr__ = __str__
1573 65360c65 Georgios D. Tsoukalas
1574 65360c65 Georgios D. Tsoukalas
    def __init__(self, *args, **kwargs):
1575 ee45eb81 Giorgos Korfiatis
        self.state = self.REQUESTED
1576 65360c65 Georgios D. Tsoukalas
        super(ProjectMembership, self).__init__(*args, **kwargs)
1577 65360c65 Georgios D. Tsoukalas
1578 4f22664f Georgios D. Tsoukalas
    def _set_history_item(self, reason, date=None):
1579 4f22664f Georgios D. Tsoukalas
        if isinstance(reason, basestring):
1580 4f22664f Georgios D. Tsoukalas
            reason = ProjectMembershipHistory.reasons.get(reason, -1)
1581 4f22664f Georgios D. Tsoukalas
1582 4f22664f Georgios D. Tsoukalas
        history_item = ProjectMembershipHistory(
1583 4f22664f Georgios D. Tsoukalas
                            serial=self.id,
1584 02d2519e Giorgos Korfiatis
                            person=self.person.uuid,
1585 02d2519e Giorgos Korfiatis
                            project=self.project_id,
1586 8f975b72 Sofia Papagiannaki
                            date=date or datetime.now(),
1587 4f22664f Georgios D. Tsoukalas
                            reason=reason)
1588 4f22664f Georgios D. Tsoukalas
        history_item.save()
1589 4f22664f Georgios D. Tsoukalas
        serial = history_item.id
1590 4f22664f Georgios D. Tsoukalas
1591 4f22664f Georgios D. Tsoukalas
    def accept(self):
1592 5200e864 Sofia Papagiannaki
        state = self.state
1593 65360c65 Georgios D. Tsoukalas
        if state != self.REQUESTED:
1594 5b9e9530 Giorgos Korfiatis
            m = _("%s: attempt to accept in state '%s'") % (self, state)
1595 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1596 4f22664f Georgios D. Tsoukalas
1597 65360c65 Georgios D. Tsoukalas
        now = datetime.now()
1598 65360c65 Georgios D. Tsoukalas
        self.acceptance_date = now
1599 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='ACCEPT', date=now)
1600 5b9e9530 Giorgos Korfiatis
        self.state = (self.PENDING if self.project.is_active()
1601 5b9e9530 Giorgos Korfiatis
                      else self.INACTIVE)
1602 65360c65 Georgios D. Tsoukalas
        self.save()
1603 4f22664f Georgios D. Tsoukalas
1604 65360c65 Georgios D. Tsoukalas
    def remove(self):
1605 974ee6a6 Sofia Papagiannaki
        state = self.state
1606 5b9e9530 Giorgos Korfiatis
        if state not in [self.ACCEPTED, self.INACTIVE]:
1607 65360c65 Georgios D. Tsoukalas
            m = _("%s: attempt to remove in state '%s'") % (self, state)
1608 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1609 4f22664f Georgios D. Tsoukalas
1610 ee45eb81 Giorgos Korfiatis
        self._set_history_item(reason='REMOVE')
1611 d6fdc91e Georgios D. Tsoukalas
        self.state = self.REMOVING
1612 0cc22d47 Sofia Papagiannaki
        self.save()
1613 b8f05f8d Sofia Papagiannaki
1614 65360c65 Georgios D. Tsoukalas
    def reject(self):
1615 974ee6a6 Sofia Papagiannaki
        state = self.state
1616 65360c65 Georgios D. Tsoukalas
        if state != self.REQUESTED:
1617 5b9e9530 Giorgos Korfiatis
            m = _("%s: attempt to reject in state '%s'") % (self, state)
1618 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1619 65360c65 Georgios D. Tsoukalas
1620 65360c65 Georgios D. Tsoukalas
        # rejected requests don't need sync,
1621 65360c65 Georgios D. Tsoukalas
        # because they were never effected
1622 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='REJECT')
1623 0cc22d47 Sofia Papagiannaki
        self.delete()
1624 b8f05f8d Sofia Papagiannaki
1625 d2b32360 Giorgos Korfiatis
    def get_diff_quotas(self, sub_list=None, add_list=None, remove=False):
1626 d2b32360 Giorgos Korfiatis
        if sub_list is None:
1627 d2b32360 Giorgos Korfiatis
            sub_list = []
1628 d2b32360 Giorgos Korfiatis
1629 d2b32360 Giorgos Korfiatis
        if add_list is None:
1630 d2b32360 Giorgos Korfiatis
            add_list = []
1631 d6fdc91e Georgios D. Tsoukalas
1632 d2b32360 Giorgos Korfiatis
        sub_append = sub_list.append
1633 d2b32360 Giorgos Korfiatis
        add_append = add_list.append
1634 d75c432e Sofia Papagiannaki
        holder = self.person.uuid
1635 d6fdc91e Georgios D. Tsoukalas
1636 d6fdc91e Georgios D. Tsoukalas
        synced_application = self.application
1637 d6fdc91e Georgios D. Tsoukalas
        if synced_application is not None:
1638 5f2e4042 Sofia Papagiannaki
            cur_grants = synced_application.projectresourcegrant_set.all()
1639 d6fdc91e Georgios D. Tsoukalas
            for grant in cur_grants:
1640 d2b32360 Giorgos Korfiatis
                sub_append(QuotaLimits(
1641 d2b32360 Giorgos Korfiatis
                               holder       = holder,
1642 f3e93707 Sofia Papagiannaki
                               resource     = str(grant.resource),
1643 d2b32360 Giorgos Korfiatis
                               capacity     = grant.member_capacity,
1644 d2b32360 Giorgos Korfiatis
                               import_limit = grant.member_import_limit,
1645 d2b32360 Giorgos Korfiatis
                               export_limit = grant.member_export_limit))
1646 d6fdc91e Georgios D. Tsoukalas
1647 d6fdc91e Georgios D. Tsoukalas
        if not remove:
1648 ce80d7ae Sofia Papagiannaki
            new_grants = self.pending_application.projectresourcegrant_set.all()
1649 d6fdc91e Georgios D. Tsoukalas
            for new_grant in new_grants:
1650 d2b32360 Giorgos Korfiatis
                add_append(QuotaLimits(
1651 d2b32360 Giorgos Korfiatis
                               holder       = holder,
1652 f3e93707 Sofia Papagiannaki
                               resource     = str(new_grant.resource),
1653 974ee6a6 Sofia Papagiannaki
                               capacity     = new_grant.member_capacity,
1654 974ee6a6 Sofia Papagiannaki
                               import_limit = new_grant.member_import_limit,
1655 974ee6a6 Sofia Papagiannaki
                               export_limit = new_grant.member_export_limit))
1656 d6fdc91e Georgios D. Tsoukalas
1657 d2b32360 Giorgos Korfiatis
        return (sub_list, add_list)
1658 65360c65 Georgios D. Tsoukalas
1659 ee45eb81 Giorgos Korfiatis
    def set_sync(self):
1660 ee45eb81 Giorgos Korfiatis
        state = self.state
1661 ee45eb81 Giorgos Korfiatis
        if state == self.PENDING:
1662 ee45eb81 Giorgos Korfiatis
            pending_application = self.pending_application
1663 ee45eb81 Giorgos Korfiatis
            if pending_application is None:
1664 ee45eb81 Giorgos Korfiatis
                m = _("%s: attempt to sync an empty pending application") % (
1665 8c7b8bb8 Giorgos Korfiatis
                    self,)
1666 ee45eb81 Giorgos Korfiatis
                raise AssertionError(m)
1667 ee45eb81 Giorgos Korfiatis
            self.application = pending_application
1668 ee45eb81 Giorgos Korfiatis
            self.pending_application = None
1669 ee45eb81 Giorgos Korfiatis
            self.pending_serial = None
1670 ee45eb81 Giorgos Korfiatis
1671 ee45eb81 Giorgos Korfiatis
            # project.application may have changed in the meantime,
1672 ee45eb81 Giorgos Korfiatis
            # in which case we stay PENDING;
1673 ee45eb81 Giorgos Korfiatis
            # we are safe to check due to select_for_update
1674 ee45eb81 Giorgos Korfiatis
            if self.application == self.project.application:
1675 ee45eb81 Giorgos Korfiatis
                self.state = self.ACCEPTED
1676 ee45eb81 Giorgos Korfiatis
            self.save()
1677 5b9e9530 Giorgos Korfiatis
        elif state == self.ACCEPTED:
1678 5b9e9530 Giorgos Korfiatis
            if self.pending_application:
1679 5b9e9530 Giorgos Korfiatis
                m = _("%s: attempt to sync in state '%s' "
1680 5b9e9530 Giorgos Korfiatis
                      "with a pending application") % (self, state)
1681 5b9e9530 Giorgos Korfiatis
                raise AssertionError(m)
1682 5b9e9530 Giorgos Korfiatis
            self.application = None
1683 5b9e9530 Giorgos Korfiatis
            self.pending_serial = None
1684 5b9e9530 Giorgos Korfiatis
            self.state = self.INACTIVE
1685 5b9e9530 Giorgos Korfiatis
            self.save()
1686 ee45eb81 Giorgos Korfiatis
        elif state == self.REMOVING:
1687 ee45eb81 Giorgos Korfiatis
            self.delete()
1688 ee45eb81 Giorgos Korfiatis
        else:
1689 ee45eb81 Giorgos Korfiatis
            m = _("%s: attempt to sync in state '%s'") % (self, state)
1690 ee45eb81 Giorgos Korfiatis
            raise AssertionError(m)
1691 ee45eb81 Giorgos Korfiatis
1692 49b74233 Georgios D. Tsoukalas
    def reset_sync(self):
1693 49b74233 Georgios D. Tsoukalas
        state = self.state
1694 5b9e9530 Giorgos Korfiatis
        if state in [self.PENDING, self.ACCEPTED, self.REMOVING]:
1695 49b74233 Georgios D. Tsoukalas
            self.pending_application = None
1696 49b74233 Georgios D. Tsoukalas
            self.pending_serial = None
1697 49b74233 Georgios D. Tsoukalas
            self.save()
1698 49b74233 Georgios D. Tsoukalas
        else:
1699 49b74233 Georgios D. Tsoukalas
            m = _("%s: attempt to reset sync in state '%s'") % (self, state)
1700 49b74233 Georgios D. Tsoukalas
            raise AssertionError(m)
1701 49b74233 Georgios D. Tsoukalas
1702 ee45eb81 Giorgos Korfiatis
class Serial(models.Model):
1703 ee45eb81 Giorgos Korfiatis
    serial  =   models.AutoField(primary_key=True)
1704 ee45eb81 Giorgos Korfiatis
1705 ee45eb81 Giorgos Korfiatis
def new_serial():
1706 5200e864 Sofia Papagiannaki
    s = Serial.objects.create()
1707 c70968bd Giorgos Korfiatis
    serial = s.serial
1708 c70968bd Giorgos Korfiatis
    s.delete()
1709 c70968bd Giorgos Korfiatis
    return serial
1710 82d7e9ef Georgios D. Tsoukalas
1711 333f6a72 Sofia Papagiannaki
def sync_finish_serials(serials_to_ack=None):
1712 333f6a72 Sofia Papagiannaki
    if serials_to_ack is None:
1713 333f6a72 Sofia Papagiannaki
        serials_to_ack = qh_query_serials([])
1714 333f6a72 Sofia Papagiannaki
1715 333f6a72 Sofia Papagiannaki
    serials_to_ack = set(serials_to_ack)
1716 d6fdc91e Georgios D. Tsoukalas
    sfu = ProjectMembership.objects.select_for_update()
1717 333f6a72 Sofia Papagiannaki
    memberships = list(sfu.filter(pending_serial__isnull=False))
1718 333f6a72 Sofia Papagiannaki
1719 60ca2f6f Giorgos Korfiatis
    if memberships:
1720 60ca2f6f Giorgos Korfiatis
        for membership in memberships:
1721 60ca2f6f Giorgos Korfiatis
            serial = membership.pending_serial
1722 60ca2f6f Giorgos Korfiatis
            if serial in serials_to_ack:
1723 60ca2f6f Giorgos Korfiatis
                membership.set_sync()
1724 60ca2f6f Giorgos Korfiatis
            else:
1725 60ca2f6f Giorgos Korfiatis
                membership.reset_sync()
1726 60ca2f6f Giorgos Korfiatis
1727 60ca2f6f Giorgos Korfiatis
        transaction.commit()
1728 60ca2f6f Giorgos Korfiatis
1729 ee45eb81 Giorgos Korfiatis
    qh_ack_serials(list(serials_to_ack))
1730 333f6a72 Sofia Papagiannaki
    return len(memberships)
1731 82d7e9ef Georgios D. Tsoukalas
1732 5b9e9530 Giorgos Korfiatis
def sync_all_projects():
1733 d6fdc91e Georgios D. Tsoukalas
    sync_finish_serials()
1734 82d7e9ef Georgios D. Tsoukalas
1735 d6fdc91e Georgios D. Tsoukalas
    PENDING = ProjectMembership.PENDING
1736 d6fdc91e Georgios D. Tsoukalas
    REMOVING = ProjectMembership.REMOVING
1737 d6fdc91e Georgios D. Tsoukalas
    objects = ProjectMembership.objects.select_for_update()
1738 82d7e9ef Georgios D. Tsoukalas
1739 d2b32360 Giorgos Korfiatis
    sub_quota, add_quota = [], []
1740 65360c65 Georgios D. Tsoukalas
1741 ee45eb81 Giorgos Korfiatis
    serial = new_serial()
1742 d6fdc91e Georgios D. Tsoukalas
1743 d6fdc91e Georgios D. Tsoukalas
    pending = objects.filter(state=PENDING)
1744 d6fdc91e Georgios D. Tsoukalas
    for membership in pending:
1745 d6fdc91e Georgios D. Tsoukalas
1746 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_application:
1747 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_application is not None (%s)" % (
1748 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_application)
1749 d6fdc91e Georgios D. Tsoukalas
            raise AssertionError(m)
1750 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_serial:
1751 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_serial is not None (%s)" % (
1752 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_serial)
1753 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
1754 b8f05f8d Sofia Papagiannaki
1755 ee45eb81 Giorgos Korfiatis
        membership.pending_application = membership.project.application
1756 d6fdc91e Georgios D. Tsoukalas
        membership.pending_serial = serial
1757 d2b32360 Giorgos Korfiatis
        membership.get_diff_quotas(sub_quota, add_quota)
1758 d6fdc91e Georgios D. Tsoukalas
        membership.save()
1759 65360c65 Georgios D. Tsoukalas
1760 d6fdc91e Georgios D. Tsoukalas
    removing = objects.filter(state=REMOVING)
1761 d6fdc91e Georgios D. Tsoukalas
    for membership in removing:
1762 bfe23b13 Sofia Papagiannaki
1763 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_application:
1764 d6fdc91e Georgios D. Tsoukalas
            m = ("%s: impossible: removing pending_application is not None (%s)"
1765 d6fdc91e Georgios D. Tsoukalas
                % (membership, membership.pending_application))
1766 d6fdc91e Georgios D. Tsoukalas
            raise AssertionError(m)
1767 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_serial:
1768 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_serial is not None (%s)" % (
1769 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_serial)
1770 d6fdc91e Georgios D. Tsoukalas
            raise AssertionError(m)
1771 d6fdc91e Georgios D. Tsoukalas
1772 d6fdc91e Georgios D. Tsoukalas
        membership.pending_serial = serial
1773 d2b32360 Giorgos Korfiatis
        membership.get_diff_quotas(sub_quota, add_quota, remove=True)
1774 d6fdc91e Georgios D. Tsoukalas
        membership.save()
1775 d6fdc91e Georgios D. Tsoukalas
1776 d6fdc91e Georgios D. Tsoukalas
    transaction.commit()
1777 ee45eb81 Giorgos Korfiatis
    # ProjectApplication.approve() unblocks here
1778 ee45eb81 Giorgos Korfiatis
    # and can set PENDING an already PENDING membership
1779 ee45eb81 Giorgos Korfiatis
    # which has been scheduled to sync with the old project.application
1780 ee45eb81 Giorgos Korfiatis
    # Need to check in ProjectMembership.set_sync()
1781 ee45eb81 Giorgos Korfiatis
1782 5cfd4acb Sofia Papagiannaki
    r = qh_add_quota(serial, sub_quota, add_quota)
1783 333f6a72 Sofia Papagiannaki
    if r:
1784 333f6a72 Sofia Papagiannaki
        m = "cannot sync serial: %d" % serial
1785 333f6a72 Sofia Papagiannaki
        raise RuntimeError(m)
1786 333f6a72 Sofia Papagiannaki
1787 333f6a72 Sofia Papagiannaki
    sync_finish_serials([serial])
1788 d6fdc91e Georgios D. Tsoukalas
1789 5b9e9530 Giorgos Korfiatis
def sync_deactivating_projects():
1790 5b9e9530 Giorgos Korfiatis
1791 5b9e9530 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
1792 5b9e9530 Giorgos Korfiatis
    PENDING = ProjectMembership.PENDING
1793 5b9e9530 Giorgos Korfiatis
    REMOVING = ProjectMembership.REMOVING
1794 5b9e9530 Giorgos Korfiatis
1795 5b9e9530 Giorgos Korfiatis
    psfu = Project.objects.select_for_update()
1796 5b9e9530 Giorgos Korfiatis
    projects = psfu.filter(deactivation_start_date__isnull=False)
1797 5b9e9530 Giorgos Korfiatis
1798 5b9e9530 Giorgos Korfiatis
    if not projects:
1799 5b9e9530 Giorgos Korfiatis
        return
1800 5b9e9530 Giorgos Korfiatis
1801 5b9e9530 Giorgos Korfiatis
    sub_quota, add_quota = [], []
1802 5b9e9530 Giorgos Korfiatis
1803 5b9e9530 Giorgos Korfiatis
    serial = new_serial()
1804 5b9e9530 Giorgos Korfiatis
1805 5b9e9530 Giorgos Korfiatis
    for project in projects:
1806 5b9e9530 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
1807 5b9e9530 Giorgos Korfiatis
        memberships = objects.filter(Q(state=ACCEPTED) |
1808 5b9e9530 Giorgos Korfiatis
                                     Q(state=PENDING) | Q(state=REMOVING))
1809 5b9e9530 Giorgos Korfiatis
        for membership in memberships:
1810 5b9e9530 Giorgos Korfiatis
            if membership.state in (PENDING, REMOVING):
1811 5b9e9530 Giorgos Korfiatis
                m = "cannot sync deactivating project '%s'" % project
1812 5b9e9530 Giorgos Korfiatis
                raise RuntimeError(m)
1813 5b9e9530 Giorgos Korfiatis
1814 5b9e9530 Giorgos Korfiatis
            # state == ACCEPTED
1815 5b9e9530 Giorgos Korfiatis
            if membership.pending_application:
1816 5b9e9530 Giorgos Korfiatis
                m = "%s: impossible: pending_application is not None (%s)" % (
1817 5b9e9530 Giorgos Korfiatis
                    membership, membership.pending_application)
1818 5b9e9530 Giorgos Korfiatis
                raise AssertionError(m)
1819 5b9e9530 Giorgos Korfiatis
            if membership.pending_serial:
1820 5b9e9530 Giorgos Korfiatis
                m = "%s: impossible: pending_serial is not None (%s)" % (
1821 5b9e9530 Giorgos Korfiatis
                    membership, membership.pending_serial)
1822 5b9e9530 Giorgos Korfiatis
                raise AssertionError(m)
1823 5b9e9530 Giorgos Korfiatis
1824 5b9e9530 Giorgos Korfiatis
            membership.pending_serial = serial
1825 5b9e9530 Giorgos Korfiatis
            membership.get_diff_quotas(sub_quota, add_quota, remove=True)
1826 5b9e9530 Giorgos Korfiatis
            membership.save()
1827 5b9e9530 Giorgos Korfiatis
1828 5b9e9530 Giorgos Korfiatis
    transaction.commit()
1829 5b9e9530 Giorgos Korfiatis
1830 5b9e9530 Giorgos Korfiatis
    r = qh_add_quota(serial, sub_quota, add_quota)
1831 5b9e9530 Giorgos Korfiatis
    if r:
1832 5b9e9530 Giorgos Korfiatis
        m = "cannot sync serial: %d" % serial
1833 5b9e9530 Giorgos Korfiatis
        raise RuntimeError(m)
1834 5b9e9530 Giorgos Korfiatis
1835 5b9e9530 Giorgos Korfiatis
    sync_finish_serials([serial])
1836 5b9e9530 Giorgos Korfiatis
1837 5b9e9530 Giorgos Korfiatis
    # finalize deactivating projects
1838 5b9e9530 Giorgos Korfiatis
    deactivating_projects = psfu.filter(deactivation_start_date__isnull=False)
1839 5b9e9530 Giorgos Korfiatis
    for project in deactivating_projects:
1840 5b9e9530 Giorgos Korfiatis
        objects = project.projectmembership_set.select_for_update()
1841 5b9e9530 Giorgos Korfiatis
        memberships = list(objects.filter(Q(state=ACCEPTED) |
1842 5b9e9530 Giorgos Korfiatis
                                          Q(state=PENDING) | Q(state=REMOVING)))
1843 5b9e9530 Giorgos Korfiatis
        if not memberships:
1844 5b9e9530 Giorgos Korfiatis
            project.set_deactivation_date()
1845 5b9e9530 Giorgos Korfiatis
            project.save()
1846 5b9e9530 Giorgos Korfiatis
1847 5b9e9530 Giorgos Korfiatis
    transaction.commit()
1848 5b9e9530 Giorgos Korfiatis
1849 5b9e9530 Giorgos Korfiatis
def sync_projects():
1850 5b9e9530 Giorgos Korfiatis
    sync_all_projects()
1851 5b9e9530 Giorgos Korfiatis
    sync_deactivating_projects()
1852 d6fdc91e Georgios D. Tsoukalas
1853 d6fdc91e Georgios D. Tsoukalas
def trigger_sync(retries=3, retry_wait=1.0):
1854 14695557 Giorgos Korfiatis
    transaction.commit()
1855 14695557 Giorgos Korfiatis
1856 d6fdc91e Georgios D. Tsoukalas
    cursor = connection.cursor()
1857 d6fdc91e Georgios D. Tsoukalas
    locked = True
1858 d6fdc91e Georgios D. Tsoukalas
    try:
1859 d6fdc91e Georgios D. Tsoukalas
        while 1:
1860 d6fdc91e Georgios D. Tsoukalas
            cursor.execute("SELECT pg_try_advisory_lock(1)")
1861 d6fdc91e Georgios D. Tsoukalas
            r = cursor.fetchone()
1862 d6fdc91e Georgios D. Tsoukalas
            if r is None:
1863 d6fdc91e Georgios D. Tsoukalas
                m = "Impossible"
1864 d6fdc91e Georgios D. Tsoukalas
                raise AssertionError(m)
1865 d6fdc91e Georgios D. Tsoukalas
            locked = r[0]
1866 d6fdc91e Georgios D. Tsoukalas
            if locked:
1867 d6fdc91e Georgios D. Tsoukalas
                break
1868 d6fdc91e Georgios D. Tsoukalas
1869 d6fdc91e Georgios D. Tsoukalas
            retries -= 1
1870 d6fdc91e Georgios D. Tsoukalas
            if retries <= 0:
1871 d6fdc91e Georgios D. Tsoukalas
                return False
1872 d6fdc91e Georgios D. Tsoukalas
            sleep(retry_wait)
1873 d6fdc91e Georgios D. Tsoukalas
1874 d6fdc91e Georgios D. Tsoukalas
        sync_projects()
1875 d6fdc91e Georgios D. Tsoukalas
        return True
1876 d6fdc91e Georgios D. Tsoukalas
1877 d6fdc91e Georgios D. Tsoukalas
    finally:
1878 d6fdc91e Georgios D. Tsoukalas
        if locked:
1879 d6fdc91e Georgios D. Tsoukalas
            cursor.execute("SELECT pg_advisory_unlock(1)")
1880 d6fdc91e Georgios D. Tsoukalas
            cursor.fetchall()
1881 4f22664f Georgios D. Tsoukalas
1882 e1a80257 Sofia Papagiannaki
1883 0cc22d47 Sofia Papagiannaki
class ProjectMembershipHistory(models.Model):
1884 425e2e95 Sofia Papagiannaki
    reasons_list    =   ['ACCEPT', 'REJECT', 'REMOVE']
1885 425e2e95 Sofia Papagiannaki
    reasons         =   dict((k, v) for v, k in enumerate(reasons_list))
1886 425e2e95 Sofia Papagiannaki
1887 02d2519e Giorgos Korfiatis
    person  =   models.CharField(max_length=255)
1888 02d2519e Giorgos Korfiatis
    project =   models.BigIntegerField()
1889 425e2e95 Sofia Papagiannaki
    date    =   models.DateField(default=datetime.now)
1890 425e2e95 Sofia Papagiannaki
    reason  =   models.IntegerField()
1891 425e2e95 Sofia Papagiannaki
    serial  =   models.BigIntegerField()
1892 fc655b6f Kostas Papadimitriou
1893 fcc1e93f Sofia Papagiannaki
### SIGNALS ###
1894 fcc1e93f Sofia Papagiannaki
################
1895 b22de10a Sofia Papagiannaki
1896 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
1897 ff9290ec Sofia Papagiannaki
    try:
1898 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
1899 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
1900 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
1901 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
1902 ff9290ec Sofia Papagiannaki
        extended_user.save()
1903 67be1883 Olga Brani
        if not extended_user.has_auth_provider('local'):
1904 67be1883 Olga Brani
            extended_user.add_auth_provider('local')
1905 fc1e2f02 Sofia Papagiannaki
    except BaseException, e:
1906 fc1e2f02 Sofia Papagiannaki
        logger.exception(e)
1907 ff9290ec Sofia Papagiannaki
1908 5ce3ce4f Sofia Papagiannaki
1909 fc1e2f02 Sofia Papagiannaki
def fix_superusers(sender, **kwargs):
1910 fc1e2f02 Sofia Papagiannaki
    # Associate superusers with AstakosUser
1911 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
1912 ff9290ec Sofia Papagiannaki
    for u in admins:
1913 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
1914 bfe23b13 Sofia Papagiannaki
post_syncdb.connect(fix_superusers)
1915 ff9290ec Sofia Papagiannaki
1916 ff9290ec Sofia Papagiannaki
1917 fc1e2f02 Sofia Papagiannaki
def user_post_save(sender, instance, created, **kwargs):
1918 aa4109d4 Sofia Papagiannaki
    if not created:
1919 aa4109d4 Sofia Papagiannaki
        return
1920 fc1e2f02 Sofia Papagiannaki
    create_astakos_user(instance)
1921 bfe23b13 Sofia Papagiannaki
post_save.connect(user_post_save, sender=User)
1922 ff9290ec Sofia Papagiannaki
1923 fc1e2f02 Sofia Papagiannaki
def astakosuser_post_save(sender, instance, created, **kwargs):
1924 fc1e2f02 Sofia Papagiannaki
    if not created:
1925 fc1e2f02 Sofia Papagiannaki
        return
1926 fc1e2f02 Sofia Papagiannaki
    # TODO handle socket.error & IOError
1927 fc1e2f02 Sofia Papagiannaki
    register_users((instance,))
1928 bfe23b13 Sofia Papagiannaki
post_save.connect(astakosuser_post_save, sender=AstakosUser)
1929 fc1e2f02 Sofia Papagiannaki
1930 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
1931 bd4f356c Sofia Papagiannaki
    if not created:
1932 bd4f356c Sofia Papagiannaki
        return
1933 bd4f356c Sofia Papagiannaki
    register_resources((instance,))
1934 bfe23b13 Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
1935 bfe23b13 Sofia Papagiannaki
1936 bfe23b13 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
1937 bfe23b13 Sofia Papagiannaki
    if not instance.auth_token:
1938 bfe23b13 Sofia Papagiannaki
        instance.renew_token()
1939 bf0c6de5 Sofia Papagiannaki
pre_save.connect(renew_token, sender=AstakosUser)
1940 2e90e3ec Kostas Papadimitriou
pre_save.connect(renew_token, sender=Service)