commit 764d99c408f15e7fc53363d8a0e29fde2bbf9e65
Author: Giorgos Korfiatis <gkorf@grnet.gr>
Date:   Mon May 13 18:11:34 2013 +0300

    astakos: Remove command user-set-initial-quota
    
    To change base quota for a specified user:
      snf-manage user-modify user --set-base-quota resource limit
    
    To import base quota in batch from a file:
      snf-manage astakos-quota --import-base-quota filename
    
    Rename `initial' to `base quota' in command output.
    Move code that creates/removes AstakosUserQuota entries in file quotas.py;
    sync user to quotaholder after such a change.
    
    Refs #3411

diff --git a/docs/admin-guide.rst b/docs/admin-guide.rst
index 374f063..c70e627 100644
--- a/docs/admin-guide.rst
+++ b/docs/admin-guide.rst
@@ -160,12 +160,9 @@ For individual users that need different quotas than the default
 you can set it for each resource like this::
 
     # use this to display quotas / uuid
-    # snf-manage user-show 'uuid or email'
+    # snf-manage user-show 'uuid or email' --quotas
 
-    # snf-manage user-set-initial-quota --set-capacity 'user-uuid' 'cyclades.vm' 10
-
-    # this applies the configuration
-    # snf-manage astakos-quota --sync --user 'user-uuid'
+    # snf-manage user-modify 'user-uuid' --set-quota 'cyclades.vm' 10
 
 
 Enable the Projects feature
@@ -185,7 +182,7 @@ per user with::
 
 You can also set a user-specific limit with::
 
-    # snf-manage user-set-initial-quota --set-capacity 'user-uuid' 'astakos.pending_app' 5
+    # snf-manage user-modify 'user-uuid' --set-quota 'astakos.pending_app' 5
 
 When users apply for projects they are not automatically granted
 the resources. They must first be approved by the administrator.
diff --git a/snf-astakos-app/README b/snf-astakos-app/README
index fe36a7a..e83c846 100644
--- a/snf-astakos-app/README
+++ b/snf-astakos-app/README
@@ -197,6 +197,5 @@ user-group-list               List available groups
 user-invite                   Invite somebody
 user-list                     List users
 user-modify                   Modify user
-user-set-initial-quota        Import user quota limits from file or set quota for a single user from the command line
 user-show                     Show user details
 ============================  ===========================
diff --git a/snf-astakos-app/astakos/im/management/commands/_common.py b/snf-astakos-app/astakos/im/management/commands/_common.py
index d8b61e1..45ce2b1 100644
--- a/snf-astakos-app/astakos/im/management/commands/_common.py
+++ b/snf-astakos-app/astakos/im/management/commands/_common.py
@@ -237,7 +237,7 @@ def is_email(s):
 
 
 def show_quotas(qh_quotas, astakos_initial, info=None):
-    labels = ('source', 'resource', 'initial', 'total', 'usage')
+    labels = ('source', 'resource', 'base quota', 'total quota', 'usage')
     if info is not None:
         labels = ('uuid', 'email') + labels
 
diff --git a/snf-astakos-app/astakos/im/management/commands/astakos-quota.py b/snf-astakos-app/astakos/im/management/commands/astakos-quota.py
index 4bb7419..31c727b 100644
--- a/snf-astakos-app/astakos/im/management/commands/astakos-quota.py
+++ b/snf-astakos-app/astakos/im/management/commands/astakos-quota.py
@@ -35,7 +35,7 @@ from optparse import make_option
 from django.core.management.base import CommandError
 
 from astakos.im.models import AstakosUser
-from astakos.im.quotas import set_user_quota, list_user_quotas
+from astakos.im.quotas import set_user_quota, list_user_quotas, add_base_quota
 from astakos.im.functions import get_user_by_uuid
 from astakos.im.management.commands._common import is_uuid, is_email
 from snf_django.lib.db.transaction import commit_on_success_strict
@@ -70,6 +70,14 @@ class Command(SynnefoCommand):
                     metavar='<uuid or email>',
                     dest='user',
                     help="List quotas for a specified user"),
+        make_option('--import-base-quota',
+                    dest='import_base_quota',
+                    metavar='<exported-quotas.txt>',
+                    help=("Import base quotas from file. "
+                          "The file must contain non-empty lines, and each "
+                          "line must contain a single-space-separated list "
+                          "of values: <user> <resource name> <capacity>")
+                    ),
     )
 
     @commit_on_success_strict()
@@ -77,6 +85,18 @@ class Command(SynnefoCommand):
         sync = options['sync']
         verify = options['verify']
         user_ident = options['user']
+        list_ = options['list']
+        import_base_quota = options['import_base_quota']
+
+        if import_base_quota:
+            if any([sync, verify, list_]):
+                m = "--from-file cannot be combined with other options."
+                raise CommandError(m)
+            self.import_from_file(import_base_quota)
+        else:
+            self.quotas(sync, verify, user_ident, options["output_format"])
+
+    def quotas(self, sync, verify, user_ident, output_format):
         list_only = not sync and not verify
 
         if user_ident is not None:
@@ -97,7 +117,7 @@ class Command(SynnefoCommand):
         if list_only:
             print_data, labels = show_quotas(qh_quotas, astakos_i, info)
             utils.pprint_table(self.stdout, print_data, labels,
-                               options["output_format"])
+                               output_format)
 
         else:
             if verify:
@@ -163,3 +183,29 @@ class Command(SynnefoCommand):
             diffs = len(diff_quotas)
             if diffs:
                 self.stdout.write("Quotas differ for %d users.\n" % (diffs))
+
+    def import_from_file(self, location):
+        users = set()
+        with open(location) as f:
+            for line in f.readlines():
+                try:
+                    t = line.rstrip('\n').split(' ')
+                    user = t[0]
+                    resource = t[1]
+                    capacity = t[2]
+                except(IndexError, TypeError):
+                    self.stdout.write('Invalid line format: %s:\n' % t)
+                    continue
+                else:
+                    try:
+                        user = self.get_user(user)
+                        users.add(user.id)
+                    except CommandError:
+                        self.stdout.write('Not found user: %s\n' % user)
+                        continue
+                    else:
+                        try:
+                            add_base_quota(user, resource, capacity)
+                        except Exception, e:
+                            self.stdout.write('Failed to add quota: %s\n' % e)
+                            continue
diff --git a/snf-astakos-app/astakos/im/management/commands/user-modify.py b/snf-astakos-app/astakos/im/management/commands/user-modify.py
index de89d54..473edcd 100644
--- a/snf-astakos-app/astakos/im/management/commands/user-modify.py
+++ b/snf-astakos-app/astakos/im/management/commands/user-modify.py
@@ -39,8 +39,10 @@ from django.core.exceptions import ValidationError
 
 from astakos.im.models import AstakosUser
 from astakos.im.functions import (activate, deactivate)
+from astakos.im import quotas
 from ._common import remove_user_permission, add_user_permission, is_uuid
 from snf_django.lib.db.transaction import commit_on_success_strict
+import string
 
 
 class Command(BaseCommand):
@@ -102,6 +104,15 @@ class Command(BaseCommand):
         make_option('--delete-permission',
                     dest='delete-permission',
                     help="Delete user permission"),
+        make_option('--set-base-quota',
+                    dest='set_base_quota',
+                    metavar='<resource> <capacity>',
+                    nargs=2,
+                    help=("Set base quota for a specified resource. "
+                          "The special value 'default' sets the user base "
+                          "quota to the default value.")
+                    ),
+
     )
 
     @commit_on_success_strict()
@@ -210,3 +221,47 @@ class Command(BaseCommand):
 
         if password:
             self.stdout.write('User\'s new password: %s\n' % password)
