Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 8cf9b2dd

History | View | Annotate | Download (25 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, get_connection
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
from django.http import Http404
50

    
51
from urllib import quote
52
from urlparse import urljoin
53
from smtplib import SMTPException
54
from datetime import datetime
55
from functools import wraps
56

    
57
from astakos.im.settings import (
58
    DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
59
    VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
60
    GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
61
    INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
62
    EMAIL_CHANGE_EMAIL_SUBJECT,
63
    PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
64
    PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
65
    PROJECT_MEMBERSHIP_CHANGE_SUBJECT,
66
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES)
67
from astakos.im.notifications import build_notification, NotificationError
68
from astakos.im.models import (
69
    AstakosUser, ProjectMembership, ProjectApplication, Project,
70
    PendingMembershipError, get_resource_names, new_chain)
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
                  connection=get_connection())
131

    
132
    except (SMTPException, socket.error) as e:
133
        logger.exception(e)
134
        raise SendVerificationError()
135
    else:
136
        msg = 'Sent activation %s' % user.email
137
        logger.log(LOGGING_LEVEL, msg)
138

    
139

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

    
145

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

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

    
170

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

    
176

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

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

    
201

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

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

    
230

    
231
def send_greeting(user, email_template_name='im/welcome_email.txt'):
232
    """
233
    Send welcome email.
234

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

    
255

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

    
274

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

    
293

    
294
def activate(
295
    user,
296
    email_template_name='im/welcome_email.txt',
297
    helpdesk_email_template_name='im/helpdesk_notification.txt',
298
    verify_email=False):
299
    """
300
    Activates the specific user and sends email.
301

302
    Raises SendGreetingError, ValidationError
