Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 1a14083b

History | View | Annotate | Download (28 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
from datetime import datetime
36

    
37
from django.utils.translation import ugettext as _
38
from django.core.mail import send_mail, get_connection
39
from django.core.urlresolvers import reverse
40
from django.contrib.auth import login as auth_login, logout as auth_logout
41
from django.core.exceptions import PermissionDenied
42
from django.db.models import Q
43

    
44
from synnefo_branding.utils import render_to_string
45

    
46
from synnefo.lib import join_urls
47
from astakos.im.models import AstakosUser, Invitation, ProjectMembership, \
48
    ProjectApplication, Project, Chain, new_chain
49
from astakos.im.quotas import qh_sync_user, get_pending_app_quota, \
50
    register_pending_apps, qh_sync_project, qh_sync_locked_users, \
51
    get_users_for_update, members_to_sync
52
from astakos.im.project_notif import membership_change_notify, \
53
    membership_enroll_notify, membership_request_notify, \
54
    membership_leave_request_notify, application_submit_notify, \
55
    application_approve_notify, application_deny_notify, \
56
    project_termination_notify, project_suspension_notify
57
from astakos.im import settings
58

    
59
import astakos.im.messages as astakos_messages
60

    
61
logger = logging.getLogger(__name__)
62

    
63

    
64
def login(request, user):
65
    auth_login(request, user)
66
    from astakos.im.models import SessionCatalog
67
    SessionCatalog(
68
        session_key=request.session.session_key,
69
        user=user
70
    ).save()
71
    logger.info('%s logged in.', user.log_display)
72

    
73

    
74
def logout(request, *args, **kwargs):
75
    user = request.user
76
    auth_logout(request, *args, **kwargs)
77
    logger.info('%s logged out.', user.log_display)
78

    
79

    
80
def send_verification(user, template_name='im/activation_email.txt'):
81
    """
82
    Send email to user to verify his/her email and activate his/her account.
83
    """
84
    url = join_urls(settings.BASE_HOST,
85
                    user.get_activation_url(nxt=reverse('index')))
86
    message = render_to_string(template_name, {
87
                               'user': user,
88
                               'url': url,
89
                               'baseurl': settings.BASE_URL,
90
                               'site_name': settings.SITENAME,
91
                               'support': settings.CONTACT_EMAIL})
92
    sender = settings.SERVER_EMAIL
93
    send_mail(_(astakos_messages.VERIFICATION_EMAIL_SUBJECT), message, sender,
94
              [user.email],
95
              connection=get_connection())
96
    logger.info("Sent user verirfication email: %s", user.log_display)
97

    
98

    
99
def _send_admin_notification(template_name,
100
                             context=None,
101
                             user=None,
102
                             msg="",
103
                             subject='alpha2 testing notification',):
104
    """
105
    Send notification email to settings.HELPDESK + settings.MANAGERS +
106
    settings.ADMINS.
107
    """
108
    if context is None:
109
        context = {}
110
    if not 'user' in context:
111
        context['user'] = user
112

    
113
    message = render_to_string(template_name, context)
114
    sender = settings.SERVER_EMAIL
115
    recipient_list = [e[1] for e in settings.HELPDESK +
116
                      settings.MANAGERS + settings.ADMINS]
117
    send_mail(subject, message, sender, recipient_list,
118
              connection=get_connection())
119
    if user:
120
        msg = 'Sent admin notification (%s) for user %s' % (msg,
121
                                                            user.log_display)
122
    else:
123
        msg = 'Sent admin notification (%s)' % msg
124

    
125
    logger.log(settings.LOGGING_LEVEL, msg)
126

    
127

    
128
def send_account_pending_moderation_notification(
129
        user,
130
        template_name='im/account_pending_moderation_notification.txt'):
131
    """
132
    Notify admins that a new user has verified his email address and moderation
133
    step is required to activate his account.
134
    """
135
    subject = (_(astakos_messages.ACCOUNT_CREATION_SUBJECT) %
136
               {'user': user.email})
137
    return _send_admin_notification(template_name, {}, subject=subject,
138
                                    user=user, msg="account creation")
139

    
140

    
141
def send_account_activated_notification(
142
        user,
143
        template_name='im/account_activated_notification.txt'):
144
    """
145
    Send email to settings.HELPDESK + settings.MANAGERES + settings.ADMINS
146
    lists to notify that a new account has been accepted and activated.
147
    """
148
    message = render_to_string(
149
        template_name,
150
        {'user': user}
151
    )
152
    sender = settings.SERVER_EMAIL
153
    recipient_list = [e[1] for e in settings.HELPDESK +
154
                      settings.MANAGERS + settings.ADMINS]
155
    send_mail(_(astakos_messages.HELPDESK_NOTIFICATION_EMAIL_SUBJECT) %
156
              {'user': user.email},
157
              message, sender, recipient_list, connection=get_connection())
158
    msg = 'Sent helpdesk admin notification for %s' % user.email
159
    logger.log(settings.LOGGING_LEVEL, msg)
160

    
161

    
162
def send_invitation(invitation, template_name='im/invitation.txt'):
163
    """
164
    Send invitation email.
165
    """
166
    subject = _(astakos_messages.INVITATION_EMAIL_SUBJECT)
167
    url = '%s?code=%d' % (join_urls(settings.BASE_HOST,
168
                                    reverse('index')), invitation.code)
169
    message = render_to_string(template_name, {
170
                               'invitation': invitation,
171
                               'url': url,
172
                               'baseurl': settings.BASE_URL,
173
                               'site_name': settings.SITENAME,
174
                               'support': settings.CONTACT_EMAIL})
175
    sender = settings.SERVER_EMAIL
176
    send_mail(subject, message, sender, [invitation.username],
177
              connection=get_connection())
178
    msg = 'Sent invitation %s' % invitation
179
    logger.log(settings.LOGGING_LEVEL, msg)
180
    inviter_invitations = invitation.inviter.invitations
181
    invitation.inviter.invitations = max(0, inviter_invitations - 1)
182
    invitation.inviter.save()
183

    
184

    
185
def send_greeting(user, email_template_name='im/welcome_email.txt'):
186
    """
187
    Send welcome email to an accepted/activated user.
188

189
    Raises SMTPException, socket.error
190
    """
191
    subject = _(astakos_messages.GREETING_EMAIL_SUBJECT)
192
    message = render_to_string(email_template_name, {
193
                               'user': user,
194
                               'url': join_urls(settings.BASE_HOST,
195
                                                reverse('index')),
196
                               'baseurl': settings.BASE_URL,
197
                               'site_name': settings.SITENAME,
198
                               'support': settings.CONTACT_EMAIL})
199
    sender = settings.SERVER_EMAIL
200
    send_mail(subject, message, sender, [user.email],
201
              connection=get_connection())
202
    msg = 'Sent greeting %s' % user.log_display
203
    logger.log(settings.LOGGING_LEVEL, msg)
204

    
205

    
206
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
207
    subject = _(astakos_messages.FEEDBACK_EMAIL_SUBJECT)
208
    from_email = settings.SERVER_EMAIL
209
    recipient_list = [e[1] for e in settings.HELPDESK]
210
    content = render_to_string(email_template_name, {
211
        'message': msg,
212
        'data': data,
213
        'user': user})
214
    send_mail(subject, content, from_email, recipient_list,
215
              connection=get_connection())
216
    msg = 'Sent feedback from %s' % user.log_display
217
    logger.log(settings.LOGGING_LEVEL, msg)
218

    
219

    
220
def send_change_email(
221
    ec, request, email_template_name='registration/email_change_email.txt'
222
):
223
    url = ec.get_url()
224
    url = request.build_absolute_uri(url)
225
    c = {'url': url, 'site_name': settings.SITENAME,
226
         'support': settings.CONTACT_EMAIL,
227
         'ec': ec}
228
    message = render_to_string(email_template_name, c)
229
    from_email = settings.SERVER_EMAIL
230
    send_mail(_(astakos_messages.EMAIL_CHANGE_EMAIL_SUBJECT), message,
231
              from_email,
232
              [ec.new_email_address], connection=get_connection())
233
    msg = 'Sent change email for %s' % ec.user.log_display
234
    logger.log(settings.LOGGING_LEVEL, msg)
235

    
236

    
237
def invite(inviter, email, realname):
238
    inv = Invitation(inviter=inviter, username=email, realname=realname)
239
    inv.save()
240
    send_invitation(inv)
241
    inviter.invitations = max(0, inviter.invitations - 1)
242
    inviter.save()
243

    
244

    
245
### PROJECT FUNCTIONS ###
246

    
247
AUTO_ACCEPT_POLICY = 1
248
MODERATED_POLICY = 2
249
CLOSED_POLICY = 3
250

    
251
POLICIES = [AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY]
252

    
253

    
254
def get_related_project_id(application_id):
255
    try:
256
        app = ProjectApplication.objects.get(id=application_id)
257
        return app.chain_id
258
    except ProjectApplication.DoesNotExist:
259
        return None
260

    
261

    
262
def get_project_by_id(project_id):
263
    try:
264
        return Project.objects.get(id=project_id)
265
    except Project.DoesNotExist:
266
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id
267
        raise IOError(m)
268

    
269

    
270
def get_chain_for_update(chain_id):
271
    try:
272
        return Chain.objects.get_for_update(chain=chain_id)
273
    except Chain.DoesNotExist:
274
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % chain_id
275
        raise IOError(m)
276

    
277

    
278
def get_chain_of_application_for_update(app_id):
279
    app = get_application(app_id)
280
    return Chain.objects.get_for_update(chain=app.chain_id)
281

    
282

    
283
def get_application(application_id):
284
    try:
285
        return ProjectApplication.objects.get(id=application_id)
286
    except ProjectApplication.DoesNotExist:
287
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
288
        raise IOError(m)
289

    
290

    
291
def get_user_by_id(user_id):
292
    try:
293
        return AstakosUser.objects.get(id=user_id)
294
    except AstakosUser.DoesNotExist:
295
        m = _(astakos_messages.UNKNOWN_USER_ID) % user_id
296
        raise IOError(m)
297

    
298

    
299
def get_user_by_uuid(uuid):
300
    try:
301
        return AstakosUser.objects.get(uuid=uuid)
302
    except AstakosUser.DoesNotExist:
303
        m = _(astakos_messages.UNKNOWN_USER_ID) % uuid
304
        raise IOError(m)
305

    
306

    
307
def get_membership(project_id, user_id):
308
    try:
309
        objs = ProjectMembership.objects.select_related('project', 'person')
310
        return objs.get(project__id=project_id, person__id=user_id)
311
    except ProjectMembership.DoesNotExist:
312
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
313
        raise IOError(m)
314

    
315

    
316
def get_membership_by_id(project_id, memb_id):
317
    try:
318
        objs = ProjectMembership.objects.select_related('project', 'person')
319
        return objs.get(project__id=project_id, id=memb_id)
320
    except ProjectMembership.DoesNotExist:
321
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
322
        raise IOError(m)
323

    
324

    
325
def checkAllowed(entity, request_user, admin_only=False):
326
    if isinstance(entity, Project):
327
        application = entity.application
328
    elif isinstance(entity, ProjectApplication):
329
        application = entity
330
    else:
331
        m = "%s not a Project nor a ProjectApplication" % (entity,)
332
        raise ValueError(m)
333

    
334
    if not request_user or request_user.is_project_admin():
335
        return
336

    
337
    if not admin_only and application.owner == request_user:
338
        return
339

    
340
    m = _(astakos_messages.NOT_ALLOWED)
341
    raise PermissionDenied(m)
342

    
343

    
344
def checkAlive(project):
345
    if not project.is_alive:
346
        m = _(astakos_messages.NOT_ALIVE_PROJECT) % project.id
347
        raise PermissionDenied(m)
348

    
349

    
350
def accept_membership_checks(project, request_user):
351
    checkAllowed(project, request_user)
352
    checkAlive(project)
353

    
354
    join_policy = project.application.member_join_policy
355
    if join_policy == CLOSED_POLICY:
356
        m = _(astakos_messages.MEMBER_JOIN_POLICY_CLOSED)
357
        raise PermissionDenied(m)
358

    
359
    if project.violates_members_limit(adding=1):
360
        m = _(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED)
361
        raise PermissionDenied(m)
362

    
363

    
364
def accept_membership(project_id, memb_id, request_user=None):
365
    get_chain_for_update(project_id)
366

    
367
    membership = get_membership_by_id(project_id, memb_id)
368
    if not membership.can_accept():
369
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
370
        raise PermissionDenied(m)
371

    
372
    project = membership.project
373
    accept_membership_checks(project, request_user)
374
    user = membership.person
375
    membership.accept()
376
    qh_sync_user(user)
377
    logger.info("User %s has been accepted in %s." %
378
                (user.log_display, project))
379

    
380
    membership_change_notify(project, user, 'accepted')
381
    return membership
382

    
383

    
384
def reject_membership_checks(project, request_user):
385
    checkAllowed(project, request_user)
386
    checkAlive(project)
387

    
388

    
389
def reject_membership(project_id, memb_id, request_user=None):
390
    get_chain_for_update(project_id)
391

    
392
    membership = get_membership_by_id(project_id, memb_id)
393
    if not membership.can_reject():
394
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
395
        raise PermissionDenied(m)
396

    
397
    project = membership.project
398
    reject_membership_checks(project, request_user)
399
    user = membership.person
400
    membership.reject()
401
    logger.info("Request of user %s for %s has been rejected." %
402
                (user.log_display, project))
403

    
404
    membership_change_notify(project, user, 'rejected')
405
    return membership
406

    
407

    
408
def cancel_membership_checks(project):
409
    checkAlive(project)
410

    
411

    
412
def cancel_membership(project_id, request_user):
413
    get_chain_for_update(project_id)
414

    
415
    membership = get_membership(project_id, request_user.id)
416
    if not membership.can_cancel():
417
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
418
        raise PermissionDenied(m)
419

    
420
    project = membership.project
421
    cancel_membership_checks(project)
422
    membership.cancel()
423
    logger.info("Request of user %s for %s has been cancelled." %
424
                (membership.person.log_display, project))
425

    
426

    
427
def remove_membership_checks(project, request_user=None):
428
    checkAllowed(project, request_user)
429
    checkAlive(project)
430

    
431
    leave_policy = project.application.member_leave_policy
432
    if leave_policy == CLOSED_POLICY:
433
        m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
434
        raise PermissionDenied(m)
435

    
436

    
437
def remove_membership(project_id, memb_id, request_user=None):
438
    get_chain_for_update(project_id)
439

    
440
    membership = get_membership_by_id(project_id, memb_id)
441
    if not membership.can_remove():
442
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
443
        raise PermissionDenied(m)
444

    
445
    project = membership.project
446
    remove_membership_checks(project, request_user)
447
    user = membership.person
448
    membership.remove()
449
    qh_sync_user(user)
450
    logger.info("User %s has been removed from %s." %
451
                (user.log_display, project))
452

    
453
    membership_change_notify(project, user, 'removed')
454
    return membership
455

    
456

    
457
def enroll_member(project_id, user, request_user=None):
458
    get_chain_for_update(project_id)
459
    project = get_project_by_id(project_id)
460
    accept_membership_checks(project, request_user)
461

    
462
    try:
463
        membership = get_membership(project_id, user.id)
464
        if not membership.can_enroll():
465
            m = _(astakos_messages.MEMBERSHIP_ACCEPTED)
466
            raise PermissionDenied(m)
467
        membership.join()
468
    except IOError:
469
        membership = new_membership(project, user)
470

    
471
    membership.accept()
472
    qh_sync_user(user)
473
    logger.info("User %s has been enrolled in %s." %
474
                (membership.person.log_display, project))
475

    
476
    membership_enroll_notify(project, membership.person)
477
    return membership
478

    
479

    
480
def leave_project_checks(project):
481
    checkAlive(project)
482

    
483
    leave_policy = project.application.member_leave_policy
484
    if leave_policy == CLOSED_POLICY:
485
        m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
486
        raise PermissionDenied(m)
487

    
488

    
489
def can_leave_request(project, user):
490
    try:
491
        leave_project_checks(project)
492
    except PermissionDenied:
493
        return False
494
    m = user.get_membership(project)
495
    if m is None:
496
        return False
497
    return m.can_leave()
498

    
499

    
500
def leave_project(project_id, request_user):
501
    get_chain_for_update(project_id)
502

    
503
    membership = get_membership(project_id, request_user.id)
504
    if not membership.can_leave():
505
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
506
        raise PermissionDenied(m)
507

    
508
    project = membership.project
509
    leave_project_checks(project)
510

    
511
    auto_accepted = False
512
    leave_policy = project.application.member_leave_policy
513
    if leave_policy == AUTO_ACCEPT_POLICY:
514
        membership.remove()
515
        qh_sync_user(request_user)
516
        logger.info("User %s has left %s." %
517
                    (request_user.log_display, project))
518
        auto_accepted = True
519
    else:
520
        membership.leave_request()
521
        logger.info("User %s requested to leave %s." %
522
                    (request_user.log_display, project))
523
        membership_leave_request_notify(project, membership.person)
524
    return auto_accepted
525

    
526

    
527
def join_project_checks(project):
528
    checkAlive(project)
529

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

    
535

    
536
def can_join_request(project, user):
537
    try:
538
        join_project_checks(project)
539
    except PermissionDenied:
540
        return False
541

    
542
    m = user.get_membership(project)
543
    if not m:
544
        return True
545
    return m.can_join()
546

    
547

    
548
def new_membership(project, user):
549
    m = ProjectMembership.objects.create(project=project, person=user)
550
    m._log_create(None, ProjectMembership.REQUESTED)
551
    return m
552

    
553

    
554
def join_project(project_id, request_user):
555
    get_chain_for_update(project_id)
556
    project = get_project_by_id(project_id)
557
    join_project_checks(project)
558

    
559
    try:
560
        membership = get_membership(project.id, request_user.id)
561
        if not membership.can_join():
562
            msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
563
            raise PermissionDenied(msg)
564
        membership.join()
565
    except IOError:
566
        membership = new_membership(project, request_user)
567

    
568
    auto_accepted = False
569
    join_policy = project.application.member_join_policy
570
    if (join_policy == AUTO_ACCEPT_POLICY and (
571
            not project.violates_members_limit(adding=1))):
572
        membership.accept()
573
        qh_sync_user(request_user)
574
        logger.info("User %s joined %s." %
575
                    (request_user.log_display, project))
576
        auto_accepted = True
577
    else:
578
        membership_request_notify(project, membership.person)
579
        logger.info("User %s requested to join %s." %
580
                    (request_user.log_display, project))
581
    return auto_accepted
582

    
583

    
584
def submit_application(owner=None,
585
                       name=None,
586
                       project_id=None,
587
                       homepage=None,
588
                       description=None,
589
                       start_date=None,
590
                       end_date=None,
591
                       member_join_policy=None,
592
                       member_leave_policy=None,
593
                       limit_on_members_number=None,
594
                       comments=None,
595
                       resource_policies=None,
596
                       request_user=None):
597

    
598
    project = None
599
    if project_id is not None:
600
        get_chain_for_update(project_id)
601
        project = Project.objects.get(id=project_id)
602

    
603
        if (request_user and
604
            (not project.application.owner == request_user and
605
             not request_user.is_superuser
606
             and not request_user.is_project_admin())):
607
            m = _(astakos_messages.NOT_ALLOWED)
608
            raise PermissionDenied(m)
609

    
610
    force = request_user.is_project_admin()
611
    ok, limit = qh_add_pending_app(owner, project, force)
612
    if not ok:
613
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
614
        raise PermissionDenied(m)
615

    
616
    application = ProjectApplication(
617
        applicant=request_user,
618
        owner=owner,
619
        name=name,
620
        homepage=homepage,
621
        description=description,
622
        start_date=start_date,
623
        end_date=end_date,
624
        member_join_policy=member_join_policy,
625
        member_leave_policy=member_leave_policy,
626
        limit_on_members_number=limit_on_members_number,
627
        comments=comments)
628

    
629
    if project is None:
630
        chain = new_chain()
631
        application.chain_id = chain.chain
632
        application.save()
633
        Project.objects.create(id=chain.chain, application=application)
634
    else:
635
        application.chain = project
636
        application.save()
637
        if project.application.state != ProjectApplication.APPROVED:
638
            project.application = application
639
            project.save()
640

    
641
        pending = ProjectApplication.objects.filter(
642
            chain=project,
643
            state=ProjectApplication.PENDING).exclude(id=application.id)
644
        for app in pending:
645
            app.state = ProjectApplication.REPLACED
646
            app.save()
647

    
648
    if resource_policies is not None:
649
        application.set_resource_policies(resource_policies)
650
    logger.info("User %s submitted %s." %
651
                (request_user.log_display, application.log_display))
652
    application_submit_notify(application)
653
    return application
654

    
655

    
656
def cancel_application(application_id, request_user=None, reason=""):
657
    get_chain_of_application_for_update(application_id)
658
    application = get_application(application_id)
659
    checkAllowed(application, request_user)
660

    
661
    if not application.can_cancel():
662
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL %
663
              (application.id, application.state_display()))
