Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (28.2 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 synnefo_branding.utils 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):
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
    application.deny()
750
    application_deny_notify(application)
751

    
752
def approve_application(app_id):
753

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

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

    
766
    application.approve()
767
    application_approve_notify(application)
768

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

    
776
    return [project.expiration_info() for project in expired]
777

    
778
def terminate(project_id):
779
    project = get_project_for_update(project_id)
780
    checkAlive(project)
781

    
782
    project.terminate()
783

    
784
    project_termination_notify(project)
785

    
786
def suspend(project_id):
787
    project = get_project_by_id(project_id)
788
    checkAlive(project)
789

    
790
    project.suspend()
791

    
792
    project_suspension_notify(project)
793

    
794
def resume(project_id):
795
    project = get_project_for_update(project_id)
796

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

    
801
    project.resume()
802

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

    
815

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

    
824

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

    
834

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

    
838

    
839
PENDING_APPLICATION_LIMIT_SETTING = 'PENDING_APPLICATION_LIMIT'
840

    
841
def get_pending_application_limit(user_id):
842
    key = PENDING_APPLICATION_LIMIT_SETTING
843
    return get_user_setting(user_id, key)
844

    
845

    
846
def set_pending_application_limit(user_id, value):
847
    key = PENDING_APPLICATION_LIMIT_SETTING
848
    return set_user_setting(user_id, key, value)
849

    
850

    
851
def unset_pending_application_limit(user_id):
852
    key = PENDING_APPLICATION_LIMIT_SETTING
853
    return unset_user_setting(user_id, key)
854

    
855

    
856
def _reached_pending_application_limit(user_id):
857
    limit = get_pending_application_limit(user_id)
858

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

    
863
    return pending >= limit, limit
864

    
865

    
866
def reached_pending_application_limit(user_id, precursor=None):
867
    reached, limit = _reached_pending_application_limit(user_id)
868

    
869
    if precursor is None:
870
        return reached, limit
871

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

    
877
    if not has_pending:
878
        return reached, limit
879

    
880
    return False, limit