Repository: techmatters/terraso-backend

Issue:
Cannot delete users who have uploaded shared files
## Description
Attempting to delete a user who has uploaded files will give an error like so

```
Cannot delete user
Deleting the selected user would require deleting the following protected related objects:

Data entry: acBie9x4 WieezMsPbKL4P2
Data entry: KoBo question set
Data entry: myfile
Data entry: plus+sign+cool
Data entry: acBie9x4WieezMsPbKL4P2

```



Discussion:

Resolving patch:
diff --git a/terraso_backend/apps/core/models/users.py b/terraso_backend/apps/core/models/users.py
--- a/terraso_backend/apps/core/models/users.py
+++ b/terraso_backend/apps/core/models/users.py
@@ -85,6 +85,17 @@ def is_landscape_manager(self, landscape_id):
             .exists()
         )
 
+    def soft_delete_policy_action(self, **kwargs):
+        """Relink files to deleted user. The default policy is to set the `created_by` field to
+        null if the user is deleted. However, for a soft deletion we want to keep this link. That
+        way if the user is restored, the created_by is still pointing to the same place."""
+        linked_dataentries = self.dataentry_set.all()
+        delete_response = super().soft_delete_policy_action()
+        for entry in linked_dataentries:
+            entry.created_by = self
+            entry.save()
+        return delete_response
+
     def is_group_manager(self, group_id):
         return self.memberships.managers_only().filter(group__pk=group_id).exists()
 
diff --git a/terraso_backend/apps/shared_data/models/data_entries.py b/terraso_backend/apps/shared_data/models/data_entries.py
--- a/terraso_backend/apps/shared_data/models/data_entries.py
+++ b/terraso_backend/apps/shared_data/models/data_entries.py
@@ -1,6 +1,7 @@
 from django.db import models
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
+from safedelete.models import SOFT_DELETE
 
 from apps.core.models import BaseModel, Group, User
 from apps.shared_data import permission_rules as perm_rules
@@ -35,6 +36,9 @@ class DataEntry(BaseModel):
         User who created the resource
     """
 
+    # file will not be deleted in cascade
+    _safedelete_policy = SOFT_DELETE
+
     name = models.CharField(max_length=128)
     description = models.TextField(blank=True, default="")
 
@@ -54,7 +58,7 @@ class DataEntry(BaseModel):
     size = models.PositiveBigIntegerField(null=True, blank=True)
 
     groups = models.ManyToManyField(Group, related_name="data_entries")
-    created_by = models.ForeignKey(User, on_delete=models.PROTECT)
+    created_by = models.ForeignKey(User, null=True, on_delete=models.DO_NOTHING)
     file_removed_at = models.DateTimeField(blank=True, null=True)
 
     class Meta(BaseModel.Meta):


Test patch:
diff --git a/terraso_backend/tests/shared_data/test_models.py b/terraso_backend/tests/shared_data/test_models.py
--- a/terraso_backend/tests/shared_data/test_models.py
+++ b/terraso_backend/tests/shared_data/test_models.py
@@ -1,5 +1,7 @@
 import pytest
+from django.db import models
 
+from apps.core.models import User
 from apps.shared_data.models import DataEntry, VisualizationConfig
 
 pytestmark = pytest.mark.django_db
@@ -108,3 +110,14 @@ def test_visualization_config_cannot_be_viewed_by_non_group_members(
 
     assert not user_b.has_perm(VisualizationConfig.get_perm("view"), obj=visualization_config)
     assert user.has_perm(VisualizationConfig.get_perm("view"), obj=visualization_config)
+
+
+def test_delete_user_with_shared_data(user, data_entry):
+    try:
+        user.delete()
+    except models.deletion.ProtectedError as e:
+        assert False, f"Deleting a user should not raise an exception. {e}"
+    assert not User.objects.filter(id=user.id).exists(), "User should be soft-deleted"
+    assert DataEntry.objects.filter(id=data_entry.id).exists(), "Data entry should not be deleted"
+    data_entry.refresh_from_db()
+    assert data_entry.created_by == user, "Data entry should still link to user"