Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (92.3 kB)

1 f557d10a Giorgos Korfiatis
# Copyright 2011, 2012, 2013 GRNET S.A. All rights reserved.
2 6c736ed7 Kostas Papadimitriou
#
3 64cd4730 Antony Chazapis
# Redistribution and use in source and binary forms, with or
4 64cd4730 Antony Chazapis
# without modification, are permitted provided that the following
5 64cd4730 Antony Chazapis
# conditions are met:
6 6c736ed7 Kostas Papadimitriou
#
7 64cd4730 Antony Chazapis
#   1. Redistributions of source code must retain the above
8 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
9 64cd4730 Antony Chazapis
#      disclaimer.
10 6c736ed7 Kostas Papadimitriou
#
11 64cd4730 Antony Chazapis
#   2. Redistributions in binary form must reproduce the above
12 64cd4730 Antony Chazapis
#      copyright notice, this list of conditions and the following
13 64cd4730 Antony Chazapis
#      disclaimer in the documentation and/or other materials
14 64cd4730 Antony Chazapis
#      provided with the distribution.
15 6c736ed7 Kostas Papadimitriou
#
16 64cd4730 Antony Chazapis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 64cd4730 Antony Chazapis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 64cd4730 Antony Chazapis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 64cd4730 Antony Chazapis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 64cd4730 Antony Chazapis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 64cd4730 Antony Chazapis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 64cd4730 Antony Chazapis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 64cd4730 Antony Chazapis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 64cd4730 Antony Chazapis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 64cd4730 Antony Chazapis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 64cd4730 Antony Chazapis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 64cd4730 Antony Chazapis
# POSSIBILITY OF SUCH DAMAGE.
28 6c736ed7 Kostas Papadimitriou
#
29 64cd4730 Antony Chazapis
# The views and conclusions contained in the software and
30 64cd4730 Antony Chazapis
# documentation are those of the authors and should not be
31 64cd4730 Antony Chazapis
# interpreted as representing official policies, either expressed
32 64cd4730 Antony Chazapis
# or implied, of GRNET S.A.
33 64cd4730 Antony Chazapis
34 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 c7c0ec58 Giorgos Korfiatis
    def settings(self):
841 c7c0ec58 Giorgos Korfiatis
        return UserSetting.objects.filter(user=self)
842 c7c0ec58 Giorgos Korfiatis
843 d4660e00 Giorgos Korfiatis
844 d2633501 Kostas Papadimitriou
class AstakosUserAuthProviderManager(models.Manager):
845 d2633501 Kostas Papadimitriou
846 63836eda Kostas Papadimitriou
    def active(self, **filters):
847 63836eda Kostas Papadimitriou
        return self.filter(active=True, **filters)
848 d2633501 Kostas Papadimitriou
849 564a2292 Kostas Papadimitriou
    def remove_unverified_providers(self, provider, **filters):
850 564a2292 Kostas Papadimitriou
        try:
851 564a2292 Kostas Papadimitriou
            existing = self.filter(module=provider, user__email_verified=False, **filters)
852 564a2292 Kostas Papadimitriou
            for p in existing:
853 564a2292 Kostas Papadimitriou
                p.user.delete()
854 564a2292 Kostas Papadimitriou
        except:
855 564a2292 Kostas Papadimitriou
            pass
856 564a2292 Kostas Papadimitriou
857 564a2292 Kostas Papadimitriou
858 d2633501 Kostas Papadimitriou
859 d2633501 Kostas Papadimitriou
class AstakosUserAuthProvider(models.Model):
860 d2633501 Kostas Papadimitriou
    """
861 d2633501 Kostas Papadimitriou
    Available user authentication methods.
862 d2633501 Kostas Papadimitriou
    """
863 e1a80257 Sofia Papagiannaki
    affiliation = models.CharField(_('Affiliation'), max_length=255, blank=True,
864 d2633501 Kostas Papadimitriou
                                   null=True, default=None)
865 d2633501 Kostas Papadimitriou
    user = models.ForeignKey(AstakosUser, related_name='auth_providers')
866 e1a80257 Sofia Papagiannaki
    module = models.CharField(_('Provider'), max_length=255, blank=False,
867 d2633501 Kostas Papadimitriou
                                default='local')
868 e1a80257 Sofia Papagiannaki
    identifier = models.CharField(_('Third-party identifier'),
869 d2633501 Kostas Papadimitriou
                                              max_length=255, null=True,
870 d2633501 Kostas Papadimitriou
                                              blank=True)
