Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (28.5 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
import astakos.im.settings as astakos_settings
58
from astakos.im.settings import (
59
    DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
60
    VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
61
    GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
62
    INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
63
    EMAIL_CHANGE_EMAIL_SUBJECT,
64
    PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
65
    PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
66
    PROJECT_MEMBERSHIP_CHANGE_SUBJECT,
67
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES)
68
from astakos.im.notifications import build_notification, NotificationError
69
from astakos.im.models import (
70
    AstakosUser, Invitation, ProjectMembership, ProjectApplication, Project,
71
    UserSetting,
72
    PendingMembershipError, get_resource_names, new_chain,
73
    users_quotas)
74
from astakos.im.project_notif import (
75
    membership_change_notify, membership_enroll_notify,
76
    membership_request_notify, membership_leave_request_notify,
77
    application_submit_notify, application_approve_notify,
78
    application_deny_notify,
79
    project_termination_notify, project_suspension_notify)
80
from astakos.im.endpoints.qh import (
81
    register_users, register_quotas, qh_get_quota)
82

    
83
import astakos.im.messages as astakos_messages
84

    
85
logger = logging.getLogger(__name__)
86

    
87

    
88
def logged(func, msg):
89
    @wraps(func)
90
    def with_logging(*args, **kwargs):
91
        email = ''
92
        user = None
93
        try:
94
            request = args[0]
95
            email = request.user.email
96
        except (KeyError, AttributeError), e:
97
            email = ''
98
        r = func(*args, **kwargs)
99
        if LOGGING_LEVEL:
100
            logger.log(LOGGING_LEVEL, msg % email)
101
        return r
102
    return with_logging
103

    
104

    
105
def login(request, user):
106
    auth_login(request, user)
107
    from astakos.im.models import SessionCatalog
108
    SessionCatalog(
109
        session_key=request.session.session_key,
110
        user=user
111
    ).save()
112

    
113
login = logged(login, '%s logged in.')
114
logout = logged(auth_logout, '%s logged out.')
115

    
116

    
117
def send_verification(user, template_name='im/activation_email.txt'):
118
    """
119
    Send email to user to verify his/her email and activate his/her account.
120

121
    Raises SendVerificationError
122
    """
123
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
124
                                  quote(user.auth_token),
125
                                  quote(urljoin(BASEURL, reverse('index'))))
126
    message = render_to_string(template_name, {
127
                               'user': user,
128
                               'url': url,
129
                               'baseurl': BASEURL,
130
                               'site_name': SITENAME,
131
                               'support': DEFAULT_CONTACT_EMAIL})
132
    sender = settings.SERVER_EMAIL
133
    try:
134
        send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email],
135
                  connection=get_connection())
136

    
137
    except (SMTPException, socket.error) as e:
138
        logger.exception(e)
139
        raise SendVerificationError()
140
    else:
141
        msg = 'Sent activation %s' % user.email
142
        logger.log(LOGGING_LEVEL, msg)
143

    
144

    
145
def send_activation(user, template_name='im/activation_email.txt'):
146
    send_verification(user, template_name)
147
    user.activation_sent = datetime.now()
148
    user.save()
149

    
150

    
151
def _send_admin_notification(template_name,
152
                             dictionary=None,
153
                             subject='alpha2 testing notification',):
154
    """
155
    Send notification email to settings.ADMINS.
156

157
    Raises SendNotificationError
158
    """
159
    if not settings.ADMINS:
160
        return
161
    dictionary = dictionary or {}
162
    message = render_to_string(template_name, dictionary)
163
    sender = settings.SERVER_EMAIL
164
    try:
165
        send_mail(subject, message, sender, [i[1] for i in settings.ADMINS],
166
                  connection=get_connection())
167
    except (SMTPException, socket.error) as e:
168
        logger.exception(e)
169
        raise SendNotificationError()
170
    else:
171
        msg = 'Sent admin notification for user %s' % dictionary.get('email',
172
                                                                     None)
173
        logger.log(LOGGING_LEVEL, msg)
174

    
175

    
176
def send_account_creation_notification(template_name, dictionary=None):
177
    user = dictionary.get('user', AnonymousUser())
178
    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
179
    return _send_admin_notification(template_name, dictionary, subject=subject)
180

    
181

    
182
def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
183
    """
184
    Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
185

186
    Raises SendNotificationError
187
    """
188
    if not DEFAULT_CONTACT_EMAIL:
189
        return
190
    message = render_to_string(
191
        template_name,
192
        {'user': user}
193
    )
194
    sender = settings.SERVER_EMAIL
195
    try:
196
        send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
197
                  message, sender, [DEFAULT_CONTACT_EMAIL],
198
                  connection=get_connection())
199
    except (SMTPException, socket.error) as e:
200
        logger.exception(e)
201
        raise SendNotificationError()
202
    else:
203
        msg = 'Sent helpdesk admin notification for %s' % user.email
204
        logger.log(LOGGING_LEVEL, msg)
205

    
206

    
207
def send_invitation(invitation, template_name='im/invitation.txt'):
208
    """
209
    Send invitation email.
210

211
    Raises SendInvitationError
212
    """
213
    subject = _(INVITATION_EMAIL_SUBJECT)
214
    url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
215
    message = render_to_string(template_name, {
216
                               'invitation': invitation,
217
                               'url': url,
218
                               'baseurl': BASEURL,
219
                               'site_name': SITENAME,
220
                               'support': DEFAULT_CONTACT_EMAIL})
221
    sender = settings.SERVER_EMAIL
222
    try:
223
        send_mail(subject, message, sender, [invitation.username],
224
                  connection=get_connection())
225
    except (SMTPException, socket.error) as e:
226
        logger.exception(e)
227
        raise SendInvitationError()
228
    else:
229
        msg = 'Sent invitation %s' % invitation
230
        logger.log(LOGGING_LEVEL, msg)
231
        inviter_invitations = invitation.inviter.invitations
232
        invitation.inviter.invitations = max(0, inviter_invitations - 1)
233
        invitation.inviter.save()
234

    
235

    
236
def send_greeting(user, email_template_name='im/welcome_email.txt'):
237
    """
238
    Send welcome email.
239

240
    Raises SMTPException, socket.error
241
    """
242
    subject = _(GREETING_EMAIL_SUBJECT)
243
    message = render_to_string(email_template_name, {
244
                               'user': user,
245
                               'url': urljoin(BASEURL, reverse('index')),
246
                               'baseurl': BASEURL,
247
                               'site_name': SITENAME,
248
                               'support': DEFAULT_CONTACT_EMAIL})
249
    sender = settings.SERVER_EMAIL
250
    try:
251
        send_mail(subject, message, sender, [user.email],
252
                  connection=get_connection())
253
    except (SMTPException, socket.error) as e:
254
        logger.exception(e)
255
        raise SendGreetingError()
256
    else:
257
        msg = 'Sent greeting %s' % user.email
258
        logger.log(LOGGING_LEVEL, msg)
259

    
260

    
261
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
262
    subject = _(FEEDBACK_EMAIL_SUBJECT)
263
    from_email = settings.SERVER_EMAIL
264
    recipient_list = [DEFAULT_CONTACT_EMAIL]
265
    content = render_to_string(email_template_name, {
266
        'message': msg,
267
        'data': data,
268
        'user': user})
269
    try:
270
        send_mail(subject, content, from_email, recipient_list,
271
                  connection=get_connection())
272
    except (SMTPException, socket.error) as e:
273
        logger.exception(e)
274
        raise SendFeedbackError()
275
    else:
276
        msg = 'Sent feedback from %s' % user.email
277
        logger.log(LOGGING_LEVEL, msg)
278

    
279

    
280
def send_change_email(
281
    ec, request, email_template_name='registration/email_change_email.txt'):
282
    try:
283
        url = ec.get_url()
284
        url = request.build_absolute_uri(url)
285
        t = loader.get_template(email_template_name)
286
        c = {'url': url, 'site_name': SITENAME,
287
             'support': DEFAULT_CONTACT_EMAIL, 'ec': ec}
288
        from_email = settings.SERVER_EMAIL
289
        send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT), t.render(Context(c)),
290
                  from_email, [ec.new_email_address],
291
                  connection=get_connection())
292
    except (SMTPException, socket.error) as e:
293
        logger.exception(e)
294
        raise ChangeEmailError()
295
    else:
296
        msg = 'Sent change email for %s' % ec.user.email
297
        logger.log(LOGGING_LEVEL, msg)
