Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (22.2 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
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
    trigger_sync, PendingMembershipError)
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
    project_termination_notify, project_suspension_notify)
75

    
76
import astakos.im.messages as astakos_messages
77

    
78
logger = logging.getLogger(__name__)
79

    
80

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

    
97

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

    
106
login = logged(login, '%s logged in.')
107
logout = logged(auth_logout, '%s logged out.')
108

    
109

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

114
    Raises SendVerificationError
115
    """
116
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
117
                                  quote(user.auth_token),
118
                                  quote(urljoin(BASEURL, reverse('index'))))
119
    message = render_to_string(template_name, {
120
                               'user': user,
121
                               'url': url,
122
                               'baseurl': BASEURL,
123
                               'site_name': SITENAME,
124
                               'support': DEFAULT_CONTACT_EMAIL})
125
    sender = settings.SERVER_EMAIL
126
    try:
127
        send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email])
128
    except (SMTPException, socket.error) as e:
129
        logger.exception(e)
130
        raise SendVerificationError()
131
    else:
132
        msg = 'Sent activation %s' % user.email
133
        logger.log(LOGGING_LEVEL, msg)
134

    
135

    
136
def send_activation(user, template_name='im/activation_email.txt'):
137
    send_verification(user, template_name)
138
    user.activation_sent = datetime.now()
139
    user.save()
140

    
141

    
142
def _send_admin_notification(template_name,
143
                             dictionary=None,
144
                             subject='alpha2 testing notification',):
145
    """
146
    Send notification email to settings.ADMINS.
147

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

    
166

    
167
def send_account_creation_notification(template_name, dictionary=None):
168
    user = dictionary.get('user', AnonymousUser())
169
    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
170
    return _send_admin_notification(template_name, dictionary, subject=subject)
171

    
172

    
173
def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
174
    """
175
    Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
176

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

    
197

    
198
def send_invitation(invitation, template_name='im/invitation.txt'):
199
    """
200
    Send invitation email.
201

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

    
224

    
225
def send_greeting(user, email_template_name='im/welcome_email.txt'):
226
    """
227
    Send welcome email.
228

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

    
248

    
249
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
250
    subject = _(FEEDBACK_EMAIL_SUBJECT)
251
    from_email = user.email
252
    recipient_list = [DEFAULT_CONTACT_EMAIL]
253
    content = render_to_string(email_template_name, {
254
        'message': msg,
255
        'data': data,
256
        'user': user})
257
    try:
258
        send_mail(subject, content, from_email, recipient_list)
259
    except (SMTPException, socket.error) as e:
260
        logger.exception(e)
261
        raise SendFeedbackError()
262
    else:
263
        msg = 'Sent feedback from %s' % user.email
264
        logger.log(LOGGING_LEVEL, msg)
265

    
266

    
267
def send_change_email(
268
    ec, request, email_template_name='registration/email_change_email.txt'):
269
    try:
270
        url = ec.get_url()
271
        url = request.build_absolute_uri(url)
272
        t = loader.get_template(email_template_name)
273
        c = {'url': url, 'site_name': SITENAME}
274
        from_email = settings.SERVER_EMAIL
275
        send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT),
276
                  t.render(Context(c)), from_email, [ec.new_email_address])
277
    except (SMTPException, socket.error) as e:
278
        logger.exception(e)
279
        raise ChangeEmailError()
280
    else:
281
        msg = 'Sent change email for %s' % ec.user.email
282
        logger.log(LOGGING_LEVEL, msg)
283

    
284

    
285
def activate(
286
    user,
287
    email_template_name='im/welcome_email.txt',
288
    helpdesk_email_template_name='im/helpdesk_notification.txt',
289
    verify_email=False):
290
    """
291
    Activates the specific user and sends email.
292

293
    Raises SendGreetingError, ValidationError
294
    """
295
    user.is_active = True
296
    user.email_verified = True
297
    if not user.activation_sent:
298
        user.activation_sent = datetime.now()
299
    user.save()
300
    send_helpdesk_notification(user, helpdesk_email_template_name)
301
    send_greeting(user, email_template_name)
302

    
303
def invite(inviter, email, realname):
304
    inv = Invitation(inviter=inviter, username=email, realname=realname)
305
    inv.save()
306
    send_invitation(inv)
307
    inviter.invitations = max(0, self.invitations - 1)
308
    inviter.save()
309

    
310
def switch_account_to_shibboleth(user, local_user,
311
                                 greeting_template_name='im/welcome_email.txt'):
312
    try:
313
        provider = user.provider
314
    except AttributeError:
315
        return
316
    else:
317
        if not provider == 'shibboleth':
318
            return
319
        user.delete()
320
        local_user.provider = 'shibboleth'
321
        local_user.third_party_identifier = user.third_party_identifier
322
        local_user.save()
323
        send_greeting(local_user, greeting_template_name)
324
        return local_user
325

    
326

    
327
class SendMailError(Exception):
328
    pass
329

    
330

    
331
class SendAdminNotificationError(SendMailError):
332
    def __init__(self):
333
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
334
        super(SendAdminNotificationError, self).__init__()
335

    
336

    
337
class SendVerificationError(SendMailError):
338
    def __init__(self):
339
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
340
        super(SendVerificationError, self).__init__()
341

    
342

    
343
class SendInvitationError(SendMailError):
344
    def __init__(self):
345
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
346
        super(SendInvitationError, self).__init__()
347

    
348

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

    
354

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

    
360

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

    
366

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

    
372

    
373
### PROJECT VIEWS ###
374

    
375
AUTO_ACCEPT_POLICY = 1
376
MODERATED_POLICY   = 2
377
CLOSED_POLICY      = 3
378

    
379
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
380

    
381
def get_project_by_application_id(project_application_id):
382
    try:
383
        return Project.objects.get(application__id=project_application_id)
384
    except Project.DoesNotExist:
385
        raise IOError(
386
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
387

    
388
def get_project_id_of_application_id(project_application_id):
389
    try:
390
        return Project.objects.get(application__id=project_application_id).id
391
    except Project.DoesNotExist:
392
        raise IOError(
393
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
394

    
395
def get_project_by_id(project_id):
396
    try:
397
        return Project.objects.get(id=project_id)
398
    except Project.DoesNotExist:
399
        raise IOError(
400
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
401

    
402
def get_project_for_update(project_id):
403
    try:
404
        return Project.objects.select_for_update().get(id=project_id)
405
    except Project.DoesNotExist:
406
        raise IOError(
407
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
408

    
409
def get_user_by_id(user_id):
410
    try:
411
        return AstakosUser.objects.get(id=user_id)
412
    except AstakosUser.DoesNotExist:
413
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
414

    
415
def create_membership(project, user):
416
    if isinstance(project, int):
417
        project = get_project_by_id(project)
418
    if isinstance(user, int):
419
        user = get_user_by_id(user)
420
    m = ProjectMembership(
421
        project=project,
422
        person=user,
423
        request_date=datetime.now())
424
    try:
425
        m.save()
426
    except IntegrityError, e:
427
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
428
    else:
429
        return m
430

    
431
def get_membership_for_update(project, user):
432
    if isinstance(project, int):
433
        project = get_project_by_id(project)
434
    if isinstance(user, int):
435
        user = get_user_by_id(user)
436
    try:
437
        sfu = ProjectMembership.objects.select_for_update()
438
        m = sfu.get(project=project, person=user)
439
        if m.is_pending:
440
            raise PendingMembershipError()
441
        return m
442
    except ProjectMembership.DoesNotExist:
443
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
444

    
445
def checkAllowed(project, request_user):
446
    if request_user and \
447
        (not project.application.owner == request_user and \
448
            not request_user.is_superuser):
449
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
450

    
451
def checkAlive(project):
452
    if not project.is_alive:
453
        raise PermissionDenied(
454
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
455

    
456
def accept_membership(project_application_id, user, request_user=None):
457
    """
458
        Raises:
459
            django.core.exceptions.PermissionDenied
460
            IOError
461
    """
462
    project_id = get_project_id_of_application_id(project_application_id)
463
    return do_accept_membership(project_id, user, request_user)
464

    
465
def do_accept_membership_checks(project, request_user):
466
    checkAllowed(project, request_user)
467
    checkAlive(project)
468

    
469
    join_policy = project.application.member_join_policy
470
    if join_policy == CLOSED_POLICY:
471
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
472

    
473
    if project.violates_members_limit(adding=1):
474
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
475

    
476
def do_accept_membership(project_id, user, request_user=None):
477
    project = get_project_for_update(project_id)
478
    do_accept_membership_checks(project, request_user)
479

    
480
    membership = get_membership_for_update(project, user)
481
    membership.accept()
482
    trigger_sync()
483

    
484
    membership_change_notify(project, membership.person, 'accepted')
485

    
486
    return membership
487

    
488
def reject_membership(project_application_id, user, request_user=None):
489
    """
490
        Raises:
491
            django.core.exceptions.PermissionDenied
492
            IOError
493
    """
494
    project_id = get_project_id_of_application_id(project_application_id)
495
    return do_reject_membership(project_id, user, request_user)
496

    
497
def do_reject_membership_checks(project, request_user):
498
    checkAllowed(project, request_user)
499
    checkAlive(project)
500

    
501
def do_reject_membership(project_id, user, request_user=None):
502
    project = get_project_for_update(project_id)
503
    do_reject_membership_checks(project, request_user)
504

    
505
    membership = get_membership_for_update(project, user)
506
    membership.reject()
507

    
508
    membership_change_notify(project, membership.person, 'rejected')
509

    
510
    return membership
511

    
512
def remove_membership(project_application_id, user, request_user=None):
513
    """
514
        Raises:
515
            django.core.exceptions.PermissionDenied
516
            IOError