871 d2633501 Kostas Papadimitriou
    active = models.BooleanField(default=True)
872 e1a80257 Sofia Papagiannaki
    auth_backend = models.CharField(_('Backend'), max_length=255, blank=False,
873 d2633501 Kostas Papadimitriou
                                   default='astakos')
874 3a72a5d4 Kostas Papadimitriou
    info_data = models.TextField(default="", null=True, blank=True)
875 c630fee6 Kostas Papadimitriou
    created = models.DateTimeField('Creation date', auto_now_add=True)
876 d2633501 Kostas Papadimitriou
877 d2633501 Kostas Papadimitriou
    objects = AstakosUserAuthProviderManager()
878 d2633501 Kostas Papadimitriou
879 d2633501 Kostas Papadimitriou
    class Meta:
880 d2633501 Kostas Papadimitriou
        unique_together = (('identifier', 'module', 'user'), )
881 c630fee6 Kostas Papadimitriou
        ordering = ('module', 'created')
882 d2633501 Kostas Papadimitriou
883 3a72a5d4 Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
884 3a72a5d4 Kostas Papadimitriou
        super(AstakosUserAuthProvider, self).__init__(*args, **kwargs)
885 3a72a5d4 Kostas Papadimitriou
        try:
886 3a72a5d4 Kostas Papadimitriou
            self.info = json.loads(self.info_data)
887 c630fee6 Kostas Papadimitriou
            if not self.info:
888 c630fee6 Kostas Papadimitriou
                self.info = {}
889 c630fee6 Kostas Papadimitriou
        except Exception, e:
890 3a72a5d4 Kostas Papadimitriou
            self.info = {}
891 c630fee6 Kostas Papadimitriou
892 3a72a5d4 Kostas Papadimitriou
        for key,value in self.info.iteritems():
893 3a72a5d4 Kostas Papadimitriou
            setattr(self, 'info_%s' % key, value)
894 3a72a5d4 Kostas Papadimitriou
895 d2633501 Kostas Papadimitriou
896 d2633501 Kostas Papadimitriou
    @property
897 d2633501 Kostas Papadimitriou
    def settings(self):
898 d2633501 Kostas Papadimitriou
        return auth_providers.get_provider(self.module)
899 d2633501 Kostas Papadimitriou
900 d2633501 Kostas Papadimitriou
    @property
901 d2633501 Kostas Papadimitriou
    def details_display(self):
902 c630fee6 Kostas Papadimitriou
        try:
903 db71675c Kostas Papadimitriou
            params = self.user.__dict__
904 db71675c Kostas Papadimitriou
            params.update(self.__dict__)
905 db71675c Kostas Papadimitriou
            return self.settings.get_details_tpl_display % params
906 c630fee6 Kostas Papadimitriou
        except:
907 db71675c Kostas Papadimitriou
            return ''
908 3a72a5d4 Kostas Papadimitriou
909 3a72a5d4 Kostas Papadimitriou
    @property
910 3a72a5d4 Kostas Papadimitriou
    def title_display(self):
911 3a72a5d4 Kostas Papadimitriou
        title_tpl = self.settings.get_title_display
912 3a72a5d4 Kostas Papadimitriou
        try:
913 3a72a5d4 Kostas Papadimitriou
            if self.settings.get_user_title_display:
914 3a72a5d4 Kostas Papadimitriou
                title_tpl = self.settings.get_user_title_display
915 3a72a5d4 Kostas Papadimitriou
        except Exception, e:
916 3a72a5d4 Kostas Papadimitriou
            pass
917 c630fee6 Kostas Papadimitriou
        try:
918 c630fee6 Kostas Papadimitriou
          return title_tpl % self.__dict__
919 c630fee6 Kostas Papadimitriou
        except:
920 c630fee6 Kostas Papadimitriou
          return self.settings.get_title_display % self.__dict__
921 d2633501 Kostas Papadimitriou
922 d2633501 Kostas Papadimitriou
    def can_remove(self):
923 d2633501 Kostas Papadimitriou
        return self.user.can_remove_auth_provider(self.module)
924 d2633501 Kostas Papadimitriou
925 d2633501 Kostas Papadimitriou
    def delete(self, *args, **kwargs):
926 d2633501 Kostas Papadimitriou
        ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs)
927 5156e663 Kostas Papadimitriou
        if self.module == 'local':