+
+        set_base_quota = options.get('set_base_quota')
+        if set_base_quota is not None:
+            resource, capacity = set_base_quota
+            self.set_limit(user, resource, capacity, False)
+
+    def set_limit(self, user, resource, capacity, force):
+        if capacity != 'default':
+            try:
+                capacity = int(capacity)
+            except ValueError:
+                m = "Please specify capacity as a decimal integer or 'default'"
+                raise CommandError(m)
+
+        try:
+            quota, default_capacity = user.get_resource_policy(resource)
+        except Resource.DoesNotExist:
+            raise CommandError("No such resource: %s" % resource)
+
+        current = quota.capacity if quota is not None else 'default'
+
+        if not force:
+            self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
+            self.stdout.write("default capacity: %s\n" % default_capacity)
+            self.stdout.write("current capacity: %s\n" % current)
+            self.stdout.write("new capacity: %s\n" % capacity)
+            self.stdout.write("Confirm? (y/n) ")
+            response = raw_input()
+            if string.lower(response) not in ['y', 'yes']:
+                self.stdout.write("Aborted.\n")
+                return
+
+        if capacity == 'default':
+            try:
+                quotas.remove_base_quota(user, resource)
+            except Exception as e:
+                import traceback
+                traceback.print_exc()
+                raise CommandError("Failed to remove policy: %s" % e)
+        else:
+            try:
+                quotas.add_base_quota(user, resource, capacity)
+            except Exception as e:
+                raise CommandError("Failed to add policy: %s" % e)
diff --git a/snf-astakos-app/astakos/im/management/commands/user-set-initial-quota.py b/snf-astakos-app/astakos/im/management/commands/user-set-initial-quota.py
deleted file mode 100644
index 30fc3e5..0000000
--- a/snf-astakos-app/astakos/im/management/commands/user-set-initial-quota.py
+++ /dev/null
@@ -1,199 +0,0 @@
-# Copyright 2012, 2013 GRNET S.A. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or
-# without modification, are permitted provided that the following
-# conditions are met:
-#
-#   1. Redistributions of source code must retain the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer.
-#
-#   2. Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials
-#      provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
-# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and
-# documentation are those of the authors and should not be
-# interpreted as representing official policies, either expressed
-# or implied, of GRNET S.A.
-
-import string
-
-from optparse import make_option
-from collections import namedtuple
-
-from django.core.management.base import BaseCommand, CommandError
-
-from snf_django.lib.db.transaction import commit_on_success_strict
-from astakos.im.models import AstakosUser, AstakosUserQuota, Resource
-from astakos.im.quotas import qh_sync_user, qh_sync_users
-
-from ._common import is_uuid, is_email
-
-AddResourceArgs = namedtuple('AddQuotaArgs', ('resource',
-                                              'capacity',
-                                              ))
-
-
-
-class Command(BaseCommand):
-    help = """Import user quota limits from file or set quota
-for a single user from the command line
-
-    The file must contain non-empty lines, and each line must
-    contain a single-space-separated list of values:
-
-    <user> <resource name> <capacity>
-
-    For example to grant the following user with 10 private networks
-    (independent of any he receives from projects):
-
-    6119a50b-cbc7-42c0-bafc-4b6570e3f6ac cyclades.network.private 10
-
-    Similar syntax is used when setting quota from the command line:
-
-    --set-capacity 6119a50b-cbc7-42c0-bafc-4b6570e3f6ac cyclades.vm 10
-
-    The special value of 'default' sets the user setting to the default.
-    """
-
-    option_list = BaseCommand.option_list + (
-        make_option('--from-file',
-                    dest='from_file',
-                    metavar='<exported-quotas.txt>',
-                    help="Import quotas from file"),
-        make_option('--set-capacity',
-                    dest='set_capacity',
-                    metavar='<uuid or email> <resource> <capacity>',
-                    nargs=3,
-                    help="Set capacity for a specified user/resource pair"),
-
-        make_option('-f', '--no-confirm',
-                    action='store_true',
-                    default=False,
-                    dest='force',
-                    help="Do not ask for confirmation"),
-    )
-
-    @commit_on_success_strict()
-    def handle(self, *args, **options):
-        from_file = options['from_file']
-        set_capacity = options['set_capacity']
-        force = options['force']
-
-        if from_file is not None:
-            if set_capacity is not None:
-                raise CommandError("Cannot combine option `--from-file' with "
-                                   "`--set-capacity'.")
-            self.import_from_file(from_file)
-            return
-
-        if set_capacity is not None:
-            user, resource, capacity = set_capacity
-            self.set_limit(user, resource, capacity, force)
-            return
-
-        m = "Please use either `--from-file' or `--set-capacity' options"
-        raise CommandError(m)
-
-    def set_limit(self, user_ident, resource, capacity, force):
-        if is_uuid(user_ident):
-            try:
-                user = AstakosUser.objects.get(uuid=user_ident)
-            except AstakosUser.DoesNotExist:
-                raise CommandError('Not found user having uuid: %s' %
-                                   user_ident)
-        elif is_email(user_ident):
-            try:
-                user = AstakosUser.objects.get(username=user_ident)
-            except AstakosUser.DoesNotExist:
-                raise CommandError('Not found user having email: %s' %
-                                   user_ident)
-        else:
-            raise CommandError('Please specify user by uuid or email')
-
-        if capacity != 'default':
-            try:
-                capacity = int(capacity)
-            except ValueError:
-                m = "Please specify capacity as a decimal integer or 'default'"
-                raise CommandError(m)
-
-        args = AddResourceArgs(resource=resource,
-                               capacity=capacity,
-                               )
-
-        try:
-            quota, default_capacity = user.get_resource_policy(resource)
-        except Resource.DoesNotExist:
-            raise CommandError("No such resource: %s" % resource)
-
-        current = quota.capacity if quota is not None else 'default'
-
-        if not force:
-            self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
-            self.stdout.write("default capacity: %s\n" % default_capacity)
-            self.stdout.write("current capacity: %s\n" % current)
-            self.stdout.write("new capacity: %s\n" % capacity)
-            self.stdout.write("Confirm? (y/n) ")
-            response = raw_input()
-            if string.lower(response) not in ['y', 'yes']:
-                self.stdout.write("Aborted.\n")
-                return
-
-        if capacity == 'default':
-            try:
-                q = AstakosUserQuota.objects.get(user=user,
-                                                 resource__name=resource)
-                q.delete()
-            except Exception as e:
-                import traceback
-                traceback.print_exc()
-                raise CommandError("Failed to remove policy: %s" % e)
-        else:
-            try:
-                user.add_resource_policy(*args)
-            except Exception as e:
-                raise CommandError("Failed to add policy: %s" % e)
-        qh_sync_user(user.id)
-
-    def import_from_file(self, location):
-        users = set()
-        with open(location) as f:
-            for line in f.readlines():
-                try:
-                    t = line.rstrip('\n').split(' ')
-                    user = t[0]
-                    args = AddResourceArgs(*t[1:])
-                except(IndexError, TypeError):
-                    self.stdout.write('Invalid line format: %s:\n' % t)
-                    continue
-                else:
-                    try:
-                        user = AstakosUser.objects.get(uuid=user)
-                        users.add(user.id)
-                    except AstakosUser.DoesNotExist:
-                        self.stdout.write('Not found user having uuid: %s\n'
-                                          % user)
-                        continue
-                    else:
-                        try:
-                            user.add_resource_policy(*args)
-                        except Exception, e:
-                            self.stdout.write('Failed to policy: %s\n' % e)
-                            continue
-        qh_sync_users(users)
diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py
index 6f54f8a..7fb64d0 100644
--- a/snf-astakos-app/astakos/im/models.py
+++ b/snf-astakos-app/astakos/im/models.py
@@ -446,30 +446,6 @@ class AstakosUser(User):
     def policies(self):
         return self.astakosuserquota_set.select_related().all()
 
