Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 2b745492

History | View | Annotate | Download (28.3 kB)

1
# Copyright 2011, 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35
import socket
36

    
37
from django.utils.translation import ugettext as _
38
from django.template.loader import render_to_string
39
from django.core.mail import send_mail, get_connection
40
from django.core.urlresolvers import reverse
41
from django.template import Context, loader
42
from django.contrib.auth import (
43
    login as auth_login,
44
    logout as auth_logout)
45
from django.conf import settings
46
from django.contrib.auth.models import AnonymousUser
47
from django.core.exceptions import PermissionDenied
48
from django.db import IntegrityError
49
from django.http import Http404
50

    
51
from urllib import quote
52
from urlparse import urljoin
53
from smtplib import SMTPException
54
from datetime import datetime
55
from functools import wraps
56

    
57
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 login(request, user):
89
    auth_login(request, user)
90
    from astakos.im.models import SessionCatalog
91
    SessionCatalog(
92
        session_key=request.session.session_key,
93
        user=user
94
    ).save()
95
    logger.info('%s logged in.', user.log_display)
96

    
97

    
98
def logout(request, *args, **kwargs):
99
    user = request.user
100
    auth_logout(request, *args, **kwargs)
101
    logger.info('%s logged out.', user.log_display)
102

    
103

    
104
def send_verification(user, template_name='im/activation_email.txt'):
105
    """
106
    Send email to user to verify his/her email and activate his/her account.
107

108
    Raises SendVerificationError
109
    """
110
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
111
                                  quote(user.auth_token),
112
                                  quote(urljoin(BASEURL, reverse('index'))))
113
    message = render_to_string(template_name, {
114
                               'user': user,
115
                               'url': url,
116
                               'baseurl': BASEURL,
117
                               'site_name': SITENAME,
118
                               'support': DEFAULT_CONTACT_EMAIL})
119
    sender = settings.SERVER_EMAIL
120
    try:
121
        send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email],
122
                  connection=get_connection())
123

    
124
    except (SMTPException, socket.error) as e:
125
        logger.exception(e)
126
        raise SendVerificationError()
127
    else:
128
        msg = 'Sent activation %s' % user.email
129
        logger.log(LOGGING_LEVEL, msg)
130

    
131

    
132
def send_activation(user, template_name='im/activation_email.txt'):
133
    send_verification(user, template_name)
134
    user.activation_sent = datetime.now()
135
    user.save()
136

    
137

    
138
def _send_admin_notification(template_name,
139
                             dictionary=None,
140
                             subject='alpha2 testing notification',):
141
    """
142
    Send notification email to settings.ADMINS.
143

144
    Raises SendNotificationError
145
    """
146
    if not settings.ADMINS:
147
        return
148
    dictionary = dictionary or {}
149
    message = render_to_string(template_name, dictionary)
150
    sender = settings.SERVER_EMAIL
151
    try:
152
        send_mail(subject, message, sender, [i[1] for i in settings.ADMINS],
153
                  connection=get_connection())
154
    except (SMTPException, socket.error) as e:
155
        logger.exception(e)
156
        raise SendNotificationError()
157
    else:
158
        msg = 'Sent admin notification for user %s' % \
159
              (dictionary.get('user', {}).get('email', None), )
160
        logger.log(LOGGING_LEVEL, msg)
161

    
162

    
163
def send_account_creation_notification(template_name, dictionary=None):
164
    user = dictionary.get('user', AnonymousUser())
165
    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
166
    return _send_admin_notification(template_name, dictionary, subject=subject)
167

    
168

    
169
def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
170
    """
171
    Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
172

173
    Raises SendNotificationError
174
    """
175
    if not DEFAULT_CONTACT_EMAIL:
176
        return
177
    message = render_to_string(
178
        template_name,
179
        {'user': user}
180
    )
181
    sender = settings.SERVER_EMAIL
182
    try:
183
        send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
184
                  message, sender, [DEFAULT_CONTACT_EMAIL],
185
                  connection=get_connection())
186
    except (SMTPException, socket.error) as e:
187
        logger.exception(e)
188
        raise SendNotificationError()
189
    else:
190
        msg = 'Sent helpdesk admin notification for %s' % user.email
191
        logger.log(LOGGING_LEVEL, msg)
192

    
193

    
194
def send_invitation(invitation, template_name='im/invitation.txt'):
195
    """
196
    Send invitation email.
197

198
    Raises SendInvitationError
199
    """
200
    subject = _(INVITATION_EMAIL_SUBJECT)
201
    url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
202
    message = render_to_string(template_name, {
203
                               'invitation': invitation,
204
                               'url': url,
205
                               'baseurl': BASEURL,
206
                               'site_name': SITENAME,
207
                               'support': DEFAULT_CONTACT_EMAIL})
208
    sender = settings.SERVER_EMAIL
209
    try:
210
        send_mail(subject, message, sender, [invitation.username],
211
                  connection=get_connection())
212
    except (SMTPException, socket.error) as e:
213
        logger.exception(e)
214
        raise SendInvitationError()
215
    else:
216
        msg = 'Sent invitation %s' % invitation
217
        logger.log(LOGGING_LEVEL, msg)
218
        inviter_invitations = invitation.inviter.invitations
219
        invitation.inviter.invitations = max(0, inviter_invitations - 1)
220
        invitation.inviter.save()
221

    
222

    
223
def send_greeting(user, email_template_name='im/welcome_email.txt'):
224
    """
225
    Send welcome email.
226

227
    Raises SMTPException, socket.error
228
    """
229
    subject = _(GREETING_EMAIL_SUBJECT)
230
    message = render_to_string(email_template_name, {
231
                               'user': user,
232
                               'url': urljoin(BASEURL, reverse('index')),
233
                               'baseurl': BASEURL,
234
                               'site_name': SITENAME,
235
                               'support': DEFAULT_CONTACT_EMAIL})
236
    sender = settings.SERVER_EMAIL
237
    try:
238
        send_mail(subject, message, sender, [user.email],
239
                  connection=get_connection())
240
    except (SMTPException, socket.error) as e:
241
        logger.exception(e)
242
        raise SendGreetingError()
243
    else:
244
        msg = 'Sent greeting %s' % user.email
245
        logger.log(LOGGING_LEVEL, msg)
246

    
247

    
248
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
249
    subject = _(FEEDBACK_EMAIL_SUBJECT)
250
    from_email = settings.SERVER_EMAIL
251
    recipient_list = [DEFAULT_CONTACT_EMAIL]
252
    content = render_to_string(email_template_name, {
253
        'message': msg,
254
        'data': data,
255
        'user': user})
256
    try:
257
        send_mail(subject, content, from_email, recipient_list,
258
                  connection=get_connection())
259
    except (SMTPException, socket.error) as e:
260
        logger.exception(e)
261
        raise SendFeedbackError()
262
    else:
263
        msg = 'Sent feedback from %s' % user.email
264
        logger.log(LOGGING_LEVEL, msg)
265

    
266

    
267
def send_change_email(
268
    ec, request, email_template_name='registration/email_change_email.txt'):
269
    try:
270
        url = ec.get_url()
271
        url = request.build_absolute_uri(url)
272
        t = loader.get_template(email_template_name)
273
        c = {'url': url, 'site_name': SITENAME,
274
             'support': DEFAULT_CONTACT_EMAIL, 'ec': ec}
275
        from_email = settings.SERVER_EMAIL
276
        send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT), t.render(Context(c)),
277
                  from_email, [ec.new_email_address],
278
                  connection=get_connection())
279
    except (SMTPException, socket.error) as e:
280
        logger.exception(e)
281
        raise ChangeEmailError()
282
    else:
283
        msg = 'Sent change email for %s' % ec.user.email
284
        logger.log(LOGGING_LEVEL, msg)
285

    
286

    
287
def activate(
288
    user,
289
    email_template_name='im/welcome_email.txt',
290
    helpdesk_email_template_name='im/helpdesk_notification.txt',
291
    verify_email=False):
292
    """
293
    Activates the specific user and sends email.
294

295
    Raises SendGreetingError, ValidationError
296
    """
297
    user.is_active = True
298
    user.email_verified = True
299
    if not user.activation_sent:
300
        user.activation_sent = datetime.now()
301
    user.save()
302
    register_user_with_quotas(user)
303
    send_helpdesk_notification(user, helpdesk_email_template_name)
304
    send_greeting(user, email_template_name)
305

    
306
def deactivate(user):
307
    user.is_active = False
308
    user.save()
309

    
310
def invite(inviter, email, realname):
311
    inv = Invitation(inviter=inviter, username=email, realname=realname)
312
    inv.save()
313
    send_invitation(inv)
314
    inviter.invitations = max(0, inviter.invitations - 1)
315
    inviter.save()
316

    
317
def switch_account_to_shibboleth(user, local_user,
318
                                 greeting_template_name='im/welcome_email.txt'):
319
    try:
320
        provider = user.provider
321
    except AttributeError:
322
        return
323
    else:
324
        if not provider == 'shibboleth':
325
            return
326
        user.delete()
327
        local_user.provider = 'shibboleth'
328
        local_user.third_party_identifier = user.third_party_identifier
329
        local_user.save()
330
        send_greeting(local_user, greeting_template_name)
331
        return local_user
332

    
333

    
334
class SendMailError(Exception):
335
    pass
336

    
337

    
338
class SendAdminNotificationError(SendMailError):
339
    def __init__(self):
340
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
341
        super(SendAdminNotificationError, self).__init__()
342

    
343

    
344
class SendVerificationError(SendMailError):
345
    def __init__(self):
346
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
347
        super(SendVerificationError, self).__init__()
348

    
349

    
350
class SendInvitationError(SendMailError):
351
    def __init__(self):
352
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
353
        super(SendInvitationError, self).__init__()
354

    
355

    
356
class SendGreetingError(SendMailError):
357
    def __init__(self):
358
        self.message = _(astakos_messages.GREETING_SEND_ERR)
359
        super(SendGreetingError, self).__init__()
360

    
361

    
362
class SendFeedbackError(SendMailError):
363
    def __init__(self):
364
        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
365
        super(SendFeedbackError, self).__init__()
366

    
367

    
368
class ChangeEmailError(SendMailError):
369
    def __init__(self):
370
        self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
371
        super(ChangeEmailError, self).__init__()
372

    
373

    
374
class SendNotificationError(SendMailError):
375
    def __init__(self):
376
        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
377
        super(SendNotificationError, self).__init__()
378

    
379

    
380
def register_user_with_quotas(user):
381
    rejected = register_users([user])
382
    if not rejected:
383
        quotas = users_quotas([user])
384
        register_quotas(quotas)
385

    
386

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

    
391

    
392
### PROJECT VIEWS ###
393

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

    
398
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
399

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

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

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

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

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

    
438

    
439
def get_project_for_update(project_id):
440
    try:
441
        return Project.objects.get_for_update(id=project_id)
442
    except Project.DoesNotExist:
443
        raise IOError(
444
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
445

    
446
def get_application_for_update(application_id):
447
    try:
448
        return ProjectApplication.objects.get_for_update(id=application_id)
449
    except ProjectApplication.DoesNotExist:
450
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
451
        raise IOError(m)
452

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

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

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

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

    
473
    m, created = ProjectMembership.objects.get_or_create(
474
        project=project,
475
        person=user)
476

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

    
483

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

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

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

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

    
515
def accept_membership_checks(project, request_user):
516
    checkAllowed(project, request_user)
517
    checkAlive(project)
518

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

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

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

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

    
535
    membership.accept()
536

    
537
    membership_change_notify(project, membership.person, 'accepted')
538

    
539
    return membership
540

    
541
def reject_membership_checks(project, request_user):
542
    checkAllowed(project, request_user)
543
    checkAlive(project)
544

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

    
553
    membership.reject()
554

    
555
    membership_change_notify(project, membership.person, 'rejected')
556

    
557
    return membership
558

    
559
def cancel_membership_checks(project):
560
    checkAlive(project)
561

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

    
570
    membership.cancel()
571

    
572
def remove_membership_checks(project, request_user=None):
573
    checkAllowed(project, request_user)
574
    checkAlive(project)
575

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

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

    
588
    membership.remove()
589

    
590
    membership_change_notify(project, membership.person, 'removed')
591

    
592
    return membership
593

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

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

    
603
    membership.accept()
604
    membership_enroll_notify(project, membership.person)
605

    
606
    return membership
607

    
608
def leave_project_checks(project):
609
    checkAlive(project)
610

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

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

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

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

    
644
def join_project_checks(project):
645
    checkAlive(project)
646

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

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

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

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

    
674
    return auto_accepted
675

    
676
def submit_application(kw, request_user=None):
677

    
678
    kw['applicant'] = request_user
679
    resource_policies = kw.pop('resource_policies', None)
680

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

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

    
695
    reached, limit = reached_pending_application_limit(request_user.id, precursor)
696
    if reached:
697
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
698
        raise PermissionDenied(m)
699

    
700
    application = ProjectApplication(**kw)
701

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

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

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

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

    
728
    application.cancel()
729

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

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

    
739
    application.dismiss()
740

    
741
def deny_application(application_id, reason=None):
742
    application = get_application_for_update(application_id)
743

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

    
749
    if reason is None:
750
        reason = ""
751
    application.deny(reason)
752
    application_deny_notify(application)
753

    
754
def approve_application(app_id):
755

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

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

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

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

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

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

    
784
    project.terminate()
785

    
786
    project_termination_notify(project)
787

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

    
792
    project.suspend()
793

    
794
    project_suspension_notify(project)
795

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

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

    
803
    project.resume()
804

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

    
817

    
818
def get_user_setting(user_id, key):
819
    try:
820
        setting = UserSetting.objects.get(
821
            user=user_id, setting=key)
822
        return setting.value
823
    except UserSetting.DoesNotExist:
824
        return getattr(astakos_settings, key)
825

    
826

    
827
def set_user_setting(user_id, key, value):
828
    try:
829
        setting = UserSetting.objects.get_for_update(
830
            user=user_id, setting=key)
831
    except UserSetting.DoesNotExist:
832
        setting = UserSetting(user_id=user_id, setting=key)
833
    setting.value = value
834
    setting.save()
835

    
836

    
837
def unset_user_setting(user_id, key):
838
    UserSetting.objects.filter(user=user_id, setting=key).delete()
839

    
840

    
841
PENDING_APPLICATION_LIMIT_SETTING = 'PENDING_APPLICATION_LIMIT'
842

    
843
def get_pending_application_limit(user_id):
844
    key = PENDING_APPLICATION_LIMIT_SETTING
845
    return get_user_setting(user_id, key)
846

    
847

    
848
def set_pending_application_limit(user_id, value):
849
    key = PENDING_APPLICATION_LIMIT_SETTING
850
    return set_user_setting(user_id, key, value)
851

    
852

    
853
def unset_pending_application_limit(user_id):
854
    key = PENDING_APPLICATION_LIMIT_SETTING
855
    return unset_user_setting(user_id, key)
856

    
857

    
858
def _reached_pending_application_limit(user_id):
859
    limit = get_pending_application_limit(user_id)
860

    
861
    PENDING = ProjectApplication.PENDING
862
    pending = ProjectApplication.objects.filter(
863
        applicant__id=user_id, state=PENDING).count()
864

    
865
    return pending >= limit, limit
866

    
867

    
868
def reached_pending_application_limit(user_id, precursor=None):
869
    reached, limit = _reached_pending_application_limit(user_id)
870

    
871
    if precursor is None:
872
        return reached, limit
873

    
874
    chain = precursor.chain
875
    objs = ProjectApplication.objects
876
    q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
877
    has_pending = q.exists()
878

    
879
    if not has_pending:
880
        return reached, limit
881

    
882
    return False, limit