diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py index bbef98952e4..7f91d3d057b 100644 --- a/readthedocs/core/signals.py +++ b/readthedocs/core/signals.py @@ -5,14 +5,19 @@ import logging from corsheaders import signals +from django.contrib.auth import get_user_model +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.oauth.tasks import bulk_delete_oauth_remote_organizations from readthedocs.projects.models import Project, Domain - +from readthedocs.projects.tasks import bulk_delete_projects log = logging.getLogger(__name__) +User = get_user_model() WHITELIST_URLS = ['/api/v2/footer_html', '/api/v2/search', '/api/v2/docsearch'] @@ -62,4 +67,25 @@ def decide_if_cors(sender, request, **kwargs): # pylint: disable=unused-argumen return False + +@receiver(pre_delete, sender=User) +def delete_projects_and_organizations(instance, *args, **kwargs): + # Projects id where user is the only owner + + # 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_id = (instance.projects.all().annotate(num_users=Count('users')) + .exclude(num_users__gt=1) + .values_list('id', flat=True)) + + # 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_id = (instance.oauth_organizations.annotate(num_users=Count('users')) + .exclude(num_users__gt=1) + .values_list('id', flat=True)) + + bulk_delete_projects.delay(projects_id) + bulk_delete_oauth_remote_organizations.delay(oauth_organizations_id) + + signals.check_request_enabled.connect(decide_if_cors) diff --git a/readthedocs/oauth/tasks.py b/readthedocs/oauth/tasks.py index b345d443046..151dce8895b 100644 --- a/readthedocs/oauth/tasks.py +++ b/readthedocs/oauth/tasks.py @@ -1,12 +1,14 @@ """Tasks for OAuth services""" from __future__ import absolute_import +from celery import task from django.contrib.auth.models import User from djcelery import celery as celery_app from readthedocs.core.utils.tasks import PublicTask from readthedocs.core.utils.tasks import permission_check from readthedocs.core.utils.tasks import user_id_matches +from readthedocs.oauth.models import RemoteOrganization from .services import registry @@ -21,5 +23,9 @@ def run_public(self, user_id): for service in service_cls.for_user(user): service.sync() +@task() +def bulk_delete_oauth_remote_organizations(organizations_id): + RemoteOrganization.objects.filter(id__in=organizations_id).delete() + sync_remote_repositories = celery_app.tasks[SyncRemoteRepositories.name] diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py index 7d50ea0881c..9000d329d9c 100644 --- a/readthedocs/projects/tasks.py +++ b/readthedocs/projects/tasks.py @@ -984,3 +984,8 @@ def clear_html_artifacts(version): if isinstance(version, int): version = Version.objects.get(pk=version) remove_dir(version.project.rtd_build_path(version=version.slug)) + + +@task() +def bulk_delete_projects(projects_id): + Project.objects.filter(id__in=projects_id).delete() diff --git a/readthedocs/templates/profiles/private/delete_account.html b/readthedocs/templates/profiles/private/delete_account.html index 6a76f5c7576..0b7696132e5 100644 --- a/readthedocs/templates/profiles/private/delete_account.html +++ b/readthedocs/templates/profiles/private/delete_account.html @@ -12,6 +12,8 @@
{% csrf_token %} {{ form }} +
+ Be careful! This can not be undone!
{% endblock %}