add a queue for decision actions that are held (#22788)

* rename utils/CinderAction* to actions/ContentAction*

* CinderDecision to ContentDecison

* Add a basic admin to view ContentDecisions

* Add basic queue to show decisions where action has been held
This commit is contained in:
Andrew Williamson 2024-10-28 13:41:16 +00:00 коммит произвёл GitHub
Родитель d174685158
Коммит 6628df7d22
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
37 изменённых файлов: 654 добавлений и 335 удалений

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

@ -28,7 +28,7 @@ POLICY_DOCUMENT_URL = (
log = olympia.core.logger.getLogger('z.abuse')
class CinderAction:
class ContentAction:
description = 'Action has been taken'
valid_targets = ()
# No reporter emails will be sent while the paths are set to None
@ -83,26 +83,6 @@ class CinderAction:
"""No owner emails will be sent. Override to send owner emails"""
return ()
def get_target_name(self):
return str(
_('"{}" for {}').format(self.target, self.target.addon.name)
if isinstance(self.target, Rating)
else getattr(self.target, 'name', self.target)
)
def get_target_type(self):
match self.target:
case target if isinstance(target, Addon):
return target.get_type_display()
case target if isinstance(target, UserProfile):
return _('User profile')
case target if isinstance(target, Collection):
return _('Collection')
case target if isinstance(target, Rating):
return _('Rating')
case target:
return target.__class__.__name__
@property
def owner_template_path(self):
return f'abuse/emails/{self.__class__.__name__}.txt'
@ -114,7 +94,7 @@ class CinderAction:
if not owners:
return
template = loader.get_template(self.owner_template_path)
target_name = self.get_target_name()
target_name = self.decision.get_target_name()
reference_id = f'ref:{self.decision.get_reference_id()}'
# override target_url to devhub if there is no public listing
target_url = (
@ -134,7 +114,7 @@ class CinderAction:
'reference_id': reference_id,
'target': self.target,
'target_url': target_url,
'type': self.get_target_type(),
'type': self.decision.get_target_type(),
'SITE_URL': settings.SITE_URL,
**(extra_context or {}),
}
@ -194,7 +174,7 @@ class CinderAction:
with translation.override(
abuse_report.application_locale or settings.LANGUAGE_CODE
):
target_name = self.get_target_name()
target_name = self.decision.get_target_name()
reference_id = (
f'ref:{self.decision.get_reference_id()}/{abuse_report.id}'
)
@ -209,7 +189,7 @@ class CinderAction:
'policy_document_url': POLICY_DOCUMENT_URL,
'reference_id': reference_id,
'target_url': absolutify(self.target.get_url_path()),
'type': self.get_target_type(),
'type': self.decision.get_target_type(),
'SITE_URL': settings.SITE_URL,
}
if is_appeal:
@ -256,7 +236,7 @@ class AnyOwnerEmailMixin:
return [target.user]
class CinderActionBanUser(CinderAction):
class ContentActionBanUser(ContentAction):
description = 'Account has been banned'
valid_targets = (UserProfile,)
reporter_template_path = 'abuse/emails/reporter_takedown_user.txt'
@ -291,7 +271,7 @@ class CinderActionBanUser(CinderAction):
return [self.target]
class CinderActionDisableAddon(CinderAction):
class ContentActionDisableAddon(ContentAction):
description = 'Add-on has been disabled'
valid_targets = (Addon,)
reporter_template_path = 'abuse/emails/reporter_takedown_addon.txt'
@ -319,7 +299,7 @@ class CinderActionDisableAddon(CinderAction):
return self.target.authors.all()
class CinderActionRejectVersion(CinderActionDisableAddon):
class ContentActionRejectVersion(ContentActionDisableAddon):
description = 'Add-on version(s) have been rejected'
def should_hold_action(self):
@ -336,13 +316,13 @@ class CinderActionRejectVersion(CinderActionDisableAddon):
raise NotImplementedError
class CinderActionRejectVersionDelayed(CinderActionRejectVersion):
class ContentActionRejectVersionDelayed(ContentActionRejectVersion):
description = 'Add-on version(s) will be rejected'
reporter_template_path = 'abuse/emails/reporter_takedown_addon_delayed.txt'
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_takedown_delayed.txt'
class CinderActionEscalateAddon(CinderAction):
class ContentActionEscalateAddon(ContentAction):
valid_targets = (Addon,)
def process_action(self):
@ -351,7 +331,7 @@ class CinderActionEscalateAddon(CinderAction):
handle_escalate_action.delay(job_pk=self.decision.cinder_job.pk)
class CinderActionDeleteCollection(CinderAction):
class ContentActionDeleteCollection(ContentAction):
valid_targets = (Collection,)
description = 'Collection has been deleted'
reporter_template_path = 'abuse/emails/reporter_takedown_collection.txt'
@ -378,7 +358,7 @@ class CinderActionDeleteCollection(CinderAction):
return [self.target.author]
class CinderActionDeleteRating(CinderAction):
class ContentActionDeleteRating(ContentAction):
valid_targets = (Rating,)
description = 'Rating has been deleted'
reporter_template_path = 'abuse/emails/reporter_takedown_rating.txt'
@ -416,7 +396,9 @@ class CinderActionDeleteRating(CinderAction):
return [self.target.user]
class CinderActionTargetAppealApprove(AnyTargetMixin, AnyOwnerEmailMixin, CinderAction):
class ContentActionTargetAppealApprove(
AnyTargetMixin, AnyOwnerEmailMixin, ContentAction
):
description = 'Reported content is within policy, after appeal'
def process_action(self):
@ -438,18 +420,18 @@ class CinderActionTargetAppealApprove(AnyTargetMixin, AnyOwnerEmailMixin, Cinder
return None
class CinderActionOverrideApprove(CinderActionTargetAppealApprove):
class ContentActionOverrideApprove(ContentActionTargetAppealApprove):
description = 'Reported content is within policy, after override'
class CinderActionApproveNoAction(AnyTargetMixin, NoActionMixin, CinderAction):
class ContentActionApproveNoAction(AnyTargetMixin, NoActionMixin, ContentAction):
description = 'Reported content is within policy, initial decision, so no action'
reporter_template_path = 'abuse/emails/reporter_content_approve.txt'
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_approve.txt'
class CinderActionApproveInitialDecision(
AnyTargetMixin, NoActionMixin, AnyOwnerEmailMixin, CinderAction
class ContentActionApproveInitialDecision(
AnyTargetMixin, NoActionMixin, AnyOwnerEmailMixin, ContentAction
):
description = (
'Reported content is within policy, initial decision, approving versions'
@ -458,23 +440,23 @@ class CinderActionApproveInitialDecision(
reporter_appeal_template_path = 'abuse/emails/reporter_appeal_approve.txt'
class CinderActionTargetAppealRemovalAffirmation(
AnyTargetMixin, NoActionMixin, AnyOwnerEmailMixin, CinderAction
class ContentActionTargetAppealRemovalAffirmation(
AnyTargetMixin, NoActionMixin, AnyOwnerEmailMixin, ContentAction
):
description = 'Reported content is still offending, after appeal.'
class CinderActionIgnore(AnyTargetMixin, NoActionMixin, CinderAction):
class ContentActionIgnore(AnyTargetMixin, NoActionMixin, ContentAction):
description = 'Report is invalid, so no action'
reporter_template_path = 'abuse/emails/reporter_invalid_ignore.txt'
# no appeal template because no appeals possible
class CinderActionAlreadyRemoved(AnyTargetMixin, NoActionMixin, CinderAction):
class ContentActionAlreadyRemoved(AnyTargetMixin, NoActionMixin, ContentAction):
description = 'Content is already disabled or deleted, so no action'
reporter_template_path = 'abuse/emails/reporter_disabled_ignore.txt'
# no appeal template because no appeals possible
class CinderActionNotImplemented(NoActionMixin, CinderAction):
class ContentActionNotImplemented(NoActionMixin, ContentAction):
pass

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

@ -19,7 +19,7 @@ from olympia.amo.admin import AMOModelAdmin, DateRangeFilter, FakeChoicesMixin
from olympia.ratings.models import Rating
from olympia.translations.utils import truncate_text
from .models import AbuseReport, CinderPolicy
from .models import AbuseReport, CinderPolicy, ContentDecision
from .tasks import sync_cinder_policies
@ -448,5 +448,46 @@ class CinderPolicyAdmin(AMOModelAdmin):
return HttpResponseRedirect(reverse('admin:abuse_cinderpolicy_changelist'))
class ContentDecisionAdmin(AMOModelAdmin):
fields = (
'id',
'created',
'modified',
'addon',
'user',
'rating',
'collection',
'action',
'action_date',
'cinder_id',
'notes',
'policies',
'appeal_job',
)
list_display = (
'created',
'action',
'addon',
'user',
'rating',
'collection',
)
readonly_fields = fields
view_on_site = False
def has_add_permission(self, request):
# Adding new decisions through the admin is useless, so we prevent it.
return False
def has_delete_permission(self, request, obj=None):
# Decisions shouldn't be deleted - if they're wrong, they should be overridden.
return False
def has_change_permission(self, request, obj=None):
# Decisions can't be changed - if they're wrong, they should be overridden.
return False
admin.site.register(AbuseReport, AbuseReportAdmin)
admin.site.register(CinderPolicy, CinderPolicyAdmin)
admin.site.register(ContentDecision, ContentDecisionAdmin)

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

@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand
import olympia.core.logger
from olympia.abuse.models import CinderDecision
from olympia.abuse.models import ContentDecision
from olympia.abuse.tasks import handle_escalate_action
from olympia.constants.abuse import DECISION_ACTIONS
@ -10,7 +10,7 @@ class Command(BaseCommand):
log = olympia.core.logger.getLogger('z.abuse')
def handle(self, *args, **options):
qs = CinderDecision.objects.filter(
qs = ContentDecision.objects.filter(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
cinder_job__forwarded_to_job__isnull=True,
cinder_job__isnull=False,

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

@ -0,0 +1,17 @@
# Generated by Django 4.2.16 on 2024-10-21 13:44
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('abuse', '0041_alter_decision_date'),
]
operations = [
migrations.AlterModelTable(
name='cinderdecision',
table='abuse_cinderdecision',
),
]

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

@ -0,0 +1,22 @@
# Generated by Django 4.2.16 on 2024-10-21 13:48
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('addons', '0052_auto_20240927_1810'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('bandwagon', '0008_alter_collection_options_alter_collection_managers_and_more'),
('ratings', '0011_remove_rating_one_review_per_user_and_more'),
('abuse', '0042_alter_cinderdecision_table'),
]
operations = [
migrations.RenameModel(
old_name='CinderDecision',
new_name='ContentDecision',
),
]

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

@ -5,7 +5,9 @@ from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models import Exists, OuterRef, Q
from django.db.transaction import atomic
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from olympia import amo
from olympia.addons.models import Addon
@ -23,6 +25,23 @@ from olympia.ratings.models import Rating
from olympia.users.models import UserProfile
from olympia.versions.models import Version, VersionReviewerFlags
from .actions import (
ContentActionAlreadyRemoved,
ContentActionApproveInitialDecision,
ContentActionApproveNoAction,
ContentActionBanUser,
ContentActionDeleteCollection,
ContentActionDeleteRating,
ContentActionDisableAddon,
ContentActionEscalateAddon,
ContentActionIgnore,
ContentActionNotImplemented,
ContentActionOverrideApprove,
ContentActionRejectVersion,
ContentActionRejectVersionDelayed,
ContentActionTargetAppealApprove,
ContentActionTargetAppealRemovalAffirmation,
)
from .cinder import (
CinderAddon,
CinderAddonHandledByReviewers,
@ -32,23 +51,6 @@ from .cinder import (
CinderUnauthenticatedReporter,
CinderUser,
)
from .utils import (
CinderActionAlreadyRemoved,
CinderActionApproveInitialDecision,
CinderActionApproveNoAction,
CinderActionBanUser,
CinderActionDeleteCollection,
CinderActionDeleteRating,
CinderActionDisableAddon,
CinderActionEscalateAddon,
CinderActionIgnore,
CinderActionNotImplemented,
CinderActionOverrideApprove,
CinderActionRejectVersion,
CinderActionRejectVersionDelayed,
CinderActionTargetAppealApprove,
CinderActionTargetAppealRemovalAffirmation,
)
class CinderJobQuerySet(BaseQuerySet):
@ -81,7 +83,7 @@ class CinderJob(ModelBase):
to=Addon, blank=True, null=True, on_delete=models.deletion.SET_NULL
)
decision = models.OneToOneField(
to='abuse.CinderDecision',
to='abuse.ContentDecision',
null=True,
on_delete=models.SET_NULL,
related_name='cinder_job',
@ -157,7 +159,7 @@ class CinderJob(ModelBase):
@classmethod
def handle_already_removed(cls, abuse_report):
decision = CinderDecision(
decision = ContentDecision(
addon=abuse_report.addon,
rating=abuse_report.rating,
collection=abuse_report.collection,
@ -270,7 +272,7 @@ class CinderJob(ModelBase):
new_job.target_addon.update_all_due_dates()
# Update our fks to connected objects
AbuseReport.objects.filter(cinder_job=self).update(cinder_job=new_job)
CinderDecision.objects.filter(appeal_job=self).update(appeal_job=new_job)
ContentDecision.objects.filter(appeal_job=self).update(appeal_job=new_job)
self.update(forwarded_to_job=new_job)
def process_decision(
@ -284,11 +286,11 @@ class CinderJob(ModelBase):
"""This is called for cinder originated decisions.
See resolve_job for reviewer tools originated decisions."""
overridden_action = getattr(self.decision, 'action', None)
# We need either an AbuseReport or CinderDecision for the target props
# We need either an AbuseReport or ContentDecision for the target props
abuse_report_or_decision = (
self.appealed_decisions.first() or self.abusereport_set.first()
)
cinder_decision, _ = CinderDecision.objects.update_or_create(
cinder_decision, _ = ContentDecision.objects.update_or_create(
cinder_job=self,
defaults={
'addon': (
@ -302,7 +304,7 @@ class CinderJob(ModelBase):
'cinder_id': decision_cinder_id,
'action': decision_action,
'notes': decision_notes[
: CinderDecision._meta.get_field('notes').max_length
: ContentDecision._meta.get_field('notes').max_length
],
},
)
@ -327,7 +329,7 @@ class CinderJob(ModelBase):
resolved_in_reviewer_tools=self.resolvable_in_reviewer_tools,
)
cinder_decision = self.decision or CinderDecision(
cinder_decision = self.decision or ContentDecision(
addon=abuse_report_or_decision.addon,
rating=abuse_report_or_decision.rating,
collection=abuse_report_or_decision.collection,
@ -908,7 +910,7 @@ class CinderPolicy(ModelBase):
verbose_name_plural = 'Cinder Policies'
class CinderDecision(ModelBase):
class ContentDecision(ModelBase):
action = models.PositiveSmallIntegerField(choices=DECISION_ACTIONS.choices)
cinder_id = models.CharField(max_length=36, default=None, null=True, unique=True)
action_date = models.DateTimeField(null=True, db_column='date')
@ -928,6 +930,7 @@ class CinderDecision(ModelBase):
collection = models.ForeignKey(Collection, null=True, on_delete=models.SET_NULL)
class Meta:
db_table = 'abuse_cinderdecision'
constraints = [
models.CheckConstraint(
name='just_one_of_addon_user_rating_collection_must_be_set',
@ -998,24 +1001,24 @@ class CinderDecision(ModelBase):
@classmethod
def get_action_helper_class(cls, decision_action):
return {
DECISION_ACTIONS.AMO_BAN_USER: CinderActionBanUser,
DECISION_ACTIONS.AMO_DISABLE_ADDON: CinderActionDisableAddon,
DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON: CinderActionRejectVersion,
DECISION_ACTIONS.AMO_BAN_USER: ContentActionBanUser,
DECISION_ACTIONS.AMO_DISABLE_ADDON: ContentActionDisableAddon,
DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON: ContentActionRejectVersion,
DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON: (
CinderActionRejectVersionDelayed
ContentActionRejectVersionDelayed
),
DECISION_ACTIONS.AMO_ESCALATE_ADDON: CinderActionEscalateAddon,
DECISION_ACTIONS.AMO_DELETE_COLLECTION: CinderActionDeleteCollection,
DECISION_ACTIONS.AMO_DELETE_RATING: CinderActionDeleteRating,
DECISION_ACTIONS.AMO_APPROVE: CinderActionApproveNoAction,
DECISION_ACTIONS.AMO_APPROVE_VERSION: CinderActionApproveInitialDecision,
DECISION_ACTIONS.AMO_IGNORE: CinderActionIgnore,
DECISION_ACTIONS.AMO_CLOSED_NO_ACTION: CinderActionAlreadyRemoved,
}.get(decision_action, CinderActionNotImplemented)
DECISION_ACTIONS.AMO_ESCALATE_ADDON: ContentActionEscalateAddon,
DECISION_ACTIONS.AMO_DELETE_COLLECTION: ContentActionDeleteCollection,
DECISION_ACTIONS.AMO_DELETE_RATING: ContentActionDeleteRating,
DECISION_ACTIONS.AMO_APPROVE: ContentActionApproveNoAction,
DECISION_ACTIONS.AMO_APPROVE_VERSION: ContentActionApproveInitialDecision,
DECISION_ACTIONS.AMO_IGNORE: ContentActionIgnore,
DECISION_ACTIONS.AMO_CLOSED_NO_ACTION: ContentActionAlreadyRemoved,
}.get(decision_action, ContentActionNotImplemented)
def get_action_helper(self, *, overridden_action=None, appealed_action=None):
# Base case when it's a new decision, that wasn't an appeal
CinderActionClass = self.get_action_helper_class(self.action)
ContentActionClass = self.get_action_helper_class(self.action)
skip_reporter_notify = False
if appealed_action:
@ -1023,22 +1026,22 @@ class CinderDecision(ModelBase):
if appealed_action in DECISION_ACTIONS.REMOVING:
if self.action in DECISION_ACTIONS.APPROVING:
# i.e. we've reversed our target takedown
CinderActionClass = CinderActionTargetAppealApprove
ContentActionClass = ContentActionTargetAppealApprove
elif self.action == appealed_action:
# i.e. we've not reversed our target takedown
CinderActionClass = CinderActionTargetAppealRemovalAffirmation
# (a reporter appeal doesn't need any alternate CinderAction class)
ContentActionClass = ContentActionTargetAppealRemovalAffirmation
# (a reporter appeal doesn't need any alternate ContentAction class)
elif overridden_action in DECISION_ACTIONS.REMOVING:
# override on a decision that was a takedown before, and wasn't an appeal
if self.action in DECISION_ACTIONS.APPROVING:
CinderActionClass = CinderActionOverrideApprove
ContentActionClass = ContentActionOverrideApprove
if self.action == overridden_action:
# For an override that is still a takedown we can send the same emails
# to the target; but we don't want to notify the reporter again.
skip_reporter_notify = True
cinder_action = CinderActionClass(decision=self)
cinder_action = ContentActionClass(decision=self)
if skip_reporter_notify:
cinder_action.reporter_template_path = None
cinder_action.reporter_appeal_template_path = None
@ -1106,7 +1109,7 @@ class CinderDecision(ModelBase):
if is_reporter:
if not abuse_report:
raise ImproperlyConfigured(
'CinderDecision.appeal() called with is_reporter=True without an '
'ContentDecision.appeal() called with is_reporter=True without an '
'abuse_report'
)
if not user:
@ -1123,7 +1126,8 @@ class CinderDecision(ModelBase):
# If we still don't have a user at this point there is nothing
# we can do, something was wrong in the call chain.
raise ImproperlyConfigured(
'CinderDecision.appeal() called with is_reporter=False without user'
'ContentDecision.appeal() called with is_reporter=False without '
'user'
)
if user:
appealer_entity = CinderUser(user)
@ -1264,11 +1268,38 @@ class CinderDecision(ModelBase):
else:
action_helper.hold_action()
def get_target_review_url(self):
return (
reverse('reviewers.review', args=(self.target.id,))
if isinstance(self.target, Addon)
else ''
)
def get_target_type(self):
match self.target:
case target if isinstance(target, Addon):
return target.get_type_display()
case target if isinstance(target, UserProfile):
return _('User profile')
case target if isinstance(target, Collection):
return _('Collection')
case target if isinstance(target, Rating):
return _('Rating')
case target:
return target.__class__.__name__
def get_target_name(self):
return str(
_('"{}" for {}').format(self.target, self.target.addon.name)
if isinstance(self.target, Rating)
else getattr(self.target, 'name', self.target)
)
class CinderAppeal(ModelBase):
text = models.TextField(blank=False, help_text='The content of the appeal.')
decision = models.ForeignKey(
to=CinderDecision, on_delete=models.CASCADE, related_name='appeals'
to=ContentDecision, on_delete=models.CASCADE, related_name='appeals'
)
reporter_report = models.OneToOneField(
to=AbuseReport, on_delete=models.CASCADE, null=True

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

@ -19,9 +19,9 @@ from olympia.users.models import UserProfile
from .models import (
AbuseReport,
AbuseReportManager,
CinderDecision,
CinderJob,
CinderPolicy,
ContentDecision,
)
@ -91,7 +91,7 @@ def appeal_to_cinder(
*, decision_cinder_id, abuse_report_id, appeal_text, user_id, is_reporter
):
try:
decision = CinderDecision.objects.get(cinder_id=decision_cinder_id)
decision = ContentDecision.objects.get(cinder_id=decision_cinder_id)
if abuse_report_id:
abuse_report = AbuseReport.objects.get(id=abuse_report_id)
else:
@ -137,7 +137,7 @@ def notify_addon_decision_to_cinder(*, log_entry_id, addon_id=None):
try:
log_entry = ActivityLog.objects.get(id=log_entry_id)
addon = Addon.unfiltered.get(id=addon_id)
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
decision.notify_reviewer_decision(
log_entry=log_entry,
entity_helper=CinderJob.get_entity_helper(
@ -190,7 +190,7 @@ def sync_cinder_policies():
data = response.json()
sync_policies(data)
CinderPolicy.objects.exclude(
Q(cinderdecision__id__gte=0)
Q(contentdecision__id__gte=0)
| Q(reviewactionreason__id__gte=0)
| Q(modified__gte=now)
).delete()

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

@ -1 +0,0 @@
{% include "abuse/emails/CinderActionTargetAppealApprove.txt" with is_override=True %}

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

@ -0,0 +1 @@
{% include "abuse/emails/ContentActionTargetAppealApprove.txt" with is_override=True %}

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

@ -11,7 +11,7 @@ import waffle
from waffle.testutils import override_switch
from olympia import amo, core
from olympia.abuse.models import AbuseReport, CinderDecision, CinderJob
from olympia.abuse.models import AbuseReport, CinderJob, ContentDecision
from olympia.activity.models import ActivityLog
from olympia.addons.models import Addon, Preview
from olympia.amo.tests import (
@ -1225,7 +1225,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
addon.current_version.file.update(is_signed=True)
job = CinderJob.objects.create(job_id='1234-xyz')
job.appealed_decisions.add(
CinderDecision.objects.create(
ContentDecision.objects.create(
addon=addon,
cinder_id='1234-decision',
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,
@ -1246,7 +1246,7 @@ class TestCinderAddonHandledByReviewers(TestCinderAddon):
job = CinderJob.objects.create(job_id='1234-xyz')
CinderJob.objects.create(forwarded_to_job=job)
job.appealed_decisions.add(
CinderDecision.objects.create(
ContentDecision.objects.create(
addon=addon,
cinder_id='1234-decision',
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,

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

@ -7,7 +7,7 @@ import responses
from olympia.amo.tests import addon_factory
from olympia.constants.abuse import DECISION_ACTIONS
from ..models import AbuseReport, CinderDecision, CinderJob
from ..models import AbuseReport, CinderJob, ContentDecision
@pytest.mark.django_db
@ -15,18 +15,18 @@ def test_backfill_cinder_escalations():
addon = addon_factory()
job_with_reports = CinderJob.objects.create(
job_id='1',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
),
)
abuse = AbuseReport.objects.create(guid=addon.guid, cinder_job=job_with_reports)
appeal_job = CinderJob.objects.create(
job_id='2',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
),
)
appealled_decision = CinderDecision.objects.create(
appealled_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon, appeal_job=appeal_job
)
@ -34,24 +34,24 @@ def test_backfill_cinder_escalations():
# decision that wasn't an escalation (or isn't any longer)
CinderJob.objects.create(
job_id='3',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
),
)
# decision without an associated cinder job (shouldn't occur, but its handled)
CinderDecision.objects.create(
ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
)
# decision that already has a forwarded job created, so we don't need to backfill
CinderJob.objects.create(
job_id='4',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon
),
forwarded_to_job=CinderJob.objects.create(job_id='5'),
)
assert CinderJob.objects.count() == 5
assert CinderDecision.objects.count() == 6
assert ContentDecision.objects.count() == 6
responses.add(
responses.POST,
f'{settings.CINDER_SERVER_URL}create_report',
@ -67,7 +67,7 @@ def test_backfill_cinder_escalations():
call_command('backfill_cinder_escalations')
assert CinderJob.objects.count() == 7
assert CinderDecision.objects.count() == 6
assert ContentDecision.objects.count() == 6
new_job_with_reports = job_with_reports.reload().forwarded_to_job
assert new_job_with_reports

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

@ -9,6 +9,7 @@ from django.core import mail
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.files.base import ContentFile
from django.db.utils import IntegrityError
from django.urls import reverse
import pytest
import responses
@ -37,6 +38,22 @@ from olympia.ratings.models import Rating
from olympia.reviewers.models import NeedsHumanReview
from olympia.versions.models import Version, VersionReviewerFlags
from ..actions import (
ContentActionAlreadyRemoved,
ContentActionApproveInitialDecision,
ContentActionApproveNoAction,
ContentActionBanUser,
ContentActionDeleteCollection,
ContentActionDeleteRating,
ContentActionDisableAddon,
ContentActionEscalateAddon,
ContentActionIgnore,
ContentActionOverrideApprove,
ContentActionRejectVersion,
ContentActionRejectVersionDelayed,
ContentActionTargetAppealApprove,
ContentActionTargetAppealRemovalAffirmation,
)
from ..cinder import (
CinderAddon,
CinderAddonHandledByReviewers,
@ -49,25 +66,9 @@ from ..models import (
AbuseReport,
AbuseReportManager,
CinderAppeal,
CinderDecision,
CinderJob,
CinderPolicy,
)
from ..utils import (
CinderActionAlreadyRemoved,
CinderActionApproveInitialDecision,
CinderActionApproveNoAction,
CinderActionBanUser,
CinderActionDeleteCollection,
CinderActionDeleteRating,
CinderActionDisableAddon,
CinderActionEscalateAddon,
CinderActionIgnore,
CinderActionOverrideApprove,
CinderActionRejectVersion,
CinderActionRejectVersionDelayed,
CinderActionTargetAppealApprove,
CinderActionTargetAppealRemovalAffirmation,
ContentDecision,
)
@ -710,7 +711,7 @@ class TestCinderJobManager(TestCase):
appeal_job = CinderJob.objects.create(job_id='appeal', target_addon=addon)
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
appeal_job=appeal_job,
addon=addon,
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
@ -725,7 +726,7 @@ class TestCinderJobManager(TestCase):
addon = addon_factory()
CinderJob.objects.create(
job_id='2',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon
),
)
@ -741,7 +742,7 @@ class TestCinderJobManager(TestCase):
)
job = CinderJob.objects.create(
job_id=2,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon_factory()
),
)
@ -793,7 +794,7 @@ class TestCinderJob(TestCase):
# case when there is already a decision
cinder_job.update(
target_addon=None,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
),
)
@ -979,7 +980,7 @@ class TestCinderJob(TestCase):
assert len(mail.outbox) == 0
addon = Addon.objects.get()
CinderJob.objects.get().update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON, addon=addon
)
)
@ -1029,7 +1030,7 @@ class TestCinderJob(TestCase):
def test_handle_job_recreated(self):
addon = addon_factory()
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
)
job = CinderJob.objects.create(
@ -1057,7 +1058,7 @@ class TestCinderJob(TestCase):
job_id='9999', target_addon=addon, forwarded_to_job=exisiting_escalation_job
)
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
)
old_job = CinderJob.objects.create(
@ -1100,7 +1101,7 @@ class TestCinderJob(TestCase):
guid=addon.guid, cinder_job=exisiting_report_job
)
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
)
old_job = CinderJob.objects.create(
@ -1138,7 +1139,7 @@ class TestCinderJob(TestCase):
def test_handle_job_recreated_appeal(self):
addon = addon_factory()
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
)
appeal_job = CinderJob.objects.create(
@ -1147,7 +1148,7 @@ class TestCinderJob(TestCase):
original_job = CinderJob.objects.create(
job_id='0000',
target_addon=addon,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE,
addon=addon,
notes='its okay',
@ -1178,9 +1179,9 @@ class TestCinderJob(TestCase):
policy_b = CinderPolicy.objects.create(uuid='678-90', name='bbb', text='BBB')
with mock.patch.object(
CinderActionBanUser, 'process_action'
ContentActionBanUser, 'process_action'
) as action_mock, mock.patch.object(
CinderActionBanUser, 'notify_owners'
ContentActionBanUser, 'notify_owners'
) as notify_mock:
action_mock.return_value = (True, mock.Mock(id=999))
cinder_job.process_decision(
@ -1209,9 +1210,9 @@ class TestCinderJob(TestCase):
)
with mock.patch.object(
CinderActionBanUser, 'process_action'
ContentActionBanUser, 'process_action'
) as action_mock, mock.patch.object(
CinderActionBanUser, 'notify_owners'
ContentActionBanUser, 'notify_owners'
) as notify_mock:
action_mock.return_value = (True, None)
cinder_job.process_decision(
@ -1420,7 +1421,7 @@ class TestCinderJob(TestCase):
)
CinderJob.objects.create(
job_id='998',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
addon=addon, action=DECISION_ACTIONS.AMO_APPROVE, appeal_job=appeal_job
),
)
@ -1490,7 +1491,7 @@ class TestCinderJob(TestCase):
)
CinderJob.objects.create(
job_id='998',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
addon=addon, action=DECISION_ACTIONS.AMO_APPROVE, appeal_job=appeal_job
),
)
@ -1622,7 +1623,7 @@ class TestCinderJob(TestCase):
appeal_job = CinderJob.objects.create(job_id='fake_appeal_job_id')
job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=addon,
appeal_job=appeal_job,
@ -1634,7 +1635,7 @@ class TestCinderJob(TestCase):
appeal_appeal_job = CinderJob.objects.create(job_id='fake_appeal_appeal_job_id')
appeal_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=addon,
appeal_job=appeal_appeal_job,
@ -1664,7 +1665,7 @@ class TestCinderJob(TestCase):
appeal = CinderJob.objects.create(job_id='an appeal job')
job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=addon_factory(),
appeal_job=appeal,
@ -1685,7 +1686,7 @@ class TestCinderJob(TestCase):
job_id='1',
target_addon=addon,
resolvable_in_reviewer_tools=True,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
),
)
@ -1729,7 +1730,7 @@ class TestCinderJob(TestCase):
# unless the other job is closed too
other_forward.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
)
)
@ -1746,7 +1747,7 @@ class TestCinderJob(TestCase):
CinderJob.objects.create(
job_id='5',
target_addon=addon,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon, appeal_job=job
),
)
@ -1759,7 +1760,7 @@ class TestCinderJob(TestCase):
CinderJob.objects.create(
job_id='7',
target_addon=addon,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE,
addon=addon,
appeal_job=other_appeal,
@ -1772,7 +1773,7 @@ class TestCinderJob(TestCase):
# unless the other job is closed too
other_appeal.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
)
)
@ -1782,12 +1783,12 @@ class TestCinderJob(TestCase):
assert not nhr_exists(NeedsHumanReview.REASONS.ADDON_REVIEW_APPEAL)
class TestCinderDecisionCanBeAppealed(TestCase):
class TestContentDecisionCanBeAppealed(TestCase):
def setUp(self):
self.reporter = user_factory()
self.author = user_factory()
self.addon = addon_factory(users=[self.author])
self.decision = CinderDecision.objects.create(
self.decision = ContentDecision.objects.create(
cinder_id='fake_decision_id',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,
@ -1804,7 +1805,7 @@ class TestCinderDecisionCanBeAppealed(TestCase):
assert not self.decision.appealed_decision_already_made()
appeal_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='appeal decision id',
addon=self.addon,
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
@ -1907,7 +1908,7 @@ class TestCinderDecisionCanBeAppealed(TestCase):
)
appeal_job = CinderJob.objects.create(
job_id='fake_appeal_job_id',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='fake_appeal_decision_id',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,
@ -1928,7 +1929,7 @@ class TestCinderDecisionCanBeAppealed(TestCase):
def test_reporter_can_appeal_appealed_decision(self):
appeal_job = CinderJob.objects.create(
job_id='fake_appeal_job_id',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='fake_appeal_decision_id',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,
@ -2028,7 +2029,7 @@ class TestCinderDecisionCanBeAppealed(TestCase):
def test_author_can_appeal_appealed_decision(self):
appeal_job = CinderJob.objects.create(
job_id='fake_appeal_job_id',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='fake_appeal_decision_id',
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=self.addon,
@ -2159,7 +2160,7 @@ class TestCinderPolicy(TestCase):
@override_switch('dsa-abuse-reports-review', active=True)
@override_switch('dsa-appeals-review', active=True)
class TestCinderDecision(TestCase):
class TestContentDecision(TestCase):
def setUp(self):
# It's the webhook's responsibility to do this before calling the
# action. We need it for the ActivityLog creation to work.
@ -2167,7 +2168,7 @@ class TestCinderDecision(TestCase):
set_user(self.task_user)
def test_get_reference_id(self):
decision = CinderDecision()
decision = ContentDecision()
assert decision.get_reference_id() == 'NoClass#None'
assert decision.get_reference_id(short=False) == 'Decision "" for NoClass #None'
@ -2187,7 +2188,7 @@ class TestCinderDecision(TestCase):
def test_target(self):
addon = addon_factory(guid='@lol')
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon
)
assert decision.target == addon
@ -2206,7 +2207,7 @@ class TestCinderDecision(TestCase):
def test_is_third_party_initiated(self):
addon = addon_factory()
current_decision = CinderDecision.objects.create(
current_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon
)
assert not current_decision.is_third_party_initiated
@ -2223,7 +2224,7 @@ class TestCinderDecision(TestCase):
def test_is_third_party_initiated_appeal(self):
addon = addon_factory()
current_decision = CinderDecision.objects.create(
current_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=addon,
)
@ -2232,7 +2233,7 @@ class TestCinderDecision(TestCase):
)
original_job = CinderJob.objects.create(
job_id='456',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon, appeal_job=current_job
),
)
@ -2243,29 +2244,29 @@ class TestCinderDecision(TestCase):
def test_get_action_helper(self):
addon = addon_factory()
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon
)
targets = {
CinderActionBanUser: {'user': user_factory()},
CinderActionDisableAddon: {'addon': addon},
CinderActionRejectVersion: {'addon': addon},
CinderActionRejectVersionDelayed: {'addon': addon},
CinderActionEscalateAddon: {'addon': addon},
CinderActionDeleteCollection: {'collection': collection_factory()},
CinderActionDeleteRating: {
ContentActionBanUser: {'user': user_factory()},
ContentActionDisableAddon: {'addon': addon},
ContentActionRejectVersion: {'addon': addon},
ContentActionRejectVersionDelayed: {'addon': addon},
ContentActionEscalateAddon: {'addon': addon},
ContentActionDeleteCollection: {'collection': collection_factory()},
ContentActionDeleteRating: {
'rating': Rating.objects.create(addon=addon, user=user_factory())
},
CinderActionApproveInitialDecision: {'addon': addon},
CinderActionApproveNoAction: {'addon': addon},
CinderActionOverrideApprove: {'addon': addon},
CinderActionTargetAppealApprove: {'addon': addon},
CinderActionTargetAppealRemovalAffirmation: {'addon': addon},
CinderActionIgnore: {'addon': addon},
CinderActionAlreadyRemoved: {'addon': addon},
ContentActionApproveInitialDecision: {'addon': addon},
ContentActionApproveNoAction: {'addon': addon},
ContentActionOverrideApprove: {'addon': addon},
ContentActionTargetAppealApprove: {'addon': addon},
ContentActionTargetAppealRemovalAffirmation: {'addon': addon},
ContentActionIgnore: {'addon': addon},
ContentActionAlreadyRemoved: {'addon': addon},
}
action_to_class = [
(decision_action, CinderDecision.get_action_helper_class(decision_action))
(decision_action, ContentDecision.get_action_helper_class(decision_action))
for decision_action in DECISION_ACTIONS.values
]
# base cases, where it's a decision without an override or appeal involved
@ -2277,22 +2278,22 @@ class TestCinderDecision(TestCase):
for action in DECISION_ACTIONS.REMOVING.values:
# add appeal success cases
action_existing_to_class[(DECISION_ACTIONS.AMO_APPROVE, None, action)] = (
CinderActionTargetAppealApprove
ContentActionTargetAppealApprove
)
action_existing_to_class[
(DECISION_ACTIONS.AMO_APPROVE_VERSION, None, action)
] = CinderActionTargetAppealApprove
] = ContentActionTargetAppealApprove
# add appeal denial cases
action_existing_to_class[(action, None, action)] = (
CinderActionTargetAppealRemovalAffirmation
ContentActionTargetAppealRemovalAffirmation
)
# add override from takedown to approve cases
action_existing_to_class[(DECISION_ACTIONS.AMO_APPROVE, action, None)] = (
CinderActionOverrideApprove
ContentActionOverrideApprove
)
action_existing_to_class[
(DECISION_ACTIONS.AMO_APPROVE_VERSION, action, None)
] = CinderActionOverrideApprove
] = ContentActionOverrideApprove
for (
new_action,
@ -2321,7 +2322,7 @@ class TestCinderDecision(TestCase):
)
action_existing_to_class_no_reporter_emails = {
(action, action): CinderDecision.get_action_helper_class(action)
(action, action): ContentDecision.get_action_helper_class(action)
for action in DECISION_ACTIONS.REMOVING.values
}
for (
@ -2358,7 +2359,7 @@ class TestCinderDecision(TestCase):
cinder_job=CinderJob.objects.create(
target_addon=addon,
resolvable_in_reviewer_tools=resolvable_in_reviewer_tools,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
@ -2420,7 +2421,7 @@ class TestCinderDecision(TestCase):
reason=AbuseReport.REASONS.ILLEGAL,
reporter=user_factory(),
cinder_job=CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
@ -2458,7 +2459,7 @@ class TestCinderDecision(TestCase):
reason=AbuseReport.REASONS.ILLEGAL,
reporter=user_factory(),
cinder_job=CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
# This (target is an add-on, decision is a user ban) shouldn't
@ -2502,7 +2503,7 @@ class TestCinderDecision(TestCase):
reason=AbuseReport.REASONS.ILLEGAL,
reporter=user_factory(),
cinder_job=CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_BAN_USER,
@ -2543,7 +2544,7 @@ class TestCinderDecision(TestCase):
abuse_report.update(
cinder_job=CinderJob.objects.create(
target_addon=addon,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_APPROVE,
@ -2587,7 +2588,7 @@ class TestCinderDecision(TestCase):
abuse_report.update(
cinder_job=CinderJob.objects.create(
target_addon=addon,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_APPROVE,
@ -2630,7 +2631,7 @@ class TestCinderDecision(TestCase):
def test_appeal_improperly_configured_reporter(self):
cinder_job = CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_APPROVE,
@ -2653,7 +2654,7 @@ class TestCinderDecision(TestCase):
reporter=user_factory(),
)
cinder_job = CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-lost',
action_date=self.days_ago(179),
action=DECISION_ACTIONS.AMO_APPROVE,
@ -2734,7 +2735,7 @@ class TestCinderDecision(TestCase):
]
self.assertCloseToNow(decision.action_date)
assert list(decision.policies.all()) == policies
assert CinderDecision.objects.count() == 1
assert ContentDecision.objects.count() == 1
assert decision.id
elif expect_create_job_decision_call:
assert create_decision_response.call_count == 0
@ -2749,12 +2750,12 @@ class TestCinderDecision(TestCase):
]
self.assertCloseToNow(decision.action_date)
assert list(decision.policies.all()) == policies
assert CinderDecision.objects.count() == 1
assert ContentDecision.objects.count() == 1
assert decision.id
else:
assert create_decision_response.call_count == 0
assert create_job_decision_response.call_count == 0
assert CinderPolicy.cinderdecision_set.through.objects.count() == 0
assert CinderPolicy.contentdecision_set.through.objects.count() == 0
assert not decision.id
if expect_email:
assert len(mail.outbox) == 1
@ -2782,7 +2783,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_new_decision(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
self._test_notify_reviewer_decision(
decision,
amo.LOG.REJECT_VERSION,
@ -2796,7 +2797,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_updated_decision(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
addon=addon, action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON
)
self._test_notify_reviewer_decision(
@ -2814,7 +2815,7 @@ class TestCinderDecision(TestCase):
addon = addon_factory(
users=[addon_developer], version_kw={'channel': amo.CHANNEL_UNLISTED}
)
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
self._test_notify_reviewer_decision(
decision,
amo.LOG.REJECT_VERSION,
@ -2831,7 +2832,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_new_decision_no_email_to_owner(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
decision.cinder_job = CinderJob.objects.create(job_id='1234')
self._test_notify_reviewer_decision(
decision,
@ -2845,7 +2846,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_updated_decision_no_email_to_owner(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
addon=addon, action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON
)
decision.cinder_job = CinderJob.objects.create(job_id='1234')
@ -2861,7 +2862,7 @@ class TestCinderDecision(TestCase):
def test_no_create_decision_for_approve_without_a_job(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
assert not hasattr(decision, 'cinder_job')
self._test_notify_reviewer_decision(
decision,
@ -2875,7 +2876,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_auto_approve_email_for_non_human_review(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
self._test_notify_reviewer_decision(
decision,
amo.LOG.APPROVE_VERSION,
@ -2890,7 +2891,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_auto_approve_email_for_human_review(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
self._test_notify_reviewer_decision(
decision,
amo.LOG.APPROVE_VERSION,
@ -2913,7 +2914,7 @@ class TestCinderDecision(TestCase):
)
with self.assertRaises(ImproperlyConfigured):
CinderDecision().notify_reviewer_decision(
ContentDecision().notify_reviewer_decision(
log_entry=log_entry, entity_helper=None
)
@ -2928,14 +2929,14 @@ class TestCinderDecision(TestCase):
)
with self.assertRaises(ImproperlyConfigured):
CinderDecision().notify_reviewer_decision(
ContentDecision().notify_reviewer_decision(
log_entry=log_entry, entity_helper=None
)
def test_notify_reviewer_decision_rejection_blocking(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
self._test_notify_reviewer_decision(
decision,
amo.LOG.REJECT_VERSION,
@ -2963,7 +2964,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_rejection_blocking_addon_being_disabled(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer])
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
self._test_notify_reviewer_decision(
decision,
amo.LOG.REJECT_VERSION,
@ -2991,7 +2992,7 @@ class TestCinderDecision(TestCase):
def test_notify_reviewer_decision_rejection_addon_already_disabled(self):
addon_developer = user_factory()
addon = addon_factory(users=[addon_developer], status=amo.STATUS_DISABLED)
decision = CinderDecision(addon=addon)
decision = ContentDecision(addon=addon)
self._test_notify_reviewer_decision(
decision,
amo.LOG.REJECT_VERSION,
@ -3014,7 +3015,7 @@ class TestCinderDecision(TestCase):
def test_process_action_ban_user_held(self):
user = user_factory(email='superstarops@mozilla.com')
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
user=user, action=DECISION_ACTIONS.AMO_BAN_USER
)
assert decision.action_date is None
@ -3030,7 +3031,7 @@ class TestCinderDecision(TestCase):
def test_process_action_ban_user(self):
user = user_factory()
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
user=user, action=DECISION_ACTIONS.AMO_BAN_USER
)
assert decision.action_date is None
@ -3044,7 +3045,7 @@ class TestCinderDecision(TestCase):
def test_process_action_disable_addon_held(self):
addon = addon_factory()
self.make_addon_promoted(addon, RECOMMENDED, approve_version=True)
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
addon=addon, action=DECISION_ACTIONS.AMO_DISABLE_ADDON
)
assert decision.action_date is None
@ -3060,7 +3061,7 @@ class TestCinderDecision(TestCase):
def test_process_action_disable_addon(self):
addon = addon_factory()
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
addon=addon, action=DECISION_ACTIONS.AMO_DISABLE_ADDON
)
assert decision.action_date is None
@ -3071,7 +3072,7 @@ class TestCinderDecision(TestCase):
def test_process_action_delete_collection_held(self):
collection = collection_factory(author=self.task_user)
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
collection=collection, action=DECISION_ACTIONS.AMO_DELETE_COLLECTION
)
assert decision.action_date is None
@ -3087,7 +3088,7 @@ class TestCinderDecision(TestCase):
def test_process_action_delete_collection(self):
collection = collection_factory(author=user_factory())
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
collection=collection, action=DECISION_ACTIONS.AMO_DELETE_COLLECTION
)
assert decision.action_date is None
@ -3110,7 +3111,7 @@ class TestCinderDecision(TestCase):
addon=addon, user=user_factory(), body='sdsd'
),
)
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
rating=rating, action=DECISION_ACTIONS.AMO_DELETE_RATING
)
self.make_addon_promoted(rating.addon, RECOMMENDED, approve_version=True)
@ -3127,7 +3128,7 @@ class TestCinderDecision(TestCase):
def test_process_action_delete_rating(self):
rating = Rating.objects.create(addon=addon_factory(), user=user_factory())
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
rating=rating, action=DECISION_ACTIONS.AMO_DELETE_RATING
)
assert decision.action_date is None
@ -3136,6 +3137,59 @@ class TestCinderDecision(TestCase):
assert rating.reload().deleted
assert ActivityLog.objects.filter(action=amo.LOG.DELETE_RATING.id).count() == 1
def test_get_target_review_url(self):
addon = addon_factory()
decision = ContentDecision.objects.create(
addon=addon, action=DECISION_ACTIONS.AMO_DISABLE_ADDON
)
assert decision.get_target_review_url() == reverse(
'reviewers.review', args=(addon.id,)
)
decision.update(addon=None, user=user_factory())
assert decision.get_target_review_url() == ''
def test_get_target_type(self):
decision = ContentDecision.objects.create(
addon=addon_factory(), action=DECISION_ACTIONS.AMO_DISABLE_ADDON
)
assert decision.get_target_type() == 'Extension'
decision.update(addon=None, user=user_factory())
assert decision.get_target_type() == 'User profile'
decision.update(user=None, collection=collection_factory())
assert decision.get_target_type() == 'Collection'
decision.update(
collection=None,
rating=Rating.objects.create(addon=addon_factory(), user=user_factory()),
)
assert decision.get_target_type() == 'Rating'
def test_get_target_name(self):
decision = ContentDecision.objects.create(
addon=addon_factory(), action=DECISION_ACTIONS.AMO_DISABLE_ADDON
)
assert decision.get_target_name() == str(decision.addon.name)
decision.update(addon=None, user=user_factory())
assert decision.get_target_name() == decision.user.name
decision.update(user=None, collection=collection_factory())
assert decision.get_target_name() == decision.collection.name
decision.update(
collection=None,
rating=Rating.objects.create(
addon=addon_factory(), user=user_factory(), body='something'
),
)
assert (
decision.get_target_name()
== f'"something" for {decision.rating.addon.name}'
)
@pytest.mark.django_db
@pytest.mark.parametrize(

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

@ -25,7 +25,7 @@ from olympia.reviewers.models import NeedsHumanReview, ReviewActionReason, Usage
from olympia.versions.models import Version
from olympia.zadmin.models import set_config
from ..models import AbuseReport, CinderDecision, CinderJob, CinderPolicy
from ..models import AbuseReport, CinderJob, CinderPolicy, ContentDecision
from ..tasks import (
appeal_to_cinder,
handle_escalate_action,
@ -430,7 +430,7 @@ def test_addon_report_to_cinder_different_locale():
def test_addon_appeal_to_cinder_reporter(statsd_incr_mock):
addon = addon_factory()
cinder_job = CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-abc',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=addon,
@ -492,7 +492,7 @@ def test_addon_appeal_to_cinder_reporter(statsd_incr_mock):
def test_addon_appeal_to_cinder_reporter_exception(statsd_incr_mock):
addon = addon_factory()
cinder_job = CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-abc',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=addon,
@ -534,7 +534,7 @@ def test_addon_appeal_to_cinder_authenticated_reporter():
user = user_factory(fxa_id='fake-fxa-id')
addon = addon_factory()
cinder_job = CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='4815162342-abc',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=addon,
@ -592,7 +592,7 @@ def test_addon_appeal_to_cinder_authenticated_reporter():
def test_addon_appeal_to_cinder_authenticated_author():
user = user_factory(fxa_id='fake-fxa-id')
addon = addon_factory(users=[user])
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
cinder_id='4815162342-abc',
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=addon,
@ -749,7 +749,7 @@ def test_notify_addon_decision_to_cinder(statsd_incr_mock):
assert request_body['policy_uuids'] == ['12345678']
assert request_body['reasoning'] == 'some review text'
assert request_body['entity']['id'] == str(addon.id)
assert CinderDecision.objects.get().action == DECISION_ACTIONS.AMO_DISABLE_ADDON
assert ContentDecision.objects.get().action == DECISION_ACTIONS.AMO_DISABLE_ADDON
assert statsd_incr_mock.call_count == 1
assert statsd_incr_mock.call_args[0] == (
@ -899,7 +899,7 @@ class TestSyncCinderPolicies(TestCase):
text='Old policy, but with linked decision',
)
old_policy_with_decision.update(modified=days_ago(1))
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=addon_factory()
)
decision.policies.add(old_policy_with_decision)
@ -1015,7 +1015,7 @@ class TestSyncCinderPolicies(TestCase):
@pytest.mark.django_db
def test_handle_escalate_action():
addon = addon_factory()
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON, addon=addon, notes='blah'
)
job = CinderJob.objects.create(job_id='1234', target_addon=addon, decision=decision)

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

@ -15,27 +15,27 @@ from olympia.constants.promoted import RECOMMENDED
from olympia.core import set_user
from olympia.ratings.models import Rating
from ..models import AbuseReport, CinderAppeal, CinderDecision, CinderJob, CinderPolicy
from ..utils import (
CinderActionApproveInitialDecision,
CinderActionApproveNoAction,
CinderActionBanUser,
CinderActionDeleteCollection,
CinderActionDeleteRating,
CinderActionDisableAddon,
CinderActionIgnore,
CinderActionOverrideApprove,
CinderActionRejectVersion,
CinderActionRejectVersionDelayed,
CinderActionTargetAppealApprove,
CinderActionTargetAppealRemovalAffirmation,
from ..actions import (
ContentActionApproveInitialDecision,
ContentActionApproveNoAction,
ContentActionBanUser,
ContentActionDeleteCollection,
ContentActionDeleteRating,
ContentActionDisableAddon,
ContentActionIgnore,
ContentActionOverrideApprove,
ContentActionRejectVersion,
ContentActionRejectVersionDelayed,
ContentActionTargetAppealApprove,
ContentActionTargetAppealRemovalAffirmation,
)
from ..models import AbuseReport, CinderAppeal, CinderJob, CinderPolicy, ContentDecision
class BaseTestCinderAction:
class BaseTestContentAction:
def setUp(self):
addon = addon_factory()
self.decision = CinderDecision.objects.create(
self.decision = ContentDecision.objects.create(
cinder_id='ab89',
action=DECISION_ACTIONS.AMO_APPROVE,
notes="extra note's",
@ -218,21 +218,21 @@ class BaseTestCinderAction:
assert ''' not in mail_item.body
assert self.decision.notes in mail_item.body
def _test_approve_appeal_or_override(CinderActionClass):
def _test_approve_appeal_or_override(ContentActionClass):
raise NotImplementedError
def test_approve_appeal_success(self):
self._test_approve_appeal_or_override(CinderActionTargetAppealApprove)
self._test_approve_appeal_or_override(ContentActionTargetAppealApprove)
assert 'After reviewing your appeal' in mail.outbox[0].body
def test_approve_override(self):
self._test_approve_appeal_or_override(CinderActionOverrideApprove)
self._test_approve_appeal_or_override(ContentActionOverrideApprove)
assert 'After reviewing your appeal' not in mail.outbox[0].body
def _test_reporter_no_action_taken(
self,
*,
ActionClass=CinderActionApproveNoAction,
ActionClass=ContentActionApproveNoAction,
action=DECISION_ACTIONS.AMO_APPROVE,
):
raise NotImplementedError
@ -245,7 +245,7 @@ class BaseTestCinderAction:
def test_reporter_appeal_approve(self):
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
addon=self.decision.addon,
user=self.decision.user,
rating=self.decision.rating,
@ -267,7 +267,7 @@ class BaseTestCinderAction:
def test_owner_content_approve_report_email(self):
# This isn't called by cinder actions, but is triggered by reviewer actions
subject = self._test_reporter_no_action_taken(
ActionClass=CinderActionApproveInitialDecision
ActionClass=ContentActionApproveInitialDecision
)
assert len(mail.outbox) == 3
self._test_reporter_content_approve_email(subject)
@ -287,7 +287,7 @@ class BaseTestCinderAction:
def test_reporter_ignore_invalid_report(self):
self.decision.policies.first().update()
subject = self._test_reporter_no_action_taken(
ActionClass=CinderActionIgnore, action=DECISION_ACTIONS.AMO_IGNORE
ActionClass=ContentActionIgnore, action=DECISION_ACTIONS.AMO_IGNORE
)
assert len(mail.outbox) == 2
assert mail.outbox[0].to == ['email@domain.com']
@ -315,7 +315,7 @@ class BaseTestCinderAction:
action.notify_owners()
assert unsafe_str in mail.outbox[0].body
action = CinderActionApproveNoAction(self.decision)
action = ContentActionApproveNoAction(self.decision)
mail.outbox.clear()
action.notify_reporters(
reporter_abuse_reports=[self.abuse_report_auth], is_appeal=True
@ -323,8 +323,8 @@ class BaseTestCinderAction:
assert unsafe_str in mail.outbox[0].body
class TestCinderActionUser(BaseTestCinderAction, TestCase):
ActionClass = CinderActionBanUser
class TestContentActionUser(BaseTestContentAction, TestCase):
ActionClass = ContentActionBanUser
def setUp(self):
super().setUp()
@ -363,7 +363,7 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
def test_ban_user_after_reporter_appeal(self):
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
user=self.user, action=DECISION_ACTIONS.AMO_APPROVE
),
)
@ -380,7 +380,7 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
def _test_reporter_no_action_taken(
self,
*,
ActionClass=CinderActionApproveNoAction,
ActionClass=ContentActionApproveNoAction,
action=DECISION_ACTIONS.AMO_APPROVE,
):
self.decision.update(action=action)
@ -395,10 +395,10 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
action.notify_owners()
return f'Mozilla Add-ons: {self.user.name}'
def _test_approve_appeal_or_override(self, CinderActionClass):
def _test_approve_appeal_or_override(self, ContentActionClass):
self.decision.update(action=DECISION_ACTIONS.AMO_APPROVE)
self.user.update(banned=self.days_ago(1), deleted=True)
action = CinderActionClass(self.decision)
action = ContentActionClass(self.decision)
assert action.process_action() is None
self.user.reload()
@ -415,7 +415,7 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
def test_target_appeal_decline(self):
self.user.update(banned=self.days_ago(1), deleted=True)
action = CinderActionTargetAppealRemovalAffirmation(self.decision)
action = ContentActionTargetAppealRemovalAffirmation(self.decision)
assert action.process_action() is None
self.user.reload()
@ -467,8 +467,8 @@ class TestCinderActionUser(BaseTestCinderAction, TestCase):
@override_switch('dsa-cinder-forwarded-review', active=True)
@override_switch('dsa-appeals-review', active=True)
class TestCinderActionAddon(BaseTestCinderAction, TestCase):
ActionClass = CinderActionDisableAddon
class TestContentActionAddon(BaseTestContentAction, TestCase):
ActionClass = ContentActionDisableAddon
def setUp(self):
super().setUp()
@ -505,7 +505,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
def test_disable_addon_after_reporter_appeal(self):
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
addon=self.addon, action=DECISION_ACTIONS.AMO_APPROVE
),
)
@ -519,10 +519,10 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
assert len(mail.outbox) == 2
self._test_reporter_appeal_takedown_email(subject)
def _test_approve_appeal_or_override(self, CinderActionClass):
def _test_approve_appeal_or_override(self, ContentActionClass):
self.addon.update(status=amo.STATUS_DISABLED)
ActivityLog.objects.all().delete()
action = CinderActionClass(self.decision)
action = ContentActionClass(self.decision)
assert action.process_action() is None
assert self.addon.reload().status == amo.STATUS_APPROVED
@ -539,7 +539,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
def _test_reporter_no_action_taken(
self,
*,
ActionClass=CinderActionApproveNoAction,
ActionClass=ContentActionApproveNoAction,
action=DECISION_ACTIONS.AMO_APPROVE,
):
self.decision.update(action=action)
@ -556,7 +556,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
def test_target_appeal_decline(self):
self.addon.update(status=amo.STATUS_DISABLED)
ActivityLog.objects.all().delete()
action = CinderActionTargetAppealRemovalAffirmation(self.decision)
action = ContentActionTargetAppealRemovalAffirmation(self.decision)
assert action.process_action() is None
self.addon.reload()
@ -572,7 +572,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
self.addon.update(status=amo.STATUS_DISABLED)
ActivityLog.objects.all().delete()
self.decision.update(notes='')
action = CinderActionTargetAppealRemovalAffirmation(self.decision)
action = ContentActionTargetAppealRemovalAffirmation(self.decision)
assert action.process_action() is None
self.addon.reload()
@ -652,7 +652,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
def _test_reject_version(self):
self.decision.update(action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON)
action = CinderActionRejectVersion(self.decision)
action = ContentActionRejectVersion(self.decision)
# process_action isn't implemented for this action currently.
with self.assertRaises(NotImplementedError):
action.process_action()
@ -687,7 +687,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
def test_reject_version_after_reporter_appeal(self):
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
addon=self.addon, action=DECISION_ACTIONS.AMO_APPROVE
),
)
@ -705,7 +705,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
self.decision.update(
action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON,
)
action = CinderActionRejectVersionDelayed(self.decision)
action = ContentActionRejectVersionDelayed(self.decision)
# note: process_action isn't implemented for this action currently.
subject = f'Mozilla Add-ons: {self.addon.name}'
@ -760,7 +760,7 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
def test_reject_version_delayed_after_reporter_appeal(self):
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_APPROVE, addon=self.addon
),
)
@ -831,8 +831,8 @@ class TestCinderActionAddon(BaseTestCinderAction, TestCase):
}
class TestCinderActionCollection(BaseTestCinderAction, TestCase):
ActionClass = CinderActionDeleteCollection
class TestContentActionCollection(BaseTestContentAction, TestCase):
ActionClass = ContentActionDeleteCollection
def setUp(self):
super().setUp()
@ -874,7 +874,7 @@ class TestCinderActionCollection(BaseTestCinderAction, TestCase):
def test_delete_collection_after_reporter_appeal(self):
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
collection=self.collection, action=DECISION_ACTIONS.AMO_APPROVE
),
)
@ -891,7 +891,7 @@ class TestCinderActionCollection(BaseTestCinderAction, TestCase):
def _test_reporter_no_action_taken(
self,
*,
ActionClass=CinderActionApproveNoAction,
ActionClass=ContentActionApproveNoAction,
action=DECISION_ACTIONS.AMO_APPROVE,
):
self.decision.update(action=action)
@ -907,9 +907,9 @@ class TestCinderActionCollection(BaseTestCinderAction, TestCase):
action.notify_owners()
return f'Mozilla Add-ons: {self.collection.name}'
def _test_approve_appeal_or_override(self, CinderActionClass):
def _test_approve_appeal_or_override(self, ContentActionClass):
self.collection.update(deleted=True)
action = CinderActionClass(self.decision)
action = ContentActionClass(self.decision)
assert action.process_action() is None
assert self.collection.reload()
@ -926,7 +926,7 @@ class TestCinderActionCollection(BaseTestCinderAction, TestCase):
def test_target_appeal_decline(self):
self.collection.update(deleted=True)
action = CinderActionTargetAppealRemovalAffirmation(self.decision)
action = ContentActionTargetAppealRemovalAffirmation(self.decision)
assert action.process_action() is None
self.collection.reload()
@ -963,8 +963,8 @@ class TestCinderActionCollection(BaseTestCinderAction, TestCase):
}
class TestCinderActionRating(BaseTestCinderAction, TestCase):
ActionClass = CinderActionDeleteRating
class TestContentActionRating(BaseTestContentAction, TestCase):
ActionClass = ContentActionDeleteRating
def setUp(self):
super().setUp()
@ -1010,7 +1010,7 @@ class TestCinderActionRating(BaseTestCinderAction, TestCase):
def test_delete_rating_after_reporter_appeal(self):
original_job = CinderJob.objects.create(
job_id='original',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
rating=self.rating, action=DECISION_ACTIONS.AMO_APPROVE
),
)
@ -1027,7 +1027,7 @@ class TestCinderActionRating(BaseTestCinderAction, TestCase):
def _test_reporter_no_action_taken(
self,
*,
ActionClass=CinderActionApproveNoAction,
ActionClass=ContentActionApproveNoAction,
action=DECISION_ACTIONS.AMO_APPROVE,
):
self.decision.update(action=action)
@ -1042,10 +1042,10 @@ class TestCinderActionRating(BaseTestCinderAction, TestCase):
action.notify_owners()
return f'Mozilla Add-ons: "Saying ..." for {self.rating.addon.name}'
def _test_approve_appeal_or_override(self, CinderActionClass):
def _test_approve_appeal_or_override(self, ContentActionClass):
self.rating.delete()
ActivityLog.objects.all().delete()
action = CinderActionClass(self.decision)
action = ContentActionClass(self.decision)
assert action.process_action() is None
assert not self.rating.reload().deleted
@ -1064,7 +1064,7 @@ class TestCinderActionRating(BaseTestCinderAction, TestCase):
def test_target_appeal_decline(self):
self.rating.delete()
ActivityLog.objects.all().delete()
action = CinderActionTargetAppealRemovalAffirmation(self.decision)
action = ContentActionTargetAppealRemovalAffirmation(self.decision)
assert action.process_action() is None
self.rating.reload()

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

@ -32,13 +32,13 @@ from olympia.constants.abuse import DECISION_ACTIONS
from olympia.core import get_user, set_user
from olympia.ratings.models import Rating
from ..models import AbuseReport, CinderAppeal, CinderDecision, CinderJob
from ..utils import (
CinderActionApproveNoAction,
CinderActionDisableAddon,
CinderActionTargetAppealApprove,
CinderActionTargetAppealRemovalAffirmation,
from ..actions import (
ContentActionApproveNoAction,
ContentActionDisableAddon,
ContentActionTargetAppealApprove,
ContentActionTargetAppealRemovalAffirmation,
)
from ..models import AbuseReport, CinderAppeal, CinderJob, ContentDecision
from ..views import CinderInboundPermission, cinder_webhook, filter_enforcement_actions
@ -1285,7 +1285,7 @@ class TestCinderWebhook(TestCase):
addon = addon_factory(guid=abuse_report.guid)
original_cinder_job = CinderJob.objects.get()
original_cinder_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
action=DECISION_ACTIONS.AMO_APPROVE,
appeal_job=CinderJob.objects.create(
@ -1321,7 +1321,7 @@ class TestCinderWebhook(TestCase):
addon = addon_factory(guid=abuse_report.guid)
original_cinder_job = CinderJob.objects.get()
original_cinder_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
action=DECISION_ACTIONS.AMO_APPROVE,
@ -1354,7 +1354,7 @@ class TestCinderWebhook(TestCase):
addon = addon_factory(guid=abuse_report.guid, users=[author])
original_cinder_job = CinderJob.objects.get()
original_cinder_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
@ -1366,7 +1366,7 @@ class TestCinderWebhook(TestCase):
)
req = self.get_request(data=data)
with mock.patch.object(
CinderActionTargetAppealRemovalAffirmation, 'process_action'
ContentActionTargetAppealRemovalAffirmation, 'process_action'
) as process_mock:
cinder_webhook(req)
process_mock.assert_called()
@ -1381,7 +1381,7 @@ class TestCinderWebhook(TestCase):
addon = addon_factory(guid=abuse_report.guid, users=[author])
original_cinder_job = CinderJob.objects.get()
original_cinder_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
@ -1393,7 +1393,7 @@ class TestCinderWebhook(TestCase):
)
req = self.get_request(data=data)
with mock.patch.object(
CinderActionTargetAppealApprove, 'process_action'
ContentActionTargetAppealApprove, 'process_action'
) as process_mock:
cinder_webhook(req)
process_mock.assert_called()
@ -1408,7 +1408,7 @@ class TestCinderWebhook(TestCase):
addon = addon_factory(guid=abuse_report.guid, users=[author])
original_cinder_job = CinderJob.objects.get()
original_cinder_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
action=DECISION_ACTIONS.AMO_APPROVE,
@ -1426,7 +1426,7 @@ class TestCinderWebhook(TestCase):
)
req = self.get_request(data=data)
with mock.patch.object(
CinderActionDisableAddon, 'process_action'
ContentActionDisableAddon, 'process_action'
) as process_mock:
cinder_webhook(req)
process_mock.assert_called()
@ -1443,7 +1443,7 @@ class TestCinderWebhook(TestCase):
addon = addon_factory(guid=abuse_report.guid, users=[author])
original_cinder_job = CinderJob.objects.get()
original_cinder_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action_date=datetime(2023, 10, 12, 9, 8, 37, 4789),
cinder_id='d1f01fae-3bce-41d5-af8a-e0b4b5ceaaed',
action=DECISION_ACTIONS.AMO_APPROVE,
@ -1461,7 +1461,7 @@ class TestCinderWebhook(TestCase):
)
req = self.get_request(data=data)
with mock.patch.object(
CinderActionApproveNoAction, 'process_action'
ContentActionApproveNoAction, 'process_action'
) as process_mock:
cinder_webhook(req)
process_mock.assert_called()
@ -2465,7 +2465,7 @@ class TestAppeal(TestCase):
def setUp(self):
self.addon = addon_factory()
self.cinder_job = CinderJob.objects.create(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='my-decision-id',
action=DECISION_ACTIONS.AMO_APPROVE,
action_date=self.days_ago(1),
@ -2712,7 +2712,7 @@ class TestAppeal(TestCase):
user = user_factory()
self.addon.authors.add(user)
self.client.force_login(user)
decision = CinderDecision.objects.create(
decision = ContentDecision.objects.create(
addon=self.addon,
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
cinder_id='some-decision-id',
@ -2845,7 +2845,7 @@ class TestAppeal(TestCase):
# specific error message (in this case we confirmed the original
# decision).
appeal_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='appeal decision id',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,
@ -2896,7 +2896,7 @@ class TestAppeal(TestCase):
# the content is already supposed to be disabled but the reporter might
# not have noticed).
appeal_job.update(
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='appeal decision id',
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=self.addon,

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

@ -33,7 +33,7 @@ from olympia.ratings.views import RatingViewSet
from olympia.users.models import UserProfile
from .forms import AbuseAppealEmailForm, AbuseAppealForm
from .models import AbuseReport, CinderDecision, CinderJob
from .models import AbuseReport, CinderJob, ContentDecision
from .serializers import (
AddonAbuseReportSerializer,
CollectionAbuseReportSerializer,
@ -178,7 +178,7 @@ def filter_enforcement_actions(enforcement_actions, cinder_job):
if DECISION_ACTIONS.has_api_value(action_slug)
and (action := DECISION_ACTIONS.for_api_value(action_slug))
and target.__class__
in CinderDecision.get_action_helper_class(action.value).valid_targets
in ContentDecision.get_action_helper_class(action.value).valid_targets
]
@ -269,7 +269,7 @@ def appeal(request, *, abuse_report_id, decision_cinder_id, **kwargs):
DECISION_ACTIONS.APPEALABLE_BY_REPORTER.values
)
cinder_decision = get_object_or_404(
CinderDecision.objects.filter(action__in=appealable_decisions),
ContentDecision.objects.filter(action__in=appealable_decisions),
cinder_id=decision_cinder_id,
)

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

@ -0,0 +1,26 @@
# Generated by Django 4.2.16 on 2024-10-21 13:48
from django.db import migrations, models
import django.db.models.deletion
import olympia.translations.fields
class Migration(migrations.Migration):
dependencies = [
('translations', '0001_initial'),
('addons', '0052_auto_20240927_1810'),
]
operations = [
migrations.AlterField(
model_name='addon',
name='summary',
field=olympia.translations.fields.NoURLsField(blank=True, db_column='summary', max_length=250, null=True, on_delete=django.db.models.deletion.SET_NULL, require_locale=True, short=True, to='translations.NoURLsTranslation', to_field='id', unique=True),
),
migrations.AlterField(
model_name='addon',
name='type',
field=models.PositiveIntegerField(choices=[(1, 'Extension'), (2, 'Deprecated Complete Theme'), (3, 'Dictionary'), (4, 'Deprecated Search Engine'), (5, 'Language Pack'), (6, 'Deprecated Language Pack (Add-on)'), (7, 'Deprecated Plugin'), (9, 'Deprecated LWT'), (10, 'Theme'), (12, 'Deprecated Site Permission')], db_column='addontype_id', default=1),
),
]

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

@ -4,8 +4,8 @@ from django.core.management.base import BaseCommand
import olympia.core.logger
from olympia import amo
from olympia.abuse.models import CinderDecision, CinderJob
from olympia.abuse.utils import CinderActionRejectVersionDelayed
from olympia.abuse.actions import ContentActionRejectVersionDelayed
from olympia.abuse.models import CinderJob, ContentDecision
from olympia.activity.models import ActivityLog
from olympia.addons.models import Addon, AddonReviewerFlags
from olympia.constants.abuse import DECISION_ACTIONS
@ -95,13 +95,13 @@ class Command(BaseCommand):
cinder_job.decision
if cinder_job
# Fake a decision if there isn't a job
else CinderDecision(
else ContentDecision(
addon=addon,
action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON,
)
)
decision.notes = relevant_activity_log.details.get('comments', '')
action_helper = CinderActionRejectVersionDelayed(decision)
action_helper = ContentActionRejectVersionDelayed(decision)
action_helper.notify_owners(
log_entry_id=relevant_activity_log.id,
extra_context={

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

@ -77,6 +77,30 @@
{% endif %}
</form>
</div>
{% elif tab == 'held_actions' %}
<table id="held-action-queue" class="data-grid">
<thead>
<tr class="listing-header">
<th>Type</th>
<th>Target</th>
<th>Action</th>
<th>Decision Date</th>
</tr>
</thead>
<tbody>
{% for decision in page.object_list %}
<tr id="{{ decision.get_reference_id(short=True) }}" class="held-item">
<td><div class="app-icon ed-sprite-action-target-{{ decision.get_target_type() }}" title="{{ decision.get_target_type() }}"></div></td>
<td>{{ decision.get_target_name() }}</td>
<td><a href="{{ decision.get_target_review_url() }}">{{ decision.get_action_display() }}</a></td>
<td>{{ decision.created|datetime }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if page.paginator.count == 0 %}
<div class="no-results">There are currently no held actions.</div>
{% endif %}
{% else %}
<div id="addon-queue-filter-form">
<button class="show-hide-toggle">Show/Hide Filter Selections</button>

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

@ -26,6 +26,7 @@ def queue_tabnav(context, reviewer_tables_registry):
'moderated',
'content_review',
'pending_rejection',
'held_actions',
):
if acl.action_allowed_for(
request.user, reviewer_tables_registry[queue].permission

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

@ -11,7 +11,7 @@ from django.test.testcases import TransactionTestCase
import responses
from olympia import amo
from olympia.abuse.models import AbuseReport, CinderDecision, CinderJob, CinderPolicy
from olympia.abuse.models import AbuseReport, CinderJob, CinderPolicy, ContentDecision
from olympia.activity.models import ActivityLog
from olympia.addons.models import AddonApprovalsCounter, AddonReviewerFlags
from olympia.amo.tests import (
@ -870,7 +870,7 @@ class TestSendPendingRejectionLastWarningNotification(TestCase):
version_factory(addon=addon, version='42.1')
cinder_job = CinderJob.objects.create(
job_id='1',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='13579',
action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON,
addon=addon,
@ -1374,7 +1374,7 @@ class TestAutoReject(AutoRejectTestsMixin, TestCase):
def test_reject_versions_with_resolved_cinder_job(self):
cinder_job = CinderJob.objects.create(
job_id='1',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='13579',
action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON,
addon=self.addon,
@ -1411,7 +1411,7 @@ class TestAutoReject(AutoRejectTestsMixin, TestCase):
def test_reject_versions_with_resolved_cinder_job_no_third_party(self):
cinder_job = CinderJob.objects.create(
job_id='2',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='13579',
action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON,
addon=self.addon,
@ -1419,7 +1419,7 @@ class TestAutoReject(AutoRejectTestsMixin, TestCase):
)
CinderJob.objects.create(
job_id='1',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='13578',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,
@ -1456,7 +1456,7 @@ class TestAutoReject(AutoRejectTestsMixin, TestCase):
def test_reject_versions_with_multiple_delayed_rejections(self):
cinder_job = CinderJob.objects.create(
job_id='2',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='13579',
action=DECISION_ACTIONS.AMO_REJECT_VERSION_WARNING_ADDON,
addon=self.addon,
@ -1464,7 +1464,7 @@ class TestAutoReject(AutoRejectTestsMixin, TestCase):
)
CinderJob.objects.create(
job_id='1',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
cinder_id='13578',
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,

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

@ -10,9 +10,9 @@ from olympia import amo
from olympia.abuse.models import (
AbuseReport,
CinderAppeal,
CinderDecision,
CinderJob,
CinderPolicy,
ContentDecision,
)
from olympia.addons.models import Addon
from olympia.amo.tests import (
@ -431,7 +431,7 @@ class TestReviewForm(TestCase):
job = CinderJob.objects.create(
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=job, addon=self.addon, action=DECISION_ACTIONS.AMO_DISABLE_ADDON
)
form = self.get_form()
@ -512,7 +512,7 @@ class TestReviewForm(TestCase):
appeal_job = CinderJob.objects.create(
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=appeal_job,
addon=self.addon,
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
@ -1039,7 +1039,7 @@ class TestReviewForm(TestCase):
cinder_job_appealed = CinderJob.objects.create(
job_id='appealed',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=self.addon,
),
@ -1078,7 +1078,7 @@ class TestReviewForm(TestCase):
CinderJob.objects.create(
job_id='forwarded_from',
forwarded_to_job=cinder_job_forwarded,
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_ESCALATE_ADDON,
notes='Why o why',
addon=self.addon,
@ -1105,7 +1105,7 @@ class TestReviewForm(TestCase):
message='fff',
cinder_job=CinderJob.objects.create(
job_id='already resovled',
decision=CinderDecision.objects.create(
decision=ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=self.addon,
),

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

@ -14,7 +14,7 @@ import pytest
import responses
from olympia import amo
from olympia.abuse.models import AbuseReport, CinderDecision, CinderJob, CinderPolicy
from olympia.abuse.models import AbuseReport, CinderJob, CinderPolicy, ContentDecision
from olympia.activity.models import (
ActivityLog,
ActivityLogToken,
@ -172,7 +172,7 @@ class TestReviewHelper(TestReviewHelperBase):
self.sign_file_mock = patcher.start()
def check_subject(self, msg):
decision = CinderDecision.objects.first() or CinderDecision(
decision = ContentDecision.objects.first() or ContentDecision(
addon=self.addon, action=DECISION_ACTIONS.AMO_APPROVE
)
assert msg.subject == (
@ -972,7 +972,7 @@ class TestReviewHelper(TestReviewHelperBase):
)
assert expected == actions
CinderDecision.objects.create(
ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=self.addon, appeal_job=job
)
expected = [
@ -1272,7 +1272,9 @@ class TestReviewHelper(TestReviewHelperBase):
assert len(mail.outbox) == 1
message = mail.outbox[0]
decision = CinderDecision(addon=self.addon, action=DECISION_ACTIONS.AMO_APPROVE)
decision = ContentDecision(
addon=self.addon, action=DECISION_ACTIONS.AMO_APPROVE
)
assert (
message.subject
== f'Mozilla Add-ons: None [ref:{decision.get_reference_id()}]'
@ -3243,7 +3245,7 @@ class TestReviewHelper(TestReviewHelperBase):
def test_clear_needs_human_review_multiple_versions_not_abuse(self):
self.setup_data(amo.STATUS_APPROVED, file_status=amo.STATUS_APPROVED)
NeedsHumanReview.objects.create(version=self.review_version)
# abuse or appeal related NHR are cleared in CinderDecision so aren't cleared
# abuse or appeal related NHR are cleared in ContentDecision so aren't cleared
NeedsHumanReview.objects.create(
version=self.review_version,
reason=NeedsHumanReview.REASONS.ABUSE_ADDON_VIOLATION,
@ -3617,12 +3619,12 @@ class TestReviewHelper(TestReviewHelperBase):
appeal_job1 = CinderJob.objects.create(
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=appeal_job1,
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=self.addon,
).policies.add(policy_a, policy_b)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=appeal_job1,
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,
addon=self.addon,
@ -3636,7 +3638,7 @@ class TestReviewHelper(TestReviewHelperBase):
appeal_job2 = CinderJob.objects.create(
job_id='2', resolvable_in_reviewer_tools=True, target_addon=self.addon
)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=appeal_job2,
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,

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

@ -27,7 +27,7 @@ from rest_framework.test import APIRequestFactory
from waffle.testutils import override_switch
from olympia import amo, core, ratings
from olympia.abuse.models import AbuseReport, CinderDecision, CinderJob, CinderPolicy
from olympia.abuse.models import AbuseReport, CinderJob, CinderPolicy, ContentDecision
from olympia.access import acl
from olympia.access.models import Group, GroupUser
from olympia.accounts.serializers import BaseUserSerializer
@ -52,6 +52,7 @@ from olympia.amo.tests import (
addon_factory,
block_factory,
check_links,
collection_factory,
formset,
initial,
reverse_ns,
@ -712,6 +713,11 @@ class TestDashboard(TestCase):
)
rating.ratingflag_set.create()
# a held decision
ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon1
)
response = self.client.get(self.url)
assert response.status_code == 200
doc = pq(response.content)
@ -730,6 +736,7 @@ class TestDashboard(TestCase):
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide/Moderation',
reverse('reviewers.motd'),
reverse('reviewers.queue_pending_rejection'),
reverse('reviewers.queue_held_actions'),
]
links = [link.attrib['href'] for link in doc('.dashboard a')]
assert links == expected_links
@ -743,6 +750,7 @@ class TestDashboard(TestCase):
assert doc('.dashboard a')[8].text == 'Ratings Awaiting Moderation (1)'
# admin tools
assert doc('.dashboard a')[12].text == 'Add-ons Pending Rejection (1)'
assert doc('.dashboard a')[13].text == 'Held Actions for 2nd Level Approval (1)'
def test_can_see_all_through_reviewer_view_all_permission(self):
self.grant_permission(self.user, 'ReviewerTools:View')
@ -764,6 +772,7 @@ class TestDashboard(TestCase):
'https://wiki.mozilla.org/Add-ons/Reviewers/Guide/Moderation',
reverse('reviewers.motd'),
reverse('reviewers.queue_pending_rejection'),
reverse('reviewers.queue_held_actions'),
]
links = [link.attrib['href'] for link in doc('.dashboard a')]
assert links == expected_links
@ -1312,6 +1321,7 @@ class TestQueueBasics(QueueTest):
expected.extend(
[
reverse('reviewers.queue_pending_rejection'),
reverse('reviewers.queue_held_actions'),
]
)
assert links == expected
@ -2562,12 +2572,12 @@ class TestReview(ReviewBase):
appeal_job1 = CinderJob.objects.create(
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=appeal_job1,
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
addon=self.addon,
)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=appeal_job1,
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,
addon=self.addon,
@ -2576,7 +2586,7 @@ class TestReview(ReviewBase):
appeal_job2 = CinderJob.objects.create(
job_id='2', resolvable_in_reviewer_tools=True, target_addon=self.addon
)
CinderDecision.objects.create(
ContentDecision.objects.create(
appeal_job=appeal_job2,
action=DECISION_ACTIONS.AMO_APPROVE,
addon=self.addon,
@ -8942,3 +8952,66 @@ class TestReviewVersionRedirect(ReviewerTest):
).status_code
== 404
)
class TestHeldActionQueue(ReviewerTest):
def setUp(self):
super().setUp()
self.url = reverse('reviewers.queue_held_actions')
self.addon_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=addon_factory()
)
self.user_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_BAN_USER, user=user_factory()
)
self.collection_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DELETE_COLLECTION,
collection=collection_factory(),
)
self.rating_decision = ContentDecision.objects.create(
action=DECISION_ACTIONS.AMO_DELETE_RATING,
rating=Rating.objects.create(addon=addon_factory(), user=user_factory()),
)
self.login_as_admin()
def test_results(self):
response = self.client.get(self.url)
assert response.status_code == 200
doc = pq(response.content)('#held-action-queue')
rows = doc('tr.held-item')
assert rows.length == 4
links = [link.attrib['href'] for link in doc('a')]
assert links == [
self.addon_decision.get_target_review_url(),
self.user_decision.get_target_review_url(),
self.collection_decision.get_target_review_url(),
self.rating_decision.get_target_review_url(),
]
assert doc('tr.held-item').attr('id') == self.addon_decision.get_reference_id(
short=True
)
assert (
doc('tr.held-item td div').attr('class')
== 'app-icon ed-sprite-action-target-Extension'
)
assert doc('tr.held-item td div').attr('title') == 'Extension'
assert doc('tr.held-item td').eq(1).text() == str(
self.addon_decision.addon.name
)
assert doc('tr.held-item td').eq(2).text() == 'Add-on disable'
def test_non_admin_cannot_access(self):
self.login_as_reviewer()
response = self.client.get(self.url)
assert response.status_code == 403
def test_reviewer_viewer_can_access(self):
user = user_factory()
self.grant_permission(user, 'ReviewerTools:View')
self.client.force_login(user)
response = self.client.get(self.url)
assert response.status_code == 200
assert pq(response.content)('#held-action-queue')

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

@ -13,7 +13,7 @@ import markupsafe
import olympia.core.logger
from olympia import amo
from olympia.abuse.models import CinderJob, CinderPolicy
from olympia.abuse.models import CinderJob, CinderPolicy, ContentDecision
from olympia.abuse.tasks import notify_addon_decision_to_cinder, resolve_job_in_cinder
from olympia.access import acl
from olympia.activity.models import ActivityLog, AttachmentLog
@ -295,6 +295,19 @@ class ModerationQueueTable:
view_name = 'queue_moderated'
class HeldActionQueueTable:
title = 'Held Actions for 2nd Level Approval'
urlname = 'queue_held_actions'
url = r'^held_actions$'
permission = amo.permissions.REVIEWS_ADMIN
show_count_in_dashboard = True
view_name = 'queue_held_actions'
@classmethod
def get_queryset(cls, request, **kw):
return ContentDecision.objects.filter(action_date=None).order_by('created')
class ReviewHelper:
"""
A class that builds enough to render the form back to the user and
@ -1080,7 +1093,7 @@ class ReviewBase:
for job in self.data.get('cinder_jobs_to_resolve', ()):
# collect all the policies we made decisions under
previous_policies = CinderPolicy.objects.filter(
cinderdecision__appeal_job=job
contentdecision__appeal_job=job
).distinct()
# we just need a single action for this appeal
# - use min() to favor AMO_DISABLE_ADDON over AMO_REJECT_VERSION_ADDON

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

@ -104,6 +104,7 @@ from olympia.reviewers.serializers import (
)
from olympia.reviewers.utils import (
ContentReviewTable,
HeldActionQueueTable,
MadReviewTable,
ModerationQueueTable,
PendingManualApprovalQueueTable,
@ -280,7 +281,13 @@ def dashboard(request):
queue_counts['pending_rejection']
),
reverse('reviewers.queue_pending_rejection'),
)
),
(
'Held Actions for 2nd Level Approval ({0})'.format(
queue_counts['held_actions']
),
reverse('reviewers.queue_held_actions'),
),
]
return TemplateResponse(
request,
@ -431,6 +438,7 @@ reviewer_tables_registry = {
'mad': MadReviewTable,
'pending_rejection': PendingRejectionTable,
'moderated': ModerationQueueTable,
'held_actions': HeldActionQueueTable,
}
@ -1578,3 +1586,21 @@ def review_version_redirect(request, addon, version):
)
url = reverse('reviewers.review', args=(channel_text, addon.pk))
return redirect(url + page_param + f'#version-{to_dom_id(version)}')
@permission_or_tools_listed_view_required(amo.permissions.REVIEWS_ADMIN)
def queue_held_actions(request, tab):
TableObj = reviewer_tables_registry[tab]
qs = TableObj.get_queryset(request)
page = paginate(request, qs, per_page=20)
return TemplateResponse(
request,
'reviewers/queue.html',
context=context(
tab=tab,
page=page,
registry=reviewer_tables_registry,
title=TableObj.title,
),
)

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

@ -57,6 +57,13 @@
.ed-sprite-needs-human-review-from-cinder { background-position: 0 -484px; }
.ed-sprite-needs-human-review-from-appeal { background-position: 0 -500px; }
.ed-sprite-action-target-Extension { background-position: 0 -532px; }
.ed-sprite-action-target-Theme { background-position: 0 -564px; }
.ed-sprite-action-target-Collection { background-position: 0 -548px; }
.ed-sprite-action-target-Rating { background-position: 0 -112px; }
.ed-sprite-action-target-User { background-position: 0 -516px; }
.platform-icon {
background: url(../../img/developers/platforms.png?8) no-repeat top left;
}

Двоичные данные
static/img/developers/reviewer-sprite.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 15 KiB

После

Ширина:  |  Высота:  |  Размер: 17 KiB