Revision 3c22bad0 snf-astakos-app/astakos/im/functions.py

b/snf-astakos-app/astakos/im/functions.py
44 44
from django.contrib.auth.models import AnonymousUser
45 45
from django.core.exceptions import PermissionDenied
46 46
from django.db import IntegrityError
47
from django.db.models import Q
47 48
from django.http import Http404
48 49

  
49 50
from synnefo_branding.utils import render_to_string
......
64 65
from astakos.im.notifications import build_notification, NotificationError
65 66
from astakos.im.models import (
66 67
    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)
68
    UserSetting, Chain, new_chain)
69
from astakos.im.quotas import (qh_sync_user, get_pending_app_quota,
70
                               register_pending_apps, qh_sync_project,
71
                               qh_sync_locked_users, get_users_for_update,
72
                               members_to_sync)
70 73
from astakos.im.project_notif import (
71 74
    membership_change_notify, membership_enroll_notify,
72 75
    membership_request_notify, membership_leave_request_notify,
......
304 307
        raise IOError(m)
305 308

  
306 309

  
307
def get_project_for_update(project_id):
310
def get_chain_for_update(chain_id):
308 311
    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
        return Chain.objects.get_for_update(chain=chain_id)
313
    except Chain.DoesNotExist:
314
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % chain_id
312 315
        raise IOError(m)
313 316

  
314 317

  
315
def get_application_for_update(application_id):
318
def get_chain_of_application_for_update(app_id):
319
    app = get_application(app_id)
320
    return Chain.objects.get_for_update(chain=app.chain_id)
321

  
322

  
323
def get_application(application_id):
316 324
    try:
317
        return ProjectApplication.objects.get_for_update(id=application_id)
325
        return ProjectApplication.objects.get(id=application_id)
318 326
    except ProjectApplication.DoesNotExist:
319 327
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
320 328
        raise IOError(m)
......
336 344
        raise IOError(m)
337 345

  
338 346

  
339
def get_membership_for_update(project_id, user_id):
347
def get_membership(project_id, user_id):
340 348
    try:
341
        objs = ProjectMembership.objects
342
        return objs.get_for_update(project__id=project_id,
343
                                   person__id=user_id)
349
        objs = ProjectMembership.objects.select_related('project', 'person')
350
        return objs.get(project__id=project_id, person__id=user_id)
344 351
    except ProjectMembership.DoesNotExist:
345 352
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
346 353
        raise IOError(m)
347 354

  
348 355

  
349
def get_membership_for_update_by_id(project_id, memb_id):
356
def get_membership_by_id(project_id, memb_id):
350 357
    try:
351
        objs = ProjectMembership.objects
352
        return objs.get_for_update(project__id=project_id,
353
                                   id=memb_id)
358
        objs = ProjectMembership.objects.select_related('project', 'person')
359
        return objs.get(project__id=project_id, id=memb_id)
354 360
    except ProjectMembership.DoesNotExist:
355 361
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
356 362
        raise IOError(m)
......
396 402

  
397 403

  
398 404
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)
405
    get_chain_for_update(project_id)
401 406

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

  
412
    project = membership.project
413
    accept_membership_checks(project, request_user)
407 414
    user = membership.person
408 415
    membership.accept()
409 416
    qh_sync_user(user)
......
420 427

  
421 428

  
422 429
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)
430
    get_chain_for_update(project_id)
431

  
432
    membership = get_membership_by_id(project_id, memb_id)
426 433
    if not membership.can_reject():
427 434
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
428 435
        raise PermissionDenied(m)
429 436

  
437
    project = membership.project
438
    reject_membership_checks(project, request_user)
430 439
    user = membership.person
431 440
    membership.reject()
432 441
    logger.info("Request of user %s for %s has been rejected." %
......
441 450

  
442 451

  
443 452
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)
453
    get_chain_for_update(project_id)
454

  
455
    membership = get_membership(project_id, request_user.id)
447 456
    if not membership.can_cancel():
448 457
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
449 458
        raise PermissionDenied(m)
450 459

  
460
    project = membership.project
461
    cancel_membership_checks(project)
451 462
    membership.cancel()
452 463
    logger.info("Request of user %s for %s has been cancelled." %
453 464
                (membership.person.log_display, project))
