Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 84a3f701

History | View | Annotate | Download (24 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
    sync_projects, PendingMembershipError, get_resource_names)
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_with_quotas, qh_get_quota
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_with_quotas(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
def get_quota(users):
381
    resources = get_resource_names()
382
    return qh_get_quota(users, resources)
383

    
384

    
385
### PROJECT VIEWS ###
386

    
387
AUTO_ACCEPT_POLICY = 1
388
MODERATED_POLICY   = 2
389
CLOSED_POLICY      = 3
390

    
391
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
392

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

    
400
def get_project_id_of_application_id(project_application_id):
401
    try:
402
        return Project.objects.get(application__id=project_application_id).id
403
    except Project.DoesNotExist:
404
        raise IOError(
405
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
406

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

    
414
def get_project_for_update(project_id):
415
    try:
416
        return Project.objects.select_for_update().get(id=project_id)
417
    except Project.DoesNotExist:
418
        raise IOError(
419
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
420

    
421
def get_application_for_update(application_id):
422
    try:
423
        objects = ProjectApplication.objects.select_for_update()
424
        return objects.get(id=application_id)
425
    except ProjectApplication.DoesNotExist:
426
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
427
        raise IOError(m)
428

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

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

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

    
465
def checkAllowed(entity, request_user):
466
    if isinstance(entity, Project):
467
        application = entity.application
468
    elif isinstance(entity, ProjectApplication):
469
        application = entity
470
    else:
471
        m = "%s not a Project nor a ProjectApplication" % (entity,)
472
        raise ValueError(m)
473

    
474
    if request_user and \
475
        (not application.owner == request_user and \
476
            not request_user.is_superuser):
477
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
478

    
479
def checkAlive(project):
480
    if not project.is_alive:
481
        raise PermissionDenied(
482
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
483

    
484
def accept_membership(project_application_id, user, request_user=None):
485
    """
486
        Raises:
487
            django.core.exceptions.PermissionDenied
488
            IOError
489
    """
490
    project_id = get_project_id_of_application_id(project_application_id)
491
    return do_accept_membership(project_id, user, request_user)
492

    
493
def do_accept_membership_checks(project, request_user):
494
    checkAllowed(project, request_user)
495
    checkAlive(project)
496

    
497
    join_policy = project.application.member_join_policy
498
    if join_policy == CLOSED_POLICY:
499
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
500

    
501
    if project.violates_members_limit(adding=1):
502
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
503

    
504
def do_accept_membership(project_id, user, request_user=None):
505
    project = get_project_for_update(project_id)
506
    do_accept_membership_checks(project, request_user)
507

    
508
    membership = get_membership_for_update(project, user)
509
    membership.accept()
510
    sync_projects()
511

    
512
    membership_change_notify(project, membership.person, 'accepted')
513

    
514
    return membership
515

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

    
525
def do_reject_membership_checks(project, request_user):
526
    checkAllowed(project, request_user)
527
    checkAlive(project)
528

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

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

    
536
    membership_change_notify(project, membership.person, 'rejected')
537

    
538
    return membership
539

    
540
def remove_membership(project_application_id, user, request_user=None):
541
    """
542
        Raises:
543
            django.core.exceptions.PermissionDenied
544
            IOError
545
    """
546
    project_id = get_project_id_of_application_id(project_application_id)
547
    return do_remove_membership(project_id, user, request_user)
548

    
549
def do_remove_membership_checks(project, membership, request_user=None):
550
    checkAllowed(project, request_user)
551
    checkAlive(project)
552

    
553
    leave_policy = project.application.member_leave_policy
554
    if leave_policy == CLOSED_POLICY:
555
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
556

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

    
561
    membership = get_membership_for_update(project, user)
562
    membership.remove()
563
    sync_projects()
564

    
565
    membership_change_notify(project, membership.person, 'removed')
566

    
567
    return membership
568

    
569
def enroll_member(project_application_id, user, request_user=None):
570
    project_id = get_project_id_of_application_id(project_application_id)
571
    return do_enroll_member(project_id, user, request_user)
572

    
573
def do_enroll_member(project_id, user, request_user=None):
574
    project = get_project_for_update(project_id)
575
    do_accept_membership_checks(project, request_user)
576

    
577
    membership = create_membership(project_id, user)
578
    membership.accept()
579
    sync_projects()
580

    
581
    # TODO send proper notification
582
    return membership
583

    
584
def leave_project(project_application_id, user_id):
585
    """
586
        Raises:
587
            django.core.exceptions.PermissionDenied
588
            IOError
589
    """
590
    project_id = get_project_id_of_application_id(project_application_id)
591
    return do_leave_project(project_id, user_id)
592

    
593
def do_leave_project_checks(project):
594
    checkAlive(project)
595

    
596
    leave_policy = project.application.member_leave_policy
597
    if leave_policy == CLOSED_POLICY:
598
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
599

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

    
604
    membership = get_membership_for_update(project, user_id)
605

    
606
    leave_policy = project.application.member_leave_policy
607
    if leave_policy == AUTO_ACCEPT_POLICY:
608
        membership.remove()
609
        sync_projects()
610
    else:
611
        membership.leave_request_date = datetime.now()
612
        membership.save()
613
    return membership
614

    
615
def join_project(project_application_id, user_id):
616
    """
617
        Raises:
618
            django.core.exceptions.PermissionDenied
619
            IOError
620
    """
621
    project_id = get_project_id_of_application_id(project_application_id)
622
    return do_join_project(project_id, user_id)
623

    
624
def do_join_project_checks(project):
625
    checkAlive(project)
626

    
627
    join_policy = project.application.member_join_policy
628
    if join_policy == CLOSED_POLICY:
629
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
630

    
631
def do_join_project(project_id, user_id):
632
    project = get_project_for_update(project_id)
633
    do_join_project_checks(project)
634

    
635
    membership = create_membership(project, user_id)
636

    
637
    join_policy = project.application.member_join_policy
638
    if (join_policy == AUTO_ACCEPT_POLICY and
639
        not project.violates_members_limit(adding=1)):
640
        membership.accept()
641
        sync_projects()
642
    return membership
643

    
644
def submit_application(kw, request_user=None):
645

    
646
    kw['applicant'] = request_user
647

    
648
    precursor_id = kw.get('precursor_application', None)
649
    if precursor_id is not None:
650
        sfu = ProjectApplication.objects.select_for_update()
651
        precursor = sfu.get(id=precursor_id)
652
        kw['precursor_application'] = precursor
653

    
654
        if request_user and \
655
            (not precursor.owner == request_user and \
656
                not request_user.is_superuser):
657
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
658

    
659
    application = models_submit_application(**kw)
660

    
661
    application_submit_notify(application)
662
    return application
663

    
664
def cancel_application(application_id, request_user=None):
665
    application = get_application_for_update(application_id)
666
    checkAllowed(application, request_user)
667

    
668
    if application.state != ProjectApplication.PENDING:
669
        raise PermissionDenied()
670

    
671
    application.cancel()
672

    
673
def dismiss_application(application_id, request_user=None):
674
    application = get_application_for_update(application_id)
675
    checkAllowed(application, request_user)
676

    
677
    if application.state != ProjectApplication.DENIED:
678
        raise PermissionDenied()
679

    
680
    application.dismiss()
681

    
682
def deny_application(application_id):
683
    application = get_application_for_update(application_id)
684
    if application.state != ProjectApplication.PENDING:
685
        raise PermissionDenied()
686

    
687
    application.deny()
688
    application_deny_notify(application)
689

    
690
def approve_application(app):
691

    
692
    app_id = app if isinstance(app, int) else app.id
693

    
694
    try:
695
        objects = ProjectApplication.objects.select_for_update()
696
        application = objects.get(id=app_id)
697
    except ProjectApplication.DoesNotExist:
698
        raise PermissionDenied()
699

    
700
    application.approve()
701
    sync_projects()
702

    
703
    application_approve_notify(application)
704

    
705
def check_expiration(execute=False):
706
    objects = Project.objects
707
    expired = objects.expired_projects()
708
    if execute:
709
        for project in expired:
710
            terminate(project.id)
711

    
712
    return [project.expiration_info() for project in expired]
713

    
714
def terminate(project_id):
715
    project = get_project_for_update(project_id)
716
    checkAlive(project)
717

    
718
    project.terminate()
719
    sync_projects()
720

    
721
    project_termination_notify(project)
722

    
723
def suspend(project_id):
724
    project = get_project_by_id(project_id)
725
    checkAlive(project)
726

    
727
    project.suspend()
728
    sync_projects()
729

    
730
    project_suspension_notify(project)
731

    
732
def resume(project_id):
733
    project = get_project_for_update(project_id)
734

    
735
    if not project.is_suspended:
736
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
737
        raise PermissionDenied(m)
738

    
739
    project.resume()
740
    sync_projects()