Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 19eb3ee6

History | View | Annotate | Download (23 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
    application_deny_notify,
75
    project_termination_notify, project_suspension_notify)
76
from astakos.im.endpoints.qh import qh_register_user
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
    except (SMTPException, socket.error) as e:
131
        logger.exception(e)
132
        raise SendVerificationError()
133
    else:
134
        msg = 'Sent activation %s' % user.email
135
        logger.log(LOGGING_LEVEL, msg)
136

    
137

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

    
143

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

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

    
168

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

    
174

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

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

    
199

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

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

    
226

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

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

    
250

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

    
268

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

    
286

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

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

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

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

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

    
333

    
334
class SendMailError(Exception):
335
    pass
336

    
337

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

    
343

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

    
349

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

    
355

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

    
361

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

    
367

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

    
373

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

    
379

    
380
### PROJECT VIEWS ###
381

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

    
386
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
387

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

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

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

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

    
416
def get_application_for_update(application_id):
417
    try:
418
        objects = ProjectApplication.objects.select_for_update()
419
        return objects.get(id=application_id)
420
    except ProjectApplication.DoesNotExist:
421
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
422
        raise IOError(m)
423

    
424
def get_user_by_id(user_id):
425
    try:
426
        return AstakosUser.objects.get(id=user_id)
427
    except AstakosUser.DoesNotExist:
428
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
429

    
430
def create_membership(project, user):
431
    if isinstance(project, int):
432
        project = get_project_by_id(project)
433
    if isinstance(user, int):
434
        user = get_user_by_id(user)
435
    m = ProjectMembership(
436
        project=project,
437
        person=user,
438
        request_date=datetime.now())
439
    try:
440
        m.save()
441
    except IntegrityError, e:
442
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
443
    else:
444
        return m
445

    
446
def get_membership_for_update(project, user):
447
    if isinstance(project, int):
448
        project = get_project_by_id(project)
449
    if isinstance(user, int):
450
        user = get_user_by_id(user)
451
    try:
452
        sfu = ProjectMembership.objects.select_for_update()
453
        m = sfu.get(project=project, person=user)
454
        if m.is_pending:
455
            raise PendingMembershipError()
456
        return m
457
    except ProjectMembership.DoesNotExist:
458
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
459

    
460
def checkAllowed(project, request_user):
461
    if request_user and \
462
        (not project.application.owner == request_user and \
463
            not request_user.is_superuser):
464
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
465

    
466
def checkAlive(project):
467
    if not project.is_alive:
468
        raise PermissionDenied(
469
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
470

    
471
def accept_membership(project_application_id, user, request_user=None):
472
    """
473
        Raises:
474
            django.core.exceptions.PermissionDenied
475
            IOError
476
    """
477
    project_id = get_project_id_of_application_id(project_application_id)
478
    return do_accept_membership(project_id, user, request_user)
479

    
480
def do_accept_membership_checks(project, request_user):
481
    checkAllowed(project, request_user)
482
    checkAlive(project)
483

    
484
    join_policy = project.application.member_join_policy
485
    if join_policy == CLOSED_POLICY:
486
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
487

    
488
    if project.violates_members_limit(adding=1):
489
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
490

    
491
def do_accept_membership(project_id, user, request_user=None):
492
    project = get_project_for_update(project_id)
493
    do_accept_membership_checks(project, request_user)
494

    
495
    membership = get_membership_for_update(project, user)
496
    membership.accept()
497
    trigger_sync()
498

    
499
    membership_change_notify(project, membership.person, 'accepted')
500

    
501
    return membership
502

    
503
def reject_membership(project_application_id, user, request_user=None):
504
    """
505
        Raises:
506
            django.core.exceptions.PermissionDenied
507
            IOError
508
    """
509
    project_id = get_project_id_of_application_id(project_application_id)
510
    return do_reject_membership(project_id, user, request_user)
511

    
512
def do_reject_membership_checks(project, request_user):
513
    checkAllowed(project, request_user)
514
    checkAlive(project)
515

    
516
def do_reject_membership(project_id, user, request_user=None):
517
    project = get_project_for_update(project_id)
518
    do_reject_membership_checks(project, request_user)
519

    
520
    membership = get_membership_for_update(project, user)
521
    membership.reject()
522

    
523
    membership_change_notify(project, membership.person, 'rejected')
524

    
525
    return membership
526

    
527
def remove_membership(project_application_id, user, request_user=None):
528
    """
529
        Raises:
530
            django.core.exceptions.PermissionDenied
531
            IOError
532
    """
533
    project_id = get_project_id_of_application_id(project_application_id)
534
    return do_remove_membership(project_id, user, request_user)
535

    
536
def do_remove_membership_checks(project, membership, request_user=None):
537
    checkAllowed(project, request_user)
538
    checkAlive(project)
539

    
540
    leave_policy = project.application.member_leave_policy
541
    if leave_policy == CLOSED_POLICY:
542
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
543

    
544
def do_remove_membership(project_id, user, request_user=None):
545
    project = get_project_for_update(project_id)
546
    do_remove_membership_checks(project, request_user)
547

    
548
    membership = get_membership_for_update(project, user)
549
    membership.remove()
550
    trigger_sync()
551

    
552
    membership_change_notify(project, membership.person, 'removed')
553

    
554
    return membership
555

    
556
def enroll_member(project_application_id, user, request_user=None):
557
    project_id = get_project_id_of_application_id(project_application_id)
558
    return do_enroll_member(project_id, user, request_user)
559

    
560
def do_enroll_member(project_id, user, request_user=None):
561
    project = get_project_for_update(project_id)
562
    do_accept_membership_checks(project, request_user)
563

    
564
    membership = create_membership(project_id, user)
565
    membership.accept()
566
    trigger_sync()
567

    
568
    # TODO send proper notification
569
    return membership
570

    
571
def leave_project(project_application_id, user_id):
572
    """
573
        Raises:
574
            django.core.exceptions.PermissionDenied
575
            IOError
576
    """
577
    project_id = get_project_id_of_application_id(project_application_id)
578
    return do_leave_project(project_id, user_id)
579

    
580
def do_leave_project_checks(project):
581
    checkAlive(project)
582

    
583
    leave_policy = project.application.member_leave_policy
584
    if leave_policy == CLOSED_POLICY:
585
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
586

    
587
def do_leave_project(project_id, user_id):
588
    project = get_project_for_update(project_id)
589
    do_leave_project_checks(project)
590

    
591
    membership = get_membership_for_update(project, user_id)
592

    
593
    leave_policy = project.application.member_leave_policy
594
    if leave_policy == AUTO_ACCEPT_POLICY:
595
        membership.remove()
596
        trigger_sync()
597
    else:
598
        membership.leave_request_date = datetime.now()
599
        membership.save()
600
    return membership
601

    
602
def join_project(project_application_id, user_id):
603
    """
604
        Raises:
605
            django.core.exceptions.PermissionDenied
606
            IOError
607
    """
608
    project_id = get_project_id_of_application_id(project_application_id)
609
    return do_join_project(project_id, user_id)
610

    
611
def do_join_project_checks(project):
612
    checkAlive(project)
613

    
614
    join_policy = project.application.member_join_policy
615
    if join_policy == CLOSED_POLICY:
616
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
617

    
618
def do_join_project(project_id, user_id):
619
    project = get_project_for_update(project_id)
620
    do_join_project_checks(project)
621

    
622
    membership = create_membership(project, user_id)
623

    
624
    join_policy = project.application.member_join_policy
625
    if (join_policy == AUTO_ACCEPT_POLICY and
626
        not project.violates_members_limit(adding=1)):
627
        membership.accept()
628
        trigger_sync()
629
    return membership
630

    
631
def submit_application(kw, request_user=None):
632

    
633
    kw['applicant'] = request_user
634

    
635
    precursor_id = kw.get('precursor_application', None)
636
    if precursor_id is not None:
637
        sfu = ProjectApplication.objects.select_for_update()
638
        precursor = sfu.get(id=precursor_id)
639
        kw['precursor_application'] = precursor
640

    
641
        if request_user and \
642
            (not precursor.owner == request_user and \
643
                not request_user.is_superuser):
644
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
645

    
646
    application = models_submit_application(**kw)
647

    
648
    application_submit_notify(application)
649
    return application
650

    
651
def update_application(app_id, **kw):
652
    app = ProjectApplication.objects.get(id=app_id)
653
    app.id = None
654
    app.state = app.PENDING
655
    app.precursor_application_id = app_id
656
    app.issue_date = datetime.now()
657

    
658
    resource_policies = kw.pop('resource_policies', None)
659
    for k, v in kw.iteritems():
660
        setattr(app, k, v)
661
    app.save()
662
    app.resource_policies = resource_policies
663
    return app.id
664

    
665
def deny_application(application_id):
666
    application = get_application_for_update(application_id)
667
    if application.state != ProjectApplication.PENDING:
668
        raise PermissionDenied()
669

    
670
    application.deny()
671
    application_deny_notify(application)
672

    
673
def approve_application(app):
674

    
675
    app_id = app if isinstance(app, int) else app.id
676

    
677
    try:
678
        objects = ProjectApplication.objects.select_for_update()
679
        application = objects.get(id=app_id)
680
    except ProjectApplication.DoesNotExist:
681
        raise PermissionDenied()
682

    
683
    application.approve()
684
    trigger_sync()
685

    
686
    application_approve_notify(application)
687

    
688
def terminate(project_id):
689
    project = get_project_for_update(project_id)
690
    checkAlive(project)
691

    
692
    project.terminate()
693
    trigger_sync()
694

    
695
    project_termination_notify(project)
696

    
697
def suspend(project_id):
698
    project = get_project_by_id(project_id)
699
    project.last_approval_date = None
700
    project.save()
701
    trigger_sync()
702

    
703
    project_suspension_notify(project)