Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 9301c7f9

History | View | Annotate | Download (26.5 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
    membership_request_notify, membership_leave_request_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 = settings.SERVER_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
             'support': DEFAULT_CONTACT_EMAIL}
284
        from_email = settings.SERVER_EMAIL
285
        send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT), t.render(Context(c)),
286
                  from_email, [ec.new_email_address],
287
                  connection=get_connection())
288
    except (SMTPException, socket.error) as e:
289
        logger.exception(e)
290
        raise ChangeEmailError()
291
    else:
292
        msg = 'Sent change email for %s' % ec.user.email
293
        logger.log(LOGGING_LEVEL, msg)
294

    
295

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

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

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

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

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

    
342

    
343
class SendMailError(Exception):
344
    pass
345

    
346

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

    
352

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

    
358

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

    
364

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

    
370

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

    
376

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

    
382

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

    
388

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

    
393

    
394
### PROJECT VIEWS ###
395

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

    
400
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
401

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

    
409
def get_related_project_id(application_id):
410
    try:
411
        app = ProjectApplication.objects.get(id=application_id)
412
        chain = app.chain
413
        project = Project.objects.get(id=chain)
414
        return chain
415
    except:
416
        return None
417

    
418
def get_chain_of_application_id(application_id):
419
    try:
420
        app = ProjectApplication.objects.get(id=application_id)
421
        chain = app.chain
422
        return chain.chain
423
    except:
424
        return None
425

    
426
def get_project_by_id(project_id):
427
    try:
428
        return Project.objects.get(id=project_id)
429
    except Project.DoesNotExist:
