Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (27.7 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
    membership, created = ProjectMembership.objects.get_or_create(
463
        project=project,
464
        person=user)
465

    
466
    if not membership.can_accept():
467
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
468
        raise PermissionDenied(m)
469

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

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

    
478

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

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

    
487

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

    
498

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

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

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

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

    
525

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

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

    
534

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

    
541
    m = user.get_membership(project)
542
    return not(m)
543

    
544

    
545
def join_project(project_id, request_user):
546
    get_chain_for_update(project_id)
547
    project = get_project_by_id(project_id)
548
    join_project_checks(project)
549

    
550
    membership, created = ProjectMembership.objects.get_or_create(
551
        project=project,
552
        person=request_user)
553

    
554
    if not created:
555
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
556
        raise PermissionDenied(msg)
557

    
558
    auto_accepted = False
559
    join_policy = project.application.member_join_policy
560
    if (join_policy == AUTO_ACCEPT_POLICY and (
561
            not project.violates_members_limit(adding=1))):
562
        membership.accept()
563
        qh_sync_user(request_user)
564
        logger.info("User %s joined %s." %
565
                    (request_user.log_display, project))
566
        auto_accepted = True
567
    else:
568
        membership_request_notify(project, membership.person)
569
        logger.info("User %s requested to join %s." %
570
                    (request_user.log_display, project))
571
    return auto_accepted
572

    
573

    
574
def submit_application(owner=None,
575
                       name=None,
576
                       project_id=None,
577
                       homepage=None,
578
                       description=None,
579
                       start_date=None,
580
                       end_date=None,
581
                       member_join_policy=None,
582
                       member_leave_policy=None,
583
                       limit_on_members_number=None,
584
                       comments=None,
585
                       resource_policies=None,
586
                       request_user=None):
587

    
588
    project = None
589
    if project_id is not None:
590
        get_chain_for_update(project_id)
591
        project = Project.objects.get(id=project_id)
592

    
593
        if (request_user and
594
            (not project.application.owner == request_user and
595
             not request_user.is_superuser
596
             and not request_user.is_project_admin())):
597
            m = _(astakos_messages.NOT_ALLOWED)
598
            raise PermissionDenied(m)
599

    
600
    force = request_user.is_project_admin()
601
    ok, limit = qh_add_pending_app(owner, project, force)
602
    if not ok:
603
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
604
        raise PermissionDenied(m)
605

    
606
    application = ProjectApplication(
607
        applicant=request_user,
608
        owner=owner,
609
        name=name,
610
        homepage=homepage,
611
        description=description,
612
        start_date=start_date,
613
        end_date=end_date,
614
        member_join_policy=member_join_policy,
615
        member_leave_policy=member_leave_policy,
616
        limit_on_members_number=limit_on_members_number,
617
        comments=comments)
618

    
619
    if project is None:
620
        chain = new_chain()
621
        application.chain_id = chain.chain
622
        application.save()
623
        Project.objects.create(id=chain.chain, application=application)
624
    else:
625
        application.chain = project
626
        application.save()
627
        if project.application.state != ProjectApplication.APPROVED:
628
            project.application = application
629
            project.save()
630

    
631
        pending = ProjectApplication.objects.filter(
632
            chain=project,
633
            state=ProjectApplication.PENDING).exclude(id=application.id)
634
        for app in pending:
635
            app.state = ProjectApplication.REPLACED
636
            app.save()
637

    
638
    if resource_policies is not None:
639
        application.set_resource_policies(resource_policies)
640
    logger.info("User %s submitted %s." %
641
                (request_user.log_display, application.log_display))
642
    application_submit_notify(application)
643
    return application
644

    
645

    
646
def cancel_application(application_id, request_user=None, reason=""):
647
    get_chain_of_application_for_update(application_id)
648
    application = get_application(application_id)
649
    checkAllowed(application, request_user)
650

    
651
    if not application.can_cancel():
652
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL %
653
              (application.id, application.state_display()))
654
        raise PermissionDenied(m)
655

    
656
    qh_release_pending_app(application.owner)
657

    
658
    application.cancel()
659
    logger.info("%s has been cancelled." % (application.log_display))
660

    
661

    
662
def dismiss_application(application_id, request_user=None, reason=""):
663
    get_chain_of_application_for_update(application_id)
664
    application = get_application(application_id)
665
    checkAllowed(application, request_user)
666

    
667
    if not application.can_dismiss():
668
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS %
669
              (application.id, application.state_display()))
670
        raise PermissionDenied(m)
671

    
672
    application.dismiss()
673
    logger.info("%s has been dismissed." % (application.log_display))
674

    
675

    
676
def deny_application(application_id, request_user=None, reason=""):
677
    get_chain_of_application_for_update(application_id)
678
    application = get_application(application_id)
679

    
680
    checkAllowed(application, request_user, admin_only=True)
681

    
682
    if not application.can_deny():
683
        m = _(astakos_messages.APPLICATION_CANNOT_DENY %
684
              (application.id, application.state_display()))
685
        raise PermissionDenied(m)
686

    
687
    qh_release_pending_app(application.owner)
