Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 01bdbd17

History | View | Annotate | Download (25.7 kB)

1
# Copyright 2011 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

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

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

    
78
import astakos.im.messages as astakos_messages
79

    
80
logger = logging.getLogger(__name__)
81

    
82

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

    
99

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

    
108
login = logged(login, '%s logged in.')
109
logout = logged(auth_logout, '%s logged out.')
110

    
111

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

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

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

    
139

    
140
def send_activation(user, template_name='im/activation_email.txt'):
141
    send_verification(user, template_name)
142
    user.activation_sent = datetime.now()
143
    user.save()
144

    
145

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

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

    
170

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

    
176

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

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

    
201

    
202
def send_invitation(invitation, template_name='im/invitation.txt'):
203
    """
204
    Send invitation email.
205

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

    
230

    
231
def send_greeting(user, email_template_name='im/welcome_email.txt'):
232
    """
233
    Send welcome email.
234

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

    
255

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

    
274

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

    
293

    
294
def activate(
295
    user,
296
    email_template_name='im/welcome_email.txt',
297
    helpdesk_email_template_name='im/helpdesk_notification.txt',
298
    verify_email=False):
299
    """
300
    Activates the specific user and sends email.
301

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

    
313
def deactivate(user):
314
    user.is_active = False
315
    user.save()
316

    
317
def invite(inviter, email, realname):
318
    inv = Invitation(inviter=inviter, username=email, realname=realname)
319
    inv.save()
320
    send_invitation(inv)
321
    inviter.invitations = max(0, self.invitations - 1)
322
    inviter.save()
323

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

    
340

    
341
class SendMailError(Exception):
342
    pass
343

    
344

    
345
class SendAdminNotificationError(SendMailError):
346
    def __init__(self):
347
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
348
        super(SendAdminNotificationError, self).__init__()
349

    
350

    
351
class SendVerificationError(SendMailError):
352
    def __init__(self):
353
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
354
        super(SendVerificationError, self).__init__()
355

    
356

    
357
class SendInvitationError(SendMailError):
358
    def __init__(self):
359
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
360
        super(SendInvitationError, self).__init__()
361

    
362

    
363
class SendGreetingError(SendMailError):
364
    def __init__(self):
365
        self.message = _(astakos_messages.GREETING_SEND_ERR)
366
        super(SendGreetingError, self).__init__()
367

    
368

    
369
class SendFeedbackError(SendMailError):
370
    def __init__(self):
371
        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
372
        super(SendFeedbackError, self).__init__()
373

    
374

    
375
class ChangeEmailError(SendMailError):
376
    def __init__(self):
377
        self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
378
        super(ChangeEmailError, self).__init__()
379

    
380

    
381
class SendNotificationError(SendMailError):
382
    def __init__(self):
383
        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
384
        super(SendNotificationError, self).__init__()
385

    
386

    
387
def get_quota(users):
388
    resources = get_resource_names()
389
    return qh_get_quota(users, resources)
390

    
391

    
392
### PROJECT VIEWS ###
393

    
394
AUTO_ACCEPT_POLICY = 1
395
MODERATED_POLICY   = 2
396
CLOSED_POLICY      = 3
397

    
398
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
399

    
400
def get_project_by_application_id(project_application_id):
401
    try:
402
        return Project.objects.get(application__id=project_application_id)
403
    except Project.DoesNotExist:
404
        raise IOError(
405
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
406

    
407
def get_project_id_of_application_id(project_application_id):
408
    try:
409
        return Project.objects.get(application__id=project_application_id).id
410
    except Project.DoesNotExist:
411
        raise IOError(
412
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
413

    
414
def get_project_by_id(project_id):
415
    try:
416
        return Project.objects.get(id=project_id)
417
    except Project.DoesNotExist:
418
        raise IOError(
419
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
420

    
421
def get_project_for_update(project_id):
422
    try:
423
        return Project.objects.select_for_update().get(id=project_id)
424
    except Project.DoesNotExist:
425
        raise IOError(
426
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
427

    
428
def get_application_for_update(application_id):
429
    try:
430
        objects = ProjectApplication.objects.select_for_update()
431
        return objects.get(id=application_id)
432
    except ProjectApplication.DoesNotExist:
433
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
434
        raise IOError(m)
435

    
436
def get_user_by_id(user_id):
437
    try:
438
        return AstakosUser.objects.get(id=user_id)
439
    except AstakosUser.DoesNotExist:
440
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
441

    
442
def get_user_by_uuid(uuid):
443
    try:
444
        return AstakosUser.objects.get(uuid=uuid)
445
    except AstakosUser.DoesNotExist:
446
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
447

    
448
def create_membership(project, user):
449
    if isinstance(project, int):
450
        project = get_project_by_id(project)
451
    if isinstance(user, int):
452
        user = get_user_by_id(user)
453
    m = ProjectMembership(
454
        project=project,
455
        person=user,
456
        request_date=datetime.now())
457
    try:
458
        m.save()
459
    except IntegrityError, e:
460
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
461
    else:
462
        return m
463

    
464
def get_membership_for_update(project, user):
465
    if isinstance(project, int):
466
        project = get_project_by_id(project)
467
    if isinstance(user, int):
468
        user = get_user_by_id(user)
469
    try:
470
        sfu = ProjectMembership.objects.select_for_update()
471
        m = sfu.get(project=project, person=user)
472
        if m.is_pending:
473
            raise PendingMembershipError()
474
        return m
475
    except ProjectMembership.DoesNotExist:
476
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
477

    
478
def checkAllowed(entity, request_user):
479
    if isinstance(entity, Project):
480
        application = entity.application
481
    elif isinstance(entity, ProjectApplication):
482
        application = entity
483
    else:
484
        m = "%s not a Project nor a ProjectApplication" % (entity,)
485
        raise ValueError(m)
486

    
487
    if request_user and \
488
        (not application.owner == request_user and \
489
            not request_user.is_superuser):
490
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
491

    
492
def checkAlive(project):
493
    if not project.is_alive:
494
        raise PermissionDenied(
495
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
496

    
497
def accept_membership(project_application_id, user, request_user=None):
498
    """
499
        Raises:
500
            django.core.exceptions.PermissionDenied
501
            IOError
502
    """
503
    project_id = get_project_id_of_application_id(project_application_id)
504
    return do_accept_membership(project_id, user, request_user)
505

    
506
def do_accept_membership_checks(project, request_user):
507
    checkAllowed(project, request_user)
508
    checkAlive(project)
509

    
510
    join_policy = project.application.member_join_policy
511
    if join_policy == CLOSED_POLICY:
512
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
513

    
514
    if project.violates_members_limit(adding=1):
515
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
516

    
517
def do_accept_membership(project_id, user, request_user=None):
518
    project = get_project_for_update(project_id)
519
    do_accept_membership_checks(project, request_user)
520

    
521
    membership = get_membership_for_update(project, user)
522
    if not membership.can_accept():
523
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
524
        raise PermissionDenied(m)
525

    
526
    membership.accept()
527
    sync_projects()
528

    
529
    membership_change_notify(project, membership.person, 'accepted')
530

    
531
    return membership
532

    
533
def reject_membership(project_application_id, user, request_user=None):
534
    """
535
        Raises:
536
            django.core.exceptions.PermissionDenied
537
            IOError
538
    """
539
    project_id = get_project_id_of_application_id(project_application_id)
540
    return do_reject_membership(project_id, user, request_user)
541

    
542
def do_reject_membership_checks(project, request_user):
543
    checkAllowed(project, request_user)
544
    checkAlive(project)
545

    
546
def do_reject_membership(project_id, user, request_user=None):
547
    project = get_project_for_update(project_id)
548
    do_reject_membership_checks(project, request_user)
549
    membership = get_membership_for_update(project, user)
550
    if not membership.can_reject():
551
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
552
        raise PermissionDenied(m)
553

    
554
    membership.reject()
555

    
556
    membership_change_notify(project, membership.person, 'rejected')
557

    
558
    return membership
559

    
560
def remove_membership(project_application_id, user, request_user=None):
561
    """
562
        Raises:
563
            django.core.exceptions.PermissionDenied
564
            IOError
565
    """
566
    project_id = get_project_id_of_application_id(project_application_id)
567
    return do_remove_membership(project_id, user, request_user)
568

    
569
def do_remove_membership_checks(project, request_user=None):
570
    checkAllowed(project, request_user)
571
    checkAlive(project)
572

    
573
    leave_policy = project.application.member_leave_policy
574
    if leave_policy == CLOSED_POLICY:
575
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
576

    
577
def do_remove_membership(project_id, user, request_user=None):
578
    project = get_project_for_update(project_id)
579
    do_remove_membership_checks(project, request_user)
580
    membership = get_membership_for_update(project, user)
581
    if not membership.can_remove():
582
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
583
        raise PermissionDenied(m)
584

    
585
    membership.remove()
586
    sync_projects()
587

    
588
    membership_change_notify(project, membership.person, 'removed')
589

    
590
    return membership
591

    
592
def enroll_member(project_application_id, user, request_user=None):
593
    project_id = get_project_id_of_application_id(project_application_id)
594
    return do_enroll_member(project_id, user, request_user)
595

    
596
def do_enroll_member(project_id, user, request_user=None):
597
    project = get_project_for_update(project_id)
598
    do_accept_membership_checks(project, request_user)
599
    membership = create_membership(project_id, user)
600

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

    
605
    membership.accept()
606
    sync_projects()
607

    
608
    # TODO send proper notification
609
    return membership
610

    
611
def leave_project(project_application_id, user_id):
612
    """
613
        Raises:
614
            django.core.exceptions.PermissionDenied
615
            IOError
616
    """
617
    project_id = get_project_id_of_application_id(project_application_id)
618
    return do_leave_project(project_id, user_id)
619

    
620
def do_leave_project_checks(project):
621
    checkAlive(project)
622

    
623
    leave_policy = project.application.member_leave_policy
624
    if leave_policy == CLOSED_POLICY:
625
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
626

    
627
def do_leave_project(project_id, user_id):
628
    project = get_project_for_update(project_id)
629
    do_leave_project_checks(project)
630
    membership = get_membership_for_update(project, user_id)
631
    if not membership.can_leave():
632
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
633
        raise PermissionDenied(m)
634

    
635
    leave_policy = project.application.member_leave_policy
636
    if leave_policy == AUTO_ACCEPT_POLICY:
637
        membership.remove()
638
        sync_projects()
639
    else:
640
        membership.leave_request_date = datetime.now()
641
        membership.save()
642
    return membership
643

    
644
def join_project(project_application_id, user_id):
645
    """
646
        Raises:
647
            django.core.exceptions.PermissionDenied
648
            IOError
649
    """
650
    project_id = get_project_id_of_application_id(project_application_id)
651
    return do_join_project(project_id, user_id)
652

    
653
def do_join_project_checks(project):
654
    checkAlive(project)
655

    
656
    join_policy = project.application.member_join_policy
657
    if join_policy == CLOSED_POLICY:
658
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
659

    
660
def do_join_project(project_id, user_id):
661
    project = get_project_for_update(project_id)
662
    do_join_project_checks(project)
663
    membership = create_membership(project, user_id)
664

    
665
    join_policy = project.application.member_join_policy
666
    if (join_policy == AUTO_ACCEPT_POLICY and
667
        not project.violates_members_limit(adding=1)):
668
        membership.accept()
669
        sync_projects()
670
    return membership
671

    
672
def submit_application(kw, request_user=None):
673

    
674
    kw['applicant'] = request_user
675

    
676
    precursor_id = kw.get('precursor_application', None)
677
    if precursor_id is not None:
678
        sfu = ProjectApplication.objects.select_for_update()
679
        precursor = sfu.get(id=precursor_id)
680
        kw['precursor_application'] = precursor
681

    
682
        if request_user and \
683
            (not precursor.owner == request_user and \
684
                not request_user.is_superuser):
685
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
686

    
687
    application = models_submit_application(**kw)
688

    
689
    application_submit_notify(application)
690
    return application
691

    
692
def cancel_application(application_id, request_user=None):
693
    application = get_application_for_update(application_id)
694
    checkAllowed(application, request_user)
695

    
696
    if not application.can_cancel():
697
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
698
                application.id, application.state_display()))
699
        raise PermissionDenied(m)
700

    
701
    application.cancel()
702

    
703
def dismiss_application(application_id, request_user=None):
704
    application = get_application_for_update(application_id)
705
    checkAllowed(application, request_user)
706

    
707
    if not application.can_dismiss():
708
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
709
                application.id, application.state_display()))
710
        raise PermissionDenied(m)
711

    
712
    application.dismiss()
713

    
714
def deny_application(application_id):
715
    application = get_application_for_update(application_id)
716

    
717
    if not application.can_deny():
718
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
719
                application.id, application.state_display()))
720
        raise PermissionDenied(m)
721

    
722
    application.deny()
723
    application_deny_notify(application)
724

    
725
def approve_application(app):
726

    
727
    app_id = app if isinstance(app, int) else app.id
728

    
729
    try:
730
        objects = ProjectApplication.objects.select_for_update()
731
        application = objects.get(id=app_id)
732
    except ProjectApplication.DoesNotExist:
733
        raise PermissionDenied()
734

    
735
    if not application.can_approve():
736
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
737
                application.id, application.state_display()))
738
        raise PermissionDenied(m)
739

    
740
    application.approve()
741
    sync_projects()
742

    
743
    application_approve_notify(application)
744

    
745
def check_expiration(execute=False):
746
    objects = Project.objects
747
    expired = objects.expired_projects()
748
    if execute:
749
        for project in expired:
750
            terminate(project.id)
751

    
752
    return [project.expiration_info() for project in expired]
753

    
754
def terminate(project_id):
755
    project = get_project_for_update(project_id)
756
    checkAlive(project)
757

    
758
    project.terminate()
759
    sync_projects()
760

    
761
    project_termination_notify(project)
762

    
763
def suspend(project_id):
764
    project = get_project_by_id(project_id)
765
    checkAlive(project)
766

    
767
    project.suspend()
768
    sync_projects()
769

    
770
    project_suspension_notify(project)
771

    
772
def resume(project_id):
773
    project = get_project_for_update(project_id)
774

    
775
    if not project.is_suspended:
776
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
777
        raise PermissionDenied(m)
778

    
779
    project.resume()
780
    sync_projects()