mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 19:41:11 +00:00
Adds migration and more tests
This commit is contained in:
parent
5e42afd85a
commit
61caeed5a3
3 changed files with 106 additions and 3 deletions
25
bookwyrm/migrations/0183_auto_20231105_1607.py
Normal file
25
bookwyrm/migrations/0183_auto_20231105_1607.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-11-05 16:07
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from bookwyrm.models import User
|
||||||
|
|
||||||
|
|
||||||
|
def erase_deleted_user_data(apps, schema_editor):
|
||||||
|
"""Retroactively clear user data"""
|
||||||
|
for user in User.get_permanently_deleted_users():
|
||||||
|
user.erase_user_data()
|
||||||
|
user.save(broadcast=False)
|
||||||
|
user.erase_user_statuses(broadcast=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0182_auto_20231027_1122"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
erase_deleted_user_data, reverse_code=migrations.RunPython.noop
|
||||||
|
)
|
||||||
|
]
|
|
@ -8,7 +8,7 @@ from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.postgres.fields import ArrayField, CICharField
|
from django.contrib.postgres.fields import ArrayField, CICharField
|
||||||
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
|
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction, IntegrityError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from model_utils import FieldTracker
|
from model_utils import FieldTracker
|
||||||
|
@ -263,6 +263,13 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
is_active=True,
|
is_active=True,
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_permanently_deleted_users(cls):
|
||||||
|
return cls.objects.filter(
|
||||||
|
is_active=False,
|
||||||
|
deactivation_reason__in=["self_deletion", "moderator_deletion"],
|
||||||
|
).distinct()
|
||||||
|
|
||||||
def update_active_date(self):
|
def update_active_date(self):
|
||||||
"""this user is here! they are doing things!"""
|
"""this user is here! they are doing things!"""
|
||||||
self.last_active_date = timezone.now()
|
self.last_active_date = timezone.now()
|
||||||
|
@ -415,10 +422,24 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
self.name = None
|
self.name = None
|
||||||
self.favorites.set([])
|
self.favorites.set([])
|
||||||
|
|
||||||
def erase_user_statuses(self):
|
def erase_user_statuses(self, broadcast=True):
|
||||||
"""Wipe the data on all the user's statuses"""
|
"""Wipe the data on all the user's statuses"""
|
||||||
|
# safety valve: make sure the user is deleted
|
||||||
|
if not self.is_permanently_deleted:
|
||||||
|
raise IntegrityError(
|
||||||
|
"Attempted to delete statuses for improperly deleted user"
|
||||||
|
)
|
||||||
|
|
||||||
for status in self.status_set.all():
|
for status in self.status_set.all():
|
||||||
status.delete()
|
status.delete(broadcast=broadcast)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_permanently_deleted(self):
|
||||||
|
"""is this user inactive, or really truly deleted?"""
|
||||||
|
return not self.is_active and self.deactivation_reason in [
|
||||||
|
"self_deletion",
|
||||||
|
"moderator_deletion",
|
||||||
|
]
|
||||||
|
|
||||||
def deactivate(self):
|
def deactivate(self):
|
||||||
"""Disable the user but allow them to reactivate"""
|
"""Disable the user but allow them to reactivate"""
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import json
|
import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
from django.db import IntegrityError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
import responses
|
import responses
|
||||||
|
|
||||||
|
@ -265,6 +266,25 @@ class User(TestCase):
|
||||||
self.assertIsNone(status.content)
|
self.assertIsNone(status.content)
|
||||||
self.assertIsNotNone(status.deleted_date)
|
self.assertIsNotNone(status.deleted_date)
|
||||||
|
|
||||||
|
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
||||||
|
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||||
|
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||||
|
def test_delete_user_erase_statuses(self, *_):
|
||||||
|
"""erase user statuses when user is deleted"""
|
||||||
|
status = models.Status.objects.create(user=self.user, content="hello")
|
||||||
|
self.assertFalse(status.deleted)
|
||||||
|
self.assertIsNotNone(status.content)
|
||||||
|
self.assertIsNone(status.deleted_date)
|
||||||
|
|
||||||
|
self.user.deactivate()
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
self.user.erase_user_statuses()
|
||||||
|
|
||||||
|
status.refresh_from_db()
|
||||||
|
self.assertFalse(status.deleted)
|
||||||
|
self.assertIsNotNone(status.content)
|
||||||
|
self.assertIsNone(status.deleted_date)
|
||||||
|
|
||||||
def test_admins_no_admins(self):
|
def test_admins_no_admins(self):
|
||||||
"""list of admins"""
|
"""list of admins"""
|
||||||
result = models.User.admins()
|
result = models.User.admins()
|
||||||
|
@ -302,3 +322,40 @@ class User(TestCase):
|
||||||
results = models.User.admins()
|
results = models.User.admins()
|
||||||
self.assertEqual(results.count(), 1)
|
self.assertEqual(results.count(), 1)
|
||||||
self.assertEqual(results.first(), self.user)
|
self.assertEqual(results.first(), self.user)
|
||||||
|
|
||||||
|
def test_get_permanently_deleted_users(self):
|
||||||
|
|
||||||
|
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||||
|
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||||
|
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||||
|
active_user = models.User.objects.create_user(
|
||||||
|
f"activeuser@{DOMAIN}",
|
||||||
|
"activeuser@activeuser.activeuser",
|
||||||
|
"activeuserword",
|
||||||
|
local=True,
|
||||||
|
localname="active",
|
||||||
|
)
|
||||||
|
deleted_user = models.User.objects.create_user(
|
||||||
|
f"deleteduser@{DOMAIN}",
|
||||||
|
"deleteduser@deleteduser.deleteduser",
|
||||||
|
"deleteduserword",
|
||||||
|
local=True,
|
||||||
|
localname="deleted",
|
||||||
|
is_active=False,
|
||||||
|
deactivation_reason="self_deletion",
|
||||||
|
)
|
||||||
|
inactive_user = models.User.objects.create_user(
|
||||||
|
f"inactiveuser@{DOMAIN}",
|
||||||
|
"inactiveuser@inactiveuser.inactiveuser",
|
||||||
|
"inactiveuserword",
|
||||||
|
local=True,
|
||||||
|
localname="inactive",
|
||||||
|
is_active=False,
|
||||||
|
deactivation_reason="self_deactivation",
|
||||||
|
)
|
||||||
|
|
||||||
|
deleted_users = models.User.get_permanently_deleted_users()
|
||||||
|
|
||||||
|
self.assertTrue(deleted_users.filter(localname="deleted").exists())
|
||||||
|
self.assertFalse(deleted_users.filter(localname="active").exists())
|
||||||
|
self.assertFalse(deleted_users.filter(localname="inactive").exists())
|
||||||
|
|
Loading…
Reference in a new issue