664
        raise PermissionDenied(m)
665

    
666
    qh_release_pending_app(application.owner)
667

    
668
    application.cancel()
669
    logger.info("%s has been cancelled." % (application.log_display))
670

    
671

    
672
def dismiss_application(application_id, request_user=None, reason=""):
673
    get_chain_of_application_for_update(application_id)
674
    application = get_application(application_id)
675
    checkAllowed(application, request_user)
676

    
677
    if not application.can_dismiss():
678
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS %
679
              (application.id, application.state_display()))
680
        raise PermissionDenied(m)
681

    
682
    application.dismiss()
683
    logger.info("%s has been dismissed." % (application.log_display))
684

    
685

    
686
def deny_application(application_id, request_user=None, reason=""):
687
    get_chain_of_application_for_update(application_id)
688
    application = get_application(application_id)
689

    
690
    checkAllowed(application, request_user, admin_only=True)
691

    
692
    if not application.can_deny():
693
        m = _(astakos_messages.APPLICATION_CANNOT_DENY %
694
              (application.id, application.state_display()))
695
        raise PermissionDenied(m)
696

    
697
    qh_release_pending_app(application.owner)
698

    
699
    application.deny(reason)
700
    logger.info("%s has been denied with reason \"%s\"." %
701
                (application.log_display, reason))
