Repository: ckan/ckan

Issue:
Creation of datasets/harvest sources with no organization specified
### CKAN Version if known (or site URL)

2.5.2
### Please describe the expected behaviour

Alternative 1: When adding a new dataset/harvest source and no organization has been created so far, the user should be promoted with a note that this has to be done first. Maybe the form could be grayed out.

Alternative 2: When adding a new dataset/harvest source and no organization has been created so far, the form should still include the organization-field. When the user puts focus on it, a dialogue opens assisting the user in creating a new organization.
### Please describe the actual behaviour

When adding a new dataset/harvest source and no organization has been created so far, the form field for organization is not displayed. The form can be filled out. After saving an error message reads

> The form contains invalid entries:
> Owner org: A organization must be supplied

This is confusing since no such field is provided.
### What steps can be taken to reproduce the issue?
1. Create a new ckan instance or delete all organizations. 2. Create a new dataset/set up a new harvest source.



Discussion:
btw. "Owner org: **An** organization must be supplied" And maybe _provided_ is better here …


Resolving patch:
diff --git a/ckan/logic/validators.py b/ckan/logic/validators.py
--- a/ckan/logic/validators.py
+++ b/ckan/logic/validators.py
@@ -29,7 +29,7 @@ def owner_org_validator(key, data, errors, context):
 
     if value is missing or value is None:
         if not authz.check_config_permission('create_unowned_dataset'):
-            raise Invalid(_('A organization must be supplied'))
+            raise Invalid(_('An organization must be provided'))
         data.pop(key, None)
         raise df.StopOnError
 
@@ -38,7 +38,7 @@ def owner_org_validator(key, data, errors, context):
     user = model.User.get(user)
     if value == '':
         if not authz.check_config_permission('create_unowned_dataset'):
-            raise Invalid(_('A organization must be supplied'))
+            raise Invalid(_('An organization must be provided'))
         return
 
     group = model.Group.get(value)


Test patch:
diff --git a/ckan/tests/controllers/test_package.py b/ckan/tests/controllers/test_package.py
--- a/ckan/tests/controllers/test_package.py
+++ b/ckan/tests/controllers/test_package.py
@@ -8,6 +8,7 @@
     assert_true,
 )
 
+from mock import patch, MagicMock
 from routes import url_for
 
 import ckan.model as model
@@ -40,6 +41,48 @@ def test_form_renders(self):
         env, response = _get_package_new_page(app)
         assert_true('dataset-edit' in response.forms)
 
+    @helpers.change_config('ckan.auth.create_unowned_dataset', 'false')
+    def test_needs_organization_but_no_organizations_has_button(self):
+        ''' Scenario: The settings say every dataset needs an organization
+        but there are no organizations. If the user is allowed to create an
+        organization they should be prompted to do so when they try to create
+        a new dataset'''
+        app = self._get_test_app()
+        sysadmin = factories.Sysadmin()
+
+        env = {'REMOTE_USER': sysadmin['name'].encode('ascii')}
+        response = app.get(
+            url=url_for(controller='package', action='new'),
+            extra_environ=env
+        )
+        assert 'dataset-edit' not in response.forms
+        assert url_for(controller='organization', action='new') in response
+
+    @helpers.mock_auth('ckan.logic.auth.create.package_create')
+    @helpers.change_config('ckan.auth.create_unowned_dataset', 'false')
+    @helpers.change_config('ckan.auth.user_create_organizations', 'false')
+    def test_needs_organization_but_no_organizations_no_button(self,
+                                                               mock_p_create):
+        ''' Scenario: The settings say every dataset needs an organization
+        but there are no organizations. If the user is not allowed to create an
+        organization they should be told to ask the admin but no link should be
+        presented. Note: This cannot happen with the default ckan and requires
+        a plugin to overwrite the package_create behavior'''
+        mock_p_create.return_value = {'success': True}
+
+        app = self._get_test_app()
+        user = factories.User()
+
+        env = {'REMOTE_USER': user['name'].encode('ascii')}
+        response = app.get(
+            url=url_for(controller='package', action='new'),
+            extra_environ=env
+        )
+
+        assert 'dataset-edit' not in response.forms
+        assert url_for(controller='organization', action='new') not in response
+        assert 'Ask a system administrator' in response
+
     def test_name_required(self):
         app = self._get_test_app()
         env, response = _get_package_new_page(app)
diff --git a/ckan/tests/helpers.py b/ckan/tests/helpers.py
--- a/ckan/tests/helpers.py
+++ b/ckan/tests/helpers.py
@@ -328,6 +328,53 @@ def wrapper(*args, **kwargs):
     return decorator
 
 
+def mock_auth(auth_function_path):
+    '''
+    Decorator to easily mock a CKAN auth method in the context of a test
+     function
+
+    It adds a mock object for the provided auth_function_path as a parameter to
+     the test function.
+
+    Essentially it makes sure that `ckan.authz.clear_auth_functions_cache` is
+     called before and after to make sure that the auth functions pick up
+     the newly changed values.
+
+    Usage::
+
+        @helpers.mock_auth('ckan.logic.auth.create.package_create')
+        def test_mock_package_create(self, mock_package_create):
+            from ckan import logic
+            mock_package_create.return_value = {'success': True}
+
+            # package_create is mocked
+            eq_(logic.check_access('package_create', {}), True)
+
+            assert mock_package_create.called
+
+    :param action_name: the full path to the auth function to be mocked,
+        e.g. ``ckan.logic.auth.create.package_create``
+    :type action_name: string
+
+    '''
+    from ckan.authz import clear_auth_functions_cache
+
+    def decorator(func):
+        def wrapper(*args, **kwargs):
+
+            try:
+                with mock.patch(auth_function_path) as mocked_auth:
+                    clear_auth_functions_cache()
+                    new_args = args + tuple([mocked_auth])
+                    return_value = func(*new_args, **kwargs)
+            finally:
+                clear_auth_functions_cache()
+            return return_value
+
+        return nose.tools.make_decorator(func)(wrapper)
+    return decorator
+
+
 def mock_action(action_name):
     '''
     Decorator to easily mock a CKAN action in the context of a test function