Repository: ckan/ckan

Issue:
group_create does not return results of group_show
When calling group_create, the action layer returns the result of calling group_dictize (which ignores the schema). The same is also true for group_list.

When calling package_create it returns the result of package_show.

It feels like it should be more consistent so that group_create definitively returns the same as group_show.



Discussion:
As per package, provide an option just to return the ID/IDs


Resolving patch:
diff --git a/ckan/lib/dictization/model_dictize.py b/ckan/lib/dictization/model_dictize.py
--- a/ckan/lib/dictization/model_dictize.py
+++ b/ckan/lib/dictization/model_dictize.py
@@ -361,14 +361,10 @@ def group_dictize(group, context,
     like tags are included unless you specify it in the params.
 
     :param packages_field: determines the format of the `packages` field - can
-    be `datasets`, `dataset_count`, `none_but_include_package_count` or None.
-    If set to `dataset_count` or `none_but_include_package_count` then you
-    can precalculate dataset counts in advance by supplying:
-    context['dataset_counts'] = get_group_dataset_counts()
+    be `datasets` or None.
     '''
-    assert packages_field in ('datasets', 'dataset_count',
-                              'none_but_include_package_count', None)
-    if packages_field in ('dataset_count', 'none_but_include_package_count'):
+    assert packages_field in ('datasets', 'dataset_count', None)
+    if packages_field == 'dataset_count':
         dataset_counts = context.get('dataset_counts', None)
 
     result_dict = d.table_dictize(group, context)
@@ -417,12 +413,11 @@ def get_packages_for_this_group(group_, just_the_count=False):
             search_results = logic.get_action('package_search')(search_context,
                                                                 q)
             return search_results['count'], search_results['results']
+
         if packages_field == 'datasets':
             package_count, packages = get_packages_for_this_group(group)
             result_dict['packages'] = packages
         else:
-            # i.e. packages_field is 'dataset_count' or
-            # 'none_but_include_package_count'
             if dataset_counts is None:
                 package_count, packages = get_packages_for_this_group(
                     group, just_the_count=True)
@@ -433,8 +428,6 @@ def get_packages_for_this_group(group_, just_the_count=False):
                     package_count = facets['owner_org'].get(group.id, 0)
                 else:
                     package_count = facets['groups'].get(group.name, 0)
-            if packages_field != 'none_but_include_package_count':
-                result_dict['packages'] = package_count
 
         result_dict['package_count'] = package_count
 
diff --git a/ckan/logic/action/create.py b/ckan/logic/action/create.py
--- a/ckan/logic/action/create.py
+++ b/ckan/logic/action/create.py
@@ -786,7 +786,14 @@ def _group_or_org_create(context, data_dict, is_org=False):
     logic.get_action('member_create')(member_create_context, member_dict)
 
     log.debug('Created object %s' % group.name)
-    return model_dictize.group_dictize(group, context)
+
+    return_id_only = context.get('return_id_only', False)
+    action = 'organization_show' if is_org else 'group_show'
+
+    output = context['id'] if return_id_only \
+        else _get_action(action)(context, {'id': group.id})
+
+    return output
 
 
 def group_create(context, data_dict):
@@ -846,7 +853,9 @@ def group_create(context, data_dict):
         a member of the group)
     :type users: list of dictionaries
 
-    :returns: the newly created group
+    :returns: the newly created group (unless 'return_id_only' is set to True
+              in the context, in which case just the group id will
+              be returned)
     :rtype: dictionary
 
     '''
@@ -905,7 +914,9 @@ def organization_create(context, data_dict):
         in which the user is a member of the organization)
     :type users: list of dictionaries
 
-    :returns: the newly created organization
+    :returns: the newly created organization (unless 'return_id_only' is set
+              to True in the context, in which case just the organization id
+              will be returned)
     :rtype: dictionary
 
     '''
diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py
--- a/ckan/logic/action/get.py
+++ b/ckan/logic/action/get.py
@@ -378,6 +378,7 @@ def _group_or_org_list(context, data_dict, is_org=False):
         log.warn('`order_by` deprecated please use `sort`')
         if not data_dict.get('sort'):
             sort = order_by
+
     # if the sort is packages and no sort direction is supplied we want to do a
     # reverse sort to maintain compatibility.
     if sort.strip() in ('packages', 'package_count'):
@@ -413,22 +414,16 @@ def _group_or_org_list(context, data_dict, is_org=False):
         query = query.filter(model.Group.type == group_type)
 
     groups = query.all()
-    if all_fields:
-        include_tags = asbool(data_dict.get('include_tags', False))
-    else:
-        include_tags = False
-    # even if we are not going to return all_fields, we need to dictize all the
-    # groups so that we can sort by any field.
-    group_list = model_dictize.group_list_dictize(
-        groups, context,
-        sort_key=lambda x: x[sort_info[0][0]],
-        reverse=sort_info[0][1] == 'desc',
-        with_package_counts=all_fields or
-        sort_info[0][0] in ('packages', 'package_count'),
-        include_groups=asbool(data_dict.get('include_groups', False)),
-        include_tags=include_tags,
-        include_extras=include_extras,
-        )
+
+    action = 'organization_show' if is_org else 'group_show'
+
+    group_list = []
+    for group in groups:
+        data_dict['id'] = group.id
+        group_list.append(logic.get_action(action)(context, data_dict))
+
+    group_list = sorted(group_list, key=lambda x: x[sort_info[0][0]],
+        reverse=sort_info[0][1] == 'desc')
 
     if not all_fields:
         group_list = [group[ref_group_by] for group in group_list]
@@ -463,7 +458,7 @@ def group_list(context, data_dict):
         (optional, default: ``False``)
     :type include_tags: boolean
     :param include_groups: if all_fields, include the groups the groups are in
-        (optional, default: ``False``)
+        (optional, default: ``False``).
     :type include_groups: boolean
 
     :rtype: list of strings
@@ -1158,8 +1153,7 @@ def _group_or_org_show(context, data_dict, is_org=False):
     context['group'] = group
 
     include_datasets = asbool(data_dict.get('include_datasets', False))
-    packages_field = 'datasets' if include_datasets \
-                     else 'none_but_include_package_count'
+    packages_field = 'datasets' if include_datasets else 'dataset_count'
 
     if group is None:
         raise NotFound


Test patch:
diff --git a/ckan/tests/legacy/functional/test_group.py b/ckan/tests/legacy/functional/test_group.py
--- a/ckan/tests/legacy/functional/test_group.py
+++ b/ckan/tests/legacy/functional/test_group.py
@@ -78,19 +78,19 @@ def test_sorting(self):
         assert results[-1]['name'] == u'alpha', results[-1]['name']
 
         # Test packages reversed
-        data_dict = {'all_fields': True, 'sort': 'packages desc'}
+        data_dict = {'all_fields': True, 'sort': 'package_count desc'}
         results = get_action('group_list')(context, data_dict)
         assert results[0]['name'] == u'beta', results[0]['name']
         assert results[1]['name'] == u'delta', results[1]['name']
 
         # Test packages forward
-        data_dict = {'all_fields': True, 'sort': 'packages asc'}
+        data_dict = {'all_fields': True, 'sort': 'package_count asc'}
         results = get_action('group_list')(context, data_dict)
         assert results[-2]['name'] == u'delta', results[-2]['name']
         assert results[-1]['name'] == u'beta', results[-1]['name']
 
         # Default ordering for packages
-        data_dict = {'all_fields': True, 'sort': 'packages'}
+        data_dict = {'all_fields': True, 'sort': 'package_count'}
         results = get_action('group_list')(context, data_dict)
         assert results[0]['name'] == u'beta', results[0]['name']
         assert results[1]['name'] == u'delta', results[1]['name']
diff --git a/ckan/tests/lib/dictization/test_model_dictize.py b/ckan/tests/lib/dictization/test_model_dictize.py
--- a/ckan/tests/lib/dictization/test_model_dictize.py
+++ b/ckan/tests/lib/dictization/test_model_dictize.py
@@ -25,7 +25,7 @@ def test_group_list_dictize(self):
 
         assert_equal(len(group_dicts), 1)
         assert_equal(group_dicts[0]['name'], group['name'])
-        assert_equal(group_dicts[0]['packages'], 0)
+        assert_equal(group_dicts[0]['package_count'], 0)
         assert 'extras' not in group_dicts[0]
         assert 'tags' not in group_dicts[0]
         assert 'groups' not in group_dicts[0]
@@ -181,7 +181,6 @@ def test_group_dictize_group_with_parent_group(self):
 
         assert_equal(len(group['groups']), 1)
         assert_equal(group['groups'][0]['name'], 'parent')
-        assert_equal(group['groups'][0]['packages'], 0)  # deprecated
         assert_equal(group['groups'][0]['package_count'], 0)
 
     def test_group_dictize_without_packages(self):
@@ -250,8 +249,6 @@ def test_group_dictize_with_package_count(self):
 
         group = model_dictize.group_dictize(group_obj, context,
                                             packages_field='dataset_count')
-
-        assert_equal(group['packages'], 1)
         assert_equal(group['package_count'], 1)
 
     def test_group_dictize_with_no_packages_field_but_still_package_count(self):
@@ -263,8 +260,7 @@ def test_group_dictize_with_no_packages_field_but_still_package_count(self):
         # not supplying dataset_counts in this case either
 
         group = model_dictize.group_dictize(group_obj, context,
-                                            packages_field=
-                                            'none_but_include_package_count')
+                                            packages_field='dataset_count')
 
         assert 'packages' not in group
         assert_equal(group['package_count'], 1)
@@ -293,7 +289,7 @@ def test_group_dictize_for_org_with_package_count(self):
         org = model_dictize.group_dictize(org_obj, context,
                                           packages_field='dataset_count')
 
-        assert_equal(org['packages'], 1)
+        assert_equal(org['package_count'], 1)
 
 
 class TestPackageDictize:
diff --git a/ckan/tests/logic/action/test_create.py b/ckan/tests/logic/action/test_create.py
--- a/ckan/tests/logic/action/test_create.py
+++ b/ckan/tests/logic/action/test_create.py
@@ -474,3 +474,159 @@ def test_id_cant_already_exist(self):
             id=dataset['id'],
             name='test-dataset',
         )
+
+
+class TestGroupCreate(helpers.FunctionalTestBase):
+
+    def test_create_group(self):
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+        }
+
+        group = helpers.call_action(
+            'group_create',
+            context=context,
+            name='test-group',
+        )
+
+        assert len(group['users']) == 1
+        assert group['display_name'] == u'test-group'
+        assert group['package_count'] == 0
+        assert not group['is_organization']
+        assert group['type'] == 'group'
+
+    @nose.tools.raises(logic.ValidationError)
+    def test_create_group_validation_fail(self):
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+        }
+
+        group = helpers.call_action(
+            'group_create',
+            context=context,
+            name='',
+        )
+
+    def test_create_group_return_id(self):
+        import re
+
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+            'return_id_only': True
+        }
+
+        group = helpers.call_action(
+            'group_create',
+            context=context,
+            name='test-group',
+        )
+
+        assert isinstance(group, str)
+        assert re.match('([a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}?)', group)
+
+    def test_create_matches_show(self):
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+        }
+
+        created = helpers.call_action(
+            'organization_create',
+            context=context,
+            name='test-organization',
+        )
+
+        shown = helpers.call_action(
+            'organization_show',
+            context=context,
+            id='test-organization',
+        )
+
+        assert sorted(created.keys()) == sorted(shown.keys())
+        for k in created.keys():
+            assert created[k] == shown[k], k
+
+
+class TestOrganizationCreate(helpers.FunctionalTestBase):
+
+    def test_create_organization(self):
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+        }
+
+        org = helpers.call_action(
+            'organization_create',
+            context=context,
+            name='test-organization',
+        )
+
+        assert len(org['users']) == 1
+        assert org['display_name'] == u'test-organization'
+        assert org['package_count'] == 0
+        assert org['is_organization']
+        assert org['type'] == 'organization'
+
+    @nose.tools.raises(logic.ValidationError)
+    def test_create_organization_validation_fail(self):
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+        }
+
+        org = helpers.call_action(
+            'organization_create',
+            context=context,
+            name='',
+        )
+
+    def test_create_organization_return_id(self):
+        import re
+
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+            'return_id_only': True
+        }
+
+        org = helpers.call_action(
+            'organization_create',
+            context=context,
+            name='test-organization',
+        )
+
+        assert isinstance(org, str)
+        assert re.match('([a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}?)', org)
+
+    def test_create_matches_show(self):
+        user = factories.User()
+        context = {
+            'user': user['name'],
+            'ignore_auth': True,
+        }
+
+        created = helpers.call_action(
+            'organization_create',
+            context=context,
+            name='test-organization',
+        )
+
+        shown = helpers.call_action(
+            'organization_show',
+            context=context,
+            id='test-organization',
+        )
+
+        assert sorted(created.keys()) == sorted(shown.keys())
+        for k in created.keys():
+            assert created[k] == shown[k], k
diff --git a/ckan/tests/logic/action/test_get.py b/ckan/tests/logic/action/test_get.py
--- a/ckan/tests/logic/action/test_get.py
+++ b/ckan/tests/logic/action/test_get.py
@@ -97,9 +97,7 @@ def test_group_list_sort_by_package_count(self):
         factories.Dataset(groups=[{'name': 'bb'}])
 
         group_list = helpers.call_action('group_list', sort='package_count')
-        # default is descending order
-
-        eq(group_list, ['bb', 'aa'])
+        eq(sorted(group_list), sorted(['bb', 'aa']))
 
     def test_group_list_sort_by_package_count_ascending(self):
 
@@ -113,6 +111,15 @@ def test_group_list_sort_by_package_count_ascending(self):
 
         eq(group_list, ['bb', 'aa'])
 
+    def assert_equals_expected(self, expected_dict, result_dict):
+        superfluous_keys = set(result_dict) - set(expected_dict)
+        assert not superfluous_keys, 'Did not expect key: %s' % \
+            ' '.join(('%s=%s' % (k, result_dict[k]) for k in superfluous_keys))
+        for key in expected_dict:
+            assert expected_dict[key] == result_dict[key], \
+                '%s=%s should be %s' % \
+                (key, result_dict[key], expected_dict[key])
+
     def test_group_list_all_fields(self):
 
         group = factories.Group()
@@ -121,8 +128,10 @@ def test_group_list_all_fields(self):
 
         expected_group = dict(group.items()[:])
         for field in ('users', 'tags', 'extras', 'groups'):
+            if field in group_list[0]:
+                del group_list[0][field]
             del expected_group[field]
-        expected_group['packages'] = 0
+
         assert group_list[0] == expected_group
         assert 'extras' not in group_list[0]
         assert 'tags' not in group_list[0]
@@ -158,25 +167,19 @@ def test_group_list_groups_returned(self):
         else:
             child_group_returned, parent_group_returned = group_list[::-1]
         expected_parent_group = dict(parent_group.items()[:])
-        for field in ('users', 'tags', 'extras'):
-            del expected_parent_group[field]
-        expected_parent_group['capacity'] = u'public'
-        expected_parent_group['packages'] = 0
-        expected_parent_group['package_count'] = 0
-        eq(child_group_returned['groups'], [expected_parent_group])
+
+        eq([g['name'] for g in child_group_returned['groups']], [expected_parent_group['name']])
 
 
 class TestGroupShow(helpers.FunctionalTestBase):
 
     def test_group_show(self):
-
         group = factories.Group(user=factories.User())
 
         group_dict = helpers.call_action('group_show', id=group['id'],
                                          include_datasets=True)
 
-        # FIXME: Should this be returned by group_create?
-        group_dict.pop('num_followers', None)
+        group_dict.pop('packages', None)
         eq(group_dict, group)
 
     def test_group_show_error_not_found(self):
@@ -359,14 +362,12 @@ def test_organization_list_in_presence_of_custom_group_types(self):
 class TestOrganizationShow(helpers.FunctionalTestBase):
 
     def test_organization_show(self):
-
         org = factories.Organization()
 
         org_dict = helpers.call_action('organization_show', id=org['id'],
                                        include_datasets=True)
 
-        # FIXME: Should this be returned by organization_create?
-        org_dict.pop('num_followers', None)
+        org_dict.pop('packages', None)
         eq(org_dict, org)
 
     def test_organization_show_error_not_found(self):