Add ability to harden / soften blocks in the admin (#22827)
* Add ability to harden / soften blocks in the admin * Use extended-choices for BlocklistSubmission ACTIONS and SIGNOFF_STATES
This commit is contained in:
Родитель
b3d0a224f1
Коммит
2a1eb8b1c5
|
@ -25,8 +25,8 @@ from .utils import splitlines
|
|||
class BlocklistSubmissionStateFilter(admin.SimpleListFilter):
|
||||
title = 'Signoff State'
|
||||
parameter_name = 'signoff_state'
|
||||
default_value = BlocklistSubmission.SIGNOFF_PENDING
|
||||
field_choices = BlocklistSubmission.SIGNOFF_STATES.items()
|
||||
default_value = BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
field_choices = BlocklistSubmission.SIGNOFF_STATES.choices
|
||||
ALL = 'all'
|
||||
DELAYED = 'delayed'
|
||||
|
||||
|
@ -185,15 +185,15 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
return False
|
||||
|
||||
def is_approvable(self, obj):
|
||||
return obj and obj.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
return obj and obj.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
|
||||
def is_changeable(self, obj):
|
||||
return obj and obj.signoff_state in (
|
||||
BlocklistSubmission.SIGNOFF_PENDING,
|
||||
BlocklistSubmission.SIGNOFF_AUTOAPPROVED,
|
||||
BlocklistSubmission.SIGNOFF_STATES.PENDING,
|
||||
BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED,
|
||||
)
|
||||
|
||||
def get_value(self, name, request, obj=None, default=None):
|
||||
def get_value(self, name, request, *, obj=None, default=None):
|
||||
"""Gets the named property from the obj if provided, or POST or GET."""
|
||||
return (
|
||||
getattr(obj, name, default)
|
||||
|
@ -201,11 +201,6 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
else request.POST.get(name, request.GET.get(name, default))
|
||||
)
|
||||
|
||||
def is_add_change_submission(self, request, obj):
|
||||
return str(self.get_value('action', request, obj, 0)) == str(
|
||||
BlocklistSubmission.ACTION_ADDCHANGE
|
||||
)
|
||||
|
||||
def has_change_permission(self, request, obj=None, strict=False):
|
||||
"""While a block submission is pending we want it to be partially
|
||||
editable (the url and reason). Once it's been rejected or approved it
|
||||
|
@ -248,7 +243,10 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
def get_fieldsets(self, request, obj):
|
||||
is_new = obj is None
|
||||
show_canned = self.has_change_permission(request, obj, strict=True)
|
||||
is_delete_submission = not self.is_add_change_submission(request, obj)
|
||||
is_delete_submission = (
|
||||
int(self.get_value('action', request, obj=obj, default=0))
|
||||
== BlocklistSubmission.ACTIONS.DELETE
|
||||
)
|
||||
|
||||
input_guids_section = (
|
||||
'Input Guids',
|
||||
|
@ -319,6 +317,10 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
return tuple(section for section in sections if section)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
is_delete_submission = (
|
||||
int(self.get_value('action', request, obj=obj, default=0))
|
||||
== BlocklistSubmission.ACTIONS.DELETE
|
||||
)
|
||||
ro_fields = [
|
||||
'ro_changed_version_ids',
|
||||
'updated_by',
|
||||
|
@ -335,7 +337,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
ro_fields += admin.utils.flatten_fieldsets(
|
||||
self.get_fieldsets(request, obj)
|
||||
)
|
||||
if obj or not self.is_add_change_submission(request, obj):
|
||||
if obj or is_delete_submission:
|
||||
ro_fields += ['block_type', 'delay_days']
|
||||
|
||||
return ro_fields
|
||||
|
@ -352,7 +354,14 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
raise PermissionDenied
|
||||
|
||||
MultiBlockForm = self.get_form(request, change=False, **kwargs)
|
||||
is_delete = not self.is_add_change_submission(request, None)
|
||||
action = int(
|
||||
self.get_value(
|
||||
'action',
|
||||
request,
|
||||
obj=None,
|
||||
default=BlocklistSubmission.ACTIONS.ADDCHANGE,
|
||||
)
|
||||
)
|
||||
guids_data = self.get_value('guids', request)
|
||||
if guids_data and 'input_guids' not in request.POST:
|
||||
# If we get a guids param it's a redirect from input_guids_view.
|
||||
|
@ -397,7 +406,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
)
|
||||
context = {
|
||||
# standard context django admin expects
|
||||
'title': 'Delete Blocks' if is_delete else 'Block Add-ons',
|
||||
'title': BlocklistSubmission(action=action).get_action_display(),
|
||||
'subtitle': None,
|
||||
'adminform': admin_form,
|
||||
'object_id': None,
|
||||
|
@ -422,7 +431,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
request, self.model._meta, object_id
|
||||
)
|
||||
extra_context['can_change_object'] = (
|
||||
obj.action == BlocklistSubmission.ACTION_ADDCHANGE
|
||||
obj.action == BlocklistSubmission.ACTIONS.ADDCHANGE
|
||||
and self.has_change_permission(request, obj, strict=True)
|
||||
)
|
||||
extra_context['can_approve'] = self.is_approvable(
|
||||
|
@ -452,12 +461,12 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
if is_approve and self.is_approvable(obj):
|
||||
if not self.has_signoff_approve_permission(request, obj):
|
||||
raise PermissionDenied
|
||||
obj.signoff_state = BlocklistSubmission.SIGNOFF_APPROVED
|
||||
obj.signoff_state = BlocklistSubmission.SIGNOFF_STATES.APPROVED
|
||||
obj.signoff_by = request.user
|
||||
elif is_reject:
|
||||
if not self.has_signoff_reject_permission(request, obj):
|
||||
raise PermissionDenied
|
||||
obj.signoff_state = BlocklistSubmission.SIGNOFF_REJECTED
|
||||
obj.signoff_state = BlocklistSubmission.SIGNOFF_STATES.REJECTED
|
||||
elif not self.has_change_permission(request, obj, strict=True):
|
||||
# users without full change permission should only do signoff
|
||||
raise PermissionDenied
|
||||
|
@ -506,7 +515,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
def ro_changed_version_ids(self, obj):
|
||||
# Annoyingly, we don't have the full context, but we stashed blocks
|
||||
# earlier in render_change_form().
|
||||
published = obj.signoff_state == obj.SIGNOFF_PUBLISHED
|
||||
published = obj.signoff_state == obj.SIGNOFF_STATES.PUBLISHED
|
||||
total_adu = sum(
|
||||
(bl.current_adu if not published else bl.average_daily_users_snapshot) or 0
|
||||
for bl in obj._blocks
|
||||
|
@ -601,7 +610,7 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
|
||||
def get_fieldsets(self, request, obj):
|
||||
details = (
|
||||
None,
|
||||
'Add-on',
|
||||
{
|
||||
'fields': (
|
||||
'addon_guid',
|
||||
|
@ -613,18 +622,18 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
},
|
||||
)
|
||||
history = ('Block History', {'fields': ('block_history',)})
|
||||
edit = (
|
||||
'Edit Block',
|
||||
versions = ('Block Versions', {'fields': ('blocked_versions',)})
|
||||
block = (
|
||||
'Block Metadata',
|
||||
{
|
||||
'fields': (
|
||||
'blocked_versions',
|
||||
('url', 'url_link'),
|
||||
'reason',
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
return (details, history, edit)
|
||||
return (details, history, versions, block)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
@ -641,6 +650,8 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
|
||||
def changeform_view(self, request, obj_id=None, form_url='', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context['ACTION_SOFTEN'] = BlocklistSubmission.ACTIONS.SOFTEN
|
||||
extra_context['ACTION_HARDEN'] = BlocklistSubmission.ACTIONS.HARDEN
|
||||
if obj_id:
|
||||
obj = (
|
||||
self.get_object(request, obj_id)
|
||||
|
@ -675,5 +686,5 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
raise PermissionDenied
|
||||
return redirect(
|
||||
reverse('admin:blocklist_blocklistsubmission_add')
|
||||
+ f'?guids={obj.guid}&action={BlocklistSubmission.ACTION_DELETE}'
|
||||
+ f'?guids={obj.guid}&action={BlocklistSubmission.ACTIONS.DELETE}'
|
||||
)
|
||||
|
|
|
@ -138,7 +138,9 @@ def _upload_mlbf_to_remote_settings(*, force_base=False):
|
|||
|
||||
def process_blocklistsubmissions():
|
||||
qs = BlocklistSubmission.objects.filter(
|
||||
signoff_state__in=BlocklistSubmission.SIGNOFF_STATES_APPROVED,
|
||||
signoff_state__in=(
|
||||
BlocklistSubmission.SIGNOFF_STATES.STATES_APPROVED.values.keys()
|
||||
),
|
||||
delayed_until__lte=datetime.now(),
|
||||
)
|
||||
for sub in qs:
|
||||
|
|
|
@ -9,7 +9,7 @@ from olympia.amo.admin import HTML5DateTimeInput
|
|||
from olympia.amo.forms import AMOModelForm
|
||||
from olympia.reviewers.models import ReviewActionReason
|
||||
|
||||
from .models import Block, BlocklistSubmission
|
||||
from .models import Block, BlocklistSubmission, BlockType
|
||||
from .utils import splitlines
|
||||
|
||||
|
||||
|
@ -77,6 +77,15 @@ class BlocksWidget(forms.widgets.SelectMultiple):
|
|||
def optgroups(self, name, value, attrs=None):
|
||||
return []
|
||||
|
||||
def get_verb(self, action):
|
||||
"""Return the verb to use when displaying a given version, depending
|
||||
on the action."""
|
||||
try:
|
||||
verb = BlocklistSubmission.ACTIONS.for_value(action).short
|
||||
except KeyError:
|
||||
verb = '?'
|
||||
return verb
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
context = super().get_context(name, value, attrs)
|
||||
return {
|
||||
|
@ -88,7 +97,7 @@ class BlocksWidget(forms.widgets.SelectMultiple):
|
|||
},
|
||||
'blocks': self.blocks,
|
||||
'total_adu': sum(block.current_adu for block in self.blocks),
|
||||
'is_add_change': self.is_add_change,
|
||||
'verb': self.get_verb(self.action),
|
||||
}
|
||||
|
||||
|
||||
|
@ -122,17 +131,39 @@ class BlocklistSubmissionForm(AMOModelForm):
|
|||
def __init__(self, data=None, *args, **kw):
|
||||
super().__init__(data, *args, **kw)
|
||||
|
||||
self.is_add_change = str(BlocklistSubmission.ACTION_ADDCHANGE) == str(
|
||||
self.get_value('action', BlocklistSubmission.ACTION_ADDCHANGE)
|
||||
self.action = int(
|
||||
self.get_value('action', BlocklistSubmission.ACTIONS.ADDCHANGE)
|
||||
)
|
||||
self.is_add_change = self.action == BlocklistSubmission.ACTIONS.ADDCHANGE
|
||||
input_guids = self.get_value('input_guids', '')
|
||||
load_full_objects = len(splitlines(input_guids)) <= GUID_FULL_LOAD_LIMIT
|
||||
is_published = self.instance.signoff_state == self.instance.SIGNOFF_PUBLISHED
|
||||
is_published = (
|
||||
self.instance.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PUBLISHED
|
||||
)
|
||||
|
||||
if not self.instance.id:
|
||||
self.fields['input_guids'].widget = HiddenInput()
|
||||
self.fields['action'].widget = HiddenInput()
|
||||
self.fields['delayed_until'].widget = HiddenInput()
|
||||
if self.action in (
|
||||
BlocklistSubmission.ACTIONS.HARDEN,
|
||||
BlocklistSubmission.ACTIONS.SOFTEN,
|
||||
):
|
||||
# When softening/hardening, the widget needs to be present for
|
||||
# us to record the block type on the blocklistsubmission, but
|
||||
# we're hiding it and forcing the value depending on the action
|
||||
# to make the UI less confusing.
|
||||
block_type = (
|
||||
BlockType.BLOCKED
|
||||
if self.action == BlocklistSubmission.ACTIONS.HARDEN
|
||||
else BlockType.SOFT_BLOCKED
|
||||
)
|
||||
self.fields['block_type'].widget = HiddenInput()
|
||||
self.fields['block_type'].choices = (
|
||||
(block_type, dict(BlockType.choices)[block_type]),
|
||||
)
|
||||
self.fields['block_type'].initial = block_type
|
||||
self.fields['block_type'].label = ''
|
||||
|
||||
objects = BlocklistSubmission.process_input_guids(
|
||||
input_guids,
|
||||
|
@ -162,6 +193,23 @@ class BlocklistSubmissionForm(AMOModelForm):
|
|||
getattr(self.instance, field_name) is not None
|
||||
)
|
||||
|
||||
def should_version_be_available_for_action(self, version):
|
||||
"""Return whether or not the given version should be available as a
|
||||
choice for the action we're currently doing."""
|
||||
conditions = {
|
||||
BlocklistSubmission.ACTIONS.ADDCHANGE: not version.is_blocked,
|
||||
BlocklistSubmission.ACTIONS.DELETE: version.is_blocked,
|
||||
BlocklistSubmission.ACTIONS.HARDEN: (
|
||||
version.is_blocked
|
||||
and version.blockversion.block_type == BlockType.SOFT_BLOCKED
|
||||
),
|
||||
BlocklistSubmission.ACTIONS.SOFTEN: (
|
||||
version.is_blocked
|
||||
and version.blockversion.block_type == BlockType.BLOCKED
|
||||
),
|
||||
}
|
||||
return conditions.get(self.action)
|
||||
|
||||
def setup_changed_version_ids_field(self, field, data):
|
||||
if not self.instance.id:
|
||||
field.choices = [
|
||||
|
@ -170,10 +218,7 @@ class BlocklistSubmissionForm(AMOModelForm):
|
|||
[
|
||||
(version.id, version.version)
|
||||
for version in block.addon_versions
|
||||
# ^ is XOR
|
||||
# - for add action it allows the version when it is NOT blocked
|
||||
# - for delete action it allows the version when it IS blocked
|
||||
if (version.is_blocked ^ self.is_add_change)
|
||||
if self.should_version_be_available_for_action(version)
|
||||
and not version.blocklist_submission_id
|
||||
],
|
||||
)
|
||||
|
@ -193,7 +238,7 @@ class BlocklistSubmissionForm(AMOModelForm):
|
|||
self.changed_version_ids_choices = self.instance.changed_version_ids
|
||||
field.widget.choices = self.changed_version_ids_choices
|
||||
field.widget.blocks = self.blocks
|
||||
field.widget.is_add_change = self.is_add_change
|
||||
field.widget.action = self.action
|
||||
|
||||
def get_value(self, field_name, default):
|
||||
return (
|
||||
|
@ -207,20 +252,20 @@ class BlocklistSubmissionForm(AMOModelForm):
|
|||
errors = []
|
||||
|
||||
# First, check we're not creating empty blocks
|
||||
# we're checking new blocks for add/change; and all blocks for delete
|
||||
# we're checking new blocks for add/change; and all blocks for other actions
|
||||
for block in (bl for bl in self.blocks if not bl.id or not self.is_add_change):
|
||||
version_ids = [v.id for v in block.addon_versions]
|
||||
changed_ids = (v_id for v_id in data if v_id in version_ids)
|
||||
blocked_ids = (v.id for v in block.addon_versions if v.is_blocked)
|
||||
# for add/change we raise if there are no changed ids for this addon
|
||||
# for delete, only if there is also at least one existing blocked version
|
||||
# for other actions, only if there is also at least one existing blocked
|
||||
# version
|
||||
if (self.is_add_change or any(blocked_ids)) and not any(changed_ids):
|
||||
errors.append(ValidationError(f'{block.guid} has no changed versions'))
|
||||
|
||||
# Second, check for duplicate version strings in reused guids.
|
||||
error_string = (
|
||||
'{}:{} exists more than once. All {} versions must be '
|
||||
f'{"blocked" if self.is_add_change else "unblocked"} together'
|
||||
'{}:{} exists more than once. All {} versions must be selected together.'
|
||||
)
|
||||
for block in self.blocks:
|
||||
version_strs = defaultdict(set) # collect version strings together
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 4.2.16 on 2024-11-12 11:24
|
||||
|
||||
from django.db import migrations, models
|
||||
import olympia.amo.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('blocklist', '0037_alter_blockversion_block_type_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='blocklistsubmission',
|
||||
name='action',
|
||||
field=models.SmallIntegerField(choices=[(0, 'Add/Change Block'), (1, 'Delete Block'), (2, 'Harden Block'), (3, 'Soften Block')], default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blocklistsubmission',
|
||||
name='block_type',
|
||||
field=olympia.amo.fields.PositiveTinyIntegerField(choices=[(0, '🛑 Hard-Blocked'), (1, '⚠️ Soft-Blocked')], default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blockversion',
|
||||
name='block_type',
|
||||
field=olympia.amo.fields.PositiveTinyIntegerField(choices=[(0, '🛑 Hard-Blocked'), (1, '⚠️ Soft-Blocked')], default=0, null=True),
|
||||
),
|
||||
]
|
|
@ -8,6 +8,7 @@ from django.utils.functional import cached_property
|
|||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from extended_choices import Choices
|
||||
from multidb import get_replica
|
||||
|
||||
from olympia import amo
|
||||
|
@ -97,6 +98,12 @@ class Block(ModelBase):
|
|||
for block in blocks:
|
||||
block.addon_versions = all_addon_versions[block.guid]
|
||||
|
||||
def has_soft_blocked_versions(self):
|
||||
return self.blockversion_set.filter(block_type=BlockType.SOFT_BLOCKED).exists()
|
||||
|
||||
def has_hard_blocked_versions(self):
|
||||
return self.blockversion_set.filter(block_type=BlockType.BLOCKED).exists()
|
||||
|
||||
def review_listed_link(self):
|
||||
has_listed = any(
|
||||
True
|
||||
|
@ -195,32 +202,25 @@ class BlocklistSubmissionManager(ManagerBase):
|
|||
|
||||
|
||||
class BlocklistSubmission(ModelBase):
|
||||
SIGNOFF_PENDING = 0
|
||||
SIGNOFF_APPROVED = 1
|
||||
SIGNOFF_REJECTED = 2
|
||||
SIGNOFF_AUTOAPPROVED = 3
|
||||
SIGNOFF_PUBLISHED = 4
|
||||
SIGNOFF_STATES = {
|
||||
SIGNOFF_PENDING: 'Pending Sign-off',
|
||||
SIGNOFF_APPROVED: 'Approved',
|
||||
SIGNOFF_REJECTED: 'Rejected',
|
||||
SIGNOFF_AUTOAPPROVED: 'Auto Sign-off',
|
||||
SIGNOFF_PUBLISHED: 'Published',
|
||||
}
|
||||
SIGNOFF_STATES_APPROVED = (
|
||||
SIGNOFF_APPROVED,
|
||||
SIGNOFF_AUTOAPPROVED,
|
||||
SIGNOFF_STATES = Choices(
|
||||
('PENDING', 0, 'Pending Sign-off'),
|
||||
('APPROVED', 1, 'Approved'),
|
||||
('REJECTED', 2, 'Rejected'),
|
||||
('AUTOAPPROVED', 3, 'Auto Sign-off'),
|
||||
('PUBLISHED', 4, 'Published'),
|
||||
)
|
||||
SIGNOFF_STATES_FINISHED = (
|
||||
SIGNOFF_REJECTED,
|
||||
SIGNOFF_PUBLISHED,
|
||||
SIGNOFF_STATES.add_subset('STATES_APPROVED', ('APPROVED', 'AUTOAPPROVED'))
|
||||
SIGNOFF_STATES.add_subset('STATES_FINISHED', ('REJECTED', 'PUBLISHED'))
|
||||
ACTIONS = Choices(
|
||||
# 'short' extra property is added to describe the short verb we use for
|
||||
# each action when displayed next to a version.
|
||||
('ADDCHANGE', 0, 'Add/Change Block', {'short': 'Block'}),
|
||||
('DELETE', 1, 'Delete Block', {'short': 'Unblock'}),
|
||||
('HARDEN', 2, 'Harden Block', {'short': 'Harden'}),
|
||||
('SOFTEN', 3, 'Soften Block', {'short': 'Soften'}),
|
||||
)
|
||||
ACTION_ADDCHANGE = 0
|
||||
ACTION_DELETE = 1
|
||||
ACTIONS = {
|
||||
ACTION_ADDCHANGE: 'Add/Change',
|
||||
ACTION_DELETE: 'Delete',
|
||||
}
|
||||
ACTIONS.add_subset('SAVE_TO_BLOCK_OBJECTS', ('ADDCHANGE', 'HARDEN', 'SOFTEN'))
|
||||
ACTIONS.add_subset('DELETE_TO_BLOCK_OBJECTS', ('DELETE',))
|
||||
FakeBlockAddonVersion = namedtuple(
|
||||
'FakeBlockAddonVersion',
|
||||
(
|
||||
|
@ -240,7 +240,9 @@ class BlocklistSubmission(ModelBase):
|
|||
),
|
||||
)
|
||||
|
||||
action = models.SmallIntegerField(choices=ACTIONS.items(), default=ACTION_ADDCHANGE)
|
||||
action = models.SmallIntegerField(
|
||||
choices=ACTIONS.choices, default=ACTIONS.ADDCHANGE
|
||||
)
|
||||
input_guids = models.TextField()
|
||||
changed_version_ids = models.JSONField(default=list)
|
||||
to_block = models.JSONField(default=list)
|
||||
|
@ -261,7 +263,7 @@ class BlocklistSubmission(ModelBase):
|
|||
UserProfile, null=True, on_delete=models.SET_NULL, related_name='+'
|
||||
)
|
||||
signoff_state = models.SmallIntegerField(
|
||||
choices=SIGNOFF_STATES.items(), default=SIGNOFF_PENDING
|
||||
choices=SIGNOFF_STATES.choices, default=SIGNOFF_STATES.PENDING
|
||||
)
|
||||
delayed_until = models.DateTimeField(
|
||||
null=True,
|
||||
|
@ -323,19 +325,19 @@ class BlocklistSubmission(ModelBase):
|
|||
return bool(self.changed_version_ids)
|
||||
|
||||
def update_signoff_for_auto_approval(self):
|
||||
is_pending = self.signoff_state == self.SIGNOFF_PENDING
|
||||
add_action = self.action == self.ACTION_ADDCHANGE
|
||||
is_pending = self.signoff_state == self.SIGNOFF_STATES.PENDING
|
||||
add_action = self.action == self.ACTIONS.ADDCHANGE
|
||||
if is_pending and (
|
||||
self.all_adu_safe() or add_action and not self.has_version_changes()
|
||||
):
|
||||
self.update(signoff_state=self.SIGNOFF_AUTOAPPROVED)
|
||||
self.update(signoff_state=self.SIGNOFF_STATES.AUTOAPPROVED)
|
||||
|
||||
@property
|
||||
def is_submission_ready(self):
|
||||
"""Has this submission been signed off, or sign-off isn't required."""
|
||||
is_auto_approved = self.signoff_state == self.SIGNOFF_AUTOAPPROVED
|
||||
is_auto_approved = self.signoff_state == self.SIGNOFF_STATES.AUTOAPPROVED
|
||||
is_signed_off = (
|
||||
self.signoff_state == self.SIGNOFF_APPROVED
|
||||
self.signoff_state == self.SIGNOFF_STATES.APPROVED
|
||||
and self.can_user_signoff(self.signoff_by)
|
||||
)
|
||||
return not self.is_delayed and (is_auto_approved or is_signed_off)
|
||||
|
@ -355,7 +357,7 @@ class BlocklistSubmission(ModelBase):
|
|||
processed = self.process_input_guids(
|
||||
self.input_guids,
|
||||
load_full_objects=False,
|
||||
filter_existing=(self.action == self.ACTION_ADDCHANGE),
|
||||
filter_existing=(self.action == self.ACTIONS.ADDCHANGE),
|
||||
)
|
||||
return [serialize_block(block) for block in processed.get('blocks', [])]
|
||||
|
||||
|
@ -389,7 +391,7 @@ class BlocklistSubmission(ModelBase):
|
|||
named=True,
|
||||
)
|
||||
)
|
||||
all_submission_versions = BlocklistSubmission.get_all_submission_versions()
|
||||
all_submission_versions = cls.get_all_submission_versions()
|
||||
|
||||
all_addon_versions = defaultdict(list)
|
||||
for version in version_qs:
|
||||
|
@ -485,7 +487,7 @@ class BlocklistSubmission(ModelBase):
|
|||
|
||||
def save_to_block_objects(self):
|
||||
assert self.is_submission_ready
|
||||
assert self.action == self.ACTION_ADDCHANGE
|
||||
assert self.action in self.ACTIONS.SAVE_TO_BLOCK_OBJECTS
|
||||
|
||||
all_guids_to_block = [block['guid'] for block in self.to_block]
|
||||
|
||||
|
@ -493,11 +495,11 @@ class BlocklistSubmission(ModelBase):
|
|||
save_versions_to_blocks(guids_chunk, self)
|
||||
self.save()
|
||||
|
||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||
self.update(signoff_state=self.SIGNOFF_STATES.PUBLISHED)
|
||||
|
||||
def delete_block_objects(self):
|
||||
assert self.is_submission_ready
|
||||
assert self.action == self.ACTION_DELETE
|
||||
assert self.action in self.ACTIONS.DELETE_TO_BLOCK_OBJECTS
|
||||
|
||||
all_guids_to_block = [block['guid'] for block in self.to_block]
|
||||
|
||||
|
@ -506,24 +508,24 @@ class BlocklistSubmission(ModelBase):
|
|||
delete_versions_from_blocks(guids_chunk, self)
|
||||
self.save()
|
||||
|
||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||
self.update(signoff_state=self.SIGNOFF_STATES.PUBLISHED)
|
||||
|
||||
@classmethod
|
||||
def get_submissions_from_guid(cls, guid, excludes=SIGNOFF_STATES_FINISHED):
|
||||
return cls.objects.exclude(signoff_state__in=excludes).filter(
|
||||
to_block__contains={'guid': guid}
|
||||
)
|
||||
def get_submissions_from_guid(cls, guid):
|
||||
return cls.objects.exclude(
|
||||
signoff_state__in=cls.SIGNOFF_STATES.STATES_FINISHED.values.keys()
|
||||
).filter(to_block__contains={'guid': guid})
|
||||
|
||||
@classmethod
|
||||
def get_submissions_from_version_id(cls, version_id):
|
||||
return cls.objects.exclude(
|
||||
signoff_state__in=cls.SIGNOFF_STATES_FINISHED
|
||||
signoff_state__in=cls.SIGNOFF_STATES.STATES_FINISHED.values.keys()
|
||||
).filter(changed_version_ids__contains=version_id)
|
||||
|
||||
@classmethod
|
||||
def get_all_submission_versions(cls):
|
||||
submission_qs = BlocklistSubmission.objects.exclude(
|
||||
signoff_state__in=BlocklistSubmission.SIGNOFF_STATES_FINISHED
|
||||
submission_qs = cls.objects.exclude(
|
||||
signoff_state__in=cls.SIGNOFF_STATES.STATES_FINISHED.values.keys()
|
||||
).values_list('id', 'changed_version_ids')
|
||||
return {
|
||||
ver_id: sub_id for sub_id, id_list in submission_qs for ver_id in id_list
|
||||
|
|
|
@ -44,15 +44,15 @@ def process_blocklistsubmission(multi_block_submit_id, **kw):
|
|||
obj = BlocklistSubmission.objects.get(pk=multi_block_submit_id)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
if obj.action == BlocklistSubmission.ACTION_ADDCHANGE:
|
||||
# create the blocks from the guids in the multi_block
|
||||
if obj.action in BlocklistSubmission.ACTIONS.SAVE_TO_BLOCK_OBJECTS:
|
||||
# create/update the blocks from the guids in the multi_block
|
||||
obj.save_to_block_objects()
|
||||
elif obj.action == BlocklistSubmission.ACTION_DELETE:
|
||||
# delete the blocks
|
||||
elif obj.action in BlocklistSubmission.ACTIONS.DELETE_TO_BLOCK_OBJECTS:
|
||||
# delete/update the blocks
|
||||
obj.delete_block_objects()
|
||||
except Exception as exc:
|
||||
# If something failed reset the submission back to Pending.
|
||||
obj.update(signoff_state=BlocklistSubmission.SIGNOFF_PENDING)
|
||||
obj.update(signoff_state=BlocklistSubmission.SIGNOFF_STATES.PENDING)
|
||||
message = f'Exception in task: {exc}'
|
||||
LogEntry.objects.log_action(
|
||||
user_id=settings.TASK_USER_ID,
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "admin/submit_line.html" %}
|
||||
{% block submit-row %}
|
||||
{% if original.pk %}
|
||||
<a href="{% url 'admin:blocklist_blocklistsubmission_add' %}?guids={{ original.guid }}&action={{ ACTION_HARDEN }}" class="button hardenlink {% if not original.has_soft_blocked_versions %}disabled{% endif %}">Harden</a>
|
||||
<a href="{% url 'admin:blocklist_blocklistsubmission_add' %}?guids={{ original.guid }}&action={{ ACTION_SOFTEN }}" class="button softenlink {% if not original.has_hard_blocked_versions %}disabled{% endif %}">Soften</a>
|
||||
{% endif %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
|
@ -29,7 +29,7 @@
|
|||
name="changed_version_ids"
|
||||
value="{{ version.id }}"
|
||||
{% if version.id in widget.value %}checked{% endif %}
|
||||
> {% if is_add_change %}Block{% else %}Unblock{% endif %} {{ version.version }} {% if version.blockversion %}({{ version.blockversion.get_block_type_display }}){% endif %}</label>
|
||||
> {{ verb }} {{ version.version }} {% if version.blockversion %}({{ version.blockversion.get_block_type_display }}){% endif %}</label>
|
||||
{% else %}
|
||||
<span>
|
||||
{{ version.version }} ({% if version.is_blocked %}{{ version.blockversion.get_block_type_display }}{% else %}🟢 Not Blocked{% endif %})
|
||||
|
|
|
@ -204,6 +204,85 @@ class TestBlockAdmin(TestCase):
|
|||
'Blocked versions:\n2.0 (🛑 Hard-Blocked), 3.0 (⚠️ Soft-Blocked)'
|
||||
)
|
||||
|
||||
def test_soften_harden(self):
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
addon = addon_factory(version_kw={'version': '1.0'})
|
||||
second_version = version_factory(addon=addon, version='2.0')
|
||||
third_version = version_factory(addon=addon, version='3.0')
|
||||
block = block_factory(
|
||||
addon=addon,
|
||||
version_ids=[second_version.id, third_version.id],
|
||||
updated_by=user,
|
||||
)
|
||||
# Make one of the blocks soft.
|
||||
block.blockversion_set.get(version=third_version).update(
|
||||
block_type=BlockType.SOFT_BLOCKED
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('admin:blocklist_block_change', args=(block.id,)),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content.decode('utf-8'))
|
||||
assert doc('.softenlink').attr('href') == (
|
||||
reverse('admin:blocklist_blocklistsubmission_add')
|
||||
+ f'?guids={addon.guid}&action={BlocklistSubmission.ACTIONS.SOFTEN}'
|
||||
)
|
||||
assert doc('.hardenlink').attr('href') == (
|
||||
reverse('admin:blocklist_blocklistsubmission_add')
|
||||
+ f'?guids={addon.guid}&action={BlocklistSubmission.ACTIONS.HARDEN}'
|
||||
)
|
||||
assert 'disabled' not in doc('.hardenlink').attr('class')
|
||||
assert 'disabled' not in doc('.softenlink').attr('class')
|
||||
|
||||
def test_harden_disabled_only_hard_blocked_versions_already(self):
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
addon = addon_factory(version_kw={'version': '1.0'})
|
||||
second_version = version_factory(addon=addon, version='2.0')
|
||||
third_version = version_factory(addon=addon, version='3.0')
|
||||
block = block_factory(
|
||||
addon=addon,
|
||||
version_ids=[second_version.id, third_version.id],
|
||||
updated_by=user,
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('admin:blocklist_block_change', args=(block.id,)),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content.decode('utf-8'))
|
||||
assert 'disabled' in doc('.hardenlink').attr('class')
|
||||
assert 'disabled' not in doc('.softenlink').attr('class')
|
||||
|
||||
def test_soften_disabled_only_soft_blocked_versions_already(self):
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
addon = addon_factory(version_kw={'version': '1.0'})
|
||||
second_version = version_factory(addon=addon, version='2.0')
|
||||
third_version = version_factory(addon=addon, version='3.0')
|
||||
block = block_factory(
|
||||
addon=addon,
|
||||
version_ids=[second_version.id, third_version.id],
|
||||
updated_by=user,
|
||||
block_type=BlockType.SOFT_BLOCKED,
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('admin:blocklist_block_change', args=(block.id,)),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content.decode('utf-8'))
|
||||
assert 'disabled' not in doc('.hardenlink').attr('class')
|
||||
assert 'disabled' in doc('.softenlink').attr('class')
|
||||
|
||||
|
||||
def check_checkbox(checkbox, version):
|
||||
assert checkbox.attrib['value'] == str(version.id)
|
||||
|
@ -324,8 +403,8 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
check_checkbox(checkboxes[1], ver_deleted)
|
||||
check_checkbox(checkboxes[2], ver_other)
|
||||
|
||||
# not a checkbox because in a submission, green circle because not
|
||||
# blocked yet technically.
|
||||
# not a checkbox because already part of a submission, green circle
|
||||
# because not blocked yet technically.
|
||||
assert doc(f'li[data-version-id="{ver_add_subm.id}"]').text() == (
|
||||
f'{ver_add_subm.version} (🟢 Not Blocked) [Edit Submission]'
|
||||
)
|
||||
|
@ -362,6 +441,110 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
check_checkbox(checkboxes[0], ver_deleted)
|
||||
check_checkbox(checkboxes[1], ver_other)
|
||||
|
||||
def test_version_checkboxes_hardening_action(self):
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
addon = addon_factory(guid='guid@', average_daily_users=100)
|
||||
ver_deleted = version_factory(addon=addon, channel=amo.CHANNEL_UNLISTED)
|
||||
ver_deleted.delete() # shouldn't affect it's status
|
||||
# these next three versions shouldn't be possible choices
|
||||
ver_add_subm = version_factory(addon=addon)
|
||||
add_submission = BlocklistSubmission.objects.create(
|
||||
input_guids=addon.guid, changed_version_ids=[ver_add_subm.id]
|
||||
)
|
||||
ver_other = addon_factory(average_daily_users=99).current_version
|
||||
ver_block = version_factory(addon=ver_other.addon)
|
||||
ver_soft_block = version_factory(addon=ver_other.addon)
|
||||
block_factory(
|
||||
addon=addon, version_ids=[ver_block.id, ver_soft_block.id], updated_by=user
|
||||
)
|
||||
ver_soft_block.blockversion.update(block_type=BlockType.SOFT_BLOCKED)
|
||||
|
||||
response = self.client.get(
|
||||
self.submission_url,
|
||||
{
|
||||
'guids': f'{addon.guid}\n {ver_block.addon.guid}\n',
|
||||
'action': BlocklistSubmission.ACTIONS.HARDEN,
|
||||
},
|
||||
)
|
||||
doc = pq(response.content.decode('utf-8'))
|
||||
checkboxes = doc('input[name=changed_version_ids]')
|
||||
|
||||
assert len(checkboxes) == 1
|
||||
check_checkbox(checkboxes[0], ver_soft_block)
|
||||
|
||||
# not a checkbox because already part of a submission, green circle
|
||||
# because not blocked yet technically.
|
||||
assert doc(f'li[data-version-id="{ver_add_subm.id}"]').text() == (
|
||||
f'{ver_add_subm.version} (🟢 Not Blocked) [Edit Submission]'
|
||||
)
|
||||
submission_link = doc(f'li[data-version-id="{ver_add_subm.id}"] a')
|
||||
assert submission_link.text() == 'Edit Submission'
|
||||
assert submission_link.attr['href'] == reverse(
|
||||
'admin:blocklist_blocklistsubmission_change',
|
||||
args=(add_submission.id,),
|
||||
)
|
||||
|
||||
# not a checkbox because hard-blocked already and this is an harden
|
||||
# action
|
||||
assert doc(f'li[data-version-id="{ver_block.id}"]').text() == (
|
||||
f'{ver_block.version} (🛑 Hard-Blocked)'
|
||||
)
|
||||
|
||||
def test_version_checkboxes_softening_action(self):
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
addon = addon_factory(guid='guid@', average_daily_users=100)
|
||||
ver_deleted = version_factory(addon=addon, channel=amo.CHANNEL_UNLISTED)
|
||||
ver_deleted.delete() # shouldn't affect it's status
|
||||
# these next three versions shouldn't be possible choices
|
||||
ver_add_subm = version_factory(addon=addon)
|
||||
add_submission = BlocklistSubmission.objects.create(
|
||||
input_guids=addon.guid, changed_version_ids=[ver_add_subm.id]
|
||||
)
|
||||
ver_other = addon_factory(average_daily_users=99).current_version
|
||||
ver_block = version_factory(addon=ver_other.addon)
|
||||
ver_soft_block = version_factory(addon=ver_other.addon)
|
||||
block_factory(
|
||||
addon=addon, version_ids=[ver_block.id, ver_soft_block.id], updated_by=user
|
||||
)
|
||||
ver_soft_block.blockversion.update(block_type=BlockType.SOFT_BLOCKED)
|
||||
|
||||
response = self.client.get(
|
||||
self.submission_url,
|
||||
{
|
||||
'guids': f'{addon.guid}\n {ver_block.addon.guid}\n',
|
||||
'action': BlocklistSubmission.ACTIONS.SOFTEN,
|
||||
},
|
||||
)
|
||||
doc = pq(response.content.decode('utf-8'))
|
||||
checkboxes = doc('input[name=changed_version_ids]')
|
||||
|
||||
assert len(checkboxes) == 1
|
||||
check_checkbox(checkboxes[0], ver_block)
|
||||
|
||||
# not a checkbox because already part of a submission, green circle
|
||||
# because not blocked yet technically.
|
||||
assert doc(f'li[data-version-id="{ver_add_subm.id}"]').text() == (
|
||||
f'{ver_add_subm.version} (🟢 Not Blocked) [Edit Submission]'
|
||||
)
|
||||
submission_link = doc(f'li[data-version-id="{ver_add_subm.id}"] a')
|
||||
assert submission_link.text() == 'Edit Submission'
|
||||
assert submission_link.attr['href'] == reverse(
|
||||
'admin:blocklist_blocklistsubmission_change',
|
||||
args=(add_submission.id,),
|
||||
)
|
||||
|
||||
# not a checkbox because soft-blocked already and this is an soften
|
||||
# action
|
||||
assert doc(f'li[data-version-id="{ver_soft_block.id}"]').text() == (
|
||||
f'{ver_soft_block.version} (⚠️ Soft-Blocked)'
|
||||
)
|
||||
|
||||
def test_add_single(self):
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
|
@ -421,7 +604,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': 'guid@',
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.BLOCKED,
|
||||
'changed_version_ids': changed_version_ids,
|
||||
'url': 'dfd',
|
||||
|
@ -600,7 +783,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': ('any@new\npartial@existing\nfull@existing\ninvalid@'),
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.BLOCKED,
|
||||
'changed_version_ids': [
|
||||
new_addon.current_version.id,
|
||||
|
@ -794,6 +977,95 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert partial_addon.status == amo.STATUS_DISABLED
|
||||
assert partial_addon_version.file.status == (amo.STATUS_DISABLED)
|
||||
|
||||
def test_soften(self):
|
||||
addon = addon_factory(guid='guid@')
|
||||
version = addon.current_version
|
||||
block = block_factory(
|
||||
guid=addon.guid,
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(
|
||||
self.submission_url,
|
||||
{
|
||||
'guids': str(addon.guid),
|
||||
'action': str(BlocklistSubmission.ACTIONS.SOFTEN),
|
||||
},
|
||||
)
|
||||
doc = pq(response.content)
|
||||
assert doc('#id_block_type').attr('value') == str(BlockType.SOFT_BLOCKED)
|
||||
|
||||
response = self.client.post(
|
||||
self.submission_url,
|
||||
{
|
||||
'input_guids': str(addon.guid),
|
||||
'action': str(BlocklistSubmission.ACTIONS.SOFTEN),
|
||||
'block_type': str(BlockType.SOFT_BLOCKED),
|
||||
'changed_version_ids': [
|
||||
version.id,
|
||||
],
|
||||
'disable_addon': False,
|
||||
'url': 'dfd',
|
||||
'reason': 'some reason',
|
||||
'update_url_value': True,
|
||||
'update_reason_value': True,
|
||||
'delay_days': 0,
|
||||
'_save': 'Save',
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert version.blockversion.reload().block_type == BlockType.SOFT_BLOCKED
|
||||
assert block.reload().updated_by == user
|
||||
|
||||
def test_harden(self):
|
||||
addon = addon_factory(guid='guid@')
|
||||
version = addon.current_version
|
||||
block = block_factory(
|
||||
guid=addon.guid,
|
||||
updated_by=user_factory(),
|
||||
block_type=BlockType.SOFT_BLOCKED,
|
||||
)
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(
|
||||
self.submission_url,
|
||||
{
|
||||
'guids': str(addon.guid),
|
||||
'action': str(BlocklistSubmission.ACTIONS.HARDEN),
|
||||
},
|
||||
)
|
||||
doc = pq(response.content)
|
||||
assert doc('#id_block_type').attr('value') == str(BlockType.BLOCKED)
|
||||
|
||||
response = self.client.post(
|
||||
self.submission_url,
|
||||
{
|
||||
'input_guids': str(addon.guid),
|
||||
'action': str(BlocklistSubmission.ACTIONS.HARDEN),
|
||||
'block_type': str(BlockType.BLOCKED),
|
||||
'changed_version_ids': [
|
||||
version.id,
|
||||
],
|
||||
'disable_addon': False,
|
||||
'url': 'dfd',
|
||||
'reason': 'some reason',
|
||||
'update_url_value': True,
|
||||
'update_reason_value': True,
|
||||
'delay_days': 0,
|
||||
'_save': 'Save',
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert version.blockversion.reload().block_type == BlockType.BLOCKED
|
||||
assert block.reload().updated_by == user
|
||||
|
||||
def test_submit_no_dual_signoff(self):
|
||||
addon_adu = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD
|
||||
(
|
||||
|
@ -825,7 +1097,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
multi = BlocklistSubmission.objects.get()
|
||||
assert multi.block_type == BlockType.BLOCKED
|
||||
multi.update(
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_APPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.APPROVED,
|
||||
signoff_by=user_factory(),
|
||||
)
|
||||
assert multi.is_submission_ready
|
||||
|
@ -872,7 +1144,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': ('any@new\npartial@existing\nfull@existing\ninvalid@'),
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.BLOCKED,
|
||||
'changed_version_ids': [
|
||||
new_addon.current_version.id,
|
||||
|
@ -1039,7 +1311,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': 'guid@\nfoo@baa\ninvalid@',
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.BLOCKED,
|
||||
'url': 'dfd',
|
||||
'reason': 'some reason',
|
||||
|
@ -1067,12 +1339,12 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
add_change_subm = BlocklistSubmission.objects.create(
|
||||
input_guids='guid@\ninvalid@\nblock@',
|
||||
updated_by=user_factory(display_name='Bób'),
|
||||
action=BlocklistSubmission.ACTION_ADDCHANGE,
|
||||
action=BlocklistSubmission.ACTIONS.ADDCHANGE,
|
||||
)
|
||||
delete_subm = BlocklistSubmission.objects.create(
|
||||
input_guids='block@',
|
||||
updated_by=user_factory(display_name='Sué'),
|
||||
action=BlocklistSubmission.ACTION_DELETE,
|
||||
action=BlocklistSubmission.ACTIONS.DELETE,
|
||||
)
|
||||
add_change_subm.save()
|
||||
delete_subm.save()
|
||||
|
@ -1106,7 +1378,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert 'Sué' in response.content.decode('utf-8')
|
||||
doc = pq(response.content)
|
||||
assert doc('th.field-blocks_count').text() == '1 add-ons 2 add-ons'
|
||||
assert doc('.field-action').text() == ('Delete Add/Change')
|
||||
assert doc('.field-action').text() == ('Delete Block Add/Change Block')
|
||||
assert doc('.field-state').text() == 'Pending Sign-off Pending Sign-off'
|
||||
|
||||
def test_can_list_with_blocklist_create(self):
|
||||
|
@ -1190,7 +1462,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert mbs.disable_addon is False
|
||||
|
||||
# The blocklistsubmission wasn't approved or rejected though
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
assert Block.objects.count() == 0
|
||||
|
||||
log_entry = LogEntry.objects.get()
|
||||
|
@ -1259,7 +1531,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
multi_url,
|
||||
{
|
||||
'input_guids': 'guid2@\nfoo@baa',
|
||||
'action': str(BlocklistSubmission.ACTION_DELETE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.DELETE),
|
||||
'changed_version_ids': [],
|
||||
'url': 'new.url',
|
||||
'reason': 'a reason',
|
||||
|
@ -1280,7 +1552,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert mbs.reason != 'a reason'
|
||||
|
||||
# The blocklistsubmission wasn't approved or rejected either
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
assert Block.objects.count() == 0
|
||||
assert LogEntry.objects.count() == 0
|
||||
|
||||
|
@ -1455,7 +1727,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert mbs.reason != 'a reason'
|
||||
|
||||
# And the blocklistsubmission was rejected, so no Blocks created
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_REJECTED
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.REJECTED
|
||||
assert Block.objects.count() == 0
|
||||
assert not mbs.is_submission_ready
|
||||
|
||||
|
@ -1524,7 +1796,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
mbs = mbs.reload()
|
||||
# It wasn't signed off
|
||||
assert not mbs.signoff_by
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
# And the details weren't updated either
|
||||
assert mbs.url != 'new.url'
|
||||
assert mbs.reason != 'a reason'
|
||||
|
@ -1567,7 +1839,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
submission = submission.reload()
|
||||
# It wasn't signed off
|
||||
assert not submission.signoff_by
|
||||
assert submission.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
assert submission.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
# And the details weren't updated either
|
||||
assert submission.url != 'new.url'
|
||||
assert submission.reason != 'a reason'
|
||||
|
@ -1598,7 +1870,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
)
|
||||
assert response.status_code == 200
|
||||
submission = submission.reload()
|
||||
assert submission.signoff_state == BlocklistSubmission.SIGNOFF_REJECTED
|
||||
assert submission.signoff_state == BlocklistSubmission.SIGNOFF_STATES.REJECTED
|
||||
assert not submission.signoff_by
|
||||
assert submission.url == 'new.url'
|
||||
assert submission.reason == 'a reason'
|
||||
|
@ -1609,7 +1881,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
input_guids='guid@\ninvalid@\nsecond@invalid',
|
||||
updated_by=user_factory(),
|
||||
signoff_by=user_factory(),
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_APPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.APPROVED,
|
||||
changed_version_ids=[addon.current_version.id],
|
||||
)
|
||||
assert mbs.to_block == [
|
||||
|
@ -1621,7 +1893,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
]
|
||||
mbs.save_to_block_objects()
|
||||
block = Block.objects.get()
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PUBLISHED
|
||||
# update addon adu to something different
|
||||
assert block.average_daily_users_snapshot == addon.average_daily_users
|
||||
addon.update(average_daily_users=1234)
|
||||
|
@ -1672,16 +1944,16 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
addon_factory(guid='published@')
|
||||
BlocklistSubmission.objects.create(
|
||||
input_guids='pending1@\npending2@',
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PENDING,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PENDING,
|
||||
)
|
||||
BlocklistSubmission.objects.create(
|
||||
input_guids='missing@',
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_APPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.APPROVED,
|
||||
delayed_until=now + timedelta(days=1),
|
||||
)
|
||||
BlocklistSubmission.objects.create(
|
||||
input_guids='published@',
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
delayed_until=now - timedelta(days=1),
|
||||
)
|
||||
|
||||
|
@ -1761,7 +2033,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': 'guid@',
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.BLOCKED,
|
||||
'changed_version_ids': [version.id],
|
||||
'disable_addon': True,
|
||||
|
@ -1809,7 +2081,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': 'guid@',
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.BLOCKED,
|
||||
'changed_version_ids': [version.id],
|
||||
'url': 'dfd',
|
||||
|
@ -1841,13 +2113,13 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
# no new Block objects yet even though under the threshold
|
||||
assert Block.objects.count() == 2
|
||||
multi = BlocklistSubmission.objects.get()
|
||||
assert multi.signoff_state == BlocklistSubmission.SIGNOFF_AUTOAPPROVED
|
||||
assert multi.signoff_state == BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED
|
||||
assert not multi.is_submission_ready
|
||||
|
||||
frozen_time.tick(delta=timedelta(days=delay_days, seconds=1))
|
||||
# Now we're past, the submission is ready
|
||||
assert multi.is_submission_ready
|
||||
assert multi.signoff_state == BlocklistSubmission.SIGNOFF_AUTOAPPROVED
|
||||
assert multi.signoff_state == BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED
|
||||
|
||||
multi.save_to_block_objects()
|
||||
self._test_add_multiple_verify_blocks(
|
||||
|
@ -1857,7 +2129,9 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
existing_and_partial,
|
||||
has_signoff=False,
|
||||
)
|
||||
assert multi.reload().signoff_state == BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
assert (
|
||||
multi.reload().signoff_state == BlocklistSubmission.SIGNOFF_STATES.PUBLISHED
|
||||
)
|
||||
|
||||
def test_approve_delayed(self):
|
||||
now = datetime.now()
|
||||
|
@ -1871,7 +2145,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
delayed_until=now + timedelta(days=2),
|
||||
)
|
||||
assert mbs.to_block[0]['guid'] == addon.guid
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
|
||||
user = user_factory(email='someone@mozilla.com')
|
||||
self.grant_permission(user, 'Blocklist:Signoff')
|
||||
|
@ -1891,7 +2165,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert mbs.signoff_by == user
|
||||
|
||||
# Approved but not published
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_APPROVED
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.APPROVED
|
||||
# And no blocks have been created
|
||||
assert not Block.objects.exists()
|
||||
|
||||
|
@ -1908,7 +2182,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
input_guids='guid@\ninvalid@',
|
||||
changed_version_ids=[version.id],
|
||||
updated_by=user_factory(),
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_AUTOAPPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED,
|
||||
delayed_until=datetime.now() + timedelta(days=1),
|
||||
)
|
||||
assert mbs.to_block[0]['guid'] == 'guid@'
|
||||
|
@ -1942,7 +2216,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert mbs.reason != 'a reason'
|
||||
|
||||
# And the blocklistsubmission was rejected, so no Blocks created
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_REJECTED
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.REJECTED
|
||||
assert Block.objects.count() == 0
|
||||
assert not mbs.is_submission_ready
|
||||
|
||||
|
@ -1971,7 +2245,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
input_guids=addon.guid,
|
||||
changed_version_ids=[addon.current_version.id],
|
||||
updated_by=user_factory(),
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PENDING,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PENDING,
|
||||
delayed_until=now + timedelta(days=1),
|
||||
)
|
||||
assert mbs.to_block[0]['guid'] == addon.guid
|
||||
|
@ -1997,7 +2271,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
'adminform'
|
||||
].form.errors
|
||||
# The blocklistsubmission wasn't approved or rejected though
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
assert Block.objects.count() == 0
|
||||
|
||||
# Then to a date that is already past
|
||||
|
@ -2012,12 +2286,12 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
# new delayed date
|
||||
assert mbs.delayed_until == now
|
||||
# No change in state because it still needs signoff
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
assert Block.objects.count() == 0
|
||||
|
||||
# But if the submission didn't need dual signoff then it will be auto approved
|
||||
# reset the submission state first
|
||||
mbs.signoff_state = BlocklistSubmission.SIGNOFF_AUTOAPPROVED
|
||||
mbs.signoff_state = BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED
|
||||
mbs.delayed_until = now + timedelta(days=5)
|
||||
mbs.to_block[0]['average_daily_users'] = threshold - 1
|
||||
mbs.save()
|
||||
|
@ -2032,7 +2306,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
# new delayed date
|
||||
assert mbs.delayed_until == now
|
||||
# The submission is auto approved
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
assert mbs.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PUBLISHED
|
||||
assert Block.objects.count() == 1
|
||||
|
||||
def test_not_disable_addon(self):
|
||||
|
@ -2064,7 +2338,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': ('any@new\npartial@existing\nfull@existing\ninvalid@'),
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.BLOCKED,
|
||||
'changed_version_ids': [
|
||||
new_addon.current_version.id,
|
||||
|
@ -2134,7 +2408,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': ('any@new\npartial@existing\nfull@existing\ninvalid@'),
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'block_type': BlockType.SOFT_BLOCKED,
|
||||
'changed_version_ids': [
|
||||
new_addon.current_version.id,
|
||||
|
@ -2244,14 +2518,14 @@ class TestBlockAdminDelete(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'guids': 'guid@\n{12345-6789}\nlegacy@',
|
||||
'action': str(BlocklistSubmission.ACTION_DELETE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.DELETE),
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
content = response.content.decode('utf-8')
|
||||
# meta data for block:
|
||||
assert 'Add-on GUIDs (one per line)' not in content
|
||||
assert 'Delete Blocks' in content
|
||||
assert 'Unblock' in content
|
||||
assert 'guid@' in content
|
||||
assert 'Normal' in content
|
||||
assert f'{block_one_ver.addon.average_daily_users} users' in content
|
||||
|
@ -2268,7 +2542,7 @@ class TestBlockAdminDelete(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'input_guids': 'guid@\n{12345-6789}\nlegacy@',
|
||||
'action': str(BlocklistSubmission.ACTION_DELETE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.DELETE),
|
||||
'changed_version_ids': [
|
||||
block_one_ver.addon.current_version.id,
|
||||
partial_new_version.id,
|
||||
|
@ -2353,7 +2627,7 @@ class TestBlockAdminDelete(TestCase):
|
|||
|
||||
submission = BlocklistSubmission.objects.get()
|
||||
submission.update(
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_APPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.APPROVED,
|
||||
signoff_by=user_factory(),
|
||||
)
|
||||
assert submission.is_submission_ready
|
||||
|
@ -2380,7 +2654,7 @@ class TestBlockAdminDelete(TestCase):
|
|||
del_submission = BlocklistSubmission.objects.create(
|
||||
input_guids=other_addon.guid,
|
||||
changed_version_ids=[ver_del_subm.id],
|
||||
action=BlocklistSubmission.ACTION_DELETE,
|
||||
action=BlocklistSubmission.ACTIONS.DELETE,
|
||||
)
|
||||
ver_deleted = version_factory(addon=other_addon)
|
||||
ver_deleted.delete() # shouldn't affect it's status
|
||||
|
@ -2396,7 +2670,7 @@ class TestBlockAdminDelete(TestCase):
|
|||
self.submission_url,
|
||||
{
|
||||
'guids': f'{addon.guid}\n {other_addon.guid}\n',
|
||||
'action': BlocklistSubmission.ACTION_DELETE,
|
||||
'action': BlocklistSubmission.ACTIONS.DELETE,
|
||||
},
|
||||
)
|
||||
doc = pq(response.content.decode('utf-8'))
|
||||
|
@ -2462,7 +2736,7 @@ class TestBlockAdminDelete(TestCase):
|
|||
mbs = BlocklistSubmission.objects.create(
|
||||
input_guids='guid@',
|
||||
updated_by=user_factory(),
|
||||
action=BlocklistSubmission.ACTION_DELETE,
|
||||
action=BlocklistSubmission.ACTIONS.DELETE,
|
||||
)
|
||||
assert mbs.to_block == [
|
||||
{
|
||||
|
|
|
@ -377,7 +377,7 @@ def test_process_blocklistsubmissions():
|
|||
input_guids=past_guid,
|
||||
updated_by=user,
|
||||
delayed_until=datetime.now() - timedelta(days=1),
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_AUTOAPPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED,
|
||||
changed_version_ids=[
|
||||
addon_factory(
|
||||
guid=past_guid,
|
||||
|
@ -391,7 +391,7 @@ def test_process_blocklistsubmissions():
|
|||
input_guids=past_signoff_guid,
|
||||
updated_by=user,
|
||||
delayed_until=datetime.now() - timedelta(days=1),
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_APPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.APPROVED,
|
||||
signoff_by=user_factory(),
|
||||
changed_version_ids=[
|
||||
addon_factory(
|
||||
|
@ -406,7 +406,7 @@ def test_process_blocklistsubmissions():
|
|||
input_guids=future_guid,
|
||||
updated_by=user,
|
||||
delayed_until=datetime.now() + timedelta(days=1),
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_AUTOAPPROVED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED,
|
||||
changed_version_ids=[
|
||||
addon_factory(
|
||||
guid=future_guid,
|
||||
|
@ -418,9 +418,14 @@ def test_process_blocklistsubmissions():
|
|||
|
||||
process_blocklistsubmissions()
|
||||
|
||||
assert past.reload().signoff_state == BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
assert past_signoff.reload().signoff_state == BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
assert future.reload().signoff_state == BlocklistSubmission.SIGNOFF_AUTOAPPROVED
|
||||
assert past.reload().signoff_state == BlocklistSubmission.SIGNOFF_STATES.PUBLISHED
|
||||
assert (
|
||||
past_signoff.reload().signoff_state
|
||||
== BlocklistSubmission.SIGNOFF_STATES.PUBLISHED
|
||||
)
|
||||
assert (
|
||||
future.reload().signoff_state == BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED
|
||||
)
|
||||
|
||||
assert Block.objects.filter(guid=past_guid).exists()
|
||||
assert Block.objects.filter(guid=past_signoff_guid).exists()
|
||||
|
|
|
@ -60,7 +60,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
|
||||
def test_changed_version_ids_choices_add_action(self):
|
||||
data = {
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'input_guids': f'{self.new_addon.guid}\n'
|
||||
f'{self.existing_block_full.guid}\n'
|
||||
f'{self.existing_block_partial.guid}\n'
|
||||
|
@ -114,7 +114,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
def test_changed_version_ids_choices_delete_action(self):
|
||||
Form = self.get_form()
|
||||
data = {
|
||||
'action': str(BlocklistSubmission.ACTION_DELETE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.DELETE),
|
||||
'input_guids': f'{self.new_addon.guid}\n'
|
||||
f'{self.existing_block_full.guid}\n'
|
||||
f'{self.existing_block_partial.guid}\n'
|
||||
|
@ -187,7 +187,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
submission = BlocklistSubmission.objects.create(
|
||||
input_guids=f'{self.new_addon.guid}\n{self.existing_block_partial.guid}',
|
||||
)
|
||||
for action in BlocklistSubmission.ACTIONS:
|
||||
for action in BlocklistSubmission.ACTIONS.values.keys():
|
||||
# they're the same for each of the actions
|
||||
submission.update(
|
||||
action=action,
|
||||
|
@ -207,7 +207,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
|
||||
def test_changed_version_ids_widget(self):
|
||||
data = {
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'input_guids': f'{self.new_addon.guid}\n'
|
||||
f'{self.existing_block_full.guid}\n'
|
||||
f'{self.existing_block_partial.guid}\n'
|
||||
|
@ -224,7 +224,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
v_id for _guid, opts in field.choices for (v_id, _text) in opts
|
||||
]
|
||||
assert field.widget.get_context(name, value, attrs) == {
|
||||
'is_add_change': True,
|
||||
'verb': 'Block',
|
||||
'widget': {
|
||||
'attrs': {'multiple': True, **attrs},
|
||||
'choices': flattened_choices,
|
||||
|
@ -295,7 +295,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
def test_new_blocks_must_have_changed_versions(self):
|
||||
Form = self.get_form()
|
||||
data = {
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'input_guids': f'{self.new_addon.guid}\n'
|
||||
f'{self.existing_block_full.guid}\n'
|
||||
f'{self.existing_block_partial.guid}\n'
|
||||
|
@ -313,7 +313,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
|
||||
# delete action is similar, but we allow deleting a block with no versions
|
||||
Block.objects.create(addon=self.new_addon, updated_by=self.user)
|
||||
data['action'] = str(BlocklistSubmission.ACTION_DELETE)
|
||||
data['action'] = str(BlocklistSubmission.ACTIONS.DELETE)
|
||||
data['changed_version_ids'] = [
|
||||
self.partial_existing_addon_v_blocked.id,
|
||||
self.full_existing_addon_v1.id,
|
||||
|
@ -339,7 +339,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
version=old_blocked_addon_version, block=self.existing_block_partial
|
||||
)
|
||||
data = {
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'action': str(BlocklistSubmission.ACTIONS.ADDCHANGE),
|
||||
'input_guids': f'{self.new_addon.guid}\n'
|
||||
f'{self.existing_block_full.guid}\n'
|
||||
f'{self.existing_block_partial.guid}',
|
||||
|
@ -360,7 +360,7 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
assert form.errors == {
|
||||
'changed_version_ids': [
|
||||
f'{self.partial_existing_addon.guid}:{ver_string} exists more than once'
|
||||
f'. All {ver_string} versions must be blocked together'
|
||||
f'. All {ver_string} versions must be selected together.'
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import datetime, timedelta
|
||||
from unittest import mock
|
||||
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
@ -13,6 +14,38 @@ from olympia.amo.tests import (
|
|||
from ..models import BlocklistSubmission, BlockType, BlockVersion
|
||||
|
||||
|
||||
class TestBlock(TestCase):
|
||||
def setUp(self):
|
||||
self.user = user_factory()
|
||||
self.addon = addon_factory()
|
||||
self.version = self.addon.current_version
|
||||
self.version_2 = version_factory(addon=self.addon, version='2.0')
|
||||
self.block = block_factory(updated_by=self.user, guid=self.addon.guid)
|
||||
|
||||
def test_has_hard_blocked_versions(self):
|
||||
assert self.block.has_hard_blocked_versions()
|
||||
self.block.blockversion_set.filter(version=self.version).update(
|
||||
block_type=BlockType.SOFT_BLOCKED
|
||||
)
|
||||
assert self.block.has_hard_blocked_versions()
|
||||
self.block.blockversion_set.filter(version=self.version_2).update(
|
||||
block_type=BlockType.SOFT_BLOCKED
|
||||
)
|
||||
assert not self.block.has_hard_blocked_versions()
|
||||
|
||||
def test_has_soft_blocked_versions(self):
|
||||
BlockVersion.objects.update(block_type=BlockType.SOFT_BLOCKED)
|
||||
assert self.block.has_soft_blocked_versions()
|
||||
self.block.blockversion_set.filter(version=self.version).update(
|
||||
block_type=BlockType.BLOCKED
|
||||
)
|
||||
assert self.block.has_soft_blocked_versions()
|
||||
self.block.blockversion_set.filter(version=self.version_2).update(
|
||||
block_type=BlockType.BLOCKED
|
||||
)
|
||||
assert not self.block.has_soft_blocked_versions()
|
||||
|
||||
|
||||
class TestBlockVersion(TestCase):
|
||||
def setUp(self):
|
||||
self.user = user_factory()
|
||||
|
@ -62,12 +95,12 @@ class TestBlocklistSubmission(TestCase):
|
|||
assert not block.signoff_by
|
||||
assert not block.is_submission_ready
|
||||
|
||||
# Except when the state is SIGNOFF_AUTOAPPROVED.
|
||||
block.update(signoff_state=BlocklistSubmission.SIGNOFF_AUTOAPPROVED)
|
||||
# Except when the state is AUTOAPPROVED.
|
||||
block.update(signoff_state=BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED)
|
||||
assert block.is_submission_ready
|
||||
|
||||
# But if the state is SIGNOFF_APPROVED we need to know the signoff user
|
||||
block.update(signoff_state=BlocklistSubmission.SIGNOFF_APPROVED)
|
||||
# But if the state is APPROVED we need to know the signoff user
|
||||
block.update(signoff_state=BlocklistSubmission.SIGNOFF_STATES.APPROVED)
|
||||
assert not block.is_submission_ready
|
||||
|
||||
# If different users update and signoff, it's permitted.
|
||||
|
@ -85,7 +118,7 @@ class TestBlocklistSubmission(TestCase):
|
|||
def test_is_delayed_submission_ready(self):
|
||||
now = datetime.now()
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_AUTOAPPROVED
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED
|
||||
)
|
||||
# auto approved submissions with no delay are ready
|
||||
assert submission.is_submission_ready
|
||||
|
@ -119,14 +152,9 @@ class TestBlocklistSubmission(TestCase):
|
|||
]
|
||||
|
||||
# But by default we ignored "finished" BlocklistSubmissions
|
||||
block_subm.update(signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED)
|
||||
block_subm.update(signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED)
|
||||
assert list(BlocklistSubmission.get_submissions_from_guid('guid@')) == []
|
||||
|
||||
# Except when we override the states to exclude
|
||||
assert list(
|
||||
BlocklistSubmission.get_submissions_from_guid('guid@', excludes=())
|
||||
) == [block_subm]
|
||||
|
||||
# And check that a guid that doesn't exist in any submissions is empty
|
||||
assert list(BlocklistSubmission.get_submissions_from_guid('ggguid@')) == []
|
||||
|
||||
|
@ -170,10 +198,30 @@ class TestBlocklistSubmission(TestCase):
|
|||
def test_is_delayed(self):
|
||||
now = datetime.now()
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_AUTOAPPROVED
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED
|
||||
)
|
||||
assert not submission.is_delayed
|
||||
submission.update(delayed_until=now + timedelta(minutes=1))
|
||||
assert submission.is_delayed
|
||||
submission.update(delayed_until=now - timedelta(minutes=1))
|
||||
assert not submission.is_delayed
|
||||
|
||||
@mock.patch('olympia.blocklist.models.save_versions_to_blocks')
|
||||
def test_calls_to_save_versions_to_blocks_depending_on_action(
|
||||
self, save_versions_to_blocks_mock
|
||||
):
|
||||
addon_factory(guid='guid1@example')
|
||||
addon_factory(guid='guid2@example')
|
||||
for action in (
|
||||
BlocklistSubmission.ACTIONS.ADDCHANGE,
|
||||
BlocklistSubmission.ACTIONS.HARDEN,
|
||||
BlocklistSubmission.ACTIONS.SOFTEN,
|
||||
):
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
input_guids='guid1@example\nguid2@example',
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.AUTOAPPROVED,
|
||||
action=action,
|
||||
)
|
||||
save_versions_to_blocks_mock.reset_mock()
|
||||
submission.save_to_block_objects()
|
||||
assert save_versions_to_blocks_mock.call_count == 1
|
||||
|
|
|
@ -73,13 +73,15 @@ def test_cleanup_old_files():
|
|||
|
||||
|
||||
class TestProcessBlocklistSubmission(TransactionTestCase):
|
||||
def test_state_reset(self):
|
||||
addon_factory(guid='guid@')
|
||||
def setUp(self):
|
||||
user_factory(id=settings.TASK_USER_ID)
|
||||
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
input_guids='guid@', signoff_state=BlocklistSubmission.SIGNOFF_APPROVED
|
||||
self.addon = addon_factory(guid='guid@')
|
||||
self.submission = BlocklistSubmission.objects.create(
|
||||
input_guids=self.addon.guid,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.APPROVED,
|
||||
)
|
||||
|
||||
def test_state_reset(self):
|
||||
with mock.patch.object(
|
||||
BlocklistSubmission,
|
||||
'save_to_block_objects',
|
||||
|
@ -87,13 +89,39 @@ class TestProcessBlocklistSubmission(TransactionTestCase):
|
|||
):
|
||||
with self.assertRaises(SuspiciousOperation):
|
||||
# we know it's going to raise, we just want to capture it safely
|
||||
process_blocklistsubmission.delay(submission.id)
|
||||
submission.reload()
|
||||
assert submission.signoff_state == BlocklistSubmission.SIGNOFF_PENDING
|
||||
process_blocklistsubmission.delay(self.submission.id)
|
||||
self.submission.reload()
|
||||
assert (
|
||||
self.submission.signoff_state == BlocklistSubmission.SIGNOFF_STATES.PENDING
|
||||
)
|
||||
log_entry = LogEntry.objects.get()
|
||||
assert log_entry.user.id == settings.TASK_USER_ID
|
||||
assert log_entry.change_message == 'Exception in task: Something happened!'
|
||||
|
||||
@mock.patch.object(BlocklistSubmission, 'save_to_block_objects')
|
||||
@mock.patch.object(BlocklistSubmission, 'delete_block_objects')
|
||||
def test_calls_save_to_block_objects_or_delete_block_objects_depending_on_action(
|
||||
self, delete_block_objects_mock, save_to_block_objects_mock
|
||||
):
|
||||
for action in (
|
||||
BlocklistSubmission.ACTIONS.ADDCHANGE,
|
||||
BlocklistSubmission.ACTIONS.HARDEN,
|
||||
BlocklistSubmission.ACTIONS.SOFTEN,
|
||||
):
|
||||
save_to_block_objects_mock.reset_mock()
|
||||
delete_block_objects_mock.reset_mock()
|
||||
self.submission.update(action=action)
|
||||
process_blocklistsubmission(self.submission.id)
|
||||
assert save_to_block_objects_mock.call_count == 1
|
||||
assert delete_block_objects_mock.call_count == 0
|
||||
for action in (BlocklistSubmission.ACTIONS.DELETE,):
|
||||
save_to_block_objects_mock.reset_mock()
|
||||
delete_block_objects_mock.reset_mock()
|
||||
self.submission.update(action=action)
|
||||
process_blocklistsubmission(self.submission.id)
|
||||
assert save_to_block_objects_mock.call_count == 0
|
||||
assert delete_block_objects_mock.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestUploadMLBFToRemoteSettings(TestCase):
|
||||
|
|
|
@ -9,7 +9,13 @@ import responses
|
|||
|
||||
from olympia import amo
|
||||
from olympia.activity.models import ActivityLog
|
||||
from olympia.amo.tests import TestCase, addon_factory, block_factory, user_factory
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
|
||||
from ..models import Block, BlocklistSubmission, BlockType
|
||||
from ..utils import datetime_to_ts, disable_versions_for_block, save_versions_to_blocks
|
||||
|
@ -58,7 +64,7 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
updated_by=user_new,
|
||||
disable_addon=True,
|
||||
changed_version_ids=[addon.current_version.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
ActivityLog.objects.all().delete()
|
||||
|
||||
|
@ -113,7 +119,7 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
disable_addon=True,
|
||||
block_type=BlockType.SOFT_BLOCKED,
|
||||
changed_version_ids=[addon.current_version.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
ActivityLog.objects.all().delete()
|
||||
|
||||
|
@ -171,7 +177,7 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
|
||||
assert not Block.objects.exists()
|
||||
|
||||
def test_save_blocks_override_existing_block_type_soft_to_hard(self):
|
||||
def test_save_blocks_harden_existing_block(self):
|
||||
user_new = user_factory()
|
||||
addon = addon_factory()
|
||||
block_factory(
|
||||
|
@ -181,32 +187,34 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
)
|
||||
assert addon.current_version.blockversion.block_type == BlockType.SOFT_BLOCKED
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
action=BlocklistSubmission.ACTIONS.HARDEN,
|
||||
input_guids=addon.guid,
|
||||
reason='some reason',
|
||||
url=None,
|
||||
updated_by=user_new,
|
||||
block_type=BlockType.BLOCKED, # Hard-block override.
|
||||
changed_version_ids=[addon.current_version.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
save_versions_to_blocks([addon.guid], submission)
|
||||
assert (
|
||||
addon.current_version.blockversion.reload().block_type == BlockType.BLOCKED
|
||||
)
|
||||
|
||||
def test_save_blocks_override_existing_block_type_hard_to_soft(self):
|
||||
def test_save_blocks_soften_existing_block(self):
|
||||
user_new = user_factory()
|
||||
addon = addon_factory()
|
||||
block_factory(guid=addon.guid, updated_by=self.task_user)
|
||||
assert addon.current_version.blockversion.block_type == BlockType.BLOCKED
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
action=BlocklistSubmission.ACTIONS.SOFTEN,
|
||||
input_guids=addon.guid,
|
||||
reason='some reason',
|
||||
url=None,
|
||||
updated_by=user_new,
|
||||
block_type=BlockType.SOFT_BLOCKED,
|
||||
changed_version_ids=[addon.current_version.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
save_versions_to_blocks([addon.guid], submission)
|
||||
assert (
|
||||
|
@ -228,7 +236,7 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
disable_addon=True,
|
||||
block_type=BlockType.BLOCKED,
|
||||
changed_version_ids=[addon.current_version.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
block = block_factory(addon=addon, updated_by=submission.updated_by)
|
||||
disable_versions_for_block(block, submission)
|
||||
|
@ -254,7 +262,7 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
disable_addon=True,
|
||||
block_type=BlockType.BLOCKED,
|
||||
changed_version_ids=[addon.current_version.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
block = block_factory(addon=addon, updated_by=submission.updated_by)
|
||||
# We can't save a block with updated_by = None, but check that we still
|
||||
|
@ -284,7 +292,7 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
disable_addon=True,
|
||||
block_type=BlockType.BLOCKED,
|
||||
changed_version_ids=[addon.current_version.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_PUBLISHED,
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
block = block_factory(addon=addon, updated_by=submission.updated_by)
|
||||
disable_versions_for_block(block, submission)
|
||||
|
@ -296,3 +304,62 @@ class TestSaveVersionsToBlocks(TestCase):
|
|||
'review_type': 'pending',
|
||||
'human_review': False, # False because it's the task user.
|
||||
}
|
||||
|
||||
def test_save_blocks_harden_block_leaving_other_versions_alone(self):
|
||||
user_new = user_factory()
|
||||
addon = addon_factory()
|
||||
version1 = addon.current_version
|
||||
version2 = version_factory(addon=addon)
|
||||
block_factory(
|
||||
guid=addon.guid,
|
||||
updated_by=self.task_user,
|
||||
block_type=BlockType.SOFT_BLOCKED,
|
||||
)
|
||||
for version in addon.versions.all():
|
||||
assert version.blockversion.block_type == BlockType.SOFT_BLOCKED
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
action=BlocklistSubmission.ACTIONS.HARDEN,
|
||||
input_guids=addon.guid,
|
||||
reason='some reason',
|
||||
url=None,
|
||||
updated_by=user_new,
|
||||
block_type=BlockType.BLOCKED,
|
||||
changed_version_ids=[version1.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
save_versions_to_blocks([addon.guid], submission)
|
||||
assert (
|
||||
version1.blockversion.reload().block_type
|
||||
== BlockType.BLOCKED # Now hard-blocked.
|
||||
)
|
||||
assert (
|
||||
version2.blockversion.reload().block_type
|
||||
== BlockType.SOFT_BLOCKED # Untouched.
|
||||
)
|
||||
|
||||
def test_save_blocks_soften_block_leaving_other_versions_alone(self):
|
||||
user_new = user_factory()
|
||||
addon = addon_factory()
|
||||
version1 = addon.current_version
|
||||
version2 = version_factory(addon=addon)
|
||||
block_factory(guid=addon.guid, updated_by=self.task_user)
|
||||
for version in addon.versions.all():
|
||||
assert version.blockversion.block_type == BlockType.BLOCKED
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
action=BlocklistSubmission.ACTIONS.SOFTEN,
|
||||
input_guids=addon.guid,
|
||||
reason='some reason',
|
||||
url=None,
|
||||
updated_by=user_new,
|
||||
block_type=BlockType.SOFT_BLOCKED,
|
||||
changed_version_ids=[version1.pk],
|
||||
signoff_state=BlocklistSubmission.SIGNOFF_STATES.PUBLISHED,
|
||||
)
|
||||
save_versions_to_blocks([addon.guid], submission)
|
||||
assert (
|
||||
version1.blockversion.reload().block_type
|
||||
== BlockType.SOFT_BLOCKED # Now soft-blocked.
|
||||
)
|
||||
assert (
|
||||
version2.blockversion.reload().block_type == BlockType.BLOCKED # Untouched.
|
||||
)
|
||||
|
|
|
@ -40,9 +40,9 @@ def block_activity_log_save(
|
|||
}
|
||||
version_details = {}
|
||||
if submission_obj:
|
||||
details['signoff_state'] = submission_obj.SIGNOFF_STATES.get(
|
||||
details['signoff_state'] = submission_obj.SIGNOFF_STATES.for_value(
|
||||
submission_obj.signoff_state
|
||||
)
|
||||
).display
|
||||
if submission_obj.signoff_by:
|
||||
details['signoff_by'] = submission_obj.signoff_by.id
|
||||
details['soft'] = version_details['soft'] = (
|
||||
|
@ -104,9 +104,9 @@ def block_activity_log_delete(obj, deleted, *, submission_obj=None, delete_user=
|
|||
)
|
||||
|
||||
if submission_obj:
|
||||
details['signoff_state'] = submission_obj.SIGNOFF_STATES.get(
|
||||
details['signoff_state'] = submission_obj.SIGNOFF_STATES.for_value(
|
||||
submission_obj.signoff_state
|
||||
)
|
||||
).display
|
||||
if submission_obj.signoff_by:
|
||||
details['signoff_by'] = submission_obj.signoff_by.id
|
||||
|
||||
|
|
|
@ -24,3 +24,20 @@
|
|||
list-style-type: none;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.submit-row a.hardenlink, .submit-row a.softenlink {
|
||||
display: inline-block;
|
||||
padding: 10px 15px;
|
||||
height: 0.9375rem;
|
||||
line-height: 0.9375rem;
|
||||
order: 3;
|
||||
}
|
||||
|
||||
.submit-row a.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.submit-row a.deletelink {
|
||||
order: 2;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче