Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 21e0fdad

History | View | Annotate | Download (22.4 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
from astakos.im.endpoints.qh import qh_register_user
76

    
77
import astakos.im.messages as astakos_messages
78

    
79
logger = logging.getLogger(__name__)
80

    
81

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

    
98

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

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

    
110

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

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

    
136

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

    
142

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

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

    
167

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

    
173

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

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

    
198

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

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

    
225

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

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

    
249

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

    
267

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

    
285

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

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

    
305
def deactivate(user):
306
    user.is_active = False
307
    user.save()
308

    
309
def invite(inviter, email, realname):
310
    inv = Invitation(inviter=inviter, username=email, realname=realname)
311
    inv.save()
312
    send_invitation(inv)
313
    inviter.invitations = max(0, self.invitations - 1)
314
    inviter.save()
315

    
316
def switch_account_to_shibboleth(user, local_user,
317
                                 greeting_template_name='im/welcome_email.txt'):
318
    try:
319
        provider = user.provider
320
    except AttributeError:
321
        return
322
    else:
323
        if not provider == 'shibboleth':
324
            return
325
        user.delete()
326
        local_user.provider = 'shibboleth'
327
        local_user.third_party_identifier = user.third_party_identifier
328
        local_user.save()
329
        send_greeting(local_user, greeting_template_name)
330
        return local_user
331

    
332

    
333
class SendMailError(Exception):
334
    pass
335

    
336

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

    
342

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

    
348

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

    
354

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

    
360

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

    
366

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

    
372

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

    
378

    
379
### PROJECT VIEWS ###
380

    
381
AUTO_ACCEPT_POLICY = 1
382
MODERATED_POLICY   = 2
383
CLOSED_POLICY      = 3
384

    
385
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
386

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

    
394
def get_project_id_of_application_id(project_application_id):
395
    try:
396
        return Project.objects.get(application__id=project_application_id).id
397
    except Project.DoesNotExist:
398
        raise IOError(
399
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
400

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

    
408
def get_project_for_update(project_id):
409
    try:
410
        return Project.objects.select_for_update().get(id=project_id)
411
    except Project.DoesNotExist:
412
        raise IOError(
413
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
414

    
415
def get_user_by_id(user_id):
416
    try:
417
        return AstakosUser.objects.get(id=user_id)
418
    except AstakosUser.DoesNotExist:
419
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
420

    
421
def create_membership(project, user):
422
    if isinstance(project, int):
423
        project = get_project_by_id(project)
424
    if isinstance(user, int):
425
        user = get_user_by_id(user)
426
    m = ProjectMembership(
427
        project=project,
428
        person=user,
429
        request_date=datetime.now())
430
    try:
431
        m.save()
432
    except IntegrityError, e:
433
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
434
    else:
435
        return m
436

    
437
def get_membership_for_update(project, user):
438
    if isinstance(project, int):
439
        project = get_project_by_id(project)
440
    if isinstance(user, int):
441
        user = get_user_by_id(user)
442
    try:
443
        sfu = ProjectMembership.objects.select_for_update()
444
        m = sfu.get(project=project, person=user)
445
        if m.is_pending:
446
            raise PendingMembershipError()
447
        return m
448
    except ProjectMembership.DoesNotExist:
449
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
450

    
451
def checkAllowed(project, request_user):
452
    if request_user and \
453
        (not project.application.owner == request_user and \
454
            not request_user.is_superuser):
455
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
456

    
457
def checkAlive(project):
458
    if not project.is_alive:
459
        raise PermissionDenied(
460
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
461

    
462
def accept_membership(project_application_id, user, request_user=None):
463
    """
464
        Raises:
465
            django.core.exceptions.PermissionDenied
466
            IOError
467
    """
468
    project_id = get_project_id_of_application_id(project_application_id)
469
    return do_accept_membership(project_id, user, request_user)
470

    
471
def do_accept_membership_checks(project, request_user):
472
    checkAllowed(project, request_user)
473
    checkAlive(project)
474

    
475
    join_policy = project.application.member_join_policy
476
    if join_policy == CLOSED_POLICY:
477
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
478

    
479
    if project.violates_members_limit(adding=1):
480
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
481

    
482
def do_accept_membership(project_id, user, request_user=None):
483
    project = get_project_for_update(project_id)
484
    do_accept_membership_checks(project, request_user)
485

    
486
    membership = get_membership_for_update(project, user)
487
    membership.accept()
488
    trigger_sync()
489

    
490
    membership_change_notify(project, membership.person, 'accepted')
491

    
492
    return membership
493

    
494
def reject_membership(project_application_id, user, request_user=None):
495
    """
496
        Raises:
497
            django.core.exceptions.PermissionDenied
498
            IOError
499
    """
500
    project_id = get_project_id_of_application_id(project_application_id)
501
    return do_reject_membership(project_id, user, request_user)
502

    
503
def do_reject_membership_checks(project, request_user):
504
    checkAllowed(project, request_user)
505
    checkAlive(project)
506

    
507
def do_reject_membership(project_id, user, request_user=None):
508
    project = get_project_for_update(project_id)
509
    do_reject_membership_checks(project, request_user)
510

    
511
    membership = get_membership_for_update(project, user)
512
    membership.reject()
513

    
514
    membership_change_notify(project, membership.person, 'rejected')
515

    
516
    return membership
517

    
518
def remove_membership(project_application_id, user, request_user=None):
519
    """
520
        Raises:
521
            django.core.exceptions.PermissionDenied
522
            IOError
523
    """
524
    project_id = get_project_id_of_application_id(project_application_id)
525
    return do_remove_membership(project_id, user, request_user)
526

    
527
def do_remove_membership_checks(project, membership, request_user=None):
528
    checkAllowed(project, request_user)
529
    checkAlive(project)
530

    
531
    leave_policy = project.application.member_leave_policy
532
    if leave_policy == CLOSED_POLICY:
533
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
534

    
535
def do_remove_membership(project_id, user, request_user=None):
536
    project = get_project_for_update(project_id)
537
    do_remove_membership_checks(project, request_user)
538

    
539
    membership = get_membership_for_update(project, user)
540
    membership.remove()
541
    trigger_sync()
542

    
543
    membership_change_notify(project, membership.person, 'removed')
544

    
545
    return membership
546

    
547
def enroll_member(project_application_id, user, request_user=None):
548
    project_id = get_project_id_of_application_id(project_application_id)
549
    return do_enroll_member(project_id, user, request_user)
550

    
551
def do_enroll_member(project_id, user, request_user=None):
552
    project = get_project_for_update(project_id)
553
    do_accept_membership_checks(project, request_user)
554

    
555
    membership = create_membership(project_id, user)
556
    membership.accept()
557
    trigger_sync()
558

    
559
    # TODO send proper notification
560
    return membership
561

    
562
def leave_project(project_application_id, user_id):
563
    """
564
        Raises:
565
            django.core.exceptions.PermissionDenied
566
            IOError
567
    """
568
    project_id = get_project_id_of_application_id(project_application_id)
569
    return do_leave_project(project_id, user_id)
570

    
571
def do_leave_project_checks(project):
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
def do_leave_project(project_id, user_id):
579
    project = get_project_for_update(project_id)
580
    do_leave_project_checks(project)
581

    
582
    membership = get_membership_for_update(project, user_id)
583

    
584
    leave_policy = project.application.member_leave_policy
585
    if leave_policy == AUTO_ACCEPT_POLICY:
586
        membership.remove()
587
        trigger_sync()
588
    else:
589
        membership.leave_request_date = datetime.now()
590
        membership.save()
591
    return membership
592

    
593
def join_project(project_application_id, user_id):
594
    """
595
        Raises:
596
            django.core.exceptions.PermissionDenied
597
            IOError
598
    """
599
    project_id = get_project_id_of_application_id(project_application_id)
600
    return do_join_project(project_id, user_id)
601

    
602
def do_join_project_checks(project):
603
    checkAlive(project)
604

    
605
    join_policy = project.application.member_join_policy
606
    if join_policy == CLOSED_POLICY:
607
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
608

    
609
def do_join_project(project_id, user_id):
610
    project = get_project_for_update(project_id)
611
    do_join_project_checks(project)
612

    
613
    membership = create_membership(project, user_id)
614

    
615
    join_policy = project.application.member_join_policy
616
    if (join_policy == AUTO_ACCEPT_POLICY and
617
        not project.violates_members_limit(adding=1)):
618
        membership.accept()
619
        trigger_sync()
620
    return membership
621

    
622
def submit_application(kw, request_user=None):
623

    
624
    kw['applicant'] = request_user
625

    
626
    precursor_id = kw.get('precursor_application', None)
627
    if precursor_id is not None:
628
        sfu = ProjectApplication.objects.select_for_update()
629
        precursor = sfu.get(id=precursor_id)
630
        kw['precursor_application'] = precursor
631

    
632
        if request_user and \
633
            (not precursor.owner == request_user and \
634
                not request_user.is_superuser):
635
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
636

    
637
    application = models_submit_application(**kw)
638

    
639
    application_submit_notify(application)
640
    return application
641

    
642
def update_application(app_id, **kw):
643
    app = ProjectApplication.objects.get(id=app_id)
644
    app.id = None
645
    app.state = app.PENDING
646
    app.precursor_application_id = app_id
647
    app.issue_date = datetime.now()
648

    
649
    resource_policies = kw.pop('resource_policies', None)
650
    for k, v in kw.iteritems():
651
        setattr(app, k, v)
652
    app.save()
653
    app.resource_policies = resource_policies
654
    return app.id
655

    
656
def approve_application(app):
657

    
658
    app_id = app if isinstance(app, int) else app.id
659

    
660
    try:
661
        objects = ProjectApplication.objects.select_for_update()
662
        application = objects.get(id=app_id)
663
    except ProjectApplication.DoesNotExist:
664
        raise PermissionDenied()
665

    
666
    application.approve()
667
    trigger_sync()
668

    
669
    application_approve_notify(application)
670

    
671
def terminate(project_id):
672
    project = get_project_for_update(project_id)
673
    checkAlive(project)
674

    
675
    project.terminate()
676
    trigger_sync()
677

    
678
    project_termination_notify(project)
679

    
680
def suspend(project_id):
681
    project = get_project_by_id(project_id)
682
    project.last_approval_date = None
683
    project.save()
684
    trigger_sync()
685

    
686
    project_suspension_notify(project)