Repository: ckan/ckan

Issue:
Enable a logged in user to move dataset to another organization
In #953 this change was introduced:

> User is logged in, and is a member of an organization, and dataset is a
> member of any org: Visibility shows but Organization does not show when
> updating a dataset.

So, this prevents a logged in user to change dataset's organization.

Why can't a legitimate user to move datasets around organizations?



Discussion:
Under "legitimate" I'd suggest "Admin of both organizations".

That's correct: https://github.com/ckan/ckan/blob/e9e56ddf44d22f63c1eb4667f1a4f4efc0541215/ckan/logic/action/get.py#L579


Resolving patch:
diff --git a/ckan/logic/validators.py b/ckan/logic/validators.py
--- a/ckan/logic/validators.py
+++ b/ckan/logic/validators.py
@@ -34,13 +34,9 @@ def owner_org_validator(key, data, errors, context):
     model = context['model']
     user = context['user']
     user = model.User.get(user)
-    if value == '' :
+    if value == '':
         if not new_authz.check_config_permission('create_unowned_dataset'):
             raise Invalid(_('A organization must be supplied'))
-        package = context.get('package')
-        # only sysadmins can remove datasets from org
-        if package and package.owner_org and not user.sysadmin:
-            raise Invalid(_('You cannot remove a dataset from an existing organization'))
         return
 
     group = model.Group.get(value)


Test patch:
diff --git a/ckan/new_tests/controllers/test_package.py b/ckan/new_tests/controllers/test_package.py
--- a/ckan/new_tests/controllers/test_package.py
+++ b/ckan/new_tests/controllers/test_package.py
@@ -1,4 +1,4 @@
-from nose.tools import assert_equal, assert_true
+from nose.tools import assert_equal, assert_true, assert_not_equal
 
 from routes import url_for
 
@@ -148,6 +148,183 @@ def test_previous_next_maintains_draft_state(self):
         pkg = model.Package.by_name(u'previous-next-maintains-draft')
         assert_equal(pkg.state, 'draft')
 
+    def test_dataset_edit_org_dropdown_visible_to_normal_user_with_orgs_available(self):
+        '''
+        The 'Organization' dropdown is available on the dataset create/edit
+        page to normal (non-sysadmin) users who have organizations available
+        to them.
+        '''
+        user = factories.User()
+        # user is admin of org.
+        org = factories.Organization(name="my-org",
+                                     users=[{'name': user['id'], 'capacity': 'admin'}])
+
+        app = self._get_test_app()
+        env = {'REMOTE_USER': user['name'].encode('ascii')}
+        response = app.get(
+            url=url_for(controller='package', action='new'),
+            extra_environ=env,
+        )
+
+        # organization dropdown available in create page.
+        assert 'id="field-organizations"' in response
+
+        # create dataset
+        form = response.forms['dataset-edit']
+        form['name'] = u'my-dataset'
+        form['owner_org'] = org['id']
+        response = submit_and_follow(app, form, env, 'save')
+
+        # add a resource to make the pkg active
+        resource_form = response.forms['resource-edit']
+        resource_form['url'] = u'http://example.com/resource'
+        submit_and_follow(app, resource_form, env, 'save', 'go-metadata')
+        pkg = model.Package.by_name(u'my-dataset')
+        assert_equal(pkg.state, 'active')
+
+        # edit package page response
+        url = url_for(controller='package',
+                      action='edit',
+                      id=pkg.id)
+        pkg_edit_response = app.get(url=url, extra_environ=env)
+        # A field with the correct id is in the response
+        assert 'id="field-organizations"' in pkg_edit_response
+        # The organization id is in the response in a value attribute
+        assert 'value="{0}"'.format(org['id']) in pkg_edit_response
+
+    def test_dataset_edit_org_dropdown_normal_user_can_remove_org(self):
+        '''
+        A normal user (non-sysadmin) can remove an organization from a dataset
+        have permissions on.
+        '''
+        user = factories.User()
+        # user is admin of org.
+        org = factories.Organization(name="my-org",
+                                     users=[{'name': user['id'], 'capacity': 'admin'}])
+
+        app = self._get_test_app()
+        env = {'REMOTE_USER': user['name'].encode('ascii')}
+        response = app.get(
+            url=url_for(controller='package', action='new'),
+            extra_environ=env,
+        )
+
+        # create dataset with owner_org
+        form = response.forms['dataset-edit']
+        form['name'] = u'my-dataset'
+        form['owner_org'] = org['id']
+        response = submit_and_follow(app, form, env, 'save')
+
+        # add a resource to make the pkg active
+        resource_form = response.forms['resource-edit']
+        resource_form['url'] = u'http://example.com/resource'
+        submit_and_follow(app, resource_form, env, 'save', 'go-metadata')
+        pkg = model.Package.by_name(u'my-dataset')
+        assert_equal(pkg.state, 'active')
+        assert_equal(pkg.owner_org, org['id'])
+        assert_not_equal(pkg.owner_org, None)
+
+        # edit package page response
+        url = url_for(controller='package',
+                      action='edit',
+                      id=pkg.id)
+        pkg_edit_response = app.get(url=url, extra_environ=env)
+
+        # edit dataset
+        edit_form = pkg_edit_response.forms['dataset-edit']
+        edit_form['owner_org'] = ''
+        submit_and_follow(app, edit_form, env, 'save')
+        post_edit_pkg = model.Package.by_name(u'my-dataset')
+        assert_equal(post_edit_pkg.owner_org, None)
+        assert_not_equal(post_edit_pkg.owner_org, org['id'])
+
+    def test_dataset_edit_org_dropdown_not_visible_to_normal_user_with_no_orgs_available(self):
+        '''
+        The 'Organization' dropdown is not available on the dataset
+        create/edit page to normal (non-sysadmin) users who have no
+        organizations available to them.
+        '''
+        user = factories.User()
+        # user isn't admin of org.
+        org = factories.Organization(name="my-org")
+
+        app = self._get_test_app()
+        env = {'REMOTE_USER': user['name'].encode('ascii')}
+        response = app.get(
+            url=url_for(controller='package', action='new'),
+            extra_environ=env,
+        )
+
+        # organization dropdown available in create page.
+        assert 'id="field-organizations"' not in response
+
+        # create dataset
+        form = response.forms['dataset-edit']
+        form['name'] = u'my-dataset'
+        response = submit_and_follow(app, form, env, 'save')
+
+        # add a resource to make the pkg active
+        resource_form = response.forms['resource-edit']
+        resource_form['url'] = u'http://example.com/resource'
+        submit_and_follow(app, resource_form, env, 'save', 'go-metadata')
+        pkg = model.Package.by_name(u'my-dataset')
+        assert_equal(pkg.state, 'active')
+
+        # edit package response
+        url = url_for(controller='package',
+                      action='edit',
+                      id=model.Package.by_name(u'my-dataset').id)
+        pkg_edit_response = app.get(url=url, extra_environ=env)
+        # A field with the correct id is in the response
+        assert 'id="field-organizations"' not in pkg_edit_response
+        # The organization id is in the response in a value attribute
+        assert 'value="{0}"'.format(org['id']) not in pkg_edit_response
+
+    def test_dataset_edit_org_dropdown_visible_to_sysadmin_with_no_orgs_available(self):
+        '''
+        The 'Organization' dropdown is available to sysadmin users regardless
+        of whether they personally have an organization they administrate.
+        '''
+        user = factories.User()
+        sysadmin = factories.Sysadmin()
+        # user is admin of org.
+        org = factories.Organization(name="my-org",
+                                     users=[{'name': user['id'], 'capacity': 'admin'}])
+
+        app = self._get_test_app()
+        # user in env is sysadmin
+        env = {'REMOTE_USER': sysadmin['name'].encode('ascii')}
+        response = app.get(
+            url=url_for(controller='package', action='new'),
+            extra_environ=env,
+        )
+
+        # organization dropdown available in create page.
+        assert 'id="field-organizations"' in response
+
+        # create dataset
+        form = response.forms['dataset-edit']
+        form['name'] = u'my-dataset'
+        form['owner_org'] = org['id']
+        response = submit_and_follow(app, form, env, 'save')
+
+        # add a resource to make the pkg active
+        resource_form = response.forms['resource-edit']
+        resource_form['url'] = u'http://example.com/resource'
+        submit_and_follow(app, resource_form, env, 'save', 'go-metadata')
+        pkg = model.Package.by_name(u'my-dataset')
+        assert_equal(pkg.state, 'active')
+
+        # edit package page response
+        url = url_for(controller='package',
+                      action='edit',
+                      id=pkg.id)
+        pkg_edit_response = app.get(url=url, extra_environ=env)
+        # A field with the correct id is in the response
+        assert 'id="field-organizations"' in pkg_edit_response
+        # The organization id is in the response in a value attribute
+        assert 'value="{0}"'.format(org['id']) in pkg_edit_response
+
 
 class TestPackageResourceRead(helpers.FunctionalTestBase):
     @classmethod