Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (28.4 kB)

1
# Copyright 2011, 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35
import socket
36

    
37
from django.utils.translation import ugettext as _
38
from django.template.loader import render_to_string
39
from django.core.mail import send_mail, get_connection
40
from django.core.urlresolvers import reverse
41
from django.template import Context, loader
42
from django.contrib.auth import (
43
    login as auth_login,
44
    logout as auth_logout)
45
from django.conf import settings
46
from django.contrib.auth.models import AnonymousUser
47
from django.core.exceptions import PermissionDenied
48
from django.db import IntegrityError
49
from django.http import Http404
50

    
51
from urllib import quote
52
from urlparse import urljoin
53
from smtplib import SMTPException
54
from datetime import datetime
55
from functools import wraps
56

    
57
import astakos.im.settings as astakos_settings
58
from astakos.im.settings import (
59
    DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
60
    VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
61
    GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
62
    INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
63
    EMAIL_CHANGE_EMAIL_SUBJECT,
64
    PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
65
    PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
66
    PROJECT_MEMBERSHIP_CHANGE_SUBJECT,
67
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES)
68
from astakos.im.notifications import build_notification, NotificationError
69
from astakos.im.models import (
70
    AstakosUser, Invitation, ProjectMembership, ProjectApplication, Project,
71
    UserSetting,
72
    PendingMembershipError, get_resource_names, new_chain)
73
from astakos.im.project_notif import (
74
    membership_change_notify, membership_enroll_notify,
75
    membership_request_notify, membership_leave_request_notify,
76
    application_submit_notify, application_approve_notify,
77
    application_deny_notify,
78
    project_termination_notify, project_suspension_notify)
79
from astakos.im.endpoints.qh import qh_register_user_with_quotas, qh_get_quota
80

    
81
import astakos.im.messages as astakos_messages
82

    
83
logger = logging.getLogger(__name__)
84

    
85

    
86
def logged(func, msg):
87
    @wraps(func)
88
    def with_logging(*args, **kwargs):
89
        email = ''
90
        user = None
91
        try:
92
            request = args[0]
93
            email = request.user.email
94
        except (KeyError, AttributeError), e:
95
            email = ''
96
        r = func(*args, **kwargs)
97
        if LOGGING_LEVEL:
98
            logger.log(LOGGING_LEVEL, msg % email)
99
        return r
100
    return with_logging
101

    
102

    
103
def login(request, user):
104
    auth_login(request, user)
105
    from astakos.im.models import SessionCatalog
106
    SessionCatalog(
107
        session_key=request.session.session_key,
108
        user=user
109
    ).save()
110

    
111
login = logged(login, '%s logged in.')
112
logout = logged(auth_logout, '%s logged out.')
113

    
114

    
115
def send_verification(user, template_name='im/activation_email.txt'):
116
    """
117
    Send email to user to verify his/her email and activate his/her account.
118

119
    Raises SendVerificationError
120
    """
121
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
122
                                  quote(user.auth_token),
123
                                  quote(urljoin(BASEURL, reverse('index'))))
124
    message = render_to_string(template_name, {
125
                               'user': user,
126
                               'url': url,
127
                               'baseurl': BASEURL,
128
                               'site_name': SITENAME,
129
                               'support': DEFAULT_CONTACT_EMAIL})
130
    sender = settings.SERVER_EMAIL
131
    try:
132
        send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email],
133
                  connection=get_connection())
134

    
135
    except (SMTPException, socket.error) as e:
136
        logger.exception(e)
137
        raise SendVerificationError()
138
    else:
139
        msg = 'Sent activation %s' % user.email
140
        logger.log(LOGGING_LEVEL, msg)
141

    
142

    
143
def send_activation(user, template_name='im/activation_email.txt'):
144
    send_verification(user, template_name)
145
    user.activation_sent = datetime.now()
146
    user.save()
147

    
148

    
149
def _send_admin_notification(template_name,
150
                             dictionary=None,
151
                             subject='alpha2 testing notification',):
152
    """
153
    Send notification email to settings.ADMINS.
154

155
    Raises SendNotificationError
156
    """
157
    if not settings.ADMINS:
158
        return
159
    dictionary = dictionary or {}
160
    message = render_to_string(template_name, dictionary)
161
    sender = settings.SERVER_EMAIL
162
    try:
163
        send_mail(subject, message, sender, [i[1] for i in settings.ADMINS],
164
                  connection=get_connection())
165
    except (SMTPException, socket.error) as e:
166
        logger.exception(e)
167
        raise SendNotificationError()
168
    else:
169
        msg = 'Sent admin notification for user %s' % dictionary.get('email',
170
                                                                     None)
171
        logger.log(LOGGING_LEVEL, msg)
172

    
173

    
174
def send_account_creation_notification(template_name, dictionary=None):
175
    user = dictionary.get('user', AnonymousUser())
176
    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
177
    return _send_admin_notification(template_name, dictionary, subject=subject)
178

    
179

    
180
def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
181
    """
182
    Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
183

184
    Raises SendNotificationError
185
    """
186
    if not DEFAULT_CONTACT_EMAIL:
187
        return
188
    message = render_to_string(
189
        template_name,
190
        {'user': user}
191
    )
192
    sender = settings.SERVER_EMAIL
193
    try:
194
        send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
195
                  message, sender, [DEFAULT_CONTACT_EMAIL],
196
                  connection=get_connection())
197
    except (SMTPException, socket.error) as e:
198
        logger.exception(e)
199
        raise SendNotificationError()
200
    else:
201
        msg = 'Sent helpdesk admin notification for %s' % user.email
202
        logger.log(LOGGING_LEVEL, msg)
203

    
204

    
205
def send_invitation(invitation, template_name='im/invitation.txt'):
206
    """
207
    Send invitation email.
208

209
    Raises SendInvitationError
210
    """
211
    subject = _(INVITATION_EMAIL_SUBJECT)
212
    url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
213
    message = render_to_string(template_name, {
214
                               'invitation': invitation,
215
                               'url': url,
216
                               'baseurl': BASEURL,
217
                               'site_name': SITENAME,
218
                               'support': DEFAULT_CONTACT_EMAIL})
219
    sender = settings.SERVER_EMAIL
220
    try:
221
        send_mail(subject, message, sender, [invitation.username],
222
                  connection=get_connection())
223
    except (SMTPException, socket.error) as e:
224
        logger.exception(e)
225
        raise SendInvitationError()
226
    else:
227
        msg = 'Sent invitation %s' % invitation
228
        logger.log(LOGGING_LEVEL, msg)
229
        inviter_invitations = invitation.inviter.invitations
230
        invitation.inviter.invitations = max(0, inviter_invitations - 1)
231
        invitation.inviter.save()
232

    
233

    
234
def send_greeting(user, email_template_name='im/welcome_email.txt'):
235
    """
236
    Send welcome email.
237

238
    Raises SMTPException, socket.error
239
    """
240
    subject = _(GREETING_EMAIL_SUBJECT)
241
    message = render_to_string(email_template_name, {
242
                               'user': user,
243
                               'url': urljoin(BASEURL, reverse('index')),
244
                               'baseurl': BASEURL,
245
                               'site_name': SITENAME,
246
                               'support': DEFAULT_CONTACT_EMAIL})
247
    sender = settings.SERVER_EMAIL
248
    try:
249
        send_mail(subject, message, sender, [user.email],
250
                  connection=get_connection())
251
    except (SMTPException, socket.error) as e:
252
        logger.exception(e)
253
        raise SendGreetingError()
254
    else:
255
        msg = 'Sent greeting %s' % user.email
256
        logger.log(LOGGING_LEVEL, msg)
257

    
258

    
259
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
260
    subject = _(FEEDBACK_EMAIL_SUBJECT)
261
    from_email = settings.SERVER_EMAIL
262
    recipient_list = [DEFAULT_CONTACT_EMAIL]
263
    content = render_to_string(email_template_name, {
264
        'message': msg,
265
        'data': data,
266
        'user': user})
267
    try:
268
        send_mail(subject, content, from_email, recipient_list,
269
                  connection=get_connection())
270
    except (SMTPException, socket.error) as e:
271
        logger.exception(e)
272
        raise SendFeedbackError()
273
    else:
274
        msg = 'Sent feedback from %s' % user.email
275
        logger.log(LOGGING_LEVEL, msg)
276

    
277

    
278
def send_change_email(
279
    ec, request, email_template_name='registration/email_change_email.txt'):
280
    try:
281
        url = ec.get_url()
282
        url = request.build_absolute_uri(url)
283
        t = loader.get_template(email_template_name)
284
        c = {'url': url, 'site_name': SITENAME,
285
             'support': DEFAULT_CONTACT_EMAIL, 'ec': ec}
286
        from_email = settings.SERVER_EMAIL
287
        send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT), t.render(Context(c)),
288
                  from_email, [ec.new_email_address],
289
                  connection=get_connection())
290
    except (SMTPException, socket.error) as e:
291
        logger.exception(e)
292
        raise ChangeEmailError()
293
    else:
294
        msg = 'Sent change email for %s' % ec.user.email
295
        logger.log(LOGGING_LEVEL, msg)
296

    
297

    
298
def activate(
299
    user,
300
    email_template_name='im/welcome_email.txt',
301
    helpdesk_email_template_name='im/helpdesk_notification.txt',
302
    verify_email=False):
303
    """
304
    Activates the specific user and sends email.
305

306
    Raises SendGreetingError, ValidationError
307
    """
308
    user.is_active = True
309
    user.email_verified = True
310
    if not user.activation_sent:
311
        user.activation_sent = datetime.now()
312
    user.save()
313
    qh_register_user_with_quotas(user)
314
    send_helpdesk_notification(user, helpdesk_email_template_name)
315
    send_greeting(user, email_template_name)
316

    
317
def deactivate(user):
318
    user.is_active = False
319
    user.save()
320

    
321
def invite(inviter, email, realname):
322
    inv = Invitation(inviter=inviter, username=email, realname=realname)
323
    inv.save()
324
    send_invitation(inv)
325
    inviter.invitations = max(0, inviter.invitations - 1)
326
    inviter.save()
327

    
328
def switch_account_to_shibboleth(user, local_user,
329
                                 greeting_template_name='im/welcome_email.txt'):
330
    try:
331
        provider = user.provider
332
    except AttributeError:
333
        return
334
    else:
335
        if not provider == 'shibboleth':
336
            return
337
        user.delete()
338
        local_user.provider = 'shibboleth'
339
        local_user.third_party_identifier = user.third_party_identifier
340
        local_user.save()
341
        send_greeting(local_user, greeting_template_name)
342
        return local_user
343

    
344

    
345
class SendMailError(Exception):
346
    pass
347

    
348

    
349
class SendAdminNotificationError(SendMailError):
350
    def __init__(self):
351
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
352
        super(SendAdminNotificationError, self).__init__()
353

    
354

    
355
class SendVerificationError(SendMailError):
356
    def __init__(self):
357
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
358
        super(SendVerificationError, self).__init__()
359

    
360

    
361
class SendInvitationError(SendMailError):
362
    def __init__(self):
363
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
364
        super(SendInvitationError, self).__init__()
365

    
366

    
367
class SendGreetingError(SendMailError):
368
    def __init__(self):
369
        self.message = _(astakos_messages.GREETING_SEND_ERR)
370
        super(SendGreetingError, self).__init__()
371

    
372

    
373
class SendFeedbackError(SendMailError):
374
    def __init__(self):
375
        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
376
        super(SendFeedbackError, self).__init__()
377

    
378

    
379
class ChangeEmailError(SendMailError):
380
    def __init__(self):
381
        self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
382
        super(ChangeEmailError, self).__init__()
383

    
384

    
385
class SendNotificationError(SendMailError):
386
    def __init__(self):
387
        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
388
        super(SendNotificationError, self).__init__()
389

    
390

    
391
def get_quota(users):
392
    resources = get_resource_names()
393
    return qh_get_quota(users, resources)
394

    
395

    
396
### PROJECT VIEWS ###
397

    
398
AUTO_ACCEPT_POLICY = 1
399
MODERATED_POLICY   = 2
400
CLOSED_POLICY      = 3
401

    
402
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
403

    
404
def get_project_by_application_id(project_application_id):
405
    try:
406
        return Project.objects.get(application__id=project_application_id)
407
    except Project.DoesNotExist:
408
        raise IOError(
409
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
410

    
411
def get_related_project_id(application_id):
412
    try:
413
        app = ProjectApplication.objects.get(id=application_id)
414
        chain = app.chain
415
        project = Project.objects.get(id=chain)
416
        return chain
417
    except:
418
        return None
419

    
420
def get_chain_of_application_id(application_id):
421
    try:
422
        app = ProjectApplication.objects.get(id=application_id)
423
        chain = app.chain
424
        return chain.chain
425
    except:
426
        return None
427

    
428
def get_project_by_id(project_id):
429
    try:
430
        return Project.objects.get(id=project_id)
431
    except Project.DoesNotExist:
432
        raise IOError(
433
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
434

    
435
def get_project_by_name(name):
436
    try:
437
        return Project.objects.get(name=name)
438
    except Project.DoesNotExist:
439
        raise IOError(
440
            _(astakos_messages.UNKNOWN_PROJECT_ID) % name)
441

    
442

    
443
def get_project_for_update(project_id):
444
    try:
445
        return Project.objects.get_for_update(id=project_id)
446
    except Project.DoesNotExist:
447
        raise IOError(
448
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
449

    
450
def get_application_for_update(application_id):
451
    try:
452
        return ProjectApplication.objects.get_for_update(id=application_id)
453
    except ProjectApplication.DoesNotExist:
454
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
455
        raise IOError(m)
456

    
457
def get_user_by_id(user_id):
458
    try:
459
        return AstakosUser.objects.get(id=user_id)
460
    except AstakosUser.DoesNotExist:
461
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
462

    
463
def get_user_by_uuid(uuid):
464
    try:
465
        return AstakosUser.objects.get(uuid=uuid)
466
    except AstakosUser.DoesNotExist:
467
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % uuid)
468

    
469
def create_membership(project, user):
470
    if isinstance(user, (int, long)):
471
        user = get_user_by_id(user)
472

    
473
    if not user.is_active:
474
        m = _(astakos_messages.ACCOUNT_NOT_ACTIVE)
475
        raise PermissionDenied(m)
476

    
477
    m, created = ProjectMembership.objects.get_or_create(
478
        project=project,
479
        person=user)
480

    
481
    if created:
482
        return m
483
    else:
484
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
485
        raise PermissionDenied(msg)
486

    
487

    
488
def get_membership_for_update(project, user):
489
    if isinstance(user, (int, long)):
490
        user = get_user_by_id(user)
491
    try:
492
        objs = ProjectMembership.objects
493
        m = objs.get_for_update(project=project, person=user)
494
        if m.is_pending:
495
            raise PendingMembershipError()
496
        return m
497
    except ProjectMembership.DoesNotExist:
498
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
499

    
500
def checkAllowed(entity, request_user):
501
    if isinstance(entity, Project):
502
        application = entity.application
503
    elif isinstance(entity, ProjectApplication):
504
        application = entity
505
    else:
506
        m = "%s not a Project nor a ProjectApplication" % (entity,)
507
        raise ValueError(m)
508

    
509
    if request_user and \
510
        (not application.owner == request_user and \
511
            not request_user.is_superuser):
512
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
513

    
514
def checkAlive(project):
515
    if not project.is_alive:
516
        raise PermissionDenied(
517
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
518

    
519
def accept_membership_checks(project, request_user):
520
    checkAllowed(project, request_user)
521
    checkAlive(project)
522

    
523
    join_policy = project.application.member_join_policy
524
    if join_policy == CLOSED_POLICY:
525
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
526

    
527
    if project.violates_members_limit(adding=1):
528
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
529

    
530
def accept_membership(project_id, user, request_user=None):
531
    project = get_project_for_update(project_id)
532
    accept_membership_checks(project, request_user)
533

    
534
    membership = get_membership_for_update(project, user)
535
    if not membership.can_accept():
536
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
537
        raise PermissionDenied(m)
538

    
539
    membership.accept()
540

    
541
    membership_change_notify(project, membership.person, 'accepted')
542

    
543
    return membership
544

    
545
def reject_membership_checks(project, request_user):
546
    checkAllowed(project, request_user)
547
    checkAlive(project)
548

    
549
def reject_membership(project_id, user, request_user=None):
550
    project = get_project_for_update(project_id)
551
    reject_membership_checks(project, request_user)
552
    membership = get_membership_for_update(project, user)
553
    if not membership.can_reject():
554
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
555
        raise PermissionDenied(m)
556

    
557
    membership.reject()
558

    
559
    membership_change_notify(project, membership.person, 'rejected')
560

    
561
    return membership
562

    
563
def cancel_membership_checks(project):
564
    checkAlive(project)
565

    
566
def cancel_membership(project_id, user_id):
567
    project = get_project_for_update(project_id)
568
    cancel_membership_checks(project)
569
    membership = get_membership_for_update(project, user_id)
570
    if not membership.can_cancel():
571
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
572
        raise PermissionDenied(m)
573

    
574
    membership.cancel()
575

    
576
def remove_membership_checks(project, request_user=None):
577
    checkAllowed(project, request_user)
578
    checkAlive(project)
579

    
580
    leave_policy = project.application.member_leave_policy
581
    if leave_policy == CLOSED_POLICY:
582
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
583

    
584
def remove_membership(project_id, user, request_user=None):
585
    project = get_project_for_update(project_id)
586
    remove_membership_checks(project, request_user)
587
    membership = get_membership_for_update(project, user)
588
    if not membership.can_remove():
589
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
590
        raise PermissionDenied(m)
591

    
592
    membership.remove()
593

    
594
    membership_change_notify(project, membership.person, 'removed')
595

    
596
    return membership
597

    
598
def enroll_member(project_id, user, request_user=None):
599
    project = get_project_for_update(project_id)
600
    accept_membership_checks(project, request_user)
601
    membership = create_membership(project, user)
602

    
603
    if not membership.can_accept():
604
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
605
        raise PermissionDenied(m)
606

    
607
    membership.accept()
608
    membership_enroll_notify(project, membership.person)
609

    
610
    return membership
611

    
612
def leave_project_checks(project):
613
    checkAlive(project)
614

    
615
    leave_policy = project.application.member_leave_policy
616
    if leave_policy == CLOSED_POLICY:
617
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
618

    
619
def can_leave_request(project, user):
620
    leave_policy = project.application.member_leave_policy
621
    if leave_policy == CLOSED_POLICY:
622
        return False
623
    m = user.get_membership(project)
624
    if m is None:
625
        return False
626
    if m.state != ProjectMembership.ACCEPTED:
627
        return False
628
    return True
629

    
630
def leave_project(project_id, user_id):
631
    project = get_project_for_update(project_id)
632
    leave_project_checks(project)
633
    membership = get_membership_for_update(project, user_id)
634
    if not membership.can_leave():
635
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
636
        raise PermissionDenied(m)
637

    
638
    auto_accepted = False
639
    leave_policy = project.application.member_leave_policy
640
    if leave_policy == AUTO_ACCEPT_POLICY:
641
        membership.remove()
642
        auto_accepted = True
643
    else:
644
        membership.leave_request()
645
        membership_leave_request_notify(project, membership.person)
646
    return auto_accepted
647

    
648
def join_project_checks(project):
649
    checkAlive(project)
650

    
651
    join_policy = project.application.member_join_policy
652
    if join_policy == CLOSED_POLICY:
653
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
654

    
655
def can_join_request(project, user):
656
    join_policy = project.application.member_join_policy
657
    if join_policy == CLOSED_POLICY:
658
        return False
659
    m = user.get_membership(project)
660
    if m:
661
        return False
662
    return True
663

    
664
def join_project(project_id, user_id):
665
    project = get_project_for_update(project_id)
666
    join_project_checks(project)
667
    membership = create_membership(project, user_id)
668

    
669
    auto_accepted = False
670
    join_policy = project.application.member_join_policy
671
    if (join_policy == AUTO_ACCEPT_POLICY and
672
        not project.violates_members_limit(adding=1)):
673
        membership.accept()
674
        auto_accepted = True
675
    else:
676
        membership_request_notify(project, membership.person)
677

    
678
    return auto_accepted
679

    
680
def submit_application(kw, request_user=None):
681

    
682
    kw['applicant'] = request_user
683
    resource_policies = kw.pop('resource_policies', None)
684

    
685
    precursor = None
686
    precursor_id = kw.get('precursor_application', None)
687
    if precursor_id is not None:
688
        objs = ProjectApplication.objects
689
        precursor = objs.get_for_update(id=precursor_id)
690
        kw['precursor_application'] = precursor
691

    
692
        if (request_user and
693
            (not precursor.owner == request_user and
694
             not request_user.is_superuser
695
             and not request_user.is_project_admin())):
696
            m = _(astakos_messages.NOT_ALLOWED)
697
            raise PermissionDenied(m)
698

    
699
    reached, limit = reached_pending_application_limit(request_user.id, precursor)
700
    if reached:
701
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
702
        raise PermissionDenied(m)
703

    
704
    application = ProjectApplication(**kw)
705

    
706
    if precursor is None:
707
        application.chain = new_chain()
708
    else:
709
        chain = precursor.chain
710
        application.chain = chain
711
        objs = ProjectApplication.objects
712
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
713
        pending = q.select_for_update()
714
        for app in pending:
715
            app.state = ProjectApplication.REPLACED
716
            app.save()
717

    
718
    application.save()
719
    application.resource_policies = resource_policies
720
    application_submit_notify(application)
721
    return application
722

    
723
def cancel_application(application_id, request_user=None):
724
    application = get_application_for_update(application_id)
725
    checkAllowed(application, request_user)
726

    
727
    if not application.can_cancel():
728
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
729
                application.id, application.state_display()))
730
        raise PermissionDenied(m)
731

    
732
    application.cancel()
733

    
734
def dismiss_application(application_id, request_user=None):
735
    application = get_application_for_update(application_id)
736
    checkAllowed(application, request_user)
737

    
738
    if not application.can_dismiss():
739
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
740
                application.id, application.state_display()))