688

    
689
    application.deny(reason)
690
    logger.info("%s has been denied with reason \"%s\"." %
691
                (application.log_display, reason))
692
    application_deny_notify(application)
693

    
694

    
695
def check_conflicting_projects(application):
696
    project = application.chain
697
    new_project_name = application.name
698
    try:
699
        q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
700
        conflicting_project = Project.objects.get(q)
701
        if (conflicting_project != project):
702
            m = (_("cannot approve: project with name '%s' "
703
                   "already exists (id: %s)") %
704
                 (new_project_name, conflicting_project.id))
705
            raise PermissionDenied(m)  # invalid argument
706
    except Project.DoesNotExist:
707
        pass
708

    
709

    
710
def approve_application(app_id, request_user=None, reason=""):
711
    get_chain_of_application_for_update(app_id)
712
    application = get_application(app_id)
713
    project = application.chain
714

    
715
    checkAllowed(application, request_user, admin_only=True)
716

    
717
    if not application.can_approve():
718
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE %
719
              (application.id, application.state_display()))
720
        raise PermissionDenied(m)
721

    
722
    check_conflicting_projects(application)
723

    
724
    # Pre-lock members and owner together in order to impose an ordering
725
    # on locking users
726
    members = members_to_sync(project)
727
    uids_to_sync = [member.id for member in members]
728
    owner = application.owner
729
    uids_to_sync.append(owner.id)
730
    get_users_for_update(uids_to_sync)
731

    
732
    qh_release_pending_app(owner, locked=True)
733
    application.approve(reason)
734
    project.application = application
735
    project.name = application.name
736
    project.save()
737
    if project.is_deactivated():
738
        project.resume()
739
    qh_sync_locked_users(members)
740
    logger.info("%s has been approved." % (application.log_display))
741
    application_approve_notify(application)
742

    
743

    
744
def check_expiration(execute=False):
745
    objects = Project.objects
746
    expired = objects.expired_projects()
747
    if execute:
748
        for project in expired:
749
            terminate(project.pk)
750

    
751
    return [project.expiration_info() for project in expired]
752

    
753

    
754
def terminate(project_id, request_user=None):
755
    get_chain_for_update(project_id)
756
    project = get_project_by_id(project_id)
757
    checkAllowed(project, request_user, admin_only=True)
758
    checkAlive(project)
759

    
760
    project.terminate()
761
    qh_sync_project(project)
762
    logger.info("%s has been terminated." % (project))
763

    
764
    project_termination_notify(project)
765

    
766

    
767
def suspend(project_id, request_user=None):
768
    get_chain_for_update(project_id)
769
    project = get_project_by_id(project_id)
770
    checkAllowed(project, request_user, admin_only=True)
771
    checkAlive(project)
772

    
773
    project.suspend()
774
    qh_sync_project(project)
775
    logger.info("%s has been suspended." % (project))
776

    
777
    project_suspension_notify(project)
778

    
779

    
780
def resume(project_id, request_user=None):
781
    get_chain_for_update(project_id)
782
    project = get_project_by_id(project_id)
783
    checkAllowed(project, request_user, admin_only=True)
784

    
785
    if not project.is_suspended:
786
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.id
787
        raise PermissionDenied(m)
788

    
789
    project.resume()
790
    qh_sync_project(project)
791
    logger.info("%s has been unsuspended." % (project))
792

    
793

    
794
def _partition_by(f, l):
795
    d = {}
796
    for x in l:
797
        group = f(x)
798
        group_l = d.get(group, [])
799
        group_l.append(x)
800
        d[group] = group_l
801
    return d
802

    
803

    
804
def count_pending_app(users):
805
    users = list(users)
806
    apps = ProjectApplication.objects.filter(state=ProjectApplication.PENDING,
807
                                             owner__in=users)
808
    apps_d = _partition_by(lambda a: a.owner.uuid, apps)
809

    
810
    usage = {}
811
    for user in users:
812
        uuid = user.uuid
813
        usage[uuid] = len(apps_d.get(uuid, []))
814
    return usage
815

    
816

    
817
def get_pending_app_diff(user, project):
818
    if project is None:
819
        diff = 1
820
    else:
821
        objs = ProjectApplication.objects
822
        q = objs.filter(chain=project, state=ProjectApplication.PENDING)
823
        count = q.count()
824
        diff = 1 - count
825
    return diff
826

    
827

    
828
def qh_add_pending_app(user, project=None, force=False):
829
    user = AstakosUser.forupdate.get_for_update(id=user.id)
830
    diff = get_pending_app_diff(user, project)
831
    return register_pending_apps(user, diff, force)
832

    
833

    
834
def check_pending_app_quota(user, project=None):
835
    diff = get_pending_app_diff(user, project)
836
    quota = get_pending_app_quota(user)
837
    limit = quota['limit']
838
    usage = quota['usage']
839
    if usage + diff > limit:
840
        return False, limit
841
    return True, None
842

    
843

    
844
def qh_release_pending_app(user, locked=False):
845
    if not locked:
846
        user = AstakosUser.forupdate.get_for_update(id=user.id)
847
    register_pending_apps(user, -1)