702
    application_deny_notify(application)
703

    
704

    
705
def check_conflicting_projects(application):
706
    project = application.chain
707
    new_project_name = application.name
708
    try:
709
        q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
710
        conflicting_project = Project.objects.get(q)
711
        if (conflicting_project != project):
712
            m = (_("cannot approve: project with name '%s' "
713
                   "already exists (id: %s)") %
714
                 (new_project_name, conflicting_project.id))
715
            raise PermissionDenied(m)  # invalid argument
716
    except Project.DoesNotExist:
717
        pass
718

    
719

    
720
def approve_application(app_id, request_user=None, reason=""):
721
    get_chain_of_application_for_update(app_id)
722
    application = get_application(app_id)
723
    project = application.chain
724

    
725
    checkAllowed(application, request_user, admin_only=True)
726

    
727
    if not application.can_approve():
728
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE %
729
              (application.id, application.state_display()))
730
        raise PermissionDenied(m)
731

    
732
    check_conflicting_projects(application)
733

    
734
    # Pre-lock members and owner together in order to impose an ordering
735
    # on locking users
736
    members = members_to_sync(project)
737
    uids_to_sync = [member.id for member in members]
738
    owner = application.owner
739
    uids_to_sync.append(owner.id)
740
    get_users_for_update(uids_to_sync)