-    @policies.setter
-    def policies(self, policies):
-        for p in policies:
-            p.setdefault('resource', '')
-            p.setdefault('capacity', 0)
-            p.setdefault('update', True)
-            self.add_resource_policy(**p)
-
-    def add_resource_policy(
-            self, resource, capacity,
-            update=True):
-        """Raises ObjectDoesNotExist, IntegrityError"""
-        resource = Resource.objects.get(name=resource)
-        if update:
-            AstakosUserQuota.objects.update_or_create(
-                user=self, resource=resource, defaults={
-                    'capacity':capacity,
-                    })
-        else:
-            q = self.astakosuserquota_set
-            q.create(
-                resource=resource, capacity=capacity,
-                )
-
     def get_resource_policy(self, resource):
         resource = Resource.objects.get(name=resource)
         default_capacity = resource.uplimit
@@ -479,11 +455,6 @@ class AstakosUser(User):
         except AstakosUserQuota.DoesNotExist:
             return None, default_capacity
 
-    def remove_resource_policy(self, service, resource):
-        """Raises ObjectDoesNotExist, IntegrityError"""
-        resource = Resource.objects.get(name=resource)
-        q = self.policies.get(resource=resource).delete()
-
     def update_uuid(self):
         while not self.uuid:
             uuid_val =  str(uuid.uuid4())
diff --git a/snf-astakos-app/astakos/im/quotas.py b/snf-astakos-app/astakos/im/quotas.py
index efbd1b8..5e29d02 100644
--- a/snf-astakos-app/astakos/im/quotas.py
+++ b/snf-astakos-app/astakos/im/quotas.py
@@ -152,6 +152,25 @@ def register_pending_apps(user, quantity, force=False, dry_run=False):
     return True, None
 
 
+def add_base_quota(user, resource, capacity):
+    resource = Resource.objects.get(name=resource)
+    obj, created = AstakosUserQuota.objects.get_or_create(
+        user=user, resource=resource, defaults={
+            'capacity': capacity,
+        })
+
+    if not created:
+        obj.capacity = capacity
+        obj.save()
+    qh_sync_user(user.id)
+
+
+def remove_base_quota(user, resource):
+    AstakosUserQuota.objects.filter(
+        user=user, resource__name=resource).delete()
+    qh_sync_user(user.id)
+
+
 def initial_quotas(users):
     initial = {}
     default_quotas = get_default_quota()
diff --git a/snf-astakos-app/astakos/im/tests.py b/snf-astakos-app/astakos/im/tests.py
index f304359..75973fb 100644
--- a/snf-astakos-app/astakos/im/tests.py
+++ b/snf-astakos-app/astakos/im/tests.py
@@ -1274,8 +1274,7 @@ class TestProjects(TestCase):
     @im_settings(PROJECT_ADMINS=['uuid1'])
     def test_applications(self):
         # let user have 2 pending applications
-        self.user.add_resource_policy('astakos.pending_app', 2)
-        quotas.qh_sync_users(AstakosUser.objects.all())
+        quotas.add_base_quota(self.user, 'astakos.pending_app', 2)
 
         r = self.user_client.get(reverse('project_add'), follow=True)
         self.assertRedirects(r, reverse('project_add'))