928 5156e663 Kostas Papadimitriou
            self.user.set_unusable_password()
929 5156e663 Kostas Papadimitriou
            self.user.save()
930 d2633501 Kostas Papadimitriou
        return ret
931 d2633501 Kostas Papadimitriou
932 f432088a Kostas Papadimitriou
    def __repr__(self):
933 f432088a Kostas Papadimitriou
        return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
934 f432088a Kostas Papadimitriou
935 b778b6fa Kostas Papadimitriou
    def __unicode__(self):
936 b778b6fa Kostas Papadimitriou
        if self.identifier:
937 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.identifier)
938 b778b6fa Kostas Papadimitriou
        if self.auth_backend:
939 b778b6fa Kostas Papadimitriou
            return "%s:%s" % (self.module, self.auth_backend)
940 b778b6fa Kostas Papadimitriou
        return self.module
941 b778b6fa Kostas Papadimitriou
942 3a72a5d4 Kostas Papadimitriou
    def save(self, *args, **kwargs):
943 3a72a5d4 Kostas Papadimitriou
        self.info_data = json.dumps(self.info)
944 3a72a5d4 Kostas Papadimitriou
        return super(AstakosUserAuthProvider, self).save(*args, **kwargs)
945 b778b6fa Kostas Papadimitriou
946 d2633501 Kostas Papadimitriou
947 e1a80257 Sofia Papagiannaki
class ExtendedManager(models.Manager):
948 9a06d96f Olga Brani
    def _update_or_create(self, **kwargs):
949 9a06d96f Olga Brani
        assert kwargs, \
950 9a06d96f Olga Brani
            'update_or_create() must be passed at least one keyword argument'
951 9a06d96f Olga Brani
        obj, created = self.get_or_create(**kwargs)
952 9a06d96f Olga Brani
        defaults = kwargs.pop('defaults', {})
953 9a06d96f Olga Brani
        if created:
954 9a06d96f Olga Brani
            return obj, True, False
955 9a06d96f Olga Brani
        else:
956 9a06d96f Olga Brani
            try:
957 9a06d96f Olga Brani
                params = dict(
958 9a06d96f Olga Brani
                    [(k, v) for k, v in kwargs.items() if '__' not in k])
959 9a06d96f Olga Brani
                params.update(defaults)
960 9a06d96f Olga Brani
                for attr, val in params.items():
961 9a06d96f Olga Brani
                    if hasattr(obj, attr):
962 9a06d96f Olga Brani
                        setattr(obj, attr, val)
963 9a06d96f Olga Brani
                sid = transaction.savepoint()
964 9a06d96f Olga Brani
                obj.save(force_update=True)
965 9a06d96f Olga Brani
                transaction.savepoint_commit(sid)
966 9a06d96f Olga Brani
                return obj, False, True
967 9a06d96f Olga Brani
            except IntegrityError, e:
968 9a06d96f Olga Brani
                transaction.savepoint_rollback(sid)
969 9a06d96f Olga Brani
                try:
970 9a06d96f Olga Brani
                    return self.get(**kwargs), False, False
971 9a06d96f Olga Brani
                except self.model.DoesNotExist:
972 9a06d96f Olga Brani
                    raise e
973 9a06d96f Olga Brani
974 9a06d96f Olga Brani
    update_or_create = _update_or_create
975 5ce3ce4f Sofia Papagiannaki
976 8e45d6fd Sofia Papagiannaki
977 8e45d6fd Sofia Papagiannaki
class AstakosUserQuota(models.Model):
978 e1a80257 Sofia Papagiannaki
    objects = ExtendedManager()
979 9deadfcd Georgios D. Tsoukalas
    capacity = intDecimalField()
980 9deadfcd Georgios D. Tsoukalas
    quantity = intDecimalField(default=0)
981 9deadfcd Georgios D. Tsoukalas
    export_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
982 9deadfcd Georgios D. Tsoukalas
    import_limit = intDecimalField(default=QH_PRACTICALLY_INFINITE)
983 8e45d6fd Sofia Papagiannaki
    resource = models.ForeignKey(Resource)
984 8e45d6fd Sofia Papagiannaki
    user = models.ForeignKey(AstakosUser)
985 5ce3ce4f Sofia Papagiannaki
986 8e45d6fd Sofia Papagiannaki
    class Meta:
987 8e45d6fd Sofia Papagiannaki
        unique_together = ("resource", "user")
988 09e7393c Sofia Papagiannaki
989 8989f446 Sofia Papagiannaki
    def quota_values(self):
