Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.6 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
from astakos.im.notifications import build_notification, NotificationError
66
from astakos.im.models import (
67
    AstakosUser, ProjectMembership, ProjectApplication, Project,
68
    MemberLeavePolicy, MemberJoinPolicy,
69
    trigger_sync)
70

    
71
import astakos.im.messages as astakos_messages
72

    
73
logger = logging.getLogger(__name__)
74

    
75

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

    
92

    
93
def login(request, user):
94
    auth_login(request, user)
95
    from astakos.im.models import SessionCatalog
96
    SessionCatalog(
97
        session_key=request.session.session_key,
98
        user=user
99
    ).save()
100

    
101
login = logged(login, '%s logged in.')
102
logout = logged(auth_logout, '%s logged out.')
103

    
104

    
105
def send_verification(user, template_name='im/activation_email.txt'):
106
    """
107
    Send email to user to verify his/her email and activate his/her account.
108

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

    
130

    
131
def send_activation(user, template_name='im/activation_email.txt'):
132
    send_verification(user, template_name)
133
    user.activation_sent = datetime.now()
134
    user.save()
135

    
136

    
137
def _send_admin_notification(template_name,
138
                             dictionary=None,
139
                             subject='alpha2 testing notification',):
140
    """
141
    Send notification email to settings.ADMINS.
142

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

    
161

    
162
def send_account_creation_notification(template_name, dictionary=None):
163
    user = dictionary.get('user', AnonymousUser())
164
    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
165
    return _send_admin_notification(template_name, dictionary, subject=subject)
166

    
167

    
168
def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
169
    """
170
    Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
171

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

    
192

    
193
def send_invitation(invitation, template_name='im/invitation.txt'):
194
    """
195
    Send invitation email.
196

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

    
219

    
220
def send_greeting(user, email_template_name='im/welcome_email.txt'):
221
    """
222
    Send welcome email.
223

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

    
243

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

    
261

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

    
279

    
280
def activate(
281
    user,
282
    email_template_name='im/welcome_email.txt',
283
    helpdesk_email_template_name='im/helpdesk_notification.txt',
284
    verify_email=False):
285
    """
286
    Activates the specific user and sends email.
287

288
    Raises SendGreetingError, ValidationError
289
    """
290
    user.is_active = True
291
    user.email_verified = True
292
    if not user.activation_sent:
293
        user.activation_sent = datetime.now()
294
    user.save()
295
    send_helpdesk_notification(user, helpdesk_email_template_name)
296
    send_greeting(user, email_template_name)
297

    
298
def invite(inviter, email, realname):
299
    inv = Invitation(inviter=inviter, username=email, realname=realname)
300
    inv.save()
301
    send_invitation(inv)
302
    inviter.invitations = max(0, self.invitations - 1)
303
    inviter.save()
304

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

    
321

    
322
class SendMailError(Exception):
323
    pass
324

    
325

    
326
class SendAdminNotificationError(SendMailError):
327
    def __init__(self):
328
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
329
        super(SendAdminNotificationError, self).__init__()
330

    
331

    
332
class SendVerificationError(SendMailError):
333
    def __init__(self):
334
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
335
        super(SendVerificationError, self).__init__()
336

    
337

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

    
343

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

    
349

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

    
355

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

    
361

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

    
367

    
368
### PROJECT VIEWS ###
369

    
370
def get_join_policy(str_policy):
371
    try:
372
        return MemberJoinPolicy.objects.get(policy=str_policy)
373
    except:
374
        return None
375

    
376
def get_leave_policy(str_policy):
377
    try:
378
        return MemberLeavePolicy.objects.get(policy=str_policy)
379
    except BaseException, e:
380
        return None
381
    
382
_auto_accept_join = None
383
def get_auto_accept_join_policy():
384
    global _auto_accept_join
385
    if _auto_accept_join is not None:
386
        return _auto_accept_join
387
    _auto_accept = get_join_policy('auto_accept')
388
    return _auto_accept
389

    
390
_closed_join = None
391
def get_closed_join_policy():
392
    global _closed_join
393
    if _closed_join is not None:
394
        return _closed_join
395
    _closed_join = get_join_policy('closed')
396
    return _closed_join
397

    
398
_auto_accept_leave = None
399
def get_auto_accept_leave_policy():
400
    global _auto_accept_leave
401
    if _auto_accept_leave is not None:
402
        return _auto_accept_leave
403
    _auto_accept_leave = get_leave_policy('auto_accept')
404
    return _auto_accept_leave
405

    
406
_closed_leave = None
407
def get_closed_leave_policy():
408
    global _closed_leave
409
    if _closed_leave is not None:
410
        return _closed_leave
411
    _closed_leave = get_leave_policy('closed')
412
    return _closed_leave
413

    
414
def get_project_by_application_id(project_application_id):
415
    try:
416
        return Project.objects.get(application__id=project_application_id)
417
    except Project.DoesNotExist:
418
        raise IOError(
419
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
420

    
421
def get_project_id_of_application_id(project_application_id):
422
    try:
423
        return Project.objects.get(application__id=project_application_id).id
424
    except Project.DoesNotExist:
425
        raise IOError(
426
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
427

    
428
def get_project_by_id(project_id):
429
    try:
430
        return Project.objects.get(id=project_id)
431
    except Project.DoesNotExist:
432
        raise IOError(
433
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
434

    
435
def get_project_for_update(project_id):
436
    try:
437
        return Project.objects.select_for_update().get(id=project_id)
438
    except Project.DoesNotExist:
439
        raise IOError(
440
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
441

    
442
def get_user_by_id(user_id):
443
    try:
444
        return AstakosUser.objects.get(id=user_id)
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
        return ProjectMembership.objects.select_for_update().get(
471
            project=project,
472
            person=user)
473
    except ProjectMembership.DoesNotExist:
474
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
475

    
476
def accept_membership(project_application_id, user, request_user=None):
477
    """