......
464 475

  
465 476

  
466 477
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)
478
    get_chain_for_update(project_id)
479

  
480
    membership = get_membership_by_id(project_id, memb_id)
470 481
    if not membership.can_remove():
471 482
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
472 483
        raise PermissionDenied(m)
473 484

  
485
    project = membership.project
486
    remove_membership_checks(project, request_user)
474 487
    user = membership.person
475 488
    membership.remove()
476 489
    qh_sync_user(user)
......
482 495

  
483 496

  
484 497
def enroll_member(project_id, user, request_user=None):
485
    project = get_project_for_update(project_id)
498
    get_chain_for_update(project_id)
499
    project = get_project_by_id(project_id)
486 500
    accept_membership_checks(project, request_user)
487 501

  
488 502
    membership, created = ProjectMembership.objects.get_or_create(
......
524 538

  
525 539

  
526 540
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)
541
    get_chain_for_update(project_id)
542

  
543
    membership = get_membership(project_id, request_user.id)
530 544
    if not membership.can_leave():
531 545
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
532 546
        raise PermissionDenied(m)
533 547

  
548
    project = membership.project
549
    leave_project_checks(project)
550

  
534 551
    auto_accepted = False
535 552
    leave_policy = project.application.member_leave_policy
536 553
    if leave_policy == AUTO_ACCEPT_POLICY:
537 554
        membership.remove()
538 555
        qh_sync_user(request_user)
539 556
        logger.info("User %s has left %s." %
540
                    (membership.person.log_display, project))
557
                    (request_user.log_display, project))
541 558
        auto_accepted = True
542 559
    else:
543 560
        membership.leave_request()
544 561
        logger.info("User %s requested to leave %s." %
545
                    (membership.person.log_display, project))
562
                    (request_user.log_display, project))
546 563
        membership_leave_request_notify(project, membership.person)
547 564
    return auto_accepted
548 565

  
......
567 584

  
568 585

  
569 586
def join_project(project_id, request_user):
570
    project = get_project_for_update(project_id)
587
    get_chain_for_update(project_id)
588
    project = get_project_by_id(project_id)
571 589
    join_project_checks(project)
572 590

  
573 591
    membership, created = ProjectMembership.objects.get_or_create(
......
585 603
        membership.accept()
586 604
        qh_sync_user(request_user)
587 605
        logger.info("User %s joined %s." %
588
                    (membership.person.log_display, project))
606
                    (request_user.log_display, project))
589 607
        auto_accepted = True
590 608
    else:
591 609
        membership_request_notify(project, membership.person)
592 610
        logger.info("User %s requested to join %s." %
593
                    (membership.person.log_display, project))
611
                    (request_user.log_display, project))
594 612
    return auto_accepted
595 613

  
596 614

  
......
610 628

  
611 629
    precursor = None
612 630
    if precursor_id is not None:
613
        objs = ProjectApplication.objects
614
        precursor = objs.get_for_update(id=precursor_id)
631
        get_chain_of_application_for_update(precursor_id)
632
        precursor = ProjectApplication.objects.get(id=precursor_id)
615 633

  
616 634
        if (request_user and
617 635
            (not precursor.owner == request_user and
......
646 664
        chain = precursor.chain
647 665
        application.chain = chain
648 666
        objs = ProjectApplication.objects
649
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
650
        pending = q.select_for_update()
667
        pending = objs.filter(chain=chain, state=ProjectApplication.PENDING)
651 668
        for app in pending:
652 669
            app.state = ProjectApplication.REPLACED
653 670
            app.save()
......
662 679

  
663 680

  
664 681
def cancel_application(application_id, request_user=None, reason=""):
665
    application = get_application_for_update(application_id)
682
    get_chain_of_application_for_update(application_id)
683
    application = get_application(application_id)
666 684
    checkAllowed(application, request_user)
667 685

  
668 686
    if not application.can_cancel():
......
677 695

  
678 696

  
679 697
def dismiss_application(application_id, request_user=None, reason=""):
680
    application = get_application_for_update(application_id)
698
    get_chain_of_application_for_update(application_id)
699
    application = get_application(application_id)
681 700
    checkAllowed(application, request_user)
682 701

  
683 702
    if not application.can_dismiss():
......
690 709

  
691 710

  
692 711
def deny_application(application_id, request_user=None, reason=""):
693
    application = get_application_for_update(application_id)
712
    get_chain_of_application_for_update(application_id)
713
    application = get_application(application_id)
694 714

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

  
......
707 727
    application_deny_notify(application)
708 728

  
709 729

  
710
def approve_application(app_id, request_user=None, reason=""):
730
def check_conflicting_projects(application):
731
    try:
732
        project = get_project_by_id(application.chain)
733
    except IOError:
734
        project = None
711 735

  
736
    new_project_name = application.name
712 737
    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)
738
        q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
739
        conflicting_project = Project.objects.get(q)
740
        if (conflicting_project != project):
741
            m = (_("cannot approve: project with name '%s' "
742
                   "already exists (id: %s)") % (
743
                    new_project_name, conflicting_project.id))
744
            raise PermissionDenied(m) # invalid argument
745
    except Project.DoesNotExist:
746
        pass
747

  
748
    return project
749

  
750

  
751
def approve_application(app_id, request_user=None, reason=""):
752
    get_chain_of_application_for_update(app_id)
753
    application = get_application(app_id)
718 754

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

  
......
723 759
              (application.id, application.state_display()))
724 760
        raise PermissionDenied(m)
725 761

  
726
    qh_release_pending_app(application.owner)
727
    project = application.approve(reason)
728
    qh_sync_project(project)
762
    project = check_conflicting_projects(application)
763

  
764
    # Pre-lock members and owner together in order to impose an ordering
765
    # on locking users
766
    members = members_to_sync(project) if project is not None else []
767
    uids_to_sync = [member.id for member in members]
768
    owner = application.owner
769
    uids_to_sync.append(owner.id)
770
    get_users_for_update(uids_to_sync)
771

  
772
    qh_release_pending_app(owner, locked=True)
773
    application.approve(reason)
774
    qh_sync_locked_users(members)
729 775
    logger.info("%s has been approved." % (application.log_display))
730 776
    application_approve_notify(application)
731 777

  
......
741 787

  
742 788

  
743 789
def terminate(project_id, request_user=None):
744
    project = get_project_for_update(project_id)
790
    get_chain_for_update(project_id)
791
    project = get_project_by_id(project_id)
745 792
    checkAllowed(project, request_user, admin_only=True)
746 793
    checkAlive(project)
747 794

  
......
753 800

  
754 801

  
755 802
def suspend(project_id, request_user=None):
756
    project = get_project_for_update(project_id)
803
    get_chain_for_update(project_id)
804
    project = get_project_by_id(project_id)
757 805
    checkAllowed(project, request_user, admin_only=True)
758 806
    checkAlive(project)
759 807

  
......
765 813

  
766 814

  
767 815
def resume(project_id, request_user=None):
768
    project = get_project_for_update(project_id)
816
    get_chain_for_update(project_id)
817
    project = get_project_by_id(project_id)
769 818
    checkAllowed(project, request_user, admin_only=True)
770 819

  
771 820
    if not project.is_suspended:
......
836 885
    return usage
837 886

  
838 887

  
839
def qh_add_pending_app(user, precursor=None, force=False, dry_run=False):
888
def get_pending_app_diff(user, precursor):
840 889
    if precursor is None:
841 890
        diff = 1
842 891
    else:
......
845 894
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
846 895
        count = q.count()
847 896
        diff = 1 - count
897
    return diff
898

  
899

  
900
def qh_add_pending_app(user, precursor=None, force=False):
901
    user = AstakosUser.forupdate.get_for_update(id=user.id)
902
    diff = get_pending_app_diff(user, precursor)
903
    return register_pending_apps(user, diff, force)
904

  
848 905

  
849
    return register_pending_apps(user, diff, force, dry_run)
906
def check_pending_app_quota(user, precursor=None):
907
    diff = get_pending_app_diff(user, precursor)
908
    quota = get_pending_app_quota(user)
909
    limit = quota['limit']
910
    usage = quota['usage']
911
    if usage + diff > limit:
912
        return False, limit
913
    return True, None
850 914

  
851 915

  
852
def qh_release_pending_app(user):
916
def qh_release_pending_app(user, locked=False):
917
    if not locked:
918
        user = AstakosUser.forupdate.get_for_update(id=user.id)
853 919
    register_pending_apps(user, -1)

Also available in: Unified diff