Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.4 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
    sync_projects, PendingMembershipError, get_resource_names)
71
from astakos.im.models import submit_application as models_submit_application
72
from astakos.im.project_notif import (
73
    membership_change_notify,
74
    application_submit_notify, application_approve_notify,
75
    application_deny_notify,
76
    project_termination_notify, project_suspension_notify)
77
from astakos.im.endpoints.qh import qh_register_user_with_quotas, qh_get_quota
78

    
79
import astakos.im.messages as astakos_messages
80

    
81
logger = logging.getLogger(__name__)
82

    
83

    
84
def logged(func, msg):
85
    @wraps(func)
86
    def with_logging(*args, **kwargs):
87
        email = ''
88
        user = None
89
        try:
90
            request = args[0]
91
            email = request.user.email
92
        except (KeyError, AttributeError), e:
93
            email = ''
94
        r = func(*args, **kwargs)
95
        if LOGGING_LEVEL:
96
            logger.log(LOGGING_LEVEL, msg % email)
97
        return r
98
    return with_logging
99

    
100

    
101
def login(request, user):
102
    auth_login(request, user)
103
    from astakos.im.models import SessionCatalog
104
    SessionCatalog(
105
        session_key=request.session.session_key,
106
        user=user
107
    ).save()
108

    
109
login = logged(login, '%s logged in.')
110
logout = logged(auth_logout, '%s logged out.')
111

    
112

    
113
def send_verification(user, template_name='im/activation_email.txt'):
114
    """
115
    Send email to user to verify his/her email and activate his/her account.
116

117
    Raises SendVerificationError
118
    """
119
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
120
                                  quote(user.auth_token),
121
                                  quote(urljoin(BASEURL, reverse('index'))))
122
    message = render_to_string(template_name, {
123
                               'user': user,
124
                               'url': url,
125
                               'baseurl': BASEURL,
126
                               'site_name': SITENAME,
127
                               'support': DEFAULT_CONTACT_EMAIL})
128
    sender = settings.SERVER_EMAIL
129
    try:
130
        send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email],
131
                  connection=get_connection())
132

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

    
140

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

    
146

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

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

    
171

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

    
177

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

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

    
202

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

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

    
231

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

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

    
256

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

    
275

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

    
294

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

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

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

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

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

    
341

    
342
class SendMailError(Exception):
343
    pass
344

    
345

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

    
351

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

    
357

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

    
363

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

    
369

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

    
375

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

    
381

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

    
387

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

    
392

    
393
### PROJECT VIEWS ###
394

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

    
399
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
400

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

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

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

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

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

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

    
443
def get_user_by_uuid(uuid):
444
    try:
445
        return AstakosUser.objects.get(uuid=uuid)
446
    except AstakosUser.DoesNotExist:
447
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
448

    
449
def create_membership(project, user):
450
    if isinstance(project, int):
451
        project = get_project_by_id(project)
452
    if isinstance(user, int):
453
        user = get_user_by_id(user)
454
    m = ProjectMembership(
455
        project=project,
456
        person=user,
457
        request_date=datetime.now())
458
    try:
459
        m.save()
460
    except IntegrityError, e:
461
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
462
    else:
463
        return m
464

    
465
def get_membership_for_update(project, user):
466
    if isinstance(project, int):
467
        project = get_project_by_id(project)
468
    if isinstance(user, int):
469
        user = get_user_by_id(user)
470
    try:
471
        sfu = ProjectMembership.objects.select_for_update()
472
        m = sfu.get(project=project, person=user)
473
        if m.is_pending:
474
            raise PendingMembershipError()
475
        return m
476
    except ProjectMembership.DoesNotExist:
477
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
478

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

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

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

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

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

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

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

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

    
518
    membership.accept()
519
    sync_projects()
520

    
521
    membership_change_notify(project, membership.person, 'accepted')
522

    
523
    return membership
524

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

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

    
537
    membership.reject()
538

    
539
    membership_change_notify(project, membership.person, 'rejected')
540

    
541
    return membership
542

    
543
def remove_membership_checks(project, request_user=None):
544
    checkAllowed(project, request_user)
545
    checkAlive(project)
546

    
547
    leave_policy = project.application.member_leave_policy
548
    if leave_policy == CLOSED_POLICY:
549
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
550

    
551
def remove_membership(project_id, user, request_user=None):
552
    project = get_project_for_update(project_id)
553
    remove_membership_checks(project, request_user)
554
    membership = get_membership_for_update(project, user)
555
    if not membership.can_remove():
556
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
557
        raise PermissionDenied(m)
558

    
559
    membership.remove()
560
    sync_projects()
561

    
562
    membership_change_notify(project, membership.person, 'removed')
563

    
564
    return membership
565

    
566
def enroll_member(project_id, user, request_user=None):
567
    project = get_project_for_update(project_id)
568
    accept_membership_checks(project, request_user)