303
    """
304
    user.is_active = True
305
    user.email_verified = True
306
    if not user.activation_sent:
307
        user.activation_sent = datetime.now()
308
    user.save()
309
    qh_register_user_with_quotas(user)
310
    send_helpdesk_notification(user, helpdesk_email_template_name)
311
    send_greeting(user, email_template_name)
312

    
313
def deactivate(user):
314
    user.is_active = False
315
    user.save()
316

    
317
def invite(inviter, email, realname):
318
    inv = Invitation(inviter=inviter, username=email, realname=realname)
319
    inv.save()
320
    send_invitation(inv)
321
    inviter.invitations = max(0, self.invitations - 1)
322
    inviter.save()
323

    
324
def switch_account_to_shibboleth(user, local_user,
325
                                 greeting_template_name='im/welcome_email.txt'):
326
    try:
327
        provider = user.provider
328
    except AttributeError:
329
        return
330
    else:
331
        if not provider == 'shibboleth':
332
            return
333
        user.delete()
334
        local_user.provider = 'shibboleth'
335
        local_user.third_party_identifier = user.third_party_identifier
336
        local_user.save()
337
        send_greeting(local_user, greeting_template_name)
338
        return local_user
339

    
340

    
341
class SendMailError(Exception):
342
    pass
343

    
344

    
345
class SendAdminNotificationError(SendMailError):
346
    def __init__(self):
347
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
348
        super(SendAdminNotificationError, self).__init__()
349

    
350

    
351
class SendVerificationError(SendMailError):
352
    def __init__(self):
353
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
354
        super(SendVerificationError, self).__init__()
355

    
356

    
357
class SendInvitationError(SendMailError):
358
    def __init__(self):
359
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
360
        super(SendInvitationError, self).__init__()
361

    
362

    
363
class SendGreetingError(SendMailError):
364
    def __init__(self):
365
        self.message = _(astakos_messages.GREETING_SEND_ERR)
366
        super(SendGreetingError, self).__init__()
367

    
368

    
369
class SendFeedbackError(SendMailError):
370
    def __init__(self):
371
        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
372
        super(SendFeedbackError, self).__init__()
373

    
374

    
375
class ChangeEmailError(SendMailError):
376
    def __init__(self):
377
        self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
378
        super(ChangeEmailError, self).__init__()
379

    
380

    
381
class SendNotificationError(SendMailError):
382
    def __init__(self):
383
        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
384
        super(SendNotificationError, self).__init__()
385

    
386

    
387
def get_quota(users):
388
    resources = get_resource_names()
389
    return qh_get_quota(users, resources)
390

    
391

    
392
### PROJECT VIEWS ###
393

    
394
AUTO_ACCEPT_POLICY = 1
395
MODERATED_POLICY   = 2
396
CLOSED_POLICY      = 3
397

    
398
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
399

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

    
407
def get_project_id_of_application_id(project_application_id):
408
    try:
409
        return Project.objects.get(application__id=project_application_id).id
410
    except Project.DoesNotExist:
411
        raise IOError(
412
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
413

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

    
421
def get_project_for_update(project_id):
422
    try:
423
        return Project.objects.select_for_update().get(id=project_id)
424
    except Project.DoesNotExist:
425
        raise IOError(
426
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
427

    
428
def get_application_for_update(application_id):
429
    try:
430
        objects = ProjectApplication.objects.select_for_update()
431
        return objects.get(id=application_id)
432
    except ProjectApplication.DoesNotExist:
433
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
434
        raise IOError(m)
435

    
436
def get_user_by_id(user_id):
437
    try:
438
        return AstakosUser.objects.get(id=user_id)
439
    except AstakosUser.DoesNotExist:
440
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
441

    
442
def get_user_by_uuid(uuid):
443
    try:
444
        return AstakosUser.objects.get(uuid=uuid)
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
        sfu = ProjectMembership.objects.select_for_update()
471
        m = sfu.get(project=project, person=user)
472
        if m.is_pending:
473
            raise PendingMembershipError()
474
        return m
475
    except ProjectMembership.DoesNotExist:
476
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
477

    
478
def checkAllowed(entity, request_user):
479
    if isinstance(entity, Project):
480
        application = entity.application
481
    elif isinstance(entity, ProjectApplication):
482
        application = entity
483
    else:
484
        m = "%s not a Project nor a ProjectApplication" % (entity,)
485
        raise ValueError(m)
486

    
487
    if request_user and \
488
        (not application.owner == request_user and \
489
            not request_user.is_superuser):
490
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
491

    
492
def checkAlive(project):
493
    if not project.is_alive:
494
        raise PermissionDenied(
495
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
496

    
497
def accept_membership_checks(project, request_user):
498
    checkAllowed(project, request_user)
499
    checkAlive(project)
500

    
501
    join_policy = project.application.member_join_policy
502
    if join_policy == CLOSED_POLICY:
503
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
504

    
505
    if project.violates_members_limit(adding=1):
506
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
507

    
508
def accept_membership(project_id, user, request_user=None):
509
    project = get_project_for_update(project_id)
510
    accept_membership_checks(project, request_user)
511

    
512
    membership = get_membership_for_update(project, user)
513
    if not membership.can_accept():
514
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
515
        raise PermissionDenied(m)
516

    
517
    membership.accept()
518

    
519
    membership_change_notify(project, membership.person, 'accepted')
520

    
521
    return membership
522

    
523
def reject_membership_checks(project, request_user):
524
    checkAllowed(project, request_user)
525
    checkAlive(project)
526

    
527
def reject_membership(project_id, user, request_user=None):
528
    project = get_project_for_update(project_id)
529
    reject_membership_checks(project, request_user)
530
    membership = get_membership_for_update(project, user)
531
    if not membership.can_reject():
532
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
533
        raise PermissionDenied(m)
534

    
535
    membership.reject()
536

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

    
539
    return membership
540

    
541
def cancel_membership_checks(project):
542
    checkAlive(project)
543

    
544
def cancel_membership(project_id, user_id):
545
    project = get_project_for_update(project_id)
546
    cancel_membership_checks(project)
547
    membership = get_membership_for_update(project, user_id)
548
    if not membership.can_cancel():
549
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
550
        raise PermissionDenied(m)
551

    
552
    membership.cancel()
553

    
554
def remove_membership_checks(project, request_user=None):
555
    checkAllowed(project, request_user)
556
    checkAlive(project)
557

    
558
    leave_policy = project.application.member_leave_policy
559
    if leave_policy == CLOSED_POLICY:
560
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
561

    
562
def remove_membership(project_id, user, request_user=None):
563
    project = get_project_for_update(project_id)
564
    remove_membership_checks(project, request_user)
565
    membership = get_membership_for_update(project, user)
566
    if not membership.can_remove():
567
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
568
        raise PermissionDenied(m)
569

    
570
    membership.remove()
571

    
572
    membership_change_notify(project, membership.person, 'removed')
573

    
574
    return membership
575

    
576
def enroll_member(project_id, user, request_user=None):
577
    project = get_project_for_update(project_id)
578
    accept_membership_checks(project, request_user)
579
    membership = create_membership(project_id, user)
580

    
581
    if not membership.can_accept():
582
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
583
        raise PermissionDenied(m)
584

    
585
    membership.accept()
586

    
587
    # TODO send proper notification
588
    return membership
589

    
590
def leave_project_checks(project):
591
    checkAlive(project)
592

    
593
    leave_policy = project.application.member_leave_policy
594
    if leave_policy == CLOSED_POLICY:
595
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
596

    
597
def leave_project(project_id, user_id):
598
    project = get_project_for_update(project_id)
599
    leave_project_checks(project)
600
    membership = get_membership_for_update(project, user_id)
601
    if not membership.can_leave():
602
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
603
        raise PermissionDenied(m)
604

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

    
613
def join_project_checks(project):
614
    checkAlive(project)
615

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

    
620
def join_project(project_id, user_id):
621
    project = get_project_for_update(project_id)
622
    join_project_checks(project)
623
    membership = create_membership(project, user_id)
624

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

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

    
633
    kw['applicant'] = request_user
634
    resource_policies = kw.pop('resource_policies', None)
635

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

    
643
        if (request_user and
644
            (not precursor.owner == request_user and
645
             not request_user.is_superuser)):
646
            m = _(astakos_messages.NOT_ALLOWED)
647
            raise PermissionDenied(m)
648

    
649
    application = ProjectApplication(**kw)
650

    
651
    if precursor is None:
652
        application.chain = new_chain()
653
    else:
654
        chain = precursor.chain
655
        application.chain = chain
656
        sfu = ProjectApplication.objects.select_for_update()
657
        pending = sfu.filter(chain=chain, state=ProjectApplication.PENDING)
658
        for app in pending:
659
            app.state = ProjectApplication.REPLACED
660
            app.save()
661

    
662
    application.save()
663
    application.resource_policies = resource_policies
664
    application_submit_notify(application)
665
    return application
666

    
667
def cancel_application(application_id, request_user=None):
668
    application = get_application_for_update(application_id)
669
    checkAllowed(application, request_user)
670

    
671
    if not application.can_cancel():
672
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
673
                application.id, application.state_display()))
674
        raise PermissionDenied(m)
675

    
676
    application.cancel()
677

    
678
def dismiss_application(application_id, request_user=None):
679
    application = get_application_for_update(application_id)
680
    checkAllowed(application, request_user)
681

    
682
    if not application.can_dismiss():
683
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
684
                application.id, application.state_display()))
685
        raise PermissionDenied(m)
686

    
687
    application.dismiss()
688

    
689
def deny_application(application_id):
690
    application = get_application_for_update(application_id)
691

    
692
    if not application.can_deny():
693
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
694
                application.id, application.state_display()))
695
        raise PermissionDenied(m)
696

    
697
    application.deny()
698
    application_deny_notify(application)
699

    
700
def approve_application(app):
701

    
702
    app_id = app if isinstance(app, int) else app.id
703

    
704
    try:
705
        objects = ProjectApplication.objects.select_for_update()
706
        application = objects.get(id=app_id)
707
    except ProjectApplication.DoesNotExist:
708
        raise PermissionDenied()
709

    
710
    if not application.can_approve():
711
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
712
                application.id, application.state_display()))
713
        raise PermissionDenied(m)
714

    
715
    application.approve()
716
    application_approve_notify(application)
717

    
718
def check_expiration(execute=False):
719
    objects = Project.objects
720
    expired = objects.expired_projects()
721
    if execute:
722
        for project in expired:
723
            terminate(project.id)
724

    
725
    return [project.expiration_info() for project in expired]
726

    
727
def terminate(project_id):
728
    project = get_project_for_update(project_id)
729
    checkAlive(project)
730

    
731
    project.terminate()
732

    
733
    project_termination_notify(project)
734

    
735
def suspend(project_id):
736
    project = get_project_by_id(project_id)
737
    checkAlive(project)
738

    
739
    project.suspend()
740

    
741
    project_suspension_notify(project)
742

    
743
def resume(project_id):
744
    project = get_project_for_update(project_id)
745

    
746
    if not project.is_suspended:
747
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
748
        raise PermissionDenied(m)
749

    
750
    project.resume()
751

    
752
def get_by_chain_or_404(chain_id):
753
    try:
754
        project = Project.objects.get(id=chain_id)
755
        application = project.application
756
        return project, application
757
    except:
758
        application = ProjectApplication.objects.latest_of_chain(chain_id)
759
        if application is None:
760
            raise Http404
761
        else:
762
            return None, application