Repository: readthedocs/readthedocs.org

Issue:
Better User deletion
With #3175 we now have a way for users to mark themselves as wanting to be deleted, now we need to actually delete them. This could be done inline during delete, or as a batch job that runs after the web view returns.

We want to actually **delete** the User instance, but we need to confirm a couple things:

* Projects that the user _only_ owns are deleted. This is a [M2m](https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/projects/models.py#L83), so we need to either *remove* them if there are multiple users, or *delete* the project if they are the only owner, so we don't end up with orphaned projects. 



Discussion:
@agjohnson Anything else you can think of that attach to users? Looking at the code I see:

* FK: Comments - will be deleted
* FK: Bookmarks - will be deleted
* M2M: [Oauth connections](https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/oauth/models.py#L38) - Believe these might need to be manually deleted (same logic, if only person in Org, delete, otherwise remove user) 

We should also add a guide entry or something that is Googleable, that shows how to do this (http://docs.readthedocs.io/en/latest/guides/index.html)
Projects is the big one. There probably isn't a good reason why oauth models shouldn't be cascading if they aren't already -- those are all single user relations, but might have to be m2m for some other reason.
Hey there,
The currently implemented "delete" feature is misleading.
I wanted to change my username, but I saw that it's impossible, so i went on and deleted (my empty) account thinking that I'd be able to create a new one with a new username. Nowhere does it say that the account is only "disabled".
Now I can't re-register because my email is in use (because the account wasn't actually deleted), nor can I reactivate the account. Something needs to be done here 😄 

And on a personal note, If someone could hack the DB a bit and reactivate my account in the meantime, it would be delightful 🙏 Is there a support email anywhere? Couldn't find it.

Thanks,
Shachar.
Hey @shacharoo, Apologies for the problem you are facing. Can you open a new issue about your problem so that we can ask out Operation teams to change the email or do other necessary things for you?
In the meantime, I am hoping to create a patch ASAP that will cover your use case.
Will do @safwanrahman, thanks!

Opened #3189 .

Resolving patch:
diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py
--- a/readthedocs/core/signals.py
+++ b/readthedocs/core/signals.py
@@ -5,13 +5,15 @@
 import logging
 
 from corsheaders import signals
+from django.conf import settings
+from django.db.models.signals import pre_delete
 from django.dispatch import Signal
-from django.db.models import Q
+from django.db.models import Q, Count
+from django.dispatch import receiver
 from future.backports.urllib.parse import urlparse
 
 from readthedocs.projects.models import Project, Domain
 
-
 log = logging.getLogger(__name__)
 
 WHITELIST_URLS = ['/api/v2/footer_html', '/api/v2/search', '/api/v2/docsearch']
@@ -62,4 +64,20 @@ def decide_if_cors(sender, request, **kwargs):  # pylint: disable=unused-argumen
 
     return False
 
+
+@receiver(pre_delete, sender=settings.AUTH_USER_MODEL)
+def delete_projects_and_organizations(sender, instance, *args, **kwargs):
+    # Here we count the owner list from the projects that the user own
+    # Then exclude the projects where there are more than one owner
+    projects = instance.projects.all().annotate(num_users=Count('users')).exclude(num_users__gt=1)
+
+    # Here we count the users list from the organization that the user belong
+    # Then exclude the organizations where there are more than one user
+    oauth_organizations = (instance.oauth_organizations.annotate(num_users=Count('users'))
+                                                       .exclude(num_users__gt=1))
+
+    projects.delete()
+    oauth_organizations.delete()
+
+
 signals.check_request_enabled.connect(decide_if_cors)
diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py
--- a/readthedocs/profiles/views.py
+++ b/readthedocs/profiles/views.py
@@ -196,11 +196,9 @@ def delete_account(request):
     if request.method == 'POST':
         form = UserDeleteForm(instance=request.user, data=request.POST)
         if form.is_valid():
-
-            # Do not delete the account permanently because it may create disaster
-            # Inactive the user instead.
-            request.user.is_active = False
-            request.user.save()
+            # Delete the user permanently
+            # It will also delete some projects where he is the only owner
+            request.user.delete()
             logout(request)
             messages.info(request, 'You have successfully deleted your account')
 


Test patch: