Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (92 kB)

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

1039 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1040 49790d9d Sofia Papagiannaki
        after activating.
1041 49790d9d Sofia Papagiannaki

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

1044 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1045 49790d9d Sofia Papagiannaki
        return ``None``.
1046 49790d9d Sofia Papagiannaki

1047 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1048 49790d9d Sofia Papagiannaki

1049 49790d9d Sofia Papagiannaki
        Throws ValueError if there is already
1050 49790d9d Sofia Papagiannaki
        """
1051 49790d9d Sofia Papagiannaki
        try:
1052 5ce3ce4f Sofia Papagiannaki
            email_change = self.model.objects.get(
1053 5ce3ce4f Sofia Papagiannaki
                activation_key=activation_key)
1054 49790d9d Sofia Papagiannaki
            if email_change.activation_key_expired():
1055 49790d9d Sofia Papagiannaki
                email_change.delete()
1056 49790d9d Sofia Papagiannaki
                raise EmailChange.DoesNotExist
1057 49790d9d Sofia Papagiannaki
            # is there an active user with this address?
1058 49790d9d Sofia Papagiannaki
            try:
1059 789a5951 Sofia Papagiannaki
                AstakosUser.objects.get(email__iexact=email_change.new_email_address)
1060 49790d9d Sofia Papagiannaki
            except AstakosUser.DoesNotExist:
1061 49790d9d Sofia Papagiannaki
                pass
1062 49790d9d Sofia Papagiannaki
            else:
1063 73fbaec4 Sofia Papagiannaki
                raise ValueError(_('The new email address is reserved.'))
1064 49790d9d Sofia Papagiannaki
            # update user
1065 49790d9d Sofia Papagiannaki
            user = AstakosUser.objects.get(pk=email_change.user_id)
1066 34a76cdb Kostas Papadimitriou
            old_email = user.email
1067 49790d9d Sofia Papagiannaki
            user.email = email_change.new_email_address
1068 49790d9d Sofia Papagiannaki
            user.save()
1069 49790d9d Sofia Papagiannaki
            email_change.delete()
1070 34a76cdb Kostas Papadimitriou
            msg = "User %d changed email from %s to %s" % (user.pk, old_email,
1071 34a76cdb Kostas Papadimitriou
                                                          user.email)
1072 34a76cdb Kostas Papadimitriou
            logger.log(LOGGING_LEVEL, msg)
1073 49790d9d Sofia Papagiannaki
            return user
1074 49790d9d Sofia Papagiannaki
        except EmailChange.DoesNotExist:
1075 73fbaec4 Sofia Papagiannaki
            raise ValueError(_('Invalid activation key.'))
1076 49790d9d Sofia Papagiannaki
1077 49790d9d Sofia Papagiannaki
1078 49790d9d Sofia Papagiannaki
class EmailChange(models.Model):
1079 73fbaec4 Sofia Papagiannaki
    new_email_address = models.EmailField(
1080 73fbaec4 Sofia Papagiannaki
        _(u'new e-mail address'),
1081 b8b8fdde Constantinos Venetsanopoulos
        help_text=_('Provide a new email address. Until you verify the new '
1082 b8b8fdde Constantinos Venetsanopoulos
                    'address by following the activation link that will be '
1083 b8b8fdde Constantinos Venetsanopoulos
                    'sent to it, your old email address will remain active.'))
1084 5ce3ce4f Sofia Papagiannaki
    user = models.ForeignKey(
1085 34a76cdb Kostas Papadimitriou
        AstakosUser, unique=True, related_name='emailchanges')
1086 3c638f72 Giorgos Korfiatis
    requested_at = models.DateTimeField(auto_now_add=True)
1087 5ce3ce4f Sofia Papagiannaki
    activation_key = models.CharField(
1088 5ce3ce4f Sofia Papagiannaki
        max_length=40, unique=True, db_index=True)
1089 49790d9d Sofia Papagiannaki
1090 49790d9d Sofia Papagiannaki
    objects = EmailChangeManager()
1091 49790d9d Sofia Papagiannaki
1092 34a76cdb Kostas Papadimitriou
    def get_url(self):
1093 34a76cdb Kostas Papadimitriou
        return reverse('email_change_confirm',
1094 34a76cdb Kostas Papadimitriou
                      kwargs={'activation_key': self.activation_key})
1095 34a76cdb Kostas Papadimitriou
1096 49790d9d Sofia Papagiannaki
    def activation_key_expired(self):
1097 49790d9d Sofia Papagiannaki
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
1098 ff9290ec Sofia Papagiannaki
        return self.requested_at + expiration_date < datetime.now()
1099 ff9290ec Sofia Papagiannaki
1100 6b03a847 Sofia Papagiannaki
1101 ca828a10 Sofia Papagiannaki
class AdditionalMail(models.Model):
1102 ca828a10 Sofia Papagiannaki
    """
1103 ca828a10 Sofia Papagiannaki
    Model for registring invitations
1104 ca828a10 Sofia Papagiannaki
    """
1105 ca828a10 Sofia Papagiannaki
    owner = models.ForeignKey(AstakosUser)
1106 1eec103a Sofia Papagiannaki
    email = models.EmailField()
1107 ca828a10 Sofia Papagiannaki
1108 5ce3ce4f Sofia Papagiannaki
1109 fc1e2f02 Sofia Papagiannaki
def _generate_invitation_code():
1110 fc1e2f02 Sofia Papagiannaki
    while True:
1111 5ce3ce4f Sofia Papagiannaki
        code = randint(1, 2L ** 63 - 1)
1112 fc1e2f02 Sofia Papagiannaki
        try:
1113 fc1e2f02 Sofia Papagiannaki
            Invitation.objects.get(code=code)
1114 fc1e2f02 Sofia Papagiannaki
            # An invitation with this code already exists, try again
1115 fc1e2f02 Sofia Papagiannaki
        except Invitation.DoesNotExist:
1116 fc1e2f02 Sofia Papagiannaki
            return code
1117 fc1e2f02 Sofia Papagiannaki
1118 5ce3ce4f Sofia Papagiannaki
1119 fc1e2f02 Sofia Papagiannaki
def get_latest_terms():
1120 fc1e2f02 Sofia Papagiannaki
    try:
1121 fc1e2f02 Sofia Papagiannaki
        term = ApprovalTerms.objects.order_by('-id')[0]
1122 fc1e2f02 Sofia Papagiannaki
        return term
1123 fc1e2f02 Sofia Papagiannaki
    except IndexError:
1124 fc1e2f02 Sofia Papagiannaki
        pass
1125 fc1e2f02 Sofia Papagiannaki
    return None
1126 fc1e2f02 Sofia Papagiannaki
1127 ef20ea07 Sofia Papagiannaki
class PendingThirdPartyUser(models.Model):
1128 ef20ea07 Sofia Papagiannaki
    """
1129 ef20ea07 Sofia Papagiannaki
    Model for registring successful third party user authentications
1130 ef20ea07 Sofia Papagiannaki
    """
1131 e1a80257 Sofia Papagiannaki
    third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
1132 e1a80257 Sofia Papagiannaki
    provider = models.CharField(_('Provider'), max_length=255, blank=True)
1133 678b2236 Sofia Papagiannaki
    email = models.EmailField(_('e-mail address'), blank=True, null=True)
1134 564a2292 Kostas Papadimitriou
    first_name = models.CharField(_('first name'), max_length=30, blank=True,
1135 564a2292 Kostas Papadimitriou
                                  null=True)
1136 564a2292 Kostas Papadimitriou
    last_name = models.CharField(_('last name'), max_length=30, blank=True,
1137 564a2292 Kostas Papadimitriou
                                 null=True)
1138 564a2292 Kostas Papadimitriou
    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
1139 564a2292 Kostas Papadimitriou
                                   null=True)
1140 d6ea9b3d Olga Brani
    username = models.CharField(_('username'), max_length=30, unique=True,  
1141 d6ea9b3d Olga Brani
                                help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
1142 e1a80257 Sofia Papagiannaki
    token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
1143 d2633501 Kostas Papadimitriou
    created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
1144 c630fee6 Kostas Papadimitriou
    info = models.TextField(default="", null=True, blank=True)
1145 d2633501 Kostas Papadimitriou
1146 678b2236 Sofia Papagiannaki
    class Meta:
1147 678b2236 Sofia Papagiannaki
        unique_together = ("provider", "third_party_identifier")
1148 ef20ea07 Sofia Papagiannaki
1149 c630fee6 Kostas Papadimitriou
    def get_user_instance(self):
1150 c630fee6 Kostas Papadimitriou
        d = self.__dict__
1151 c630fee6 Kostas Papadimitriou
        d.pop('_state', None)
1152 c630fee6 Kostas Papadimitriou
        d.pop('id', None)
1153 c630fee6 Kostas Papadimitriou
        d.pop('token', None)
1154 c630fee6 Kostas Papadimitriou
        d.pop('created', None)
1155 c630fee6 Kostas Papadimitriou
        d.pop('info', None)
1156 c630fee6 Kostas Papadimitriou
        user = AstakosUser(**d)
1157 c630fee6 Kostas Papadimitriou
1158 c630fee6 Kostas Papadimitriou
        return user
1159 c630fee6 Kostas Papadimitriou
1160 ef20ea07 Sofia Papagiannaki
    @property
1161 ef20ea07 Sofia Papagiannaki
    def realname(self):
1162 ef20ea07 Sofia Papagiannaki
        return '%s %s' %(self.first_name, self.last_name)
1163 ef20ea07 Sofia Papagiannaki
1164 ef20ea07 Sofia Papagiannaki
    @realname.setter
1165 ef20ea07 Sofia Papagiannaki
    def realname(self, value):
1166 ef20ea07 Sofia Papagiannaki
        parts = value.split(' ')
1167 ef20ea07 Sofia Papagiannaki
        if len(parts) == 2:
1168 ef20ea07 Sofia Papagiannaki
            self.first_name = parts[0]
1169 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[1]
1170 ef20ea07 Sofia Papagiannaki
        else:
1171 ef20ea07 Sofia Papagiannaki
            self.last_name = parts[0]
1172 2e90e3ec Kostas Papadimitriou
1173 ef20ea07 Sofia Papagiannaki
    def save(self, **kwargs):
1174 ef20ea07 Sofia Papagiannaki
        if not self.id:
1175 ef20ea07 Sofia Papagiannaki
            # set username
1176 ef20ea07 Sofia Papagiannaki
            while not self.username:
1177 ef20ea07 Sofia Papagiannaki
                username =  uuid.uuid4().hex[:30]
1178 ef20ea07 Sofia Papagiannaki
                try:
1179 ef20ea07 Sofia Papagiannaki
                    AstakosUser.objects.get(username = username)
1180 ef20ea07 Sofia Papagiannaki
                except AstakosUser.DoesNotExist, e:
1181 ef20ea07 Sofia Papagiannaki
                    self.username = username
1182 ef20ea07 Sofia Papagiannaki
        super(PendingThirdPartyUser, self).save(**kwargs)
1183 ef20ea07 Sofia Papagiannaki
1184 d2633501 Kostas Papadimitriou
    def generate_token(self):
1185 d2633501 Kostas Papadimitriou
        self.password = self.third_party_identifier
1186 d2633501 Kostas Papadimitriou
        self.last_login = datetime.now()
1187 d2633501 Kostas Papadimitriou
        self.token = default_token_generator.make_token(self)
1188 d2633501 Kostas Papadimitriou
1189 606dea8d Kostas Papadimitriou
    def existing_user(self):
1190 606dea8d Kostas Papadimitriou
        return AstakosUser.objects.filter(auth_providers__module=self.provider,
1191 606dea8d Kostas Papadimitriou
                                         auth_providers__identifier=self.third_party_identifier)
1192 606dea8d Kostas Papadimitriou
1193 bf0c6de5 Sofia Papagiannaki
class SessionCatalog(models.Model):
1194 bf0c6de5 Sofia Papagiannaki
    session_key = models.CharField(_('session key'), max_length=40)
1195 bf0c6de5 Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1196 bf0c6de5 Sofia Papagiannaki
1197 fcc1e93f Sofia Papagiannaki
1198 fcc1e93f Sofia Papagiannaki
### PROJECTS ###
1199 fcc1e93f Sofia Papagiannaki
################
1200 fcc1e93f Sofia Papagiannaki
1201 e546df49 Georgios D. Tsoukalas
def synced_model_metaclass(class_name, class_parents, class_attributes):
1202 65360c65 Georgios D. Tsoukalas
1203 e546df49 Georgios D. Tsoukalas
    new_attributes = {}
1204 e546df49 Georgios D. Tsoukalas
    sync_attributes = {}
1205 65360c65 Georgios D. Tsoukalas
1206 e546df49 Georgios D. Tsoukalas
    for name, value in class_attributes.iteritems():
1207 e546df49 Georgios D. Tsoukalas
        sync, underscore, rest = name.partition('_')
1208 e546df49 Georgios D. Tsoukalas
        if sync == 'sync' and underscore == '_':
1209 e546df49 Georgios D. Tsoukalas
            sync_attributes[rest] = value
1210 e546df49 Georgios D. Tsoukalas
        else:
1211 e546df49 Georgios D. Tsoukalas
            new_attributes[name] = value
1212 65360c65 Georgios D. Tsoukalas
1213 e546df49 Georgios D. Tsoukalas
    if 'prefix' not in sync_attributes:
1214 e546df49 Georgios D. Tsoukalas
        m = ("you did not specify a 'sync_prefix' attribute "
1215 e546df49 Georgios D. Tsoukalas
             "in class '%s'" % (class_name,))
1216 e546df49 Georgios D. Tsoukalas
        raise ValueError(m)
1217 65360c65 Georgios D. Tsoukalas
1218 e546df49 Georgios D. Tsoukalas
    prefix = sync_attributes.pop('prefix')
1219 e546df49 Georgios D. Tsoukalas
    class_name = sync_attributes.pop('classname', prefix + '_model')
1220 65360c65 Georgios D. Tsoukalas
1221 e546df49 Georgios D. Tsoukalas
    for name, value in sync_attributes.iteritems():
1222 e546df49 Georgios D. Tsoukalas
        newname = prefix + '_' + name
1223 e546df49 Georgios D. Tsoukalas
        if newname in new_attributes:
1224 e546df49 Georgios D. Tsoukalas
            m = ("class '%s' was specified with prefix '%s' "
1225 e546df49 Georgios D. Tsoukalas
                 "but it already has an attribute named '%s'"
1226 e546df49 Georgios D. Tsoukalas
                 % (class_name, prefix, newname))
1227 e546df49 Georgios D. Tsoukalas
            raise ValueError(m)
1228 65360c65 Georgios D. Tsoukalas
1229 e546df49 Georgios D. Tsoukalas
        new_attributes[newname] = value
1230 e546df49 Georgios D. Tsoukalas
1231 e546df49 Georgios D. Tsoukalas
    newclass = type(class_name, class_parents, new_attributes)
1232 e546df49 Georgios D. Tsoukalas
    return newclass
1233 e546df49 Georgios D. Tsoukalas
1234 e546df49 Georgios D. Tsoukalas
1235 e546df49 Georgios D. Tsoukalas
def make_synced(prefix='sync', name='SyncedState'):
1236 e546df49 Georgios D. Tsoukalas
1237 e546df49 Georgios D. Tsoukalas
    the_name = name
1238 e546df49 Georgios D. Tsoukalas
    the_prefix = prefix
1239 e546df49 Georgios D. Tsoukalas
1240 e546df49 Georgios D. Tsoukalas
    class SyncedState(models.Model):
1241 e546df49 Georgios D. Tsoukalas
1242 e546df49 Georgios D. Tsoukalas
        sync_classname      = the_name
1243 e546df49 Georgios D. Tsoukalas
        sync_prefix         = the_prefix
1244 e546df49 Georgios D. Tsoukalas
        __metaclass__       = synced_model_metaclass
1245 e546df49 Georgios D. Tsoukalas
1246 e546df49 Georgios D. Tsoukalas
        sync_new_state      = models.BigIntegerField(null=True)
1247 e546df49 Georgios D. Tsoukalas
        sync_synced_state   = models.BigIntegerField(null=True)
1248 e546df49 Georgios D. Tsoukalas
        STATUS_SYNCED       = 0
1249 e546df49 Georgios D. Tsoukalas
        STATUS_PENDING      = 1
1250 e546df49 Georgios D. Tsoukalas
        sync_status         = models.IntegerField(db_index=True)
1251 e546df49 Georgios D. Tsoukalas
1252 e546df49 Georgios D. Tsoukalas
        class Meta:
1253 e546df49 Georgios D. Tsoukalas
            abstract = True
1254 e546df49 Georgios D. Tsoukalas
1255 e546df49 Georgios D. Tsoukalas
        class NotSynced(Exception):
1256 e546df49 Georgios D. Tsoukalas
            pass
1257 e546df49 Georgios D. Tsoukalas
1258 e546df49 Georgios D. Tsoukalas
        def sync_init_state(self, state):
1259 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = state
1260 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = state
1261 65360c65 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1262 65360c65 Georgios D. Tsoukalas
1263 e546df49 Georgios D. Tsoukalas
        def sync_get_status(self):
1264 e546df49 Georgios D. Tsoukalas
            return self.sync_status
1265 65360c65 Georgios D. Tsoukalas
1266 e546df49 Georgios D. Tsoukalas
        def sync_set_status(self):
1267 e546df49 Georgios D. Tsoukalas
            if self.sync_new_state != self.sync_synced_state:
1268 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_PENDING
1269 e546df49 Georgios D. Tsoukalas
            else:
1270 e546df49 Georgios D. Tsoukalas
                self.sync_status = self.STATUS_SYNCED
1271 65360c65 Georgios D. Tsoukalas
1272 e546df49 Georgios D. Tsoukalas
        def sync_set_synced(self):
1273 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = self.sync_new_state
1274 e546df49 Georgios D. Tsoukalas
            self.sync_status = self.STATUS_SYNCED
1275 65360c65 Georgios D. Tsoukalas
1276 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_state(self):
1277 e546df49 Georgios D. Tsoukalas
            return self.sync_synced_state
1278 65360c65 Georgios D. Tsoukalas
1279 e546df49 Georgios D. Tsoukalas
        def sync_set_new_state(self, new_state):
1280 e546df49 Georgios D. Tsoukalas
            self.sync_new_state = new_state
1281 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1282 65360c65 Georgios D. Tsoukalas
1283 e546df49 Georgios D. Tsoukalas
        def sync_get_new_state(self):
1284 e546df49 Georgios D. Tsoukalas
            return self.sync_new_state
1285 65360c65 Georgios D. Tsoukalas
1286 e546df49 Georgios D. Tsoukalas
        def sync_set_synced_state(self, synced_state):
1287 e546df49 Georgios D. Tsoukalas
            self.sync_synced_state = synced_state
1288 e546df49 Georgios D. Tsoukalas
            self.sync_set_status()
1289 65360c65 Georgios D. Tsoukalas
1290 e546df49 Georgios D. Tsoukalas
        def sync_get_pending_objects(self):
1291 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_PENDING))
1292 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1293 65360c65 Georgios D. Tsoukalas
1294 e546df49 Georgios D. Tsoukalas
        def sync_get_synced_objects(self):
1295 e546df49 Georgios D. Tsoukalas
            kw = dict((the_prefix + '_status', self.STATUS_SYNCED))
1296 e546df49 Georgios D. Tsoukalas
            return self.objects.filter(**kw)
1297 65360c65 Georgios D. Tsoukalas
1298 e546df49 Georgios D. Tsoukalas
        def sync_verify_get_synced_state(self):
1299 e546df49 Georgios D. Tsoukalas
            status = self.sync_get_status()
1300 e546df49 Georgios D. Tsoukalas
            state = self.sync_get_synced_state()
1301 e546df49 Georgios D. Tsoukalas
            verified = (status == self.STATUS_SYNCED)
1302 e546df49 Georgios D. Tsoukalas
            return state, verified
1303 e546df49 Georgios D. Tsoukalas
1304 e546df49 Georgios D. Tsoukalas
        def sync_is_synced(self):
1305 e546df49 Georgios D. Tsoukalas
            state, verified = self.sync_verify_get_synced_state()
1306 e546df49 Georgios D. Tsoukalas
            return verified
1307 e546df49 Georgios D. Tsoukalas
1308 e546df49 Georgios D. Tsoukalas
    return SyncedState
1309 e546df49 Georgios D. Tsoukalas
1310 e546df49 Georgios D. Tsoukalas
SyncedState = make_synced(prefix='sync', name='SyncedState')
1311 425e2e95 Sofia Papagiannaki
1312 425e2e95 Sofia Papagiannaki
1313 2529745f Giorgos Korfiatis
class ChainManager(ForUpdateManager):
1314 2529745f Giorgos Korfiatis
1315 2529745f Giorgos Korfiatis
    def search_by_name(self, *search_strings):
1316 2529745f Giorgos Korfiatis
        projects = Project.objects.search_by_name(*search_strings)
1317 2529745f Giorgos Korfiatis
        chains = [p.id for p in projects]
1318 2529745f Giorgos Korfiatis
        apps  = ProjectApplication.objects.search_by_name(*search_strings)
1319 2529745f Giorgos Korfiatis
        apps = (app for app in apps if app.is_latest())
1320 2529745f Giorgos Korfiatis
        app_chains = [app.chain for app in apps if app.chain not in chains]
1321 2529745f Giorgos Korfiatis
        return chains + app_chains
1322 2529745f Giorgos Korfiatis
1323 2529745f Giorgos Korfiatis
    def all_full_state(self):
1324 2529745f Giorgos Korfiatis
        d = {}
1325 2529745f Giorgos Korfiatis
        chains = self.all()
1326 2529745f Giorgos Korfiatis
        for chain in chains:
1327 2529745f Giorgos Korfiatis
            d[chain.pk] = chain.full_state()
1328 2529745f Giorgos Korfiatis
        return d
1329 2529745f Giorgos Korfiatis
1330 2529745f Giorgos Korfiatis
    def of_project(self, project):
1331 2529745f Giorgos Korfiatis
        if project is None:
1332 2529745f Giorgos Korfiatis
            return None
1333 2529745f Giorgos Korfiatis
        try:
1334 2529745f Giorgos Korfiatis
            return self.get(chain=project.id)
1335 2529745f Giorgos Korfiatis
        except Chain.DoesNotExist:
1336 2529745f Giorgos Korfiatis
            raise AssertionError('project with no chain')
1337 2529745f Giorgos Korfiatis
1338 2529745f Giorgos Korfiatis
1339 033f2822 Giorgos Korfiatis
class Chain(models.Model):
1340 033f2822 Giorgos Korfiatis
    chain  =   models.AutoField(primary_key=True)
1341 033f2822 Giorgos Korfiatis
1342 033f2822 Giorgos Korfiatis
    def __str__(self):
1343 033f2822 Giorgos Korfiatis
        return "%s" % (self.chain,)
1344 033f2822 Giorgos Korfiatis
1345 2529745f Giorgos Korfiatis
    objects = ChainManager()
1346 2529745f Giorgos Korfiatis
1347 2529745f Giorgos Korfiatis
    PENDING            = 0
1348 2529745f Giorgos Korfiatis
    DENIED             = 3
1349 2529745f Giorgos Korfiatis
    DISMISSED          = 4
1350 2529745f Giorgos Korfiatis
    CANCELLED          = 5
1351 2529745f Giorgos Korfiatis
1352 2529745f Giorgos Korfiatis
    APPROVED           = 10
1353 2529745f Giorgos Korfiatis
    APPROVED_PENDING   = 11
1354 2529745f Giorgos Korfiatis
    SUSPENDED          = 12
1355 2529745f Giorgos Korfiatis
    SUSPENDED_PENDING  = 13
1356 2529745f Giorgos Korfiatis
    TERMINATED         = 14
1357 2529745f Giorgos Korfiatis
    TERMINATED_PENDING = 15
1358 2529745f Giorgos Korfiatis
1359 2529745f Giorgos Korfiatis
    PENDING_STATES = [PENDING,
1360 2529745f Giorgos Korfiatis
                      APPROVED_PENDING,
1361 2529745f Giorgos Korfiatis
                      SUSPENDED_PENDING,
1362 2529745f Giorgos Korfiatis
                      TERMINATED_PENDING,
1363 2529745f Giorgos Korfiatis
                      ]
1364 2529745f Giorgos Korfiatis
1365 f557d10a Giorgos Korfiatis
    MODIFICATION_STATES = [APPROVED_PENDING,
1366 f557d10a Giorgos Korfiatis
                           SUSPENDED_PENDING,
1367 f557d10a Giorgos Korfiatis
                           TERMINATED_PENDING,
1368 f557d10a Giorgos Korfiatis
                           ]
1369 f557d10a Giorgos Korfiatis
1370 f557d10a Giorgos Korfiatis
    RELEVANT_STATES = [PENDING,
1371 f557d10a Giorgos Korfiatis
                       DENIED,
1372 f557d10a Giorgos Korfiatis
                       APPROVED,
1373 f557d10a Giorgos Korfiatis
                       APPROVED_PENDING,
1374 f557d10a Giorgos Korfiatis
                       SUSPENDED,
1375 f557d10a Giorgos Korfiatis
                       SUSPENDED_PENDING,
1376 f557d10a Giorgos Korfiatis
                       TERMINATED_PENDING,
1377 f557d10a Giorgos Korfiatis
                       ]
1378 f557d10a Giorgos Korfiatis
1379 2529745f Giorgos Korfiatis
    SKIP_STATES = [DISMISSED,
1380 2529745f Giorgos Korfiatis
                   CANCELLED,
1381 2529745f Giorgos Korfiatis
                   TERMINATED]
1382 2529745f Giorgos Korfiatis
1383 2529745f Giorgos Korfiatis
    STATE_DISPLAY = {
1384 5d209685 Giorgos Korfiatis
        PENDING            : _("Pending"),
1385 2529745f Giorgos Korfiatis
        DENIED             : _("Denied"),
1386 2529745f Giorgos Korfiatis
        DISMISSED          : _("Dismissed"),
1387 2529745f Giorgos Korfiatis
        CANCELLED          : _("Cancelled"),
1388 2529745f Giorgos Korfiatis
        APPROVED           : _("Active"),
1389 2529745f Giorgos Korfiatis
        APPROVED_PENDING   : _("Active - Pending"),
1390 2529745f Giorgos Korfiatis
        SUSPENDED          : _("Suspended"),
1391 2529745f Giorgos Korfiatis
        SUSPENDED_PENDING  : _("Suspended - Pending"),
1392 2529745f Giorgos Korfiatis
        TERMINATED         : _("Terminated"),
1393 2529745f Giorgos Korfiatis
        TERMINATED_PENDING : _("Terminated - Pending"),
1394 2529745f Giorgos Korfiatis
        }
1395 2529745f Giorgos Korfiatis
1396 2529745f Giorgos Korfiatis
1397 2529745f Giorgos Korfiatis
    @classmethod
1398 2529745f Giorgos Korfiatis
    def _chain_state(cls, project_state, app_state):
1399 2529745f Giorgos Korfiatis
        s = CHAIN_STATE.get((project_state, app_state), None)
1400 2529745f Giorgos Korfiatis
        if s is None:
1401 2529745f Giorgos Korfiatis
            raise AssertionError('inconsistent chain state')
1402 2529745f Giorgos Korfiatis
        return s
1403 2529745f Giorgos Korfiatis
1404 2529745f Giorgos Korfiatis
    @classmethod
1405 2529745f Giorgos Korfiatis
    def chain_state(cls, project, app):
1406 2529745f Giorgos Korfiatis
        p_state = project.state if project else None
1407 2529745f Giorgos Korfiatis
        return cls._chain_state(p_state, app.state)
1408 2529745f Giorgos Korfiatis
1409 2529745f Giorgos Korfiatis
    @classmethod
1410 2529745f Giorgos Korfiatis
    def state_display(cls, s):
1411 2529745f Giorgos Korfiatis
        if s is None:
1412 2529745f Giorgos Korfiatis
            return _("Unknown")
1413 2529745f Giorgos Korfiatis
        return cls.STATE_DISPLAY.get(s, _("Inconsistent"))
1414 2529745f Giorgos Korfiatis
1415 2529745f Giorgos Korfiatis
    def last_application(self):
1416 2529745f Giorgos Korfiatis
        return self.chained_apps.order_by('-id')[0]
1417 2529745f Giorgos Korfiatis
1418 2529745f Giorgos Korfiatis
    def get_project(self):
1419 2529745f Giorgos Korfiatis
        try:
1420 2529745f Giorgos Korfiatis
            return self.chained_project
1421 2529745f Giorgos Korfiatis
        except Project.DoesNotExist:
1422 2529745f Giorgos Korfiatis
            return None
1423 2529745f Giorgos Korfiatis
1424 2529745f Giorgos Korfiatis
    def get_elements(self):
1425 2529745f Giorgos Korfiatis
        project = self.get_project()
1426 2529745f Giorgos Korfiatis
        app = self.last_application()
1427 2529745f Giorgos Korfiatis
        return project, app
1428 2529745f Giorgos Korfiatis
1429 2529745f Giorgos Korfiatis
    def full_state(self):
1430 2529745f Giorgos Korfiatis
        project, app = self.get_elements()
1431 2529745f Giorgos Korfiatis
        s = self.chain_state(project, app)
1432 2529745f Giorgos Korfiatis
        return s, project, app
1433 2529745f Giorgos Korfiatis
1434 033f2822 Giorgos Korfiatis
def new_chain():
1435 033f2822 Giorgos Korfiatis
    c = Chain.objects.create()
1436 033f2822 Giorgos Korfiatis
    return c
1437 033f2822 Giorgos Korfiatis
1438 033f2822 Giorgos Korfiatis
1439 6dcf53eb Kostas Papadimitriou
class ProjectApplicationManager(ForUpdateManager):
1440 5550bcfb Kostas Papadimitriou
1441 05617ab9 Kostas Papadimitriou
    def user_visible_projects(self, *filters, **kw_filters):
1442 689226c3 Giorgos Korfiatis
        model = self.model
1443 689226c3 Giorgos Korfiatis
        return self.filter(model.Q_PENDING | model.Q_APPROVED)
1444 05617ab9 Kostas Papadimitriou
1445 7184f408 Giorgos Korfiatis
    def user_visible_by_chain(self, flt):
1446 689226c3 Giorgos Korfiatis
        model = self.model
1447 3e3743f2 Giorgos Korfiatis
        pending = self.filter(model.Q_PENDING | model.Q_DENIED).values_list('chain')
1448 689226c3 Giorgos Korfiatis
        approved = self.filter(model.Q_APPROVED).values_list('chain')
1449 a3530159 Georgios D. Tsoukalas
        by_chain = dict(pending.annotate(models.Max('id')))
1450 a3530159 Georgios D. Tsoukalas
        by_chain.update(approved.annotate(models.Max('id')))
1451 7184f408 Giorgos Korfiatis
        return self.filter(flt, id__in=by_chain.values())
1452 05617ab9 Kostas Papadimitriou
1453 05617ab9 Kostas Papadimitriou
    def user_accessible_projects(self, user):
1454 5550bcfb Kostas Papadimitriou
        """
1455 5550bcfb Kostas Papadimitriou
        Return projects accessed by specified user.
