Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (66.7 kB)

1 aba1e498 Antony Chazapis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 6c736ed7 Kostas Papadimitriou
#
3 64cd4730 Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 64cd4730 Antony Chazapis
# without modification, are permitted provided that the following
5 64cd4730 Antony Chazapis
# conditions are met:
6 6c736ed7 Kostas Papadimitriou
#
7 64cd4730 Antony Chazapis
#   1. Redistributions of source code must retain the above
8 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
9 64cd4730 Antony Chazapis
#      disclaimer.
10 6c736ed7 Kostas Papadimitriou
#
11 64cd4730 Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
13 64cd4730 Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 64cd4730 Antony Chazapis
#      provided with the distribution.
15 6c736ed7 Kostas Papadimitriou
#
16 64cd4730 Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 64cd4730 Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 64cd4730 Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 64cd4730 Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 64cd4730 Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 64cd4730 Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 64cd4730 Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 64cd4730 Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 64cd4730 Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 64cd4730 Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 64cd4730 Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 64cd4730 Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 6c736ed7 Kostas Papadimitriou
#
29 64cd4730 Antony Chazapis
# The views and conclusions contained in the software and
30 64cd4730 Antony Chazapis
# documentation are those of the authors and should not be
31 64cd4730 Antony Chazapis
# interpreted as representing official policies, either expressed
32 64cd4730 Antony Chazapis
# or implied, of GRNET S.A.
33 64cd4730 Antony Chazapis
34 64cd4730 Antony Chazapis
import hashlib
35 15efc749 Sofia Papagiannaki
import uuid
36 18ffbee1 Sofia Papagiannaki
import logging
37 3a72a5d4 Kostas Papadimitriou
import json
38 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 21e0fdad Giorgos Korfiatis
    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 b6fe8bb8 Giorgos Korfiatis
            if not p.is_active_strict():
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 a9ba418f Giorgos Korfiatis
class Chain(models.Model):
1175 a9ba418f Giorgos Korfiatis
    chain  =   models.AutoField(primary_key=True)
1176 a9ba418f Giorgos Korfiatis
1177 a9ba418f Giorgos Korfiatis
def new_chain():
1178 a9ba418f Giorgos Korfiatis
    c = Chain.objects.create()
1179 a9ba418f Giorgos Korfiatis
    chain = c.chain
1180 a9ba418f Giorgos Korfiatis
    c.delete()
1181 a9ba418f Giorgos Korfiatis
    return chain
1182 a9ba418f Giorgos Korfiatis
1183 a9ba418f Giorgos Korfiatis
1184 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1185 85d444db Sofia Papagiannaki
    PENDING, APPROVED, REPLACED, UNKNOWN = 'Pending', 'Approved', 'Replaced', 'Unknown'