741

    
742
    qh_release_pending_app(owner, locked=True)
743
    application.approve(reason)
744
    project.application = application
745
    project.name = application.name
746
    project.save()
747
    if project.is_deactivated():
748
        project.resume()
749
    qh_sync_locked_users(members)
750
    logger.info("%s has been approved." % (application.log_display))
751
    application_approve_notify(application)
752

    
753

    
754
def check_expiration(execute=False):
755
    objects = Project.objects
756
    expired = objects.expired_projects()
757
    if execute:
758
        for project in expired:
759
            terminate(project.pk)
760

    
761
    return [project.expiration_info() for project in expired]
762

    
763

    
764
def terminate(project_id, request_user=None):
765
    get_chain_for_update(project_id)
766
    project = get_project_by_id(project_id)
767
    checkAllowed(project, request_user, admin_only=True)
768
    checkAlive(project)
769

    
770
    project.terminate()
771
    qh_sync_project(project)
772
    logger.info("%s has been terminated." % (project))
773

    
774
    project_termination_notify(project)
775

    
776

    
777
def suspend(project_id, request_user=None):
778
    get_chain_for_update(project_id)
779
    project = get_project_by_id(project_id)
780
    checkAllowed(project, request_user, admin_only=True)
781
    checkAlive(project)
782

    
783
    project.suspend()