1456 5550bcfb Kostas Papadimitriou
        """
1457 8e1a5af5 Georgios D. Tsoukalas
        if user.is_project_admin():
1458 8e1a5af5 Georgios D. Tsoukalas
            participates_filters = Q()
1459 8e1a5af5 Georgios D. Tsoukalas
        else:
1460 8e1a5af5 Georgios D. Tsoukalas
            participates_filters = Q(owner=user) | Q(applicant=user) | \
1461 8e1a5af5 Georgios D. Tsoukalas
                                   Q(project__projectmembership__person=user)
1462 05617ab9 Kostas Papadimitriou
1463 a3530159 Georgios D. Tsoukalas
        return self.user_visible_by_chain(participates_filters).order_by('issue_date').distinct()
1464 5550bcfb Kostas Papadimitriou
1465 a5cef8d0 Kostas Papadimitriou
    def search_by_name(self, *search_strings):
1466 a5cef8d0 Kostas Papadimitriou
        q = Q()
1467 a5cef8d0 Kostas Papadimitriou
        for s in search_strings:
1468 a5cef8d0 Kostas Papadimitriou
            q = q | Q(name__icontains=s)
1469 a5cef8d0 Kostas Papadimitriou
        return self.filter(q)
1470 a5cef8d0 Kostas Papadimitriou
1471 ff67242a Giorgos Korfiatis
    def latest_of_chain(self, chain_id):
1472 ff67242a Giorgos Korfiatis
        try:
1473 ff67242a Giorgos Korfiatis
            return self.filter(chain=chain_id).order_by('-id')[0]
1474 ff67242a Giorgos Korfiatis
        except IndexError:
1475 ff67242a Giorgos Korfiatis
            return None
1476 a5cef8d0 Kostas Papadimitriou
1477 a9ba418f Giorgos Korfiatis
1478 8aed306c Giorgos Korfiatis
class ProjectApplication(models.Model):
1479 425e2e95 Sofia Papagiannaki
    applicant               =   models.ForeignKey(
1480 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1481 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_applied',
1482 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1483 d6fdc91e Georgios D. Tsoukalas
1484 d0e78bbe Giorgos Korfiatis
    PENDING     =    0
1485 d0e78bbe Giorgos Korfiatis
    APPROVED    =    1
1486 d0e78bbe Giorgos Korfiatis
    REPLACED    =    2
1487 d0e78bbe Giorgos Korfiatis
    DENIED      =    3
1488 3c638f72 Giorgos Korfiatis
    DISMISSED   =    4
1489 3c638f72 Giorgos Korfiatis
    CANCELLED   =    5
1490 d0e78bbe Giorgos Korfiatis
1491 69ab4df9 Giorgos Korfiatis
    state                   =   models.IntegerField(default=PENDING,
1492 69ab4df9 Giorgos Korfiatis
                                                    db_index=True)
1493 d6fdc91e Georgios D. Tsoukalas
1494 425e2e95 Sofia Papagiannaki
    owner                   =   models.ForeignKey(
1495 425e2e95 Sofia Papagiannaki
                                    AstakosUser,
1496 d6fdc91e Georgios D. Tsoukalas
                                    related_name='projects_owned',
1497 d6fdc91e Georgios D. Tsoukalas
                                    db_index=True)
1498 d6fdc91e Georgios D. Tsoukalas
1499 5195c0e9 Giorgos Korfiatis
    chain                   =   models.ForeignKey(Chain,
1500 5195c0e9 Giorgos Korfiatis
                                                  related_name='chained_apps',
1501 5195c0e9 Giorgos Korfiatis
                                                  db_column='chain')
1502 3c638f72 Giorgos Korfiatis
    precursor_application   =   models.ForeignKey('ProjectApplication',
1503 3c638f72 Giorgos Korfiatis
                                                  null=True,
1504 3c638f72 Giorgos Korfiatis
                                                  blank=True)
1505 425e2e95 Sofia Papagiannaki
1506 67980f56 Georgios D. Tsoukalas
    name                    =   models.CharField(max_length=80)
1507 94e49e22 Kostas Papadimitriou
    homepage                =   models.URLField(max_length=255, null=True,
1508 94e49e22 Kostas Papadimitriou
                                                verify_exists=False)
1509 67980f56 Georgios D. Tsoukalas
    description             =   models.TextField(null=True, blank=True)
1510 e729f165 Kostas Papadimitriou
    start_date              =   models.DateTimeField(null=True, blank=True)
1511 67980f56 Georgios D. Tsoukalas
    end_date                =   models.DateTimeField()
1512 272cf735 Sofia Papagiannaki
    member_join_policy      =   models.IntegerField()
1513 272cf735 Sofia Papagiannaki
    member_leave_policy     =   models.IntegerField()
1514 67980f56 Georgios D. Tsoukalas
    limit_on_members_number =   models.PositiveIntegerField(null=True)
1515 425e2e95 Sofia Papagiannaki
    resource_grants         =   models.ManyToManyField(
1516 425e2e95 Sofia Papagiannaki
                                    Resource,
1517 425e2e95 Sofia Papagiannaki
                                    null=True,
1518 425e2e95 Sofia Papagiannaki
                                    blank=True,
1519 d6fdc91e Georgios D. Tsoukalas
                                    through='ProjectResourceGrant')
1520 425e2e95 Sofia Papagiannaki
    comments                =   models.TextField(null=True, blank=True)
1521 3c638f72 Giorgos Korfiatis
    issue_date              =   models.DateTimeField(auto_now_add=True)
1522 3c638f72 Giorgos Korfiatis
    response_date           =   models.DateTimeField(null=True, blank=True)
1523 6dcf53eb Kostas Papadimitriou
1524 5550bcfb Kostas Papadimitriou
    objects                 =   ProjectApplicationManager()
1525 7729e9cc Giorgos Korfiatis
1526 689226c3 Giorgos Korfiatis
    # Compiled queries
1527 689226c3 Giorgos Korfiatis
    Q_PENDING  = Q(state=PENDING)
1528 689226c3 Giorgos Korfiatis
    Q_APPROVED = Q(state=APPROVED)
1529 3e3743f2 Giorgos Korfiatis
    Q_DENIED   = Q(state=DENIED)
1530 689226c3 Giorgos Korfiatis
1531 c4892cd2 Sofia Papagiannaki
    class Meta:
1532 c4892cd2 Sofia Papagiannaki
        unique_together = ("chain", "id")
1533 c4892cd2 Sofia Papagiannaki
1534 f3a45fc6 Kostas Papadimitriou
    def __unicode__(self):
1535 f3a45fc6 Kostas Papadimitriou
        return "%s applied by %s" % (self.name, self.applicant)
1536 f3a45fc6 Kostas Papadimitriou
1537 d0e78bbe Giorgos Korfiatis
    # TODO: Move to a more suitable place
1538 9307cd46 Giorgos Korfiatis
    APPLICATION_STATE_DISPLAY = {
1539 3c638f72 Giorgos Korfiatis
        PENDING  : _('Pending review'),
1540 d77b32f2 Giorgos Korfiatis
        APPROVED : _('Approved'),
1541 3c638f72 Giorgos Korfiatis
        REPLACED : _('Replaced'),
1542 3c638f72 Giorgos Korfiatis
        DENIED   : _('Denied'),
1543 3c638f72 Giorgos Korfiatis
        DISMISSED: _('Dismissed'),
1544 3c638f72 Giorgos Korfiatis
        CANCELLED: _('Cancelled')
1545 bd9af366 Kostas Papadimitriou
    }
1546 d0e78bbe Giorgos Korfiatis
1547 a3530159 Georgios D. Tsoukalas
    def get_project(self):
1548 a3530159 Georgios D. Tsoukalas
        try:
1549 a3530159 Georgios D. Tsoukalas
            project = Project.objects.get(id=self.chain, state=Project.APPROVED)
1550 a3530159 Georgios D. Tsoukalas
            return Project
1551 a3530159 Georgios D. Tsoukalas
        except Project.DoesNotExist, e:
1552 a3530159 Georgios D. Tsoukalas
            return None
1553 a3530159 Georgios D. Tsoukalas
1554 db9a498c Kostas Papadimitriou
    def state_display(self):
1555 9307cd46 Giorgos Korfiatis
        return self.APPLICATION_STATE_DISPLAY.get(self.state, _('Unknown'))
1556 db9a498c Kostas Papadimitriou
1557 d4660e00 Giorgos Korfiatis
    def project_state_display(self):
1558 d4660e00 Giorgos Korfiatis
        try:
1559 d4660e00 Giorgos Korfiatis
            project = self.project
1560 d4660e00 Giorgos Korfiatis
            return project.state_display()
1561 d4660e00 Giorgos Korfiatis
        except Project.DoesNotExist:
1562 d4660e00 Giorgos Korfiatis
            return self.state_display()
1563 d4660e00 Giorgos Korfiatis
1564 a7aba804 Sofia Papagiannaki
    def add_resource_policy(self, service, resource, uplimit):
1565 e1a80257 Sofia Papagiannaki
        """Raises ObjectDoesNotExist, IntegrityError"""
1566 a7aba804 Sofia Papagiannaki
        q = self.projectresourcegrant_set
1567 e1a80257 Sofia Papagiannaki
        resource = Resource.objects.get(service__name=service, name=resource)
1568 a7aba804 Sofia Papagiannaki
        q.create(resource=resource, member_capacity=uplimit)
1569 e1a80257 Sofia Papagiannaki
1570 5550bcfb Kostas Papadimitriou
    def members_count(self):
1571 5550bcfb Kostas Papadimitriou
        return self.project.approved_memberships.count()
1572 5550bcfb Kostas Papadimitriou
1573 669cfe19 Olga Brani
    @property
1574 669cfe19 Olga Brani
    def grants(self):
1575 3d6dade7 Sofia Papagiannaki
        return self.projectresourcegrant_set.values(
1576 3d6dade7 Sofia Papagiannaki
            'member_capacity', 'resource__name', 'resource__service__name')
1577 5550bcfb Kostas Papadimitriou
1578 e1a80257 Sofia Papagiannaki
    @property
1579 e1a80257 Sofia Papagiannaki
    def resource_policies(self):
1580 b98e1df0 Sofia Papagiannaki
        return [str(rp) for rp in self.projectresourcegrant_set.all()]
1581 e1a80257 Sofia Papagiannaki
1582 e1a80257 Sofia Papagiannaki
    @resource_policies.setter
1583 e1a80257 Sofia Papagiannaki
    def resource_policies(self, policies):
1584 e1a80257 Sofia Papagiannaki
        for p in policies:
1585 e1a80257 Sofia Papagiannaki
            service = p.get('service', None)
1586 e1a80257 Sofia Papagiannaki
            resource = p.get('resource', None)
1587 e1a80257 Sofia Papagiannaki
            uplimit = p.get('uplimit', 0)
1588 a7aba804 Sofia Papagiannaki
            self.add_resource_policy(service, resource, uplimit)
1589 425e2e95 Sofia Papagiannaki
1590 a75dbd7b Giorgos Korfiatis
    def pending_modifications_incl_me(self):
1591 3e3743f2 Giorgos Korfiatis
        q = self.chained_applications()
1592 a75dbd7b Giorgos Korfiatis
        q = q.filter(Q(state=self.PENDING))
1593 3e3743f2 Giorgos Korfiatis
        return q
1594 ece3b66e Giorgos Korfiatis
1595 a75dbd7b Giorgos Korfiatis
    def last_pending_incl_me(self):
1596 a75dbd7b Giorgos Korfiatis
        try:
1597 a75dbd7b Giorgos Korfiatis
            return self.pending_modifications_incl_me().order_by('-id')[0]
1598 a75dbd7b Giorgos Korfiatis
        except IndexError:
1599 a75dbd7b Giorgos Korfiatis
            return None
1600 a75dbd7b Giorgos Korfiatis
1601 a75dbd7b Giorgos Korfiatis
    def pending_modifications(self):
1602 a75dbd7b Giorgos Korfiatis
        return self.pending_modifications_incl_me().filter(~Q(id=self.id))
1603 a75dbd7b Giorgos Korfiatis
1604 3e3743f2 Giorgos Korfiatis
    def last_pending(self):
1605 9b32e2fb Kostas Papadimitriou
        try:
1606 3e3743f2 Giorgos Korfiatis
            return self.pending_modifications().order_by('-id')[0]
1607 9b32e2fb Kostas Papadimitriou
        except IndexError:
1608 05617ab9 Kostas Papadimitriou
            return None
1609 05617ab9 Kostas Papadimitriou
1610 efc58b65 Kostas Papadimitriou
    def is_modification(self):
1611 d4660e00 Giorgos Korfiatis
        # if self.state != self.PENDING:
1612 d4660e00 Giorgos Korfiatis
        #     return False
1613 efc58b65 Kostas Papadimitriou
        parents = self.chained_applications().filter(id__lt=self.id)
1614 efc58b65 Kostas Papadimitriou
        parents = parents.filter(state__in=[self.APPROVED])
1615 efc58b65 Kostas Papadimitriou
        return parents.count() > 0
1616 efc58b65 Kostas Papadimitriou
1617 efc58b65 Kostas Papadimitriou
    def chained_applications(self):
1618 efc58b65 Kostas Papadimitriou
        return ProjectApplication.objects.filter(chain=self.chain)
1619 efc58b65 Kostas Papadimitriou
1620 2529745f Giorgos Korfiatis
    def is_latest(self):
1621 2529745f Giorgos Korfiatis
        return self.chained_applications().order_by('-id')[0] == self
1622 2529745f Giorgos Korfiatis
1623 05617ab9 Kostas Papadimitriou
    def has_pending_modifications(self):
1624 3e3743f2 Giorgos Korfiatis
        return bool(self.last_pending())
1625 05617ab9 Kostas Papadimitriou
1626 022cc8e2 Giorgos Korfiatis
    def denied_modifications(self):
1627 022cc8e2 Giorgos Korfiatis
        q = self.chained_applications()
1628 022cc8e2 Giorgos Korfiatis
        q = q.filter(Q(state=self.DENIED))
1629 022cc8e2 Giorgos Korfiatis
        q = q.filter(~Q(id=self.id))
1630 022cc8e2 Giorgos Korfiatis
        return q
1631 022cc8e2 Giorgos Korfiatis
1632 022cc8e2 Giorgos Korfiatis
    def last_denied(self):
1633 022cc8e2 Giorgos Korfiatis
        try:
1634 022cc8e2 Giorgos Korfiatis
            return self.denied_modifications().order_by('-id')[0]
1635 022cc8e2 Giorgos Korfiatis
        except IndexError:
1636 022cc8e2 Giorgos Korfiatis
            return None
1637 022cc8e2 Giorgos Korfiatis
1638 022cc8e2 Giorgos Korfiatis
    def has_denied_modifications(self):
1639 022cc8e2 Giorgos Korfiatis
        return bool(self.last_denied())
1640 022cc8e2 Giorgos Korfiatis
1641 2529745f Giorgos Korfiatis
    def is_applied(self):
1642 2529745f Giorgos Korfiatis
        try:
1643 2529745f Giorgos Korfiatis
            self.project
1644 2529745f Giorgos Korfiatis
            return True
1645 2529745f Giorgos Korfiatis
        except Project.DoesNotExist:
1646 2529745f Giorgos Korfiatis
            return False
1647 2529745f Giorgos Korfiatis
1648 05617ab9 Kostas Papadimitriou
    def get_project(self):
1649 05617ab9 Kostas Papadimitriou
        try:
1650 05617ab9 Kostas Papadimitriou
            return Project.objects.get(id=self.chain)
1651 05617ab9 Kostas Papadimitriou
        except Project.DoesNotExist:
1652 9b32e2fb Kostas Papadimitriou
            return None
1653 4f22664f Georgios D. Tsoukalas
1654 d74111be Giorgos Korfiatis
    def project_exists(self):
1655 d74111be Giorgos Korfiatis
        return self.get_project() is not None
1656 d74111be Giorgos Korfiatis
1657 b6fe8bb8 Giorgos Korfiatis
    def _get_project_for_update(self):
1658 a9ba418f Giorgos Korfiatis
        try:
1659 ea1e5d9f Giorgos Korfiatis
            objects = Project.objects
1660 ea1e5d9f Giorgos Korfiatis
            project = objects.get_for_update(id=self.chain)
1661 a9ba418f Giorgos Korfiatis
            return project
1662 a9ba418f Giorgos Korfiatis
        except Project.DoesNotExist:
1663 a9ba418f Giorgos Korfiatis
            return None
1664 4f22664f Georgios D. Tsoukalas
1665 01bdbd17 Giorgos Korfiatis
    def can_cancel(self):
1666 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1667 01bdbd17 Giorgos Korfiatis
1668 3c638f72 Giorgos Korfiatis
    def cancel(self):
1669 01bdbd17 Giorgos Korfiatis
        if not self.can_cancel():
1670 3c638f72 Giorgos Korfiatis
            m = _("cannot cancel: application '%s' in state '%s'") % (
1671 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1672 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1673 3c638f72 Giorgos Korfiatis
1674 3c638f72 Giorgos Korfiatis
        self.state = self.CANCELLED
1675 3c638f72 Giorgos Korfiatis
        self.save()
1676 3c638f72 Giorgos Korfiatis
1677 01bdbd17 Giorgos Korfiatis
    def can_dismiss(self):
1678 01bdbd17 Giorgos Korfiatis
        return self.state == self.DENIED
1679 01bdbd17 Giorgos Korfiatis
1680 3c638f72 Giorgos Korfiatis
    def dismiss(self):
1681 01bdbd17 Giorgos Korfiatis
        if not self.can_dismiss():
1682 3c638f72 Giorgos Korfiatis
            m = _("cannot dismiss: application '%s' in state '%s'") % (
1683 3c638f72 Giorgos Korfiatis
                    self.id, self.state)
1684 3c638f72 Giorgos Korfiatis
            raise AssertionError(m)
1685 3c638f72 Giorgos Korfiatis
1686 3c638f72 Giorgos Korfiatis
        self.state = self.DISMISSED
1687 3c638f72 Giorgos Korfiatis
        self.save()
1688 3c638f72 Giorgos Korfiatis
1689 01bdbd17 Giorgos Korfiatis
    def can_deny(self):
1690 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1691 01bdbd17 Giorgos Korfiatis
1692 19eb3ee6 Giorgos Korfiatis
    def deny(self):
1693 01bdbd17 Giorgos Korfiatis
        if not self.can_deny():
1694 19eb3ee6 Giorgos Korfiatis
            m = _("cannot deny: application '%s' in state '%s'") % (
1695 19eb3ee6 Giorgos Korfiatis
                    self.id, self.state)
1696 19eb3ee6 Giorgos Korfiatis
            raise AssertionError(m)
1697 19eb3ee6 Giorgos Korfiatis
1698 19eb3ee6 Giorgos Korfiatis
        self.state = self.DENIED
1699 3c638f72 Giorgos Korfiatis
        self.response_date = datetime.now()
1700 19eb3ee6 Giorgos Korfiatis
        self.save()
1701 19eb3ee6 Giorgos Korfiatis
1702 01bdbd17 Giorgos Korfiatis
    def can_approve(self):
1703 01bdbd17 Giorgos Korfiatis
        return self.state == self.PENDING
1704 01bdbd17 Giorgos Korfiatis
1705 ccab6eb5 Sofia Papagiannaki
    def approve(self, approval_user=None):
1706 ccab6eb5 Sofia Papagiannaki
        """
