Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (27.8 kB)

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

    
34
import logging
35
import socket
36

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

    
49
from synnefo_branding.utils import render_to_string
50

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

    
56
from synnefo.lib import join_urls
57
from astakos.im.settings import (
58
    CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
59
    VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
60
    GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
61
    INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
62
    EMAIL_CHANGE_EMAIL_SUBJECT,
63
    )
64
from astakos.im.notifications import build_notification, NotificationError
65
from astakos.im.models import (
66
    AstakosUser, Invitation, ProjectMembership, ProjectApplication, Project,
67
    UserSetting, new_chain)
68
from astakos.im.quotas import (qh_sync_user,
69
                               register_pending_apps, qh_sync_project)
70
from astakos.im.project_notif import (
71
    membership_change_notify, membership_enroll_notify,
72
    membership_request_notify, membership_leave_request_notify,
73
    application_submit_notify, application_approve_notify,
74
    application_deny_notify,
75
    project_termination_notify, project_suspension_notify)
76
from astakos.im import settings
77
import astakos.im.messages as astakos_messages
78

    
79
logger = logging.getLogger(__name__)
80

    
81

    
82
def login(request, user):
83
    auth_login(request, user)
84
    from astakos.im.models import SessionCatalog
85
    SessionCatalog(
86
        session_key=request.session.session_key,
87
        user=user
88
    ).save()
89
    logger.info('%s logged in.', user.log_display)
90

    
91

    
92
def logout(request, *args, **kwargs):
93
    user = request.user
94
    auth_logout(request, *args, **kwargs)
95
    logger.info('%s logged out.', user.log_display)
96

    
97

    
98
def send_verification(user, template_name='im/activation_email.txt'):
99
    """
100
    Send email to user to verify his/her email and activate his/her account.
101
    """
102
    url = join_urls(BASEURL, user.get_activation_url(nxt=reverse('index')))
103
    message = render_to_string(template_name, {
104
                               'user': user,
105
                               'url': url,
106
                               'baseurl': BASEURL,
107
                               'site_name': SITENAME,
108
                               'support': CONTACT_EMAIL})
109
    sender = settings.SERVER_EMAIL
110
    send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email],
111
              connection=get_connection())
112
    logger.info("Sent user verirfication email: %s", user.log_display)
113

    
114

    
115
def _send_admin_notification(template_name,
116
                             context=None,
117
                             user=None,
118
                             msg="",
119
                             subject='alpha2 testing notification',):
120
    """
121
    Send notification email to settings.HELPDESK + settings.MANAGERS +
122
    settings.ADMINS.
123
    """
124
    if context is None:
125
        context = {}
126
    if not 'user' in context:
127
        context['user'] = user
128

    
129
    message = render_to_string(template_name, context)
130
    sender = settings.SERVER_EMAIL
131
    recipient_list = [e[1] for e in settings.HELPDESK +
132
                      settings.MANAGERS + settings.ADMINS]
133
    send_mail(subject, message, sender, recipient_list,
134
              connection=get_connection())
135
    if user:
136
        msg = 'Sent admin notification (%s) for user %s' % (msg,
137
                                                            user.log_display)
138
    else:
139
        msg = 'Sent admin notification (%s)' % msg
140

    
141
    logger.log(LOGGING_LEVEL, msg)
142

    
143

    
144
def send_account_pending_moderation_notification(
145
        user,
146
        template_name='im/account_pending_moderation_notification.txt'):
147
    """
148
    Notify admins that a new user has verified his email address and moderation
149
    step is required to activate his account.
150
    """
151
    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user': user.email}
152
    return _send_admin_notification(template_name, {}, subject=subject,
153
                                    user=user, msg="account creation")
154

    
155

    
156
def send_account_activated_notification(
157
        user,
158
        template_name='im/account_activated_notification.txt'):
159
    """
160
    Send email to settings.HELPDESK + settings.MANAGERES + settings.ADMINS
161
    lists to notify that a new account has been accepted and activated.
162
    """
163
    message = render_to_string(
164
        template_name,
165
        {'user': user}
166
    )
167
    sender = settings.SERVER_EMAIL
168
    recipient_list = [e[1] for e in settings.HELPDESK +
169
                      settings.MANAGERS + settings.ADMINS]
170
    send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
171
              message, sender, recipient_list, connection=get_connection())
172
    msg = 'Sent helpdesk admin notification for %s' % user.email
