Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (26.3 kB)

1
# Copyright 2011, 2012, 2013 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, Invitation, 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, 'ec': ec}
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, inviter.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) % name)
439

    
440

    
441
def get_project_for_update(project_id):
442
    try:
443
        return Project.objects.get_for_update(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
        return ProjectApplication.objects.get_for_update(id=application_id)
451
    except ProjectApplication.DoesNotExist:
452
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
453
        raise IOError(m)
454

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

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

    
467
def create_membership(project, user):
468
    if isinstance(user, (int, long)):
469
        user = get_user_by_id(user)
470

    
471
    if not user.is_active:
472
        m = _(astakos_messages.ACCOUNT_NOT_ACTIVE)
473
        raise PermissionDenied(m)
474

    
475
    m, created = ProjectMembership.objects.get_or_create(
476
        project=project,
477
        person=user)
478

    
479
    if created:
480
        return m
481
    else:
482
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
483
        raise PermissionDenied(msg)
484

    
485

    
486
def get_membership_for_update(project, user):
487
    if isinstance(user, (int, long)):
488
        user = get_user_by_id(user)
489
    try:
490
        objs = ProjectMembership.objects
491
        m = objs.get_for_update(project=project, person=user)
492
        if m.is_pending:
493
            raise PendingMembershipError()
494
        return m
495
    except ProjectMembership.DoesNotExist:
496
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
497

    
498
def checkAllowed(entity, request_user):
499
    if isinstance(entity, Project):
500
        application = entity.application
501
    elif isinstance(entity, ProjectApplication):
502
        application = entity
503
    else:
504
        m = "%s not a Project nor a ProjectApplication" % (entity,)
505
        raise ValueError(m)
506

    
507
    if request_user and \
508
        (not application.owner == request_user and \
509
            not request_user.is_superuser):
510
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
511

    
512
def checkAlive(project):
513
    if not project.is_alive:
514
        raise PermissionDenied(
515
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
516

    
517
def accept_membership_checks(project, request_user):
518
    checkAllowed(project, request_user)
519
    checkAlive(project)
520

    
521
    join_policy = project.application.member_join_policy
522
    if join_policy == CLOSED_POLICY:
523
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
524

    
525
    if project.violates_members_limit(adding=1):
526
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
527

    
528
def accept_membership(project_id, user, request_user=None):
529
    project = get_project_for_update(project_id)
530
    accept_membership_checks(project, request_user)
531

    
532
    membership = get_membership_for_update(project, user)
533
    if not membership.can_accept():
534
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
535
        raise PermissionDenied(m)
536

    
537
    membership.accept()
538

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

    
541
    return membership
542

    
543
def reject_membership_checks(project, request_user):
544
    checkAllowed(project, request_user)
545
    checkAlive(project)
546

    
547
def reject_membership(project_id, user, request_user=None):
548
    project = get_project_for_update(project_id)
549
    reject_membership_checks(project, request_user)
550
    membership = get_membership_for_update(project, user)
551
    if not membership.can_reject():
552
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
553
        raise PermissionDenied(m)
554

    
555
    membership.reject()
556

    
557
    membership_change_notify(project, membership.person, 'rejected')
558

    
559
    return membership
560

    
561
def cancel_membership_checks(project):
562
    checkAlive(project)
563

    
564
def cancel_membership(project_id, user_id):
565
    project = get_project_for_update(project_id)
566
    cancel_membership_checks(project)
567
    membership = get_membership_for_update(project, user_id)
568
    if not membership.can_cancel():
569
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
570
        raise PermissionDenied(m)
571

    
572
    membership.cancel()
573

    
574
def remove_membership_checks(project, request_user=None):
575
    checkAllowed(project, request_user)
576
    checkAlive(project)
577

    
578
    leave_policy = project.application.member_leave_policy
579
    if leave_policy == CLOSED_POLICY:
580
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
581

    
582
def remove_membership(project_id, user, request_user=None):
583
    project = get_project_for_update(project_id)
584
    remove_membership_checks(project, request_user)
585
    membership = get_membership_for_update(project, user)
586
    if not membership.can_remove():
587
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
588
        raise PermissionDenied(m)
589

    
590
    membership.remove()
591

    
592
    membership_change_notify(project, membership.person, 'removed')
593

    
594
    return membership
595

    
596
def enroll_member(project_id, user, request_user=None):
597
    project = get_project_for_update(project_id)
598
    accept_membership_checks(project, request_user)
599
    membership = create_membership(project, user)
600

    
601
    if not membership.can_accept():
602
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
603
        raise PermissionDenied(m)
604

    
605
    membership.accept()
606
    membership_enroll_notify(project, membership.person)
607

    
608
    return membership
609

    
610
def leave_project_checks(project):
611
    checkAlive(project)
612

    
613
    leave_policy = project.application.member_leave_policy
614
    if leave_policy == CLOSED_POLICY:
615
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
616

    
617
def can_leave_request(project, user):
618
    leave_policy = project.application.member_leave_policy
619
    if leave_policy == CLOSED_POLICY:
620
        return False
621
    m = user.get_membership(project)
622
    if m is None:
623
        return False
624
    if m.state != ProjectMembership.ACCEPTED:
625
        return False
626
    return True
627

    
628
def leave_project(project_id, user_id):
629
    project = get_project_for_update(project_id)
630
    leave_project_checks(project)
631
    membership = get_membership_for_update(project, user_id)
632
    if not membership.can_leave():
633
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
634
        raise PermissionDenied(m)
635

    
636
    auto_accepted = False
637
    leave_policy = project.application.member_leave_policy
638
    if leave_policy == AUTO_ACCEPT_POLICY:
639
        membership.remove()
640
        auto_accepted = True
641
    else:
642
        membership.leave_request()
643
        membership_leave_request_notify(project, membership.person)
644
    return auto_accepted
645

    
646
def join_project_checks(project):
647
    checkAlive(project)
648

    
649
    join_policy = project.application.member_join_policy
650
    if join_policy == CLOSED_POLICY:
651
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
652

    
653
def can_join_request(project, user):
654
    join_policy = project.application.member_join_policy
655
    if join_policy == CLOSED_POLICY:
656
        return False
657
    m = user.get_membership(project)
658
    if m:
659
        return False
660
    return True
661

    
662
def join_project(project_id, user_id):
663
    project = get_project_for_update(project_id)
664
    join_project_checks(project)
665
    membership = create_membership(project, user_id)
666

    
667
    auto_accepted = False
668
    join_policy = project.application.member_join_policy
669
    if (join_policy == AUTO_ACCEPT_POLICY and
670
        not project.violates_members_limit(adding=1)):
671
        membership.accept()
672
        auto_accepted = True
673
    else:
674
        membership_request_notify(project, membership.person)
675

    
676
    return auto_accepted
677

    
678
def submit_application(kw, request_user=None):
679

    
680
    kw['applicant'] = request_user
681
    resource_policies = kw.pop('resource_policies', None)
682

    
683
    precursor = None
684
    precursor_id = kw.get('precursor_application', None)
685
    if precursor_id is not None:
686
        objs = ProjectApplication.objects
687
        precursor = objs.get_for_update(id=precursor_id)
688
        kw['precursor_application'] = precursor
689

    
690
        if (request_user and
691
            (not precursor.owner == request_user and
692
             not request_user.is_superuser
693
             and not request_user.is_project_admin())):
694
            m = _(astakos_messages.NOT_ALLOWED)
695
            raise PermissionDenied(m)
696

    
697
    application = ProjectApplication(**kw)
698

    
699
    if precursor is None:
700
        application.chain = new_chain()
701
    else:
702
        chain = precursor.chain
703
        application.chain = chain
704
        objs = ProjectApplication.objects
705
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
706
        pending = q.select_for_update()
707
        for app in pending:
708
            app.state = ProjectApplication.REPLACED
709
            app.save()
710

    
711
    application.save()
712
    application.resource_policies = resource_policies
713
    application_submit_notify(application)
714
    return application
715

    
716
def cancel_application(application_id, request_user=None):
717
    application = get_application_for_update(application_id)
718
    checkAllowed(application, request_user)
719

    
720
    if not application.can_cancel():
721
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
722
                application.id, application.state_display()))
