Skip to content

Commit c48f611

Browse files
committed
[Fix readthedocs#3182] Better user deletion
1 parent b65e2f3 commit c48f611

File tree

4 files changed

+39
-2
lines changed

4 files changed

+39
-2
lines changed

readthedocs/core/signals.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55
import logging
66

77
from corsheaders import signals
8+
from django.contrib.auth import get_user_model
9+
from django.db.models.signals import pre_delete
810
from django.dispatch import Signal
9-
from django.db.models import Q
11+
from django.db.models import Q, Count
12+
from django.dispatch import receiver
1013
from future.backports.urllib.parse import urlparse
1114

15+
from readthedocs.oauth.tasks import bulk_delete_oauth_remote_organizations
1216
from readthedocs.projects.models import Project, Domain
13-
17+
from readthedocs.projects.tasks import bulk_delete_projects
1418

1519
log = logging.getLogger(__name__)
20+
User = get_user_model()
1621

1722
WHITELIST_URLS = ['/api/v2/footer_html', '/api/v2/search', '/api/v2/docsearch']
1823

@@ -62,4 +67,23 @@ def decide_if_cors(sender, request, **kwargs): # pylint: disable=unused-argumen
6267

6368
return False
6469

70+
71+
@receiver(pre_delete, sender=User)
72+
def delete_projects_and_organizations(instance, *args, **kwargs):
73+
# Here we count the owner list from the projects that the user own
74+
# Then exclude the projects where there are more than one owner
75+
projects_id = (instance.projects.all().annotate(num_users=Count('users'))
76+
.exclude(num_users__gt=1)
77+
.values_list('id', flat=True))
78+
79+
# Here we count the users list from the organization that the user belong
80+
# Then exclude the organizations where there are more than one user
81+
oauth_organizations_id = (instance.oauth_organizations.annotate(num_users=Count('users'))
82+
.exclude(num_users__gt=1)
83+
.values_list('id', flat=True))
84+
85+
bulk_delete_projects.delay(projects_id)
86+
bulk_delete_oauth_remote_organizations.delay(oauth_organizations_id)
87+
88+
6589
signals.check_request_enabled.connect(decide_if_cors)

readthedocs/oauth/tasks.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"""Tasks for OAuth services"""
22

33
from __future__ import absolute_import
4+
from celery import task
45
from django.contrib.auth.models import User
56
from djcelery import celery as celery_app
67

78
from readthedocs.core.utils.tasks import PublicTask
89
from readthedocs.core.utils.tasks import permission_check
910
from readthedocs.core.utils.tasks import user_id_matches
11+
from readthedocs.oauth.models import RemoteOrganization
1012
from .services import registry
1113

1214

@@ -21,5 +23,9 @@ def run_public(self, user_id):
2123
for service in service_cls.for_user(user):
2224
service.sync()
2325

26+
@task()
27+
def bulk_delete_oauth_remote_organizations(organizations_id):
28+
RemoteOrganization.objects.filter(id__in=organizations_id).delete()
29+
2430

2531
sync_remote_repositories = celery_app.tasks[SyncRemoteRepositories.name]

readthedocs/projects/tasks.py

+5
Original file line numberDiff line numberDiff line change
@@ -984,3 +984,8 @@ def clear_html_artifacts(version):
984984
if isinstance(version, int):
985985
version = Version.objects.get(pk=version)
986986
remove_dir(version.project.rtd_build_path(version=version.slug))
987+
988+
989+
@task()
990+
def bulk_delete_projects(projects_id):
991+
Project.objects.filter(id__in=projects_id).delete()

readthedocs/templates/profiles/private/delete_account.html

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<form method="POST" action=".">
1313
{% csrf_token %}
1414
{{ form }}
15+
<br>
16+
<strong>Be careful! This can not be undone!</strong>
1517
<input type="submit" name="submit" value="{% trans "Delete Account" %}" id="submit"/>
1618
</form>
1719
{% endblock %}

0 commit comments

Comments
 (0)