298

    
299

    
300
def activate(
301
    user,
302
    email_template_name='im/welcome_email.txt',
303
    helpdesk_email_template_name='im/helpdesk_notification.txt',
304
    verify_email=False):
305
    """
306
    Activates the specific user and sends email.
307

308
    Raises SendGreetingError, ValidationError
309
    """
310
    user.is_active = True
311
    user.email_verified = True
312
    if not user.activation_sent:
313
        user.activation_sent = datetime.now()
314
    user.save()
315
    register_user_with_quotas(user)
316
    send_helpdesk_notification(user, helpdesk_email_template_name)
317
    send_greeting(user, email_template_name)
318

    
319
def deactivate(user):
320
    user.is_active = False
321
    user.save()
322

    
323
def invite(inviter, email, realname):
324
    inv = Invitation(inviter=inviter, username=email, realname=realname)
325
    inv.save()
326
    send_invitation(inv)
327
    inviter.invitations = max(0, inviter.invitations - 1)
328
    inviter.save()
329

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

    
346

    
347
class SendMailError(Exception):
348
    pass
349

    
350

    
351
class SendAdminNotificationError(SendMailError):
352
    def __init__(self):
353
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
354
        super(SendAdminNotificationError, self).__init__()
355

    
356

    
357
class SendVerificationError(SendMailError):
358
    def __init__(self):
359
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
360
        super(SendVerificationError, self).__init__()
361

    
362

    
363
class SendInvitationError(SendMailError):
364
    def __init__(self):
365
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
366
        super(SendInvitationError, self).__init__()
367

    
368

    
369
class SendGreetingError(SendMailError):
370
    def __init__(self):
371
        self.message = _(astakos_messages.GREETING_SEND_ERR)
372
        super(SendGreetingError, self).__init__()
373

    
374

    
375
class SendFeedbackError(SendMailError):
376
    def __init__(self):
377
        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
378
        super(SendFeedbackError, self).__init__()
379

    
380

    
381
class ChangeEmailError(SendMailError):
382
    def __init__(self):
383
        self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
384
        super(ChangeEmailError, self).__init__()
385

    
386

    
387
class SendNotificationError(SendMailError):
388
    def __init__(self):
389
        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
390
        super(SendNotificationError, self).__init__()
391

    
392

    
393
def register_user_with_quotas(user):
394
    rejected = register_users([user])
395
    if not rejected:
396
        quotas = users_quotas([user])
397
        register_quotas(quotas)
398

    
399

    
400
def get_quota(users):
401
    resources = get_resource_names()
402
    return qh_get_quota(users, resources)
403

    
404

    
405
### PROJECT VIEWS ###
406

    
407
AUTO_ACCEPT_POLICY = 1
408
MODERATED_POLICY   = 2
409
CLOSED_POLICY      = 3
410

    
411
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
412

    
413
def get_project_by_application_id(project_application_id):
414
    try:
415
        return Project.objects.get(application__id=project_application_id)
416
    except Project.DoesNotExist:
