Implement unbanning user(s) functionality in admin/cinder webhook action (#21472)

* Implement unbanning user(s) functionality in admin/cinder webhook action
This commit is contained in:
Mathieu Pillard 2023-11-21 17:56:28 +01:00 коммит произвёл GitHub
Родитель 6182109a5f
Коммит 4005602c5d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 349 добавлений и 154 удалений

Просмотреть файл

@ -37,8 +37,17 @@ class TestCinderAction(TestCase):
self.cinder_report.abuse_report.update(user=user, guid=None)
action = CinderActionBanUser(self.cinder_report)
action.process()
user.reload()
self.assertCloseToNow(user.banned)
def test_approve_user(self):
user = user_factory(banned=self.days_ago(1), deleted=True)
self.cinder_report.abuse_report.update(user=user, guid=None)
action = CinderActionApproveAppealOverride(self.cinder_report)
action.process()
user.reload()
assert not user.banned
def test_disable_addon(self):
addon = addon_factory()
self.cinder_report.abuse_report.update(guid=addon.guid)

Просмотреть файл

@ -34,7 +34,7 @@ class CinderActionBanUser(CinderAction):
def process(self):
if user := self.abuse_report.user:
log_create(amo.LOG.ADMIN_USER_BANNED, user)
UserProfile.ban_and_disable_related_content_bulk([user], move_files=True)
UserProfile.objects.filter(pk=user.pk).ban_and_disable_related_content()
self.notify_reporter()
self.notify_targets([user])
@ -112,7 +112,9 @@ class CinderActionApproveAppealOverride(CinderAction):
self.notify_targets(target.authors.all())
elif isinstance(target, UserProfile) and target.banned:
# TODO: un-ban the user
UserProfile.objects.filter(
pk=target.pk
).unban_and_reenable_related_content()
self.notify_targets([target])
elif isinstance(target, Collection) and target.deleted:

Просмотреть файл

@ -992,6 +992,13 @@ class ADMIN_USER_CONTENT_RESTORED(_LOG):
admin_event = True
class ADMIN_USER_UNBAN(_LOG):
id = 187
format = _('User {user} unbanned.')
keep = True
admin_event = True
LOGS = [x for x in vars().values() if isclass(x) and issubclass(x, _LOG) and x != _LOG]
# Make sure there's no duplicate IDs.
assert len(LOGS) == len({log.id for log in LOGS})

Просмотреть файл

@ -177,7 +177,12 @@ class UserAdmin(AMOModelAdmin):
),
)
actions = ['ban_action', 'reset_api_key_action', 'reset_session_action']
actions = [
'ban_action',
'unban_action',
'reset_api_key_action',
'reset_session_action',
]
def get_urls(self):
def wrap(view):
@ -193,6 +198,11 @@ class UserAdmin(AMOModelAdmin):
wrap(self.ban_view),
name='users_userprofile_ban',
),
re_path(
r'^(?P<object_id>.+)/unban/$',
wrap(self.unban_view),
name='users_userprofile_unban',
),
re_path(
r'^(?P<object_id>.+)/reset_api_key/$',
wrap(self.reset_api_key_view),
@ -214,9 +224,10 @@ class UserAdmin(AMOModelAdmin):
def get_actions(self, request):
actions = super().get_actions(request)
if not acl.action_allowed_for(request.user, amo.permissions.USERS_EDIT):
# You need Users:Edit to be able to ban users and reset their api
# key confirmation.
# You need Users:Edit to be able to (un)ban users and reset their
# api key confirmation/session.
actions.pop('ban_action')
actions.pop('unban_action')
actions.pop('reset_api_key_action')
actions.pop('reset_session_action')
return actions
@ -271,14 +282,31 @@ class UserAdmin(AMOModelAdmin):
if not acl.action_allowed_for(request.user, amo.permissions.USERS_EDIT):
return HttpResponseForbidden()
ActivityLog.create(amo.LOG.ADMIN_USER_BANNED, obj)
UserProfile.ban_and_disable_related_content_bulk([obj], move_files=True)
self.model.objects.filter(pk=obj.pk).ban_and_disable_related_content()
kw = {'user': force_str(obj)}
self.message_user(request, 'The user "%(user)s" has been banned.' % kw)
return HttpResponseRedirect(
reverse('admin:users_userprofile_change', args=(obj.pk,))
)
def unban_view(self, request, object_id, extra_context=None):
if request.method != 'POST':
return HttpResponseNotAllowed(['POST'])
obj = self.get_object(request, unquote(object_id))
if obj is None:
raise Http404()
if not acl.action_allowed_for(request.user, amo.permissions.USERS_EDIT):
return HttpResponseForbidden()
self.model.objects.filter(pk=obj.pk).unban_and_reenable_related_content()
kw = {'user': force_str(obj)}
self.message_user(request, 'The user "%(user)s" has been unbanned.' % kw)
return HttpResponseRedirect(
reverse('admin:users_userprofile_change', args=(obj.pk,))
)
def reset_api_key_view(self, request, object_id, extra_context=None):
if request.method != 'POST':
return HttpResponseNotAllowed(['POST'])
@ -336,16 +364,19 @@ class UserAdmin(AMOModelAdmin):
)
def ban_action(self, request, qs):
users = []
UserProfile.ban_and_disable_related_content_bulk(qs)
for obj in qs:
ActivityLog.create(amo.LOG.ADMIN_USER_BANNED, obj)
users.append(force_str(obj))
kw = {'users': ', '.join(users)}
qs.ban_and_disable_related_content()
kw = {'users': ', '.join(str(user) for user in qs)}
self.message_user(request, 'The users "%(users)s" have been banned.' % kw)
ban_action.short_description = 'Ban selected users'
def unban_action(self, request, qs):
qs.unban_and_reenable_related_content()
kw = {'users': ', '.join(str(user) for user in qs)}
self.message_user(request, 'The users "%(users)s" have been unbanned.' % kw)
unban_action.short_description = 'Unban selected users'
def reset_session_action(self, request, qs):
users = []
qs.update(auth_id=None) # A new value will be generated at next login.

