Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (22.1 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_by_id(project_id):
422
    try:
423
        return Project.objects.get(id=project_id)
424
    except Project.DoesNotExist:
425
        raise IOError(
426
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
427

    
428
def get_user_by_id(user_id):
429
    try:
430
        return AstakosUser.objects.get(id=user_id)
431
    except AstakosUser.DoesNotExist:
432
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
433

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

    
450
def get_membership(project, user):
451
    if isinstance(project, int):
452
        project = get_project_by_application_id(project)
453
    if isinstance(user, int):
454
        user = get_user_by_id(user)
455
    try:
456
        return ProjectMembership.objects.select_related().get(
457
            project=project,
458
            person=user)
459
    except ProjectMembership.DoesNotExist:
460
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
461

    
462
def accept_membership(project, user, request_user=None):
463
    """
464
        Raises:
465
            django.core.exceptions.PermissionDenied
466
            IOError
467
    """
468
    membership = get_membership(project, user)
469
    if request_user and \
470
        (not membership.project.application.owner == request_user and \
471
            not request_user.is_superuser):
472
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
473
    if not membership.project.is_alive:
474
        raise PermissionDenied(
475
            _(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
476
    if len(membership.project.approved_members) + 1 > \
477
        membership.project.application.limit_on_members_number:
478
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
479

    
480
    membership.accept()
481
    trigger_sync()
482

    
483
    try:
484
        notification = build_notification(
485
            settings.SERVER_EMAIL,
486
            [membership.person.email],
487
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % membership.project.__dict__,
488
            template='im/projects/project_membership_change_notification.txt',
489
            dictionary={'object':membership.project.application, 'action':'accepted'})
490
        notification.send()
491
    except NotificationError, e:
492
        logger.error(e.message)
493
    return membership
494

    
495
def reject_membership(project, user, request_user=None):
496
    """
497
        Raises:
498
            django.core.exceptions.PermissionDenied
499
            IOError
500
    """
501
    membership = get_membership(project, user)
502
    if request_user and \
503
        (not membership.project.application.owner == request_user and \
504
            not request_user.is_superuser):
505
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
506
    if not membership.project.is_alive:
507
        raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
508

    
509
    membership.reject()
510

    
511
    try:
512
        notification = build_notification(
513
            settings.SERVER_EMAIL,
514
            [membership.person.email],
515
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % membership.project.__dict__,
516
            template='im/projects/project_membership_change_notification.txt',
517
            dictionary={'object':membership.project.application, 'action':'rejected'})
518
        notification.send()
519
    except NotificationError, e:
520
        logger.error(e.message)
521
    return membership
522

    
523
def remove_membership(project, user, request_user=None):
524
    """
525
        Raises:
526
            django.core.exceptions.PermissionDenied
527
            IOError
528
    """
529
    membership = get_membership(project, user)
530
    if request_user and \
531
        (not membership.project.application.owner == request_user and \
532
            not request_user.is_superuser):
533
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
534
    if not membership.project.is_alive:
535
        raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
536

    
537
    membership.remove()
538
    trigger_sync()
539

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

    
552
def enroll_member(project, user, request_user=None):
553
    membership = create_membership(project, user)
554
    accept_membership(project, user, request_user)
555
    
556
def leave_project(project_application_id, user_id):
557
    """
558
        Raises:
559
            django.core.exceptions.PermissionDenied
560
            IOError
561
    """
562
    project = get_project_by_application_id(project_application_id)
563
    leave_policy = project.application.member_leave_policy
564
    print '>>>', leave_policy, get_closed_leave_policy()
565
    if leave_policy == get_closed_leave_policy():
566
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
567

    
568
    membership = get_membership(project_application_id, user_id)
569
    if leave_policy == get_auto_accept_leave_policy():
570
        membership.remove()
571
        trigger_sync()
572
    else:
573
        membership.leave_request_date = datetime.now()
574
        membership.save()
575
    return membership
576

    
577
def join_project(project_application_id, user_id):
578
    """
579
        Raises:
580
            django.core.exceptions.PermissionDenied
581
            IOError
582
    """
583
    project = get_project_by_application_id(project_application_id)
584
    join_policy = project.application.member_join_policy
585
    if join_policy == get_closed_join_policy():
586
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
587

    
588
    membership = create_membership(project_application_id, user_id)
589

    
590
    if join_policy == get_auto_accept_join_policy():
591
        membership.accept()
592
        trigger_sync()
593
    return membership
594
    
595
def submit_application(
596
    application, resource_policies, applicant, comments, precursor_application=None):
597

    
598
    application.submit(
599
        resource_policies, applicant, comments, precursor_application)
600
    
601
    try:
602
        notification = build_notification(
603
            settings.SERVER_EMAIL,
604
            [i[1] for i in settings.ADMINS],
605
            _(PROJECT_CREATION_SUBJECT) % application.__dict__,
606
            template='im/projects/project_creation_notification.txt',
607
            dictionary={'object':application})
608
        notification.send()
609
    except NotificationError, e:
610
        logger.error(e)
611
    return application
612

    
613
def approve_application(application):
614
    application.approve()
615
    trigger_sync()
616
    
617
    try:
618
        notification = build_notification(
619
            settings.SERVER_EMAIL,
620
            [application.owner.email],
621
            _(PROJECT_APPROVED_SUBJECT) % application.__dict__,
622
            template='im/projects/project_approval_notification.txt',
623
            dictionary={'object':application})
624
        notification.send()
625
    except NotificationError, e:
626
        logger.error(e.message)
627

    
628
def terminate(project_id):
629
    project = get_project_by_id(project_id)
630
    project.set_termination_start_date()
631
    trigger_sync()
632
    project.set_termination_date()
633

    
634
    try:
635
        notification = build_notification(
636
            settings.SERVER_EMAIL,
637
            [project.application.owner.email],
638
            _(PROJECT_TERMINATION_SUBJECT) % project.__dict__,
639
            template='im/projects/project_termination_notification.txt',
640
            dictionary={'object':project.application}
641
        ).send()
642
    except NotificationError, e:
643
        logger.error(e.message)
644

    
645
def suspend(project_id):
646
    project = get_project_by_id(project_id)
647
    project.last_approval_date = None
648
    project.save()
649
    trigger_sync()
650
    
651
    try:
652
        notification = build_notification(
653
            settings.SERVER_EMAIL,
654
            [project.application.owner.email],
655
            _(PROJECT_SUSPENSION_SUBJECT) % project.__dict__,
656
            template='im/projects/project_suspension_notification.txt',
657
            dictionary={'object':project.application}
658
        ).send()
659
    except NotificationError, e:
660
        logger.error(e.message)