417
        raise IOError(
418
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
419

    
420
def get_related_project_id(application_id):
421
    try:
422
        app = ProjectApplication.objects.get(id=application_id)
423
        chain = app.chain
424
        project = Project.objects.get(id=chain)
425
        return chain
426
    except:
427
        return None
428

    
429
def get_chain_of_application_id(application_id):
430
    try:
431
        app = ProjectApplication.objects.get(id=application_id)
432
        chain = app.chain
433
        return chain.chain
434
    except:
435
        return None
436

    
437
def get_project_by_id(project_id):
438
    try:
439
        return Project.objects.get(id=project_id)
440
    except Project.DoesNotExist:
441
        raise IOError(
442
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
443

    
444
def get_project_by_name(name):
445
    try:
446
        return Project.objects.get(name=name)
447
    except Project.DoesNotExist:
448
        raise IOError(
449
            _(astakos_messages.UNKNOWN_PROJECT_ID) % name)
450

    
451

    
452
def get_project_for_update(project_id):
453
    try:
454
        return Project.objects.get_for_update(id=project_id)
455
    except Project.DoesNotExist:
456
        raise IOError(
457
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
458

    
459
def get_application_for_update(application_id):
460
    try:
461
        return ProjectApplication.objects.get_for_update(id=application_id)
462
    except ProjectApplication.DoesNotExist:
463
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
464
        raise IOError(m)
465

    
466
def get_user_by_id(user_id):
467
    try:
468
        return AstakosUser.objects.get(id=user_id)
469
    except AstakosUser.DoesNotExist:
470
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
471

    
472
def get_user_by_uuid(uuid):
473
    try:
474
        return AstakosUser.objects.get(uuid=uuid)
475
    except AstakosUser.DoesNotExist:
476
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % uuid)
477

    
478
def create_membership(project, user):
479
    if isinstance(user, (int, long)):
480
        user = get_user_by_id(user)
481

    
482
    if not user.is_active:
483
        m = _(astakos_messages.ACCOUNT_NOT_ACTIVE)
484
        raise PermissionDenied(m)
485

    
486
    m, created = ProjectMembership.objects.get_or_create(
487
        project=project,
488
        person=user)
489

    
490
    if created:
491
        return m
492
    else:
493
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
494
        raise PermissionDenied(msg)
495

    
496

    
497
def get_membership_for_update(project, user):
498
    if isinstance(user, (int, long)):
499
        user = get_user_by_id(user)
500
    try:
501
        objs = ProjectMembership.objects
502
        m = objs.get_for_update(project=project, person=user)
503
        if m.is_pending:
504
            raise PendingMembershipError()
505
        return m
506
    except ProjectMembership.DoesNotExist:
507
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
508

    
509
def checkAllowed(entity, request_user):
510
    if isinstance(entity, Project):
511
        application = entity.application
512
    elif isinstance(entity, ProjectApplication):
513
        application = entity
514
    else:
515
        m = "%s not a Project nor a ProjectApplication" % (entity,)
516
        raise ValueError(m)
517

    
518
    if request_user and \
519
        (not application.owner == request_user and \
520
            not request_user.is_superuser):
521
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
522

    
523
def checkAlive(project):
524
    if not project.is_alive:
525
        raise PermissionDenied(
526
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
527

    
528
def accept_membership_checks(project, request_user):
529
    checkAllowed(project, request_user)
530
    checkAlive(project)
531

    
532
    join_policy = project.application.member_join_policy
533
    if join_policy == CLOSED_POLICY:
534
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
535

    
536
    if project.violates_members_limit(adding=1):
537
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
538

    
539
def accept_membership(project_id, user, request_user=None):
540
    project = get_project_for_update(project_id)
541
    accept_membership_checks(project, request_user)
542

    
543
    membership = get_membership_for_update(project, user)
544
    if not membership.can_accept():
545
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
546
        raise PermissionDenied(m)
547

    
548
    membership.accept()
549

    
550
    membership_change_notify(project, membership.person, 'accepted')
551

    
552
    return membership
553

    
554
def reject_membership_checks(project, request_user):
555
    checkAllowed(project, request_user)
556
    checkAlive(project)
557

    
558
def reject_membership(project_id, user, request_user=None):
559
    project = get_project_for_update(project_id)
560
    reject_membership_checks(project, request_user)
561
    membership = get_membership_for_update(project, user)
562
    if not membership.can_reject():
563
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
564
        raise PermissionDenied(m)
565

    
566
    membership.reject()
567

    
568
    membership_change_notify(project, membership.person, 'rejected')
569

    
570
    return membership
571

    
572
def cancel_membership_checks(project):
573
    checkAlive(project)
574

    
575
def cancel_membership(project_id, user_id):
576
    project = get_project_for_update(project_id)
577
    cancel_membership_checks(project)
578
    membership = get_membership_for_update(project, user_id)
579
    if not membership.can_cancel():
580
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
581
        raise PermissionDenied(m)
582

    
583
    membership.cancel()
584

    
585
def remove_membership_checks(project, request_user=None):
586
    checkAllowed(project, request_user)
587
    checkAlive(project)
588

    
589
    leave_policy = project.application.member_leave_policy
590
    if leave_policy == CLOSED_POLICY:
591
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
592

    
593
def remove_membership(project_id, user, request_user=None):
594
    project = get_project_for_update(project_id)
595
    remove_membership_checks(project, request_user)
596
    membership = get_membership_for_update(project, user)
597
    if not membership.can_remove():
598
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
599
        raise PermissionDenied(m)
600

    
601
    membership.remove()
602

    
603
    membership_change_notify(project, membership.person, 'removed')
604

    
605
    return membership
606

    
607
def enroll_member(project_id, user, request_user=None):
608
    project = get_project_for_update(project_id)
609
    accept_membership_checks(project, request_user)
610
    membership = create_membership(project, user)
611

    
612
    if not membership.can_accept():
613
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
614
        raise PermissionDenied(m)
615

    
616
    membership.accept()
617
    membership_enroll_notify(project, membership.person)
618

    
619
    return membership
620

    
621
def leave_project_checks(project):
622
    checkAlive(project)
623

    
624
    leave_policy = project.application.member_leave_policy
625
    if leave_policy == CLOSED_POLICY:
626
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
627

    
628
def can_leave_request(project, user):
629
    leave_policy = project.application.member_leave_policy
630
    if leave_policy == CLOSED_POLICY:
631
        return False
632
    m = user.get_membership(project)
633
    if m is None:
634
        return False
635
    if m.state != ProjectMembership.ACCEPTED:
636
        return False
637
    return True
638

    
639
def leave_project(project_id, user_id):
640
    project = get_project_for_update(project_id)
641
    leave_project_checks(project)
642
    membership = get_membership_for_update(project, user_id)
643
    if not membership.can_leave():
644
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
645
        raise PermissionDenied(m)
646

    
647
    auto_accepted = False
648
    leave_policy = project.application.member_leave_policy
649
    if leave_policy == AUTO_ACCEPT_POLICY:
650
        membership.remove()
651
        auto_accepted = True
652
    else:
653
        membership.leave_request()
654
        membership_leave_request_notify(project, membership.person)
655
    return auto_accepted
656

    
657
def join_project_checks(project):
658
    checkAlive(project)
659

    
660
    join_policy = project.application.member_join_policy
661
    if join_policy == CLOSED_POLICY:
662
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
663

    
664
def can_join_request(project, user):
665
    join_policy = project.application.member_join_policy
666
    if join_policy == CLOSED_POLICY:
667
        return False
668
    m = user.get_membership(project)
669
    if m:
670
        return False
671
    return True
672

    
673
def join_project(project_id, user_id):
674
    project = get_project_for_update(project_id)
675
    join_project_checks(project)
676
    membership = create_membership(project, user_id)
677

    
678
    auto_accepted = False
679
    join_policy = project.application.member_join_policy
680
    if (join_policy == AUTO_ACCEPT_POLICY and
681
        not project.violates_members_limit(adding=1)):
682
        membership.accept()
683
        auto_accepted = True
684
    else:
685
        membership_request_notify(project, membership.person)
686

    
687
    return auto_accepted
688

    
689
def submit_application(kw, request_user=None):
690

    
691
    kw['applicant'] = request_user
692
    resource_policies = kw.pop('resource_policies', None)
693

    
694
    precursor = None
695
    precursor_id = kw.get('precursor_application', None)
696
    if precursor_id is not None:
697
        objs = ProjectApplication.objects
698
        precursor = objs.get_for_update(id=precursor_id)
699
        kw['precursor_application'] = precursor
700

    
701
        if (request_user and
702
            (not precursor.owner == request_user and
703
             not request_user.is_superuser
704
             and not request_user.is_project_admin())):
705
            m = _(astakos_messages.NOT_ALLOWED)
706
            raise PermissionDenied(m)
707

    
708
    reached, limit = reached_pending_application_limit(request_user.id, precursor)
709
    if reached:
710
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
711
        raise PermissionDenied(m)
712

    
713
    application = ProjectApplication(**kw)
714

    
715
    if precursor is None:
716
        application.chain = new_chain()
717
    else:
718
        chain = precursor.chain
719
        application.chain = chain
720
        objs = ProjectApplication.objects
721
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
722
        pending = q.select_for_update()
723
        for app in pending:
724
            app.state = ProjectApplication.REPLACED
725
            app.save()
726

    
727
    application.save()
728
    application.resource_policies = resource_policies
729
    application_submit_notify(application)
730
    return application
731

    
732
def cancel_application(application_id, request_user=None):
733
    application = get_application_for_update(application_id)
734
    checkAllowed(application, request_user)
735

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

    
741
    application.cancel()
742

    
743
def dismiss_application(application_id, request_user=None):
744
    application = get_application_for_update(application_id)
745
    checkAllowed(application, request_user)
746

    
747
    if not application.can_dismiss():
748
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
749
                application.id, application.state_display()))
750
        raise PermissionDenied(m)
751

    
752
    application.dismiss()
753

    
754
def deny_application(application_id):
755
    application = get_application_for_update(application_id)
756

    
757
    if not application.can_deny():
758
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
759
                application.id, application.state_display()))
