Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (26.2 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_by_name(name):
425
    try:
426
        return Project.objects.get(name=name)
427
    except Project.DoesNotExist:
428
        raise IOError(
429
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
430

    
431

    
432
def get_project_for_update(project_id):
433
    try:
434
        return Project.objects.select_for_update().get(id=project_id)
435
    except Project.DoesNotExist:
436
        raise IOError(
437
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
438

    
439
def get_application_for_update(application_id):
440
    try:
441
        objects = ProjectApplication.objects.select_for_update()
442
        return objects.get(id=application_id)
443
    except ProjectApplication.DoesNotExist:
444
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
445
        raise IOError(m)
446

    
447
def get_user_by_id(user_id):
448
    try:
449
        return AstakosUser.objects.get(id=user_id)
450
    except AstakosUser.DoesNotExist:
451
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
452

    
453
def get_user_by_uuid(uuid):
454
    try:
455
        return AstakosUser.objects.get(uuid=uuid)
456
    except AstakosUser.DoesNotExist:
457
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
458

    
459
def create_membership(project, user):
460
    if isinstance(project, int):
461
        project = get_project_by_id(project)
462
    if isinstance(user, int):
463
        user = get_user_by_id(user)
464

    
465
    if not user.is_active:
466
        m = _(astakos_messages.ACCOUNT_NOT_ACTIVE)
467
        raise PermissionDenied(m)
468

    
469
    m = ProjectMembership(
470
        project=project,
471
        person=user,
472
        request_date=datetime.now())
473
    try:
474
        m.save()
475
    except IntegrityError, e:
476
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
477
    else:
478
        return m
479

    
480
def get_membership_for_update(project, user):
481
    if isinstance(project, int):
482
        project = get_project_by_id(project)
483
    if isinstance(user, int):
484
        user = get_user_by_id(user)
485
    try:
486
        sfu = ProjectMembership.objects.select_for_update()
487
        m = sfu.get(project=project, person=user)
488
        if m.is_pending:
489
            raise PendingMembershipError()
490
        return m
491
    except ProjectMembership.DoesNotExist:
492
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
493

    
494
def checkAllowed(entity, request_user):
495
    if isinstance(entity, Project):
496
        application = entity.application
497
    elif isinstance(entity, ProjectApplication):
498
        application = entity
499
    else:
500
        m = "%s not a Project nor a ProjectApplication" % (entity,)
501
        raise ValueError(m)
502

    
503
    if request_user and \
504
        (not application.owner == request_user and \
505
            not request_user.is_superuser):
506
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
507

    
508
def checkAlive(project):
509
    if not project.is_alive:
510
        raise PermissionDenied(
511
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
512

    
513
def accept_membership_checks(project, request_user):
514
    checkAllowed(project, request_user)
515
    checkAlive(project)
516

    
517
    join_policy = project.application.member_join_policy
518
    if join_policy == CLOSED_POLICY:
519
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
520

    
521
    if project.violates_members_limit(adding=1):
522
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
523

    
524
def accept_membership(project_id, user, request_user=None):
525
    project = get_project_for_update(project_id)
526
    accept_membership_checks(project, request_user)
527

    
528
    membership = get_membership_for_update(project, user)
529
    if not membership.can_accept():
530
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
531
        raise PermissionDenied(m)
532

    
533
    membership.accept()
534

    
535
    membership_change_notify(project, membership.person, 'accepted')
536

    
537
    return membership
538

    
539
def reject_membership_checks(project, request_user):
540
    checkAllowed(project, request_user)
541
    checkAlive(project)
542

    
543
def reject_membership(project_id, user, request_user=None):
544
    project = get_project_for_update(project_id)
545
    reject_membership_checks(project, request_user)
546
    membership = get_membership_for_update(project, user)
547
    if not membership.can_reject():
548
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
549
        raise PermissionDenied(m)
550

    
551
    membership.reject()
552

    
553
    membership_change_notify(project, membership.person, 'rejected')
554

    
555
    return membership
556

    
557
def cancel_membership_checks(project):
558
    checkAlive(project)
559

    
560
def cancel_membership(project_id, user_id):
561
    project = get_project_for_update(project_id)
562
    cancel_membership_checks(project)
563
    membership = get_membership_for_update(project, user_id)
564
    if not membership.can_cancel():
565
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
566
        raise PermissionDenied(m)
567

    
568
    membership.cancel()
569

    
570
def remove_membership_checks(project, request_user=None):
571
    checkAllowed(project, request_user)
572
    checkAlive(project)
573

    
574
    leave_policy = project.application.member_leave_policy
575
    if leave_policy == CLOSED_POLICY:
576
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
577

    
578
def remove_membership(project_id, user, request_user=None):
579
    project = get_project_for_update(project_id)
580
    remove_membership_checks(project, request_user)
581
    membership = get_membership_for_update(project, user)
582
    if not membership.can_remove():
583
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
584
        raise PermissionDenied(m)
585

    
586
    membership.remove()
587

    
588
    membership_change_notify(project, membership.person, 'removed')
589

    
590
    return membership
591

    
592
def enroll_member(project_id, user, request_user=None):
593
    project = get_project_for_update(project_id)
594
    accept_membership_checks(project, request_user)
595
    membership = create_membership(project_id, user)
596

    
597
    if not membership.can_accept():
598
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
599
        raise PermissionDenied(m)
600

    
601
    membership.accept()
602
    membership_enroll_notify(project, membership.person)
603

    
604
    return membership
605

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

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

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

    
624
def leave_project(project_id, user_id):
625
    project = get_project_for_update(project_id)
626
    leave_project_checks(project)
627
    membership = get_membership_for_update(project, user_id)
628
    if not membership.can_leave():
629
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
630
        raise PermissionDenied(m)
631

    
632
    auto_accepted = False
633
    leave_policy = project.application.member_leave_policy
634
    if leave_policy == AUTO_ACCEPT_POLICY:
635
        membership.remove()
636
        auto_accepted = True
637
    else:
638
        membership.leave_request()
639
        membership_leave_request_notify(project, membership.person)
640
    return auto_accepted
641

    
642
def join_project_checks(project):
643
    checkAlive(project)
644

    
645
    join_policy = project.application.member_join_policy
646
    if join_policy == CLOSED_POLICY:
647
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
648

    
649
def can_join_request(project, user):
650
    join_policy = project.application.member_join_policy
651
    if join_policy == CLOSED_POLICY:
652
        return False
653
    m = user.get_membership(project)
654
    if m:
655
        return False
656
    return True
657

    
658
def join_project(project_id, user_id):
659
    project = get_project_for_update(project_id)
660
    join_project_checks(project)
661
    membership = create_membership(project, user_id)
662

    
663
    auto_accepted = False
664
    join_policy = project.application.member_join_policy
665
    if (join_policy == AUTO_ACCEPT_POLICY and
666
        not project.violates_members_limit(adding=1)):
667
        membership.accept()
668
        auto_accepted = True
669
    else:
670
        membership_request_notify(project, membership.person)
671

    
672
    return auto_accepted
673

    
674
def submit_application(kw, request_user=None):
675

    
676
    kw['applicant'] = request_user
677
    resource_policies = kw.pop('resource_policies', None)
678

    
679
    precursor = None
680
    precursor_id = kw.get('precursor_application', None)
681
    if precursor_id is not None:
682
        sfu = ProjectApplication.objects.select_for_update()
683
        precursor = sfu.get(id=precursor_id)
684
        kw['precursor_application'] = precursor
685

    
686
        if (request_user and
687
            (not precursor.owner == request_user and
688
             not request_user.is_superuser
689
             and not request_user.is_project_admin())):
690
            m = _(astakos_messages.NOT_ALLOWED)
691
            raise PermissionDenied(m)
692

    
693
    application = ProjectApplication(**kw)
694

    
695
    if precursor is None:
696
        application.chain = new_chain()
697
    else:
698
        chain = precursor.chain
699
        application.chain = chain
700
        sfu = ProjectApplication.objects.select_for_update()
701
        pending = sfu.filter(chain=chain, state=ProjectApplication.PENDING)
702
        for app in pending:
703
            app.state = ProjectApplication.REPLACED
704
            app.save()
705

    
706
    application.save()
707
    application.resource_policies = resource_policies
708
    application_submit_notify(application)
709
    return application
710

    
711
def cancel_application(application_id, request_user=None):
712
    application = get_application_for_update(application_id)
713
    checkAllowed(application, request_user)
714

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

    
720
    application.cancel()
721

    
722
def dismiss_application(application_id, request_user=None):
723
    application = get_application_for_update(application_id)
724
    checkAllowed(application, request_user)
725

    
726
    if not application.can_dismiss():
727
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
728
                application.id, application.state_display()))
729
        raise PermissionDenied(m)
730

    
731
    application.dismiss()
732

    
733
def deny_application(application_id):
734
    application = get_application_for_update(application_id)
735

    
736
    if not application.can_deny():
737
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
738
                application.id, application.state_display()))
