Revision 3c22bad0

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)
b/snf-astakos-app/astakos/im/management/commands/quota.py
36 36

  
37 37
from astakos.im.models import AstakosUser
38 38
from astakos.im.quotas import (
39
    qh_sync_users, list_user_quotas, add_base_quota)
39
    qh_sync_users_diffs, list_user_quotas, add_base_quota)
40 40
from astakos.im.functions import get_user_by_uuid
41 41
from astakos.im.management.commands._common import is_uuid, is_email
42 42
from snf_django.lib.db.transaction import commit_on_success_strict
......
117 117
                               output_format)
118 118

  
119 119
        elif verify or sync:
120
            qh_limits, diff_q = qh_sync_users(users, sync=sync, diff_only=True)
120
            qh_limits, diff_q = qh_sync_users_diffs(users, sync=sync)
121 121
            if verify:
122 122
                self.print_verify(qh_limits, diff_q)
123 123
            if sync:
b/snf-astakos-app/astakos/im/models.py
1473 1473
        return "application %s (%s) for project %s" % (
1474 1474
            self.id, self.name, self.chain)
1475 1475

  
1476
    def get_project(self):
1477
        try:
1478
            project = Project.objects.get(id=self.chain, state=Project.APPROVED)
1479
            return Project
1480
        except Project.DoesNotExist, e:
1481
            return None
1482

  
1483 1476
    def state_display(self):
1484 1477
        return self.APPLICATION_STATE_DISPLAY.get(self.state, _('Unknown'))
1485 1478

  
......
1579 1572
    def project_exists(self):
1580 1573
        return self.get_project() is not None
1581 1574

  
1582
    def _get_project_for_update(self):
1583
        try:
1584
            objects = Project.objects
1585
            project = objects.get_for_update(id=self.chain)
1586
            return project
1587
        except Project.DoesNotExist:
1588
            return None
1589

  
1590 1575
    def can_cancel(self):
1591 1576
        return self.state == self.PENDING
1592 1577

  
......
1629 1614
        return self.state == self.PENDING
1630 1615

  
1631 1616
    def approve(self, reason):
1632
        new_project_name = self.name
1633 1617
        if not self.can_approve():
1634 1618
            m = _("cannot approve: project '%s' in state '%s'") % (
1635
                    new_project_name, self.state)
1619
                    self.name, self.state)
1636 1620
            raise AssertionError(m) # invalid argument
1637 1621

  
1638 1622
        now = datetime.now()
1639
        project = self._get_project_for_update()
1640

  
1641
        try:
1642
            q = Q(name=new_project_name) & ~Q(state=Project.TERMINATED)
1643
            conflicting_project = Project.objects.get(q)
1644
            if (conflicting_project != project):
1645
                m = (_("cannot approve: project with name '%s' "
1646
                       "already exists (id: %s)") % (
1647
                        new_project_name, conflicting_project.id))
1648
                raise PermissionDenied(m) # invalid argument
1649
        except Project.DoesNotExist:
1650
            pass
1623
        self.state = self.APPROVED
1624
        self.response_date = now
1625
        self.response = reason
1626
        self.save()
1651 1627

  
1652
        new_project = False
1628
        project = self.get_project()
1653 1629
        if project is None:
1654
            new_project = True
1655 1630
            project = Project(id=self.chain)
1656 1631

  
1657
        project.name = new_project_name
1632
        project.name = self.name
1658 1633
        project.application = self
1659 1634
        project.last_approval_date = now
1660

  
1661 1635
        project.save()
1662

  
1663
        self.state = self.APPROVED
1664
        self.response_date = now
1665
        self.response = reason
1666
        self.save()
1667 1636
        return project
1668 1637

  
1669 1638
    @property
b/snf-astakos-app/astakos/im/quotas.py
128 128

  
129 129

  
130 130
SYSTEM = 'system'
131
PENDING_APP_RESOURCE = 'astakos.pending_app'
131 132

  
132 133

  
133
def register_pending_apps(user, quantity, force=False, dry_run=False):
134
    provision = (user.uuid, SYSTEM, 'astakos.pending_app'), quantity
135
    name = "DRYRUN" if dry_run else ""
134
def register_pending_apps(user, quantity, force=False):
135
    provision = (user.uuid, SYSTEM, PENDING_APP_RESOURCE), quantity
136 136
    try:
137 137
        s = qh.issue_commission(clientkey='astakos',
138 138
                                force=force,
139
                                name=name,
140 139
                                provisions=[provision])
141 140
    except NoCapacityError as e:
142 141
        limit = e.data['limit']
143 142
        return False, limit
144
    accept = not dry_run
145
    qh.resolve_pending_commission('astakos', s, accept)
143
    qh.resolve_pending_commission('astakos', s)
146 144
    return True, None
147 145

  
148 146

  
147
def get_pending_app_quota(user):
148
    quota = get_user_quotas(user)
149
    return quota[SYSTEM][PENDING_APP_RESOURCE]
150

  
151

  
149 152
def add_base_quota(user, resource, capacity):
150 153
    resource = Resource.objects.get(name=resource)
154
    user = get_user_for_update(user.id)
151 155
    obj, created = AstakosUserQuota.objects.get_or_create(
152 156
        user=user, resource=resource, defaults={
153 157
            'capacity': capacity,
......
156 160
    if not created:
157 161
        obj.capacity = capacity
158 162
        obj.save()
159
    qh_sync_user(user)
163
    qh_sync_locked_user(user)
160 164

  
161 165

  
162 166
def remove_base_quota(user, resource):
167
    user = get_user_for_update(user.id)
163 168
    AstakosUserQuota.objects.filter(
164 169
        user=user, resource__name=resource).delete()
165
    qh_sync_user(user)
170
    qh_sync_locked_user(user)
166 171

  
167 172

  
168 173
def initial_quotas(users):
......
248 253

  
249 254
# Syncing to quotaholder
250 255

  
251
def qh_sync_users(users, sync=True, diff_only=False):
256
def get_users_for_update(user_ids):
257
    uids = sorted(user_ids)
258
    objs = AstakosUser.forupdate
259
    return list(objs.filter(id__in=uids).order_by('id').select_for_update())
260

  
261

  
262
def get_user_for_update(user_id):
263
    return get_users_for_update([user_id])[0]
264

  
265

  
266
def qh_sync_locked_users(users):
267
    astakos_quotas = astakos_users_quotas(users)
268
    _set_user_quota(astakos_quotas)
269

  
270

  
271
def qh_sync_users(users):
272
    uids = [user.id for user in users]
273
    users = get_users_for_update(uids)
274
    qh_sync_locked_users(users)
275

  
276

  
277
def qh_sync_users_diffs(users, sync=True):
252 278
    uids = [user.id for user in users]
253 279
    if sync:
254
        users = AstakosUser.forupdate.filter(id__in=uids).select_for_update()
280
        users = get_users_for_update(uids)
255 281

  
256 282
    astakos_quotas = astakos_users_quotas(users)
283
    qh_limits = get_users_quota_limits(users)
284
    diff_quotas = {}
285
    for holder, local in astakos_quotas.iteritems():
286
        registered = qh_limits.get(holder, None)
287
        if local != registered:
288
            diff_quotas[holder] = dict(local)
289

  
290
    if sync:
291
        _set_user_quota(diff_quotas)
292
    return qh_limits, diff_quotas
257 293

  
258
    if diff_only:
259
        qh_limits = get_users_quota_limits(users)
260
        diff_quotas = {}
261
        for holder, local in astakos_quotas.iteritems():
262
            registered = qh_limits.get(holder, None)
263
            if local != registered:
264
                diff_quotas[holder] = dict(local)
265

  
266
        if sync:
267
            _set_user_quota(diff_quotas)
268
        return qh_limits, diff_quotas
269
    else:
270
        if sync:
271
            _set_user_quota(astakos_quotas)
272
        return None
294

  
295
def qh_sync_locked_user(user):
296
    qh_sync_locked_users([user])
273 297

  
274 298

  
275 299
def qh_sync_user(user):
276 300
    qh_sync_users([user])
277 301

  
278 302

  
279
def qh_sync_projects(projects):
280
    projects = list(projects)
281
    memberships = ProjectMembership.objects.filter(
282
        project__in=projects, state__in=ProjectMembership.ACTUALLY_ACCEPTED)
283
    users = set(m.person for m in memberships)
284

  
285
    qh_sync_users(users)
303
def members_to_sync(project):
304
    objs = ProjectMembership.objects.select_related('person')
305
    memberships = objs.filter(project=project,
306
                              state__in=ProjectMembership.ACTUALLY_ACCEPTED)
307
    return set(m.person for m in memberships)
286 308

  
287 309

  
288 310
def qh_sync_project(project):
289
    qh_sync_projects([project])
311
    users = members_to_sync(project)
312
    qh_sync_users(users)
290 313

  
291 314

  
292 315
def qh_add_resource_limit(resource, diff):
293 316
    objs = AstakosUser.forupdate.filter(Q(email_verified=True) &
294 317
                                        ~Q(policy=resource))
295
    users = objs.select_for_update()
318
    users = objs.order_by('id').select_for_update()
296 319
    uuids = [u.uuid for u in users]
297 320
    qh.add_resource_limit(holders=uuids, sources=[SYSTEM],
298 321
                          resources=[resource.name], diff=diff)
......
300 323

  
301 324
def qh_sync_new_resource(resource, limit):
302 325
    users = AstakosUser.forupdate.filter(
303
        email_verified=True).select_for_update()
326
        email_verified=True).order_by('id').select_for_update()
304 327

  
305 328
    resource_name = resource.name
306 329
    data = []
b/snf-astakos-app/astakos/im/views.py
94 94
    send_feedback,
95 95
    logout as auth_logout,
96 96
    invite as invite_func,
97
    qh_add_pending_app,
97
    check_pending_app_quota,
98 98
    accept_membership, reject_membership, remove_membership, cancel_membership,
99 99
    leave_project, join_project, enroll_member, can_join_request,
100 100
    can_leave_request,
......
1144 1144
def project_add(request):
1145 1145
    user = request.user
1146 1146
    if not user.is_project_admin():
1147
        ok, limit = qh_add_pending_app(user, dry_run=True)
1147
        ok, limit = check_pending_app_quota(user)
1148 1148
        if not ok:
1149 1149
            m = _(astakos_messages.PENDING_APPLICATION_LIMIT_ADD) % limit
1150 1150
            messages.error(request, m)
......
1260 1260

  
1261 1261
    if not user.is_project_admin():
1262 1262
        owner = app.owner
1263
        ok, limit = qh_add_pending_app(owner, precursor=app, dry_run=True)
1263
        ok, limit = check_pending_app_quota(owner, precursor=app)
1264 1264
        if not ok:
1265 1265
            m = _(astakos_messages.PENDING_APPLICATION_LIMIT_MODIFY) % limit
1266 1266
            messages.error(request, m)
b/snf-astakos-app/astakos/test/views.py
37 37
from astakos.im.models import AstakosUser, ProjectApplication
38 38
from astakos.im.functions import (join_project, leave_project,
39 39
                                  submit_application, approve_application,
40
                                  get_user_by_id, qh_add_pending_app)
40
                                  get_user_by_id, check_pending_app_quota)
41 41
from snf_django.lib.db.transaction import commit_on_success_strict
42 42

  
43 43

  
......
62 62
                 if prec is not None
63 63
                 else None)
64 64

  
65
    ok, limit = qh_add_pending_app(owner, precursor=precursor, dry_run=True)
65
    ok, limit = check_pending_app_quota(owner, precursor=precursor)
66 66
    if not ok:
67 67
        raise PermissionDenied('Limit %s reached', limit)
68 68

  

Also available in: Unified diff