723
        raise PermissionDenied(m)
724

    
725
    application.cancel()
726

    
727
def dismiss_application(application_id, request_user=None):
728
    application = get_application_for_update(application_id)
729
    checkAllowed(application, request_user)
730

    
731
    if not application.can_dismiss():
732
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
733
                application.id, application.state_display()))
734
        raise PermissionDenied(m)
735

    
736
    application.dismiss()
737

    
738
def deny_application(application_id):
739
    application = get_application_for_update(application_id)
740

    
741
    if not application.can_deny():
742
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
743
                application.id, application.state_display()))
744
        raise PermissionDenied(m)
745

    
746
    application.deny()
747
    application_deny_notify(application)
748

    
749
def approve_application(app_id):
750

    
751
    try:
752
        objects = ProjectApplication.objects
753
        application = objects.get_for_update(id=app_id)
754
    except ProjectApplication.DoesNotExist:
755
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
756
        raise PermissionDenied(m)
757

    
758
    if not application.can_approve():
759
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
760
                application.id, application.state_display()))
761
        raise PermissionDenied(m)
762

    
763
    application.approve()
764
    application_approve_notify(application)
765

    
766
def check_expiration(execute=False):
767
    objects = Project.objects
768
    expired = objects.expired_projects()
769
    if execute:
770
        for project in expired:
771
            terminate(project.id)
772

    
773
    return [project.expiration_info() for project in expired]
774

    
775
def terminate(project_id):
776
    project = get_project_for_update(project_id)
777
    checkAlive(project)
778

    
779
    project.terminate()
780

    
781
    project_termination_notify(project)
782

    
783
def suspend(project_id):
784
    project = get_project_by_id(project_id)
785
    checkAlive(project)
786

    
787
    project.suspend()
788

    
789
    project_suspension_notify(project)
790

    
791
def resume(project_id):
792
    project = get_project_for_update(project_id)
793

    
794
    if not project.is_suspended:
795
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
796
        raise PermissionDenied(m)
797

    
798
    project.resume()
799

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