517
    """
518
    project_id = get_project_id_of_application_id(project_application_id)
519
    return do_remove_membership(project_id, user, request_user)
520

    
521
def do_remove_membership_checks(project, membership, request_user=None):
522
    checkAllowed(project, request_user)
523
    checkAlive(project)
524

    
525
    leave_policy = project.application.member_leave_policy
526
    if leave_policy == CLOSED_POLICY:
527
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
528

    
529
def do_remove_membership(project_id, user, request_user=None):
530
    project = get_project_for_update(project_id)
531
    do_remove_membership_checks(project, request_user)
532

    
533
    membership = get_membership_for_update(project, user)
534
    membership.remove()
535
    trigger_sync()
536

    
537
    membership_change_notify(project, membership.person, 'removed')
538

    
539
    return membership
540

    
541
def enroll_member(project_application_id, user, request_user=None):
542
    project_id = get_project_id_of_application_id(project_application_id)
543
    return do_enroll_member(project_id, user, request_user)
544

    
545
def do_enroll_member(project_id, user, request_user=None):
546
    project = get_project_for_update(project_id)
547
    do_accept_membership_checks(project, request_user)
548

    
549
    membership = create_membership(project_id, user)
550
    membership.accept()
551
    trigger_sync()
552

    
553
    # TODO send proper notification
554
    return membership
555

    
556
def leave_project(project_application_id, user_id):
557
    """
558
        Raises:
559
            django.core.exceptions.PermissionDenied
560
            IOError
561
    """
562
    project_id = get_project_id_of_application_id(project_application_id)
563
    return do_leave_project(project_id, user_id)
564

    
565
def do_leave_project_checks(project):
566
    checkAlive(project)
567

    
568
    leave_policy = project.application.member_leave_policy
569
    if leave_policy == CLOSED_POLICY:
570
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
571

    
572
def do_leave_project(project_id, user_id):
573
    project = get_project_for_update(project_id)
574
    do_leave_project_checks(project)
575

    
576
    membership = get_membership_for_update(project, user_id)
577

    
578
    leave_policy = project.application.member_leave_policy
579
    if leave_policy == AUTO_ACCEPT_POLICY:
580
        membership.remove()
581
        trigger_sync()
582
    else:
583
        membership.leave_request_date = datetime.now()
584
        membership.save()
585
    return membership
586

    
587
def join_project(project_application_id, user_id):
588
    """
589
        Raises:
590
            django.core.exceptions.PermissionDenied
591
            IOError
592
    """
593
    project_id = get_project_id_of_application_id(project_application_id)
594
    return do_join_project(project_id, user_id)
595

    
596
def do_join_project_checks(project):
597
    checkAlive(project)
598

    
599
    join_policy = project.application.member_join_policy
600
    if join_policy == CLOSED_POLICY:
601
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
602

    
603
def do_join_project(project_id, user_id):
604
    project = get_project_for_update(project_id)
605
    do_join_project_checks(project)
606

    
607
    membership = create_membership(project, user_id)
608

    
609
    join_policy = project.application.member_join_policy
610
    if (join_policy == AUTO_ACCEPT_POLICY and
611
        not project.violates_members_limit(adding=1)):
612
        membership.accept()
613
        trigger_sync()
614
    return membership
615

    
616
def submit_application(kw, request_user=None):
617

    
618
    kw['applicant'] = request_user
619

    
620
    precursor_id = kw.get('precursor_application', None)
621
    if precursor_id is not None:
622
        sfu = ProjectApplication.objects.select_for_update()
623
        precursor = sfu.get(id=precursor_id)
624
        kw['precursor_application'] = precursor
625

    
626
        if request_user and \
627
            (not precursor.owner == request_user and \
628
                not request_user.is_superuser):
629
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
630

    
631
    application = models_submit_application(**kw)
632

    
633
    application_submit_notify(application)
634
    return application
635

    
636
def update_application(app_id, **kw):
637
    app = ProjectApplication.objects.get(id=app_id)
638
    app.id = None
639
    app.state = app.PENDING
640
    app.precursor_application_id = app_id
641
    app.issue_date = datetime.now()
642

    
643
    resource_policies = kw.pop('resource_policies', None)
644
    for k, v in kw.iteritems():
645
        setattr(app, k, v)
646
    app.save()
647
    app.resource_policies = resource_policies
648
    return app.id
649

    
650
def approve_application(app):
651

    
652
    app_id = app if isinstance(app, int) else app.id
653

    
654
    try:
655
        objects = ProjectApplication.objects.select_for_update()
656
        application = objects.get(id=app_id)
657
    except ProjectApplication.DoesNotExist:
658
        raise PermissionDenied()
659

    
660
    application.approve()
661
    trigger_sync()
662

    
663
    application_approve_notify(application)
664

    
665
def terminate(project_id):
666
    project = get_project_for_update(project_id)
667
    checkAlive(project)
668

    
669
    project.terminate()
670
    trigger_sync()
671

    
672
    project_termination_notify(project)
673

    
674
def suspend(project_id):
675
    project = get_project_by_id(project_id)
676
    project.last_approval_date = None
677
    project.save()
678
    trigger_sync()
679

    
680
    project_suspension_notify(project)