Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 466cc12c

History | View | Annotate | Download (25.6 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, membership_enroll_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_related_project_id(application_id):
408
    try:
409
        app = ProjectApplication.objects.get(id=application_id)
410
        chain = app.chain
411
        project = Project.objects.get(id=chain)
412
        return chain
413
    except:
414
        return None
415

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

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

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

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

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

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

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

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

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

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

    
499
def 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 accept_membership(project_id, user, request_user=None):
511
    project = get_project_for_update(project_id)
512
    accept_membership_checks(project, request_user)
513

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

    
519
    membership.accept()
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 cancel_membership_checks(project):
544
    checkAlive(project)
545

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

    
554
    membership.cancel()
555

    
556
def remove_membership_checks(project, request_user=None):
557
    checkAllowed(project, request_user)
558
    checkAlive(project)
559

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

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

    
572
    membership.remove()
573

    
574
    membership_change_notify(project, membership.person, 'removed')
575

    
576
    return membership
577

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

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

    
587
    membership.accept()
588
    membership_enroll_notify(project, membership.person)
589

    
590
    return membership
591

    
592
def leave_project_checks(project):
593
    checkAlive(project)
594

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

    
599
def can_leave_request(project, user):
600
    leave_policy = project.application.member_leave_policy
601
    if leave_policy == CLOSED_POLICY:
602
        return False
603
    m = user.get_membership(project)
604
    if m is None:
605
        return False
606
    if m.state != ProjectMembership.ACCEPTED:
607
        return False
608
    return True
609

    
610
def leave_project(project_id, user_id):
611
    project = get_project_for_update(project_id)
612
    leave_project_checks(project)
613
    membership = get_membership_for_update(project, user_id)
614
    if not membership.can_leave():
615
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
616
        raise PermissionDenied(m)
617

    
618
    leave_policy = project.application.member_leave_policy
619
    if leave_policy == AUTO_ACCEPT_POLICY:
620
        membership.remove()
621
    else:
622
        membership.leave_request_date = datetime.now()
623
        membership.save()
624
    return membership
625

    
626
def join_project_checks(project):
627
    checkAlive(project)
628

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

    
633
def can_join_request(project, user):
634
    join_policy = project.application.member_join_policy
635
    if join_policy == CLOSED_POLICY:
636
        return False
637
    m = user.get_membership(project)
638
    if m:
639
        return False
640
    return True
641

    
642
def join_project(project_id, user_id):
643
    project = get_project_for_update(project_id)
644
    join_project_checks(project)
645
    membership = create_membership(project, user_id)
646

    
647
    join_policy = project.application.member_join_policy
648
    if (join_policy == AUTO_ACCEPT_POLICY and
649
        not project.violates_members_limit(adding=1)):
650
        membership.accept()
651
    return membership
652

    
653
def submit_application(kw, request_user=None):
654

    
655
    kw['applicant'] = request_user
656
    resource_policies = kw.pop('resource_policies', None)
657

    
658
    precursor = None
659
    precursor_id = kw.get('precursor_application', None)
660
    if precursor_id is not None:
661
        sfu = ProjectApplication.objects.select_for_update()
662
        precursor = sfu.get(id=precursor_id)
663
        kw['precursor_application'] = precursor
664

    
665
        if (request_user and
666
            (not precursor.owner == request_user and
667
             not request_user.is_superuser
668
             and not request_user.is_project_admin())):
669
            m = _(astakos_messages.NOT_ALLOWED)
670
            raise PermissionDenied(m)
671

    
672
    application = ProjectApplication(**kw)
673

    
674
    if precursor is None:
675
        application.chain = new_chain()
676
    else:
677
        chain = precursor.chain
678
        application.chain = chain
679
        sfu = ProjectApplication.objects.select_for_update()
680
        pending = sfu.filter(chain=chain, state=ProjectApplication.PENDING)
681
        for app in pending:
682
            app.state = ProjectApplication.REPLACED
683
            app.save()
684

    
685
    application.save()
686
    application.resource_policies = resource_policies
687
    application_submit_notify(application)
688
    return application
689

    
690
def cancel_application(application_id, request_user=None):
691
    application = get_application_for_update(application_id)
692
    checkAllowed(application, request_user)
693

    
694
    if not application.can_cancel():
695
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
696
                application.id, application.state_display()))
697
        raise PermissionDenied(m)
698

    
699
    application.cancel()
700

    
701
def dismiss_application(application_id, request_user=None):
702
    application = get_application_for_update(application_id)
703
    checkAllowed(application, request_user)
704

    
705
    if not application.can_dismiss():
706
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
707
                application.id, application.state_display()))
708
        raise PermissionDenied(m)
709

    
710
    application.dismiss()
711

    
712
def deny_application(application_id):
713
    application = get_application_for_update(application_id)
714

    
715
    if not application.can_deny():
716
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
717
                application.id, application.state_display()))
718
        raise PermissionDenied(m)
719

    
720
    application.deny()
721
    application_deny_notify(application)
722

    
723
def approve_application(app_id):
724

    
725
    try:
726
        objects = ProjectApplication.objects.select_for_update()
727
        application = objects.get(id=app_id)
728
    except ProjectApplication.DoesNotExist:
729
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
730
        raise PermissionDenied(m)
731

    
732
    if not application.can_approve():
733
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
734
                application.id, application.state_display()))
735
        raise PermissionDenied(m)
736

    
737
    application.approve()
738
    application_approve_notify(application)
739

    
740
def check_expiration(execute=False):
741
    objects = Project.objects
742
    expired = objects.expired_projects()
743
    if execute:
744
        for project in expired:
745
            terminate(project.id)
746

    
747
    return [project.expiration_info() for project in expired]
748

    
749
def terminate(project_id):
750
    project = get_project_for_update(project_id)
751
    checkAlive(project)
752

    
753
    project.terminate()
754

    
755
    project_termination_notify(project)
756

    
757
def suspend(project_id):
758
    project = get_project_by_id(project_id)
759
    checkAlive(project)
760

    
761
    project.suspend()
762

    
763
    project_suspension_notify(project)
764

    
765
def resume(project_id):
766
    project = get_project_for_update(project_id)
767

    
768
    if not project.is_suspended:
769
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
770
        raise PermissionDenied(m)
771

    
772
    project.resume()
773

    
774
def get_by_chain_or_404(chain_id):
775
    try:
776
        project = Project.objects.get(id=chain_id)
777
        application = project.application
778
        return project, application
779
    except:
780
        application = ProjectApplication.objects.latest_of_chain(chain_id)
781
        if application is None:
782
            raise Http404
783
        else:
784
            return None, application