990 0514bcc7 Giorgos Korfiatis
        return QuotaValues(
991 0514bcc7 Giorgos Korfiatis
            quantity = self.quantity,
992 0514bcc7 Giorgos Korfiatis
            capacity = self.capacity,
993 0514bcc7 Giorgos Korfiatis
            import_limit = self.import_limit,
994 0514bcc7 Giorgos Korfiatis
            export_limit = self.export_limit)
995 0514bcc7 Giorgos Korfiatis
996 5ce3ce4f Sofia Papagiannaki
997 270dd48d Sofia Papagiannaki
class ApprovalTerms(models.Model):
998 270dd48d Sofia Papagiannaki
    """
999 270dd48d Sofia Papagiannaki
    Model for approval terms
1000 270dd48d Sofia Papagiannaki
    """
1001 6c736ed7 Kostas Papadimitriou
1002 5ce3ce4f Sofia Papagiannaki
    date = models.DateTimeField(
1003 3c638f72 Giorgos Korfiatis
        _('Issue date'), db_index=True, auto_now_add=True)
1004 e1a80257 Sofia Papagiannaki
    location = models.CharField(_('Terms location'), max_length=255)
1005 270dd48d Sofia Papagiannaki
1006 5ce3ce4f Sofia Papagiannaki
1007 64cd4730 Antony Chazapis
class Invitation(models.Model):
1008 890b0eaf Sofia Papagiannaki
    """
1009 890b0eaf Sofia Papagiannaki
    Model for registring invitations
1010 890b0eaf Sofia Papagiannaki
    """
1011 0905ccd2 Sofia Papagiannaki
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
1012 64cd4730 Antony Chazapis
                                null=True)
1013 e1a80257 Sofia Papagiannaki
    realname = models.CharField(_('Real name'), max_length=255)
1014 e1a80257 Sofia Papagiannaki
    username = models.CharField(_('Unique ID'), max_length=255, unique=True)
1015 e1a80257 Sofia Papagiannaki
    code = models.BigIntegerField(_('Invitation code'), db_index=True)
1016 e1a80257 Sofia Papagiannaki
    is_consumed = models.BooleanField(_('Consumed?'), default=False)
1017 e1a80257 Sofia Papagiannaki
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
1018 e1a80257 Sofia Papagiannaki
    consumed = models.DateTimeField(_('Consumption date'), null=True, blank=True)
1019 5ce3ce4f Sofia Papagiannaki
1020 18ffbee1 Sofia Papagiannaki
    def __init__(self, *args, **kwargs):
1021 18ffbee1 Sofia Papagiannaki
        super(Invitation, self).__init__(*args, **kwargs)
1022 8f5a3a06 Sofia Papagiannaki
        if not self.id:
1023 8f5a3a06 Sofia Papagiannaki
            self.code = _generate_invitation_code()
1024 5ce3ce4f Sofia Papagiannaki
1025 64cd4730 Antony Chazapis
    def consume(self):
1026 64cd4730 Antony Chazapis
        self.is_consumed = True
1027 64cd4730 Antony Chazapis
        self.consumed = datetime.now()
1028 64cd4730 Antony Chazapis
        self.save()
1029 6c736ed7 Kostas Papadimitriou
1030 64cd4730 Antony Chazapis
    def __unicode__(self):
1031 0905ccd2 Sofia Papagiannaki
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
1032 9c01d9d1 Sofia Papagiannaki
1033 49790d9d Sofia Papagiannaki
1034 49790d9d Sofia Papagiannaki
class EmailChangeManager(models.Manager):
1035 34a76cdb Kostas Papadimitriou
1036 49790d9d Sofia Papagiannaki
    @transaction.commit_on_success
1037 49790d9d Sofia Papagiannaki
    def change_email(self, activation_key):
1038 49790d9d Sofia Papagiannaki
        """
1039 49790d9d Sofia Papagiannaki
        Validate an activation key and change the corresponding
1040 49790d9d Sofia Papagiannaki
        ``User`` if valid.
1041 49790d9d Sofia Papagiannaki

1042 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1043 49790d9d Sofia Papagiannaki
        after activating.
1044 49790d9d Sofia Papagiannaki

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

1047 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1048 49790d9d Sofia Papagiannaki
        return ``None``.
1049 49790d9d Sofia Papagiannaki

1050 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1051 49790d9d Sofia Papagiannaki

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

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