Repository: ckan/ckan

Issue:
`current_package_list_with_resources` needs to use `package_search`
The `current_package_list_with_resources` action calls the models directly. This needs to use `package_search` for speed and handling private datasets correctly.



Discussion:
for CKAN 2.0 and 2.1 the package_search results don't look like the package_show results when IDatasetForm plugins are in use. Won't this be a problem for backporting this fix?

Yes, we can not change the output of the API calls for old releases. For CKAN 2.0 and 2.1 we should really only add the one line DB fix. For 2.2 we can backport the `package_search` refactor (as long as the output don't change)


Resolving patch:
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
@@ -47,14 +47,6 @@
 _text = sqlalchemy.text
 
 
-def _package_list_with_resources(context, package_revision_list):
-    package_list = []
-    for package in package_revision_list:
-        result_dict = model_dictize.package_dictize(package, context)
-        package_list.append(result_dict)
-    return package_list
-
-
 def site_read(context, data_dict=None):
     '''Return ``True``.
 
@@ -130,6 +122,7 @@ def current_package_list_with_resources(context, data_dict):
     model = context["model"]
     limit = data_dict.get('limit')
     offset = data_dict.get('offset', 0)
+    user = context['user']
 
     if not 'offset' in data_dict and 'page' in data_dict:
         log.warning('"page" parameter is deprecated.  '
@@ -142,16 +135,11 @@ def current_package_list_with_resources(context, data_dict):
 
     _check_access('current_package_list_with_resources', context, data_dict)
 
-    query = model.Session.query(model.PackageRevision)
-    query = query.filter(model.PackageRevision.state == 'active')
-    query = query.filter(model.PackageRevision.current == True)
-    query = query.order_by(
-        model.package_revision_table.c.revision_timestamp.desc())
-    if limit is not None:
-        query = query.limit(limit)
-    query = query.offset(offset)
-    pack_rev = query.all()
-    return _package_list_with_resources(context, pack_rev)
+    is_sysadmin = new_authz.is_sysadmin(user)
+    q = '+capacity:public' if not is_sysadmin else '*:*'
+    context['ignore_capacity_check'] = True
+    search = package_search(context, {'q': q, 'rows': limit, 'start': offset})
+    return search.get('results', [])
 
 
 def revision_list(context, data_dict):


Test patch:
diff --git a/ckan/new_tests/factories.py b/ckan/new_tests/factories.py
--- a/ckan/new_tests/factories.py
+++ b/ckan/new_tests/factories.py
@@ -6,10 +6,10 @@
 
 http://factoryboy.readthedocs.org/en/latest/
 
-These are not meant to be used for the actual testing, e.g. if you're writing a
-test for the :py:func:`~ckan.logic.action.create.user_create` function then
-call :py:func:`~ckan.new_tests.helpers.call_action`, don't test it
-via the :py:class:`~ckan.new_tests.factories.User` factory below.
+These are not meant to be used for the actual testing, e.g. if you're writing
+a test for the :py:func:`~ckan.logic.action.create.user_create` function then
+call :py:func:`~ckan.new_tests.helpers.call_action`, don't test it via the
+:py:class:`~ckan.new_tests.factories.User` factory below.
 
 Usage::
 
@@ -205,11 +205,13 @@ def _create(cls, target_class, *args, **kwargs):
         if args:
             assert False, "Positional args aren't supported, use keyword args."
 
-        #TODO: we will need to be able to define this when creating the
-        #      instance perhaps passing a 'user' param?
-        context = {
-            'user': helpers.call_action('get_site_user')['name']
-        }
+        if 'user' in kwargs:
+            user_dict = kwargs.pop('user')
+            context = {'user': user_dict['name']}
+        else:
+            context = {
+                'user': helpers.call_action('get_site_user')['name']
+            }
 
         group_dict = helpers.call_action('organization_create',
                                          context=context,
@@ -247,6 +249,39 @@ def _create(cls, target_class, *args, **kwargs):
         return related_dict
 
 
+class Dataset(factory.Factory):
+    '''A factory class for creating CKAN datasets.'''
+
+    FACTORY_FOR = ckan.model.package
+
+    # These are the default params that will be used to create new groups.
+    title = 'Test Dataset'
+    description = 'Just another test dataset.'
+
+    # Generate a different group name param for each user that gets created.
+    name = factory.Sequence(lambda n: 'test_dataset_{n}'.format(n=n))
+
+    @classmethod
+    def _build(cls, target_class, *args, **kwargs):
+        raise NotImplementedError(".build() isn't supported in CKAN")
+
+    @classmethod
+    def _create(cls, target_class, *args, **kwargs):
+        if args:
+            assert False, "Positional args aren't supported, use keyword args."
+
+        assert 'user' in kwargs, ('The Dataset factory requires an extra '
+                                  'user=user_dict keyword argument (the user '
+                                  'who will create the group)')
+        user_dict = kwargs.pop('user')
+        context = {'user': user_dict['name']}
+
+        dataset_dict = helpers.call_action('package_create',
+                                           context=context,
+                                           **kwargs)
+        return dataset_dict
+
+
 class MockUser(factory.Factory):
     '''A factory class for creating mock CKAN users using the mock library.'''
 
diff --git a/ckan/new_tests/logic/action/test_get.py b/ckan/new_tests/logic/action/test_get.py
--- a/ckan/new_tests/logic/action/test_get.py
+++ b/ckan/new_tests/logic/action/test_get.py
@@ -6,6 +6,9 @@
 import ckan.new_tests.factories as factories
 
 
+eq = nose.tools.eq_
+
+
 class TestGet(object):
 
     @classmethod
@@ -246,6 +249,71 @@ def test_related_list_featured(self):
         # TODO: Create related items associated with a dataset and test
         # related_list with them
 
+    def test_current_package_list(self):
+        '''
+        Test current_package_list_with_resources with no parameters
+        '''
+        user = factories.User()
+        dataset1 = factories.Dataset(user=user)
+        dataset2 = factories.Dataset(user=user)
+        current_package_list = helpers. \
+            call_action('current_package_list_with_resources')
+        eq(len(current_package_list), 2)
+
+    def test_current_package_list_limit_param(self):
+        '''
+        Test current_package_list_with_resources with limit parameter
+        '''
+        user = factories.User()
+        dataset1 = factories.Dataset(user=user)
+        dataset2 = factories.Dataset(user=user)
+        current_package_list = helpers. \
+            call_action('current_package_list_with_resources', limit=1)
+        eq(len(current_package_list), 1)
+        eq(current_package_list[0]['name'], dataset2['name'])
+
+    def test_current_package_list_offset_param(self):
+        '''
+        Test current_package_list_with_resources with offset parameter
+        '''
+        user = factories.User()
+        dataset1 = factories.Dataset(user=user)
+        dataset2 = factories.Dataset(user=user)
+        current_package_list = helpers. \
+            call_action('current_package_list_with_resources', offset=1)
+        eq(len(current_package_list), 1)
+        eq(current_package_list[0]['name'], dataset1['name'])
+
+    def test_current_package_list_private_datasets_anonoymous_user(self):
+        '''
+        Test current_package_list_with_resources with an anoymous user and
+        a private dataset
+        '''
+        user = factories.User()
+        org = factories.Organization(user=user)
+        dataset1 = factories.Dataset(user=user, owner_org=org['name'],
+                                     private=True)
+        dataset2 = factories.Dataset(user=user)
+        current_package_list = helpers. \
+            call_action('current_package_list_with_resources', context={})
+        eq(len(current_package_list), 1)
+
+    def test_current_package_list_private_datasets_sysadmin_user(self):
+        '''
+        Test current_package_list_with_resources with a sysadmin user and a
+        private dataset
+        '''
+        user = factories.User()
+        org = factories.Organization(user=user)
+        dataset1 = factories.Dataset(user=user, owner_org=org['name'],
+                                     private=True)
+        dataset2 = factories.Dataset(user=user)
+        sysadmin = factories.Sysadmin()
+        current_package_list = helpers. \
+            call_action('current_package_list_with_resources', context={'user':
+                        sysadmin['name']})
+        eq(len(current_package_list), 2)
+
 
 class TestBadLimitQueryParameters(object):
     '''test class for #1258 non-int query parameters cause 500 errors
@@ -266,6 +334,7 @@ def test_activity_list_actions(self):
             'group_activity_list_html',
             'organization_activity_list_html',
             'recently_changed_packages_activity_list_html',
+            'current_package_list_with_resources',
         ]
         for action in actions:
             nose.tools.assert_raises(
diff --git a/ckan/tests/logic/test_action.py b/ckan/tests/logic/test_action.py
--- a/ckan/tests/logic/test_action.py
+++ b/ckan/tests/logic/test_action.py
@@ -114,48 +114,6 @@ def test_01_package_list_private(self):
         assert 'public_dataset' in res
         assert not 'private_dataset' in res
 
-    def test_01_current_package_list_with_resources(self):
-        url = '/api/action/current_package_list_with_resources'
-
-        postparams = '%s=1' % json.dumps({
-            'limit': 1,
-            'offset': 1})
-        res = json.loads(self.app.post(url, params=postparams).body)
-        assert res['success']
-        assert len(res['result']) == 1
-
-        postparams = '%s=1' % json.dumps({
-            'limit': '5'})
-        res = json.loads(self.app.post(url, params=postparams).body)
-        assert res['success']
-
-        postparams = '%s=1' % json.dumps({
-            'limit': -2})
-        res = json.loads(self.app.post(url, params=postparams,
-                         status=StatusCodes.STATUS_409_CONFLICT).body)
-        assert not res['success']
-
-        postparams = '%s=1' % json.dumps({
-            'offset': 'a'})
-        res = json.loads(self.app.post(url, params=postparams,
-                         status=StatusCodes.STATUS_409_CONFLICT).body)
-        assert not res['success']
-
-        postparams = '%s=1' % json.dumps({
-            'limit': 2,
-            'page': 1})
-        res = json.loads(self.app.post(url, params=postparams).body)
-        assert res['success']
-        assert len(res['result']) == 2
-
-        postparams = '%s=1' % json.dumps({
-            'limit': 1,
-            'page': 0})
-        res = json.loads(self.app.post(url,
-                         params=postparams,
-                         status=StatusCodes.STATUS_409_CONFLICT).body)
-        assert not res['success']
-
     def test_01_package_show(self):
         anna_id = model.Package.by_name(u'annakarenina').id
         postparams = '%s=1' % json.dumps({'id': anna_id})