commit a53ec93b4e77cedeee6fd10595402631de93b7fd
Author: Giorgos Korfiatis <gkorf@grnet.gr>
Date:   Tue Jul 23 15:37:23 2013 +0300

    astakos: Sync project-based quota
    
    Update quota code to take into account the differentiated sources.
    There is no more need to lock the user when updating quota; locking
    the project is now adequate.
    
    Update quota listing in management commands; introduce option --quota in
    project-show.

diff --git a/snf-astakos-app/astakos/im/functions.py b/snf-astakos-app/astakos/im/functions.py
index 1380e48..ae78731 100644
--- a/snf-astakos-app/astakos/im/functions.py
+++ b/snf-astakos-app/astakos/im/functions.py
@@ -448,7 +448,7 @@ def accept_membership(memb_id, request_user=None, reason=None):
     accept_membership_checks(membership, request_user)
     user = membership.person
     membership.perform_action("accept", actor=request_user, reason=reason)
-    quotas.qh_sync_user(user)
+    quotas.qh_sync_membership(membership)
     logger.info("User %s has been accepted in %s." %
                 (user.log_display, project))
 
@@ -519,7 +519,7 @@ def remove_membership(memb_id, request_user=None, reason=None):
     remove_membership_checks(membership, request_user)
     user = membership.person
     membership.perform_action("remove", actor=request_user, reason=reason)
-    quotas.qh_sync_user(user)
+    quotas.qh_sync_membership(membership)
     logger.info("User %s has been removed from %s." %
                 (user.log_display, project))
 
@@ -552,7 +552,7 @@ def enroll_member(project_id, user, request_user=None, reason=None):
         membership = new_membership(project, user, actor=request_user,
                                     enroll=True)
 
-    quotas.qh_sync_user(user)
+    quotas.qh_sync_membership(membership)
     logger.info("User %s has been enrolled in %s." %
                 (membership.person.log_display, project))
 
@@ -595,7 +595,7 @@ def leave_project(memb_id, request_user, reason=None):
     leave_policy = project.member_leave_policy
     if leave_policy == AUTO_ACCEPT_POLICY:
         membership.perform_action("remove", actor=request_user, reason=reason)
-        quotas.qh_sync_user(request_user)
+        quotas.qh_sync_membership(membership)
         logger.info("User %s has left %s." %
                     (request_user.log_display, project))
         auto_accepted = True
@@ -661,7 +661,7 @@ def join_project(project_id, request_user, reason=None):
     if (join_policy == AUTO_ACCEPT_POLICY and (
             not project.violates_members_limit(adding=1))):
         membership.perform_action("accept", actor=request_user, reason=reason)
-        quotas.qh_sync_user(request_user)
+        quotas.qh_sync_membership(membership)
         logger.info("User %s joined %s." %
                     (request_user.log_display, project))
     else:
@@ -945,15 +945,7 @@ def approve_application(application_id, project_id=None, request_user=None,
     if application.name:
         check_conflicting_projects(project, application.name)
 
-    # Pre-lock members and owner together in order to impose an ordering
-    # on locking users
-    members = quotas.members_to_sync(project)
-    uids_to_sync = [member.id for member in members]
-    applicant = application.applicant
-    uids_to_sync.append(applicant.id)
-    quotas.get_users_for_update(uids_to_sync)
-
-    qh_release_pending_app(applicant, locked=True)
+    qh_release_pending_app(application.applicant)
     application.approve(actor=request_user, reason=reason)
 
     if project.state == Project.UNINITIALIZED:
@@ -962,7 +954,7 @@ def approve_application(application_id, project_id=None, request_user=None,
         _apply_modifications(project, application)
     project.activate(actor=request_user, reason=reason)
 
-    quotas.qh_sync_locked_users(members)
+    quotas.qh_sync_project(project)
     logger.info("%s has been approved." % (application.log_display))
     project_notif.application_notify(application, "approve")
     return project
@@ -1122,10 +1114,12 @@ def get_pending_app_diff(project):
     return diff
 
 
-def qh_add_pending_app(user, project=None, force=False):
-    user = AstakosUser.objects.select_for_update().get(id=user.id)
+def qh_add_pending_app(user, project=None, force=False, assign_project=None):
+    if assign_project is None:
+        assign_project = user.base_project
     diff = get_pending_app_diff(project)
-    return quotas.register_pending_apps(user, diff, force)
+    return quotas.register_pending_apps(user, assign_project,
+                                        diff, force=force)
 
 
 def check_pending_app_quota(user, project=None):
@@ -1138,7 +1132,7 @@ def check_pending_app_quota(user, project=None):
     return True, None
 
 
-def qh_release_pending_app(user, locked=False):
-    if not locked:
-        user = AstakosUser.objects.select_for_update().get(id=user.id)
-    quotas.register_pending_apps(user, -1)
+def qh_release_pending_app(user, assign_project=None):
+    if assign_project is None:
+        assign_project = user.base_project
+    quotas.register_pending_apps(user, assign_project, -1)
diff --git a/snf-astakos-app/astakos/im/management/commands/_common.py b/snf-astakos-app/astakos/im/management/commands/_common.py
index 14d276c..4ab7f96 100644
--- a/snf-astakos-app/astakos/im/management/commands/_common.py
+++ b/snf-astakos-app/astakos/im/management/commands/_common.py
@@ -197,48 +197,34 @@ def show_resource_value(number, resource, style):
     return units.show(number, unit, style)
 
 
-def collect_holder_quotas(holder_quotas, h_initial, style=None):
+def collect_holder_quotas(holder_quotas, style=None):
     print_data = []
     for source, source_quotas in holder_quotas.iteritems():
-        try:
-            s_initial = h_initial[source]
-        except KeyError:
-            continue
         for resource, values in source_quotas.iteritems():
-            try:
-                initial = s_initial[resource]
-            except KeyError:
-                continue
-            initial = show_resource_value(initial, resource, style)
             limit = show_resource_value(values['limit'], resource, style)
             usage = show_resource_value(values['usage'], resource, style)
-            fields = (source, resource, initial, limit, usage)
+            fields = (source, resource, limit, usage)
             print_data.append(fields)
     return print_data
 
 
-def show_user_quotas(holder_quotas, h_initial, style=None):
-    labels = ('source', 'resource', 'base_quota', 'total_quota', 'usage')
-    print_data = collect_holder_quotas(holder_quotas, h_initial, style=style)
+def show_user_quotas(holder_quotas, style=None):
+    labels = ('source', 'resource', 'limit', 'usage')
+    print_data = collect_holder_quotas(holder_quotas, style=style)
     return print_data, labels
 
 
-def show_quotas(qh_quotas, astakos_initial, info=None, style=None):
-    labels = ('user', 'source', 'resource', 'base_quota', 'total_quota',
-              'usage')
+def show_quotas(qh_quotas, info=None, style=None):
+    labels = ('holder', 'source', 'resource', 'limit', 'usage')
     if info is not None:
         labels = ('displayname',) + labels
 
     print_data = []
     for holder, holder_quotas in qh_quotas.iteritems():
-        h_initial = astakos_initial.get(holder)
-        if h_initial is None:
-            continue
-
         if info is not None:
             email = info.get(holder, "")
 
-        h_data = collect_holder_quotas(holder_quotas, h_initial, style=style)
+        h_data = collect_holder_quotas(holder_quotas, style=style)
         if info is not None:
             h_data = [(email, holder) + fields for fields in h_data]
         else:
diff --git a/snf-astakos-app/astakos/im/management/commands/project-show.py b/snf-astakos-app/astakos/im/management/commands/project-show.py
index 13ee131..1b756d4 100644
--- a/snf-astakos-app/astakos/im/management/commands/project-show.py
+++ b/snf-astakos-app/astakos/im/management/commands/project-show.py
@@ -38,6 +38,7 @@ from synnefo.lib.ordereddict import OrderedDict
 from snf_django.management.commands import SynnefoCommand
 from snf_django.management import utils
 from astakos.im.models import ProjectApplication, Project
+from astakos.im import quotas
 from ._common import show_resource_value, style_options, check_style
 from synnefo.util import units
 
@@ -60,6 +61,11 @@ class Command(SynnefoCommand):
                     default=False,
                     help=("Show a list of project memberships")
                     ),
+        make_option('--quota',
+                    action='store_true',
+                    dest='list_quotas',
+                    default=False,
+                    help="List project quota"),
         make_option('--unit-style',
                     default='mb',
                     help=("Specify display unit for resource values "
@@ -81,7 +87,7 @@ class Command(SynnefoCommand):
         id_ = args[0]
         if True:
             project = get_chain_state(id_)
-            self.print_project(project)
+            self.print_project(project, show_quota)
             if show_members and project is not None:
                 self.stdout.write("\n")
                 fields, labels = members_fields(project)
@@ -105,13 +111,15 @@ class Command(SynnefoCommand):
         self.pprint_dict(app_info)
         self.print_app_resources(app)
 
-    def print_project(self, project):
+    def print_project(self, project, show_quota=False):
         self.pprint_dict(project_fields(project))
-        self.print_resources(project)
+        quota = (quotas.get_project_quota(project)
+                 if show_quota else None)
+        self.print_resources(project, quota=quota)
 
-    def print_resources(self, project):
+    def print_resources(self, project, quota=None):
         policies = project.projectresourcequota_set.all()
-        fields, labels = resource_fields(policies, self.unit_style)
+        fields, labels = resource_fields(policies, quota, self.unit_style)
         if fields:
             self.stdout.write("\n")
             self.pprint_table(fields, labels, title="Resource limits")
@@ -131,15 +139,23 @@ def get_chain_state(project_id):
         raise CommandError("Project with id %s not found." % project_id)
 
 
-def resource_fields(policies, style):
-    labels = ('name', 'description', 'max per member')
+def resource_fields(policies, quota, style):
+    labels = ('name', 'max per member', 'max per project')
+    if quota:
+        labels += ('usage',)
     collect = []
     for policy in policies:
         name = policy.resource.name
-        desc = policy.resource.desc
         capacity = policy.member_capacity
-        collect.append((name, desc,
-                        show_resource_value(capacity, name, style)))
+        p_capacity = policy.project_capacity
+        row = (name,
+               show_resource_value(capacity, name, style),
+               show_resource_value(p_capacity, name, style))
+        if quota:
+            r_quota = quota.get(name)
+            usage = r_quota.get('project_usage')
+            row += (show_resource_value(usage, name, style),)
+        collect.append(row)
     return collect, labels
 
 
diff --git a/snf-astakos-app/astakos/im/management/commands/quota-list.py b/snf-astakos-app/astakos/im/management/commands/quota-list.py
index c7f9362..2622f51 100644
--- a/snf-astakos-app/astakos/im/management/commands/quota-list.py
+++ b/snf-astakos-app/astakos/im/management/commands/quota-list.py
@@ -58,10 +58,6 @@ class Command(SynnefoCommand):
         make_option('--overlimit',
                     action='store_true',
                     help="Show quota that is over limit"),
-        make_option('--with-custom',
-                    metavar='True|False',
-                    help=("Filter quota different from the default or "
-                          "equal to it")),
         make_option('--filter-by',
                     help="Filter by field; "
                     "e.g. \"user=uuid,usage>=10M,base_quota<inf\""),
@@ -71,17 +67,13 @@ class Command(SynnefoCommand):
     )
 
     QHFLT = {
-        "total_quota": ("limit", filtering.parse_with_unit),
+        "limit": ("limit", filtering.parse_with_unit),
         "usage": ("usage_max", filtering.parse_with_unit),
         "user": ("holder", lambda x: x),
         "resource": ("resource", lambda x: x),
         "source": ("source", lambda x: x),
         }
 
-    INITFLT = {
-        "base_quota": ("capacity", filtering.parse_with_unit),
-        }
-
     @transaction.commit_on_success
     def handle(self, *args, **options):
         output_format = options["output_format"]
@@ -95,30 +87,19 @@ class Command(SynnefoCommand):
         else:
             filters = []
 
-        QHQ, INITQ = Q(), Q()
+        QHQ = Q()
         for flt in filters:
             q = filtering.make_query(flt, self.QHFLT)
             if q is not None:
                 QHQ &= q
-            q = filtering.make_query(flt, self.INITFLT)
-            if q is not None:
-                INITQ &= q
 
         overlimit = bool(options["overlimit"])
         if overlimit:
             QHQ &= Q(usage_max__gt=F("limit"))
 
-        with_custom = options["with_custom"]
-        if with_custom is not None:
-            qeq = Q(capacity=F("resource__uplimit"))
-            try:
-                INITQ &= ~qeq if utils.parse_bool(with_custom) else qeq
-            except ValueError as e:
-                raise CommandError(e)
-
         users = AstakosUser.objects.accepted()
-        qh_quotas, astakos_i = list_user_quotas(
-            users, qhflt=QHQ, initflt=INITQ)
+        qh_quotas = list_user_quotas(
+            users, qhflt=QHQ)
 
         if displayname:
             info = {}
@@ -128,5 +109,5 @@ class Command(SynnefoCommand):
             info = None
 
         print_data, labels = common.show_quotas(
-            qh_quotas, astakos_i, info, style=unit_style)
+            qh_quotas, info, style=unit_style)
         utils.pprint_table(self.stdout, print_data, labels, output_format)
diff --git a/snf-astakos-app/astakos/im/management/commands/user-show.py b/snf-astakos-app/astakos/im/management/commands/user-show.py
index 07c291c..5a14031 100644
--- a/snf-astakos-app/astakos/im/management/commands/user-show.py
+++ b/snf-astakos-app/astakos/im/management/commands/user-show.py
@@ -36,7 +36,7 @@ from optparse import make_option
 
 from django.db.models import Q
 from astakos.im.models import AstakosUser, get_latest_terms, Project
-from astakos.im.quotas import list_user_quotas
+from astakos.im.quotas import get_user_quotas
 
 from synnefo.lib.ordereddict import OrderedDict
 from snf_django.management.commands import SynnefoCommand
@@ -127,12 +127,10 @@ class Command(SynnefoCommand):
                 unit_style = options["unit_style"]
                 check_style(unit_style)
 
-                quotas, initial = list_user_quotas([user])
-                h_quotas = quotas[user.uuid]
-                h_initial = initial[user.uuid]
+                quotas = get_user_quotas(user)
                 if quotas:
                     self.stdout.write("\n")
-                    print_data, labels = show_user_quotas(h_quotas, h_initial,
+                    print_data, labels = show_user_quotas(quotas,
                                                           style=unit_style)
                     utils.pprint_table(self.stdout, print_data, labels,
                                        options["output_format"],
diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py
index 823ba48..cf6fa67 100644
--- a/snf-astakos-app/astakos/im/models.py
+++ b/snf-astakos-app/astakos/im/models.py
@@ -1850,8 +1850,10 @@ class ProjectMembershipManager(models.Manager):
         q = self.model.Q_ACCEPTED_STATES
         return self.filter(q)
 
-    def actually_accepted(self):
+    def actually_accepted(self, projects=None):
         q = self.model.Q_ACTUALLY_ACCEPTED
+        if projects is not None:
+            q &= Q(project__in=projects)
         return self.filter(q)
 
     def initialized(self, projects=None):
diff --git a/snf-astakos-app/astakos/im/quotas.py b/snf-astakos-app/astakos/im/quotas.py
index d006a46..2a6a8a6 100644
--- a/snf-astakos-app/astakos/im/quotas.py
+++ b/snf-astakos-app/astakos/im/quotas.py
@@ -31,64 +31,99 @@
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
-from synnefo.util import units
 from astakos.im.models import (
     Resource, AstakosUserQuota, AstakosUser, Service,
-    Project, ProjectMembership, ProjectResourceQuota, ProjectApplication)
+    Project, ProjectMembership, ProjectResourceQuota)
 import astakos.quotaholder_app.callpoint as qh
 from astakos.quotaholder_app.exception import NoCapacityError
 from django.db.models import Q
+from synnefo.util.keypath import set_path
 
 
-def from_holding(holding):
+PROJECT_TAG = "project:"
+USER_TAG = "user:"
+
+def project_ref(value):
+    return  PROJECT_TAG + value
+
+
+def get_project_ref(project):
+    return project_ref(project.uuid)
+
+
+def user_ref(value):
+    return USER_TAG + value
+
+
+def get_user_ref(user):
+    return user_ref(user.uuid)
+
+
+def from_holding(holding, is_project=False):
     limit, usage_min, usage_max = holding
-    body = {'limit':       limit,
-            'usage':       usage_max,
-            'pending':     usage_max-usage_min,
+    prefix = 'project_' if is_project else ''
+    body = {prefix+'limit':   limit,
+            prefix+'usage':   usage_max,
+            prefix+'pending': usage_max-usage_min,
             }
     return body
 
 
-def limits_only(holding):
-    limit, usage_min, usage_max = holding
-    return limit
+def get_user_counters(users, resources=None, sources=None, flt=None):
+    holders = [get_user_ref(user) for user in users]
+    return qh.get_quota(holders=holders,
+                        resources=resources,
+                        sources=sources,
+                        flt=flt)
 
 
-def transform_data(holdings, func=None):
-    if func is None:
-        func = from_holding
+def get_project_counters(projects, resources=None, sources=None):
+    holders = [get_project_ref(project) for project in projects]
+    return qh.get_quota(holders=holders,
+                        resources=resources,
+                        sources=sources)
 
-    quota = {}
-    for (holder, source, resource), value in holdings.iteritems():
-        holder_quota = quota.get(holder, {})
-        source_quota = holder_quota.get(source, {})
-        body = func(value)
-        source_quota[resource] = body
-        holder_quota[source] = source_quota
-        quota[holder] = holder_quota
-    return quota
 
+def strip_names(counters):
+    stripped = {}
+    for ((holder, source, resource), value) in counters.iteritems():
+        prefix, sep, holder = holder.partition(":")
+        assert prefix in ["user", "project"]
+        if source is not None:
+            prefix, sep, source = source.partition(":")
+            assert prefix == "project"
+        stripped[(holder, source, resource)] = value
+    return stripped
 
-def get_counters(users, resources=None, sources=None, flt=None):
-    uuids = [user.uuid for user in users]
 
-    counters = qh.get_quota(holders=uuids,
-                            resources=resources,
-                            sources=sources,
-                            flt=flt)
-    return counters
+def get_related_sources(counters):
+    projects = set()
+    for (holder, source, resource) in counters.iterkeys():
+        projects.add(source)
+    return list(projects)
 
 
-def get_users_quotas(users, resources=None, sources=None, flt=None):
-    counters = get_counters(users, resources, sources, flt=flt)
-    quotas = transform_data(counters)
-    return quotas
+def mk_quota_dict(users_counters, project_counters):
+    quota = {}
+    for (holder, source, resource), u_value in users_counters.iteritems():
+        p_value = project_counters[(source, None, resource)]
+        values_dict = from_holding(u_value)
+        values_dict.update(from_holding(p_value, is_project=True))
+        set_path(quota, [holder, source, resource], values_dict,
+                 createpath=True)
+    return quota
+
 
+def get_users_quotas_counters(users, resources=None, sources=None, flt=None):
+    user_counters = get_user_counters(users, resources, sources, flt=flt)
+    projects = get_related_sources(user_counters)
+    project_counters = qh.get_quota(holders=projects, resources=resources)
+    return strip_names(user_counters), strip_names(project_counters)
 
-def get_users_quota_limits(users, resources=None, sources=None):
-    counters = get_counters(users, resources, sources)
-    limits = transform_data(counters, limits_only)
-    return limits
+
+def get_users_quotas(users, resources=None, sources=None, flt=None):
+    u_c, p_c = get_users_quotas_counters(users, resources, sources, flt=flt)
+    return mk_quota_dict(u_c, p_c)
 
 
 def get_user_quotas(user, resources=None, sources=None):
@@ -102,8 +137,43 @@ def service_get_quotas(component, users=None):
     service_names = [t for (t,) in name_values]
     resources = Resource.objects.filter(service_origin__in=service_names)
     resource_names = [r.name for r in resources]
-    counters = qh.get_quota(holders=users, resources=resource_names)
-    return transform_data(counters)
+    astakosusers = AstakosUser.objects.verified()
+    if users is not None:
+        astakosusers = astakosusers.filter(uuid__in=users)
+    return get_users_quotas(astakosusers, resources=resource_names)
+
+
+def mk_limits_dict(counters):
+    quota = {}
+    for key, (limit, _, _) in counters.iteritems():
+        path = list(key)
+        set_path(quota, path, limit, createpath=True)
+    return quota
+
+
+def mk_project_quota_dict(project_counters):
+    quota = {}
+    for (holder, _, resource), p_value in project_counters.iteritems():
+        values_dict = from_holding(p_value, is_project=True)
+        set_path(quota, [holder, resource], values_dict,
+                 createpath=True)
+    return quota
+
+
+def get_projects_quota(projects, resources=None, sources=None):
+    project_counters = get_project_counters(projects, resources, sources)
+    return mk_project_quota_dict(strip_names(project_counters))
+
+
+def get_project_quota(project, resources=None, sources=None):
+    quotas = get_projects_quota([project], resources, sources)
+    return quotas.get(project.uuid, {})
+
+
+def get_projects_quota_limits():
+    project_counters = qh.get_quota(flt=Q(holder__startswith=PROJECT_TAG))
+    user_counters = qh.get_quota(flt=Q(holder__startswith=USER_TAG))
+    return mk_limits_dict(project_counters), mk_limits_dict(user_counters)
 
 
 def _level_quota_dict(quotas):
@@ -116,21 +186,37 @@ def _level_quota_dict(quotas):
     return lst
 
 
-def _set_user_quota(quotas, resource=None):
+def set_quota(quotas, resource=None):
     q = _level_quota_dict(quotas)
     qh.set_quota(q, resource=resource)
 
 
-SYSTEM = 'system'
 PENDING_APP_RESOURCE = 'astakos.pending_app'
 
 
-def register_pending_apps(user, quantity, force=False):
-    provision = (user.uuid, SYSTEM, PENDING_APP_RESOURCE), quantity
+def mk_user_provision(user, source, resource, quantity):
+    holder = user_ref(user)
+    source = project_ref(source)
+    return (holder, source, resource), quantity
+
+
+def mk_project_provision(project, resource, quantity):
+    holder = project_ref(project)
+    return (holder, None, resource), quantity
+
+
+def _mk_provisions(holder, source, resource, quantity):
+    return [((holder, source, resource), quantity),
+            ((source, None, resource), quantity)]
+
+
+def register_pending_apps(user, project, quantity, force=False):
+    provisions = _mk_provisions(get_user_ref(user), get_project_ref(project),
+                                PENDING_APP_RESOURCE, quantity)
     try:
         s = qh.issue_commission(clientkey='astakos',
                                 force=force,
-                                provisions=[provision])
+                                provisions=provisions)
     except NoCapacityError as e:
         limit = e.data['limit']
         return False, limit
@@ -140,7 +226,8 @@ def register_pending_apps(user, quantity, force=False):
 
 def get_pending_app_quota(user):
     quota = get_user_quotas(user)
-    return quota[SYSTEM][PENDING_APP_RESOURCE]
+    source = user.base_project.uuid
+    return quota[source][PENDING_APP_RESOURCE]
 
 
 def update_base_quota(users, resource, value):
@@ -161,154 +248,101 @@ def _partition_by(f, l):
     return d
 
 
-def initial_quotas(users, flt=None):
-    if flt is None:
-        flt = Q()
-
-    userids = [user.pk for user in users]
-    objs = AstakosUserQuota.objects.select_related('resource')
-    orig_quotas = objs.filter(user__pk__in=userids).filter(flt)
-    orig_quotas = _partition_by(lambda q: q.user_id, orig_quotas)
-
-    initial = {}
-    for user in users:
-        qs = {}
-        for q in orig_quotas.get(user.pk, []):
-            qs[q.resource.name] = q.capacity
-        initial[user.uuid] = {SYSTEM: qs}
-    return initial
-
-
-def get_grant_source(grant):
-    return SYSTEM
-
-
-def add_limits(x, y):
-    return min(x+y, units.PRACTICALLY_INFINITE)
-
-
-def astakos_users_quotas(users, resource=None):
-    users = list(users)
-    flt = Q(resource__name=resource) if resource is not None else Q()
-    quotas = initial_quotas(users, flt=flt)
-
-    userids = [user.pk for user in users]
-    ACTUALLY_ACCEPTED = ProjectMembership.ACTUALLY_ACCEPTED
-    objs = ProjectMembership.objects.select_related(
-        'project', 'person')
-    memberships = objs.filter(
-        person__pk__in=userids,
-        state__in=ACTUALLY_ACCEPTED,
-        project__state=Project.NORMAL,
-        )
-
-    projs = set(m.project_id for m in memberships)
-
+def astakos_project_quotas(projects, resource=None):
     objs = ProjectResourceQuota.objects.select_related()
-    grants = objs.filter(project__in=projs).filter(flt)
-
-    for membership in memberships:
-        uuid = membership.person.uuid
-        userquotas = quotas.get(uuid, {})
-
-        proj = membership.project
-        for grant in grants:
-            if grant.project_id != proj.id:
-                continue
-
-            source = get_grant_source(grant)
-            source_quotas = userquotas.get(source, {})
-
-            resource = grant.resource.full_name()
-            prev = source_quotas.get(resource, 0)
-            new = add_limits(prev, grant.member_capacity)
-            source_quotas[resource] = new
-            userquotas[source] = source_quotas
-        quotas[uuid] = userquotas
-
-    return quotas
-
-
-def list_user_quotas(users, qhflt=None, initflt=None):
+    flt = Q(resource__name=resource) if resource is not None else Q()
+    grants = objs.filter(project__in=projects).filter(flt)
+    grants_d = _partition_by(lambda g: g.project_id, grants)
+
+    objs = ProjectMembership.objects
+    memberships = objs.initialized(projects).select_related(
+        "person", "project")
+    memberships_d = _partition_by(lambda m: m.project_id, memberships)
+
+    user_quota = {}
+    project_quota = {}
+
+    for project in projects:
+        pr_ref = get_project_ref(project)
+        state = project.state
+        if state not in Project.INITIALIZED_STATES:
+            continue
+
+        project_grants = grants_d.get(project.id, [])
+        project_memberships = memberships_d.get(project.id, [])
+        for grant in project_grants:
+            resource = grant.resource.name
+            path = [pr_ref, None, resource]
+            val = grant.project_capacity if state == Project.NORMAL else 0
+            set_path(project_quota, path, val, createpath=True)
+            for membership in project_memberships:
+                u_ref = get_user_ref(membership.person)
+                path = [u_ref, pr_ref, resource]
+                val = grant.member_capacity if membership.is_active() else 0
+                set_path(user_quota, path, val, createpath=True)
+
+    return project_quota, user_quota
+
+
+def list_user_quotas(users, qhflt=None):
     qh_quotas = get_users_quotas(users, flt=qhflt)
-    astakos_initial = initial_quotas(users, flt=initflt)
-    return qh_quotas, astakos_initial
-
-
-# Syncing to quotaholder
-
-def get_users_for_update(user_ids):
-    uids = sorted(user_ids)
-    objs = AstakosUser.objects
-    return list(objs.filter(id__in=uids).order_by('id').select_for_update())
+    return qh_quotas
 
 
-def get_user_for_update(user_id):
-    return get_users_for_update([user_id])[0]
+def qh_sync_projects(projects, resource=None):
+    p_quota, u_quota = astakos_project_quotas(projects, resource=resource)
+    p_quota.update(u_quota)
+    set_quota(p_quota, resource=resource)
 
 
-def qh_sync_locked_users(users, resource=None):
-    astakos_quotas = astakos_users_quotas(users, resource=resource)
-    _set_user_quota(astakos_quotas, resource=resource)
-
-
-def qh_sync_users(users, resource=None):
-    uids = [user.id for user in users]
-    users = get_users_for_update(uids)
-    qh_sync_locked_users(users, resource=resource)
-
+def qh_sync_project(project):
+    qh_sync_projects([project])
 
-def qh_sync_users_diffs(users, sync=True):
-    uids = [user.id for user in users]
-    if sync:
-        users = get_users_for_update(uids)
 
-    astakos_quotas = astakos_users_quotas(users)
-    qh_limits = get_users_quota_limits(users)
-    diff_quotas = {}
-    for holder, local in astakos_quotas.iteritems():
-        registered = qh_limits.get(holder, None)
-        if local != registered:
-            diff_quotas[holder] = dict(local)
+def membership_quota(membership):
+    project = membership.project
+    pr_ref = get_project_ref(project)
+    u_ref = get_user_ref(membership.person)
+    objs = ProjectResourceQuota.objects.select_related()
+    grants = objs.filter(project=project)
+    user_quota = {}
+    is_active = membership.is_active()
+    for grant in grants:
+        resource = grant.resource.name
+        path = [u_ref, pr_ref, resource]
+        value = grant.member_capacity if is_active else 0
+        set_path(user_quota, path, value, createpath=True)
+    return user_quota
+
+
+def qh_sync_membership(membership):
+    quota = membership_quota(membership)
+    set_quota(quota)
+
+
+def compute_diff_quota(local, registered):
+    diff = {}
+    for holder, h_quota in local.iteritems():
+        for source, value in h_quota.iteritems():
+            reg_h_quota = registered.get(holder, {})
+            reg_value = reg_h_quota.get(source, {})
+            if value != reg_value:
+                set_path(diff, [holder, source], value, createpath=True)
+    return diff
+
+
+def qh_sync_projects_diffs(projects, sync=True):
+    local_project, local_user = astakos_project_quotas(projects)
+    registered_project, registered_user = get_projects_quota_limits(projects)
+    diff_quotas = compute_diff_quota(local_project, registered_project)
+    diff_quotas.update(compute_diff_quota(local_user, registered_user))
 
     if sync:
-        _set_user_quota(diff_quotas)
-    return qh_limits, diff_quotas
-
-
-def qh_sync_locked_user(user):
-    qh_sync_locked_users([user])
-
+        set_quota(diff_quotas)
 
-def qh_sync_user(user):
-    qh_sync_users([user])
-
-
-def qh_sync_new_users(users):
-    entries = []
-    for resource in Resource.objects.all():
-        for user in users:
-            entries.append(
-                AstakosUserQuota(user=user, resource=resource,
-                                 capacity=resource.uplimit))
-    AstakosUserQuota.objects.bulk_create(entries)
-    qh_sync_users(users)
-
-
-def qh_sync_new_user(user):
-    qh_sync_new_users([user])
-
-
-def members_to_sync(project):
-    objs = ProjectMembership.objects.select_related('person')
-    memberships = objs.filter(project=project,
-                              state__in=ProjectMembership.ACTUALLY_ACCEPTED)
-    return set(m.person for m in memberships)
-
-
-def qh_sync_project(project):
-    users = members_to_sync(project)
-    qh_sync_users(users)
+    all_registered = registered_project
+    all_registered.update(registered_user)
+    return all_registered, diff_quotas
 
 
 def pick_limit_scheme(project, resource):
@@ -316,13 +350,18 @@ def pick_limit_scheme(project, resource):
 
 
 def qh_sync_new_resource(resource):
-    users = AstakosUser.objects.filter(
-        moderated=True, is_rejected=False).order_by('id').select_for_update()
+    projects = Project.objects.filter(state__in=Project.INITIALIZED_STATES).\
+        select_for_update()
 
     entries = []
-    for user in users:
+    for project in projects:
+        limit = pick_limit_scheme(project, resource)
         entries.append(
-            AstakosUserQuota(user=user, resource=resource,
-                             capacity=resource.uplimit))
-    AstakosUserQuota.objects.bulk_create(entries)
-    qh_sync_users(users, resource=resource.name)
+            ProjectResourceQuota(
+                project=project,
+                resource=resource,
+                project_capacity=limit,
+                member_capacity=limit))
+
+    ProjectResourceQuota.objects.bulk_create(entries)
+    qh_sync_projects(projects, resource=resource.name)