430
        raise IOError(
431
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
432

    
433
def get_project_by_name(name):
434
    try:
435
        return Project.objects.get(name=name)
436
    except Project.DoesNotExist:
437
        raise IOError(
438
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
439

    
440

    
441
def get_project_for_update(project_id):
442
    try:
443
        return Project.objects.select_for_update().get(id=project_id)
444
    except Project.DoesNotExist:
445
        raise IOError(
446
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
447

    
448
def get_application_for_update(application_id):
449
    try:
450
        objects = ProjectApplication.objects.select_for_update()
451
        return objects.get(id=application_id)
452
    except ProjectApplication.DoesNotExist:
453
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
454
        raise IOError(m)
455

    
456
def get_user_by_id(user_id):
457
    try:
458
        return AstakosUser.objects.get(id=user_id)
459
    except AstakosUser.DoesNotExist:
460
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
461

    
462
def get_user_by_uuid(uuid):
463
    try:
464
        return AstakosUser.objects.get(uuid=uuid)
465
    except AstakosUser.DoesNotExist:
466
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
467

    
468
def create_membership(project, user):
469
    if isinstance(project, (int, long)):
470
        project = get_project_by_id(project)
471
    if isinstance(user, (int, long)):
472
        user = get_user_by_id(user)
473

    
474
    if not user.is_active:
475
        m = _(astakos_messages.ACCOUNT_NOT_ACTIVE)
476
        raise PermissionDenied(m)
477

    
478
    m = ProjectMembership(
479
        project=project,
480
        person=user,
481
        request_date=datetime.now())
482
    try:
483
        m.save()
484
    except IntegrityError, e:
485
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
486
    else:
487
        return m
488

    
489
def get_membership_for_update(project, user):
490
    if isinstance(project, (int, long)):
491
        project = get_project_by_id(project)
492
    if isinstance(user, (int, long)):
493
        user = get_user_by_id(user)
494
    try:
495
        sfu = ProjectMembership.objects.select_for_update()
496
        m = sfu.get(project=project, person=user)
497
        if m.is_pending:
498
            raise PendingMembershipError()
499
        return m
500
    except ProjectMembership.DoesNotExist:
501
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
502

    
503
def checkAllowed(entity, request_user):
504
    if isinstance(entity, Project):
505
        application = entity.application
506
    elif isinstance(entity, ProjectApplication):
507
        application = entity
508
    else:
509
        m = "%s not a Project nor a ProjectApplication" % (entity,)
510
        raise ValueError(m)
511

    
512
    if request_user and \
513
        (not application.owner == request_user and \
514
            not request_user.is_superuser):
515
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
516

    
517
def checkAlive(project):
518
    if not project.is_alive:
519
        raise PermissionDenied(
520
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
521

    
522
def accept_membership_checks(project, request_user):
523
    checkAllowed(project, request_user)
524
    checkAlive(project)
525

    
526
    join_policy = project.application.member_join_policy
527
    if join_policy == CLOSED_POLICY:
528
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
529

    
530
    if project.violates_members_limit(adding=1):
531
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
532

    
533
def accept_membership(project_id, user, request_user=None):
534
    project = get_project_for_update(project_id)
535
    accept_membership_checks(project, request_user)
536

    
537
    membership = get_membership_for_update(project, user)
538
    if not membership.can_accept():
539
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
540
        raise PermissionDenied(m)
541

    
542
    membership.accept()
543

    
544
    membership_change_notify(project, membership.person, 'accepted')
545

    
546
    return membership
547

    
548
def reject_membership_checks(project, request_user):
549
    checkAllowed(project, request_user)
550
    checkAlive(project)
551

    
552
def reject_membership(project_id, user, request_user=None):
553
    project = get_project_for_update(project_id)
554
    reject_membership_checks(project, request_user)
555
    membership = get_membership_for_update(project, user)
556
    if not membership.can_reject():
557
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
558
        raise PermissionDenied(m)
559

    
560
    membership.reject()
561

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

    
564
    return membership
565

    
566
def cancel_membership_checks(project):
567
    checkAlive(project)
568

    
569
def cancel_membership(project_id, user_id):
570
    project = get_project_for_update(project_id)
571
    cancel_membership_checks(project)
572
    membership = get_membership_for_update(project, user_id)
573
    if not membership.can_cancel():
574
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
575
        raise PermissionDenied(m)
576

    
577
    membership.cancel()
578

    
579
def remove_membership_checks(project, request_user=None):
580
    checkAllowed(project, request_user)
581
    checkAlive(project)
582

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

    
587
def remove_membership(project_id, user, request_user=None):
588
    project = get_project_for_update(project_id)
589
    remove_membership_checks(project, request_user)
590
    membership = get_membership_for_update(project, user)
591
    if not membership.can_remove():
592
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
593
        raise PermissionDenied(m)
594

    
595
    membership.remove()
596

    
597
    membership_change_notify(project, membership.person, 'removed')
598

    
599
    return membership
600

    
601
def enroll_member(project_id, user, request_user=None):
602
    project = get_project_for_update(project_id)
603
    accept_membership_checks(project, request_user)
604
    membership = create_membership(project_id, user)
605

    
606
    if not membership.can_accept():
607
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
608
        raise PermissionDenied(m)
609

    
610
    membership.accept()
611
    membership_enroll_notify(project, membership.person)
612

    
613
    return membership
614

    
615
def leave_project_checks(project):
616
    checkAlive(project)
617

    
618
    leave_policy = project.application.member_leave_policy
619
    if leave_policy == CLOSED_POLICY:
620
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
621

    
622
def can_leave_request(project, user):
623
    leave_policy = project.application.member_leave_policy
624
    if leave_policy == CLOSED_POLICY:
625
        return False
626
    m = user.get_membership(project)
627
    if m is None:
628
        return False
629
    if m.state != ProjectMembership.ACCEPTED:
630
        return False
631
    return True
632

    
633
def leave_project(project_id, user_id):
634
    project = get_project_for_update(project_id)
635
    leave_project_checks(project)
636
    membership = get_membership_for_update(project, user_id)
637
    if not membership.can_leave():
638
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
639
        raise PermissionDenied(m)
640

    
641
    auto_accepted = False
642
    leave_policy = project.application.member_leave_policy
643
    if leave_policy == AUTO_ACCEPT_POLICY:
644
        membership.remove()
645
        auto_accepted = True
646
    else:
647
        membership.leave_request()
648
        membership_leave_request_notify(project, membership.person)
649
    return auto_accepted
650

    
651
def join_project_checks(project):
652
    checkAlive(project)
653

    
654
    join_policy = project.application.member_join_policy
655
    if join_policy == CLOSED_POLICY:
656
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
657

    
658
def can_join_request(project, user):
659
    join_policy = project.application.member_join_policy
660
    if join_policy == CLOSED_POLICY:
661
        return False
662
    m = user.get_membership(project)
663
    if m:
664
        return False
665
    return True
666

    
667
def join_project(project_id, user_id):
668
    project = get_project_for_update(project_id)
669
    join_project_checks(project)
670
    membership = create_membership(project, user_id)
671

    
672
    auto_accepted = False
673
    join_policy = project.application.member_join_policy
674
    if (join_policy == AUTO_ACCEPT_POLICY and
675
        not project.violates_members_limit(adding=1)):
676
        membership.accept()
677
        auto_accepted = True
678
    else:
679
        membership_request_notify(project, membership.person)
680

    
681
    return auto_accepted
682

    
683
def submit_application(kw, request_user=None):
684

    
685
    kw['applicant'] = request_user
686
    resource_policies = kw.pop('resource_policies', None)
687

    
688
    precursor = None
689
    precursor_id = kw.get('precursor_application', None)
690
    if precursor_id is not None:
691
        sfu = ProjectApplication.objects.select_for_update()
692
        precursor = sfu.get(id=precursor_id)
693
        kw['precursor_application'] = precursor
694

    
695
        if (request_user and
696
            (not precursor.owner == request_user and
697
             not request_user.is_superuser
698
             and not request_user.is_project_admin())):
699
            m = _(astakos_messages.NOT_ALLOWED)
700
            raise PermissionDenied(m)
701

    
702
    application = ProjectApplication(**kw)
703

    
704
    if precursor is None:
705
        application.chain = new_chain()
706
    else:
707
        chain = precursor.chain
708
        application.chain = chain
709
        sfu = ProjectApplication.objects.select_for_update()
710
        pending = sfu.filter(chain=chain, state=ProjectApplication.PENDING)
711
        for app in pending:
712
            app.state = ProjectApplication.REPLACED
713
            app.save()
714

    
715
    application.save()
716
    application.resource_policies = resource_policies
717
    application_submit_notify(application)
718
    return application
719

    
720
def cancel_application(application_id, request_user=None):
721
    application = get_application_for_update(application_id)
722
    checkAllowed(application, request_user)
723

    
724
    if not application.can_cancel():
725
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
726
                application.id, application.state_display()))
727
        raise PermissionDenied(m)
728

    
729
    application.cancel()
730

    
731
def dismiss_application(application_id, request_user=None):
732
    application = get_application_for_update(application_id)
733
    checkAllowed(application, request_user)
734

    
735
    if not application.can_dismiss():
736
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
737
                application.id, application.state_display()))
