Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25.3 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, membership, 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
    if membership != ProjectMembership.REQUESTED:
518
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
519
        raise PermissionDenied(m)
520

    
521
def do_accept_membership(project_id, user, request_user=None):
522
    project = get_project_for_update(project_id)
523
    membership = get_membership_for_update(project, user)
524
    do_accept_membership_checks(project, membership, request_user)
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, membership, request_user):
543
    checkAllowed(project, request_user)
544
    checkAlive(project)
545

    
546
    if membership != ProjectMembership.REQUESTED:
547
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
548
        raise PermissionDenied(m)
549

    
550
def do_reject_membership(project_id, user, request_user=None):
551
    project = get_project_for_update(project_id)
552
    membership = get_membership_for_update(project, user)
553
    do_reject_membership_checks(project, membership, request_user)
554

    
555
    membership.reject()
556

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

    
559
    return membership
560

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

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

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

    
578
    if membership.state not in ProjectMembership.ACCEPTED_STATES:
579
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
580
        raise PermissionDenied(m)
581

    
582
def do_remove_membership(project_id, user, request_user=None):
583
    project = get_project_for_update(project_id)
584
    membership = get_membership_for_update(project, user)
585
    do_remove_membership_checks(project, membership, request_user)
586

    
587
    membership.remove()
588
    sync_projects()
589

    
590
    membership_change_notify(project, membership.person, 'removed')
591

    
592
    return membership
593

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

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

    
603
    membership.accept()
604
    sync_projects()
605

    
606
    # TODO send proper notification
607
    return membership
608

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

    
618
def do_leave_project_checks(project, membership):
619
    checkAlive(project)
620

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

    
625
    if membership.state not in ProjectMembership.ACCEPTED_STATES:
626
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
627
        raise PermissionDenied(m)
628

    
629
def do_leave_project(project_id, user_id):
630
    project = get_project_for_update(project_id)
631
    membership = get_membership_for_update(project, user_id)
632
    do_leave_project_checks(project, membership)
633

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

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

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

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

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

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

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

    
673
    kw['applicant'] = request_user
674

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

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

    
686
    application = models_submit_application(**kw)
687

    
688
    application_submit_notify(application)
689
    return application
690

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

    
695
    if application.state != ProjectApplication.PENDING:
696
        raise PermissionDenied()
697

    
698
    application.cancel()
699

    
700
def dismiss_application(application_id, request_user=None):
701
    application = get_application_for_update(application_id)
702
    checkAllowed(application, request_user)
703

    
704
    if application.state != ProjectApplication.DENIED:
705
        raise PermissionDenied()
706

    
707
    application.dismiss()
708

    
709
def deny_application(application_id):
710
    application = get_application_for_update(application_id)
711
    if application.state != ProjectApplication.PENDING:
712
        raise PermissionDenied()
713

    
714
    application.deny()
715
    application_deny_notify(application)
716

    
717
def approve_application(app):
718

    
719
    app_id = app if isinstance(app, int) else app.id
720

    
721
    try:
722
        objects = ProjectApplication.objects.select_for_update()
723
        application = objects.get(id=app_id)
724
    except ProjectApplication.DoesNotExist:
725
        raise PermissionDenied()
726

    
727
    application.approve()
728
    sync_projects()
729

    
730
    application_approve_notify(application)
731

    
732
def check_expiration(execute=False):
733
    objects = Project.objects
734
    expired = objects.expired_projects()
735
    if execute:
736
        for project in expired:
737
            terminate(project.id)
738

    
739
    return [project.expiration_info() for project in expired]
740

    
741
def terminate(project_id):
742
    project = get_project_for_update(project_id)
743
    checkAlive(project)
744

    
745
    project.terminate()
746
    sync_projects()
747

    
748
    project_termination_notify(project)
749

    
750
def suspend(project_id):
751
    project = get_project_by_id(project_id)
752
    checkAlive(project)
753

    
754
    project.suspend()
755
    sync_projects()
756

    
757
    project_suspension_notify(project)
758

    
759
def resume(project_id):
760
    project = get_project_for_update(project_id)
761

    
762
    if not project.is_suspended:
763
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
764
        raise PermissionDenied(m)
765

    
766
    project.resume()
767
    sync_projects()