1707 ccab6eb5 Sofia Papagiannaki
        If approval_user then during owner membership acceptance
1708 ccab6eb5 Sofia Papagiannaki
        it is checked whether the request_user is eligible.
1709 262e04c6 Giorgos Korfiatis

1710 2553efae Sofia Papagiannaki
        Raises:
1711 b8f05f8d Sofia Papagiannaki
            PermissionDenied
1712 ccab6eb5 Sofia Papagiannaki
        """
1713 4f22664f Georgios D. Tsoukalas
1714 4f22664f Georgios D. Tsoukalas
        if not transaction.is_managed():
1715 4f22664f Georgios D. Tsoukalas
            raise AssertionError("NOPE")
1716 4f22664f Georgios D. Tsoukalas
1717 73fbaec4 Sofia Papagiannaki
        new_project_name = self.name
1718 01bdbd17 Giorgos Korfiatis
        if not self.can_approve():
1719 65360c65 Georgios D. Tsoukalas
            m = _("cannot approve: project '%s' in state '%s'") % (
1720 65360c65 Georgios D. Tsoukalas
                    new_project_name, self.state)
1721 01bdbd17 Giorgos Korfiatis
            raise AssertionError(m) # invalid argument
1722 262e04c6 Giorgos Korfiatis
1723 fdafae27 Giorgos Korfiatis
        now = datetime.now()
1724 b6fe8bb8 Giorgos Korfiatis
        project = self._get_project_for_update()
1725 3cc9637a Giorgos Korfiatis
1726 99463445 Giorgos Korfiatis
        try:
1727 99463445 Giorgos Korfiatis
            q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
1728 99463445 Giorgos Korfiatis
            conflicting_project = Project.objects.get(q)
1729 99463445 Giorgos Korfiatis
            if (conflicting_project != project):
1730 3cc9637a Giorgos Korfiatis
                m = (_("cannot approve: project with name '%s' "
1731 e1017df9 Giorgos Korfiatis
                       "already exists (id: %s)") % (
1732 3cc9637a Giorgos Korfiatis
                        new_project_name, conflicting_project.id))
1733 3cc9637a Giorgos Korfiatis
                raise PermissionDenied(m) # invalid argument
1734 99463445 Giorgos Korfiatis
        except Project.DoesNotExist:
1735 99463445 Giorgos Korfiatis
            pass
1736 3cc9637a Giorgos Korfiatis
1737 4bf02ea5 Giorgos Korfiatis
        new_project = False
1738 4f22664f Georgios D. Tsoukalas
        if project is None:
1739 4bf02ea5 Giorgos Korfiatis
            new_project = True
1740 3c638f72 Giorgos Korfiatis
            project = Project(id=self.chain)
1741 fdafae27 Giorgos Korfiatis
1742 3cc9637a Giorgos Korfiatis
        project.name = new_project_name
1743 ee45eb81 Giorgos Korfiatis
        project.application = self
1744 4bf02ea5 Giorgos Korfiatis
        project.last_approval_date = now
1745 a769d7ba Sofia Papagiannaki
        if not new_project:
1746 a769d7ba Sofia Papagiannaki
            project.is_modified = True
1747 4bf02ea5 Giorgos Korfiatis
1748 a769d7ba Sofia Papagiannaki
        project.save()
1749 425e2e95 Sofia Papagiannaki
1750 85d444db Sofia Papagiannaki
        self.state = self.APPROVED
1751 3c638f72 Giorgos Korfiatis
        self.response_date = now
1752 bfe23b13 Sofia Papagiannaki
        self.save()
1753 262e04c6 Giorgos Korfiatis
1754 b98e1df0 Sofia Papagiannaki
    @property
1755 b98e1df0 Sofia Papagiannaki
    def member_join_policy_display(self):
1756 b98e1df0 Sofia Papagiannaki
        return PROJECT_MEMBER_JOIN_POLICIES.get(str(self.member_join_policy))
1757 b98e1df0 Sofia Papagiannaki
1758 b98e1df0 Sofia Papagiannaki
    @property
1759 b98e1df0 Sofia Papagiannaki
    def member_leave_policy_display(self):
1760 b98e1df0 Sofia Papagiannaki
        return PROJECT_MEMBER_LEAVE_POLICIES.get(str(self.member_leave_policy))
1761 b98e1df0 Sofia Papagiannaki
1762 73fbaec4 Sofia Papagiannaki
class ProjectResourceGrant(models.Model):
1763 e1a80257 Sofia Papagiannaki
1764 425e2e95 Sofia Papagiannaki
    resource                =   models.ForeignKey(Resource)
1765 425e2e95 Sofia Papagiannaki
    project_application     =   models.ForeignKey(ProjectApplication,
1766 5200e864 Sofia Papagiannaki
                                                  null=True)
1767 c11dc0ce Giorgos Korfiatis
    project_capacity        =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1768 c11dc0ce Giorgos Korfiatis
    project_import_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1769 c11dc0ce Giorgos Korfiatis
    project_export_limit    =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1770 c11dc0ce Giorgos Korfiatis
    member_capacity         =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1771 c11dc0ce Giorgos Korfiatis
    member_import_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1772 c11dc0ce Giorgos Korfiatis
    member_export_limit     =   intDecimalField(default=QH_PRACTICALLY_INFINITE)
1773 73fbaec4 Sofia Papagiannaki
1774 73fbaec4 Sofia Papagiannaki
    objects = ExtendedManager()
1775 73fbaec4 Sofia Papagiannaki
1776 73fbaec4 Sofia Papagiannaki
    class Meta:
1777 73fbaec4 Sofia Papagiannaki
        unique_together = ("resource", "project_application")
1778 8327782d Sofia Papagiannaki
1779 0514bcc7 Giorgos Korfiatis
    def member_quota_values(self):
1780 0514bcc7 Giorgos Korfiatis
        return QuotaValues(
1781 0514bcc7 Giorgos Korfiatis
            quantity = 0,
1782 0514bcc7 Giorgos Korfiatis
            capacity = self.member_capacity,
1783 0514bcc7 Giorgos Korfiatis
            import_limit = self.member_import_limit,
1784 0514bcc7 Giorgos Korfiatis
            export_limit = self.member_export_limit)
1785 0514bcc7 Giorgos Korfiatis
1786 b98e1df0 Sofia Papagiannaki
    def display_member_capacity(self):
1787 b98e1df0 Sofia Papagiannaki
        if self.member_capacity:
1788 b98e1df0 Sofia Papagiannaki
            if self.resource.unit:
1789 b98e1df0 Sofia Papagiannaki
                return ProjectResourceGrant.display_filesize(
1790 b98e1df0 Sofia Papagiannaki
                    self.member_capacity)
1791 b98e1df0 Sofia Papagiannaki
            else:
1792 b98e1df0 Sofia Papagiannaki
                if math.isinf(self.member_capacity):
1793 b98e1df0 Sofia Papagiannaki
                    return 'Unlimited'
1794 b98e1df0 Sofia Papagiannaki
                else:
1795 b98e1df0 Sofia Papagiannaki
                    return self.member_capacity
1796 b98e1df0 Sofia Papagiannaki
        else:
1797 b98e1df0 Sofia Papagiannaki
            return 'Unlimited'
1798 b98e1df0 Sofia Papagiannaki
1799 b98e1df0 Sofia Papagiannaki
    def __str__(self):
1800 b98e1df0 Sofia Papagiannaki
        return 'Max %s per user: %s' % (self.resource.pluralized_display_name,
1801 b98e1df0 Sofia Papagiannaki
                                        self.display_member_capacity())
1802 b98e1df0 Sofia Papagiannaki
1803 b98e1df0 Sofia Papagiannaki
    @classmethod
1804 b98e1df0 Sofia Papagiannaki
    def display_filesize(cls, value):
1805 b98e1df0 Sofia Papagiannaki
        try:
1806 b98e1df0 Sofia Papagiannaki
            value = float(value)
1807 b98e1df0 Sofia Papagiannaki
        except:
1808 b98e1df0 Sofia Papagiannaki
            return
1809 b98e1df0 Sofia Papagiannaki
        else:
1810 b98e1df0 Sofia Papagiannaki
            if math.isinf(value):
1811 b98e1df0 Sofia Papagiannaki
                return 'Unlimited'
1812 b98e1df0 Sofia Papagiannaki
            if value > 1:
1813 b98e1df0 Sofia Papagiannaki
                unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'],
1814 b98e1df0 Sofia Papagiannaki
                                [0, 0, 0, 0, 0, 0])
1815 b98e1df0 Sofia Papagiannaki
                exponent = min(int(math.log(value, 1024)), len(unit_list) - 1)
1816 b98e1df0 Sofia Papagiannaki
                quotient = float(value) / 1024**exponent
1817 b98e1df0 Sofia Papagiannaki
                unit, value_decimals = unit_list[exponent]
1818 b98e1df0 Sofia Papagiannaki
                format_string = '{0:.%sf} {1}' % (value_decimals)
1819 b98e1df0 Sofia Papagiannaki
                return format_string.format(quotient, unit)
1820 b98e1df0 Sofia Papagiannaki
            if value == 0:
1821 b98e1df0 Sofia Papagiannaki
                return '0 bytes'
1822 b98e1df0 Sofia Papagiannaki
            if value == 1:
1823 b98e1df0 Sofia Papagiannaki
                return '1 byte'
1824 b98e1df0 Sofia Papagiannaki
            else:
1825 b98e1df0 Sofia Papagiannaki
               return '0'
1826 b98e1df0 Sofia Papagiannaki
1827 e546df49 Georgios D. Tsoukalas
1828 123be68a Giorgos Korfiatis
class ProjectManager(ForUpdateManager):
1829 123be68a Giorgos Korfiatis
1830 123be68a Giorgos Korfiatis
    def terminated_projects(self):
1831 689226c3 Giorgos Korfiatis
        q = self.model.Q_TERMINATED
1832 123be68a Giorgos Korfiatis
        return self.filter(q)
1833 123be68a Giorgos Korfiatis
1834 123be68a Giorgos Korfiatis
    def not_terminated_projects(self):
1835 689226c3 Giorgos Korfiatis
        q = ~self.model.Q_TERMINATED
1836 123be68a Giorgos Korfiatis
        return self.filter(q)
1837 123be68a Giorgos Korfiatis
1838 b6fe8bb8 Giorgos Korfiatis
    def terminating_projects(self):
1839 689226c3 Giorgos Korfiatis
        q = self.model.Q_TERMINATED & Q(is_active=True)
1840 b6fe8bb8 Giorgos Korfiatis
        return self.filter(q)
1841 b6fe8bb8 Giorgos Korfiatis
1842 db99f198 Giorgos Korfiatis
    def deactivated_projects(self):
1843 689226c3 Giorgos Korfiatis
        q = self.model.Q_DEACTIVATED
1844 db99f198 Giorgos Korfiatis
        return self.filter(q)
1845 db99f198 Giorgos Korfiatis
1846 db99f198 Giorgos Korfiatis
    def deactivating_projects(self):
1847 689226c3 Giorgos Korfiatis
        q = self.model.Q_DEACTIVATED & Q(is_active=True)
1848 db99f198 Giorgos Korfiatis
        return self.filter(q)
1849 db99f198 Giorgos Korfiatis
1850 b6fe8bb8 Giorgos Korfiatis
    def modified_projects(self):
1851 b6fe8bb8 Giorgos Korfiatis
        return self.filter(is_modified=True)
1852 b6fe8bb8 Giorgos Korfiatis
1853 db99f198 Giorgos Korfiatis
    def reactivating_projects(self):
1854 db99f198 Giorgos Korfiatis
        return self.filter(state=Project.APPROVED, is_active=False)
1855 b6fe8bb8 Giorgos Korfiatis
1856 7eadc230 Giorgos Korfiatis
    def expired_projects(self):
1857 7eadc230 Giorgos Korfiatis
        q = (~Q(state=Project.TERMINATED) &
1858 7eadc230 Giorgos Korfiatis
              Q(application__end_date__lt=datetime.now()))
1859 7eadc230 Giorgos Korfiatis
        return self.filter(q)
1860 7eadc230 Giorgos Korfiatis
1861 d77b32f2 Giorgos Korfiatis
    def search_by_name(self, *search_strings):
1862 d77b32f2 Giorgos Korfiatis
        q = Q()
1863 d77b32f2 Giorgos Korfiatis
        for s in search_strings:
1864 d77b32f2 Giorgos Korfiatis
            q = q | Q(name__icontains=s)
1865 d77b32f2 Giorgos Korfiatis
        return self.filter(q)
1866 d77b32f2 Giorgos Korfiatis
1867 7eadc230 Giorgos Korfiatis
1868 d6fdc91e Georgios D. Tsoukalas
class Project(models.Model):
1869 e546df49 Georgios D. Tsoukalas
1870 5195c0e9 Giorgos Korfiatis
    id                          =   models.OneToOneField(Chain,
1871 5195c0e9 Giorgos Korfiatis
                                                      related_name='chained_project',
1872 5195c0e9 Giorgos Korfiatis
                                                      db_column='id',
1873 5195c0e9 Giorgos Korfiatis
                                                      primary_key=True)
1874 5195c0e9 Giorgos Korfiatis
1875 ee45eb81 Giorgos Korfiatis
    application                 =   models.OneToOneField(
1876 4f22664f Georgios D. Tsoukalas
                                            ProjectApplication,
1877 782d9118 Giorgos Korfiatis
                                            related_name='project')
1878 4f22664f Georgios D. Tsoukalas
    last_approval_date          =   models.DateTimeField(null=True)
1879 4f22664f Georgios D. Tsoukalas
1880 4f22664f Georgios D. Tsoukalas
    members                     =   models.ManyToManyField(
1881 4f22664f Georgios D. Tsoukalas
                                            AstakosUser,
1882 4f22664f Georgios D. Tsoukalas
                                            through='ProjectMembership')
1883 4f22664f Georgios D. Tsoukalas
1884 5b9e9530 Giorgos Korfiatis
    deactivation_reason         =   models.CharField(max_length=255, null=True)
1885 5b9e9530 Giorgos Korfiatis
    deactivation_date           =   models.DateTimeField(null=True)
1886 4f22664f Georgios D. Tsoukalas
1887 3c638f72 Giorgos Korfiatis
    creation_date               =   models.DateTimeField(auto_now_add=True)
1888 4f22664f Georgios D. Tsoukalas
    name                        =   models.CharField(
1889 4f22664f Georgios D. Tsoukalas
                                            max_length=80,
1890 e1017df9 Giorgos Korfiatis
                                            null=True,
1891 4f22664f Georgios D. Tsoukalas
                                            db_index=True,
1892 4f22664f Georgios D. Tsoukalas
                                            unique=True)
1893 425e2e95 Sofia Papagiannaki
1894 b6fe8bb8 Giorgos Korfiatis
    APPROVED    = 1
1895 b6fe8bb8 Giorgos Korfiatis
    SUSPENDED   = 10
1896 b6fe8bb8 Giorgos Korfiatis
    TERMINATED  = 100
1897 5b9e9530 Giorgos Korfiatis
1898 b6fe8bb8 Giorgos Korfiatis
    is_modified                 =   models.BooleanField(default=False,
1899 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1900 b6fe8bb8 Giorgos Korfiatis
    is_active                   =   models.BooleanField(default=True,
1901 b6fe8bb8 Giorgos Korfiatis
                                                        db_index=True)
1902 b6fe8bb8 Giorgos Korfiatis
    state                       =   models.IntegerField(default=APPROVED,
1903 123be68a Giorgos Korfiatis
                                                        db_index=True)
1904 123be68a Giorgos Korfiatis
1905 123be68a Giorgos Korfiatis
    objects     =   ProjectManager()
1906 7729e9cc Giorgos Korfiatis
1907 689226c3 Giorgos Korfiatis
    # Compiled queries
1908 689226c3 Giorgos Korfiatis
    Q_TERMINATED  = Q(state=TERMINATED)
1909 689226c3 Giorgos Korfiatis
    Q_SUSPENDED   = Q(state=SUSPENDED)
1910 689226c3 Giorgos Korfiatis
    Q_DEACTIVATED = Q_TERMINATED | Q_SUSPENDED
1911 689226c3 Giorgos Korfiatis
1912 8c7b8bb8 Giorgos Korfiatis
    def __str__(self):
1913 b6eaca30 Giorgos Korfiatis
        return uenc(_("<project %s '%s'>") %
1914 b6eaca30 Giorgos Korfiatis
                    (self.id, udec(self.application.name)))
1915 8c7b8bb8 Giorgos Korfiatis
1916 8c7b8bb8 Giorgos Korfiatis
    __repr__ = __str__
1917 8c7b8bb8 Giorgos Korfiatis
1918 b6eaca30 Giorgos Korfiatis
    def __unicode__(self):
1919 b6eaca30 Giorgos Korfiatis
        return _("<project %s '%s'>") % (self.id, self.application.name)
1920 b6eaca30 Giorgos Korfiatis
1921 e1f31e63 Giorgos Korfiatis
    STATE_DISPLAY = {
1922 d77b32f2 Giorgos Korfiatis
        APPROVED   : 'Active',
1923 d77b32f2 Giorgos Korfiatis
        SUSPENDED  : 'Suspended',
1924 d77b32f2 Giorgos Korfiatis
        TERMINATED : 'Terminated'
1925 e1f31e63 Giorgos Korfiatis
        }
1926 e1f31e63 Giorgos Korfiatis
1927 e1f31e63 Giorgos Korfiatis
    def state_display(self):
1928 e1f31e63 Giorgos Korfiatis
        return self.STATE_DISPLAY.get(self.state, _('Unknown'))
1929 e1f31e63 Giorgos Korfiatis
1930 2a2c6876 Giorgos Korfiatis
    def admin_state_display(self):
1931 2a2c6876 Giorgos Korfiatis
        s = self.state_display()
1932 2a2c6876 Giorgos Korfiatis
        if self.sync_pending():
1933 2a2c6876 Giorgos Korfiatis
            s += ' (sync pending)'
1934 2a2c6876 Giorgos Korfiatis
        return s
1935 2a2c6876 Giorgos Korfiatis
1936 2a2c6876 Giorgos Korfiatis
    def sync_pending(self):
1937 2a2c6876 Giorgos Korfiatis
        if self.state != self.APPROVED:
1938 2a2c6876 Giorgos Korfiatis
            return self.is_active
1939 2a2c6876 Giorgos Korfiatis
        return not self.is_active or self.is_modified
1940 2a2c6876 Giorgos Korfiatis
1941 7eadc230 Giorgos Korfiatis
    def expiration_info(self):
1942 7eadc230 Giorgos Korfiatis
        return (str(self.id), self.name, self.state_display(),
1943 7eadc230 Giorgos Korfiatis
                str(self.application.end_date))
1944 7eadc230 Giorgos Korfiatis
1945 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated(self, reason=None):
1946 b6fe8bb8 Giorgos Korfiatis
        if reason is not None:
1947 b6fe8bb8 Giorgos Korfiatis
            return self.state == reason
1948 425e2e95 Sofia Papagiannaki
1949 b6fe8bb8 Giorgos Korfiatis
        return self.state != self.APPROVED
1950 123be68a Giorgos Korfiatis
1951 123be68a Giorgos Korfiatis
    def is_deactivating(self, reason=None):
1952 b6fe8bb8 Giorgos Korfiatis
        if not self.is_active:
1953 b6fe8bb8 Giorgos Korfiatis
            return False
1954 123be68a Giorgos Korfiatis
1955 b6fe8bb8 Giorgos Korfiatis
        return self.is_deactivated(reason)
1956 123be68a Giorgos Korfiatis
1957 b6fe8bb8 Giorgos Korfiatis
    def is_deactivated_strict(self, reason=None):
1958 b6fe8bb8 Giorgos Korfiatis
        if self.is_active:
1959 b6fe8bb8 Giorgos Korfiatis
            return False
1960 123be68a Giorgos Korfiatis
1961 b6fe8bb8 Giorgos Korfiatis
        return self.is_deactivated(reason)
1962 425e2e95 Sofia Papagiannaki
1963 123be68a Giorgos Korfiatis
    ### Deactivation calls
1964 425e2e95 Sofia Papagiannaki
1965 123be68a Giorgos Korfiatis
    def deactivate(self):
1966 b6fe8bb8 Giorgos Korfiatis
        self.deactivation_date = datetime.now()
1967 b6fe8bb8 Giorgos Korfiatis
        self.is_active = False
1968 425e2e95 Sofia Papagiannaki
1969 db99f198 Giorgos Korfiatis
    def reactivate(self):
1970 db99f198 Giorgos Korfiatis
        self.deactivation_date = None
1971 db99f198 Giorgos Korfiatis
        self.is_active = True
1972 db99f198 Giorgos Korfiatis
1973 123be68a Giorgos Korfiatis
    def terminate(self):
1974 123be68a Giorgos Korfiatis
        self.deactivation_reason = 'TERMINATED'
1975 b6fe8bb8 Giorgos Korfiatis
        self.state = self.TERMINATED
1976 e1017df9 Giorgos Korfiatis
        self.name = None
1977 123be68a Giorgos Korfiatis
        self.save()
1978 8aed306c Giorgos Korfiatis
1979 db99f198 Giorgos Korfiatis
    def suspend(self):
1980 db99f198 Giorgos Korfiatis
        self.deactivation_reason = 'SUSPENDED'
1981 db99f198 Giorgos Korfiatis
        self.state = self.SUSPENDED
1982 db99f198 Giorgos Korfiatis
        self.save()
1983 db99f198 Giorgos Korfiatis
1984 db99f198 Giorgos Korfiatis
    def resume(self):
1985 db99f198 Giorgos Korfiatis
        self.deactivation_reason = None
1986 db99f198 Giorgos Korfiatis
        self.state = self.APPROVED
1987 db99f198 Giorgos Korfiatis
        self.save()
1988 123be68a Giorgos Korfiatis
1989 123be68a Giorgos Korfiatis
    ### Logical checks
1990 425e2e95 Sofia Papagiannaki
1991 e1a80257 Sofia Papagiannaki
    def is_inconsistent(self):
1992 e1a80257 Sofia Papagiannaki
        now = datetime.now()
1993 5b9e9530 Giorgos Korfiatis
        dates = [self.creation_date,
1994 5b9e9530 Giorgos Korfiatis
                 self.last_approval_date,
1995 5b9e9530 Giorgos Korfiatis
                 self.deactivation_date]
1996 5b9e9530 Giorgos Korfiatis
        return any([date > now for date in dates])
1997 5b9e9530 Giorgos Korfiatis
1998 b6fe8bb8 Giorgos Korfiatis
    def is_active_strict(self):
1999 b6fe8bb8 Giorgos Korfiatis
        return self.is_active and self.state == self.APPROVED
2000 5b9e9530 Giorgos Korfiatis
2001 db99f198 Giorgos Korfiatis
    def is_approved(self):
2002 db99f198 Giorgos Korfiatis
        return self.state == self.APPROVED
2003 db99f198 Giorgos Korfiatis
2004 123be68a Giorgos Korfiatis
    @property
2005 123be68a Giorgos Korfiatis
    def is_alive(self):
2006 72a6e1e8 Giorgos Korfiatis
        return not self.is_terminated
2007 123be68a Giorgos Korfiatis
2008 123be68a Giorgos Korfiatis
    @property
2009 123be68a Giorgos Korfiatis
    def is_terminated(self):
2010 123be68a Giorgos Korfiatis
        return self.is_deactivated(self.TERMINATED)
2011 123be68a Giorgos Korfiatis
2012 123be68a Giorgos Korfiatis
    @property
2013 123be68a Giorgos Korfiatis
    def is_suspended(self):
2014 db99f198 Giorgos Korfiatis
        return self.is_deactivated(self.SUSPENDED)
2015 5b9e9530 Giorgos Korfiatis
2016 5b9e9530 Giorgos Korfiatis
    def violates_resource_grants(self):
2017 e1a80257 Sofia Papagiannaki
        return False
2018 65360c65 Georgios D. Tsoukalas
2019 5b9e9530 Giorgos Korfiatis
    def violates_members_limit(self, adding=0):
2020 5b9e9530 Giorgos Korfiatis
        application = self.application
2021 943d5554 Giorgos Korfiatis
        limit = application.limit_on_members_number
2022 022c61cd Sofia Papagiannaki
        if limit is None:
2023 022c61cd Sofia Papagiannaki
            return False
2024 022c61cd Sofia Papagiannaki
        return (len(self.approved_members) + adding > limit)
2025 5b9e9530 Giorgos Korfiatis
2026 123be68a Giorgos Korfiatis
2027 123be68a Giorgos Korfiatis
    ### Other
2028 5b9e9530 Giorgos Korfiatis
2029 7db8c163 Georgios D. Tsoukalas
    def count_pending_memberships(self):
2030 7db8c163 Georgios D. Tsoukalas
        memb_set = self.projectmembership_set
2031 7db8c163 Georgios D. Tsoukalas
        memb_count = memb_set.filter(state=ProjectMembership.REQUESTED).count()
2032 7db8c163 Georgios D. Tsoukalas
        return memb_count
2033 7db8c163 Georgios D. Tsoukalas
2034 d77b32f2 Giorgos Korfiatis
    def members_count(self):
2035 d77b32f2 Giorgos Korfiatis
        return self.approved_memberships.count()
2036 d77b32f2 Giorgos Korfiatis
2037 425e2e95 Sofia Papagiannaki
    @property
2038 425e2e95 Sofia Papagiannaki
    def approved_memberships(self):
2039 689226c3 Giorgos Korfiatis
        query = ProjectMembership.Q_ACCEPTED_STATES
2040 5b9e9530 Giorgos Korfiatis
        return self.projectmembership_set.filter(query)
2041 4f22664f Georgios D. Tsoukalas
2042 425e2e95 Sofia Papagiannaki
    @property
2043 425e2e95 Sofia Papagiannaki
    def approved_members(self):
2044 425e2e95 Sofia Papagiannaki
        return [m.person for m in self.approved_memberships]
2045 4f22664f Georgios D. Tsoukalas
2046 4f22664f Georgios D. Tsoukalas
    def add_member(self, user):
2047 bfe23b13 Sofia Papagiannaki
        """