173
    logger.log(LOGGING_LEVEL, msg)
174

    
175

    
176
def send_invitation(invitation, template_name='im/invitation.txt'):
177
    """
178
    Send invitation email.
179
    """
180
    subject = _(INVITATION_EMAIL_SUBJECT)
181
    url = '%s?code=%d' % (join_urls(BASEURL, reverse('index')), invitation.code)
182
    message = render_to_string(template_name, {
183
                               'invitation': invitation,
184
                               'url': url,
185
                               'baseurl': BASEURL,
186
                               'site_name': SITENAME,
187
                               'support': CONTACT_EMAIL})
188
    sender = settings.SERVER_EMAIL
189
    send_mail(subject, message, sender, [invitation.username],
190
              connection=get_connection())
191
    msg = 'Sent invitation %s' % invitation
192
    logger.log(LOGGING_LEVEL, msg)
193
    inviter_invitations = invitation.inviter.invitations
194
    invitation.inviter.invitations = max(0, inviter_invitations - 1)
195
    invitation.inviter.save()
196

    
197

    
198
def send_greeting(user, email_template_name='im/welcome_email.txt'):
199
    """
200
    Send welcome email to an accepted/activated user.
201

202
    Raises SMTPException, socket.error
203
    """
204
    subject = _(GREETING_EMAIL_SUBJECT)
205
    message = render_to_string(email_template_name, {
206
                               'user': user,
207
                               'url': join_urls(BASEURL, reverse('index')),
208
                               'baseurl': BASEURL,
209
                               'site_name': SITENAME,
210
                               'support': CONTACT_EMAIL})
211
    sender = settings.SERVER_EMAIL
212
    send_mail(subject, message, sender, [user.email],
213
              connection=get_connection())
214
    msg = 'Sent greeting %s' % user.log_display
215
    logger.log(LOGGING_LEVEL, msg)
216

    
217

    
218
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
219
    subject = _(FEEDBACK_EMAIL_SUBJECT)
220
    from_email = settings.SERVER_EMAIL
221
    recipient_list = [e[1] for e in settings.HELPDESK]
222
    content = render_to_string(email_template_name, {
223
        'message': msg,
224
        'data': data,
225
        'user': user})
226
    send_mail(subject, content, from_email, recipient_list,
227
              connection=get_connection())
228
    msg = 'Sent feedback from %s' % user.log_display
229
    logger.log(LOGGING_LEVEL, msg)
230

    
231

    
232
def send_change_email(
233
    ec, request, email_template_name='registration/email_change_email.txt'):
234
    url = ec.get_url()
235
    url = request.build_absolute_uri(url)
236
    c = {'url': url, 'site_name': SITENAME, 'support': CONTACT_EMAIL,
237
         'ec': ec}
238
    message = render_to_string(email_template_name, c)
239
    from_email = settings.SERVER_EMAIL
240
    send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT), message, from_email,
241
              [ec.new_email_address], connection=get_connection())
242
    msg = 'Sent change email for %s' % ec.user.log_display
243
    logger.log(LOGGING_LEVEL, msg)
244

    
245

    
246
def invite(inviter, email, realname):
247
    inv = Invitation(inviter=inviter, username=email, realname=realname)
248
    inv.save()
249
    send_invitation(inv)
250
    inviter.invitations = max(0, inviter.invitations - 1)
251
    inviter.save()
252

    
253

    
254
### PROJECT FUNCTIONS ###
255

    
256
AUTO_ACCEPT_POLICY = 1
257
MODERATED_POLICY = 2
258
CLOSED_POLICY = 3
259

    
260
POLICIES = [AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY]
261

    
262

    
263
def get_project_by_application_id(project_application_id):
264
    try:
265
        return Project.objects.get(application__id=project_application_id)
266
    except Project.DoesNotExist:
267
        m = (_(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) %
268
             project_application_id)
269
        raise IOError(m)
270

    
271

    
272
def get_related_project_id(application_id):
273
    try:
274
        app = ProjectApplication.objects.get(id=application_id)
275
        chain = app.chain
276
        Project.objects.get(id=chain)
277
        return chain
278
    except (ProjectApplication.DoesNotExist, Project.DoesNotExist):
279
        return None
280

    
281

    
282
def get_chain_of_application_id(application_id):
283
    try:
284
        app = ProjectApplication.objects.get(id=application_id)
285
        chain = app.chain
286
        return chain.chain
