Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 7549d16f

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

1097 49790d9d Sofia Papagiannaki
        If the key is valid and has not expired, return the ``User``
1098 49790d9d Sofia Papagiannaki
        after activating.
1099 49790d9d Sofia Papagiannaki

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

1102 49790d9d Sofia Papagiannaki
        If the key is valid but the ``User`` is already active,
1103 49790d9d Sofia Papagiannaki
        return ``None``.
1104 49790d9d Sofia Papagiannaki

1105 49790d9d Sofia Papagiannaki
        After successful email change the activation record is deleted.
1106 49790d9d Sofia Papagiannaki

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

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