per version blocks (#20829)
* track Blocked versions per version * update api docs * test fixes * scanners test fixes * drop now obsolete create_blockversions command * address review comments; fix failing tests * restore warning messages about a version being in a submission or block * add extra tests + optimizations * Fix failing blocklist/test_serializers.py test
This commit is contained in:
Родитель
a07f8d2eab
Коммит
706798d37e
|
@ -118,12 +118,12 @@ class FileInline(admin.TabularInline):
|
|||
version__deleted.boolean = True
|
||||
|
||||
def version__is_blocked(self, obj):
|
||||
block = self.instance.block
|
||||
if not (block and block.is_version_blocked(obj.version.version)):
|
||||
blockversion = getattr(obj.version, 'blockversion', None)
|
||||
if not blockversion:
|
||||
return ''
|
||||
url = block.get_admin_url_path()
|
||||
template = '<a href="{}">Blocked ({} - {})</a>'
|
||||
return format_html(template, url, block.min_version, block.max_version)
|
||||
url = blockversion.block.get_admin_url_path()
|
||||
template = '<a href="{}">Blocked</a>'
|
||||
return format_html(template, url)
|
||||
|
||||
version__is_blocked.short_description = 'Block status'
|
||||
|
||||
|
@ -167,7 +167,9 @@ class FileInline(admin.TabularInline):
|
|||
sub_qs = NeedsHumanReview.objects.filter(
|
||||
is_active=True, version=OuterRef('version')
|
||||
)
|
||||
return qs.select_related('version').annotate(needs_human_review=Exists(sub_qs))
|
||||
return qs.select_related('version', 'version__blockversion').annotate(
|
||||
needs_human_review=Exists(sub_qs)
|
||||
)
|
||||
|
||||
|
||||
class AddonAdmin(AMOModelAdmin):
|
||||
|
|
|
@ -1808,12 +1808,11 @@ class Addon(OnChangeMixin, ModelBase):
|
|||
# Block.guid is unique so it's either on the list or not.
|
||||
return Block.objects.filter(guid=self.addonguid_guid).last()
|
||||
|
||||
@cached_property
|
||||
def blocklistsubmission(self):
|
||||
@property
|
||||
def blocklistsubmissions(self):
|
||||
from olympia.blocklist.models import BlocklistSubmission
|
||||
|
||||
# GUIDs should only exist in one (active) submission at once.
|
||||
return BlocklistSubmission.get_submissions_from_guid(self.addonguid_guid).last()
|
||||
return BlocklistSubmission.get_submissions_from_guid(self.addonguid_guid)
|
||||
|
||||
@property
|
||||
def git_extraction_is_in_progress(self):
|
||||
|
|
|
@ -32,7 +32,6 @@ from olympia.api.serializers import AMOModelSerializer, BaseESSerializer
|
|||
from olympia.api.utils import is_gate_active
|
||||
from olympia.applications.models import AppVersion
|
||||
from olympia.bandwagon.models import Collection
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.constants.applications import APP_IDS, APPS_ALL
|
||||
from olympia.constants.base import ADDON_TYPE_CHOICES_API
|
||||
from olympia.constants.categories import CATEGORIES_BY_ID
|
||||
|
@ -525,22 +524,6 @@ class DeveloperVersionSerializer(VersionSerializer):
|
|||
raise exceptions.ValidationError(msg)
|
||||
return disable
|
||||
|
||||
def _check_blocklist(self, guid, version_string):
|
||||
# check the guid/version isn't in the addon blocklist
|
||||
block_qs = Block.objects.filter(guid=guid) if guid else ()
|
||||
if block_qs and block_qs.first().is_version_blocked(version_string):
|
||||
msg = gettext(
|
||||
'Version {version} matches {block_link} for this add-on. '
|
||||
'You can contact {amo_admins} for additional information.'
|
||||
)
|
||||
raise exceptions.ValidationError(
|
||||
msg.format(
|
||||
version=version_string,
|
||||
block_link=absolutify(reverse('blocklist.block', args=[guid])),
|
||||
amo_admins='amo-admins@mozilla.com',
|
||||
),
|
||||
)
|
||||
|
||||
def _check_for_existing_versions(self, version_string):
|
||||
# Make sure we don't already have this version.
|
||||
existing_versions = Version.unfiltered.filter(
|
||||
|
@ -557,9 +540,7 @@ class DeveloperVersionSerializer(VersionSerializer):
|
|||
|
||||
def validate(self, data):
|
||||
if not self.instance:
|
||||
guid = self.addon.guid if self.addon else self.parsed_data.get('guid')
|
||||
version_string = self.parsed_data.get('version')
|
||||
self._check_blocklist(guid, version_string)
|
||||
if self.addon:
|
||||
self._check_for_existing_versions(version_string)
|
||||
|
||||
|
|
|
@ -23,11 +23,11 @@ from olympia.amo.reverse import django_reverse
|
|||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
collection_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.constants.browsers import CHROME
|
||||
from olympia.git.models import GitExtractionEntry
|
||||
|
||||
|
@ -731,15 +731,11 @@ class TestAddonAdmin(TestCase):
|
|||
assert response.status_code == 200
|
||||
assert 'Blocked' not in response.content.decode('utf-8')
|
||||
|
||||
block = Block.objects.create(
|
||||
addon=addon, min_version=addon.current_version.version, updated_by=user
|
||||
)
|
||||
block = block_factory(addon=addon, updated_by=user)
|
||||
|
||||
response = self.client.get(self.detail_url, follow=True)
|
||||
assert response.status_code == 200
|
||||
assert f'Blocked ({addon.current_version.version} - *)' in (
|
||||
response.content.decode('utf-8')
|
||||
)
|
||||
assert 'Blocked' in response.content.decode('utf-8')
|
||||
link = pq(response.content)('.field-version__is_blocked a')[0]
|
||||
assert link.attrib['href'] == block.get_admin_url_path()
|
||||
|
||||
|
@ -750,7 +746,7 @@ class TestAddonAdmin(TestCase):
|
|||
self.grant_permission(user, 'Addons:Edit')
|
||||
self.grant_permission(user, 'Admin:Advanced')
|
||||
self.client.force_login(user)
|
||||
with self.assertNumQueries(22):
|
||||
with self.assertNumQueries(20):
|
||||
# It's very high because most of AddonAdmin is unoptimized but we
|
||||
# don't want it unexpectedly increasing.
|
||||
# FIXME: explain each query
|
||||
|
@ -760,7 +756,7 @@ class TestAddonAdmin(TestCase):
|
|||
|
||||
version_factory(addon=addon)
|
||||
version_factory(addon=addon)
|
||||
with self.assertNumQueries(22):
|
||||
with self.assertNumQueries(20):
|
||||
# Confirm it scales correctly by doing the same number of queries
|
||||
# when number of versions increases.
|
||||
# FIXME: explain each query
|
||||
|
|
|
@ -1853,25 +1853,22 @@ class TestAddonModels(TestCase):
|
|||
AddonGUID.objects.create(addon=addon, guid='not-a-guid')
|
||||
assert addon.block == block
|
||||
|
||||
def test_blocklistsubmission_property(self):
|
||||
def test_blocklistsubmissions_property(self):
|
||||
addon = Addon.objects.get(id=3615)
|
||||
assert addon.blocklistsubmission is None
|
||||
assert not addon.blocklistsubmissions.exists()
|
||||
|
||||
del addon.blocklistsubmission
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
input_guids=addon.guid, updated_by=user_factory()
|
||||
)
|
||||
assert addon.blocklistsubmission == submission
|
||||
assert list(addon.blocklistsubmissions) == [submission]
|
||||
|
||||
del addon.blocklistsubmission
|
||||
submission.update(input_guids='not-a-guid')
|
||||
submission.update(to_block=[{'guid': 'not-a-guid'}])
|
||||
assert addon.blocklistsubmission is None
|
||||
assert not addon.blocklistsubmissions.exists()
|
||||
|
||||
del addon.blocklistsubmission
|
||||
addon.delete()
|
||||
AddonGUID.objects.create(addon=addon, guid='not-a-guid')
|
||||
assert addon.blocklistsubmission == submission
|
||||
assert list(addon.blocklistsubmissions) == [submission]
|
||||
|
||||
|
||||
class TestAddonUser(TestCase):
|
||||
|
|
|
@ -45,7 +45,6 @@ from olympia.amo.tests import (
|
|||
from olympia.amo.tests.test_helpers import get_image_path
|
||||
from olympia.amo.urlresolvers import get_outgoing_url
|
||||
from olympia.bandwagon.models import CollectionAddon
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.constants.browsers import CHROME
|
||||
from olympia.constants.categories import CATEGORIES, CATEGORIES_BY_ID
|
||||
from olympia.constants.licenses import LICENSE_GPL3
|
||||
|
@ -3433,16 +3432,6 @@ class TestVersionViewSetCreate(UploadMixin, VersionViewSetCreateUpdateMixin, Tes
|
|||
assert version.approval_notes == 'This!'
|
||||
self.statsd_incr_mock.assert_any_call('addons.submission.version.unlisted')
|
||||
|
||||
def test_check_blocklist(self):
|
||||
Block.objects.create(guid=self.addon.guid, updated_by=self.user)
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
data=self.minimal_data,
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert 'Version 0.0.1 matches ' in str(response.data['non_field_errors'])
|
||||
assert self.addon.reload().versions.count() == 1
|
||||
|
||||
def test_cant_update_disabled_addon(self):
|
||||
self.addon.update(status=amo.STATUS_DISABLED)
|
||||
response = self.client.post(
|
||||
|
|
|
@ -53,6 +53,7 @@ from olympia.amo.utils import SafeStorage, use_fake_fxa
|
|||
from olympia.api.tests import JWTAuthKeyTester
|
||||
from olympia.applications.models import AppVersion
|
||||
from olympia.bandwagon.models import Collection
|
||||
from olympia.blocklist.models import Block, BlockVersion
|
||||
from olympia.constants.categories import CATEGORIES
|
||||
from olympia.files.models import File
|
||||
from olympia.promoted.models import (
|
||||
|
@ -960,6 +961,18 @@ def version_factory(file_kw=None, **kw):
|
|||
return ver
|
||||
|
||||
|
||||
def block_factory(*, version_ids=None, **kwargs):
|
||||
block = Block.objects.create(**kwargs)
|
||||
if version_ids is None and block.addon:
|
||||
version_ids = list(block.addon.versions.values_list('id', flat=True))
|
||||
if version_ids is not None:
|
||||
BlockVersion.objects.bulk_create(
|
||||
BlockVersion(block=block, version_id=version_id)
|
||||
for version_id in version_ids
|
||||
)
|
||||
return block
|
||||
|
||||
|
||||
@pytest.mark.es_tests
|
||||
class ESTestCaseMixin:
|
||||
@classmethod
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from types import SimpleNamespace
|
||||
|
||||
from django import http
|
||||
from django.contrib import admin, auth, contenttypes, messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms.fields import ChoiceField
|
||||
from django.forms.widgets import HiddenInput
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.loader import render_to_string
|
||||
|
@ -13,35 +14,17 @@ from olympia.activity.models import ActivityLog
|
|||
from olympia.addons.models import Addon
|
||||
from olympia.amo.admin import AMOModelAdmin
|
||||
from olympia.amo.utils import HttpResponseTemporaryRedirect
|
||||
from olympia.versions.models import Version
|
||||
|
||||
from .forms import BlocklistSubmissionForm, MultiAddForm, MultiDeleteForm
|
||||
from .models import Block, BlocklistSubmission
|
||||
from .forms import (
|
||||
BlocklistSubmissionForm,
|
||||
MultiAddForm,
|
||||
MultiDeleteForm,
|
||||
)
|
||||
from .models import Block, BlocklistSubmission, BlockVersion
|
||||
from .tasks import process_blocklistsubmission
|
||||
from .utils import splitlines
|
||||
|
||||
|
||||
# The limit for how many GUIDs should be fully loaded with all metadata
|
||||
GUID_FULL_LOAD_LIMIT = 100
|
||||
|
||||
|
||||
def _get_version_choices(block, field_name):
|
||||
# field_name will be `min_version` or `max_version`
|
||||
default = block._meta.get_field(field_name).default
|
||||
choices = [(default, default)] + list(
|
||||
(version.version, version.version) for version in block.addon_versions
|
||||
)
|
||||
block_version = getattr(block, field_name)
|
||||
if block_version and (block_version, block_version) not in choices:
|
||||
# if the current version isn't in choices it's not a valid version of
|
||||
# the addon. This is either because:
|
||||
# - the Block was created as a multiple submission so was a free input
|
||||
# - it's a new Block and the min|max_version was passed as a GET param
|
||||
# - the version was hard-deleted from the addon afterwards (unlikely)
|
||||
choices = [(block_version, '(invalid)')] + choices
|
||||
return choices
|
||||
|
||||
|
||||
class BlocklistSubmissionStateFilter(admin.SimpleListFilter):
|
||||
title = 'Signoff State'
|
||||
parameter_name = 'signoff_state'
|
||||
|
@ -106,17 +89,9 @@ class BlockAdminAddMixin:
|
|||
if request.method == 'POST':
|
||||
form = MultiForm(request.POST)
|
||||
if form.is_valid():
|
||||
guids = splitlines(form.data.get('guids'))
|
||||
if len(guids) == 1 and form.existing_block:
|
||||
# If the guid already has a Block go to the change view
|
||||
return redirect(
|
||||
'admin:blocklist_block_change', form.existing_block.id
|
||||
)
|
||||
elif len(guids) > 0:
|
||||
# Otherwise go to multi view.
|
||||
return HttpResponseTemporaryRedirect(
|
||||
reverse('admin:blocklist_blocklistsubmission_add')
|
||||
)
|
||||
return HttpResponseTemporaryRedirect(
|
||||
reverse('admin:blocklist_blocklistsubmission_add')
|
||||
)
|
||||
else:
|
||||
form = MultiForm()
|
||||
|
||||
|
@ -138,55 +113,36 @@ class BlockAdminAddMixin:
|
|||
|
||||
def add_from_addon_pk_view(self, request, pk, **kwargs):
|
||||
addon = get_object_or_404(Addon.unfiltered, pk=pk or kwargs.get('pk'))
|
||||
get_params = request.GET.copy()
|
||||
if changed_version_ids := get_params.pop('v', None):
|
||||
version_strings = Version.unfiltered.filter(
|
||||
id__in=(int(id_) for id_ in changed_version_ids)
|
||||
).values_list('version', flat=True)
|
||||
get_params['min_version'] = min(version_strings)
|
||||
get_params['max_version'] = max(version_strings)
|
||||
warning_message = (
|
||||
'The version id:{version_id} could not be selected because {reason}'
|
||||
)
|
||||
|
||||
if 'min_version' in get_params or 'max_version' in get_params:
|
||||
warning_message = (
|
||||
f"The versions {get_params.get('min_version', '0')} to "
|
||||
f"{get_params.get('max_version', '*')} could not be "
|
||||
'pre-selected because {reason}'
|
||||
)
|
||||
else:
|
||||
warning_message = None
|
||||
|
||||
if addon.blocklistsubmission:
|
||||
if 'min_version' in get_params or 'max_version' in get_params:
|
||||
if v_ids := [int(v) for v in request.GET.getlist('v')]:
|
||||
submissions = BlocklistSubmission.get_all_submission_versions()
|
||||
clashes = set(v_ids) & set(submissions)
|
||||
for version_id in clashes:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.WARNING,
|
||||
warning_message.format(
|
||||
reason='this addon is part of a pending submission'
|
||||
version_id=version_id,
|
||||
reason='this version is part of a pending submission',
|
||||
),
|
||||
)
|
||||
return redirect(
|
||||
reverse(
|
||||
'admin:blocklist_blocklistsubmission_change',
|
||||
args=(addon.blocklistsubmission.pk,),
|
||||
)
|
||||
)
|
||||
elif addon.block:
|
||||
if 'min_version' in get_params or 'max_version' in get_params:
|
||||
for block in BlockVersion.objects.filter(version_id__in=v_ids):
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.WARNING,
|
||||
warning_message.format(
|
||||
reason='some versions have been blocked already'
|
||||
version_id=block.version_id,
|
||||
reason='this version is already blocked',
|
||||
),
|
||||
)
|
||||
return redirect(
|
||||
reverse('admin:blocklist_block_change', args=(addon.block.pk,))
|
||||
)
|
||||
else:
|
||||
return redirect(
|
||||
reverse('admin:blocklist_blocklistsubmission_add')
|
||||
+ f'?guids={addon.addonguid_guid}&{get_params.urlencode()}'
|
||||
)
|
||||
|
||||
return redirect(
|
||||
reverse('admin:blocklist_blocklistsubmission_add')
|
||||
+ f'?guids={addon.addonguid_guid}&{request.GET.urlencode()}'
|
||||
)
|
||||
|
||||
|
||||
@admin.register(BlocklistSubmission)
|
||||
|
@ -308,8 +264,8 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
{
|
||||
'fields': (
|
||||
'blocks',
|
||||
'min_version',
|
||||
'max_version',
|
||||
'disable_addon',
|
||||
'changed_version_ids',
|
||||
'url',
|
||||
'reason',
|
||||
'updated_by',
|
||||
|
@ -338,6 +294,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
{
|
||||
'fields': (
|
||||
'blocks',
|
||||
'changed_version_ids',
|
||||
'updated_by',
|
||||
'signoff_by',
|
||||
'submission_logs',
|
||||
|
@ -368,10 +325,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
ro_fields += [
|
||||
'input_guids',
|
||||
'action',
|
||||
'min_version',
|
||||
'max_version',
|
||||
'existing_min_version',
|
||||
'existing_max_version',
|
||||
'changed_version_ids',
|
||||
'delay_days',
|
||||
]
|
||||
if not self.has_change_permission(request, obj, strict=True):
|
||||
|
@ -388,25 +342,9 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
)
|
||||
)
|
||||
|
||||
def formfield_for_dbfield(self, db_field, request, **kwargs):
|
||||
single_guid = len(self._get_input_guids(request)) == 1
|
||||
if single_guid and db_field.name in ('min_version', 'max_version'):
|
||||
return ChoiceField(**kwargs)
|
||||
return super().formfield_for_dbfield(db_field, request, **kwargs)
|
||||
|
||||
def get_form(self, request, obj=None, change=False, **kwargs):
|
||||
form = super().get_form(request, obj, change, **kwargs)
|
||||
if not change:
|
||||
guids = self._get_input_guids(request)
|
||||
if len(guids) == 1:
|
||||
block_obj = Block(guid=guids[0])
|
||||
if 'min_version' in form.base_fields:
|
||||
form.base_fields['min_version'].choices = _get_version_choices(
|
||||
block_obj, 'min_version'
|
||||
)
|
||||
form.base_fields['max_version'].choices = _get_version_choices(
|
||||
block_obj, 'max_version'
|
||||
)
|
||||
form.base_fields['input_guids'].widget = HiddenInput()
|
||||
form.base_fields['action'].widget = HiddenInput()
|
||||
if 'delayed_until' in form.base_fields:
|
||||
|
@ -419,18 +357,18 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
|
||||
MultiBlockForm = self.get_form(request, change=False, **kwargs)
|
||||
is_delete = not self.is_add_change_submission(request, None)
|
||||
|
||||
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.
|
||||
initial = {key: values for key, values in request.GET.items()}
|
||||
initial.update(
|
||||
**{
|
||||
'input_guids': guids_data,
|
||||
'existing_min_version': initial.get('min_version', Block.MIN),
|
||||
'existing_max_version': initial.get('max_version', Block.MAX),
|
||||
}
|
||||
)
|
||||
initial = {
|
||||
key: value
|
||||
for key, value in request.GET.items()
|
||||
if key not in ('v', 'guids')
|
||||
}
|
||||
if version_ids := request.GET.getlist('v'):
|
||||
# `v` can contain multiple version ids
|
||||
initial['changed_version_ids'] = version_ids
|
||||
initial.update(**{'input_guids': guids_data})
|
||||
if 'action' in request.POST:
|
||||
initial['action'] = request.POST['action']
|
||||
form = MultiBlockForm(initial=initial)
|
||||
|
@ -444,15 +382,8 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
self.save_model(request, obj, form, change=False)
|
||||
self.log_addition(request, obj, [{'added': {}}])
|
||||
return self.response_add(request, obj)
|
||||
elif not is_delete:
|
||||
else:
|
||||
guids_data = request.POST.get('input_guids')
|
||||
form_data = form.data.copy()
|
||||
# each time we render the form we pass along the existing
|
||||
# versions so we can detect if they've been changed and we'd '
|
||||
# need a recalculation how existing blocks are affected.
|
||||
form_data['existing_min_version'] = form_data['min_version']
|
||||
form_data['existing_max_version'] = form_data['max_version']
|
||||
form.data = form_data
|
||||
else:
|
||||
# if its not a POST and no ?guids there's nothing to do so go back
|
||||
return redirect('admin:blocklist_block_add')
|
||||
|
@ -468,27 +399,15 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
'title': 'Delete Blocks' if is_delete else 'Block Add-ons',
|
||||
'save_as': False,
|
||||
'block_history': self.block_history(self.model(input_guids=guids_data)),
|
||||
'submission_complete': False,
|
||||
'submission_published': False,
|
||||
'site_title': None,
|
||||
'is_popup': False,
|
||||
'form_url': '',
|
||||
}
|
||||
context.update(**self._get_enhanced_guid_context(request, guids_data))
|
||||
return TemplateResponse(
|
||||
request, 'admin/blocklist/blocklistsubmission_add_form.html', context
|
||||
)
|
||||
|
||||
def _get_enhanced_guid_context(self, request, guids_data, obj=None):
|
||||
load_full_objects = len(splitlines(guids_data)) <= GUID_FULL_LOAD_LIMIT
|
||||
objects = self.model.process_input_guids(
|
||||
guids_data,
|
||||
v_min=self.get_value('min_version', request, obj, Block.MIN),
|
||||
v_max=self.get_value('max_version', request, obj, Block.MAX),
|
||||
load_full_objects=load_full_objects,
|
||||
filter_existing=self.is_add_change_submission(request, obj),
|
||||
)
|
||||
if load_full_objects:
|
||||
Block.preload_addon_versions(objects['blocks'])
|
||||
objects['total_adu'] = sum(block.current_adu for block in objects['blocks'])
|
||||
return objects
|
||||
|
||||
def change_view(self, request, object_id, form_url='', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
obj = self.get_object(request, object_id)
|
||||
|
@ -506,19 +425,6 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
extra_context['can_reject'] = self.is_changeable(
|
||||
obj
|
||||
) and self.has_signoff_reject_permission(request, obj)
|
||||
if obj.signoff_state != BlocklistSubmission.SIGNOFF_PUBLISHED:
|
||||
extra_context.update(
|
||||
**self._get_enhanced_guid_context(request, obj.input_guids, obj)
|
||||
)
|
||||
else:
|
||||
extra_context['blocks'] = obj.get_blocks_submitted(
|
||||
load_full_objects_threshold=GUID_FULL_LOAD_LIMIT
|
||||
)
|
||||
if len(extra_context['blocks']) <= GUID_FULL_LOAD_LIMIT:
|
||||
# if it's less than the limit we loaded full Block instances
|
||||
# so preload the addon_versions so the review links are
|
||||
# generated efficiently.
|
||||
Block.preload_addon_versions(extra_context['blocks'])
|
||||
return super().change_view(
|
||||
request, object_id, form_url=form_url, extra_context=extra_context
|
||||
)
|
||||
|
@ -528,7 +434,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
):
|
||||
if change:
|
||||
# add this to the instance so blocks() below can reference it.
|
||||
obj._blocks = context['blocks']
|
||||
obj._blocks = context['adminform'].form.blocks
|
||||
return super().render_change_form(
|
||||
request, context, add=add, change=change, form_url=form_url, obj=obj
|
||||
)
|
||||
|
@ -594,12 +500,13 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
def blocks(self, obj):
|
||||
# Annoyingly, we don't have the full context, but we stashed blocks
|
||||
# earlier in render_change_form().
|
||||
complete = obj.signoff_state == BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
is_published = obj.signoff_state == BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
|
||||
return render_to_string(
|
||||
'admin/blocklist/includes/enhanced_blocks.html',
|
||||
{
|
||||
'blocks': obj._blocks,
|
||||
'submission_complete': complete,
|
||||
'form': SimpleNamespace(blocks=obj._blocks),
|
||||
'submission_published': is_published,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -620,7 +527,7 @@ class BlocklistSubmissionAdmin(AMOModelAdmin):
|
|||
|
||||
@admin.register(Block)
|
||||
class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
||||
list_display = ('guid', 'min_version', 'max_version', 'updated_by', 'modified')
|
||||
list_display = ('guid', 'updated_by', 'modified')
|
||||
readonly_fields = (
|
||||
'addon_guid',
|
||||
'addon_name',
|
||||
|
@ -630,6 +537,7 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
'review_unlisted_link',
|
||||
'block_history',
|
||||
'url_link',
|
||||
'blocked_versions',
|
||||
)
|
||||
ordering = ['-modified']
|
||||
view_on_site = False
|
||||
|
@ -655,6 +563,11 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
def users(self, obj):
|
||||
return obj.average_daily_users_snapshot
|
||||
|
||||
def blocked_versions(self, obj):
|
||||
return ', '.join(
|
||||
sorted(obj.blockversion_set.values_list('version__version', flat=True))
|
||||
)
|
||||
|
||||
def block_history(self, obj):
|
||||
logs = (
|
||||
ActivityLog.objects.for_guidblock(obj.guid)
|
||||
|
@ -694,8 +607,7 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
'Edit Block',
|
||||
{
|
||||
'fields': (
|
||||
'min_version',
|
||||
'max_version',
|
||||
'blocked_versions',
|
||||
('url', 'url_link'),
|
||||
'reason',
|
||||
),
|
||||
|
@ -705,10 +617,7 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
return (details, history, edit)
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
if obj and obj.is_readonly:
|
||||
return False
|
||||
else:
|
||||
return super().has_change_permission(request, obj=obj)
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
if obj and obj.is_readonly:
|
||||
|
@ -726,22 +635,6 @@ class BlockAdmin(BlockAdminAddMixin, AMOModelAdmin):
|
|||
# wrong.
|
||||
raise PermissionDenied
|
||||
|
||||
def get_form(self, request, obj=None, change=False, **kwargs):
|
||||
form = super().get_form(request, obj=obj, change=change, **kwargs)
|
||||
if 'min_version' in form.base_fields:
|
||||
form.base_fields['min_version'].choices = _get_version_choices(
|
||||
obj, 'min_version'
|
||||
)
|
||||
form.base_fields['max_version'].choices = _get_version_choices(
|
||||
obj, 'max_version'
|
||||
)
|
||||
return form
|
||||
|
||||
def formfield_for_dbfield(self, db_field, request, **kwargs):
|
||||
if db_field.name in ('min_version', 'max_version'):
|
||||
return ChoiceField(**kwargs)
|
||||
return super().formfield_for_dbfield(db_field, request, **kwargs)
|
||||
|
||||
def changeform_view(self, request, obj_id=None, form_url='', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
if obj_id:
|
||||
|
|
|
@ -11,17 +11,8 @@ from .models import Block, BlocklistSubmission
|
|||
from .utils import splitlines
|
||||
|
||||
|
||||
def _get_matching_guids_and_errors(guids):
|
||||
error_list = []
|
||||
matching = list(Block.objects.filter(guid__in=guids).values_list('guid', flat=True))
|
||||
for guid in guids:
|
||||
if BlocklistSubmission.get_submissions_from_guid(guid):
|
||||
error_list.append(
|
||||
ValidationError(
|
||||
_('GUID %(guid)s is in a pending Submission'), params={'guid': guid}
|
||||
)
|
||||
)
|
||||
return matching, error_list
|
||||
# The limit for how many GUIDs should be fully loaded with all metadata
|
||||
GUID_FULL_LOAD_LIMIT = 100
|
||||
|
||||
|
||||
class MultiGUIDInputForm(forms.Form):
|
||||
|
@ -37,18 +28,15 @@ class MultiGUIDInputForm(forms.Form):
|
|||
class MultiDeleteForm(MultiGUIDInputForm):
|
||||
def clean(self):
|
||||
guids = splitlines(self.cleaned_data.get('guids'))
|
||||
matching, errors = _get_matching_guids_and_errors(guids)
|
||||
matching = Block.objects.filter(guid__in=guids).values_list('guid', flat=True)
|
||||
|
||||
missing_guids = [guid for guid in guids if guid not in matching]
|
||||
if missing_guids:
|
||||
errors.append(
|
||||
[
|
||||
ValidationError(
|
||||
_('Block with GUID %(guid)s not found'), params={'guid': guid}
|
||||
)
|
||||
for guid in missing_guids
|
||||
]
|
||||
missing_guids = (guid for guid in guids if guid not in matching)
|
||||
errors = [
|
||||
ValidationError(
|
||||
_('Block with GUID %(guid)s not found'), params={'guid': guid}
|
||||
)
|
||||
for guid in missing_guids
|
||||
]
|
||||
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
@ -57,8 +45,8 @@ class MultiDeleteForm(MultiGUIDInputForm):
|
|||
class MultiAddForm(MultiGUIDInputForm):
|
||||
def clean(self):
|
||||
guids = splitlines(self.cleaned_data.get('guids'))
|
||||
matching, errors = _get_matching_guids_and_errors(guids)
|
||||
|
||||
errors = []
|
||||
if len(guids) == 1:
|
||||
guid = guids[0]
|
||||
blk = self.existing_block = Block.objects.filter(guid=guid).first()
|
||||
|
@ -74,13 +62,21 @@ class MultiAddForm(MultiGUIDInputForm):
|
|||
raise ValidationError(errors)
|
||||
|
||||
|
||||
def _get_version_choices(blocks, ver_filter=lambda v: True):
|
||||
return [
|
||||
(
|
||||
block.guid,
|
||||
[
|
||||
(version.id, version.version)
|
||||
for version in block.addon_versions
|
||||
if ver_filter(version)
|
||||
],
|
||||
)
|
||||
for block in blocks
|
||||
]
|
||||
|
||||
|
||||
class BlocklistSubmissionForm(AMOModelForm):
|
||||
existing_min_version = forms.fields.CharField(
|
||||
widget=forms.widgets.HiddenInput, required=False
|
||||
)
|
||||
existing_max_version = forms.fields.CharField(
|
||||
widget=forms.widgets.HiddenInput, required=False
|
||||
)
|
||||
delay_days = forms.fields.IntegerField(
|
||||
widget=forms.widgets.NumberInput,
|
||||
initial=0,
|
||||
|
@ -91,62 +87,76 @@ class BlocklistSubmissionForm(AMOModelForm):
|
|||
delayed_until = forms.fields.DateTimeField(
|
||||
widget=HTML5DateTimeInput, required=False
|
||||
)
|
||||
# Note we don't render the widget - we manually create the checkboxes in
|
||||
# enhanced_blocks.html
|
||||
changed_version_ids = forms.fields.TypedMultipleChoiceField(choices=(), coerce=int)
|
||||
|
||||
def _check_if_existing_blocks_changed(
|
||||
self, all_guids, v_min, v_max, existing_v_min, existing_v_max
|
||||
):
|
||||
# shortcut if the min/max versions havn't changed
|
||||
if v_min == existing_v_min and v_max == existing_v_max:
|
||||
return False
|
||||
def __init__(self, data=None, *args, **kw):
|
||||
instance = kw.get('instance')
|
||||
|
||||
block_data = list(
|
||||
Block.objects.filter(guid__in=all_guids).values_list(
|
||||
'guid', 'min_version', 'max_version'
|
||||
def get_value(field_name, default):
|
||||
return (
|
||||
getattr(instance, field_name, default)
|
||||
if instance
|
||||
else (
|
||||
(data or {}).get(field_name)
|
||||
or (kw.get('initial') or {}).get(field_name, default)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
to_update_based_on_existing_v = [
|
||||
guid
|
||||
for (guid, min_version, max_version) in block_data
|
||||
if not (min_version == existing_v_min and max_version == existing_v_max)
|
||||
]
|
||||
to_update_based_on_new_v = [
|
||||
guid
|
||||
for (guid, min_version, max_version) in block_data
|
||||
if not (min_version == v_min and max_version == v_max)
|
||||
]
|
||||
is_add_change = get_value(
|
||||
'action', str(BlocklistSubmission.ACTION_ADDCHANGE)
|
||||
) == str(BlocklistSubmission.ACTION_ADDCHANGE)
|
||||
input_guids = get_value('input_guids', '')
|
||||
super().__init__(data, *args, **kw)
|
||||
|
||||
return to_update_based_on_existing_v != to_update_based_on_new_v
|
||||
load_full_objects = len(splitlines(input_guids)) <= GUID_FULL_LOAD_LIMIT
|
||||
|
||||
if (
|
||||
not instance
|
||||
or instance.signoff_state != BlocklistSubmission.SIGNOFF_PUBLISHED
|
||||
):
|
||||
objects = BlocklistSubmission.process_input_guids(
|
||||
input_guids,
|
||||
load_full_objects=load_full_objects,
|
||||
filter_existing=is_add_change,
|
||||
)
|
||||
objects['total_adu'] = sum(block.current_adu for block in objects['blocks'])
|
||||
|
||||
if changed_version_ids_field := self.fields.get('changed_version_ids'):
|
||||
changed_version_ids_field.choices = _get_version_choices(
|
||||
objects['blocks'],
|
||||
# ^ 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
|
||||
lambda v: (v.is_blocked ^ is_add_change)
|
||||
and not v.blocklist_submission_id,
|
||||
)
|
||||
self.changed_version_ids_choices = [
|
||||
v_id
|
||||
for _guid, opts in changed_version_ids_field.choices
|
||||
for (v_id, _text) in opts
|
||||
]
|
||||
if not data and 'changed_version_ids' not in (self.initial or {}):
|
||||
# preselect all the options
|
||||
self.initial = {
|
||||
**(self.initial or {}),
|
||||
'changed_version_ids': self.changed_version_ids_choices,
|
||||
}
|
||||
for key, value in objects.items():
|
||||
setattr(self, key, value)
|
||||
elif instance:
|
||||
self.blocks = instance.get_blocks_submitted(
|
||||
load_full_objects_threshold=GUID_FULL_LOAD_LIMIT
|
||||
)
|
||||
if load_full_objects:
|
||||
# if it's less than the limit we loaded full Block instances
|
||||
# so preload the addon_versions so the review links are
|
||||
# generated efficiently.
|
||||
Block.preload_addon_versions(self.blocks)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
data = self.cleaned_data
|
||||
guids = splitlines(data.get('input_guids'))
|
||||
# Ignore for a single guid because we always update it irrespective of
|
||||
# whether it needs to be updated.
|
||||
is_addchange_submission = (
|
||||
data.get('action', BlocklistSubmission.ACTION_ADDCHANGE)
|
||||
== BlocklistSubmission.ACTION_ADDCHANGE
|
||||
)
|
||||
blocks, errors = _get_matching_guids_and_errors(guids)
|
||||
if len(guids) > 1 and is_addchange_submission:
|
||||
blocks_have_changed = self._check_if_existing_blocks_changed(
|
||||
guids,
|
||||
data.get('min_version'),
|
||||
data.get('max_version'),
|
||||
data.get('existing_min_version'),
|
||||
data.get('existing_max_version'),
|
||||
)
|
||||
if blocks_have_changed:
|
||||
errors.append(
|
||||
ValidationError(
|
||||
_(
|
||||
'Blocks to be updated are different because Min or '
|
||||
'Max version has changed.'
|
||||
)
|
||||
)
|
||||
)
|
||||
if delay_days := data.get('delay_days', 0):
|
||||
data['delayed_until'] = datetime.now() + timedelta(days=delay_days)
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
import olympia.core.logger
|
||||
from olympia.blocklist.models import Block, BlockVersion
|
||||
from olympia.files.models import File
|
||||
|
||||
|
||||
log = olympia.core.logger.getLogger('z.amo.blocklist')
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Migration to create BlockVersion instances for every Block'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
count = 0
|
||||
for block in Block.objects.all().iterator(1000):
|
||||
versions_qs = (
|
||||
File.objects.filter(version__addon__addonguid__guid=block.guid)
|
||||
.exclude(version__blockversion__id__isnull=False)
|
||||
.values_list('version__version', 'version_id')
|
||||
)
|
||||
block_versions = [
|
||||
BlockVersion(version_id=version_id, block=block)
|
||||
for version_str, version_id in versions_qs
|
||||
if block.min_version <= version_str and block.max_version >= version_str
|
||||
]
|
||||
BlockVersion.objects.bulk_create(block_versions)
|
||||
count += 1
|
||||
if (count % 1000) == 0:
|
||||
log.info('Progress: %s Blocks processed so far' % count)
|
||||
|
||||
log.info('Finished: %s Blocks processed' % count)
|
|
@ -1,10 +1,16 @@
|
|||
# Generated by Django 2.2.16 on 2020-11-03 11:07
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import migrations
|
||||
|
||||
import olympia.blocklist.models
|
||||
import olympia.versions.fields
|
||||
|
||||
|
||||
def no_asterisk(value):
|
||||
if '*' in value:
|
||||
raise ValidationError('%(value)s contains *', params={'value': value})
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
|
@ -15,11 +21,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='block',
|
||||
name='min_version',
|
||||
field=olympia.versions.fields.VersionStringField(default='0', max_length=255, validators=[olympia.blocklist.models.no_asterisk]),
|
||||
field=olympia.versions.fields.VersionStringField(default='0', max_length=255, validators=[no_asterisk]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blocklistsubmission',
|
||||
name='min_version',
|
||||
field=olympia.versions.fields.VersionStringField(default='0', max_length=255, validators=[olympia.blocklist.models.no_asterisk]),
|
||||
field=olympia.versions.fields.VersionStringField(default='0', max_length=255, validators=[no_asterisk]),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 4.2.1 on 2023-06-05 11:12
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import olympia.amo.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('blocklist', '0030_alter_blocklistsubmission_signoff_state_blockversion'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='blocklistsubmission',
|
||||
name='disable_addon',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='blocklistsubmission',
|
||||
name='changed_version_ids',
|
||||
field=models.JSONField(default=list),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='block',
|
||||
name='max_version',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='block',
|
||||
name='min_version',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blocklistsubmission',
|
||||
name='max_version',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='blocklistsubmission',
|
||||
name='min_version',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
]
|
|
@ -1,7 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import secrets
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -48,39 +47,15 @@ def generate_mlbf(stats, blocked, not_blocked):
|
|||
|
||||
|
||||
def fetch_blocked_from_db():
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.files.models import File
|
||||
from olympia.blocklist.models import BlockVersion
|
||||
|
||||
blocks = Block.objects.all()
|
||||
blocks_guids = [block.guid for block in blocks]
|
||||
|
||||
file_qs = (
|
||||
File.objects.filter(
|
||||
version__addon__addonguid__guid__in=blocks_guids,
|
||||
is_signed=True,
|
||||
all_versions = {
|
||||
block_version.version_id: (
|
||||
block_version.block.guid,
|
||||
block_version.version.version,
|
||||
)
|
||||
.order_by('version_id')
|
||||
.values('version__addon__addonguid__guid', 'version__version', 'version_id')
|
||||
)
|
||||
addons_versions = defaultdict(list)
|
||||
for file_ in file_qs:
|
||||
addon_key = file_['version__addon__addonguid__guid']
|
||||
addons_versions[addon_key].append(
|
||||
(file_['version__version'], file_['version_id'])
|
||||
)
|
||||
|
||||
all_versions = {}
|
||||
# collect all the blocked versions
|
||||
for block in blocks:
|
||||
is_all_versions = (
|
||||
block.min_version == Block.MIN and block.max_version == Block.MAX
|
||||
)
|
||||
versions = {
|
||||
version_id: (block.guid, version)
|
||||
for version, version_id in addons_versions[block.guid]
|
||||
if is_all_versions or block.is_version_blocked(version)
|
||||
}
|
||||
all_versions.update(versions)
|
||||
for block_version in BlockVersion.objects.filter(version__file__is_signed=True)
|
||||
}
|
||||
return all_versions
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ from collections import defaultdict, namedtuple
|
|||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -17,31 +16,17 @@ from olympia.amo.models import BaseQuerySet, ManagerBase, ModelBase
|
|||
from olympia.amo.templatetags.jinja_helpers import absolutify
|
||||
from olympia.amo.utils import chunked
|
||||
from olympia.users.models import UserProfile
|
||||
from olympia.versions.compare import VersionString
|
||||
from olympia.versions.fields import VersionStringField
|
||||
from olympia.versions.models import Version
|
||||
|
||||
from .utils import (
|
||||
block_activity_log_delete,
|
||||
save_guids_to_blocks,
|
||||
delete_versions_from_blocks,
|
||||
save_versions_to_blocks,
|
||||
splitlines,
|
||||
)
|
||||
|
||||
|
||||
def no_asterisk(value):
|
||||
if '*' in value:
|
||||
raise ValidationError(_('%(value)s contains *'), params={'value': value})
|
||||
|
||||
|
||||
class Block(ModelBase):
|
||||
MIN = VersionString('0')
|
||||
MAX = VersionString('*')
|
||||
|
||||
guid = models.CharField(max_length=255, unique=True, null=False)
|
||||
min_version = VersionStringField(
|
||||
max_length=255, blank=False, default=MIN, validators=(no_asterisk,)
|
||||
)
|
||||
max_version = VersionStringField(max_length=255, blank=False, default=MAX)
|
||||
url = models.CharField(max_length=255, blank=True)
|
||||
reason = models.TextField(blank=True)
|
||||
updated_by = models.ForeignKey(UserProfile, null=True, on_delete=models.SET_NULL)
|
||||
|
@ -102,28 +87,15 @@ class Block(ModelBase):
|
|||
.annotate(**{GUID: models.F(GUID)})
|
||||
.select_related('blockversion')
|
||||
)
|
||||
all_submission_versions = BlocklistSubmission.get_all_submission_versions()
|
||||
|
||||
all_addon_versions = defaultdict(list)
|
||||
for version in qs:
|
||||
version.blocklist_submission_id = all_submission_versions.get(version.id, 0)
|
||||
all_addon_versions[getattr(version, GUID)].append(version)
|
||||
for block in blocks:
|
||||
block.addon_versions = all_addon_versions[block.guid]
|
||||
|
||||
def clean(self):
|
||||
if self.id:
|
||||
# We're only concerned with edits - self.guid isn't set at this
|
||||
# point for new instances anyway.
|
||||
choices = list(version.version for version in self.addon_versions)
|
||||
if self.min_version not in choices + [self.MIN]:
|
||||
raise ValidationError({'min_version': _('Invalid version')})
|
||||
if self.max_version not in choices + [self.MAX]:
|
||||
raise ValidationError({'max_version': _('Invalid version')})
|
||||
if self.min_version > self.max_version:
|
||||
raise ValidationError(_('Min version can not be greater than Max version'))
|
||||
|
||||
def is_version_blocked(self, version):
|
||||
return self.min_version <= version and self.max_version >= version
|
||||
|
||||
def review_listed_link(self):
|
||||
has_listed = any(
|
||||
True
|
||||
|
@ -168,22 +140,24 @@ class Block(ModelBase):
|
|||
addons = list(cls.get_addons_for_guids_qs(guids).using(using_db))
|
||||
|
||||
# And then any existing block instances
|
||||
existing_blocks = {
|
||||
blocks = {
|
||||
block.guid: block
|
||||
for block in cls.objects.using(using_db).filter(guid__in=guids)
|
||||
}
|
||||
|
||||
for addon in addons:
|
||||
# get the existing block object or create a new instance
|
||||
block = existing_blocks.get(addon.guid, None)
|
||||
block = blocks.get(addon.guid, None)
|
||||
if block:
|
||||
# if it exists hook up the addon instance
|
||||
block.addon = addon
|
||||
else:
|
||||
# otherwise create a new Block
|
||||
block = Block(addon=addon)
|
||||
existing_blocks[block.guid] = block
|
||||
return list(existing_blocks.values())
|
||||
blocks[block.guid] = block
|
||||
blocks = list(blocks.values()) # flatten to just the Block instances
|
||||
Block.preload_addon_versions(blocks)
|
||||
return blocks
|
||||
|
||||
|
||||
class BlockVersion(ModelBase):
|
||||
|
@ -233,25 +207,30 @@ class BlocklistSubmission(ModelBase):
|
|||
ACTION_ADDCHANGE: 'Add/Change',
|
||||
ACTION_DELETE: 'Delete',
|
||||
}
|
||||
FakeBlockAddonVersion = namedtuple(
|
||||
'FakeBlockAddonVersion',
|
||||
(
|
||||
'id',
|
||||
'version',
|
||||
'is_blocked',
|
||||
'blocklist_submission_id',
|
||||
),
|
||||
)
|
||||
FakeBlock = namedtuple(
|
||||
'FakeBlock',
|
||||
(
|
||||
'id',
|
||||
'guid',
|
||||
'min_version',
|
||||
'max_version',
|
||||
'current_adu',
|
||||
'addon_versions',
|
||||
),
|
||||
)
|
||||
|
||||
action = models.SmallIntegerField(choices=ACTIONS.items(), default=ACTION_ADDCHANGE)
|
||||
|
||||
input_guids = models.TextField()
|
||||
changed_version_ids = models.JSONField(default=list)
|
||||
to_block = models.JSONField(default=list)
|
||||
min_version = VersionStringField(
|
||||
max_length=255, blank=False, default=Block.MIN, validators=(no_asterisk,)
|
||||
)
|
||||
max_version = VersionStringField(max_length=255, blank=False, default=Block.MAX)
|
||||
url = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
|
@ -274,6 +253,7 @@ class BlocklistSubmission(ModelBase):
|
|||
blank=True,
|
||||
help_text='The submission will not be published into blocks before this time.',
|
||||
)
|
||||
disable_addon = models.BooleanField(default=True)
|
||||
|
||||
objects = BlocklistSubmissionManager()
|
||||
|
||||
|
@ -297,8 +277,7 @@ class BlocklistSubmission(ModelBase):
|
|||
# as a dict of property_name: (old_value, new_value).
|
||||
changes = {}
|
||||
properties = (
|
||||
'min_version',
|
||||
'max_version',
|
||||
'versions',
|
||||
'url',
|
||||
'reason',
|
||||
)
|
||||
|
@ -307,26 +286,34 @@ class BlocklistSubmission(ModelBase):
|
|||
changes[prop] = (getattr(block, prop), getattr(self, prop))
|
||||
return changes
|
||||
|
||||
def clean(self):
|
||||
if self.min_version > self.max_version:
|
||||
raise ValidationError(_('Min version can not be greater than Max version'))
|
||||
|
||||
def get_blocks_submitted(self, load_full_objects_threshold=1_000_000_000):
|
||||
blocks = self.block_set.all().order_by('id')
|
||||
if blocks.count() > load_full_objects_threshold:
|
||||
blocks_qs = self.block_set.all().order_by('id')
|
||||
load_fakes = blocks_qs.count() > load_full_objects_threshold
|
||||
if load_fakes:
|
||||
blocks = list(blocks_qs.values_list('id', 'guid', named=True))
|
||||
blocked_versions = list(
|
||||
BlockVersion.objects.filter(
|
||||
block__in=(b.id for b in blocks)
|
||||
).values_list('block_id', 'version__version')
|
||||
)
|
||||
if load_fakes:
|
||||
# If we'd be returning too many Block objects, fake them with the
|
||||
# minimum needed to display the link to the Block change page.
|
||||
blocks = [
|
||||
return [
|
||||
self.FakeBlock(
|
||||
id=block.id,
|
||||
guid=block.guid,
|
||||
min_version=None,
|
||||
max_version=None,
|
||||
current_adu=None,
|
||||
addon_versions=[
|
||||
self.FakeBlockAddonVersion(None, bv.version, True, 0)
|
||||
for bv in blocked_versions
|
||||
if bv.block_id == block.id
|
||||
],
|
||||
)
|
||||
for block in blocks
|
||||
]
|
||||
return blocks
|
||||
else:
|
||||
return blocks_qs.prefetch_related('blockversion_set')
|
||||
|
||||
def can_user_signoff(self, signoff_user):
|
||||
require_different_users = not settings.DEBUG
|
||||
|
@ -344,14 +331,7 @@ class BlocklistSubmission(ModelBase):
|
|||
)
|
||||
|
||||
def has_version_changes(self):
|
||||
block_ids = [block['id'] for block in self.to_block]
|
||||
|
||||
has_new_blocks = any(not id_ for id_ in block_ids)
|
||||
blocks_with_version_changes_qs = Block.objects.filter(id__in=block_ids).exclude(
|
||||
min_version=self.min_version, max_version=self.max_version
|
||||
)
|
||||
|
||||
return has_new_blocks or blocks_with_version_changes_qs.exists()
|
||||
return bool(self.changed_version_ids)
|
||||
|
||||
def update_signoff_for_auto_approval(self):
|
||||
is_pending = self.signoff_state == self.SIGNOFF_PENDING
|
||||
|
@ -385,8 +365,6 @@ class BlocklistSubmission(ModelBase):
|
|||
|
||||
processed = self.process_input_guids(
|
||||
self.input_guids,
|
||||
self.min_version,
|
||||
self.max_version,
|
||||
load_full_objects=False,
|
||||
filter_existing=(self.action == self.ACTION_ADDCHANGE),
|
||||
)
|
||||
|
@ -409,15 +387,38 @@ class BlocklistSubmission(ModelBase):
|
|||
|
||||
# And then any existing block instances
|
||||
block_qs = Block.objects.filter(guid__in=guids).values_list(
|
||||
'id', 'guid', 'min_version', 'max_version', named=True
|
||||
'id', 'guid', named=True
|
||||
)
|
||||
version_qs = (
|
||||
Version.unfiltered.filter(addon__addonguid__guid__in=guids)
|
||||
.order_by('id')
|
||||
.values_list(
|
||||
'id',
|
||||
'version',
|
||||
'blockversion__block_id',
|
||||
'addon__addonguid__guid',
|
||||
named=True,
|
||||
)
|
||||
)
|
||||
all_submission_versions = BlocklistSubmission.get_all_submission_versions()
|
||||
|
||||
all_addon_versions = defaultdict(list)
|
||||
for version in version_qs:
|
||||
all_addon_versions[version.addon__addonguid__guid].append(
|
||||
cls.FakeBlockAddonVersion(
|
||||
version.id,
|
||||
version.version,
|
||||
version.blockversion__block_id is not None,
|
||||
all_submission_versions.get(version.id, 0),
|
||||
)
|
||||
)
|
||||
|
||||
blocks = {
|
||||
block.guid: cls.FakeBlock(
|
||||
id=block.id,
|
||||
guid=block.guid,
|
||||
min_version=block.min_version,
|
||||
max_version=block.max_version,
|
||||
current_adu=adu_lookup.get(block.guid, -1),
|
||||
addon_versions=tuple(all_addon_versions.get(block.guid, [])),
|
||||
)
|
||||
for block in block_qs
|
||||
}
|
||||
|
@ -431,29 +432,29 @@ class BlocklistSubmission(ModelBase):
|
|||
block = cls.FakeBlock(
|
||||
id=None,
|
||||
guid=addon.guid,
|
||||
min_version=Block.MIN,
|
||||
max_version=Block.MAX,
|
||||
current_adu=adu_lookup.get(addon.guid, -1),
|
||||
addon_versions=tuple(all_addon_versions.get(addon.guid, [])),
|
||||
)
|
||||
blocks[addon.guid] = block
|
||||
return list(blocks.values())
|
||||
blocks_list = blocks.values()
|
||||
return list(blocks_list)
|
||||
|
||||
@classmethod
|
||||
def process_input_guids(
|
||||
cls, input_guids, v_min, v_max, *, load_full_objects=True, filter_existing=True
|
||||
cls, input_guids, *, load_full_objects=True, filter_existing=True
|
||||
):
|
||||
"""Process a line-return separated list of guids into a list of invalid
|
||||
guids, a list of guids that are blocked already for v_min - vmax, and a
|
||||
guids, a list of guids that are completely blocked already, and a
|
||||
list of Block instances - including new Blocks (unsaved) and existing
|
||||
partial Blocks. If `filter_existing` is False, all existing blocks are
|
||||
included.
|
||||
|
||||
If `load_full_objects=False` is passed the Block instances are fake
|
||||
(namedtuples) with only minimal data available in the "Block" objects:
|
||||
Block.id
|
||||
Block.guid,
|
||||
Block.current_adu,
|
||||
Block.min_version,
|
||||
Block.max_version,
|
||||
Block.addon_versions,
|
||||
"""
|
||||
all_guids = set(splitlines(input_guids))
|
||||
|
||||
|
@ -468,13 +469,13 @@ class BlocklistSubmission(ModelBase):
|
|||
blocks = unfiltered_blocks
|
||||
existing_guids = []
|
||||
else:
|
||||
# unfiltered_blocks contains blocks that don't need to be updated.
|
||||
# Get a list of a blocks from unfiltered_blocks that are either new or
|
||||
# not completely blocked.
|
||||
blocks = [
|
||||
block
|
||||
for block in unfiltered_blocks
|
||||
if not block.id
|
||||
or block.min_version != v_min
|
||||
or block.max_version != v_max
|
||||
or any(not ver.is_blocked for ver in block.addon_versions)
|
||||
]
|
||||
existing_guids = [
|
||||
block.guid for block in unfiltered_blocks if block not in blocks
|
||||
|
@ -484,7 +485,6 @@ class BlocklistSubmission(ModelBase):
|
|||
invalid_guids = list(
|
||||
all_guids - set(existing_guids) - {block.guid for block in blocks}
|
||||
)
|
||||
|
||||
return {
|
||||
'invalid_guids': invalid_guids,
|
||||
'existing_guids': existing_guids,
|
||||
|
@ -496,8 +496,6 @@ class BlocklistSubmission(ModelBase):
|
|||
assert self.action == self.ACTION_ADDCHANGE
|
||||
|
||||
fields_to_set = [
|
||||
'min_version',
|
||||
'max_version',
|
||||
'url',
|
||||
'reason',
|
||||
'updated_by',
|
||||
|
@ -505,7 +503,7 @@ class BlocklistSubmission(ModelBase):
|
|||
all_guids_to_block = [block['guid'] for block in self.to_block]
|
||||
|
||||
for guids_chunk in chunked(all_guids_to_block, 100):
|
||||
save_guids_to_blocks(guids_chunk, self, fields_to_set=fields_to_set)
|
||||
save_versions_to_blocks(guids_chunk, self, fields_to_set=fields_to_set)
|
||||
self.save()
|
||||
|
||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||
|
@ -513,14 +511,18 @@ class BlocklistSubmission(ModelBase):
|
|||
def delete_block_objects(self):
|
||||
assert self.is_submission_ready
|
||||
assert self.action == self.ACTION_DELETE
|
||||
block_ids_to_delete = [block['id'] for block in self.to_block]
|
||||
for ids_chunk in chunked(block_ids_to_delete, 100):
|
||||
blocks = list(Block.objects.filter(id__in=ids_chunk))
|
||||
Block.preload_addon_versions(blocks)
|
||||
for block in blocks:
|
||||
block_activity_log_delete(block, submission_obj=self)
|
||||
|
||||
fields_to_set = [
|
||||
'url',
|
||||
'reason',
|
||||
'updated_by',
|
||||
]
|
||||
all_guids_to_block = [block['guid'] for block in self.to_block]
|
||||
|
||||
for guids_chunk in chunked(all_guids_to_block, 100):
|
||||
# This function will remove BlockVersions and delete the Block if empty
|
||||
delete_versions_from_blocks(guids_chunk, self, fields_to_set=fields_to_set)
|
||||
self.save()
|
||||
Block.objects.filter(id__in=ids_chunk).delete()
|
||||
|
||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||
|
||||
|
@ -529,3 +531,18 @@ class BlocklistSubmission(ModelBase):
|
|||
return cls.objects.exclude(signoff_state__in=excludes).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
|
||||
).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
|
||||
).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
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ from .models import Block
|
|||
class BlockSerializer(AMOModelSerializer):
|
||||
addon_name = TranslationSerializerField(source='addon.name')
|
||||
url = OutgoingURLField()
|
||||
min_version = fields.SerializerMethodField()
|
||||
max_version = fields.SerializerMethodField()
|
||||
versions = fields.SerializerMethodField()
|
||||
is_all_versions = fields.SerializerMethodField()
|
||||
|
||||
|
@ -31,11 +33,19 @@ class BlockSerializer(AMOModelSerializer):
|
|||
)
|
||||
|
||||
def get_versions(self, obj):
|
||||
return list(
|
||||
obj.blockversion_set.order_by('version__version').values_list(
|
||||
'version__version', flat=True
|
||||
if not hasattr(obj, '_blockversion_set_qs_values_list'):
|
||||
obj._blockversion_set_qs_values_list = sorted(
|
||||
obj.blockversion_set.order_by('version__version').values_list(
|
||||
'version__version', flat=True
|
||||
)
|
||||
)
|
||||
)
|
||||
return obj._blockversion_set_qs_values_list
|
||||
|
||||
def get_min_version(self, obj):
|
||||
return versions[0] if (versions := self.get_versions(obj)) else ''
|
||||
|
||||
def get_max_version(self, obj):
|
||||
return versions[-1] if (versions := self.get_versions(obj)) else ''
|
||||
|
||||
def get_is_all_versions(self, obj):
|
||||
cannot_upload_new_versions = not obj.addon or obj.addon.status in (
|
||||
|
|
|
@ -29,21 +29,21 @@
|
|||
{% csrf_token %}
|
||||
{{ form.action }}
|
||||
{{ form.input_guids }}
|
||||
{% if not existing_guids|length == 0 or not invalid_guids|length == 0 %}
|
||||
{% if not form.existing_guids|length == 0 or not form.invalid_guids|length == 0 %}
|
||||
<div>
|
||||
<div class="form-row horizontal-grid">
|
||||
<div>
|
||||
<h3>{{ existing_guids|length }} Add-on GUIDs are already blocked for {{ form.min_version.value }} to {{ form.max_version.value }}:</h3>
|
||||
<h3>{{ form.existing_guids|length }} Add-on GUIDs already completely blocked:</h3>
|
||||
<ul class="guid_list field-existing-guids">
|
||||
{% for guid in existing_guids %}
|
||||
{% for guid in form.existing_guids %}
|
||||
<li>{{ guid }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>{{ invalid_guids|length }} Add-on GUIDs were not found:</h3>
|
||||
<h3>{{ form.invalid_guids|length }} Add-on GUIDs were not found:</h3>
|
||||
<ul class="guid_list">
|
||||
{% for guid in invalid_guids %}
|
||||
{% for guid in form.invalid_guids %}
|
||||
<li>{{ guid }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -57,10 +57,11 @@
|
|||
<div>
|
||||
{% include 'admin/blocklist/includes/enhanced_blocks.html' %}
|
||||
</div>
|
||||
{{ form.changed_version_ids.errors }}
|
||||
</div>
|
||||
{% if block_history %}
|
||||
<div class="form-row field-block_history">
|
||||
<label for="id_min_version">Block History:</label>
|
||||
<label>Block History:</label>
|
||||
<div class="readonly">{{ block_history }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -70,18 +71,10 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<div class="form-row">
|
||||
<div>
|
||||
{{ form.min_version.errors }}
|
||||
{{ form.min_version.label_tag }}
|
||||
{{ form.min_version }}
|
||||
{{ form.existing_min_version }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
{{ form.max_version.errors }}
|
||||
{{ form.max_version.label_tag }}
|
||||
{{ form.max_version }}
|
||||
{{ form.existing_max_version }}
|
||||
{{ form.disable_addon.errors }}
|
||||
{{ form.disable_addon.label_tag }}
|
||||
{{ form.disable_addon }}
|
||||
<p class="help">{{ form.disable_addon.help_text }}</p>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
{{ form.url.errors }}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{% load humanize %}
|
||||
|
||||
<h3>{{ blocks|length|intcomma }} Add-on GUIDs with {{ total_adu|intcomma }} users:</h3>
|
||||
<h3>{{ form.blocks|length|intcomma }} Add-on GUIDs with {{ form.total_adu|intcomma }} users:</h3>
|
||||
<ul class="guid_list">
|
||||
{% for block_obj in blocks %}
|
||||
{% for block_obj in form.blocks %}
|
||||
<li>
|
||||
{{ block_obj.guid }}.
|
||||
{% if block_obj.addon %}
|
||||
<span class="addon-name">{{ block_obj.addon.name }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if submission_complete|default_if_none:False %}
|
||||
{% if submission_published|default_if_none:False %}
|
||||
{% if block_obj.average_daily_users_snapshot is not None %}
|
||||
({{ block_obj.average_daily_users_snapshot }} users).
|
||||
{% endif %}
|
||||
|
@ -20,8 +20,28 @@
|
|||
{{ block_obj.review_listed_link }}
|
||||
{{ block_obj.review_unlisted_link }}
|
||||
{% if block_obj.id %}
|
||||
<span class="existing_block">[<a href="{% url 'admin:blocklist_block_change' block_obj.id %}">Edit Block</a>: {{ block_obj.min_version }} - {{ block_obj.max_version }}]</span>
|
||||
<span class="existing_block">[<a href="{% url 'admin:blocklist_block_change' block_obj.id %}">Edit Block</a>]</span>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for version in block_obj.addon_versions %}
|
||||
<li data-version-id="{{ version.id }}">
|
||||
<label><input
|
||||
type="checkbox"
|
||||
name="changed_version_ids"
|
||||
value="{{ version.id }}"
|
||||
{% if version.id in form.changed_version_ids_choices %}
|
||||
{% if version.id in form.changed_version_ids.value %}checked{% endif %}
|
||||
{% else %}
|
||||
disabled
|
||||
{% if version.is_blocked %}checked{% endif %}
|
||||
{% endif %}
|
||||
>{{ version.version }}</label>
|
||||
{% if version.blocklist_submission_id %}
|
||||
[<a href="{% url 'admin:blocklist_blocklistsubmission_change' version.blocklist_submission_id %}">Edit Submission</a>]
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
{{ log.log.short }} by {{ log.user.name }}:
|
||||
{% if log.details %}{{ log.details.guid }}{% else %}{{ log.arguments.1 }}{% endif %}{% if 'min_version' in log.details %}
|
||||
, versions {{ log.details.min_version }} - {{ log.details.max_version }}.
|
||||
{% elif 'versions' in log.details %}
|
||||
, versions [{{ log.details.versions|join:',' }}].
|
||||
{% else %}.
|
||||
{% endif %}
|
||||
<ul>
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,9 +4,13 @@ from django.conf import settings
|
|||
from django.core.management import call_command
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory, version_factory
|
||||
|
||||
from ..models import Block, BlockVersion
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
|
||||
|
||||
class TestExportBlocklist(TestCase):
|
||||
|
@ -14,19 +18,19 @@ class TestExportBlocklist(TestCase):
|
|||
user = user_factory()
|
||||
for _idx in range(0, 5):
|
||||
addon_factory()
|
||||
# one version, 0 - *
|
||||
Block.objects.create(
|
||||
# all versions
|
||||
block_factory(
|
||||
addon=addon_factory(file_kw={'is_signed': True}),
|
||||
updated_by=user,
|
||||
)
|
||||
# one version, 0 - 9999
|
||||
Block.objects.create(
|
||||
# one version
|
||||
one = block_factory(
|
||||
addon=addon_factory(file_kw={'is_signed': True}),
|
||||
updated_by=user,
|
||||
max_version='9999',
|
||||
)
|
||||
# one version, 0 - *, unlisted
|
||||
Block.objects.create(
|
||||
version_factory(addon=one.addon)
|
||||
# all versions, unlisted
|
||||
block_factory(
|
||||
addon=addon_factory(
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'is_signed': True},
|
||||
|
@ -37,45 +41,3 @@ class TestExportBlocklist(TestCase):
|
|||
call_command('export_blocklist', '1')
|
||||
out_path = os.path.join(settings.MLBF_STORAGE_PATH, '1', 'filter')
|
||||
assert os.path.exists(out_path)
|
||||
|
||||
|
||||
class TestCreateBlockversions(TestCase):
|
||||
def test_command(self):
|
||||
user = user_factory()
|
||||
Block.objects.create(guid='missing@', min_version='123', updated_by=user)
|
||||
addon = addon_factory(version_kw={'version': '0.1'})
|
||||
v1 = version_factory(addon=addon, version='1')
|
||||
v2 = version_factory(addon=addon, version='2.0.0')
|
||||
v2.delete()
|
||||
minmax_block = Block.objects.create(
|
||||
addon=addon, min_version='0.1.1', max_version='2', updated_by=user
|
||||
)
|
||||
full_block = Block.objects.create(addon=addon_factory(), updated_by=user)
|
||||
|
||||
call_command('create_blockversions')
|
||||
|
||||
assert BlockVersion.objects.count() == 3
|
||||
v1.refresh_from_db()
|
||||
v2.refresh_from_db()
|
||||
full_block.refresh_from_db()
|
||||
assert v1.blockversion.block == minmax_block
|
||||
assert v2.blockversion.block == minmax_block
|
||||
assert full_block.addon.current_version.blockversion.block == full_block
|
||||
|
||||
# we can run it again without a problem
|
||||
call_command('create_blockversions')
|
||||
assert BlockVersion.objects.count() == 3
|
||||
|
||||
# and extra versions / blocks are created
|
||||
new_version = version_factory(
|
||||
addon=addon, channel=amo.CHANNEL_UNLISTED, version='0.2'
|
||||
)
|
||||
new_block = Block.objects.create(addon=addon_factory(), updated_by=user)
|
||||
|
||||
call_command('create_blockversions')
|
||||
|
||||
assert BlockVersion.objects.count() == 5
|
||||
new_block.refresh_from_db()
|
||||
new_version.refresh_from_db()
|
||||
assert new_version.blockversion.block == minmax_block
|
||||
assert new_block.addon.current_version.blockversion.block == new_block
|
||||
|
|
|
@ -10,7 +10,13 @@ import pytest
|
|||
from freezegun import freeze_time
|
||||
from waffle.testutils import override_switch
|
||||
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory, version_factory
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.blocklist.cron import (
|
||||
get_blocklist_last_modified_time,
|
||||
process_blocklistsubmissions,
|
||||
|
@ -33,7 +39,7 @@ class TestUploadToRemoteSettings(TestCase):
|
|||
addon = addon_factory()
|
||||
version_factory(addon=addon)
|
||||
version_factory(addon=addon)
|
||||
self.block = Block.objects.create(
|
||||
self.block = block_factory(
|
||||
addon=addon_factory(
|
||||
version_kw={'version': '1.2b3'},
|
||||
file_kw={'is_signed': True},
|
||||
|
@ -316,7 +322,7 @@ class TestUploadToRemoteSettings(TestCase):
|
|||
self.cleanup_files_mock.assert_not_called()
|
||||
|
||||
# But if we add a new Block a new filter is needed
|
||||
Block.objects.create(
|
||||
block_factory(
|
||||
addon=addon_factory(file_kw={'is_signed': True}),
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
|
|
|
@ -2,7 +2,13 @@ from django.contrib import admin as admin_site
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.test import RequestFactory
|
||||
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.blocklist.admin import BlocklistSubmissionAdmin
|
||||
from olympia.blocklist.forms import MultiAddForm, MultiDeleteForm
|
||||
from olympia.blocklist.models import Block, BlocklistSubmission
|
||||
|
@ -16,116 +22,146 @@ class TestBlocklistSubmissionForm(TestCase):
|
|||
self.another_new_addon = addon_factory(
|
||||
guid='another@new',
|
||||
average_daily_users=100000,
|
||||
version_kw={'version': '34.545'},
|
||||
)
|
||||
self.existing_one_to_ten = Block.objects.create(
|
||||
addon=addon_factory(guid='partial@existing'),
|
||||
min_version='1',
|
||||
max_version='10',
|
||||
|
||||
self.full_existing_addon = addon_factory(guid='full@existing')
|
||||
self.full_existing_addon_v1 = self.full_existing_addon.current_version
|
||||
self.full_existing_addon_v2 = version_factory(addon=self.full_existing_addon)
|
||||
self.existing_block_full = block_factory(
|
||||
addon=self.full_existing_addon,
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
self.existing_zero_to_max = Block.objects.create(
|
||||
addon=addon_factory(
|
||||
guid='full@existing',
|
||||
average_daily_users=99,
|
||||
version_kw={'version': '10'},
|
||||
|
||||
self.partial_existing_addon = addon_factory(
|
||||
guid='partial@existing',
|
||||
average_daily_users=99,
|
||||
)
|
||||
self.partial_existing_addon_v_blocked = (
|
||||
self.partial_existing_addon.current_version
|
||||
)
|
||||
self.existing_block_partial = block_factory(
|
||||
addon=self.partial_existing_addon,
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
self.partial_existing_addon_v_notblocked = version_factory(
|
||||
addon=self.partial_existing_addon
|
||||
)
|
||||
|
||||
def test_changed_version_ids_choices_add_action(self):
|
||||
block_admin = BlocklistSubmissionAdmin(
|
||||
model=BlocklistSubmission, admin_site=admin_site
|
||||
)
|
||||
request = RequestFactory().get('/')
|
||||
|
||||
Form = block_admin.get_form(request=request)
|
||||
data = {
|
||||
'action': str(BlocklistSubmission.ACTION_ADDCHANGE),
|
||||
'input_guids': f'{self.new_addon.guid}\n'
|
||||
f'{self.existing_block_full.guid}\n'
|
||||
f'{self.existing_block_partial.guid}\n'
|
||||
'invalid@guid',
|
||||
}
|
||||
form = Form(data=data)
|
||||
assert form.fields['changed_version_ids'].choices == [
|
||||
(
|
||||
self.new_addon.guid,
|
||||
[
|
||||
(
|
||||
self.new_addon.current_version.id,
|
||||
self.new_addon.current_version.version,
|
||||
)
|
||||
],
|
||||
),
|
||||
min_version='0',
|
||||
max_version='*',
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
(
|
||||
self.existing_block_partial.guid,
|
||||
[
|
||||
(
|
||||
self.partial_existing_addon_v_notblocked.id,
|
||||
self.partial_existing_addon_v_notblocked.version,
|
||||
)
|
||||
],
|
||||
),
|
||||
]
|
||||
assert form.invalid_guids == ['invalid@guid']
|
||||
|
||||
def test_existing_blocks_no_existing(self):
|
||||
data = {
|
||||
'input_guids': 'any@new\nanother@new',
|
||||
'min_version': '0',
|
||||
'max_version': '*',
|
||||
'existing_min_version': '1',
|
||||
'existing_max_version': '10',
|
||||
form = Form(
|
||||
data={**data, 'changed_version_ids': [self.new_addon.current_version.id]}
|
||||
)
|
||||
assert form.is_valid()
|
||||
assert not form.errors
|
||||
|
||||
form = Form(
|
||||
data={
|
||||
**data,
|
||||
'changed_version_ids': [self.partial_existing_addon_v_blocked.id],
|
||||
}
|
||||
)
|
||||
assert not form.is_valid()
|
||||
assert form.errors == {
|
||||
'changed_version_ids': [
|
||||
f'Select a valid choice. {self.partial_existing_addon_v_blocked.id} is '
|
||||
'not one of the available choices.'
|
||||
]
|
||||
}
|
||||
|
||||
def test_test_changed_version_ids_choices_delete_action(self):
|
||||
block_admin = BlocklistSubmissionAdmin(
|
||||
model=BlocklistSubmission, admin_site=admin_site
|
||||
)
|
||||
request = RequestFactory().get('/')
|
||||
|
||||
# All new guids should always be fine
|
||||
form = block_admin.get_form(request=request)(data=data)
|
||||
form.is_valid()
|
||||
form.clean() # would raise if there needed to be a recalculation
|
||||
|
||||
def test_existing_blocks_some_existing(self):
|
||||
Form = block_admin.get_form(request=request)
|
||||
data = {
|
||||
'input_guids': 'full@existing',
|
||||
'min_version': '0',
|
||||
'max_version': '*',
|
||||
'existing_min_version': '1',
|
||||
'existing_max_version': '10',
|
||||
'action': str(BlocklistSubmission.ACTION_DELETE),
|
||||
'input_guids': f'{self.new_addon.guid}\n'
|
||||
f'{self.existing_block_full.guid}\n'
|
||||
f'{self.existing_block_partial.guid}\n'
|
||||
'invalid@guid',
|
||||
}
|
||||
block_admin = BlocklistSubmissionAdmin(
|
||||
model=BlocklistSubmission, admin_site=admin_site
|
||||
form = Form(data=data)
|
||||
assert form.fields['changed_version_ids'].choices == [
|
||||
(
|
||||
self.existing_block_full.guid,
|
||||
[
|
||||
(
|
||||
self.full_existing_addon_v1.id,
|
||||
self.full_existing_addon_v1.version,
|
||||
),
|
||||
(
|
||||
self.full_existing_addon_v2.id,
|
||||
self.full_existing_addon_v2.version,
|
||||
),
|
||||
],
|
||||
),
|
||||
(self.new_addon.guid, []),
|
||||
(
|
||||
self.existing_block_partial.guid,
|
||||
[
|
||||
(
|
||||
self.partial_existing_addon_v_blocked.id,
|
||||
self.partial_existing_addon_v_blocked.version,
|
||||
)
|
||||
],
|
||||
),
|
||||
]
|
||||
assert form.invalid_guids == ['invalid@guid']
|
||||
|
||||
form = Form(
|
||||
data={**data, 'changed_version_ids': [self.full_existing_addon_v1.id]}
|
||||
)
|
||||
request = RequestFactory().get('/')
|
||||
assert form.is_valid()
|
||||
assert not form.errors
|
||||
|
||||
# A single guid is always updated so checks are bypassed
|
||||
form = block_admin.get_form(request=request)(data=data)
|
||||
form.is_valid()
|
||||
form.clean() # would raise
|
||||
|
||||
# Two or more guids trigger the checks
|
||||
data.update(input_guids='partial@existing\nfull@existing')
|
||||
form = block_admin.get_form(request=request)(data=data)
|
||||
form.is_valid()
|
||||
with self.assertRaises(ValidationError):
|
||||
form.clean()
|
||||
|
||||
# Not if the existing min/max versions match, i.e. they've not been
|
||||
# changed
|
||||
data.update(
|
||||
existing_min_version=data['min_version'],
|
||||
existing_max_version=data['max_version'],
|
||||
form = Form(
|
||||
data={**data, 'changed_version_ids': [self.new_addon.current_version.id]}
|
||||
)
|
||||
form = block_admin.get_form(request=request)(data=data)
|
||||
form.is_valid()
|
||||
form.clean() # would raise
|
||||
|
||||
# It should also be okay if the min/max *have* changed but the blocks
|
||||
# affected are the same
|
||||
data = {
|
||||
'input_guids': 'partial@existing\nfull@existing',
|
||||
'min_version': '56',
|
||||
'max_version': '156',
|
||||
'existing_min_version': '23',
|
||||
'existing_max_version': '123',
|
||||
assert not form.is_valid()
|
||||
assert form.errors == {
|
||||
'changed_version_ids': [
|
||||
f'Select a valid choice. {self.new_addon.current_version.id} is not '
|
||||
'one of the available choices.'
|
||||
]
|
||||
}
|
||||
form = block_admin.get_form(request=request)(data=data)
|
||||
form.is_valid()
|
||||
form.clean() # would raise
|
||||
|
||||
def test_all_existing_blocks_but_delete_action(self):
|
||||
data = {
|
||||
'input_guids': 'any@thing\nsecond@thing',
|
||||
'action': BlocklistSubmission.ACTION_DELETE,
|
||||
}
|
||||
block_admin = BlocklistSubmissionAdmin(
|
||||
model=BlocklistSubmission, admin_site=admin_site
|
||||
)
|
||||
request = RequestFactory().get('/')
|
||||
|
||||
# checks are bypassed if action != BlocklistSubmission.ACTION_ADDCHANGE
|
||||
form = block_admin.get_form(request=request)(data=data)
|
||||
form.is_valid()
|
||||
form.clean() # would raise
|
||||
|
||||
# Even if min_version or max_version are provided
|
||||
data.update(
|
||||
min_version='0',
|
||||
max_version='*',
|
||||
existing_min_version='1234',
|
||||
existing_max_version='4567',
|
||||
)
|
||||
form = block_admin.get_form(request=request)(data=data)
|
||||
form.is_valid()
|
||||
form.clean() # would raise
|
||||
|
||||
|
||||
class TestMultiDeleteForm(TestCase):
|
||||
|
@ -145,15 +181,6 @@ class TestMultiDeleteForm(TestCase):
|
|||
form.is_valid()
|
||||
form.clean() # would raise
|
||||
|
||||
# except if one of the Blocks is already being changed/deleted
|
||||
bls = BlocklistSubmission.objects.create(
|
||||
input_guids=data['guids'], action=BlocklistSubmission.ACTION_DELETE
|
||||
)
|
||||
bls.save()
|
||||
form.is_valid()
|
||||
with self.assertRaises(ValidationError):
|
||||
form.clean()
|
||||
|
||||
|
||||
class TestMultiAddForm(TestCase):
|
||||
def test_guid_must_exist_in_database(self):
|
||||
|
@ -179,12 +206,3 @@ class TestMultiAddForm(TestCase):
|
|||
addon_factory(guid='second@thing')
|
||||
form.is_valid()
|
||||
form.clean() # would raise
|
||||
|
||||
# except if one of the Blocks is already being changed/deleted
|
||||
bls = BlocklistSubmission.objects.create(
|
||||
input_guids=data['guids'], action=BlocklistSubmission.ACTION_ADDCHANGE
|
||||
)
|
||||
bls.save()
|
||||
form.is_valid()
|
||||
with self.assertRaises(ValidationError):
|
||||
form.clean()
|
||||
|
|
|
@ -7,7 +7,13 @@ from filtercascade import FilterCascade
|
|||
|
||||
from olympia import amo
|
||||
from olympia.addons.models import GUID_REUSE_FORMAT
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory, version_factory
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.files.models import File
|
||||
|
||||
from ..mlbf import (
|
||||
|
@ -16,30 +22,24 @@ from ..mlbf import (
|
|||
fetch_blocked_from_db,
|
||||
generate_mlbf,
|
||||
)
|
||||
from ..models import Block
|
||||
|
||||
|
||||
class TestMLBF(TestCase):
|
||||
def setup_data(self):
|
||||
user = user_factory()
|
||||
for idx in range(0, 5):
|
||||
for _idx in range(0, 5):
|
||||
addon_factory()
|
||||
# one version, 0 - *
|
||||
Block.objects.create(
|
||||
# one version, listed (twice)
|
||||
block_factory(
|
||||
addon=addon_factory(file_kw={'is_signed': True}),
|
||||
updated_by=user,
|
||||
)
|
||||
# one version, 0 - 9999
|
||||
Block.objects.create(
|
||||
addon=addon_factory(
|
||||
version_kw={'version': '11.7'},
|
||||
file_kw={'is_signed': True},
|
||||
),
|
||||
block_factory(
|
||||
addon=addon_factory(file_kw={'is_signed': True}),
|
||||
updated_by=user,
|
||||
max_version='9999',
|
||||
)
|
||||
# one version, 0 - *, unlisted
|
||||
Block.objects.create(
|
||||
# one version, unlisted
|
||||
block_factory(
|
||||
addon=addon_factory(
|
||||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'is_signed': True},
|
||||
|
@ -47,53 +47,60 @@ class TestMLBF(TestCase):
|
|||
updated_by=user,
|
||||
)
|
||||
# five versions, but only two within block (123.40, 123.5)
|
||||
self.five_ver_block = Block.objects.create(
|
||||
addon=addon_factory(
|
||||
version_kw={'version': '123.40'},
|
||||
file_kw={'is_signed': True},
|
||||
),
|
||||
updated_by=user,
|
||||
max_version='123.45',
|
||||
five_ver_block_addon = addon_factory(
|
||||
version_kw={'version': '123.40'},
|
||||
file_kw={'is_signed': True},
|
||||
)
|
||||
self.five_ver_123_40 = self.five_ver_block.addon.current_version
|
||||
self.five_ver_123_40 = five_ver_block_addon.current_version
|
||||
self.five_ver_123_5 = version_factory(
|
||||
addon=self.five_ver_block.addon,
|
||||
addon=five_ver_block_addon,
|
||||
version='123.5',
|
||||
deleted=True,
|
||||
file_kw={'is_signed': True},
|
||||
)
|
||||
self.five_ver_123_45_1 = version_factory(
|
||||
addon=self.five_ver_block.addon,
|
||||
addon=five_ver_block_addon,
|
||||
version='123.45.1',
|
||||
file_kw={'is_signed': True},
|
||||
)
|
||||
# these two would be included if they were signed
|
||||
self.not_signed_version = version_factory(
|
||||
addon=self.five_ver_block.addon,
|
||||
addon=five_ver_block_addon,
|
||||
version='123.5.1',
|
||||
file_kw={'is_signed': False},
|
||||
)
|
||||
self.not_signed_version2 = version_factory(
|
||||
addon=self.five_ver_block.addon,
|
||||
addon=five_ver_block_addon,
|
||||
version='123.5.2',
|
||||
file_kw={'is_signed': False},
|
||||
)
|
||||
self.five_ver_block = block_factory(
|
||||
addon=five_ver_block_addon,
|
||||
updated_by=user,
|
||||
version_ids=[
|
||||
self.five_ver_123_40.id,
|
||||
self.five_ver_123_5.id,
|
||||
self.not_signed_version.id,
|
||||
self.not_signed_version2.id,
|
||||
],
|
||||
)
|
||||
|
||||
# no matching versions (edge cases)
|
||||
self.over = Block.objects.create(
|
||||
self.no_versions = block_factory(
|
||||
addon=addon_factory(
|
||||
version_kw={'version': '0.1'},
|
||||
file_kw={'is_signed': True},
|
||||
),
|
||||
updated_by=user,
|
||||
max_version='0',
|
||||
version_ids=[],
|
||||
)
|
||||
self.under = Block.objects.create(
|
||||
self.no_versions2 = block_factory(
|
||||
addon=addon_factory(
|
||||
version_kw={'version': '9998.0'},
|
||||
version_kw={'version': '0.1'},
|
||||
file_kw={'is_signed': True},
|
||||
),
|
||||
updated_by=user,
|
||||
min_version='9999',
|
||||
version_ids=[],
|
||||
)
|
||||
|
||||
# A blocked addon has been uploaded and deleted before
|
||||
|
@ -122,7 +129,7 @@ class TestMLBF(TestCase):
|
|||
# not signed, but shouldn't override the signed 2.1 version
|
||||
file_kw={'is_signed': False},
|
||||
)
|
||||
version_factory(
|
||||
self.addon_deleted_before_3_0_ver = version_factory(
|
||||
addon=current_addon,
|
||||
version='3.0',
|
||||
file_kw={'is_signed': True},
|
||||
|
@ -131,8 +138,15 @@ class TestMLBF(TestCase):
|
|||
reused_2_5_addon.update(guid=GUID_REUSE_FORMAT.format(reused_2_1_addon.id))
|
||||
reused_2_1_addon.addonguid.update(guid=current_addon.guid)
|
||||
reused_2_5_addon.addonguid.update(guid=current_addon.guid)
|
||||
self.addon_deleted_before_block = Block.objects.create(
|
||||
guid=current_addon.guid, min_version='2.0.1', updated_by=user
|
||||
self.addon_deleted_before_block = block_factory(
|
||||
guid=current_addon.guid,
|
||||
updated_by=user,
|
||||
version_ids=[
|
||||
self.addon_deleted_before_2_1_ver.id,
|
||||
self.addon_deleted_before_2_5_ver.id,
|
||||
self.addon_deleted_before_unsigned_ver.id,
|
||||
self.addon_deleted_before_3_0_ver.id,
|
||||
],
|
||||
)
|
||||
|
||||
def test_fetch_all_versions_from_db(self):
|
||||
|
@ -144,10 +158,16 @@ class TestMLBF(TestCase):
|
|||
assert (self.five_ver_block.guid, '123.45.1') in all_versions
|
||||
assert (self.five_ver_block.guid, '123.5.1') in all_versions
|
||||
assert (self.five_ver_block.guid, '123.5.2') in all_versions
|
||||
over_tuple = (self.over.guid, self.over.addon.current_version.version)
|
||||
under_tuple = (self.under.guid, self.under.addon.current_version.version)
|
||||
assert over_tuple in all_versions
|
||||
assert under_tuple in all_versions
|
||||
no_versions_tuple = (
|
||||
self.no_versions.guid,
|
||||
self.no_versions.addon.current_version.version,
|
||||
)
|
||||
assert no_versions_tuple in all_versions
|
||||
no_versions2_tuple = (
|
||||
self.no_versions2.guid,
|
||||
self.no_versions2.addon.current_version.version,
|
||||
)
|
||||
assert no_versions2_tuple in all_versions
|
||||
|
||||
assert (self.addon_deleted_before_block.guid, '2') in all_versions
|
||||
# this is fine; test_hash_filter_inputs removes duplicates.
|
||||
|
@ -164,10 +184,16 @@ class TestMLBF(TestCase):
|
|||
assert (self.five_ver_block.guid, '123.45.1') in all_versions
|
||||
assert (self.five_ver_block.guid, '123.5.1') in all_versions
|
||||
assert (self.five_ver_block.guid, '123.5.2') in all_versions
|
||||
over_tuple = (self.over.guid, self.over.addon.current_version.version)
|
||||
under_tuple = (self.under.guid, self.under.addon.current_version.version)
|
||||
assert over_tuple in all_versions
|
||||
assert under_tuple in all_versions
|
||||
no_versions_tuple = (
|
||||
self.no_versions.guid,
|
||||
self.no_versions.addon.current_version.version,
|
||||
)
|
||||
assert no_versions_tuple in all_versions
|
||||
no_versions2_tuple = (
|
||||
self.no_versions2.guid,
|
||||
self.no_versions2.addon.current_version.version,
|
||||
)
|
||||
assert no_versions2_tuple in all_versions
|
||||
|
||||
def test_fetch_blocked_from_db(self):
|
||||
self.setup_data()
|
||||
|
@ -181,10 +207,16 @@ class TestMLBF(TestCase):
|
|||
assert (self.five_ver_block.guid, '123.45.1') not in blocked_guids
|
||||
assert (self.five_ver_block.guid, '123.5.1') not in blocked_guids
|
||||
assert (self.five_ver_block.guid, '123.5.2') not in blocked_guids
|
||||
over_tuple = (self.over.guid, self.over.addon.current_version.version)
|
||||
under_tuple = (self.under.guid, self.under.addon.current_version.version)
|
||||
assert over_tuple not in blocked_guids
|
||||
assert under_tuple not in blocked_guids
|
||||
no_versions_tuple = (
|
||||
self.no_versions.guid,
|
||||
self.no_versions.addon.current_version.version,
|
||||
)
|
||||
assert no_versions_tuple not in blocked_guids
|
||||
no_versions2_tuple = (
|
||||
self.no_versions2.guid,
|
||||
self.no_versions2.addon.current_version.version,
|
||||
)
|
||||
assert no_versions2_tuple not in blocked_guids
|
||||
assert (self.addon_deleted_before_block.guid, '2.1') in blocked_guids
|
||||
assert (self.addon_deleted_before_block.guid, '2.5') in blocked_guids
|
||||
assert (self.addon_deleted_before_block.guid, '3.0') in blocked_guids
|
||||
|
@ -196,8 +228,8 @@ class TestMLBF(TestCase):
|
|||
assert self.five_ver_123_45_1.id not in blocked_versions
|
||||
assert self.not_signed_version.id not in blocked_versions
|
||||
assert self.not_signed_version2.id not in blocked_versions
|
||||
assert self.over.addon.current_version.id not in blocked_versions
|
||||
assert self.under.addon.current_version.id not in blocked_versions
|
||||
assert self.no_versions.addon.current_version.id not in blocked_versions
|
||||
assert self.no_versions2.addon.current_version.id not in blocked_versions
|
||||
|
||||
assert self.addon_deleted_before_unblocked_ver.id not in (blocked_versions)
|
||||
assert self.addon_deleted_before_unsigned_ver.id not in (blocked_versions)
|
||||
|
@ -349,7 +381,7 @@ class TestMLBF(TestCase):
|
|||
assert json.load(stash_file) == empty_stash
|
||||
assert new_mlbf.stash_json == empty_stash
|
||||
# add a new Block and delete one
|
||||
Block.objects.create(
|
||||
block_factory(
|
||||
addon=addon_factory(
|
||||
guid='fooo@baaaa',
|
||||
version_kw={'version': '999'},
|
||||
|
@ -385,7 +417,7 @@ class TestMLBF(TestCase):
|
|||
assert not no_change_mlbf.blocks_changed_since_previous(base_mlbf)
|
||||
|
||||
# make some changes
|
||||
Block.objects.create(
|
||||
block_factory(
|
||||
addon=addon_factory(
|
||||
guid='fooo@baaaa',
|
||||
version_kw={'version': '999'},
|
||||
|
|
|
@ -1,61 +1,27 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
|
||||
from ..models import Block, BlocklistSubmission
|
||||
from ..models import BlocklistSubmission
|
||||
|
||||
|
||||
class TestBlock(TestCase):
|
||||
def test_is_version_blocked(self):
|
||||
block = Block.objects.create(guid='anyguid@', updated_by=user_factory())
|
||||
# default is 0 to *
|
||||
assert block.is_version_blocked('0')
|
||||
# 999999999 is the maximum version part permitted by the linter
|
||||
over_linter_max = str(999999999 + 1234)
|
||||
assert block.is_version_blocked(over_linter_max)
|
||||
|
||||
# Now with some restricted version range
|
||||
block.update(min_version='2.0')
|
||||
assert not block.is_version_blocked('1')
|
||||
assert not block.is_version_blocked('2.0b1')
|
||||
assert block.is_version_blocked('2')
|
||||
assert block.is_version_blocked('3')
|
||||
assert block.is_version_blocked(over_linter_max)
|
||||
block.update(max_version='10.*')
|
||||
assert not block.is_version_blocked('11')
|
||||
assert not block.is_version_blocked(over_linter_max)
|
||||
assert block.is_version_blocked('10')
|
||||
assert block.is_version_blocked('9')
|
||||
assert block.is_version_blocked('10.1')
|
||||
assert block.is_version_blocked('10.%s' % (over_linter_max))
|
||||
|
||||
def test_is_readonly(self):
|
||||
block = Block.objects.create(guid='foo@baa', updated_by=user_factory())
|
||||
block = block_factory(guid='foo@baa', updated_by=user_factory())
|
||||
# not read only by default
|
||||
assert not block.is_readonly
|
||||
# but should be if there's an active BlocklistSubmission
|
||||
block.active_submissions = [object()] # just needs to be non-empty
|
||||
assert block.is_readonly
|
||||
|
||||
def test_no_asterisk_in_min_version(self):
|
||||
non_user_writeable_fields = (
|
||||
'average_daily_users_snapshot',
|
||||
'guid',
|
||||
)
|
||||
block = Block(min_version='123.4', max_version='*', updated_by=user_factory())
|
||||
block.full_clean(exclude=non_user_writeable_fields)
|
||||
block.min_version = '*'
|
||||
with self.assertRaises(ValidationError):
|
||||
block.full_clean(exclude=non_user_writeable_fields)
|
||||
block.min_version = '0'
|
||||
block.full_clean(exclude=non_user_writeable_fields)
|
||||
block.min_version = '123.*'
|
||||
with self.assertRaises(ValidationError):
|
||||
block.full_clean(exclude=non_user_writeable_fields)
|
||||
|
||||
|
||||
class TestBlocklistSubmissionManager(TestCase):
|
||||
def test_delayed(self):
|
||||
|
@ -149,20 +115,11 @@ class TestBlocklistSubmission(TestCase):
|
|||
assert list(BlocklistSubmission.get_submissions_from_guid('ggguid@')) == []
|
||||
|
||||
def test_all_adu_safe(self):
|
||||
Block.objects.create(
|
||||
addon=addon_factory(guid='zero@adu', average_daily_users=0),
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
Block.objects.create(
|
||||
addon=addon_factory(guid='normal@adu', average_daily_users=500),
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
Block.objects.create(
|
||||
addon=addon_factory(guid='high@adu', average_daily_users=999_999),
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
addon_factory(guid='zero@adu', average_daily_users=0)
|
||||
addon_factory(guid='normal@adu', average_daily_users=500)
|
||||
addon_factory(guid='high@adu', average_daily_users=999_999)
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
input_guids='zero@adu\nnormal@adu', min_version=99
|
||||
input_guids='zero@adu\nnormal@adu'
|
||||
)
|
||||
|
||||
submission.to_block = submission._serialize_blocks()
|
||||
|
@ -180,38 +137,20 @@ class TestBlocklistSubmission(TestCase):
|
|||
assert not submission.all_adu_safe()
|
||||
|
||||
def test_has_version_changes(self):
|
||||
block = Block.objects.create(
|
||||
addon=addon_factory(guid='guid@'), updated_by=user_factory()
|
||||
addon = addon_factory(guid='guid@')
|
||||
block_factory(addon=addon, updated_by=user_factory(), reason='things')
|
||||
new_version = version_factory(addon=addon)
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
input_guids=addon.guid, changed_version_ids=[]
|
||||
)
|
||||
submission = BlocklistSubmission.objects.create(input_guids='guid@')
|
||||
|
||||
submission.to_block = submission._serialize_blocks()
|
||||
# no changes to anything
|
||||
# reason is chaning, but no versions are being changed
|
||||
assert not submission.has_version_changes()
|
||||
|
||||
block.update(min_version='999', reason='things')
|
||||
# min_version has changed (and reason)
|
||||
submission.update(changed_version_ids=[new_version.id])
|
||||
assert submission.has_version_changes()
|
||||
|
||||
submission.update(min_version='999')
|
||||
# if min_version is the same then it's only the metadata (reason)
|
||||
assert not submission.has_version_changes()
|
||||
|
||||
def test_no_asterisk_in_min_version(self):
|
||||
non_user_writeable_fields = ('updated_by', 'signoff_by', 'to_block')
|
||||
submission = BlocklistSubmission(
|
||||
min_version='123.4', max_version='*', input_guids='df@'
|
||||
)
|
||||
submission.full_clean(exclude=non_user_writeable_fields)
|
||||
submission.min_version = '*'
|
||||
with self.assertRaises(ValidationError):
|
||||
submission.full_clean(exclude=non_user_writeable_fields)
|
||||
submission.min_version = '0'
|
||||
submission.full_clean(exclude=non_user_writeable_fields)
|
||||
submission.min_version = '123.*'
|
||||
with self.assertRaises(ValidationError):
|
||||
submission.full_clean(exclude=non_user_writeable_fields)
|
||||
|
||||
def test_is_delayed(self):
|
||||
now = datetime.now()
|
||||
submission = BlocklistSubmission.objects.create(
|
||||
|
|
|
@ -10,7 +10,6 @@ class TestBlockSerializer(TestCase):
|
|||
def setUp(self):
|
||||
self.block = Block.objects.create(
|
||||
guid='foo@baa',
|
||||
min_version='45',
|
||||
reason='something happened',
|
||||
url='https://goo.gol',
|
||||
updated_by=user_factory(),
|
||||
|
@ -22,8 +21,8 @@ class TestBlockSerializer(TestCase):
|
|||
'id': self.block.id,
|
||||
'addon_name': None,
|
||||
'guid': 'foo@baa',
|
||||
'min_version': '45',
|
||||
'max_version': '*',
|
||||
'min_version': '',
|
||||
'max_version': '',
|
||||
'reason': 'something happened',
|
||||
'url': {
|
||||
'url': 'https://goo.gol',
|
||||
|
@ -36,13 +35,36 @@ class TestBlockSerializer(TestCase):
|
|||
}
|
||||
|
||||
def test_with_addon(self):
|
||||
addon_factory(guid=self.block.guid, name='Addón náme')
|
||||
BlockVersion.objects.create(
|
||||
block=self.block, version=self.block.addon.current_version
|
||||
addon = addon_factory(
|
||||
guid=self.block.guid, name='Addón náme', version_kw={'version': '1.0'}
|
||||
)
|
||||
_version_1 = addon.current_version
|
||||
_version_5 = version_factory(addon=addon, version='5555')
|
||||
version_2 = version_factory(
|
||||
addon=addon, channel=amo.CHANNEL_UNLISTED, version='2.0.2'
|
||||
)
|
||||
version_4 = version_factory(addon=addon, version='4')
|
||||
_version_3 = version_factory(addon=addon, version='3b1')
|
||||
BlockVersion.objects.create(block=self.block, version=version_2)
|
||||
BlockVersion.objects.create(block=self.block, version=version_4)
|
||||
|
||||
serializer = BlockSerializer(instance=self.block)
|
||||
assert serializer.data['addon_name'] == {'en-US': 'Addón náme'}
|
||||
assert serializer.data['versions'] == [self.block.addon.current_version.version]
|
||||
assert serializer.data == {
|
||||
'id': self.block.id,
|
||||
'addon_name': {'en-US': 'Addón náme'},
|
||||
'guid': 'foo@baa',
|
||||
'min_version': version_2.version,
|
||||
'max_version': version_4.version,
|
||||
'reason': 'something happened',
|
||||
'url': {
|
||||
'url': 'https://goo.gol',
|
||||
'outgoing': get_outgoing_url('https://goo.gol'),
|
||||
},
|
||||
'versions': [version_2.version, version_4.version],
|
||||
'is_all_versions': False,
|
||||
'created': self.block.created.isoformat()[:-7] + 'Z',
|
||||
'modified': self.block.modified.isoformat()[:-7] + 'Z',
|
||||
}
|
||||
|
||||
def test_is_all_versions(self):
|
||||
# no add-on so True
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
from django.test.utils import override_settings
|
||||
|
||||
from olympia.amo.tests import TestCase, addon_factory, reverse_ns, user_factory
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
reverse_ns,
|
||||
user_factory,
|
||||
)
|
||||
from olympia.amo.urlresolvers import get_outgoing_url
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.blocklist.serializers import BlockSerializer
|
||||
|
||||
|
||||
class TestBlockViewSet(TestCase):
|
||||
def setUp(self):
|
||||
self.block = Block.objects.create(
|
||||
guid='foo@baa.com',
|
||||
min_version='45',
|
||||
self.addon = addon_factory(
|
||||
guid='foo@baa.com', name='English name', default_locale='en-CA'
|
||||
)
|
||||
self.block = block_factory(
|
||||
addon=self.addon,
|
||||
reason='something happened',
|
||||
url='https://goo.gol',
|
||||
updated_by=user_factory(),
|
||||
|
@ -43,11 +50,8 @@ class TestBlockViewSet(TestCase):
|
|||
}
|
||||
|
||||
def test_addon_name(self):
|
||||
addon = addon_factory(
|
||||
guid=self.block.guid, name='English name', default_locale='en-CA'
|
||||
)
|
||||
addon.name = {'fr': 'Lé name Francois'}
|
||||
addon.save()
|
||||
self.addon.name = {'fr': 'Lé name Francois'}
|
||||
self.addon.save()
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
assert response.json()['addon_name'] == {
|
||||
|
|
|
@ -9,28 +9,32 @@ from olympia.users.utils import get_task_user
|
|||
log = olympia.core.logger.getLogger('z.amo.blocklist')
|
||||
|
||||
|
||||
def add_version_log_for_blocked_versions(obj, old_obj, al):
|
||||
def add_version_log_for_blocked_versions(obj, al, submission_obj=None):
|
||||
from olympia.activity.models import VersionLog
|
||||
|
||||
VersionLog.objects.bulk_create(
|
||||
[
|
||||
VersionLog(activity_log=al, version_id=version.id)
|
||||
VersionLog(activity_log=al, version=version)
|
||||
for version in obj.addon_versions
|
||||
if obj.is_version_blocked(version.version)
|
||||
or old_obj.is_version_blocked(version.version)
|
||||
if version.is_blocked
|
||||
or (submission_obj and version.id in submission_obj.changed_version_ids)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def block_activity_log_save(obj, change, submission_obj=None, old_obj=None):
|
||||
def block_activity_log_save(
|
||||
obj,
|
||||
change,
|
||||
submission_obj=None,
|
||||
):
|
||||
action = amo.LOG.BLOCKLIST_BLOCK_EDITED if change else amo.LOG.BLOCKLIST_BLOCK_ADDED
|
||||
version_ids = sorted(ver.id for ver in obj.addon_versions if ver.is_blocked)
|
||||
details = {
|
||||
'guid': obj.guid,
|
||||
'min_version': obj.min_version,
|
||||
'max_version': obj.max_version,
|
||||
'versions': version_ids,
|
||||
'url': obj.url,
|
||||
'reason': obj.reason,
|
||||
'comments': f'Versions {obj.min_version} - {obj.max_version} blocked.',
|
||||
'comments': f'{len(version_ids)} versions blocked.',
|
||||
}
|
||||
if submission_obj:
|
||||
details['signoff_state'] = submission_obj.SIGNOFF_STATES.get(
|
||||
|
@ -50,19 +54,25 @@ def block_activity_log_save(obj, change, submission_obj=None, old_obj=None):
|
|||
user=submission_obj.signoff_by,
|
||||
)
|
||||
|
||||
add_version_log_for_blocked_versions(obj, old_obj or obj, al)
|
||||
add_version_log_for_blocked_versions(obj, al)
|
||||
|
||||
|
||||
def block_activity_log_delete(obj, *, submission_obj=None, delete_user=None):
|
||||
def block_activity_log_delete(obj, deleted, *, submission_obj=None, delete_user=None):
|
||||
assert submission_obj or delete_user
|
||||
version_ids = [ver.id for ver in obj.addon_versions if ver.is_blocked]
|
||||
details = {
|
||||
'guid': obj.guid,
|
||||
'min_version': obj.min_version,
|
||||
'max_version': obj.max_version,
|
||||
'versions': version_ids,
|
||||
'url': obj.url,
|
||||
'reason': obj.reason,
|
||||
'comments': f'Versions {obj.min_version} - {obj.max_version} unblocked.',
|
||||
'comments': f'{len(version_ids)} versions unblocked.',
|
||||
}
|
||||
action = (
|
||||
amo.LOG.BLOCKLIST_BLOCK_EDITED
|
||||
if not deleted
|
||||
else amo.LOG.BLOCKLIST_BLOCK_DELETED
|
||||
)
|
||||
|
||||
if submission_obj:
|
||||
details['signoff_state'] = submission_obj.SIGNOFF_STATES.get(
|
||||
submission_obj.signoff_state
|
||||
|
@ -70,22 +80,21 @@ def block_activity_log_delete(obj, *, submission_obj=None, delete_user=None):
|
|||
if submission_obj.signoff_by:
|
||||
details['signoff_by'] = submission_obj.signoff_by.id
|
||||
addon = obj.addon
|
||||
args = (
|
||||
[amo.LOG.BLOCKLIST_BLOCK_DELETED] + ([addon] if addon else []) + [obj.guid, obj]
|
||||
)
|
||||
al = log_create(
|
||||
*args,
|
||||
*[action, *([addon] if addon else []), obj.guid, obj],
|
||||
details=details,
|
||||
user=submission_obj.updated_by if submission_obj else delete_user,
|
||||
)
|
||||
if addon:
|
||||
add_version_log_for_blocked_versions(obj, obj, al)
|
||||
add_version_log_for_blocked_versions(obj, al, submission_obj)
|
||||
if submission_obj and submission_obj.signoff_by:
|
||||
args = (
|
||||
[amo.LOG.BLOCKLIST_SIGNOFF]
|
||||
+ ([addon] if addon else [])
|
||||
+ [obj.guid, amo.LOG.BLOCKLIST_BLOCK_DELETED.action_class, obj]
|
||||
)
|
||||
args = [
|
||||
amo.LOG.BLOCKLIST_SIGNOFF,
|
||||
*([addon] if addon else []),
|
||||
obj.guid,
|
||||
action.action_class,
|
||||
obj,
|
||||
]
|
||||
log_create(*args, user=submission_obj.signoff_by)
|
||||
|
||||
|
||||
|
@ -99,14 +108,10 @@ def datetime_to_ts(dt=None):
|
|||
return int((dt or datetime.now()).timestamp() * 1000)
|
||||
|
||||
|
||||
def disable_addon_for_block(block):
|
||||
"""Disable appropriate addon versions that are affected by the Block, and
|
||||
the addon too if 0 - *."""
|
||||
from olympia.addons.models import GuidAlreadyDeniedError
|
||||
def disable_versions_for_block(block, submission):
|
||||
"""Disable appropriate addon versions that are affected by the Block."""
|
||||
from olympia.reviewers.utils import ReviewBase
|
||||
|
||||
from .models import Block
|
||||
|
||||
review = ReviewBase(
|
||||
addon=block.addon,
|
||||
version=None,
|
||||
|
@ -120,7 +125,7 @@ def disable_addon_for_block(block):
|
|||
# We don't need to reject versions from older deleted instances
|
||||
# and already disabled files
|
||||
if ver.addon == block.addon
|
||||
and block.is_version_blocked(ver.version)
|
||||
and ver.id in submission.changed_version_ids
|
||||
and ver.file.status != amo.STATUS_DISABLED
|
||||
]
|
||||
review.set_data({'versions': versions_to_reject})
|
||||
|
@ -132,51 +137,34 @@ def disable_addon_for_block(block):
|
|||
# versions we are rejecting, which is only a subset).
|
||||
review.clear_specific_needs_human_review_flags(version)
|
||||
|
||||
if block.min_version == Block.MIN and block.max_version == Block.MAX:
|
||||
if block.addon.status == amo.STATUS_DELETED:
|
||||
try:
|
||||
block.addon.deny_resubmission()
|
||||
except GuidAlreadyDeniedError:
|
||||
pass
|
||||
else:
|
||||
block.addon.update(status=amo.STATUS_DISABLED)
|
||||
|
||||
def save_versions_to_blocks(guids, submission, *, fields_to_set):
|
||||
from olympia.addons.models import GuidAlreadyDeniedError
|
||||
|
||||
def save_guids_to_blocks(guids, submission, *, fields_to_set):
|
||||
from .models import Block, BlockVersion
|
||||
|
||||
common_args = {field: getattr(submission, field) for field in fields_to_set}
|
||||
modified_datetime = datetime.now()
|
||||
|
||||
blocks = Block.get_blocks_from_guids(guids)
|
||||
Block.preload_addon_versions(blocks)
|
||||
for block in blocks:
|
||||
change = bool(block.id)
|
||||
if change:
|
||||
block_obj_before_change = Block(
|
||||
min_version=block.min_version, max_version=block.max_version
|
||||
)
|
||||
setattr(block, 'modified', modified_datetime)
|
||||
else:
|
||||
block_obj_before_change = None
|
||||
for field, val in common_args.items():
|
||||
setattr(block, field, val)
|
||||
block.average_daily_users_snapshot = block.current_adu
|
||||
block.save()
|
||||
|
||||
if change:
|
||||
# if not a new Block then delete any BlockVersions that are outside min-max
|
||||
BlockVersion.objects.filter(block=block).exclude(
|
||||
version__version__gte=block.min_version,
|
||||
version__version__lte=block.max_version,
|
||||
).delete()
|
||||
# create BlockVersions for versions in min-max range, that don't already exist
|
||||
BlockVersion.objects.bulk_create(
|
||||
BlockVersion(version=version, block=block)
|
||||
for version in block.addon_versions
|
||||
if not hasattr(version, 'blockversion')
|
||||
and block.is_version_blocked(version.version)
|
||||
)
|
||||
# And now update the BlockVersion instances - instances to add first
|
||||
block_versions_to_create = []
|
||||
for version in block.addon_versions:
|
||||
if version.id in submission.changed_version_ids and (
|
||||
not change or not version.is_blocked
|
||||
):
|
||||
block_version = BlockVersion(block=block, version=version)
|
||||
block_versions_to_create.append(block_version)
|
||||
version.blockversion = block_version
|
||||
BlockVersion.objects.bulk_create(block_versions_to_create)
|
||||
|
||||
if submission.id:
|
||||
block.submission.add(submission)
|
||||
|
@ -184,8 +172,56 @@ def save_guids_to_blocks(guids, submission, *, fields_to_set):
|
|||
block,
|
||||
change=change,
|
||||
submission_obj=submission if submission.id else None,
|
||||
old_obj=block_obj_before_change,
|
||||
)
|
||||
disable_addon_for_block(block)
|
||||
disable_versions_for_block(block, submission)
|
||||
if submission.disable_addon:
|
||||
if block.addon.status == amo.STATUS_DELETED:
|
||||
try:
|
||||
block.addon.deny_resubmission()
|
||||
except GuidAlreadyDeniedError:
|
||||
pass
|
||||
else:
|
||||
block.addon.update(status=amo.STATUS_DISABLED)
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
def delete_versions_from_blocks(guids, submission, *, fields_to_set):
|
||||
from .models import Block, BlockVersion
|
||||
|
||||
common_args = {field: getattr(submission, field) for field in fields_to_set}
|
||||
modified_datetime = datetime.now()
|
||||
|
||||
blocks = Block.get_blocks_from_guids(guids)
|
||||
for block in blocks:
|
||||
if not block.id:
|
||||
continue
|
||||
setattr(block, 'modified', modified_datetime)
|
||||
|
||||
BlockVersion.objects.filter(
|
||||
block=block, version_id__in=submission.changed_version_ids
|
||||
).delete()
|
||||
|
||||
if BlockVersion.objects.filter(block=block).exists():
|
||||
# if there are still other versions blocked update the metadata
|
||||
for field, val in common_args.items():
|
||||
setattr(block, field, val)
|
||||
block.average_daily_users_snapshot = block.current_adu
|
||||
block.save()
|
||||
should_delete = False
|
||||
|
||||
if submission.id:
|
||||
block.submission.add(submission)
|
||||
else:
|
||||
# otherwise we can delete the Block instance
|
||||
should_delete = True
|
||||
|
||||
block_activity_log_delete(
|
||||
block,
|
||||
deleted=should_delete,
|
||||
submission_obj=submission if submission.id else None,
|
||||
)
|
||||
if should_delete:
|
||||
block.delete()
|
||||
|
||||
return blocks
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.forms.models import BaseModelFormSet, modelformset_factory
|
|||
from django.forms.widgets import RadioSelect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import keep_lazy_text
|
||||
from django.utils.html import escape, format_html
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ngettext
|
||||
|
||||
|
@ -45,7 +45,6 @@ from olympia.amo.messages import DoubleSafe
|
|||
from olympia.amo.utils import remove_icons, slug_validator
|
||||
from olympia.amo.validators import OneOrMoreLetterOrNumberCharacterValidator
|
||||
from olympia.applications.models import AppVersion
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.constants.categories import CATEGORIES, CATEGORIES_BY_ID, CATEGORIES_NO_APP
|
||||
from olympia.devhub.widgets import CategoriesSelectMultiple, IconTypeSelect
|
||||
from olympia.files.models import FileUpload
|
||||
|
@ -1051,31 +1050,6 @@ class NewUploadForm(CheckThrottlesMixin, forms.Form):
|
|||
gettext('There was an error with your upload. Please try again.')
|
||||
)
|
||||
|
||||
def check_blocklist(self, guid, version_string):
|
||||
# check the guid/version isn't in the addon blocklist
|
||||
block = Block.objects.filter(guid=guid).first()
|
||||
if block and block.is_version_blocked(version_string):
|
||||
msg = escape(
|
||||
gettext(
|
||||
'Version {version} matches {block_link} for this add-on. '
|
||||
'You can contact {amo_admins} for additional information.'
|
||||
)
|
||||
)
|
||||
formatted_msg = DoubleSafe(
|
||||
msg.format(
|
||||
version=version_string,
|
||||
block_link=format_html(
|
||||
'<a href="{}">{}</a>',
|
||||
reverse('blocklist.block', args=[guid]),
|
||||
gettext('a blocklist entry'),
|
||||
),
|
||||
amo_admins=(
|
||||
'<a href="mailto:amo-admins@mozilla.com">AMO Admins</a>'
|
||||
),
|
||||
)
|
||||
)
|
||||
raise forms.ValidationError(formatted_msg)
|
||||
|
||||
def check_for_existing_versions(self, version_string):
|
||||
# Make sure we don't already have this version.
|
||||
existing_versions = Version.unfiltered.filter(
|
||||
|
@ -1111,10 +1085,6 @@ class NewUploadForm(CheckThrottlesMixin, forms.Form):
|
|||
self.cleaned_data['upload'], self.addon, user=self.request.user
|
||||
)
|
||||
|
||||
self.check_blocklist(
|
||||
self.addon.guid if self.addon else parsed_data.get('guid'),
|
||||
parsed_data.get('version'),
|
||||
)
|
||||
if self.addon:
|
||||
self.check_for_existing_versions(parsed_data.get('version'))
|
||||
if self.cleaned_data['upload'].channel == amo.CHANNEL_LISTED:
|
||||
|
|
|
@ -28,10 +28,8 @@ from olympia.amo.tests import (
|
|||
create_default_webext_appversion,
|
||||
formset,
|
||||
initial,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.constants.licenses import LICENSES_BY_BUILTIN
|
||||
from olympia.constants.promoted import NOTABLE
|
||||
from olympia.devhub import views
|
||||
|
@ -539,24 +537,6 @@ class TestAddonSubmitUpload(UploadMixin, TestCase):
|
|||
assert 'new_addon_form' not in response.context
|
||||
assert get_addon_count('Beastify') == 2
|
||||
|
||||
def test_new_addon_is_already_blocked(self):
|
||||
self.upload = self.get_upload('webextension.xpi', user=self.user)
|
||||
guid = '@webextension-guid'
|
||||
block = Block.objects.create(guid=guid, updated_by=user_factory())
|
||||
|
||||
response = self.post(expect_errors=True)
|
||||
assert pq(response.content)('ul.errorlist').text() == (
|
||||
'Version 0.0.1 matches a blocklist entry for this add-on. '
|
||||
'You can contact AMO Admins for additional information.'
|
||||
)
|
||||
assert pq(response.content)('ul.errorlist a').attr('href') == (
|
||||
reverse('blocklist.block', args=[guid])
|
||||
)
|
||||
|
||||
# Though we allow if the version is outside of the specified range
|
||||
block.update(min_version='0.0.2')
|
||||
response = self.post(expect_errors=False)
|
||||
|
||||
def test_success_listed(self):
|
||||
assert Addon.objects.count() == 0
|
||||
response = self.post()
|
||||
|
@ -2024,21 +2004,6 @@ class VersionSubmitUploadMixin:
|
|||
)
|
||||
)
|
||||
|
||||
def test_addon_version_is_blocked(self):
|
||||
block = Block.objects.create(guid=self.addon.guid, updated_by=user_factory())
|
||||
response = self.post(expected_status=200)
|
||||
assert pq(response.content)('ul.errorlist').text() == (
|
||||
'Version 0.0.1 matches a blocklist entry for this add-on. '
|
||||
'You can contact AMO Admins for additional information.'
|
||||
)
|
||||
assert pq(response.content)('ul.errorlist a').attr('href') == (
|
||||
reverse('blocklist.block', args=[self.addon.guid])
|
||||
)
|
||||
|
||||
# Though we allow if the version is outside of the specified range
|
||||
block.update(min_version='0.2')
|
||||
response = self.post(expected_status=302), response.content
|
||||
|
||||
def test_distribution_link(self):
|
||||
response = self.client.get(self.url)
|
||||
channel_text = 'listed' if self.channel == amo.CHANNEL_LISTED else 'unlisted'
|
||||
|
|
|
@ -395,10 +395,11 @@
|
|||
{% endif %}
|
||||
|
||||
<li>
|
||||
{% if addon.blocklistsubmission %}
|
||||
<a href="{{ url('admin:blocklist_blocklistsubmission_change', addon.blocklistsubmission.id) }}" class="button"
|
||||
id="edit_addon_blocklistsubmission" type="button">View Blocklist Submission</a>
|
||||
{% elif not addon.block %}
|
||||
{% if addon.blocklistsubmissions %}
|
||||
<a href="{{ url('admin:blocklist_blocklistsubmission_change', addon.blocklistsubmissions[0].id) }}" class="button"
|
||||
id="edit_addon_blocklistsubmission" type="button">View Active Blocklist Submission</a>
|
||||
{% endif %}
|
||||
{% if not addon.block %}
|
||||
<a href="{{ url('admin:blocklist_block_addaddon', addon.pk) }}" class="button"
|
||||
id="block_addon" type="button">Block add-on</a>
|
||||
{% else %}
|
||||
|
|
|
@ -6,8 +6,13 @@ from pyquery import PyQuery as pq
|
|||
|
||||
from olympia import amo
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory, version_factory
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.constants.reviewers import REVIEWER_DELAYED_REJECTION_PERIOD_DAYS_DEFAULT
|
||||
from olympia.reviewers.forms import ReviewForm
|
||||
from olympia.reviewers.models import (
|
||||
|
@ -368,10 +373,9 @@ class TestReviewForm(TestCase):
|
|||
channel=amo.CHANNEL_LISTED,
|
||||
file_kw={'status': amo.STATUS_DISABLED},
|
||||
)
|
||||
Block.objects.create(
|
||||
addon=self.addon,
|
||||
min_version=blocked_version.version,
|
||||
max_version=blocked_version.version,
|
||||
block_factory(
|
||||
addon=blocked_version.addon,
|
||||
version_ids=[blocked_version.id],
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
# auto-approve everything (including self.addon.current_version)
|
||||
|
@ -480,10 +484,9 @@ class TestReviewForm(TestCase):
|
|||
channel=amo.CHANNEL_UNLISTED,
|
||||
file_kw={'status': amo.STATUS_DISABLED},
|
||||
)
|
||||
Block.objects.create(
|
||||
addon=self.addon,
|
||||
min_version=blocked_version.version,
|
||||
max_version=blocked_version.version,
|
||||
block_factory(
|
||||
addon=blocked_version.addon,
|
||||
version_ids=[blocked_version.id],
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
self.version.update(channel=amo.CHANNEL_UNLISTED)
|
||||
|
|
|
@ -13,10 +13,11 @@ from olympia.addons.models import AddonApprovalsCounter, AddonReviewerFlags, Add
|
|||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.blocklist.models import BlockVersion
|
||||
from olympia.constants.promoted import (
|
||||
LINE,
|
||||
NOTABLE,
|
||||
|
@ -1285,18 +1286,18 @@ class TestAutoApprovalSummary(TestCase):
|
|||
def test_check_is_blocked(self):
|
||||
assert AutoApprovalSummary.check_is_blocked(self.version) is False
|
||||
|
||||
block = Block.objects.create(addon=self.addon, updated_by=user_factory())
|
||||
del self.version.addon.block
|
||||
block_factory(
|
||||
addon=self.addon, updated_by=user_factory(), version_ids=[self.version.id]
|
||||
)
|
||||
self.version.refresh_from_db()
|
||||
assert AutoApprovalSummary.check_is_blocked(self.version) is True
|
||||
|
||||
block.update(min_version='9999999')
|
||||
del self.version.addon.block
|
||||
BlockVersion.objects.get().update(
|
||||
version=version_factory(addon=self.version.addon)
|
||||
)
|
||||
self.version.refresh_from_db()
|
||||
assert AutoApprovalSummary.check_is_blocked(self.version) is False
|
||||
|
||||
block.update(min_version='0')
|
||||
del self.version.addon.block
|
||||
assert AutoApprovalSummary.check_is_blocked(self.version) is True
|
||||
|
||||
def test_check_is_locked(self):
|
||||
assert AutoApprovalSummary.check_is_locked(self.version) is False
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ from olympia.amo.templatetags.jinja_helpers import absolutify
|
|||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
version_review_flags_factory,
|
||||
|
@ -676,8 +677,9 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
)
|
||||
|
||||
# But when the add-on is blocked 'public' shouldn't be available
|
||||
block = Block.objects.create(addon=self.addon, updated_by=self.user)
|
||||
del self.addon.block
|
||||
block_factory(addon=self.addon, updated_by=self.user)
|
||||
self.review_version.refresh_from_db()
|
||||
assert self.review_version.is_blocked
|
||||
expected = [
|
||||
'reject',
|
||||
'reject_multiple_versions',
|
||||
|
@ -695,8 +697,10 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
== expected
|
||||
)
|
||||
|
||||
# it's okay if the version is outside the blocked range though
|
||||
block.update(min_version=self.review_version.version + '.1')
|
||||
# it's okay if a different version of the add-on is blocked though
|
||||
self.review_version = version_factory(addon=self.review_version.addon)
|
||||
self.file = self.review_version.file
|
||||
assert not self.review_version.is_blocked
|
||||
expected = [
|
||||
'public',
|
||||
'reject',
|
||||
|
@ -705,7 +709,15 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
'reply',
|
||||
'comment',
|
||||
]
|
||||
del self.addon.block
|
||||
assert (
|
||||
list(
|
||||
self.get_review_actions(
|
||||
addon_status=amo.STATUS_APPROVED,
|
||||
file_status=amo.STATUS_AWAITING_REVIEW,
|
||||
).keys()
|
||||
)
|
||||
== expected
|
||||
)
|
||||
|
||||
def test_actions_pending_rejection(self):
|
||||
# An addon having its latest version pending rejection won't be
|
||||
|
@ -2837,7 +2849,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
self._test_block_multiple_unlisted_versions(redirect_url)
|
||||
|
||||
def test_existing_block_multiple_unlisted_versions(self):
|
||||
Block.objects.create(guid=self.addon.guid, updated_by=user_factory())
|
||||
block_factory(guid=self.addon.guid, updated_by=user_factory())
|
||||
redirect_url = (
|
||||
reverse('admin:blocklist_block_addaddon', args=(self.addon.id,))
|
||||
+ '?v=%s&v=%s'
|
||||
|
@ -2845,9 +2857,9 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
self._test_block_multiple_unlisted_versions(redirect_url)
|
||||
|
||||
def test_approve_latest_version_fails_for_blocked_version(self):
|
||||
Block.objects.create(addon=self.addon, updated_by=user_factory())
|
||||
block_factory(addon=self.addon, updated_by=user_factory())
|
||||
self.review_version.refresh_from_db()
|
||||
self.setup_data(amo.STATUS_NOMINATED)
|
||||
del self.addon.block
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
self.helper.handler.approve_latest_version()
|
||||
|
|
|
@ -46,6 +46,7 @@ from olympia.amo.tests import (
|
|||
APITestClientSessionID,
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
check_links,
|
||||
formset,
|
||||
initial,
|
||||
|
@ -54,7 +55,7 @@ from olympia.amo.tests import (
|
|||
version_factory,
|
||||
version_review_flags_factory,
|
||||
)
|
||||
from olympia.blocklist.models import Block, BlocklistSubmission
|
||||
from olympia.blocklist.models import Block, BlocklistSubmission, BlockVersion
|
||||
from olympia.blocklist.utils import block_activity_log_save
|
||||
from olympia.constants.promoted import LINE, NOTABLE, RECOMMENDED, SPOTLIGHT, STRATEGIC
|
||||
from olympia.constants.reviewers import REVIEWER_DELAYED_REJECTION_PERIOD_DAYS_DEFAULT
|
||||
|
@ -3241,13 +3242,13 @@ class TestReview(ReviewBase):
|
|||
reverse('admin:blocklist_block_addaddon', args=(self.addon.id,))
|
||||
)
|
||||
|
||||
# If the guid is in a pending submission we show a link to that instead
|
||||
# If the guid is in a pending submission we show a link to that too
|
||||
subm = BlocklistSubmission.objects.create(input_guids=self.addon.guid)
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
doc = pq(response.content)
|
||||
assert not doc('#block_addon')
|
||||
assert not doc('#edit_addon_block')
|
||||
assert doc('#edit_addon_block')
|
||||
blocklistsubmission_block = doc('#edit_addon_blocklistsubmission')
|
||||
assert blocklistsubmission_block
|
||||
assert blocklistsubmission_block[0].attrib.get('href') == (
|
||||
|
@ -4460,10 +4461,8 @@ class TestReview(ReviewBase):
|
|||
|
||||
new_block_url = reverse(
|
||||
'admin:blocklist_blocklistsubmission_add'
|
||||
) + '?guids={}&min_version={}&max_version={}'.format(
|
||||
self.addon.guid,
|
||||
old_version.version,
|
||||
self.version.version,
|
||||
) + '?guids={}&v={}&v={}'.format(
|
||||
self.addon.guid, old_version.pk, self.version.pk
|
||||
)
|
||||
self.assertRedirects(response, new_block_url)
|
||||
|
||||
|
@ -5332,21 +5331,24 @@ class TestReview(ReviewBase):
|
|||
assert response.status_code == 200
|
||||
assert b'Blocked' not in response.content
|
||||
|
||||
block = Block.objects.create(guid=self.addon.guid, updated_by=user_factory())
|
||||
block = block_factory(guid=self.addon.guid, updated_by=user_factory())
|
||||
response = self.client.get(self.url)
|
||||
assert b'Blocked' in response.content
|
||||
span = pq(response.content)('#versions-history .blocked-version')
|
||||
assert span.text() == 'Blocked'
|
||||
assert span.length == 1 # addon only has 1 version
|
||||
|
||||
version_factory(addon=self.addon, version='99')
|
||||
blockversion = BlockVersion.objects.create(
|
||||
block=block, version=version_factory(addon=self.addon, version='99')
|
||||
)
|
||||
response = self.client.get(self.url)
|
||||
span = pq(response.content)('#versions-history .blocked-version')
|
||||
assert span.text() == 'Blocked Blocked'
|
||||
assert span.length == 2 # a new version is blocked too
|
||||
|
||||
block_reason = 'Very bad addon!'
|
||||
block.update(max_version='98', reason=block_reason)
|
||||
blockversion.delete()
|
||||
block.update(reason=block_reason)
|
||||
block_activity_log_save(obj=block, change=False)
|
||||
response = self.client.get(self.url)
|
||||
span = pq(response.content)('#versions-history .blocked-version')
|
||||
|
|
|
@ -495,6 +495,7 @@ def review(request, addon, channel=None):
|
|||
.select_related('autoapprovalsummary')
|
||||
.select_related('reviewerflags')
|
||||
.select_related('file___webext_permissions')
|
||||
.select_related('blockversion')
|
||||
# Prefetch needshumanreview existence into a property that the
|
||||
# VersionsChoiceWidget will use.
|
||||
.annotate(needs_human_review=Exists(needs_human_review_qs))
|
||||
|
|
|
@ -10,10 +10,10 @@ from olympia import amo
|
|||
from olympia.amo.tests import (
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.constants.scanners import (
|
||||
ABORTED,
|
||||
ABORTING,
|
||||
|
@ -749,7 +749,7 @@ class TestRunYaraQueryRule(TestCase):
|
|||
|
||||
def test_run_on_chunk_was_blocked(self):
|
||||
self.rule.update(state=RUNNING) # Pretend we started running the rule.
|
||||
Block.objects.create(guid=self.version.addon.guid, updated_by=user_factory())
|
||||
block_factory(addon=self.version.addon, updated_by=user_factory())
|
||||
run_yara_query_rule_on_versions_chunk([self.version.pk], self.rule.pk)
|
||||
|
||||
yara_results = ScannerQueryResult.objects.all()
|
||||
|
@ -761,13 +761,16 @@ class TestRunYaraQueryRule(TestCase):
|
|||
def test_run_on_chunk_not_blocked(self):
|
||||
self.rule.update(state=RUNNING) # Pretend we started running the rule.
|
||||
self.version.update(version='2.0')
|
||||
Block.objects.create(
|
||||
guid=self.version.addon.guid,
|
||||
updated_by=user_factory(),
|
||||
max_version='1.0',
|
||||
another_version = version_factory(
|
||||
addon=self.version.addon, channel=amo.CHANNEL_UNLISTED
|
||||
)
|
||||
Block.objects.create(
|
||||
guid='@differentguid',
|
||||
block_factory(
|
||||
addon=self.version.addon,
|
||||
updated_by=user_factory(),
|
||||
version_ids=[another_version.id],
|
||||
)
|
||||
block_factory(
|
||||
addon=addon_factory(guid='@differentguid'),
|
||||
updated_by=user_factory(),
|
||||
)
|
||||
run_yara_query_rule_on_versions_chunk([self.version.pk], self.rule.pk)
|
||||
|
|
|
@ -9,12 +9,12 @@ from olympia.amo.tests import (
|
|||
APITestClientSessionID,
|
||||
TestCase,
|
||||
addon_factory,
|
||||
block_factory,
|
||||
reverse_ns,
|
||||
user_factory,
|
||||
version_factory,
|
||||
)
|
||||
from olympia.api.tests.utils import APIKeyAuthTestMixin
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.blocklist.utils import block_activity_log_save
|
||||
from olympia.constants.scanners import (
|
||||
CUSTOMS,
|
||||
|
@ -231,21 +231,21 @@ class TestScannerResultViewInternal(TestCase):
|
|||
blocked_addon_1 = addon_factory()
|
||||
blocked_version_1 = version_factory(addon=blocked_addon_1)
|
||||
ScannerResult.objects.create(scanner=YARA, version=blocked_version_1)
|
||||
block_1 = Block.objects.create(guid=blocked_addon_1.guid, updated_by=self.user)
|
||||
block_1 = block_factory(guid=blocked_addon_1.guid, updated_by=self.user)
|
||||
block_activity_log_save(block_1, change=False)
|
||||
# result labelled as "bad" because the add-on is blocked and the block
|
||||
# has been edited.
|
||||
blocked_addon_2 = addon_factory()
|
||||
blocked_version_2 = version_factory(addon=blocked_addon_2)
|
||||
ScannerResult.objects.create(scanner=YARA, version=blocked_version_2)
|
||||
block_2 = Block.objects.create(guid=blocked_addon_2.guid, updated_by=self.user)
|
||||
block_2 = block_factory(guid=blocked_addon_2.guid, updated_by=self.user)
|
||||
block_activity_log_save(block_2, change=True)
|
||||
# result labelled as "bad" because the add-on is blocked and the block
|
||||
# has been added *and* edited. It should only return one result.
|
||||
blocked_addon_3 = addon_factory()
|
||||
blocked_version_3 = version_factory(addon=blocked_addon_3)
|
||||
ScannerResult.objects.create(scanner=YARA, version=blocked_version_3)
|
||||
block_3 = Block.objects.create(guid=blocked_addon_3.guid, updated_by=self.user)
|
||||
block_3 = block_factory(guid=blocked_addon_3.guid, updated_by=self.user)
|
||||
block_activity_log_save(block_3, change=False)
|
||||
block_activity_log_save(block_3, change=True)
|
||||
# result labelled as "bad" because its state is TRUE_POSITIVE and the
|
||||
|
@ -255,7 +255,7 @@ class TestScannerResultViewInternal(TestCase):
|
|||
ScannerResult.objects.create(
|
||||
scanner=YARA, version=blocked_version_4, state=TRUE_POSITIVE
|
||||
)
|
||||
block_4 = Block.objects.create(guid=blocked_addon_4.guid, updated_by=self.user)
|
||||
block_4 = block_factory(guid=blocked_addon_4.guid, updated_by=self.user)
|
||||
block_activity_log_save(block_4, change=False)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
@ -267,7 +267,7 @@ class TestScannerResultViewInternal(TestCase):
|
|||
result_1 = ScannerResult.objects.create(scanner=CUSTOMS, version=version_1)
|
||||
ActivityLog.create(amo.LOG.APPROVE_VERSION, version_1, user=self.user)
|
||||
# Oh noes! The version has been blocked.
|
||||
block_1 = Block.objects.create(guid=version_1.addon.guid, updated_by=self.user)
|
||||
block_1 = block_factory(guid=version_1.addon.guid, updated_by=self.user)
|
||||
block_activity_log_save(block_1, change=False)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
|
|
@ -7,7 +7,6 @@ from django.conf import settings
|
|||
from django.forms import ValidationError
|
||||
from django.test.testcases import TransactionTestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils import translation
|
||||
|
||||
import responses
|
||||
|
@ -26,10 +25,8 @@ from olympia.amo.tests import (
|
|||
developer_factory,
|
||||
get_random_ip,
|
||||
reverse_ns,
|
||||
user_factory,
|
||||
)
|
||||
from olympia.api.tests.utils import APIKeyAuthTestMixin
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.files.models import File, FileUpload
|
||||
from olympia.files.utils import get_sha256
|
||||
from olympia.users.models import (
|
||||
|
@ -894,78 +891,6 @@ class TestUploadVersion(BaseUploadVersionTestMixin, TestCase):
|
|||
)
|
||||
assert response.status_code == 202
|
||||
|
||||
def test_version_blocked(self):
|
||||
block = Block.objects.create(
|
||||
guid=self.guid, max_version='3.0', updated_by=user_factory()
|
||||
)
|
||||
response = self.request('PUT', self.url(self.guid, '3.0'))
|
||||
assert response.status_code == 400
|
||||
block_url = absolutify(reverse('blocklist.block', args=(self.guid,)))
|
||||
assert response.data['error'] == (
|
||||
f'Version 3.0 matches {block_url} for this add-on. '
|
||||
'You can contact amo-admins@mozilla.com for additional '
|
||||
'information.'
|
||||
)
|
||||
# it's okay if it's outside of the blocked range though
|
||||
block.update(max_version='2.9')
|
||||
response = self.request('PUT', self.url(self.guid, '3.0'))
|
||||
assert response.status_code == 202
|
||||
|
||||
def test_addon_blocked(self):
|
||||
guid = '@create-webextension'
|
||||
block = Block.objects.create(
|
||||
guid=guid, max_version='3.0', updated_by=user_factory()
|
||||
)
|
||||
qs = Addon.unfiltered.filter(guid=guid)
|
||||
assert not qs.exists()
|
||||
|
||||
# Testing when a new addon guid is specified in the url
|
||||
response = self.request('PUT', guid=guid, version='1.0')
|
||||
assert response.status_code == 400
|
||||
block_url = absolutify(reverse('blocklist.block', args=(guid,)))
|
||||
error_msg = (
|
||||
f'Version 1.0 matches {block_url} for this add-on. '
|
||||
'You can contact amo-admins@mozilla.com for additional '
|
||||
'information.'
|
||||
)
|
||||
assert response.data['error'] == error_msg
|
||||
assert not qs.exists()
|
||||
|
||||
# it's okay if it's outside of the blocked range though
|
||||
block.update(min_version='2.0')
|
||||
response = self.request('PUT', guid=guid, version='1.0')
|
||||
assert response.status_code == 201
|
||||
|
||||
def test_addon_blocked_guid_in_xpi(self):
|
||||
guid = '@webextension-with-guid'
|
||||
block = Block.objects.create(
|
||||
guid=guid, max_version='3.0', updated_by=user_factory()
|
||||
)
|
||||
qs = Addon.unfiltered.filter(guid=guid)
|
||||
assert not qs.exists()
|
||||
filename = self.xpi_filepath('@create-webextension-with-guid', '1.0')
|
||||
url = reverse_ns('signing.version', api_version='v4')
|
||||
|
||||
response = self.request(
|
||||
'POST', guid=guid, version='1.0', filename=filename, url=url
|
||||
)
|
||||
assert response.status_code == 400
|
||||
block_url = absolutify(reverse('blocklist.block', args=(guid,)))
|
||||
error_msg = (
|
||||
f'Version 1.0 matches {block_url} for this add-on. '
|
||||
'You can contact amo-admins@mozilla.com for additional '
|
||||
'information.'
|
||||
)
|
||||
assert response.data['error'] == error_msg
|
||||
assert not qs.exists()
|
||||
|
||||
# it's okay if it's outside of the blocked range though
|
||||
block.update(min_version='2.0')
|
||||
response = self.request(
|
||||
'POST', guid=guid, version='1.0', filename=filename, url=url
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
||||
def test_deleted_webextension(self):
|
||||
guid = '@webextension-with-guid'
|
||||
addon = addon_factory(guid=guid, users=[self.user])
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import functools
|
||||
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext
|
||||
|
||||
from rest_framework import status
|
||||
|
@ -18,10 +17,8 @@ from olympia.addons.utils import (
|
|||
webext_version_stats,
|
||||
)
|
||||
from olympia.amo.decorators import use_primary_db
|
||||
from olympia.amo.templatetags.jinja_helpers import absolutify
|
||||
from olympia.api.authentication import JWTKeyAuthentication
|
||||
from olympia.api.throttling import addon_submission_throttles
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.devhub.permissions import IsSubmissionAllowedFor
|
||||
from olympia.devhub.views import handle_upload as devhub_handle_upload
|
||||
from olympia.files.models import FileUpload
|
||||
|
@ -188,22 +185,6 @@ class VersionView(APIView):
|
|||
status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
block_qs = Block.objects.filter(guid=addon.guid if addon else guid)
|
||||
if block_qs and block_qs.first().is_version_blocked(version_string):
|
||||
msg = gettext(
|
||||
'Version {version} matches {block_link} for this add-on. '
|
||||
'You can contact {amo_admins} for additional information.'
|
||||
)
|
||||
|
||||
raise forms.ValidationError(
|
||||
msg.format(
|
||||
version=version_string,
|
||||
block_link=absolutify(reverse('blocklist.block', args=[guid])),
|
||||
amo_admins='amo-admins@mozilla.com',
|
||||
),
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# channel will be ignored for new addons.
|
||||
if addon is None:
|
||||
channel = amo.CHANNEL_UNLISTED # New is always unlisted.
|
||||
|
|
|
@ -1034,8 +1034,22 @@ class Version(OnChangeMixin, ModelBase):
|
|||
|
||||
@property
|
||||
def is_blocked(self):
|
||||
block = self.addon.block
|
||||
return bool(block and block.is_version_blocked(self.version))
|
||||
return hasattr(self, 'blockversion')
|
||||
|
||||
@cached_property
|
||||
def blocklist_submission_id(self):
|
||||
from olympia.blocklist.models import BlocklistSubmission
|
||||
|
||||
return (
|
||||
submission.id
|
||||
if self.id
|
||||
and (
|
||||
submission := BlocklistSubmission.get_submissions_from_version_id(
|
||||
self.id
|
||||
).last()
|
||||
)
|
||||
else 0
|
||||
)
|
||||
|
||||
@property
|
||||
def pending_rejection(self):
|
||||
|
|
|
@ -25,7 +25,7 @@ from olympia.amo.tests import (
|
|||
from olympia.amo.tests.test_models import BasePreviewMixin
|
||||
from olympia.amo.utils import utc_millesecs_from_epoch
|
||||
from olympia.applications.models import AppVersion
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.blocklist.models import Block, BlockVersion
|
||||
from olympia.constants.promoted import (
|
||||
LINE,
|
||||
NOT_PROMOTED,
|
||||
|
@ -1241,17 +1241,18 @@ class TestVersion(AMOPaths, TestCase):
|
|||
assert not addon.current_version.can_be_disabled_and_deleted()
|
||||
|
||||
def test_is_blocked(self):
|
||||
addon = Addon.objects.get(id=3615)
|
||||
assert addon.current_version.is_blocked is False
|
||||
version = Addon.objects.get(id=3615).current_version
|
||||
assert version.is_blocked is False
|
||||
|
||||
block = Block.objects.create(addon=addon, updated_by=user_factory())
|
||||
assert Addon.objects.get(id=3615).current_version.is_blocked is True
|
||||
block = Block.objects.create(addon=version.addon, updated_by=user_factory())
|
||||
assert version.reload().is_blocked is False
|
||||
|
||||
block.update(min_version='999999999')
|
||||
assert Addon.objects.get(id=3615).current_version.is_blocked is False
|
||||
blockversion = BlockVersion.objects.create(block=block, version=version)
|
||||
assert version.reload().is_blocked is True
|
||||
|
||||
block.update(min_version='0')
|
||||
assert Addon.objects.get(id=3615).current_version.is_blocked is True
|
||||
blockversion.update(version=version_factory(addon=version.addon))
|
||||
version.refresh_from_db()
|
||||
assert version.is_blocked is False
|
||||
|
||||
def test_pending_rejection_property(self):
|
||||
addon = Addon.objects.get(id=3615)
|
||||
|
|
|
@ -13,6 +13,10 @@ form ul.guid_list {
|
|||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
form .guid_list li ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
form ul.guid_list li {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче