Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.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

    
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
                  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 = user.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(project_application_id, user, request_user=None):
498
    """
499
        Raises:
500
            django.core.exceptions.PermissionDenied
501
            IOError
502
    """
503
    project_id = get_project_id_of_application_id(project_application_id)
504
    return do_accept_membership(project_id, user, request_user)
505

    
506
def do_accept_membership_checks(project, request_user):
507
    checkAllowed(project, request_user)
508
    checkAlive(project)
509

    
510
    join_policy = project.application.member_join_policy
511
    if join_policy == CLOSED_POLICY:
512
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
513

    
514
    if project.violates_members_limit(adding=1):
515
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
516

    
517
def do_accept_membership(project_id, user, request_user=None):
518
    project = get_project_for_update(project_id)
519
    do_accept_membership_checks(project, request_user)
520

    
521
    membership = get_membership_for_update(project, user)
522
    membership.accept()
523
    sync_projects()
524

    
525
    membership_change_notify(project, membership.person, 'accepted')
526

    
527
    return membership
528

    
529
def reject_membership(project_application_id, user, request_user=None):
530
    """
531
        Raises:
532
            django.core.exceptions.PermissionDenied
533
            IOError
534
    """
535
    project_id = get_project_id_of_application_id(project_application_id)
536
    return do_reject_membership(project_id, user, request_user)
537

    
538
def do_reject_membership_checks(project, request_user):
539
    checkAllowed(project, request_user)
540
    checkAlive(project)
541

    
542
def do_reject_membership(project_id, user, request_user=None):
543
    project = get_project_for_update(project_id)
544
    do_reject_membership_checks(project, request_user)
545

    
546
    membership = get_membership_for_update(project, user)
547
    membership.reject()
548

    
549
    membership_change_notify(project, membership.person, 'rejected')
550

    
551
    return membership
552

    
553
def remove_membership(project_application_id, user, request_user=None):
554
    """
555
        Raises:
556
            django.core.exceptions.PermissionDenied
557
            IOError
558
    """
559
    project_id = get_project_id_of_application_id(project_application_id)
560
    return do_remove_membership(project_id, user, request_user)
561

    
562
def do_remove_membership_checks(project, membership, request_user=None):
563
    checkAllowed(project, request_user)
564
    checkAlive(project)
565

    
566
    leave_policy = project.application.member_leave_policy
567
    if leave_policy == CLOSED_POLICY:
568
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
569

    
570
def do_remove_membership(project_id, user, request_user=None):
571
    project = get_project_for_update(project_id)
572
    do_remove_membership_checks(project, request_user)
573

    
574
    membership = get_membership_for_update(project, user)
575
    membership.remove()
576
    sync_projects()
577

    
578
    membership_change_notify(project, membership.person, 'removed')
579

    
580
    return membership
581

    
582
def enroll_member(project_application_id, user, request_user=None):
583
    project_id = get_project_id_of_application_id(project_application_id)
584
    return do_enroll_member(project_id, user, request_user)
585

    
586
def do_enroll_member(project_id, user, request_user=None):
587
    project = get_project_for_update(project_id)
588
    do_accept_membership_checks(project, request_user)
589

    
590
    membership = create_membership(project_id, user)
591
    membership.accept()
592
    sync_projects()
593

    
594
    # TODO send proper notification
595
    return membership
596

    
597
def leave_project(project_application_id, user_id):
598
    """
599
        Raises:
600
            django.core.exceptions.PermissionDenied
601
            IOError
602
    """
603
    project_id = get_project_id_of_application_id(project_application_id)
604
    return do_leave_project(project_id, user_id)
605

    
606
def do_leave_project_checks(project):
607
    checkAlive(project)
608

    
609
    leave_policy = project.application.member_leave_policy
610
    if leave_policy == CLOSED_POLICY:
611
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
612

    
613
def do_leave_project(project_id, user_id):
614
    project = get_project_for_update(project_id)
615
    do_leave_project_checks(project)
616

    
617
    membership = get_membership_for_update(project, user_id)
618

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

    
628
def join_project(project_application_id, user_id):
629
    """
630
        Raises:
631
            django.core.exceptions.PermissionDenied
632
            IOError
633
    """
634
    project_id = get_project_id_of_application_id(project_application_id)
635
    return do_join_project(project_id, user_id)
636

    
637
def do_join_project_checks(project):
638
    checkAlive(project)
639

    
640
    join_policy = project.application.member_join_policy
641
    if join_policy == CLOSED_POLICY:
642
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
643

    
644
def do_join_project(project_id, user_id):
645
    project = get_project_for_update(project_id)
646
    do_join_project_checks(project)
647

    
648
    membership = create_membership(project, user_id)
649

    
650
    join_policy = project.application.member_join_policy
651
    if (join_policy == AUTO_ACCEPT_POLICY and
652
        not project.violates_members_limit(adding=1)):
653
        membership.accept()
654
        sync_projects()
655
    return membership
656

    
657
def submit_application(kw, request_user=None):
658

    
659
    kw['applicant'] = request_user
660

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

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

    
672
    application = models_submit_application(**kw)
673

    
674
    application_submit_notify(application)
675
    return application
676

    
677
def cancel_application(application_id, request_user=None):
678
    application = get_application_for_update(application_id)
679
    checkAllowed(application, request_user)
680

    
681
    if application.state != ProjectApplication.PENDING:
682
        raise PermissionDenied()
683

    
684
    application.cancel()
685

    
686
def dismiss_application(application_id, request_user=None):
687
    application = get_application_for_update(application_id)
688
    checkAllowed(application, request_user)
689

    
690
    if application.state != ProjectApplication.DENIED:
691
        raise PermissionDenied()
692

    
693
    application.dismiss()
694

    
695
def deny_application(application_id):
696
    application = get_application_for_update(application_id)
697
    if application.state != ProjectApplication.PENDING:
698
        raise PermissionDenied()
699

    
700
    application.deny()
701
    application_deny_notify(application)
702

    
703
def approve_application(app):
704

    
705
    app_id = app if isinstance(app, int) else app.id
706

    
707
    try:
708
        objects = ProjectApplication.objects.select_for_update()
709
        application = objects.get(id=app_id)
710
    except ProjectApplication.DoesNotExist:
711
        raise PermissionDenied()
712

    
713
    application.approve()
714
    sync_projects()
715

    
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
    sync_projects()
733

    
734
    project_termination_notify(project)
735

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

    
740
    project.suspend()
741
    sync_projects()
742

    
743
    project_suspension_notify(project)
744

    
745
def resume(project_id):
746
    project = get_project_for_update(project_id)
747

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

    
752
    project.resume()
753
    sync_projects()