478
        Raises:
479
            django.core.exceptions.PermissionDenied
480
            IOError
481
    """
482
    project_id = get_project_id_of_application_id(project_application_id)
483
    return do_accept_membership(project_id, user, request_user)
484

    
485
def do_accept_membership(project_id, user, request_user=None):
486
    project = get_project_for_update(project_id)
487

    
488
    if request_user and \
489
        (not project.application.owner == request_user and \
490
            not request_user.is_superuser):
491
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
492
    if not project.is_alive:
493
        raise PermissionDenied(
494
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
495
    if project.violates_members_limit(adding=1):
496
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
497

    
498
    membership = get_membership_for_update(project, user)
499
    membership.accept()
500
    trigger_sync()
501

    
502
    try:
503
        notification = build_notification(
504
            settings.SERVER_EMAIL,
505
            [membership.person.email],
506
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % project.__dict__,
507
            template='im/projects/project_membership_change_notification.txt',
508
            dictionary={'object':project.application, 'action':'accepted'})
509
        notification.send()
510
    except NotificationError, e:
511
        logger.error(e.message)
512
    return membership
513

    
514
def reject_membership(project_application_id, user, request_user=None):
515
    """
516
        Raises:
517
            django.core.exceptions.PermissionDenied
518
            IOError
519
    """
520
    project_id = get_project_id_of_application_id(project_application_id)
521
    return do_reject_membership(project_id, user, request_user)
522

    
523
def do_reject_membership(project_id, user, request_user=None):
524
    project = get_project_for_update(project_id)
525

    
526
    if request_user and \
527
        (not project.application.owner == request_user and \
528
            not request_user.is_superuser):
529
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
530
    if not project.is_alive:
531
        raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
532

    
533
    membership = get_membership_for_update(project, user)
534
    membership.reject()
535

    
536
    try:
537
        notification = build_notification(
538
            settings.SERVER_EMAIL,
539
            [membership.person.email],
540
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % project.__dict__,
541
            template='im/projects/project_membership_change_notification.txt',
542
            dictionary={'object':project.application, 'action':'rejected'})
543
        notification.send()
544
    except NotificationError, e:
545
        logger.error(e.message)
546
    return membership
547

    
548
def remove_membership(project_application_id, user, request_user=None):
549
    """
550
        Raises:
551
            django.core.exceptions.PermissionDenied
552
            IOError
553
    """
554
    project_id = get_project_id_of_application_id(project_application_id)
555
    return do_remove_membership(project_id, user, request_user)
556

    
557
def do_remove_membership(project_id, user, request_user=None):
558
    project = get_project_for_update(project_id)
559

    
560
    if request_user and \
561
        (not project.application.owner == request_user and \
562
            not request_user.is_superuser):
563
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
564
    if not project.is_alive:
565
        raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
566

    
567
    membership = get_membership_for_update(project, user)
568
    membership.remove()
569
    trigger_sync()
570

    
571
    try:
572
        notification = build_notification(
573
            settings.SERVER_EMAIL,
574
            [membership.person.email],
575
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % project.__dict__,
576
            template='im/projects/project_membership_change_notification.txt',
577
            dictionary={'object':project.application, 'action':'removed'})
578
        notification.send()
579
    except NotificationError, e:
580
        logger.error(e.message)
581
    return membership
582

    
583
def enroll_member(project_application_id, user, request_user=None):
584
    project_id = get_project_id_of_application_id(project_application_id)
585
    return do_enroll_member(project_id, user, request_user)
586

    
587
def do_enroll_member(project_id, user, request_user=None):
588
    membership = create_membership(project_id, user)
589
    return do_accept_membership(project_id, user, request_user)
590

    
591
def leave_project(project_application_id, user_id):
592
    """