1186 425e2e95 Sofia Papagiannaki
    applicant               =   models.ForeignKey(
1187 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1188 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_applied',
1189 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1190 d6fdc91e Georgios D. Tsoukalas
1191 d6fdc91e Georgios D. Tsoukalas
    state                   =   models.CharField(max_length=80,
1192 69c822cc Giorgos Korfiatis
                                                default=PENDING)
1193 d6fdc91e Georgios D. Tsoukalas
1194 425e2e95 Sofia Papagiannaki
    owner                   =   models.ForeignKey(
1195 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1196 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_owned',
1197 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1198 d6fdc91e Georgios D. Tsoukalas
1199 a9ba418f Giorgos Korfiatis
    chain                   =   models.IntegerField(db_index=True)
1200 425e2e95 Sofia Papagiannaki
    precursor_application   =   models.OneToOneField('ProjectApplication',
1201 425e2e95 Sofia Papagiannaki
                                                     null=True,
1202 425e2e95 Sofia Papagiannaki
                                                     blank=True,
1203 d6fdc91e Georgios D. Tsoukalas
                                                     db_index=True)
1204 425e2e95 Sofia Papagiannaki
1205 67980f56 Georgios D. Tsoukalas
    name                    =   models.CharField(max_length=80)
1206 67980f56 Georgios D. Tsoukalas
    homepage                =   models.URLField(max_length=255, null=True)
1207 67980f56 Georgios D. Tsoukalas
    description             =   models.TextField(null=True, blank=True)
1208 e729f165 Kostas Papadimitriou
    start_date              =   models.DateTimeField(null=True, blank=True)
1209 67980f56 Georgios D. Tsoukalas
    end_date                =   models.DateTimeField()
1210 272cf735 Sofia Papagiannaki
    member_join_policy      =   models.IntegerField()
1211 272cf735 Sofia Papagiannaki
    member_leave_policy     =   models.IntegerField()
1212 67980f56 Georgios D. Tsoukalas
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1213 425e2e95 Sofia Papagiannaki
    resource_grants         =   models.ManyToManyField(
1214 425e2e95 Sofia Papagiannaki
                                    Resource,
1215 425e2e95 Sofia Papagiannaki
                                    null=True,
1216 425e2e95 Sofia Papagiannaki
                                    blank=True,
1217 d6fdc91e Georgios D. Tsoukalas
                                    through='ProjectResourceGrant')
1218 425e2e95 Sofia Papagiannaki
    comments                =   models.TextField(null=True, blank=True)
1219 69c822cc Giorgos Korfiatis
    issue_date              =   models.DateTimeField(default=datetime.now)
1220 425e2e95 Sofia Papagiannaki
1221 6dcf53eb Kostas Papadimitriou
1222 5550bcfb Kostas Papadimitriou
    objects                 =   ProjectApplicationManager()
1223 7729e9cc Giorgos Korfiatis
1224 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1225 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1226 f3a45fc6 Kostas Papadimitriou
1227 db9a498c Kostas Papadimitriou
    def state_display(self):
1228 db9a498c Kostas Papadimitriou
        return PROJECT_STATE_DISPLAY.get(self.state, _('Unknown'))
1229 db9a498c Kostas Papadimitriou
1230 a7aba804 Sofia Papagiannaki
    def add_resource_policy(self, service, resource, uplimit):
1231 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1232 a7aba804 Sofia Papagiannaki
        q = self.projectresourcegrant_set
1233 e1a80257 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=service, name=resource)
1234 a7aba804 Sofia Papagiannaki
        q.create(resource=resource, member_capacity=uplimit)
1235 e1a80257 Sofia Papagiannaki
1236 e87bbb41 Kostas Papadimitriou
    def user_status(self, user):
1237 e87bbb41 Kostas Papadimitriou
        """
1238 e87bbb41 Kostas Papadimitriou
        100 OWNER
1239 e87bbb41 Kostas Papadimitriou
        0   REQUESTED
1240 e87bbb41 Kostas Papadimitriou
        1   PENDING
1241 e87bbb41 Kostas Papadimitriou
        2   ACCEPTED
1242 e87bbb41 Kostas Papadimitriou
        3   REMOVING
1243 e87bbb41 Kostas Papadimitriou
        4   REMOVED
1244 e87bbb41 Kostas Papadimitriou
       -1   User has no association with the project
1245 e87bbb41 Kostas Papadimitriou
        """
1246 db9a498c Kostas Papadimitriou
        try:
1247 db9a498c Kostas Papadimitriou
            membership = self.project.projectmembership_set.get(person=user)
1248 db9a498c Kostas Papadimitriou
            status = membership.state
1249 db9a498c Kostas Papadimitriou
        except Project.DoesNotExist:
1250 db9a498c Kostas Papadimitriou
            status = -1
1251 db9a498c Kostas Papadimitriou
        except ProjectMembership.DoesNotExist:
1252 db9a498c Kostas Papadimitriou
            status = -1
1253 5550bcfb Kostas Papadimitriou
1254 5550bcfb Kostas Papadimitriou
        return status
1255 5550bcfb Kostas Papadimitriou
1256 db9a498c Kostas Papadimitriou
    def user_status_display(self, user):
1257 db9a498c Kostas Papadimitriou
        return USER_STATUS_DISPLAY.get(self.user_status(user), _('Unknown'))
1258 db9a498c Kostas Papadimitriou
1259 5550bcfb Kostas Papadimitriou
    def members_count(self):
1260 5550bcfb Kostas Papadimitriou
        return self.project.approved_memberships.count()
1261 5550bcfb Kostas Papadimitriou
1262 669cfe19 Olga Brani
    @property
1263 669cfe19 Olga Brani
    def grants(self):
1264 669cfe19 Olga Brani
        return self.projectresourcegrant_set.values('member_capacity', 'resource__name', 'resource__service__name')
1265 5550bcfb Kostas Papadimitriou
1266 e1a80257 Sofia Papagiannaki
    @property
1267 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1268 f3342849 Sofia Papagiannaki
        return self.projectresourcegrant_set.all()
1269 e1a80257 Sofia Papagiannaki
1270 e1a80257 Sofia Papagiannaki
    @resource_policies.setter
1271 e1a80257 Sofia Papagiannaki
    def resource_policies(self, policies):
1272 e1a80257 Sofia Papagiannaki
        for p in policies:
1273 e1a80257 Sofia Papagiannaki
            service = p.get('service', None)
1274 e1a80257 Sofia Papagiannaki
            resource = p.get('resource', None)
1275 e1a80257 Sofia Papagiannaki
            uplimit = p.get('uplimit', 0)
1276 a7aba804 Sofia Papagiannaki
            self.add_resource_policy(service, resource, uplimit)
1277 425e2e95 Sofia Papagiannaki
1278 ccab6eb5 Sofia Papagiannaki
    @property
1279 ccab6eb5 Sofia Papagiannaki
    def follower(self):
1280 ccab6eb5 Sofia Papagiannaki
        try:
1281 ccab6eb5 Sofia Papagiannaki
            return ProjectApplication.objects.get(precursor_application=self)
1282 ccab6eb5 Sofia Papagiannaki
        except ProjectApplication.DoesNotExist:
1283 ccab6eb5 Sofia Papagiannaki
            return
1284 ccab6eb5 Sofia Papagiannaki
1285 9b32e2fb Kostas Papadimitriou
    def followers(self):
1286 9b32e2fb Kostas Papadimitriou
        current = self
1287 9b32e2fb Kostas Papadimitriou
        try:
1288 9b32e2fb Kostas Papadimitriou
            while current.projectapplication:
1289 9b32e2fb Kostas Papadimitriou
                yield current.follower
1290 9b32e2fb Kostas Papadimitriou
                current = current.follower
1291 9b32e2fb Kostas Papadimitriou
        except:
1292 9b32e2fb Kostas Papadimitriou
            pass
1293 9b32e2fb Kostas Papadimitriou
1294 9b32e2fb Kostas Papadimitriou
    def last_follower(self):
1295 9b32e2fb Kostas Papadimitriou
        try:
1296 9b32e2fb Kostas Papadimitriou
            return list(self.followers())[-1]
1297 9b32e2fb Kostas Papadimitriou
        except IndexError:
1298 9b32e2fb Kostas Papadimitriou
            return None
1299 9b32e2fb Kostas Papadimitriou
1300 b6fe8bb8 Giorgos Korfiatis
    def _get_project_for_update(self):
1301 a9ba418f Giorgos Korfiatis
        try:
1302 a9ba418f Giorgos Korfiatis
            objects = Project.objects.select_for_update()
1303 a9ba418f Giorgos Korfiatis
            project = objects.get(id=self.chain)
1304 a9ba418f Giorgos Korfiatis
            return project
1305 a9ba418f Giorgos Korfiatis
        except Project.DoesNotExist:
1306 a9ba418f Giorgos Korfiatis
            return None
1307 4f22664f Georgios D. Tsoukalas
1308 ccab6eb5 Sofia Papagiannaki
    def approve(self, approval_user=None):
1309 ccab6eb5 Sofia Papagiannaki
        """
1310 ccab6eb5 Sofia Papagiannaki
        If approval_user then during owner membership acceptance
1311 ccab6eb5 Sofia Papagiannaki
        it is checked whether the request_user is eligible.
1312 262e04c6 Giorgos Korfiatis

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