diff --git a/users/admin.py b/users/admin.py index 4cb02d3db1..c8fb8a28dc 100644 --- a/users/admin.py +++ b/users/admin.py @@ -135,7 +135,7 @@ class UserAdmin(admin.ModelAdmin): actions = [ "mark_selected_as_spam", "soft_delete_selected", - "hard_delete_selected", + "clean_user_data_deletion", "run_profile_spam_detection_on_selected", ] search_fields = ["username", "email", "pk"] @@ -220,8 +220,13 @@ def soft_delete_selected(self, request, queryset: QuerySet[User]): for user in queryset: user.soft_delete() - def hard_delete_selected(self, request, queryset: QuerySet[User]): - queryset.delete() + def clean_user_data_deletion(self, request, queryset: QuerySet[User]): + for user in queryset: + user.clean_user_data_delete() + + clean_user_data_deletion.short_description = ( + "One click Personal Data deletion (GDPR compliant)" + ) def run_profile_spam_detection_on_selected(self, request, queryset: QuerySet[User]): for user in queryset: diff --git a/users/models.py b/users/models.py index 6cb62cf773..03d13d02aa 100644 --- a/users/models.py +++ b/users/models.py @@ -5,9 +5,11 @@ from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager from django.contrib.postgres.fields import ArrayField -from django.db import models +from django.db import models, transaction from django.db.models import QuerySet from django.utils import timezone +from rest_framework.authtoken.models import Token +from social_django.models import UserSocialAuth from utils.models import TimeStampedModel @@ -154,6 +156,68 @@ def soft_delete(self: "User") -> None: self.save() + @transaction.atomic + def clean_user_data_delete(self: "User") -> None: + # Update User object + self.is_active = False + self.bio = "" + self.old_usernames = [] + self.website = None + self.twitter = None + self.linkedin = None + self.facebook = None + self.github = None + self.good_judgement_open = None + self.kalshi = None + self.manifold = None + self.infer = None + self.hypermind = None + self.occupation = None + self.location = None + self.profile_picture = None + self.unsubscribed_mailing_tags = [] + self.language = None + self.username = "deleted_user-" + str(self.id) + self.first_name = "" + self.last_name = "" + self.email = "" + self.set_password(None) + self.save() + + # Comments + self.comment_set.filter(is_private=True).delete() + # don't touch public comments + + # Token + Token.objects.filter(user=self).delete() + + # Social Auth login credentials + UserSocialAuth.objects.filter(user=self).delete() + + # Posts (Notebooks/Questions) + from posts.models import Post + + def hard_delete_post(post: Post): + if question := post.question: + question.delete() + if group_of_questions := post.group_of_questions: + group_of_questions.delete() + if conditional := post.conditional: + conditional.delete() + if notebook := post.notebook: + notebook.delete() + post.delete() + + posts = self.posts.all() + for post in posts: + # keep if there is at least one non-author comment + if post.comments.exclude(author=self).exists(): + continue + # keep if there is at least one non-author forecast + if post.forecasts.exclude(author=self).exists(): + continue + hard_delete_post(post) + class UserCampaignRegistration(TimeStampedModel): """