739
        raise PermissionDenied(m)
740

    
741
    application.deny()
742
    application_deny_notify(application)
743

    
744
def approve_application(app_id):
745

    
746
    try:
747
        objects = ProjectApplication.objects.select_for_update()
748
        application = objects.get(id=app_id)
749
    except ProjectApplication.DoesNotExist:
750
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
751
        raise PermissionDenied(m)
752

    
753
    if not application.can_approve():
754
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
755
                application.id, application.state_display()))
756
        raise PermissionDenied(m)
757

    
758
    application.approve()
759
    application_approve_notify(application)
760

    
761
def check_expiration(execute=False):
762
    objects = Project.objects
763
    expired = objects.expired_projects()
764
    if execute:
765
        for project in expired:
766
            terminate(project.id)
767

    
768
    return [project.expiration_info() for project in expired]
769

    
770
def terminate(project_id):
771
    project = get_project_for_update(project_id)
772
    checkAlive(project)
773

    
774
    project.terminate()
775

    
776
    project_termination_notify(project)
777

    
778
def suspend(project_id):
779
    project = get_project_by_id(project_id)
780
    checkAlive(project)
781

    
782
    project.suspend()
783

    
784
    project_suspension_notify(project)
785

    
786
def resume(project_id):
787
    project = get_project_for_update(project_id)
788

    
789
    if not project.is_suspended:
790
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
791
        raise PermissionDenied(m)
792

    
793
    project.resume()
794

    
795
def get_by_chain_or_404(chain_id):
796
    try:
797
        project = Project.objects.get(id=chain_id)
798
        application = project.application
799
        return project, application
800
    except:
801
        application = ProjectApplication.objects.latest_of_chain(chain_id)
802
        if application is None:
803
            raise Http404
804
        else:
805
            return None, application