738
        raise PermissionDenied(m)
739

    
740
    application.dismiss()
741

    
742
def deny_application(application_id):
743
    application = get_application_for_update(application_id)
744

    
745
    if not application.can_deny():
746
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
747
                application.id, application.state_display()))
748
        raise PermissionDenied(m)
749

    
750
    application.deny()
751
    application_deny_notify(application)
752

    
753
def approve_application(app_id):
754

    
755
    try:
756
        objects = ProjectApplication.objects.select_for_update()
757
        application = objects.get(id=app_id)
758
    except ProjectApplication.DoesNotExist:
759
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
760
        raise PermissionDenied(m)
761

    
762
    if not application.can_approve():
763
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
764
                application.id, application.state_display()))
765
        raise PermissionDenied(m)
766

    
767
    application.approve()
768
    application_approve_notify(application)
769

    
770
def check_expiration(execute=False):
771
    objects = Project.objects
772
    expired = objects.expired_projects()
773
    if execute:
774
        for project in expired:
775
            terminate(project.id)
776

    
777
    return [project.expiration_info() for project in expired]
778

    
779
def terminate(project_id):
780
    project = get_project_for_update(project_id)
781
    checkAlive(project)
782

    
783
    project.terminate()
784

    
785
    project_termination_notify(project)
786

    
787
def suspend(project_id):
788
    project = get_project_by_id(project_id)
789
    checkAlive(project)
790

    
791
    project.suspend()
792

    
793
    project_suspension_notify(project)
794

    
795
def resume(project_id):
796
    project = get_project_for_update(project_id)
797

    
798
    if not project.is_suspended:
799
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
800
        raise PermissionDenied(m)
801

    
802
    project.resume()
803

    
804
def get_by_chain_or_404(chain_id):
805
    try:
806
        project = Project.objects.get(id=chain_id)
807
        application = project.application
808
        return project, application
809
    except:
810
        application = ProjectApplication.objects.latest_of_chain(chain_id)
811
        if application is None:
812
            raise Http404
813
        else:
814
            return None, application