593
        Raises:
594
            django.core.exceptions.PermissionDenied
595
            IOError
596
    """
597
    project_id = get_project_id_of_application_id(project_application_id)
598
    return do_leave_project(project_id, user_id)
599

    
600
def do_leave_project(project_id, user_id):
601
    project = get_project_for_update(project_id)
602

    
603
    if not project.is_alive:
604
        m = _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__
605
        raise PermissionDenied(m)
606

    
607
    leave_policy = project.application.member_leave_policy
608
    if leave_policy == get_closed_leave_policy():
609
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
610

    
611
    membership = get_membership_for_update(project, user_id)
612
    if leave_policy == get_auto_accept_leave_policy():
613
        membership.remove()
614
        trigger_sync()
615
    else:
616
        membership.leave_request_date = datetime.now()
617
        membership.save()
618
    return membership
619

    
620
def join_project(project_application_id, user_id):
621
    """
622
        Raises:
623
            django.core.exceptions.PermissionDenied
624
            IOError
625
    """
626
    project_id = get_project_id_of_application_id(project_application_id)
627
    return do_join_project(project_id, user_id)
628

    
629
def do_join_project(project_id, user_id):
630
    project = get_project_for_update(project_id)
631

    
632
    if not project.is_alive:
633
        m = _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__
634
        raise PermissionDenied(m)
635

    
636
    join_policy = project.application.member_join_policy
637
    if join_policy == get_closed_join_policy():
638
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
639

    
640
    membership = create_membership(project, user_id)
641

    
642
    if (join_policy == get_auto_accept_join_policy() and
643
        not project.violates_members_limit(adding=1)):
644
        membership.accept()
645
        trigger_sync()
646
    return membership
647

    
648
def submit_application(
649
    application, resource_policies, applicant, comments, precursor_application=None):
650

    
651
    application.submit(
652
        resource_policies, applicant, comments, precursor_application)
653
    
654
    try:
655
        notification = build_notification(
656
            settings.SERVER_EMAIL,
657
            [i[1] for i in settings.ADMINS],
658
            _(PROJECT_CREATION_SUBJECT) % application.__dict__,
659
            template='im/projects/project_creation_notification.txt',
660
            dictionary={'object':application})
661
        notification.send()
662
    except NotificationError, e:
663
        logger.error(e)
664
    return application
665

    
666
def update_application(app_id, **kw):
667
    app = ProjectApplication.objects.get(id=app_id)
668
    app.id = None
669
    app.state = app.PENDING
670
    app.precursor_application_id = app_id
671
    app.issue_date = datetime.now()
672

    
673
    resource_policies = kw.pop('resource_policies', None)
674
    for k, v in kw:
675
        setattr(app, k, v)
676
    app.save()
677
    app.resource_policies = resource_policies
678
    return app.id
679

    
680
def approve_application(app):
681

    
682
    app_id = app if isinstance(app, int) else app.id
683

    
684
    try:
685
        objects = ProjectApplication.objects.select_for_update()
686
        application = objects.get(id=app_id)
687
    except ProjectApplication.DoesNotExist:
688
        raise PermissionDenied()
689

    
690
    application.approve()
691
    trigger_sync()
692

    
693
    try:
694
        notification = build_notification(
695
            settings.SERVER_EMAIL,
696
            [application.owner.email],
697
            _(PROJECT_APPROVED_SUBJECT) % application.__dict__,
698
            template='im/projects/project_approval_notification.txt',
699
            dictionary={'object':application})
700
        notification.send()
701
    except NotificationError, e:
702
        logger.error(e.message)
703

    
704
def terminate(project_id):
705
    project = get_project_by_id(project_id)
706
    project.set_termination_start_date()
707
    trigger_sync()
708
    project.set_termination_date()
709

    
710
    try:
711
        notification = build_notification(
712
            settings.SERVER_EMAIL,
713
            [project.application.owner.email],
714
            _(PROJECT_TERMINATION_SUBJECT) % project.__dict__,
715
            template='im/projects/project_termination_notification.txt',
716
            dictionary={'object':project.application}
717
        ).send()
718
    except NotificationError, e:
719
        logger.error(e.message)
720

    
721
def suspend(project_id):
722
    project = get_project_by_id(project_id)
723
    project.last_approval_date = None
724
    project.save()
725
    trigger_sync()
726
    
727
    try:
728
        notification = build_notification(
729
            settings.SERVER_EMAIL,
730
            [project.application.owner.email],
731
            _(PROJECT_SUSPENSION_SUBJECT) % project.__dict__,
732
            template='im/projects/project_suspension_notification.txt',
733
            dictionary={'object':project.application}
734
        ).send()
735
    except NotificationError, e:
736
        logger.error(e.message)