2048 bfe23b13 Sofia Papagiannaki
        Raises:
2049 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
2050 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
2051 bfe23b13 Sofia Papagiannaki
        """
2052 8c7229a8 Giorgos Korfiatis
        if isinstance(user, (int, long)):
2053 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
2054 4f22664f Georgios D. Tsoukalas
2055 ccab6eb5 Sofia Papagiannaki
        m, created = ProjectMembership.objects.get_or_create(
2056 ccab6eb5 Sofia Papagiannaki
            person=user, project=self
2057 2a965273 Sofia Papagiannaki
        )
2058 4f22664f Georgios D. Tsoukalas
        m.accept()
2059 ccab6eb5 Sofia Papagiannaki
2060 4f22664f Georgios D. Tsoukalas
    def remove_member(self, user):
2061 bfe23b13 Sofia Papagiannaki
        """
2062 bfe23b13 Sofia Papagiannaki
        Raises:
2063 bfe23b13 Sofia Papagiannaki
            django.exceptions.PermissionDenied
2064 bfe23b13 Sofia Papagiannaki
            astakos.im.models.AstakosUser.DoesNotExist
2065 bfe23b13 Sofia Papagiannaki
            astakos.im.models.ProjectMembership.DoesNotExist
2066 bfe23b13 Sofia Papagiannaki
        """
2067 8c7229a8 Giorgos Korfiatis
        if isinstance(user, (int, long)):
2068 4f22664f Georgios D. Tsoukalas
            user = AstakosUser.objects.get(user=user)
2069 4f22664f Georgios D. Tsoukalas
2070 bfe23b13 Sofia Papagiannaki
        m = ProjectMembership.objects.get(person=user, project=self)
2071 bfe23b13 Sofia Papagiannaki
        m.remove()
2072 4f22664f Georgios D. Tsoukalas
2073 425e2e95 Sofia Papagiannaki
2074 2529745f Giorgos Korfiatis
CHAIN_STATE = {
2075 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.PENDING)  : Chain.APPROVED_PENDING,
2076 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.APPROVED) : Chain.APPROVED,
2077 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.DENIED)   : Chain.APPROVED,
2078 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.DISMISSED): Chain.APPROVED,
2079 2529745f Giorgos Korfiatis
    (Project.APPROVED,   ProjectApplication.CANCELLED): Chain.APPROVED,
2080 2529745f Giorgos Korfiatis
2081 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.PENDING)  : Chain.SUSPENDED_PENDING,
2082 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.APPROVED) : Chain.SUSPENDED,
2083 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.DENIED)   : Chain.SUSPENDED,
2084 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.DISMISSED): Chain.SUSPENDED,
2085 2529745f Giorgos Korfiatis
    (Project.SUSPENDED,  ProjectApplication.CANCELLED): Chain.SUSPENDED,
2086 2529745f Giorgos Korfiatis
2087 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.PENDING)  : Chain.TERMINATED_PENDING,
2088 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.APPROVED) : Chain.TERMINATED,
2089 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.DENIED)   : Chain.TERMINATED,
2090 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.DISMISSED): Chain.TERMINATED,
2091 2529745f Giorgos Korfiatis
    (Project.TERMINATED, ProjectApplication.CANCELLED): Chain.TERMINATED,
2092 2529745f Giorgos Korfiatis
2093 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.PENDING)  : Chain.PENDING,
2094 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.DENIED)   : Chain.DENIED,
2095 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.DISMISSED): Chain.DISMISSED,
2096 2529745f Giorgos Korfiatis
    (None,               ProjectApplication.CANCELLED): Chain.CANCELLED,
2097 2529745f Giorgos Korfiatis
    }
2098 2529745f Giorgos Korfiatis
2099 2529745f Giorgos Korfiatis
2100 b6fe8bb8 Giorgos Korfiatis
class PendingMembershipError(Exception):
2101 b6fe8bb8 Giorgos Korfiatis
    pass
2102 b22de10a Sofia Papagiannaki
2103 4f22664f Georgios D. Tsoukalas
2104 db99f198 Giorgos Korfiatis
class ProjectMembershipManager(ForUpdateManager):
2105 d77b32f2 Giorgos Korfiatis
2106 d77b32f2 Giorgos Korfiatis
    def any_accepted(self):
2107 d77b32f2 Giorgos Korfiatis
        q = (Q(state=ProjectMembership.ACCEPTED) |
2108 d77b32f2 Giorgos Korfiatis
             Q(state=ProjectMembership.PROJECT_DEACTIVATED))
2109 d77b32f2 Giorgos Korfiatis
        return self.filter(q)
2110 d77b32f2 Giorgos Korfiatis
2111 c1007621 Giorgos Korfiatis
    def actually_accepted(self):
2112 c1007621 Giorgos Korfiatis
        q = self.model.Q_ACTUALLY_ACCEPTED
2113 c1007621 Giorgos Korfiatis
        return self.filter(q)
2114 c1007621 Giorgos Korfiatis
2115 d77b32f2 Giorgos Korfiatis
    def requested(self):
2116 d77b32f2 Giorgos Korfiatis
        return self.filter(state=ProjectMembership.REQUESTED)
2117 d77b32f2 Giorgos Korfiatis
2118 d77b32f2 Giorgos Korfiatis
    def suspended(self):
2119 d77b32f2 Giorgos Korfiatis
        return self.filter(state=ProjectMembership.USER_SUSPENDED)
2120 db99f198 Giorgos Korfiatis
2121 d6fdc91e Georgios D. Tsoukalas
class ProjectMembership(models.Model):
2122 4f22664f Georgios D. Tsoukalas
2123 425e2e95 Sofia Papagiannaki
    person              =   models.ForeignKey(AstakosUser)
2124 3c638f72 Giorgos Korfiatis
    request_date        =   models.DateField(auto_now_add=True)
2125 d6fdc91e Georgios D. Tsoukalas
    project             =   models.ForeignKey(Project)
2126 d6fdc91e Georgios D. Tsoukalas
2127 db99f198 Giorgos Korfiatis
    REQUESTED           =   0
2128 db99f198 Giorgos Korfiatis
    ACCEPTED            =   1
2129 c1007621 Giorgos Korfiatis
    LEAVE_REQUESTED     =   5
2130 db99f198 Giorgos Korfiatis
    # User deactivation
2131 db99f198 Giorgos Korfiatis
    USER_SUSPENDED      =   10
2132 db99f198 Giorgos Korfiatis
    # Project deactivation
2133 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED =   100
2134 b6fe8bb8 Giorgos Korfiatis
2135 db99f198 Giorgos Korfiatis
    REMOVED             =   200
2136 db99f198 Giorgos Korfiatis
2137 db99f198 Giorgos Korfiatis
    ASSOCIATED_STATES   =   set([REQUESTED,
2138 db99f198 Giorgos Korfiatis
                                 ACCEPTED,
2139 c1007621 Giorgos Korfiatis
                                 LEAVE_REQUESTED,
2140 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
2141 db99f198 Giorgos Korfiatis
                                 PROJECT_DEACTIVATED])
2142 db99f198 Giorgos Korfiatis
2143 db99f198 Giorgos Korfiatis
    ACCEPTED_STATES     =   set([ACCEPTED,
2144 c1007621 Giorgos Korfiatis
                                 LEAVE_REQUESTED,
2145 db99f198 Giorgos Korfiatis
                                 USER_SUSPENDED,
2146 db99f198 Giorgos Korfiatis
                                 PROJECT_DEACTIVATED])
2147 05617ab9 Kostas Papadimitriou
2148 c1007621 Giorgos Korfiatis
    ACTUALLY_ACCEPTED   =   set([ACCEPTED, LEAVE_REQUESTED])
2149 c1007621 Giorgos Korfiatis
2150 b6fe8bb8 Giorgos Korfiatis
    state               =   models.IntegerField(default=REQUESTED,
2151 b6fe8bb8 Giorgos Korfiatis
                                                db_index=True)
2152 b6fe8bb8 Giorgos Korfiatis
    is_pending          =   models.BooleanField(default=False, db_index=True)
2153 b6fe8bb8 Giorgos Korfiatis
    is_active           =   models.BooleanField(default=False, db_index=True)
2154 5200e864 Sofia Papagiannaki
    application         =   models.ForeignKey(
2155 5200e864 Sofia Papagiannaki
                                ProjectApplication,
2156 5200e864 Sofia Papagiannaki
                                null=True,
2157 5200e864 Sofia Papagiannaki
                                related_name='memberships')
2158 5200e864 Sofia Papagiannaki
    pending_application =   models.ForeignKey(
2159 5200e864 Sofia Papagiannaki
                                ProjectApplication,
2160 5200e864 Sofia Papagiannaki
                                null=True,
2161 cd633c29 Giorgos Korfiatis
                                related_name='pending_memberships')
2162 d6fdc91e Georgios D. Tsoukalas
    pending_serial      =   models.BigIntegerField(null=True, db_index=True)
2163 425e2e95 Sofia Papagiannaki
2164 425e2e95 Sofia Papagiannaki
    acceptance_date     =   models.DateField(null=True, db_index=True)
2165 425e2e95 Sofia Papagiannaki
    leave_request_date  =   models.DateField(null=True)
2166 2a965273 Sofia Papagiannaki
2167 db99f198 Giorgos Korfiatis
    objects     =   ProjectMembershipManager()
2168 ee45eb81 Giorgos Korfiatis
2169 689226c3 Giorgos Korfiatis
    # Compiled queries
2170 689226c3 Giorgos Korfiatis
    Q_ACCEPTED_STATES = ~Q(state=REQUESTED) & ~Q(state=REMOVED)
2171 c1007621 Giorgos Korfiatis
    Q_ACTUALLY_ACCEPTED = Q(state=ACCEPTED) | Q(state=LEAVE_REQUESTED)
2172 5b9e9530 Giorgos Korfiatis
2173 d77b32f2 Giorgos Korfiatis
    MEMBERSHIP_STATE_DISPLAY = {
2174 d4660e00 Giorgos Korfiatis
        REQUESTED           : _('Requested'),
2175 d4660e00 Giorgos Korfiatis
        ACCEPTED            : _('Accepted'),
2176 c1007621 Giorgos Korfiatis
        LEAVE_REQUESTED     : _('Leave Requested'),
2177 d4660e00 Giorgos Korfiatis
        USER_SUSPENDED      : _('Suspended'),
2178 d4660e00 Giorgos Korfiatis
        PROJECT_DEACTIVATED : _('Accepted'), # sic
2179 d4660e00 Giorgos Korfiatis
        REMOVED             : _('Pending removal'),
2180 d4660e00 Giorgos Korfiatis
        }
2181 d4660e00 Giorgos Korfiatis
2182 d4660e00 Giorgos Korfiatis
    USER_FRIENDLY_STATE_DISPLAY = {
2183 d4660e00 Giorgos Korfiatis
        REQUESTED           : _('Join requested'),
2184 d4660e00 Giorgos Korfiatis
        ACCEPTED            : _('Accepted member'),
2185 c1007621 Giorgos Korfiatis
        LEAVE_REQUESTED     : _('Requested to leave'),
2186 d4660e00 Giorgos Korfiatis
        USER_SUSPENDED      : _('Suspended member'),
2187 d4660e00 Giorgos Korfiatis
        PROJECT_DEACTIVATED : _('Accepted member'), # sic
2188 d4660e00 Giorgos Korfiatis
        REMOVED             : _('Pending removal'),
2189 d77b32f2 Giorgos Korfiatis
        }
2190 d77b32f2 Giorgos Korfiatis
2191 d77b32f2 Giorgos Korfiatis
    def state_display(self):
2192 d77b32f2 Giorgos Korfiatis
        return self.MEMBERSHIP_STATE_DISPLAY.get(self.state, _('Unknown'))
2193 d77b32f2 Giorgos Korfiatis
2194 d4660e00 Giorgos Korfiatis
    def user_friendly_state_display(self):
2195 d4660e00 Giorgos Korfiatis
        return self.USER_FRIENDLY_STATE_DISPLAY.get(self.state, _('Unknown'))
2196 d4660e00 Giorgos Korfiatis
2197 b6fe8bb8 Giorgos Korfiatis
    def get_combined_state(self):
2198 b6fe8bb8 Giorgos Korfiatis
        return self.state, self.is_active, self.is_pending
2199 5b9e9530 Giorgos Korfiatis
2200 0cc22d47 Sofia Papagiannaki
    class Meta:
2201 0cc22d47 Sofia Papagiannaki
        unique_together = ("person", "project")
2202 d6fdc91e Georgios D. Tsoukalas
        #index_together = [["project", "state"]]
2203 bfe23b13 Sofia Papagiannaki
2204 65360c65 Georgios D. Tsoukalas
    def __str__(self):
2205 b6eaca30 Giorgos Korfiatis
        return uenc(_("<'%s' membership in '%s'>") % (
2206 b6eaca30 Giorgos Korfiatis
                self.person.username, self.project))
2207 65360c65 Georgios D. Tsoukalas
2208 65360c65 Georgios D. Tsoukalas
    __repr__ = __str__
2209 65360c65 Georgios D. Tsoukalas
2210 65360c65 Georgios D. Tsoukalas
    def __init__(self, *args, **kwargs):
2211 ee45eb81 Giorgos Korfiatis
        self.state = self.REQUESTED
2212 65360c65 Georgios D. Tsoukalas
        super(ProjectMembership, self).__init__(*args, **kwargs)
2213 65360c65 Georgios D. Tsoukalas
2214 4f22664f Georgios D. Tsoukalas
    def _set_history_item(self, reason, date=None):
2215 4f22664f Georgios D. Tsoukalas
        if isinstance(reason, basestring):
2216 4f22664f Georgios D. Tsoukalas
            reason = ProjectMembershipHistory.reasons.get(reason, -1)
2217 4f22664f Georgios D. Tsoukalas
2218 4f22664f Georgios D. Tsoukalas
        history_item = ProjectMembershipHistory(
2219 4f22664f Georgios D. Tsoukalas
                            serial=self.id,
2220 d0e78bbe Giorgos Korfiatis
                            person=self.person_id,
2221 02d2519e Giorgos Korfiatis
                            project=self.project_id,
2222 8f975b72 Sofia Papagiannaki
                            date=date or datetime.now(),
2223 4f22664f Georgios D. Tsoukalas
                            reason=reason)
2224 4f22664f Georgios D. Tsoukalas
        history_item.save()
2225 4f22664f Georgios D. Tsoukalas
        serial = history_item.id
2226 4f22664f Georgios D. Tsoukalas
2227 14f7f6a5 Giorgos Korfiatis
    def can_accept(self):
2228 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
2229 14f7f6a5 Giorgos Korfiatis
2230 4f22664f Georgios D. Tsoukalas
    def accept(self):
2231 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
2232 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to accept while is pending") % (self,)
2233 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
2234 b6fe8bb8 Giorgos Korfiatis
2235 14f7f6a5 Giorgos Korfiatis
        if not self.can_accept():
2236 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to accept in state '%s'") % (self, self.state)
2237 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2238 4f22664f Georgios D. Tsoukalas
2239 65360c65 Georgios D. Tsoukalas
        now = datetime.now()
2240 65360c65 Georgios D. Tsoukalas
        self.acceptance_date = now
2241 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='ACCEPT', date=now)
2242 db99f198 Giorgos Korfiatis
        if self.project.is_approved():
2243 b6fe8bb8 Giorgos Korfiatis
            self.state = self.ACCEPTED
2244 b6fe8bb8 Giorgos Korfiatis
            self.is_pending = True
2245 b6fe8bb8 Giorgos Korfiatis
        else:
2246 db99f198 Giorgos Korfiatis
            self.state = self.PROJECT_DEACTIVATED
2247 b6fe8bb8 Giorgos Korfiatis
2248 65360c65 Georgios D. Tsoukalas
        self.save()
2249 4f22664f Georgios D. Tsoukalas
2250 14f7f6a5 Giorgos Korfiatis
    def can_leave(self):
2251 c1007621 Giorgos Korfiatis
        return self.state in self.ACCEPTED_STATES
2252 c1007621 Giorgos Korfiatis
2253 c1007621 Giorgos Korfiatis
    def leave_request(self):
2254 c1007621 Giorgos Korfiatis
        if self.is_pending:
2255 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to request to leave while is pending") % (self,)
2256 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2257 c1007621 Giorgos Korfiatis
2258 c1007621 Giorgos Korfiatis
        if not self.can_leave():
2259 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to request to leave in state '%s'") % (
2260 c1007621 Giorgos Korfiatis
                self, self.state)
2261 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2262 c1007621 Giorgos Korfiatis
2263 c1007621 Giorgos Korfiatis
        self.leave_request_date = datetime.now()
2264 c1007621 Giorgos Korfiatis
        self.state = self.LEAVE_REQUESTED
2265 c1007621 Giorgos Korfiatis
        self.save()
2266 c1007621 Giorgos Korfiatis
2267 c1007621 Giorgos Korfiatis
    def can_deny_leave(self):
2268 c1007621 Giorgos Korfiatis
        return self.state == self.LEAVE_REQUESTED
2269 c1007621 Giorgos Korfiatis
2270 c1007621 Giorgos Korfiatis
    def leave_request_deny(self):
2271 c1007621 Giorgos Korfiatis
        if self.is_pending:
2272 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to deny leave request while is pending") % (
2273 c1007621 Giorgos Korfiatis
                self,)
2274 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2275 c1007621 Giorgos Korfiatis
2276 c1007621 Giorgos Korfiatis
        if not self.can_deny_leave():
2277 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to deny leave request in state '%s'") % (
2278 c1007621 Giorgos Korfiatis
                self, self.state)
2279 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2280 c1007621 Giorgos Korfiatis
2281 c1007621 Giorgos Korfiatis
        self.leave_request_date = None
2282 c1007621 Giorgos Korfiatis
        self.state = self.ACCEPTED
2283 c1007621 Giorgos Korfiatis
        self.save()
2284 c1007621 Giorgos Korfiatis
2285 c1007621 Giorgos Korfiatis
    def can_cancel_leave(self):
2286 c1007621 Giorgos Korfiatis
        return self.state == self.LEAVE_REQUESTED
2287 c1007621 Giorgos Korfiatis
2288 c1007621 Giorgos Korfiatis
    def leave_request_cancel(self):
2289 c1007621 Giorgos Korfiatis
        if self.is_pending:
2290 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to cancel leave request while is pending") % (
2291 c1007621 Giorgos Korfiatis
                self,)
2292 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2293 c1007621 Giorgos Korfiatis
2294 c1007621 Giorgos Korfiatis
        if not self.can_cancel_leave():
2295 c1007621 Giorgos Korfiatis
            m = _("%s: attempt to cancel leave request in state '%s'") % (
2296 c1007621 Giorgos Korfiatis
                self, self.state)
2297 c1007621 Giorgos Korfiatis
            raise AssertionError(m)
2298 c1007621 Giorgos Korfiatis
2299 c1007621 Giorgos Korfiatis
        self.leave_request_date = None
2300 c1007621 Giorgos Korfiatis
        self.state = self.ACCEPTED
2301 c1007621 Giorgos Korfiatis
        self.save()
2302 14f7f6a5 Giorgos Korfiatis
2303 14f7f6a5 Giorgos Korfiatis
    def can_remove(self):
2304 14f7f6a5 Giorgos Korfiatis
        return self.state in self.ACCEPTED_STATES
2305 14f7f6a5 Giorgos Korfiatis
2306 65360c65 Georgios D. Tsoukalas
    def remove(self):
2307 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
2308 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to remove while is pending") % (self,)
2309 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
2310 b6fe8bb8 Giorgos Korfiatis
2311 14f7f6a5 Giorgos Korfiatis
        if not self.can_remove():
2312 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to remove in state '%s'") % (self, self.state)
2313 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2314 4f22664f Georgios D. Tsoukalas
2315 ee45eb81 Giorgos Korfiatis
        self._set_history_item(reason='REMOVE')
2316 b6fe8bb8 Giorgos Korfiatis
        self.state = self.REMOVED
2317 b6fe8bb8 Giorgos Korfiatis
        self.is_pending = True
2318 0cc22d47 Sofia Papagiannaki
        self.save()
2319 b8f05f8d Sofia Papagiannaki
2320 14f7f6a5 Giorgos Korfiatis
    def can_reject(self):
2321 14f7f6a5 Giorgos Korfiatis
        return self.state == self.REQUESTED
2322 14f7f6a5 Giorgos Korfiatis
2323 65360c65 Georgios D. Tsoukalas
    def reject(self):
2324 b6fe8bb8 Giorgos Korfiatis
        if self.is_pending:
2325 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to reject while is pending") % (self,)
2326 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
2327 b6fe8bb8 Giorgos Korfiatis
2328 14f7f6a5 Giorgos Korfiatis
        if not self.can_reject():
2329 14f7f6a5 Giorgos Korfiatis
            m = _("%s: attempt to reject in state '%s'") % (self, self.state)
2330 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2331 65360c65 Georgios D. Tsoukalas
2332 65360c65 Georgios D. Tsoukalas
        # rejected requests don't need sync,
2333 65360c65 Georgios D. Tsoukalas
        # because they were never effected
2334 65360c65 Georgios D. Tsoukalas
        self._set_history_item(reason='REJECT')
2335 0cc22d47 Sofia Papagiannaki
        self.delete()
2336 b8f05f8d Sofia Papagiannaki
2337 aad0e329 Giorgos Korfiatis
    def can_cancel(self):
2338 aad0e329 Giorgos Korfiatis
        return self.state == self.REQUESTED
2339 aad0e329 Giorgos Korfiatis
2340 aad0e329 Giorgos Korfiatis
    def cancel(self):
2341 aad0e329 Giorgos Korfiatis
        if self.is_pending:
2342 aad0e329 Giorgos Korfiatis
            m = _("%s: attempt to cancel while is pending") % (self,)
2343 aad0e329 Giorgos Korfiatis
            raise AssertionError(m)
2344 aad0e329 Giorgos Korfiatis
2345 aad0e329 Giorgos Korfiatis
        if not self.can_cancel():
2346 aad0e329 Giorgos Korfiatis
            m = _("%s: attempt to cancel in state '%s'") % (self, self.state)
2347 aad0e329 Giorgos Korfiatis
            raise AssertionError(m)
2348 aad0e329 Giorgos Korfiatis
2349 aad0e329 Giorgos Korfiatis
        # rejected requests don't need sync,
2350 aad0e329 Giorgos Korfiatis
        # because they were never effected
2351 aad0e329 Giorgos Korfiatis
        self._set_history_item(reason='CANCEL')
2352 aad0e329 Giorgos Korfiatis
        self.delete()
2353 aad0e329 Giorgos Korfiatis
2354 b6fe8bb8 Giorgos Korfiatis
    def get_diff_quotas(self, sub_list=None, add_list=None):
2355 d2b32360 Giorgos Korfiatis
        if sub_list is None:
2356 d2b32360 Giorgos Korfiatis
            sub_list = []
2357 d2b32360 Giorgos Korfiatis
2358 d2b32360 Giorgos Korfiatis
        if add_list is None:
2359 d2b32360 Giorgos Korfiatis
            add_list = []
2360 d6fdc91e Georgios D. Tsoukalas
2361 d2b32360 Giorgos Korfiatis
        sub_append = sub_list.append
2362 d2b32360 Giorgos Korfiatis
        add_append = add_list.append
2363 d75c432e Sofia Papagiannaki
        holder = self.person.uuid
2364 d6fdc91e Georgios D. Tsoukalas
2365 d6fdc91e Georgios D. Tsoukalas
        synced_application = self.application
2366 d6fdc91e Georgios D. Tsoukalas
        if synced_application is not None:
2367 5f2e4042 Sofia Papagiannaki
            cur_grants = synced_application.projectresourcegrant_set.all()
2368 d6fdc91e Georgios D. Tsoukalas
            for grant in cur_grants:
2369 d2b32360 Giorgos Korfiatis
                sub_append(QuotaLimits(
2370 d2b32360 Giorgos Korfiatis
                               holder       = holder,
2371 f3e93707 Sofia Papagiannaki
                               resource     = str(grant.resource),
2372 d2b32360 Giorgos Korfiatis
                               capacity     = grant.member_capacity,
2373 d2b32360 Giorgos Korfiatis
                               import_limit = grant.member_import_limit,
2374 d2b32360 Giorgos Korfiatis
                               export_limit = grant.member_export_limit))
2375 d6fdc91e Georgios D. Tsoukalas
2376 b6fe8bb8 Giorgos Korfiatis
        pending_application = self.pending_application
2377 b6fe8bb8 Giorgos Korfiatis
        if pending_application is not None:
2378 b6fe8bb8 Giorgos Korfiatis
            new_grants = pending_application.projectresourcegrant_set.all()
2379 d6fdc91e Georgios D. Tsoukalas
            for new_grant in new_grants:
2380 d2b32360 Giorgos Korfiatis
                add_append(QuotaLimits(
2381 d2b32360 Giorgos Korfiatis
                               holder       = holder,
2382 f3e93707 Sofia Papagiannaki
                               resource     = str(new_grant.resource),
2383 974ee6a6 Sofia Papagiannaki
                               capacity     = new_grant.member_capacity,
2384 974ee6a6 Sofia Papagiannaki
                               import_limit = new_grant.member_import_limit,
2385 974ee6a6 Sofia Papagiannaki
                               export_limit = new_grant.member_export_limit))
2386 d6fdc91e Georgios D. Tsoukalas
2387 d2b32360 Giorgos Korfiatis
        return (sub_list, add_list)
2388 65360c65 Georgios D. Tsoukalas
2389 ee45eb81 Giorgos Korfiatis
    def set_sync(self):
2390 b6fe8bb8 Giorgos Korfiatis
        if not self.is_pending:
2391 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to sync a non pending membership") % (self,)
2392 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
2393 b6fe8bb8 Giorgos Korfiatis
2394 ee45eb81 Giorgos Korfiatis
        state = self.state
2395 c1007621 Giorgos Korfiatis
        if state in self.ACTUALLY_ACCEPTED:
2396 ee45eb81 Giorgos Korfiatis
            pending_application = self.pending_application
2397 ee45eb81 Giorgos Korfiatis
            if pending_application is None:
2398 ee45eb81 Giorgos Korfiatis
                m = _("%s: attempt to sync an empty pending application") % (
2399 8c7b8bb8 Giorgos Korfiatis
                    self,)
2400 ee45eb81 Giorgos Korfiatis
                raise AssertionError(m)
2401 b6fe8bb8 Giorgos Korfiatis
2402 ee45eb81 Giorgos Korfiatis
            self.application = pending_application
2403 b6fe8bb8 Giorgos Korfiatis
            self.is_active = True
2404 b6fe8bb8 Giorgos Korfiatis
2405 ee45eb81 Giorgos Korfiatis
            self.pending_application = None
2406 ee45eb81 Giorgos Korfiatis
            self.pending_serial = None
2407 ee45eb81 Giorgos Korfiatis
2408 ee45eb81 Giorgos Korfiatis
            # project.application may have changed in the meantime,
2409 ee45eb81 Giorgos Korfiatis
            # in which case we stay PENDING;
2410 ee45eb81 Giorgos Korfiatis
            # we are safe to check due to select_for_update
2411 ee45eb81 Giorgos Korfiatis
            if self.application == self.project.application:
2412 b6fe8bb8 Giorgos Korfiatis
                self.is_pending = False
2413 ee45eb81 Giorgos Korfiatis
            self.save()
2414 b6fe8bb8 Giorgos Korfiatis
2415 db99f198 Giorgos Korfiatis
        elif state == self.PROJECT_DEACTIVATED:
2416 5b9e9530 Giorgos Korfiatis
            if self.pending_application:
2417 5b9e9530 Giorgos Korfiatis
                m = _("%s: attempt to sync in state '%s' "
2418 5b9e9530 Giorgos Korfiatis
                      "with a pending application") % (self, state)
2419 5b9e9530 Giorgos Korfiatis
                raise AssertionError(m)
2420 b6fe8bb8 Giorgos Korfiatis
2421 5b9e9530 Giorgos Korfiatis
            self.application = None
2422 2a666c36 Giorgos Korfiatis
            self.is_active = False
2423 5b9e9530 Giorgos Korfiatis
            self.pending_serial = None
2424 b6fe8bb8 Giorgos Korfiatis
            self.is_pending = False
2425 5b9e9530 Giorgos Korfiatis
            self.save()
2426 b6fe8bb8 Giorgos Korfiatis
2427 b6fe8bb8 Giorgos Korfiatis
        elif state == self.REMOVED:
2428 ee45eb81 Giorgos Korfiatis
            self.delete()
2429 b6fe8bb8 Giorgos Korfiatis
2430 ee45eb81 Giorgos Korfiatis
        else:
2431 ee45eb81 Giorgos Korfiatis
            m = _("%s: attempt to sync in state '%s'") % (self, state)
2432 ee45eb81 Giorgos Korfiatis
            raise AssertionError(m)
2433 ee45eb81 Giorgos Korfiatis
2434 49b74233 Georgios D. Tsoukalas
    def reset_sync(self):
2435 b6fe8bb8 Giorgos Korfiatis
        if not self.is_pending:
2436 b6fe8bb8 Giorgos Korfiatis
            m = _("%s: attempt to reset a non pending membership") % (self,)
2437 b6fe8bb8 Giorgos Korfiatis
            raise AssertionError(m)
2438 b6fe8bb8 Giorgos Korfiatis
2439 49b74233 Georgios D. Tsoukalas
        state = self.state
2440 c1007621 Giorgos Korfiatis
        if state in [self.ACCEPTED, self.LEAVE_REQUESTED,
2441 c1007621 Giorgos Korfiatis
                     self.PROJECT_DEACTIVATED, self.REMOVED]:
2442 49b74233 Georgios D. Tsoukalas
            self.pending_application = None
2443 49b74233 Georgios D. Tsoukalas
            self.pending_serial = None
2444 49b74233 Georgios D. Tsoukalas
            self.save()
2445 49b74233 Georgios D. Tsoukalas
        else:
2446 49b74233 Georgios D. Tsoukalas
            m = _("%s: attempt to reset sync in state '%s'") % (self, state)
2447 49b74233 Georgios D. Tsoukalas
            raise AssertionError(m)
2448 49b74233 Georgios D. Tsoukalas
2449 ee45eb81 Giorgos Korfiatis
class Serial(models.Model):
2450 ee45eb81 Giorgos Korfiatis
    serial  =   models.AutoField(primary_key=True)
2451 ee45eb81 Giorgos Korfiatis
2452 ee45eb81 Giorgos Korfiatis
def new_serial():
2453 5200e864 Sofia Papagiannaki
    s = Serial.objects.create()
2454 c70968bd Giorgos Korfiatis
    serial = s.serial
2455 c70968bd Giorgos Korfiatis
    s.delete()
2456 c70968bd Giorgos Korfiatis
    return serial
2457 82d7e9ef Georgios D. Tsoukalas
2458 c8a8cb19 Giorgos Korfiatis
class SyncError(Exception):
2459 c8a8cb19 Giorgos Korfiatis
    pass
2460 c8a8cb19 Giorgos Korfiatis
2461 c8a8cb19 Giorgos Korfiatis
def reset_serials(serials):
2462 ea1e5d9f Giorgos Korfiatis
    objs = ProjectMembership.objects
2463 ea1e5d9f Giorgos Korfiatis
    q = objs.filter(pending_serial__in=serials).select_for_update()
2464 ea1e5d9f Giorgos Korfiatis
    memberships = list(q)
2465 c8a8cb19 Giorgos Korfiatis
2466 c8a8cb19 Giorgos Korfiatis
    if memberships:
2467 c8a8cb19 Giorgos Korfiatis
        for membership in memberships:
2468 c8a8cb19 Giorgos Korfiatis
            membership.reset_sync()
2469 c8a8cb19 Giorgos Korfiatis
2470 c8a8cb19 Giorgos Korfiatis
        transaction.commit()
2471 c8a8cb19 Giorgos Korfiatis
2472 333f6a72 Sofia Papagiannaki
def sync_finish_serials(serials_to_ack=None):
2473 333f6a72 Sofia Papagiannaki
    if serials_to_ack is None:
2474 333f6a72 Sofia Papagiannaki
        serials_to_ack = qh_query_serials([])
2475 333f6a72 Sofia Papagiannaki
2476 333f6a72 Sofia Papagiannaki
    serials_to_ack = set(serials_to_ack)
2477 ea1e5d9f Giorgos Korfiatis
    objs = ProjectMembership.objects
2478 ea1e5d9f Giorgos Korfiatis
    q = objs.filter(pending_serial__isnull=False).select_for_update()
2479 ea1e5d9f Giorgos Korfiatis
    memberships = list(q)
2480 333f6a72 Sofia Papagiannaki
2481 60ca2f6f Giorgos Korfiatis
    if memberships:
2482 60ca2f6f Giorgos Korfiatis
        for membership in memberships:
2483 60ca2f6f Giorgos Korfiatis
            serial = membership.pending_serial
2484 60ca2f6f Giorgos Korfiatis
            if serial in serials_to_ack:
2485 60ca2f6f Giorgos Korfiatis
                membership.set_sync()
2486 60ca2f6f Giorgos Korfiatis
            else:
2487 60ca2f6f Giorgos Korfiatis
                membership.reset_sync()
2488 60ca2f6f Giorgos Korfiatis
2489 60ca2f6f Giorgos Korfiatis
        transaction.commit()
2490 60ca2f6f Giorgos Korfiatis
2491 ee45eb81 Giorgos Korfiatis
    qh_ack_serials(list(serials_to_ack))
2492 333f6a72 Sofia Papagiannaki
    return len(memberships)
2493 82d7e9ef Georgios D. Tsoukalas
2494 762900a2 Giorgos Korfiatis
def pre_sync_projects(sync=True):
2495 b6fe8bb8 Giorgos Korfiatis
    ACCEPTED = ProjectMembership.ACCEPTED
2496 c1007621 Giorgos Korfiatis
    LEAVE_REQUESTED = ProjectMembership.LEAVE_REQUESTED
2497 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
2498 ea1e5d9f Giorgos Korfiatis
    objs = Project.objects
2499 b6fe8bb8 Giorgos Korfiatis
2500 ea1e5d9f Giorgos Korfiatis
    modified = list(objs.modified_projects().select_for_update())
2501 762900a2 Giorgos Korfiatis
    if sync:
2502 762900a2 Giorgos Korfiatis
        for project in modified:
2503 ea1e5d9f Giorgos Korfiatis
            objects = project.projectmembership_set
2504 762900a2 Giorgos Korfiatis
2505 ea1e5d9f Giorgos Korfiatis
            memberships = objects.actually_accepted().select_for_update()
2506 762900a2 Giorgos Korfiatis
            for membership in memberships:
2507 762900a2 Giorgos Korfiatis
                membership.is_pending = True
2508 762900a2 Giorgos Korfiatis
                membership.save()
2509 762900a2 Giorgos Korfiatis
2510 ea1e5d9f Giorgos Korfiatis
    reactivating = list(objs.reactivating_projects().select_for_update())
2511 762900a2 Giorgos Korfiatis
    if sync:
2512 762900a2 Giorgos Korfiatis
        for project in reactivating:
2513 ea1e5d9f Giorgos Korfiatis
            objects = project.projectmembership_set
2514 762900a2 Giorgos Korfiatis
2515 ea1e5d9f Giorgos Korfiatis
            q = objects.filter(state=PROJECT_DEACTIVATED)
2516 ea1e5d9f Giorgos Korfiatis
            memberships = q.select_for_update()
2517 762900a2 Giorgos Korfiatis
            for membership in memberships:
2518 762900a2 Giorgos Korfiatis
                membership.is_pending = True
2519 c1007621 Giorgos Korfiatis
                if membership.leave_request_date is None:
2520 c1007621 Giorgos Korfiatis
                    membership.state = ACCEPTED
2521 c1007621 Giorgos Korfiatis
                else:
2522 c1007621 Giorgos Korfiatis
                    membership.state = LEAVE_REQUESTED
2523 762900a2 Giorgos Korfiatis
                membership.save()
2524 762900a2 Giorgos Korfiatis
2525 ea1e5d9f Giorgos Korfiatis
    deactivating = list(objs.deactivating_projects().select_for_update())
2526 762900a2 Giorgos Korfiatis
    if sync:
2527 762900a2 Giorgos Korfiatis
        for project in deactivating:
2528 ea1e5d9f Giorgos Korfiatis
            objects = project.projectmembership_set
2529 762900a2 Giorgos Korfiatis
2530 762900a2 Giorgos Korfiatis
            # Note: we keep a user-level deactivation
2531 762900a2 Giorgos Korfiatis
            # (e.g. USER_SUSPENDED) intact
2532 ea1e5d9f Giorgos Korfiatis
            memberships = objects.actually_accepted().select_for_update()
2533 762900a2 Giorgos Korfiatis
            for membership in memberships:
2534 762900a2 Giorgos Korfiatis
                membership.is_pending = True
2535 762900a2 Giorgos Korfiatis
                membership.state = PROJECT_DEACTIVATED
2536 762900a2 Giorgos Korfiatis
                membership.save()
2537 762900a2 Giorgos Korfiatis
2538 ea1e5d9f Giorgos Korfiatis
#    transaction.commit()
2539 762900a2 Giorgos Korfiatis
    return (modified, reactivating, deactivating)
2540 b6fe8bb8 Giorgos Korfiatis
2541 c8a8cb19 Giorgos Korfiatis
def set_sync_projects(exclude=None):
2542 82d7e9ef Georgios D. Tsoukalas
2543 c1007621 Giorgos Korfiatis
    ACTUALLY_ACCEPTED = ProjectMembership.ACTUALLY_ACCEPTED
2544 ea1e5d9f Giorgos Korfiatis
    objects = ProjectMembership.objects
2545 82d7e9ef Georgios D. Tsoukalas
2546 d2b32360 Giorgos Korfiatis
    sub_quota, add_quota = [], []
2547 65360c65 Georgios D. Tsoukalas
2548 ee45eb81 Giorgos Korfiatis
    serial = new_serial()
2549 d6fdc91e Georgios D. Tsoukalas
2550 ea1e5d9f Giorgos Korfiatis
    pending = objects.filter(is_pending=True).select_for_update()
2551 d6fdc91e Georgios D. Tsoukalas
    for membership in pending:
2552 d6fdc91e Georgios D. Tsoukalas
2553 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_application:
2554 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_application is not None (%s)" % (
2555 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_application)
2556 d6fdc91e Georgios D. Tsoukalas
            raise AssertionError(m)
2557 d6fdc91e Georgios D. Tsoukalas
        if membership.pending_serial:
2558 d6fdc91e Georgios D. Tsoukalas
            m = "%s: impossible: pending_serial is not None (%s)" % (
2559 d6fdc91e Georgios D. Tsoukalas
                membership, membership.pending_serial)
2560 65360c65 Georgios D. Tsoukalas
            raise AssertionError(m)
2561 b8f05f8d Sofia Papagiannaki
2562 c8a8cb19 Giorgos Korfiatis
        if exclude is not None:
2563 c8a8cb19 Giorgos Korfiatis
            uuid = membership.person.uuid
2564 c8a8cb19 Giorgos Korfiatis
            if uuid in exclude:
2565 c8a8cb19 Giorgos Korfiatis
                logger.warning("Excluded from sync: %s" % uuid)
2566 c8a8cb19 Giorgos Korfiatis
                continue
2567 c8a8cb19 Giorgos Korfiatis
2568 c1007621 Giorgos Korfiatis
        if membership.state in ACTUALLY_ACCEPTED:
2569 b6fe8bb8 Giorgos Korfiatis
            membership.pending_application = membership.project.application
2570 d6fdc91e Georgios D. Tsoukalas
2571 d6fdc91e Georgios D. Tsoukalas
        membership.pending_serial = serial
2572 b6fe8bb8 Giorgos Korfiatis
        membership.get_diff_quotas(sub_quota, add_quota)
2573 d6fdc91e Georgios D. Tsoukalas
        membership.save()
2574 d6fdc91e Georgios D. Tsoukalas
2575 d6fdc91e Georgios D. Tsoukalas
    transaction.commit()
2576 c8a8cb19 Giorgos Korfiatis
    return serial, sub_quota, add_quota
2577 c8a8cb19 Giorgos Korfiatis
2578 c8a8cb19 Giorgos Korfiatis
def do_sync_projects():
2579 c8a8cb19 Giorgos Korfiatis
    serial, sub_quota, add_quota = set_sync_projects()
2580 c8a8cb19 Giorgos Korfiatis
    r = qh_add_quota(serial, sub_quota, add_quota)
2581 c8a8cb19 Giorgos Korfiatis
    if not r:
2582 c8a8cb19 Giorgos Korfiatis
        return serial
2583 ee45eb81 Giorgos Korfiatis
2584 c8a8cb19 Giorgos Korfiatis
    m = "cannot sync serial: %d" % serial
2585 c8a8cb19 Giorgos Korfiatis
    logger.error(m)
2586 c8a8cb19 Giorgos Korfiatis
    logger.error("Failed: %s" % r)
2587 c8a8cb19 Giorgos Korfiatis
2588 c8a8cb19 Giorgos Korfiatis
    reset_serials([serial])
2589 c8a8cb19 Giorgos Korfiatis
    uuids = set(uuid for (uuid, resource) in r)
2590 c8a8cb19 Giorgos Korfiatis
    serial, sub_quota, add_quota = set_sync_projects(exclude=uuids)
2591 5cfd4acb Sofia Papagiannaki
    r = qh_add_quota(serial, sub_quota, add_quota)
2592 c8a8cb19 Giorgos Korfiatis
    if not r:
2593 c8a8cb19 Giorgos Korfiatis
        return serial
2594 333f6a72 Sofia Papagiannaki
2595 c8a8cb19 Giorgos Korfiatis
    m = "cannot sync serial: %d" % serial
2596 c8a8cb19 Giorgos Korfiatis
    logger.error(m)
2597 c8a8cb19 Giorgos Korfiatis
    logger.error("Failed: %s" % r)
2598 c8a8cb19 Giorgos Korfiatis
    raise SyncError(m)
2599 5b9e9530 Giorgos Korfiatis
2600 84a3f701 Giorgos Korfiatis
def post_sync_projects():
2601 db99f198 Giorgos Korfiatis
    PROJECT_DEACTIVATED = ProjectMembership.PROJECT_DEACTIVATED
2602 c1007621 Giorgos Korfiatis
    Q_ACTUALLY_ACCEPTED = ProjectMembership.Q_ACTUALLY_ACCEPTED
2603 ea1e5d9f Giorgos Korfiatis
    objs = Project.objects
2604 5b9e9530 Giorgos Korfiatis
2605 ea1e5d9f Giorgos Korfiatis
    modified = objs.modified_projects().select_for_update()
2606 b6fe8bb8 Giorgos Korfiatis
    for project in modified:
2607 ea1e5d9f Giorgos Korfiatis
        objects = project.projectmembership_set
2608 ea1e5d9f Giorgos Korfiatis
        q = objects.filter(Q_ACTUALLY_ACCEPTED & Q(is_pending=True))
2609 ea1e5d9f Giorgos Korfiatis
        memberships = list(q.select_for_update())
2610 b6fe8bb8 Giorgos Korfiatis
        if not memberships:
2611 b6fe8bb8 Giorgos Korfiatis
            project.is_modified = False
2612 b6fe8bb8 Giorgos Korfiatis
            project.save()
2613 5b9e9530 Giorgos Korfiatis
2614 ea1e5d9f Giorgos Korfiatis
    reactivating = objs.reactivating_projects().select_for_update()
2615 db99f198 Giorgos Korfiatis
    for project in reactivating:
2616 ea1e5d9f Giorgos Korfiatis
        objects = project.projectmembership_set
2617 ea1e5d9f Giorgos Korfiatis
        q = objects.filter(Q(state=PROJECT_DEACTIVATED) | Q(is_pending=True))
2618 ea1e5d9f Giorgos Korfiatis
        memberships = list(q.select_for_update())
2619 db99f198 Giorgos Korfiatis
        if not memberships:
2620 db99f198 Giorgos Korfiatis
            project.reactivate()
2621 db99f198 Giorgos Korfiatis
            project.save()
2622 db99f198 Giorgos Korfiatis
2623 ea1e5d9f Giorgos Korfiatis
    deactivating = objs.deactivating_projects().select_for_update()
2624 db99f198 Giorgos Korfiatis
    for project in deactivating:
2625 ea1e5d9f Giorgos Korfiatis
        objects = project.projectmembership_set
2626 ea1e5d9f Giorgos Korfiatis
        q = objects.filter(Q_ACTUALLY_ACCEPTED | Q(is_pending=True))
2627 ea1e5d9f Giorgos Korfiatis
        memberships = list(q.select_for_update())
2628 5b9e9530 Giorgos Korfiatis
        if not memberships:
2629 b6fe8bb8 Giorgos Korfiatis
            project.deactivate()
2630 5b9e9530 Giorgos Korfiatis
            project.save()
2631 5b9e9530 Giorgos Korfiatis
2632 5b9e9530 Giorgos Korfiatis
    transaction.commit()
2633 5b9e9530 Giorgos Korfiatis
2634 140da2d1 Giorgos Korfiatis
def sync_projects(sync=True, retries=3, retry_wait=1.0):
2635 57f5ea5c Giorgos Korfiatis
    @with_lock(retries, retry_wait)
2636 57f5ea5c Giorgos Korfiatis
    def _sync_projects(sync):
2637 57f5ea5c Giorgos Korfiatis
        sync_finish_serials()
2638 57f5ea5c Giorgos Korfiatis
        # Informative only -- no select_for_update()
2639 57f5ea5c Giorgos Korfiatis
        pending = list(ProjectMembership.objects.filter(is_pending=True))
2640 57f5ea5c Giorgos Korfiatis
2641 57f5ea5c Giorgos Korfiatis
        projects_log = pre_sync_projects(sync)
2642 57f5ea5c Giorgos Korfiatis
        if sync:
2643 57f5ea5c Giorgos Korfiatis
            serial = do_sync_projects()
2644 57f5ea5c Giorgos Korfiatis
            sync_finish_serials([serial])
2645 57f5ea5c Giorgos Korfiatis
            post_sync_projects()
2646 84a3f701 Giorgos Korfiatis
2647 57f5ea5c Giorgos Korfiatis
        return (pending, projects_log)
2648 57f5ea5c Giorgos Korfiatis
    return _sync_projects(sync)
2649 5aac5dc4 Giorgos Korfiatis
2650 5aac5dc4 Giorgos Korfiatis
def all_users_quotas(users):
2651 f557d10a Giorgos Korfiatis
    initial = {}
2652 5aac5dc4 Giorgos Korfiatis
    quotas = {}
2653 f557d10a Giorgos Korfiatis
    info = {}
2654 5aac5dc4 Giorgos Korfiatis
    for user in users:
2655 f557d10a Giorgos Korfiatis
        uuid = user.uuid
2656 f557d10a Giorgos Korfiatis
        info[uuid] = user.email
2657 f557d10a Giorgos Korfiatis
        init = user.initial_quotas()
2658 f557d10a Giorgos Korfiatis
        initial[uuid] = init
2659 f557d10a Giorgos Korfiatis
        quotas[user.uuid] = user.all_quotas(initial=init)
2660 f557d10a Giorgos Korfiatis
    return initial, quotas, info
2661 5aac5dc4 Giorgos Korfiatis
2662 57f5ea5c Giorgos Korfiatis
def sync_users(users, sync=True, retries=3, retry_wait=1.0):
2663 57f5ea5c Giorgos Korfiatis
    @with_lock(retries, retry_wait)
2664 57f5ea5c Giorgos Korfiatis
    def _sync_users(users, sync):
2665 57f5ea5c Giorgos Korfiatis
        sync_finish_serials()
2666 84a3f701 Giorgos Korfiatis
2667 57f5ea5c Giorgos Korfiatis
        existing, nonexisting = qh_check_users(users)
2668 57f5ea5c Giorgos Korfiatis
        resources = get_resource_names()
2669 f557d10a Giorgos Korfiatis
        qh_limits, qh_counters = qh_get_quotas(existing, resources)
2670 f557d10a Giorgos Korfiatis
        astakos_initial, astakos_quotas, info = all_users_quotas(users)
2671 d6fdc91e Georgios D. Tsoukalas
2672 57f5ea5c Giorgos Korfiatis
        if sync:
2673 57f5ea5c Giorgos Korfiatis
            r = register_users(nonexisting)
2674 57f5ea5c Giorgos Korfiatis
            r = send_quotas(astakos_quotas)
2675 84a3f701 Giorgos Korfiatis
2676 f557d10a Giorgos Korfiatis
        return (existing, nonexisting,
2677 f557d10a Giorgos Korfiatis
                qh_limits, qh_counters,
2678 f557d10a Giorgos Korfiatis
                astakos_initial, astakos_quotas, info)
2679 57f5ea5c Giorgos Korfiatis
    return _sync_users(users, sync)
2680 84a3f701 Giorgos Korfiatis
2681 140da2d1 Giorgos Korfiatis
def sync_all_users(sync=True, retries=3, retry_wait=1.0):
2682 d9907a5e Giorgos Korfiatis
    users = AstakosUser.objects.verified()
2683 57f5ea5c Giorgos Korfiatis
    return sync_users(users, sync, retries, retry_wait)
2684 e1a80257 Sofia Papagiannaki
2685 0cc22d47 Sofia Papagiannaki
class ProjectMembershipHistory(models.Model):
2686 425e2e95 Sofia Papagiannaki
    reasons_list    =   ['ACCEPT', 'REJECT', 'REMOVE']
2687 425e2e95 Sofia Papagiannaki
    reasons         =   dict((k, v) for v, k in enumerate(reasons_list))
2688 425e2e95 Sofia Papagiannaki
2689 d0e78bbe Giorgos Korfiatis
    person  =   models.BigIntegerField()
2690 02d2519e Giorgos Korfiatis
    project =   models.BigIntegerField()
2691 3c638f72 Giorgos Korfiatis
    date    =   models.DateField(auto_now_add=True)
2692 425e2e95 Sofia Papagiannaki
    reason  =   models.IntegerField()
2693 425e2e95 Sofia Papagiannaki
    serial  =   models.BigIntegerField()
2694 fc655b6f Kostas Papadimitriou
2695 fcc1e93f Sofia Papagiannaki
### SIGNALS ###
2696 fcc1e93f Sofia Papagiannaki
################
2697 b22de10a Sofia Papagiannaki
2698 ff9290ec Sofia Papagiannaki
def create_astakos_user(u):
2699 ff9290ec Sofia Papagiannaki
    try:
2700 ff9290ec Sofia Papagiannaki
        AstakosUser.objects.get(user_ptr=u.pk)
2701 ff9290ec Sofia Papagiannaki
    except AstakosUser.DoesNotExist:
2702 ff9290ec Sofia Papagiannaki
        extended_user = AstakosUser(user_ptr_id=u.pk)
2703 ff9290ec Sofia Papagiannaki
        extended_user.__dict__.update(u.__dict__)
2704 ff9290ec Sofia Papagiannaki
        extended_user.save()
2705 67be1883 Olga Brani
        if not extended_user.has_auth_provider('local'):
2706 67be1883 Olga Brani
            extended_user.add_auth_provider('local')
2707 fc1e2f02 Sofia Papagiannaki
    except BaseException, e:
2708 fc1e2f02 Sofia Papagiannaki
        logger.exception(e)
2709 ff9290ec Sofia Papagiannaki
2710 a7752e95 Sofia Papagiannaki
def fix_superusers():
2711 fc1e2f02 Sofia Papagiannaki
    # Associate superusers with AstakosUser
2712 ff9290ec Sofia Papagiannaki
    admins = User.objects.filter(is_superuser=True)
2713 ff9290ec Sofia Papagiannaki
    for u in admins:
2714 ff9290ec Sofia Papagiannaki
        create_astakos_user(u)
2715 ff9290ec Sofia Papagiannaki
2716 fc1e2f02 Sofia Papagiannaki
def user_post_save(sender, instance, created, **kwargs):
2717 aa4109d4 Sofia Papagiannaki
    if not created:
2718 aa4109d4 Sofia Papagiannaki
        return
2719 fc1e2f02 Sofia Papagiannaki
    create_astakos_user(instance)
2720 bfe23b13 Sofia Papagiannaki
post_save.connect(user_post_save, sender=User)
2721 ff9290ec Sofia Papagiannaki
2722 fc1e2f02 Sofia Papagiannaki
def astakosuser_post_save(sender, instance, created, **kwargs):
2723 21e0fdad Giorgos Korfiatis
    pass
2724 21e0fdad Giorgos Korfiatis
2725 bfe23b13 Sofia Papagiannaki
post_save.connect(astakosuser_post_save, sender=AstakosUser)
2726 fc1e2f02 Sofia Papagiannaki
2727 bd4f356c Sofia Papagiannaki
def resource_post_save(sender, instance, created, **kwargs):
2728 0514bcc7 Giorgos Korfiatis
    pass
2729 0514bcc7 Giorgos Korfiatis
2730 bfe23b13 Sofia Papagiannaki
post_save.connect(resource_post_save, sender=Resource)
2731 bfe23b13 Sofia Papagiannaki
2732 bfe23b13 Sofia Papagiannaki
def renew_token(sender, instance, **kwargs):
2733 bfe23b13 Sofia Papagiannaki
    if not instance.auth_token:
2734 bfe23b13 Sofia Papagiannaki
        instance.renew_token()
2735 bf0c6de5 Sofia Papagiannaki
pre_save.connect(renew_token, sender=AstakosUser)
2736 2e90e3ec Kostas Papadimitriou
pre_save.connect(renew_token, sender=Service)