741
        raise PermissionDenied(m)
742

    
743
    application.dismiss()
744

    
745
def deny_application(application_id):
746
    application = get_application_for_update(application_id)
747

    
748
    if not application.can_deny():
749
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
750
                application.id, application.state_display()))
751
        raise PermissionDenied(m)
752

    
753
    application.deny()
754
    application_deny_notify(application)
755

    
756
def approve_application(app_id):
757

    
758
    try:
759
        objects = ProjectApplication.objects
760
        application = objects.get_for_update(id=app_id)
761
    except ProjectApplication.DoesNotExist:
762
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
763
        raise PermissionDenied(m)
764

    
765
    if not application.can_approve():
766
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
767
                application.id, application.state_display()))
768
        raise PermissionDenied(m)
769

    
770
    application.approve()
771
    application_approve_notify(application)
772

    
773
def check_expiration(execute=False):
774
    objects = Project.objects
775
    expired = objects.expired_projects()
776
    if execute:
777
        for project in expired:
778
            terminate(project.id)
779

    
780
    return [project.expiration_info() for project in expired]
781

    
782
def terminate(project_id):
783
    project = get_project_for_update(project_id)
784
    checkAlive(project)
785

    
786
    project.terminate()
787

    
788
    project_termination_notify(project)
789

    
790
def suspend(project_id):
791
    project = get_project_by_id(project_id)