287
    except ProjectApplication.DoesNotExist:
288
        return None
289

    
290

    
291
def get_project_by_id(project_id):
292
    try:
293
        return Project.objects.get(id=project_id)
294
    except Project.DoesNotExist:
295
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id
296
        raise IOError(m)
297

    
298

    
299
def get_project_by_name(name):
300
    try:
301
        return Project.objects.get(name=name)
302
    except Project.DoesNotExist:
303
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % name
304
        raise IOError(m)
305

    
306

    
307
def get_project_for_update(project_id):
308
    try:
309
        return Project.objects.get_for_update(id=project_id)
310
    except Project.DoesNotExist:
311
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id
312
        raise IOError(m)
313

    
314

    
315
def get_application_for_update(application_id):
316
    try:
317
        return ProjectApplication.objects.get_for_update(id=application_id)
318
    except ProjectApplication.DoesNotExist:
319
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
320
        raise IOError(m)
321

    
322

    
323
def get_user_by_id(user_id):
324
    try:
325
        return AstakosUser.objects.get(id=user_id)
326
    except AstakosUser.DoesNotExist:
327
        m = _(astakos_messages.UNKNOWN_USER_ID) % user_id
328
        raise IOError(m)
329

    
330

    
331
def get_user_by_uuid(uuid):
332
    try:
333
        return AstakosUser.objects.get(uuid=uuid)
334
    except AstakosUser.DoesNotExist:
335
        m = _(astakos_messages.UNKNOWN_USER_ID) % uuid
336
        raise IOError(m)
337

    
338

    
339
def get_membership_for_update(project_id, user_id):
340
    try:
341
        objs = ProjectMembership.objects
342
        return objs.get_for_update(project__id=project_id,
343
                                   person__id=user_id)
344
    except ProjectMembership.DoesNotExist:
345
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
346
        raise IOError(m)
347

    
348

    
349
def get_membership_for_update_by_id(project_id, memb_id):
350
    try:
351
        objs = ProjectMembership.objects
352
        return objs.get_for_update(project__id=project_id,
353
                                   id=memb_id)
354
    except ProjectMembership.DoesNotExist:
355
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
356
        raise IOError(m)
357

    
358

    
359
def checkAllowed(entity, request_user, admin_only=False):
360
    if isinstance(entity, Project):
361
        application = entity.application
362
    elif isinstance(entity, ProjectApplication):
363
        application = entity
364
    else:
365
        m = "%s not a Project nor a ProjectApplication" % (entity,)
366
        raise ValueError(m)
367

    
368
    if not request_user or request_user.is_project_admin():
369
        return
370

    
371
    if not admin_only and application.owner == request_user:
372
        return
373

    
374
    m = _(astakos_messages.NOT_ALLOWED)
375
    raise PermissionDenied(m)
376

    
377

    
378
def checkAlive(project):
379
    if not project.is_alive:
380
        m = _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__
381
        raise PermissionDenied(m)
382

    
383

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

    
388
    join_policy = project.application.member_join_policy
389
    if join_policy == CLOSED_POLICY:
390
        m = _(astakos_messages.MEMBER_JOIN_POLICY_CLOSED)
391
        raise PermissionDenied(m)
392

    
393
    if project.violates_members_limit(adding=1):
394
        m = _(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED)
395
        raise PermissionDenied(m)
396

    
397

    
398
def accept_membership(project_id, memb_id, request_user=None):
399
    project = get_project_for_update(project_id)
400
    accept_membership_checks(project, request_user)
401

    
402
    membership = get_membership_for_update_by_id(project_id, memb_id)
403
    if not membership.can_accept():
404
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
405
        raise PermissionDenied(m)
406

    
407
    user = membership.person
408
    membership.accept()
409
    qh_sync_user(user)
410
    logger.info("User %s has been accepted in %s." %
411
                (user.log_display, project))
412

    
413
    membership_change_notify(project, user, 'accepted')
414
    return membership
415

    
416

    
417
def reject_membership_checks(project, request_user):
418
    checkAllowed(project, request_user)
419
    checkAlive(project)
420

    
421

    
422
def reject_membership(project_id, memb_id, request_user=None):
423
    project = get_project_for_update(project_id)
424
    reject_membership_checks(project, request_user)
425
    membership = get_membership_for_update_by_id(project_id, memb_id)
426
    if not membership.can_reject():
427
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
428
        raise PermissionDenied(m)
