Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 3c049f6d

History | View | Annotate | Download (24.2 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 get_user_by_uuid(uuid):
436
    try:
437
        return AstakosUser.objects.get(uuid=uuid)
438
    except AstakosUser.DoesNotExist:
439
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
440

    
441
def create_membership(project, user):
442
    if isinstance(project, int):
443
        project = get_project_by_id(project)
444
    if isinstance(user, int):
445
        user = get_user_by_id(user)
446
    m = ProjectMembership(
447
        project=project,
448
        person=user,
449
        request_date=datetime.now())
450
    try:
451
        m.save()
452
    except IntegrityError, e:
453
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
454
    else:
455
        return m
456

    
457
def get_membership_for_update(project, user):
458
    if isinstance(project, int):
459
        project = get_project_by_id(project)
460
    if isinstance(user, int):
461
        user = get_user_by_id(user)
462
    try:
463
        sfu = ProjectMembership.objects.select_for_update()
464
        m = sfu.get(project=project, person=user)
465
        if m.is_pending:
466
            raise PendingMembershipError()
467
        return m
468
    except ProjectMembership.DoesNotExist:
469
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
470

    
471
def checkAllowed(entity, request_user):
472
    if isinstance(entity, Project):
473
        application = entity.application
474
    elif isinstance(entity, ProjectApplication):
475
        application = entity
476
    else:
477
        m = "%s not a Project nor a ProjectApplication" % (entity,)
478
        raise ValueError(m)
479

    
480
    if request_user and \
481
        (not application.owner == request_user and \
482
            not request_user.is_superuser):
483
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
484

    
485
def checkAlive(project):
486
    if not project.is_alive:
487
        raise PermissionDenied(
488
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
489

    
490
def accept_membership(project_application_id, user, request_user=None):
491
    """
492
        Raises:
493
            django.core.exceptions.PermissionDenied
494
            IOError
495
    """
496
    project_id = get_project_id_of_application_id(project_application_id)
497
    return do_accept_membership(project_id, user, request_user)
498

    
499
def do_accept_membership_checks(project, request_user):
500
    checkAllowed(project, request_user)
501
    checkAlive(project)
502

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

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

    
510
def do_accept_membership(project_id, user, request_user=None):
511
    project = get_project_for_update(project_id)
512
    do_accept_membership_checks(project, request_user)
513

    
514
    membership = get_membership_for_update(project, user)
515
    membership.accept()
516
    sync_projects()
517

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

    
520
    return membership
521

    
522
def reject_membership(project_application_id, user, request_user=None):
523
    """
524
        Raises:
525
            django.core.exceptions.PermissionDenied
526
            IOError
527
    """
528
    project_id = get_project_id_of_application_id(project_application_id)
529
    return do_reject_membership(project_id, user, request_user)
530

    
531
def do_reject_membership_checks(project, request_user):
532
    checkAllowed(project, request_user)
533
    checkAlive(project)
534

    
535
def do_reject_membership(project_id, user, request_user=None):
536
    project = get_project_for_update(project_id)
537
    do_reject_membership_checks(project, request_user)
538

    
539
    membership = get_membership_for_update(project, user)
540
    membership.reject()
541

    
542
    membership_change_notify(project, membership.person, 'rejected')
543

    
544
    return membership
545

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

    
555
def do_remove_membership_checks(project, membership, request_user=None):
556
    checkAllowed(project, request_user)
557
    checkAlive(project)
558

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

    
563
def do_remove_membership(project_id, user, request_user=None):
564
    project = get_project_for_update(project_id)
565
    do_remove_membership_checks(project, request_user)
566

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

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

    
573
    return membership
574

    
575
def enroll_member(project_application_id, user, request_user=None):
576
    project_id = get_project_id_of_application_id(project_application_id)
577
    return do_enroll_member(project_id, user, request_user)
578

    
579
def do_enroll_member(project_id, user, request_user=None):
580
    project = get_project_for_update(project_id)
581
    do_accept_membership_checks(project, request_user)
582

    
583
    membership = create_membership(project_id, user)
584
    membership.accept()
585
    sync_projects()
586

    
587
    # TODO send proper notification
588
    return membership
589

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

    
599
def do_leave_project_checks(project):
600
    checkAlive(project)
601

    
602
    leave_policy = project.application.member_leave_policy
603
    if leave_policy == CLOSED_POLICY:
604
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
605

    
606
def do_leave_project(project_id, user_id):
607
    project = get_project_for_update(project_id)
608
    do_leave_project_checks(project)
609

    
610
    membership = get_membership_for_update(project, user_id)
611

    
612
    leave_policy = project.application.member_leave_policy
613
    if leave_policy == AUTO_ACCEPT_POLICY:
614
        membership.remove()
615
        sync_projects()
616
    else:
617
        membership.leave_request_date = datetime.now()
618
        membership.save()
619
    return membership
620

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

    
630
def do_join_project_checks(project):
631
    checkAlive(project)
632

    
633
    join_policy = project.application.member_join_policy
634
    if join_policy == CLOSED_POLICY:
635
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
636

    
637
def do_join_project(project_id, user_id):
638
    project = get_project_for_update(project_id)
639
    do_join_project_checks(project)
640

    
641
    membership = create_membership(project, user_id)
642

    
643
    join_policy = project.application.member_join_policy
644
    if (join_policy == AUTO_ACCEPT_POLICY and
645
        not project.violates_members_limit(adding=1)):
646
        membership.accept()
647
        sync_projects()
648
    return membership
649

    
650
def submit_application(kw, request_user=None):
651

    
652
    kw['applicant'] = request_user
653

    
654
    precursor_id = kw.get('precursor_application', None)
655
    if precursor_id is not None:
656
        sfu = ProjectApplication.objects.select_for_update()
657
        precursor = sfu.get(id=precursor_id)
658
        kw['precursor_application'] = precursor
659

    
660
        if request_user and \
661
            (not precursor.owner == request_user and \
662
                not request_user.is_superuser):
663
            raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
664

    
665
    application = models_submit_application(**kw)
666

    
667
    application_submit_notify(application)
668
    return application
669

    
670
def cancel_application(application_id, request_user=None):
671
    application = get_application_for_update(application_id)
672
    checkAllowed(application, request_user)
673

    
674
    if application.state != ProjectApplication.PENDING:
675
        raise PermissionDenied()
676

    
677
    application.cancel()
678

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

    
683
    if application.state != ProjectApplication.DENIED:
684
        raise PermissionDenied()
685

    
686
    application.dismiss()
687

    
688
def deny_application(application_id):
689
    application = get_application_for_update(application_id)
690
    if application.state != ProjectApplication.PENDING:
691
        raise PermissionDenied()
692

    
693
    application.deny()
694
    application_deny_notify(application)
695

    
696
def approve_application(app):
697

    
698
    app_id = app if isinstance(app, int) else app.id
699

    
700
    try:
701
        objects = ProjectApplication.objects.select_for_update()
702
        application = objects.get(id=app_id)
703
    except ProjectApplication.DoesNotExist:
704
        raise PermissionDenied()
705

    
706
    application.approve()
707
    sync_projects()
708

    
709
    application_approve_notify(application)
710

    
711
def check_expiration(execute=False):
712
    objects = Project.objects
713
    expired = objects.expired_projects()
714
    if execute:
715
        for project in expired:
716
            terminate(project.id)
717

    
718
    return [project.expiration_info() for project in expired]
719

    
720
def terminate(project_id):
721
    project = get_project_for_update(project_id)
722
    checkAlive(project)
723

    
724
    project.terminate()
725
    sync_projects()
726

    
727
    project_termination_notify(project)
728

    
729
def suspend(project_id):
730
    project = get_project_by_id(project_id)
731
    checkAlive(project)
732

    
733
    project.suspend()
734
    sync_projects()
735

    
736
    project_suspension_notify(project)
737

    
738
def resume(project_id):
739
    project = get_project_for_update(project_id)
740

    
741
    if not project.is_suspended:
742
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
743
        raise PermissionDenied(m)
744

    
745
    project.resume()
746
    sync_projects()