792
    checkAlive(project)
793

    
794
    project.suspend()
795

    
796
    project_suspension_notify(project)
797

    
798
def resume(project_id):
799
    project = get_project_for_update(project_id)
800

    
801
    if not project.is_suspended:
802
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
803
        raise PermissionDenied(m)
804

    
805
    project.resume()
806

    
807
def get_by_chain_or_404(chain_id):
808
    try:
809
        project = Project.objects.get(id=chain_id)
810
        application = project.application
811
        return project, application
812
    except:
813
        application = ProjectApplication.objects.latest_of_chain(chain_id)
814
        if application is None:
815
            raise Http404
816
        else:
817
            return None, application
818

    
819

    
820
def get_user_setting(user_id, key):
821
    try:
822
        setting = UserSetting.objects.get(
823
            user=user_id, setting=key)
824
        return setting.value
825
    except UserSetting.DoesNotExist:
826
        return getattr(astakos_settings, key)
827

    
828

    
829
def set_user_setting(user_id, key, value):
830
    try:
831
        setting = UserSetting.objects.get_for_update(
832
            user=user_id, setting=key)
833
    except UserSetting.DoesNotExist:
834
        setting = UserSetting(user_id=user_id, setting=key)
835
    setting.value = value
836
    setting.save()
837

    
838

    
839
def unset_user_setting(user_id, key):
840
    UserSetting.objects.filter(user=user_id, setting=key).delete()
841

    
842

    
843
PENDING_APPLICATION_LIMIT_SETTING = 'PENDING_APPLICATION_LIMIT'
844

    
845
def get_pending_application_limit(user_id):
846
    key = PENDING_APPLICATION_LIMIT_SETTING
847
    return get_user_setting(user_id, key)
848

    
849

    
850
def set_pending_application_limit(user_id, value):
851
    key = PENDING_APPLICATION_LIMIT_SETTING
852
    return set_user_setting(user_id, key, value)
853

    
854

    
855
def unset_pending_application_limit(user_id):
856
    key = PENDING_APPLICATION_LIMIT_SETTING
857
    return unset_user_setting(user_id, key)
858

    
859

    
860
def _reached_pending_application_limit(user_id):
861
    limit = get_pending_application_limit(user_id)
862

    
863
    PENDING = ProjectApplication.PENDING
864
    pending = ProjectApplication.objects.filter(
865
        applicant__id=user_id, state=PENDING).count()
866

    
867
    return pending >= limit, limit
868

    
869

    
870
def reached_pending_application_limit(user_id, precursor=None):
871
    reached, limit = _reached_pending_application_limit(user_id)
872

    
873
    if precursor is None:
874
        return reached, limit
875

    
876
    chain = precursor.chain
877
    objs = ProjectApplication.objects
878
    q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
879
    has_pending = q.exists()
880

    
881
    if not has_pending:
882
        return reached, limit
883

    
884
    return False, limit