Просмотреть файл

@ -32,7 +32,13 @@ from olympia import activity, amo, core
from olympia.access.models import Group, GroupUser
from olympia.amo.decorators import use_primary_db
from olympia.amo.fields import CIDRField, PositiveAutoField
from olympia.amo.models import LongNameIndex, ManagerBase, ModelBase, OnChangeMixin
from olympia.amo.models import (
BaseQuerySet,
LongNameIndex,
ManagerBase,
ModelBase,
OnChangeMixin,
)
from olympia.amo.utils import id_to_path
from olympia.amo.validators import OneOrMorePrintableCharacterValidator
from olympia.files.models import File
@ -95,7 +101,150 @@ class UserEmailBoundField(forms.boundfield.BoundField):
return attrs
class UserQuerySet(BaseQuerySet):
def ban_and_disable_related_content(self):
"""Admin method to ban multiple users and disable the content they
produced.
Similar to deletion, except that the content produced by the user is
forcibly soft-disabled instead of being deleted where possible, and the
user is not anonymized: we keep their data until hard-deletion kicks in
(see clear_old_user_data), including fxa_id and email so that they are
never able to log back in.
"""
from olympia.addons.models import Addon, AddonUser
from olympia.addons.tasks import index_addons
from olympia.bandwagon.models import Collection
from olympia.ratings.models import Rating
from olympia.users.tasks import delete_photo
users = self.all()
BannedUserContent.objects.bulk_create(
[BannedUserContent(user=user) for user in users], ignore_conflicts=True
)
# Collect affected addons
addon_ids = set(
Addon.unfiltered.exclude(
status__in=(amo.STATUS_DELETED, amo.STATUS_DISABLED)
)
.filter(addonuser__user__in=users)
.values_list('id', flat=True)
)
# First addons who have other authors we aren't banning - we are
# keeping the add-ons up, but soft-deleting the relationships.
addon_joint_ids = set(
AddonUser.objects.filter(addon_id__in=addon_ids)
.exclude(user__in=users)
.values_list('addon_id', flat=True)
)
joint_addonusers_qs = AddonUser.objects.filter(
user__in=users, addon_id__in=addon_joint_ids
)
# Keep track of the AddonUser we are (soft-)deleting.
BannedAddonsUsersModel = BannedUserContent.addons_users.through
BannedAddonsUsersModel.objects.bulk_create(
[
BannedAddonsUsersModel(
bannedusercontent_id=val['user'], addonuser_id=val['pk']
)
for val in joint_addonusers_qs.values('user', 'pk')
]
)
# (Soft-)delete them.
joint_addonusers_qs.delete()
# Then deal with users who are the sole author - we are disabling them.
addons_sole = Addon.unfiltered.filter(id__in=addon_ids - addon_joint_ids)
# set the status to disabled - using the manager update() method
addons_sole.update(status=amo.STATUS_DISABLED)
# disable Files in bulk that need to be disabled now the addons are disabled
Addon.disable_all_files(addons_sole, File.STATUS_DISABLED_REASONS.ADDON_DISABLE)
# Keep track of the Addons and the relevant user.
sole_addonusers_qs = AddonUser.objects.filter(
user__in=users, addon__in=addons_sole
)
BannedAddonsModel = BannedUserContent.addons.through
BannedAddonsModel.objects.bulk_create(
[
BannedAddonsModel(
bannedusercontent_id=val['user'], addon_id=val['addon']
)
for val in sole_addonusers_qs.values('user', 'addon')
]
)
# Finally run Addon.force_disable to add the logging; update versions.
addons_sole_ids = []
for addon in addons_sole:
addons_sole_ids.append(addon.pk)
addon.force_disable()
index_addons.delay(addons_sole_ids)
# Soft-delete the other content associated with the user: Ratings and
# Collections.
# Keep track of the Collections
collections_qs = Collection.objects.filter(author__in=users)
BannedCollectionsModel = BannedUserContent.collections.through
BannedCollectionsModel.objects.bulk_create(
[
BannedCollectionsModel(
bannedusercontent_id=val['author'], collection_id=val['pk']
)
for val in collections_qs.values('author', 'pk')
]
)
# Soft-delete them (keeping their slug - will be restored if unbanned).
collections_qs.delete()
# Keep track of the Ratings
ratings_qs = Rating.objects.filter(user__in=users)
BannedRatingsModel = BannedUserContent.ratings.through
BannedRatingsModel.objects.bulk_create(
[
BannedRatingsModel(
bannedusercontent_id=val['user'], rating_id=val['pk']
)
for val in ratings_qs.values('user', 'pk')
]
)
# Soft-delete them
ratings_qs.delete()
# And then ban the users.
ids = []
for user in users:
activity.log_create(amo.LOG.ADMIN_USER_BANNED, user)
log.info(
'User (%s: <%s>) is being banned.',
user,
user.email,
extra={'sensitive': True},
)
user.banned = user.modified = datetime.now()
user.deleted = True
ids.append(user.pk)
# To delete their photo, avoid delete_picture() that updates
# picture_type immediately.
delete_photo.delay(user.pk)
return self.bulk_update(users, fields=('banned', 'deleted', 'modified'))
def unban_and_reenable_related_content(self):
"""Admin method to unban users and restore their content that was
disabled when they were banned."""
for user in self:
banned_user_content = BannedUserContent.objects.filter(user=user).first()
if banned_user_content:
banned_user_content.restore()
activity.log_create(amo.LOG.ADMIN_USER_UNBAN, user)
user.deleted = False
user.banned = None
user.save()
class UserManager(BaseUserManager, ManagerBase):
_queryset_class = UserQuerySet
def create_user(self, email, fxa_id=None, **kwargs):
now = timezone.now()
user = self.model(email=email, fxa_id=fxa_id, last_login=now, **kwargs)
@ -117,6 +266,12 @@ class UserManager(BaseUserManager, ManagerBase):
GroupUser.objects.create(user=user, group=admins)
return user
def ban_and_disable_related_content(self):
return self.all().ban_and_disable_related_content()
def unban_and_reenable_related_content(self):
return self.all().unban_and_reenable_related_content()
class UserProfile(OnChangeMixin, ModelBase, AbstractBaseUser):
objects = UserManager()
@ -463,127 +618,6 @@ class UserProfile(OnChangeMixin, ModelBase, AbstractBaseUser):
setattr(self, field_name, field.get_default())
self.delete_picture()
@classmethod
def ban_and_disable_related_content_bulk(cls, users, move_files=False):
"""Admin method to ban users and disable the content they produced.
Similar to deletion, except that the content produced by the user is
forcibly disabled instead of being deleted where possible, and the user
is not anonymized: we keep their data until hard-deletion kicks in
(see clear_old_user_data), including fxa_id and email so that they are
never able to log back in.
"""
from olympia.addons.models import Addon, AddonUser
from olympia.addons.tasks import index_addons
from olympia.bandwagon.models import Collection
from olympia.ratings.models import Rating
from olympia.users.tasks import delete_photo
BannedUserContent.objects.bulk_create(
[BannedUserContent(user=user) for user in users], ignore_conflicts=True
)
# Collect affected addons
addon_ids = set(
Addon.unfiltered.exclude(
status__in=(amo.STATUS_DELETED, amo.STATUS_DISABLED)
)
.filter(addonuser__user__in=users)
.values_list('id', flat=True)
)
# First addons who have other authors we aren't banning - we are
# keeping the add-ons up, but soft-deleting the relationships.
addon_joint_ids = set(
AddonUser.objects.filter(addon_id__in=addon_ids)
.exclude(user__in=users)
.values_list('addon_id', flat=True)
)
qs = AddonUser.objects.filter(user__in=users, addon_id__in=addon_joint_ids)
# Keep track of the AddonUser we are (soft-)deleting.
BannedAddonsUsersModel = BannedUserContent.addons_users.through
BannedAddonsUsersModel.objects.bulk_create(
[
BannedAddonsUsersModel(
bannedusercontent_id=val['user'], addonuser_id=val['pk']
)
for val in qs.values('user', 'pk')
]
)
# (Soft-)delete them.
qs.delete()
# Then deal with users who are the sole author - we are disabling them.
addons_sole = Addon.unfiltered.filter(id__in=addon_ids - addon_joint_ids)
# set the status to disabled - using the manager update() method
addons_sole.update(status=amo.STATUS_DISABLED)
# disable Files in bulk that need to be disabled now the addons are disabled
Addon.disable_all_files(addons_sole, File.STATUS_DISABLED_REASONS.ADDON_DISABLE)
# Keep track of the Addons and the relevant user.
qs = AddonUser.objects.filter(user__in=users, addon__in=addons_sole)
BannedAddonsModel = BannedUserContent.addons.through
BannedAddonsModel.objects.bulk_create(
[
BannedAddonsModel(
bannedusercontent_id=val['user'], addon_id=val['addon']
)
for val in qs.values('user', 'addon')
]
)
# Finally run Addon.force_disable to add the logging; update versions.
addons_sole_ids = []
for addon in addons_sole:
addons_sole_ids.append(addon.pk)
addon.force_disable()
index_addons.delay(addons_sole_ids)
# Soft-delete the other content associated with the user: Ratings and
# Collections.
# Keep track of the Collections
qs = Collection.objects.filter(author__in=users)
BannedCollectionsModel = BannedUserContent.collections.through
BannedCollectionsModel.objects.bulk_create(
[
BannedCollectionsModel(
bannedusercontent_id=val['author'], collection_id=val['pk']
)
for val in qs.values('author', 'pk')
]
)
# Soft-delete them (keeping their slug - will be restored if unbanned).
qs.delete()
# Keep track of the Ratings
qs = Rating.objects.filter(user__in=users)
BannedRatingsModel = BannedUserContent.ratings.through
BannedRatingsModel.objects.bulk_create(
[
BannedRatingsModel(
bannedusercontent_id=val['user'], rating_id=val['pk']
)
for val in qs.values('user', 'pk')
]
)
# Soft-delete them
Rating.objects.filter(user__in=users).delete()
# And then ban the users.
ids = []
for user in users:
log.info(
'User (%s: <%s>) is being banned.',
user,
user.email,
extra={'sensitive': True},
)
user.banned = user.modified = datetime.now()
user.deleted = True
ids.append(user.pk)
# To delete their photo, avoid delete_picture() that updates
# picture_type immediately.
delete_photo.delay(user.pk)
cls.objects.bulk_update(users, fields=('banned', 'deleted', 'modified'))
def _prepare_delete_email(self):
site_url = settings.EXTERNAL_SITE_URL
template = loader.get_template('users/emails/user_deleted.ltxt')

Просмотреть файл

@ -12,7 +12,9 @@
{% endcomment %}
{% if has_users_edit_permission %}
<input formaction="{% url 'admin:users_userprofile_ban' original.pk|admin_urlquote %}"
type="submit" value="Ban" />
type="submit" value="Ban" {% if original.banned %}disabled="disabled"{% endif %}/>
<input formaction="{% url 'admin:users_userprofile_unban' original.pk|admin_urlquote %}"
type="submit" value="Unban" {% if not original.banned %}disabled="disabled"{% endif %}/>
<input formaction="{% url 'admin:users_userprofile_reset_api_key' original.pk|admin_urlquote %}"
type="submit" value="Reset API Key" />
<input formaction="{% url 'admin:users_userprofile_reset_session' original.pk|admin_urlquote %}"

Просмотреть файл

@ -573,6 +573,7 @@ class TestUserAdmin(TestCase):
self.grant_permission(request.user, 'Users:Edit')
assert list(user_admin.get_actions(request).keys()) == [
'ban_action',
'unban_action',
'reset_api_key_action',
'reset_session_action',
]
@ -591,8 +592,10 @@ class TestUserAdmin(TestCase):
another_user.reload()
self.user.reload()
assert another_user.deleted
assert another_user.banned
assert another_user.email
assert self.user.deleted
assert self.user.banned
assert self.user.email
# The 3rd user should be unaffected.
assert not a_third_user.reload().deleted
@ -602,14 +605,44 @@ class TestUserAdmin(TestCase):
ActivityLog.objects.filter(action=amo.LOG.ADMIN_USER_BANNED.id).count() == 2
)
def test_ban_button_in_change_view(self):
def test_unban_action(self):
self.user.update(banned=self.days_ago(1), deleted=True)
another_user = user_factory(banned=self.days_ago(2), deleted=True)
a_third_user = user_factory(banned=self.days_ago(3), deleted=True)
users = UserProfile.objects.filter(pk__in=(another_user.pk, self.user.pk))
user_admin = UserAdmin(UserProfile, admin.site)
request = RequestFactory().get('/')
request.user = user_factory()
core.set_user(request.user)
request._messages = default_messages_storage(request)
user_admin.unban_action(request, users)
# Both users should be banned.
another_user.reload()
self.user.reload()
assert not another_user.deleted
assert not self.user.deleted
assert not another_user.banned
assert not self.user.banned
# The 3rd user should be unaffected.
a_third_user.reload()
assert a_third_user.deleted
assert a_third_user.banned
# We should see 2 activity logs for unbanning.
assert (
ActivityLog.objects.filter(action=amo.LOG.ADMIN_USER_UNBAN.id).count() == 2
)
def test_ban_and_unban_buttons_in_change_view(self):
ban_url = reverse('admin:users_userprofile_ban', args=(self.user.pk,))
unban_url = reverse('admin:users_userprofile_unban', args=(self.user.pk,))
user = user_factory(email='someone@mozilla.com')
self.grant_permission(user, 'Users:Edit')
self.client.force_login(user)
response = self.client.get(self.detail_url, follow=True)
assert response.status_code == 200
assert ban_url in response.content.decode('utf-8')
assert unban_url in response.content.decode('utf-8')
def test_reset_api_key_action(self):
another_user = user_factory()
@ -737,6 +770,38 @@ class TestUserAdmin(TestCase):
assert alog.action == amo.LOG.ADMIN_USER_BANNED.id
assert alog.arguments == [self.user]
def test_unban(self):
unban_url = reverse('admin:users_userprofile_unban', args=(self.user.pk,))
wrong_unban_url = reverse(
'admin:users_userprofile_unban', args=(self.user.pk + 42,)
)
self.user.update(banned=self.days_ago(42), deleted=True)
user = user_factory(email='someone@mozilla.com')
self.client.force_login(user)
core.set_user(user)
response = self.client.post(unban_url, follow=True)
assert response.status_code == 403
self.grant_permission(user, 'Users:Edit')
response = self.client.get(unban_url, follow=True)
assert response.status_code == 405 # Wrong http method.
response = self.client.post(wrong_unban_url, follow=True)
assert response.status_code == 404 # Wrong pk.
self.user.reload()
assert self.user.deleted
response = self.client.post(unban_url, follow=True)
assert response.status_code == 200
assert response.redirect_chain[-1][0].endswith(self.detail_url)
assert response.redirect_chain[-1][1] == 302
self.user.reload()
assert not self.user.deleted
assert not self.user.banned
assert self.user.email
alog = ActivityLog.objects.latest('pk')
assert alog.action == amo.LOG.ADMIN_USER_UNBAN.id
assert alog.arguments == [self.user]
def test_reset_api_key(self):
APIKey.objects.create(user=self.user, is_active=True, key='foo')
APIKeyConfirmation.objects.create(user=self.user)

