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:
Родитель
6182109a5f
Коммит
4005602c5d
|
@ -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()
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче