Repository: ckan/ckan

Issue:
Delete organization
### CKAN Version if known (or site URL)

Tested on 2.7 and 2.6.4 ckan versions

### Please describe the expected behavior

If you delete the organization(not purge) what should happen with the datasets for that organization ?
create_unowned_dataset flag is set as False, and there is no owner organization for those datasets.

### Please describe the actual behavior

If you delete the organization the datasets that belongs to that organizations are not deleted and owner organization is null. Is this expected behavior ?

### What steps can be taken to reproduce the issue? 

Delete organization with datasets


Discussion:

Resolving patch:
diff --git a/ckan/controllers/group.py b/ckan/controllers/group.py
--- a/ckan/controllers/group.py
+++ b/ckan/controllers/group.py
@@ -639,6 +639,9 @@ def delete(self, id):
             abort(403, _('Unauthorized to delete group %s') % '')
         except NotFound:
             abort(404, _('Group not found'))
+        except ValidationError as e:
+            h.flash_error(e.error_dict['message'])
+            h.redirect_to(controller='organization', action='read', id=id)
         return self._render_template('group/confirm_delete.html', group_type)
 
     def members(self, id):
diff --git a/ckan/logic/action/delete.py b/ckan/logic/action/delete.py
--- a/ckan/logic/action/delete.py
+++ b/ckan/logic/action/delete.py
@@ -346,12 +346,26 @@ def _group_or_org_delete(context, data_dict, is_org=False):
     else:
         _check_access('group_delete', context, data_dict)
 
-    # organization delete will delete all datasets for that org
-    # FIXME this gets all the packages the user can see which generally will
-    # be all but this is only a fluke so we should fix this properly
+    # organization delete will not occure whilke all datasets for that org are
+    # not deleted
     if is_org:
-        for pkg in group.packages(with_private=True):
-            _get_action('package_delete')(context, {'id': pkg.id})
+        datasets = model.Session.query(model.Package) \
+                        .filter_by(owner_org=group.id) \
+                        .filter(model.Package.state != 'deleted') \
+                        .count()
+        if datasets:
+            if not authz.check_config_permission('ckan.auth.create_unowned_dataset'):
+                raise ValidationError(_('Organization cannot be deleted while it '
+                                      'still has datasets'))
+            
+            pkg_table = model.package_table
+            # using Core SQLA instead of the ORM should be faster
+            model.Session.execute(
+                pkg_table.update().where(
+                    sqla.and_(pkg_table.c.owner_org == group.id,
+                              pkg_table.c.state != 'deleted')
+                ).values(owner_org=None)
+            )
 
     rev = model.repo.new_revision()
     rev.author = user
@@ -391,7 +405,9 @@ def group_delete(context, data_dict):
 def organization_delete(context, data_dict):
     '''Delete an organization.
 
-    You must be authorized to delete the organization.
+    You must be authorized to delete the organization
+    and no datasets should belong to the organization
+    unless 'ckan.auth.create_unowned_dataset=True'
 
     :param id: the name or id of the organization
     :type id: string


Test patch:
diff --git a/ckan/tests/controllers/test_organization.py b/ckan/tests/controllers/test_organization.py
--- a/ckan/tests/controllers/test_organization.py
+++ b/ckan/tests/controllers/test_organization.py
@@ -1,5 +1,6 @@
 # encoding: utf-8
 
+from ckan.common import config
 from bs4 import BeautifulSoup
 from nose.tools import assert_equal, assert_true, assert_in
 from ckan.lib.helpers import url_for
@@ -209,6 +210,35 @@ def test_anon_user_trying_to_delete_fails(self):
                                            id=self.organization['id'])
         assert_equal(organization['state'], 'active')
 
+    @helpers.change_config('ckan.auth.create_unowned_dataset', False)
+    def test_delete_organization_with_datasets(self):
+        ''' Test deletion of organization that has datasets'''
+        text = 'Organization cannot be deleted while it still has datasets'
+        datasets = [factories.Dataset(owner_org=self.organization['id'])
+                    for i in range(0, 5)]
+        response = self.app.get(
+            url=url_for(
+                controller='organization',
+                action='delete',
+                id=self.organization['id']),
+            status=200,
+            extra_environ=self.user_env)
+
+        form = response.forms['organization-confirm-delete-form']
+        response = submit_and_follow(
+            self.app, form, name='delete', extra_environ=self.user_env)
+        assert text in response.body
+
+    def test_delete_organization_with_unknown_dataset_true(self):
+        ''' Test deletion of organization that has datasets and unknown
+            datasets are set to true'''
+        dataset = factories.Dataset(owner_org=self.organization['id'])
+        assert_equal(dataset['owner_org'], self.organization['id'])
+        helpers.call_action('organization_delete', id=self.organization['id'])
+
+        dataset = helpers.call_action('package_show', id=dataset['id'])
+        assert_equal(dataset['owner_org'], None)
+
 
 class TestOrganizationBulkProcess(helpers.FunctionalTestBase):
     def setup(self):
@@ -217,9 +247,10 @@ def setup(self):
         self.user = factories.User()
         self.user_env = {'REMOTE_USER': self.user['name'].encode('ascii')}
         self.organization = factories.Organization(user=self.user)
-        self.organization_bulk_url = url_for(controller='organization',
-                                             action='bulk_process',
-                                             id=self.organization['id'])
+        self.organization_bulk_url = url_for(
+            controller='organization',
+            action='bulk_process',
+            id=self.organization['id'])
 
     def test_make_private(self):
         datasets = [factories.Dataset(owner_org=self.organization['id'])