429

    
430
    user = membership.person
431
    membership.reject()
432
    logger.info("Request of user %s for %s has been rejected." %
433
                (user.log_display, project))
434

    
435
    membership_change_notify(project, user, 'rejected')
436
    return membership
437

    
438

    
439
def cancel_membership_checks(project):
440
    checkAlive(project)
441

    
442

    
443
def cancel_membership(project_id, request_user):
444
    project = get_project_for_update(project_id)
445
    cancel_membership_checks(project)
446
    membership = get_membership_for_update(project_id, request_user.id)
447
    if not membership.can_cancel():
448
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
449
        raise PermissionDenied(m)
450

    
451
    membership.cancel()
452
    logger.info("Request of user %s for %s has been cancelled." %
453
                (membership.person.log_display, project))
454

    
455

    
456
def remove_membership_checks(project, request_user=None):
457
    checkAllowed(project, request_user)
458
    checkAlive(project)
459

    
460
    leave_policy = project.application.member_leave_policy
461
    if leave_policy == CLOSED_POLICY:
462
        m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
463
        raise PermissionDenied(m)
464

    
465

    
466
def remove_membership(project_id, memb_id, request_user=None):
467
    project = get_project_for_update(project_id)
468
    remove_membership_checks(project, request_user)
469
    membership = get_membership_for_update_by_id(project_id, memb_id)
470
    if not membership.can_remove():
471
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
472
        raise PermissionDenied(m)
473

    
474
    user = membership.person
475
    membership.remove()
476
    qh_sync_user(user)
477
    logger.info("User %s has been removed from %s." %
478
                (user.log_display, project))
479

    
480
    membership_change_notify(project, user, 'removed')
481
    return membership
482

    
483

    
484
def enroll_member(project_id, user, request_user=None):
485
    project = get_project_for_update(project_id)
486
    accept_membership_checks(project, request_user)
487

    
488
    membership, created = ProjectMembership.objects.get_or_create(
489
        project=project,
490
        person=user)
491

    
492
    if not membership.can_accept():
493
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
494
        raise PermissionDenied(m)
495

    
496
    membership.accept()
497
    qh_sync_user(user)
498
    logger.info("User %s has been enrolled in %s." %
499
                (membership.person.log_display, project))
500

    
501
    membership_enroll_notify(project, membership.person)
502
    return membership
503

    
504

    
505
def leave_project_checks(project):
506
    checkAlive(project)
507

    
508
    leave_policy = project.application.member_leave_policy
509
    if leave_policy == CLOSED_POLICY:
510
        m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
511
        raise PermissionDenied(m)
512

    
513

    
514
def can_leave_request(project, user):
515
    leave_policy = project.application.member_leave_policy
516
    if leave_policy == CLOSED_POLICY:
517
        return False
518
    m = user.get_membership(project)
519
    if m is None:
520
        return False
521
    if m.state != ProjectMembership.ACCEPTED:
522
        return False
523
    return True
524

    
525

    
526
def leave_project(project_id, request_user):
527
    project = get_project_for_update(project_id)
528
    leave_project_checks(project)
529
    membership = get_membership_for_update(project_id, request_user.id)
530
    if not membership.can_leave():
531
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
532
        raise PermissionDenied(m)
533

    
534
    auto_accepted = False
535
    leave_policy = project.application.member_leave_policy
536
    if leave_policy == AUTO_ACCEPT_POLICY:
537
        membership.remove()
538
        qh_sync_user(request_user)
539
        logger.info("User %s has left %s." %
540
                    (membership.person.log_display, project))
541
        auto_accepted = True
542
    else:
543
        membership.leave_request()
544
        logger.info("User %s requested to leave %s." %
545
                    (membership.person.log_display, project))
546
        membership_leave_request_notify(project, membership.person)
547
    return auto_accepted
548

    
549

    
550
def join_project_checks(project):
551
    checkAlive(project)
552

    
553
    join_policy = project.application.member_join_policy
554
    if join_policy == CLOSED_POLICY:
555
        m = _(astakos_messages.MEMBER_JOIN_POLICY_CLOSED)
556
        raise PermissionDenied(m)
557

    
558

    
559
def can_join_request(project, user):
560
    join_policy = project.application.member_join_policy
561
    if join_policy == CLOSED_POLICY:
562
        return False
563
    m = user.get_membership(project)
564
    if m:
565
        return False
566
    return True