784
    qh_sync_project(project)
785
    logger.info("%s has been suspended." % (project))
786

    
787
    project_suspension_notify(project)
788

    
789

    
790
def resume(project_id, request_user=None):
791
    get_chain_for_update(project_id)
792
    project = get_project_by_id(project_id)
793
    checkAllowed(project, request_user, admin_only=True)
794

    
795
    if not project.is_suspended:
796
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.id
797
        raise PermissionDenied(m)
798

    
799
    project.resume()
800
    qh_sync_project(project)
801
    logger.info("%s has been unsuspended." % (project))
802

    
803

    
804
def _partition_by(f, l):
805
    d = {}
806
    for x in l:
807
        group = f(x)
808
        group_l = d.get(group, [])
809
        group_l.append(x)
810
        d[group] = group_l
811
    return d
812

    
813

    
814
def count_pending_app(users):
815
    users = list(users)
816
    apps = ProjectApplication.objects.filter(state=ProjectApplication.PENDING,
817
                                             owner__in=users)
818
    apps_d = _partition_by(lambda a: a.owner.uuid, apps)
819

    
820
    usage = {}
821
    for user in users:
822
        uuid = user.uuid
823
        usage[uuid] = len(apps_d.get(uuid, []))
824
    return usage
825

    
826

    
827
def get_pending_app_diff(user, project):
828
    if project is None:
829
        diff = 1
830
    else:
831
        objs = ProjectApplication.objects
832
        q = objs.filter(chain=project, state=ProjectApplication.PENDING)
833
        count = q.count()
834
        diff = 1 - count
835
    return diff
836

    
837

    
838
def qh_add_pending_app(user, project=None, force=False):
839
    user = AstakosUser.forupdate.get_for_update(id=user.id)
840
    diff = get_pending_app_diff(user, project)
841
    return register_pending_apps(user, diff, force)
842

    
843

    
844
def check_pending_app_quota(user, project=None):
845
    diff = get_pending_app_diff(user, project)
846
    quota = get_pending_app_quota(user)
847
    limit = quota['limit']
848
    usage = quota['usage']
849
    if usage + diff > limit:
850
        return False, limit
851
    return True, None
852

    
853

    
854
def qh_release_pending_app(user, locked=False):
855
    if not locked:
856
        user = AstakosUser.forupdate.get_for_update(id=user.id)
857
    register_pending_apps(user, -1)