Statistics
| Branch: | Tag: | Revision:

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

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

    
294

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

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

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

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

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

    
341

    
342
class SendMailError(Exception):
343
    pass
344

    
345

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

    
351

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

    
357

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

    
363

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

    
369

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

    
375

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

    
381

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

    
387

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

    
392

    
393
### PROJECT VIEWS ###
394

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

    
399
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
400

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

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

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

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

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

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

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

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

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

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

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

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

    
500
def accept_membership_checks(project, request_user):
501
    checkAllowed(project, request_user)
502
    checkAlive(project)
503

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

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

    
511
def accept_membership(project_id, user, request_user=None):
512
    project = get_project_for_update(project_id)
513
    accept_membership_checks(project, request_user)
514

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

    
520
    membership.accept()
521

    
522
    membership_change_notify(project, membership.person, 'accepted')
523

    
524
    return membership
525

    
526
def reject_membership_checks(project, request_user):
527
    checkAllowed(project, request_user)
528
    checkAlive(project)
529

    
530
def reject_membership(project_id, user, request_user=None):
531
    project = get_project_for_update(project_id)
532
    reject_membership_checks(project, request_user)
533
    membership = get_membership_for_update(project, user)
534
    if not membership.can_reject():
535
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
536
        raise PermissionDenied(m)
537

    
538
    membership.reject()
539

    
540
    membership_change_notify(project, membership.person, 'rejected')
541

    
542
    return membership
543

    
544
def cancel_membership_checks(project):
545
    checkAlive(project)
546

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

    
555
    membership.cancel()
556

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

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

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

    
573
    membership.remove()
574

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

    
577
    return membership
578

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

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

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

    
591
    return membership
592

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

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

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

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

    
619
    auto_accepted = False
620
    leave_policy = project.application.member_leave_policy
621
    if leave_policy == AUTO_ACCEPT_POLICY:
622
        membership.remove()
623
        auto_accepted = True
624
    else:
625
        membership.leave_request_date = datetime.now()
626
        membership.save()
627
        membership_leave_request_notify(project, membership.person)
628
    return auto_accepted
629

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

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

    
637
def can_join_request(project, user):
638
    join_policy = project.application.member_join_policy
639
    if join_policy == CLOSED_POLICY:
640
        return False
641
    m = user.get_membership(project)
642
    if m:
643
        return False
644
    return True
645

    
646
def join_project(project_id, user_id):
647
    project = get_project_for_update(project_id)
648
    join_project_checks(project)
649
    membership = create_membership(project, user_id)
650

    
651
    auto_accepted = False
652
    join_policy = project.application.member_join_policy
653
    if (join_policy == AUTO_ACCEPT_POLICY and
654
        not project.violates_members_limit(adding=1)):
655
        membership.accept()
656
        auto_accepted = True
657
    else:
658
        membership_request_notify(project, membership.person)
659

    
660
    return auto_accepted
661

    
662
def submit_application(kw, request_user=None):
663

    
664
    kw['applicant'] = request_user
665
    resource_policies = kw.pop('resource_policies', None)
666

    
667
    precursor = None
668
    precursor_id = kw.get('precursor_application', None)
669
    if precursor_id is not None:
670
        sfu = ProjectApplication.objects.select_for_update()
671
        precursor = sfu.get(id=precursor_id)
672
        kw['precursor_application'] = precursor
673

    
674
        if (request_user and
675
            (not precursor.owner == request_user and
676
             not request_user.is_superuser
677
             and not request_user.is_project_admin())):
678
            m = _(astakos_messages.NOT_ALLOWED)
679
            raise PermissionDenied(m)
680

    
681
    application = ProjectApplication(**kw)
682

    
683
    if precursor is None:
684
        application.chain = new_chain()
685
    else:
686
        chain = precursor.chain
687
        application.chain = chain
688
        sfu = ProjectApplication.objects.select_for_update()
689
        pending = sfu.filter(chain=chain, state=ProjectApplication.PENDING)
690
        for app in pending:
691
            app.state = ProjectApplication.REPLACED
692
            app.save()
693

    
694
    application.save()
695
    application.resource_policies = resource_policies
696
    application_submit_notify(application)
697
    return application
698

    
699
def cancel_application(application_id, request_user=None):
700
    application = get_application_for_update(application_id)
701
    checkAllowed(application, request_user)
702

    
703
    if not application.can_cancel():
704
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
705
                application.id, application.state_display()))
706
        raise PermissionDenied(m)
707

    
708
    application.cancel()
709

    
710
def dismiss_application(application_id, request_user=None):
711
    application = get_application_for_update(application_id)
712
    checkAllowed(application, request_user)
713

    
714
    if not application.can_dismiss():
715
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
716
                application.id, application.state_display()))
717
        raise PermissionDenied(m)
718

    
719
    application.dismiss()
720

    
721
def deny_application(application_id):
722
    application = get_application_for_update(application_id)
723

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

    
729
    application.deny()
730
    application_deny_notify(application)
731

    
732
def approve_application(app_id):
733

    
734
    try:
735
        objects = ProjectApplication.objects.select_for_update()
736
        application = objects.get(id=app_id)
737
    except ProjectApplication.DoesNotExist:
738
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
739
        raise PermissionDenied(m)
740

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

    
746
    application.approve()
747
    application_approve_notify(application)
748

    
749
def check_expiration(execute=False):
750
    objects = Project.objects
751
    expired = objects.expired_projects()
752
    if execute:
753
        for project in expired:
754
            terminate(project.id)
755

    
756
    return [project.expiration_info() for project in expired]
757

    
758
def terminate(project_id):
759
    project = get_project_for_update(project_id)
760
    checkAlive(project)
761

    
762
    project.terminate()
763

    
764
    project_termination_notify(project)
765

    
766
def suspend(project_id):
767
    project = get_project_by_id(project_id)
768
    checkAlive(project)
769

    
770
    project.suspend()
771

    
772
    project_suspension_notify(project)
773

    
774
def resume(project_id):
775
    project = get_project_for_update(project_id)
776

    
777
    if not project.is_suspended:
778
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
779
        raise PermissionDenied(m)
780

    
781
    project.resume()
782

    
783
def get_by_chain_or_404(chain_id):
784
    try:
785
        project = Project.objects.get(id=chain_id)
786
        application = project.application
787
        return project, application
788
    except:
789
        application = ProjectApplication.objects.latest_of_chain(chain_id)
790
        if application is None:
791
            raise Http404
792
        else:
793
            return None, application