567

    
568

    
569
def join_project(project_id, request_user):
570
    project = get_project_for_update(project_id)
571
    join_project_checks(project)
572

    
573
    membership, created = ProjectMembership.objects.get_or_create(
574
        project=project,
575
        person=request_user)
576

    
577
    if not created:
578
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
579
        raise PermissionDenied(msg)
580

    
581
    auto_accepted = False
582
    join_policy = project.application.member_join_policy
583
    if (join_policy == AUTO_ACCEPT_POLICY and (
584
            not project.violates_members_limit(adding=1))):
585
        membership.accept()
586
        qh_sync_user(request_user)
587
        logger.info("User %s joined %s." %
588
                    (membership.person.log_display, project))
589
        auto_accepted = True
590
    else:
591
        membership_request_notify(project, membership.person)
592
        logger.info("User %s requested to join %s." %
593
                    (membership.person.log_display, project))
594
    return auto_accepted
595

    
596

    
597
def submit_application(owner=None,
598
                       name=None,
599
                       precursor_id=None,
600
                       homepage=None,
601
                       description=None,
602
                       start_date=None,
603
                       end_date=None,
604
                       member_join_policy=None,
605
                       member_leave_policy=None,
606
                       limit_on_members_number=None,
607
                       comments=None,
608
                       resource_policies=None,
609
                       request_user=None):
610

    
611
    precursor = None
612
    if precursor_id is not None:
613
        objs = ProjectApplication.objects
614
        precursor = objs.get_for_update(id=precursor_id)
615

    
616
        if (request_user and
617
            (not precursor.owner == request_user and
618
             not request_user.is_superuser
619
             and not request_user.is_project_admin())):
620
            m = _(astakos_messages.NOT_ALLOWED)
621
            raise PermissionDenied(m)
622

    
623
    force = request_user.is_project_admin()
624
    ok, limit = qh_add_pending_app(owner, precursor, force)
625
    if not ok:
626
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
627
        raise PermissionDenied(m)
628

    
629
    application = ProjectApplication(
630
        applicant=request_user,
631
        owner=owner,
632
        name=name,
633
        precursor_application_id=precursor_id,
634
        homepage=homepage,
635
        description=description,
636
        start_date=start_date,
637
        end_date=end_date,
638
        member_join_policy=member_join_policy,
639
        member_leave_policy=member_leave_policy,
640
        limit_on_members_number=limit_on_members_number,
641
        comments=comments)
642

    
643
    if precursor is None:
644
        application.chain = new_chain()
645
    else:
646
        chain = precursor.chain
647
        application.chain = chain
648
        objs = ProjectApplication.objects
649
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
650
        pending = q.select_for_update()
651
        for app in pending:
652
            app.state = ProjectApplication.REPLACED
653
            app.save()
654

    
655
    application.save()
656
    if resource_policies is not None:
657
        application.set_resource_policies(resource_policies)
658
    logger.info("User %s submitted %s." %
659
                (request_user.log_display, application.log_display))
660
    application_submit_notify(application)
661
    return application
662

    
663

    
664
def cancel_application(application_id, request_user=None, reason=""):
665
    application = get_application_for_update(application_id)
666
    checkAllowed(application, request_user)
667

    
668
    if not application.can_cancel():
669
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL %
670
              (application.id, application.state_display()))
671
        raise PermissionDenied(m)
672

    
673
    qh_release_pending_app(application.owner)
674

    
675
    application.cancel()
676
    logger.info("%s has been cancelled." % (application.log_display))
677

    
678

    
679
def dismiss_application(application_id, request_user=None, reason=""):
680
    application = get_application_for_update(application_id)
681
    checkAllowed(application, request_user)
682

    
683
    if not application.can_dismiss():
684
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS %
685
              (application.id, application.state_display()))
686
        raise PermissionDenied(m)
687

    
688
    application.dismiss()
689
    logger.info("%s has been dismissed." % (application.log_display))
690

    
691

    
692
def deny_application(application_id, request_user=None, reason=""):
693
    application = get_application_for_update(application_id)
694

    
695
    checkAllowed(application, request_user, admin_only=True)
696

    
697
    if not application.can_deny():
698
        m = _(astakos_messages.APPLICATION_CANNOT_DENY %
699
              (application.id, application.state_display()))
700
        raise PermissionDenied(m)
701

    
702
    qh_release_pending_app(application.owner)
703

    
704
    application.deny(reason)
705
    logger.info("%s has been denied with reason \"%s\"." %
706
                (application.log_display, reason))
707
    application_deny_notify(application)
708

    
709

    
710
def approve_application(app_id, request_user=None, reason=""):
711

    
712
    try:
713
        objects = ProjectApplication.objects
714
        application = objects.get_for_update(id=app_id)
715
    except ProjectApplication.DoesNotExist:
716
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
717
        raise PermissionDenied(m)
718

    
719
    checkAllowed(application, request_user, admin_only=True)
720

    
721
    if not application.can_approve():
722
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE %
723
              (application.id, application.state_display()))
724
        raise PermissionDenied(m)
725

    
726
    qh_release_pending_app(application.owner)
727
    project = application.approve(reason)
728
    qh_sync_project(project)
729
    logger.info("%s has been approved." % (application.log_display))
730
    application_approve_notify(application)
731

    
732

    
733
def check_expiration(execute=False):
734
    objects = Project.objects
735
    expired = objects.expired_projects()
736
    if execute:
737
        for project in expired:
738
            terminate(project.id)
739

    
740
    return [project.expiration_info() for project in expired]
741

    
742

    
743
def terminate(project_id, request_user=None):
744
    project = get_project_for_update(project_id)
745
    checkAllowed(project, request_user, admin_only=True)
746
    checkAlive(project)
747

    
748
    project.terminate()
749
    qh_sync_project(project)
750
    logger.info("%s has been terminated." % (project))
751

    
752
    project_termination_notify(project)
753

    
754

    
755
def suspend(project_id, request_user=None):
756
    project = get_project_for_update(project_id)
757
    checkAllowed(project, request_user, admin_only=True)
758
    checkAlive(project)
759

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

    
764
    project_suspension_notify(project)
765

    
766

    
767
def resume(project_id, request_user=None):
768
    project = get_project_for_update(project_id)
769
    checkAllowed(project, request_user, admin_only=True)
770

    
771
    if not project.is_suspended:
772
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
773
        raise PermissionDenied(m)
774

    
775
    project.resume()
776
    qh_sync_project(project)
777
    logger.info("%s has been unsuspended." % (project))
778

    
779

    
780
def get_by_chain_or_404(chain_id):
781
    try:
782
        project = Project.objects.get(id=chain_id)
783
        application = project.application
784
        return project, application
785
    except:
786
        application = ProjectApplication.objects.latest_of_chain(chain_id)
787
        if application is None:
788
            raise Http404
789
        else:
790
            return None, application
791

    
792

    
793
def get_user_setting(user_id, key):
794
    try:
795
        setting = UserSetting.objects.get(
796
            user=user_id, setting=key)
797
        return setting.value
798
    except UserSetting.DoesNotExist:
799
        return getattr(settings, key)
800

    
801

    
802
def set_user_setting(user_id, key, value):
803
    try:
804
        setting = UserSetting.objects.get_for_update(
805
            user=user_id, setting=key)
806
    except UserSetting.DoesNotExist:
807
        setting = UserSetting(user_id=user_id, setting=key)
808
    setting.value = value
809
    setting.save()
810

    
811

    
812
def unset_user_setting(user_id, key):
813
    UserSetting.objects.filter(user=user_id, setting=key).delete()
814

    
815

    
816
def _partition_by(f, l):
817
    d = {}
818
    for x in l:
819
        group = f(x)
820
        group_l = d.get(group, [])
821
        group_l.append(x)
822
        d[group] = group_l
823
    return d
824

    
825

    
826
def count_pending_app(users):
827
    users = list(users)
828
    apps = ProjectApplication.objects.filter(state=ProjectApplication.PENDING,
829
                                             owner__in=users)
830
    apps_d = _partition_by(lambda a: a.owner.uuid, apps)
831

    
832
    usage = {}
833
    for user in users:
834
        uuid = user.uuid
835
        usage[uuid] = len(apps_d.get(uuid, []))
836
    return usage
837

    
838

    
839
def qh_add_pending_app(user, precursor=None, force=False, dry_run=False):
840
    if precursor is None:
841
        diff = 1
842
    else:
843
        chain = precursor.chain
844
        objs = ProjectApplication.objects
845
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
846
        count = q.count()
847
        diff = 1 - count
848

    
849
    return register_pending_apps(user, diff, force, dry_run)
850

    
851

    
852
def qh_release_pending_app(user):
853
    register_pending_apps(user, -1)