760
        raise PermissionDenied(m)
761

    
762
    application.deny()
763
    application_deny_notify(application)
764

    
765
def approve_application(app_id):
766

    
767
    try:
768
        objects = ProjectApplication.objects
769
        application = objects.get_for_update(id=app_id)
770
    except ProjectApplication.DoesNotExist:
771
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
772
        raise PermissionDenied(m)
773

    
774
    if not application.can_approve():
775
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
776
                application.id, application.state_display()))
777
        raise PermissionDenied(m)
778

    
779
    application.approve()
780
    application_approve_notify(application)
781

    
782
def check_expiration(execute=False):
783
    objects = Project.objects
784
    expired = objects.expired_projects()
785
    if execute:
786
        for project in expired:
787
            terminate(project.id)
788

    
789
    return [project.expiration_info() for project in expired]
790

    
791
def terminate(project_id):
792
    project = get_project_for_update(project_id)
793
    checkAlive(project)
794

    
795
    project.terminate()
796

    
797
    project_termination_notify(project)
798

    
799
def suspend(project_id):
800
    project = get_project_by_id(project_id)
801
    checkAlive(project)
802

    
803
    project.suspend()
804

    
805
    project_suspension_notify(project)
806

    
807
def resume(project_id):
808
    project = get_project_for_update(project_id)