Просмотреть файл

@ -239,7 +239,12 @@ class TestUserProfile(TestCase):
Rating.objects.create(user=user_innocent, addon=addon_multi, rating=5)
# Now that everything is set up, disable/delete related content.
UserProfile.ban_and_disable_related_content_bulk([user_sole, user_multi])
UserProfile.objects.filter(
pk__in=(user_sole.pk, user_multi.pk)
).ban_and_disable_related_content()
user_sole.reload()
user_multi.reload()
addon_sole.reload()
addon_multi.reload()
@ -349,7 +354,7 @@ class TestUserProfile(TestCase):
'user_multi': user_multi,
}
def test_restore_banned_content(self):
def test_unban_and_restore_banned_content(self):
fake_admin = user_factory(display_name='Fake Admin')
core.set_user(fake_admin) # Needed for activity log
users = self.test_ban_and_disable_related_content_bulk()
@ -358,9 +363,58 @@ class TestUserProfile(TestCase):
assert BannedUserContent.objects.filter(user=user_sole).exists()
assert BannedUserContent.objects.filter(user=user_multi).exists()
user_sole.content_disabled_on_ban.restore()
UserProfile.objects.filter(
pk__in=(user_sole.pk, user_multi.pk)
).unban_and_reenable_related_content()
# user_sole content was restored.
# user_sole was unbanned and content was restored.
user_sole.reload()
assert not user_sole.banned
assert not user_sole.deleted
addon_sole = user_sole.addons.get()
assert addon_sole.status == amo.STATUS_APPROVED
assert user_sole._ratings_all.all().count() == 2 # Includes replies
assert user_sole.collections.count() == 1
assert not BannedUserContent.objects.filter(user=user_sole).exists()
# user_multi was unbanned and content was restored.
user_multi.reload()
assert not user_multi.banned
assert not user_multi.deleted
assert user_multi.addons.count() == 1
assert user_multi._ratings_all.count() == 2 # Includes replies
assert user_multi.collections.count() == 1
for action in (
amo.LOG.ADMIN_USER_CONTENT_RESTORED,
amo.LOG.ADMIN_USER_UNBAN,
):
assert (
ActivityLog.objects.filter(action=action.id, user=fake_admin).count()
== 2
)
assert {
activity.arguments[0]
for activity in ActivityLog.objects.filter(
action=action.id, user=fake_admin
)
} == {user_multi, user_sole}
def test_unban_and_restore_banned_content_single(self):
fake_admin = user_factory(display_name='Fake Admin')
core.set_user(fake_admin) # Needed for activity log
users = self.test_ban_and_disable_related_content_bulk()
user_sole = users['user_sole']
user_multi = users['user_multi']
assert BannedUserContent.objects.filter(user=user_sole).exists()
assert BannedUserContent.objects.filter(user=user_multi).exists()
UserProfile.objects.filter(pk=user_sole.pk).unban_and_reenable_related_content()
# user_sole was unbanned and content was restored.
user_sole.reload()
assert not user_sole.banned
assert not user_sole.deleted
addon_sole = user_sole.addons.get()
assert addon_sole.status == amo.STATUS_APPROVED
assert user_sole._ratings_all.all().count() == 2 # Includes replies
@ -372,7 +426,10 @@ class TestUserProfile(TestCase):
assert activity.arguments == [user_sole]
assert activity.user == fake_admin
# user_multi still not restored yet.
# user_multi was not touched.
user_multi.reload()
assert user_multi.deleted
assert user_multi.banned
assert BannedUserContent.objects.filter(user=user_multi).exists()
assert user_multi.collections.count() == 0
assert user_multi.ratings.count() == 0
@ -383,18 +440,6 @@ class TestUserProfile(TestCase):
.exists()
)
user_multi.content_disabled_on_ban.restore()
assert user_multi.addons.count() == 1
assert user_multi._ratings_all.count() == 2 # Includes replies
assert user_multi.collections.count() == 1
activity = ActivityLog.objects.filter(
action=amo.LOG.ADMIN_USER_CONTENT_RESTORED.id
).latest('pk')
assert activity.arguments == [user_multi]
assert activity.user == fake_admin
assert not BannedUserContent.objects.exists()
def setup_user_to_be_have_content_disabled(self, user):
addon = user.addons.last()