569
    membership = create_membership(project_id, user)
570

    
571
    if not membership.can_accept():
572
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
573
        raise PermissionDenied(m)
574

    
575
    membership.accept()
576
    sync_projects()
577

    
578
    # TODO send proper notification
579
    return membership
580

    
581
def leave_project_checks(project):
582
    checkAlive(project)
583

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

    
588
def leave_project(project_id, user_id):
589
    project = get_project_for_update(project_id)
590
    leave_project_checks(project)
591
    membership = get_membership_for_update(project, user_id)
592
    if not membership.can_leave():
593
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
594
        raise PermissionDenied(m)
595

    
596
    leave_policy = project.application.member_leave_policy
597
    if leave_policy == AUTO_ACCEPT_POLICY:
598
        membership.remove()
599
        sync_projects()
600
    else:
601
        membership.leave_request_date = datetime.now()
602
        membership.save()
603
    return membership
604

    
605
def join_project_checks(project):
606
    checkAlive(project)
607

    
608
    join_policy = project.application.member_join_policy
609
    if join_policy == CLOSED_POLICY:
610
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
611

    
612
def join_project(project_id, user_id):
613
    project = get_project_for_update(project_id)
614
    join_project_checks(project)
615
    membership = create_membership(project, user_id)
616

    
617
    join_policy = project.application.member_join_policy
618
    if (join_policy == AUTO_ACCEPT_POLICY and
619
        not project.violates_members_limit(adding=1)):
620
        membership.accept()
621
        sync_projects()
622
    return membership
623

    
624
def submit_application(kw, request_user=None):
625

    
626
    kw['applicant'] = request_user
627

    
628
    precursor_id = kw.get('precursor_application', None)
629
    if precursor_id is not None:
630
        sfu = ProjectApplication.objects.select_for_update()
631
        precursor = sfu.get(id=precursor_id)
632
        kw['precursor_application'] = precursor
633

    
634
        if request_user and \
635
            (not precursor.owner == request_user and \
636
                not request_user.is_superuser):
637
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
638

    
639
    application = models_submit_application(**kw)
640

    
641
    application_submit_notify(application)
642
    return application
643

    
644
def cancel_application(application_id, request_user=None):
645
    application = get_application_for_update(application_id)
646
    checkAllowed(application, request_user)
647

    
648
    if not application.can_cancel():
649
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
650
                application.id, application.state_display()))
651
        raise PermissionDenied(m)
652

    
653
    application.cancel()
654

    
655
def dismiss_application(application_id, request_user=None):
656
    application = get_application_for_update(application_id)
657
    checkAllowed(application, request_user)
658

    
659
    if not application.can_dismiss():
660
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
661
                application.id, application.state_display()))
662
        raise PermissionDenied(m)
663

    
664
    application.dismiss()
665

    
666
def deny_application(application_id):
667
    application = get_application_for_update(application_id)
668

    
669
    if not application.can_deny():
670
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
671
                application.id, application.state_display()))
672
        raise PermissionDenied(m)
673

    
674
    application.deny()
675
    application_deny_notify(application)
676

    
677
def approve_application(app):
678

    
679
    app_id = app if isinstance(app, int) else app.id
680

    
681
    try:
682
        objects = ProjectApplication.objects.select_for_update()
683
        application = objects.get(id=app_id)
684
    except ProjectApplication.DoesNotExist:
685
        raise PermissionDenied()
686

    
687
    if not application.can_approve():
688
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
689
                application.id, application.state_display()))
690
        raise PermissionDenied(m)
691

    
692
    application.approve()
693
    sync_projects()
694

    
695
    application_approve_notify(application)
696

    
697
def check_expiration(execute=False):
698
    objects = Project.objects
699
    expired = objects.expired_projects()
700
    if execute:
701
        for project in expired:
702
            terminate(project.id)
703

    
704
    return [project.expiration_info() for project in expired]
705

    
706
def terminate(project_id):
707
    project = get_project_for_update(project_id)
708
    checkAlive(project)
709

    
710
    project.terminate()
711
    sync_projects()
712

    
713
    project_termination_notify(project)
714

    
715
def suspend(project_id):
716
    project = get_project_by_id(project_id)
717
    checkAlive(project)
718

    
719
    project.suspend()
720
    sync_projects()
721

    
722
    project_suspension_notify(project)
723

    
724
def resume(project_id):
725
    project = get_project_for_update(project_id)
726

    
727
    if not project.is_suspended:
728
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
729
        raise PermissionDenied(m)
730

    
731
    project.resume()
732
    sync_projects()
733

    
734
def get_by_chain_or_404(chain_id):
735
    try:
736
        project = Project.objects.get(id=chain_id)
737
        application = project.application
738
        return project, application
739
    except:
740
        application = ProjectApplication.objects.latest_of_chain(chain_id)
741
        if application is None:
742
            raise Http404
743
        else:
744
            return None, application