809

    
810
    if not project.is_suspended:
811
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
812
        raise PermissionDenied(m)
813

    
814
    project.resume()
815

    
816
def get_by_chain_or_404(chain_id):
817
    try:
818
        project = Project.objects.get(id=chain_id)
819
        application = project.application
820
        return project, application
821
    except:
822
        application = ProjectApplication.objects.latest_of_chain(chain_id)
823
        if application is None:
824
            raise Http404
825
        else:
826
            return None, application
827

    
828

    
829
def get_user_setting(user_id, key):
830
    try:
831
        setting = UserSetting.objects.get(
832
            user=user_id, setting=key)
833
        return setting.value
834
    except UserSetting.DoesNotExist:
835
        return getattr(astakos_settings, key)
836

    
837

    
838
def set_user_setting(user_id, key, value):
839
    try:
840
        setting = UserSetting.objects.get_for_update(
841
            user=user_id, setting=key)
842
    except UserSetting.DoesNotExist:
843
        setting = UserSetting(user_id=user_id, setting=key)
844
    setting.value = value
845
    setting.save()
846

    
847

    
848
def unset_user_setting(user_id, key):
849
    UserSetting.objects.filter(user=user_id, setting=key).delete()
850

    
851

    
852
PENDING_APPLICATION_LIMIT_SETTING = 'PENDING_APPLICATION_LIMIT'
853

    
854
def get_pending_application_limit(user_id):
855
    key = PENDING_APPLICATION_LIMIT_SETTING
856
    return get_user_setting(user_id, key)
857

    
858

    
859
def set_pending_application_limit(user_id, value):
860
    key = PENDING_APPLICATION_LIMIT_SETTING
861
    return set_user_setting(user_id, key, value)
862

    
863

    
864
def unset_pending_application_limit(user_id):
865
    key = PENDING_APPLICATION_LIMIT_SETTING
866
    return unset_user_setting(user_id, key)
867

    
868

    
869
def _reached_pending_application_limit(user_id):
870
    limit = get_pending_application_limit(user_id)
871

    
872
    PENDING = ProjectApplication.PENDING
873
    pending = ProjectApplication.objects.filter(
874
        applicant__id=user_id, state=PENDING).count()
875

    
876
    return pending >= limit, limit
877

    
878

    
879
def reached_pending_application_limit(user_id, precursor=None):
880
    reached, limit = _reached_pending_application_limit(user_id)
881

    
882
    if precursor is None:
883
        return reached, limit
884

    
885
    chain = precursor.chain
886
    objs = ProjectApplication.objects
887
    q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
888
    has_pending = q.exists()
889

    
890
    if not has_pending:
891
        return reached, limit
892

    
893
    return False, limit