create BlockVersion instances for versions in Block.min - max (#20837)
* create BlockVersion instances for versions in Block.min - max * review fixes
This commit is contained in:
Родитель
7565186413
Коммит
998a2b7cdc
|
@ -30,3 +30,4 @@ This endpoint returns an add-on Block from the blocklist, specified by guid or i
|
|||
:>json string max_version: The maximum version of the add-on that will be blocked. "*" is the highest version, meaning all versions from min_version will be blocked. ("0" - "*" would be all versions).
|
||||
:>json string|null reason: Why the add-on needed to be blocked.
|
||||
:>json object|null url: A url to the report/request that detailed why the add-on should potentially be blocked. Typically a bug report on bugzilla.mozilla.org. (See :ref:`Outgoing Links <api-overview-outgoing>`)
|
||||
:>json string versions[]: The versions of this add-on that are blocked.
|
||||
|
|
|
@ -462,6 +462,7 @@ These are `v5` specific changes - `v4` changes apply also.
|
|||
* 2023-04-13: removed signing api from api/v5+ in favor of addon submission api. https://github.com/mozilla/addons-server/issues/20560
|
||||
* 2023-06-01: renamed add-ons search endpoint sort by ratings parameter to ``sort=ratings``, ``sort=rating`` is still supported for backwards-compatibility. https://github.com/mozilla/addons-server/issues/20763
|
||||
* 2023-06-06: added the /addons/browser-mappings/ endpoint. https://github.com/mozilla/addons-server/issues/20798
|
||||
* 2023-06-22: added ``versions`` to blocklist block endpoint. https://github.com/mozilla/addons-server/issues/20748
|
||||
|
||||
.. _`#11380`: https://github.com/mozilla/addons-server/issues/11380/
|
||||
.. _`#11379`: https://github.com/mozilla/addons-server/issues/11379/
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.db.models import Exists, F, OuterRef, Q
|
||||
from django.db.models.functions import Collate
|
||||
|
||||
from olympia import amo
|
||||
from olympia.abuse.models import AbuseReport
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.addons.tasks import (
|
||||
delete_addons,
|
||||
disable_addons,
|
||||
extract_colors_from_static_themes,
|
||||
find_inconsistencies_between_es_and_db,
|
||||
recreate_theme_previews,
|
||||
)
|
||||
from olympia.amo.management import ProcessObjectsCommand
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.constants.base import (
|
||||
_ADDON_LPADDON,
|
||||
_ADDON_PERSONA,
|
||||
|
@ -150,21 +147,6 @@ class Command(ProcessObjectsCommand):
|
|||
'task': addon_rating_aggregates,
|
||||
'queryset_filters': [Q(status=amo.STATUS_APPROVED)],
|
||||
},
|
||||
'disable_blocked_addons': {
|
||||
'task': disable_addons,
|
||||
'queryset_filters': [
|
||||
Q(
|
||||
status__in=(
|
||||
amo.STATUS_NULL,
|
||||
amo.STATUS_NOMINATED,
|
||||
amo.STATUS_APPROVED,
|
||||
),
|
||||
guid__in=Block.objects.filter(
|
||||
min_version=Block.MIN, max_version=Block.MAX
|
||||
).values(guidc=Collate('guid', 'utf8mb4_unicode_ci')),
|
||||
)
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
def add_arguments(self, parser):
|
||||
|
|
|
@ -24,11 +24,9 @@ from olympia.amo.tests import (
|
|||
version_factory,
|
||||
)
|
||||
from olympia.applications.models import AppVersion
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.files.models import FileValidation
|
||||
from olympia.ratings.models import Rating, RatingAggregate
|
||||
from olympia.reviewers.models import AutoApprovalSummary
|
||||
from olympia.users.models import UserProfile
|
||||
from olympia.versions.models import ApplicationsVersions, Version, VersionPreview
|
||||
|
||||
|
||||
|
@ -578,35 +576,3 @@ def test_delete_list_theme_previews():
|
|||
assert VersionPreview.objects.filter(id=other_firefox_preview.id).exists()
|
||||
assert VersionPreview.objects.filter(id=other_amo_preview.id).exists()
|
||||
assert not VersionPreview.objects.filter(id=other_old_list_preview.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_disable_blocked_addons():
|
||||
UserProfile.objects.create(pk=settings.TASK_USER_ID)
|
||||
user = user_factory()
|
||||
incomplete = addon_factory(status=amo.STATUS_NULL)
|
||||
Block.objects.create(guid=incomplete.guid, updated_by=user)
|
||||
nominated = addon_factory(status=amo.STATUS_NOMINATED)
|
||||
Block.objects.create(guid=nominated.guid, updated_by=user)
|
||||
approved = addon_factory(status=amo.STATUS_APPROVED)
|
||||
Block.objects.create(guid=approved.guid, updated_by=user)
|
||||
deleted = addon_factory(status=amo.STATUS_DELETED)
|
||||
Block.objects.create(guid=deleted.guid, updated_by=user)
|
||||
only_partially_blocked = addon_factory(status=amo.STATUS_APPROVED)
|
||||
Block.objects.create(
|
||||
guid=only_partially_blocked.guid, min_version='1', updated_by=user
|
||||
)
|
||||
addon_factory() # just a random addon
|
||||
assert Addon.unfiltered.filter(status=amo.STATUS_DISABLED).count() == 0
|
||||
|
||||
call_command('process_addons', task='disable_blocked_addons')
|
||||
|
||||
# these should have changed
|
||||
assert incomplete.reload().status == amo.STATUS_DISABLED
|
||||
assert nominated.reload().status == amo.STATUS_DISABLED
|
||||
assert approved.reload().status == amo.STATUS_DISABLED
|
||||
# these shouldn't have been changed
|
||||
assert deleted.reload().status == amo.STATUS_DELETED
|
||||
assert only_partially_blocked.reload().status == amo.STATUS_APPROVED
|
||||
|
||||
assert Addon.unfiltered.filter(status=amo.STATUS_DISABLED).count() == 3
|
||||
|
|
|
@ -139,12 +139,12 @@ 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()
|
||||
for key in ('min', 'max'):
|
||||
if key in get_params:
|
||||
version = get_object_or_404(
|
||||
Version.unfiltered, pk=get_params.pop(key)[0]
|
||||
)
|
||||
get_params[f'{key}_version'] = version.version
|
||||
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)
|
||||
|
||||
if 'min_version' in get_params or 'max_version' in get_params:
|
||||
warning_message = (
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
import olympia.core.logger
|
||||
from olympia.amo.utils import chunked
|
||||
from olympia.blocklist.models import BlocklistSubmission
|
||||
from olympia.blocklist.utils import save_guids_to_blocks, splitlines
|
||||
from olympia.users.utils import get_task_user
|
||||
|
||||
|
||||
log = olympia.core.logger.getLogger('z.amo.blocklist')
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Create Blocks for provided guids to add them to the v3 blocklist'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Handle command arguments."""
|
||||
parser.add_argument('--min_version')
|
||||
parser.add_argument('--max_version')
|
||||
parser.add_argument('--reason')
|
||||
parser.add_argument('--url'),
|
||||
parser.add_argument(
|
||||
'--guids-input',
|
||||
help='Path to file with one guid per line that should be blocked',
|
||||
required=True,
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with open(options.get('guids_input')) as guid_file:
|
||||
input_guids = guid_file.read()
|
||||
guids = splitlines(input_guids)
|
||||
|
||||
block_args = {
|
||||
prop: options.get(prop)
|
||||
for prop in ('min_version', 'max_version', 'reason', 'url')
|
||||
if options.get(prop)
|
||||
}
|
||||
block_args['updated_by'] = get_task_user()
|
||||
submission = BlocklistSubmission(**block_args)
|
||||
|
||||
for guids_chunk in chunked(guids, 100):
|
||||
blocks = save_guids_to_blocks(
|
||||
guids_chunk, submission, fields_to_set=block_args.keys()
|
||||
)
|
||||
print(f'Added/Updated {len(blocks)} blocks from {len(guids_chunk)} guids')
|
|
@ -0,0 +1,32 @@
|
|||
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)
|
|
@ -0,0 +1,70 @@
|
|||
# Generated by Django 4.2.1 on 2023-06-16 09:01
|
||||
|
||||
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', '0029_alter_blocklistsubmission_delayed_until_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='blocklistsubmission',
|
||||
name='signoff_state',
|
||||
field=models.SmallIntegerField(
|
||||
choices=[
|
||||
(0, 'Pending Sign-off'),
|
||||
(1, 'Approved'),
|
||||
(2, 'Rejected'),
|
||||
(3, 'Auto Sign-off'),
|
||||
(4, 'Published'),
|
||||
],
|
||||
default=0,
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BlockVersion',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'created',
|
||||
models.DateTimeField(
|
||||
blank=True, default=django.utils.timezone.now, editable=False
|
||||
),
|
||||
),
|
||||
('modified', models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
'block',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='blocklist.block',
|
||||
),
|
||||
),
|
||||
(
|
||||
'version',
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='versions.version',
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'get_latest_by': 'created',
|
||||
'abstract': False,
|
||||
'base_manager_name': 'objects',
|
||||
},
|
||||
bases=(olympia.amo.models.SaveUpdateMixin, models.Model),
|
||||
),
|
||||
]
|
|
@ -100,6 +100,7 @@ class Block(ModelBase):
|
|||
.order_by('id')
|
||||
.no_transforms()
|
||||
.annotate(**{GUID: models.F(GUID)})
|
||||
.select_related('blockversion')
|
||||
)
|
||||
|
||||
all_addon_versions = defaultdict(list)
|
||||
|
@ -185,6 +186,14 @@ class Block(ModelBase):
|
|||
return list(existing_blocks.values())
|
||||
|
||||
|
||||
class BlockVersion(ModelBase):
|
||||
version = models.OneToOneField(Version, on_delete=models.CASCADE)
|
||||
block = models.ForeignKey(Block, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'Block.id={self.block_id} -> Version.id={self.version_id}'
|
||||
|
||||
|
||||
class BlocklistSubmissionQuerySet(BaseQuerySet):
|
||||
def delayed(self):
|
||||
return self.filter(delayed_until__gt=datetime.now())
|
||||
|
@ -208,7 +217,7 @@ class BlocklistSubmission(ModelBase):
|
|||
SIGNOFF_APPROVED: 'Approved',
|
||||
SIGNOFF_REJECTED: 'Rejected',
|
||||
SIGNOFF_AUTOAPPROVED: 'Auto Sign-off',
|
||||
SIGNOFF_PUBLISHED: 'Published to Blocks',
|
||||
SIGNOFF_PUBLISHED: 'Published',
|
||||
}
|
||||
SIGNOFF_STATES_APPROVED = (
|
||||
SIGNOFF_APPROVED,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from rest_framework import fields
|
||||
|
||||
from olympia.api.fields import OutgoingURLField, TranslationSerializerField
|
||||
from olympia.api.serializers import AMOModelSerializer
|
||||
|
||||
|
@ -7,6 +9,7 @@ from .models import Block
|
|||
class BlockSerializer(AMOModelSerializer):
|
||||
addon_name = TranslationSerializerField(source='addon.name')
|
||||
url = OutgoingURLField()
|
||||
versions = fields.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Block
|
||||
|
@ -20,4 +23,12 @@ class BlockSerializer(AMOModelSerializer):
|
|||
'max_version',
|
||||
'reason',
|
||||
'url',
|
||||
'versions',
|
||||
)
|
||||
|
||||
def get_versions(self, obj):
|
||||
return list(
|
||||
obj.blockversion_set.order_by('version__version').values_list(
|
||||
'version__version', flat=True
|
||||
)
|
||||
)
|
||||
|
|
|
@ -139,7 +139,7 @@ class TestBlockAdmin(TestCase):
|
|||
self.grant_permission(user, 'Blocklist:Create')
|
||||
self.client.force_login(user)
|
||||
|
||||
addon = addon_factory()
|
||||
addon = addon_factory(version_kw={'version': '123.456'})
|
||||
url = reverse('admin:blocklist_block_addaddon', args=(addon.id,))
|
||||
response = self.client.post(url, follow=True)
|
||||
self.assertRedirects(response, self.submission_url + f'?guids={addon.guid}')
|
||||
|
@ -154,37 +154,60 @@ class TestBlockAdmin(TestCase):
|
|||
|
||||
# GET params are passed along
|
||||
version = addon.current_version
|
||||
second_version = version_factory(
|
||||
addon=addon, channel=amo.CHANNEL_UNLISTED, version='100.000.000'
|
||||
)
|
||||
response = self.client.post(
|
||||
url + f'?min_version={version.version}', follow=True
|
||||
url + f'?min_version={version.version}&', follow=True
|
||||
)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
self.submission_url + f'?guids={addon.guid}&min_version={version.version}',
|
||||
)
|
||||
|
||||
# And version ids as short params are expanded and passed along
|
||||
response = self.client.post(url + f'?max={version.pk}', follow=True)
|
||||
# And version ids are expanded and passed along
|
||||
response = self.client.post(
|
||||
url + f'?v={version.pk}&v={second_version.pk}', follow=True
|
||||
)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
self.submission_url + f'?guids={addon.guid}&max_version={version.version}',
|
||||
self.submission_url
|
||||
+ f'?guids={addon.guid}'
|
||||
+ f'&min_version={second_version.version}&max_version={version.version}',
|
||||
)
|
||||
assert not response.context['messages']
|
||||
|
||||
# The order of the params doesn't determine max and min
|
||||
response = self.client.post(
|
||||
url + f'?v={second_version.pk}&v={version.pk}', follow=True
|
||||
)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
self.submission_url
|
||||
+ f'?guids={addon.guid}'
|
||||
+ f'&min_version={second_version.version}&max_version={version.version}',
|
||||
)
|
||||
assert not response.context['messages']
|
||||
|
||||
# Existing blocks are redirected to the change view instead
|
||||
block = Block.objects.create(addon=addon, updated_by=user_factory())
|
||||
response = self.client.post(url + f'?max={version.pk}', follow=True)
|
||||
response = self.client.post(
|
||||
url + f'?v={version.pk}&v={second_version.pk}', follow=True
|
||||
)
|
||||
self.assertRedirects(
|
||||
response, reverse('admin:blocklist_block_change', args=(block.pk,))
|
||||
)
|
||||
# with a message warning the versions were ignored
|
||||
assert [msg.message for msg in response.context['messages']] == [
|
||||
f'The versions 0 to {version.version} could not be pre-selected '
|
||||
'because some versions have been blocked already'
|
||||
f'The versions {second_version.version} to {version.version} could not be '
|
||||
'pre-selected because some versions have been blocked already'
|
||||
]
|
||||
|
||||
# Pending blocksubmissions are redirected to the submission view
|
||||
submission = BlocklistSubmission.objects.create(input_guids=addon.guid)
|
||||
response = self.client.post(url + f'?max={version.pk}', follow=True)
|
||||
response = self.client.post(
|
||||
url + f'?v={version.pk}&v={second_version.pk}', follow=True
|
||||
)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
reverse(
|
||||
|
@ -193,8 +216,8 @@ class TestBlockAdmin(TestCase):
|
|||
)
|
||||
# with a message warning the versions were ignored
|
||||
assert [msg.message for msg in response.context['messages']] == [
|
||||
f'The versions 0 to {version.version} could not be pre-selected '
|
||||
'because this addon is part of a pending submission'
|
||||
f'The versions {second_version.version} to {version.version} could not be '
|
||||
'pre-selected because this addon is part of a pending submission'
|
||||
]
|
||||
|
||||
def test_guid_redirects(self):
|
||||
|
@ -1626,7 +1649,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
('Approved', '?signoff_state=1'),
|
||||
('Rejected', '?signoff_state=2'),
|
||||
('Auto Sign-off', '?signoff_state=3'),
|
||||
('Published to Blocks', '?signoff_state=4'),
|
||||
('Published', '?signoff_state=4'),
|
||||
]
|
||||
filters = [(x.text, x.attrib['href']) for x in doc('#changelist-filter a')]
|
||||
assert filters == expected_filters
|
||||
|
@ -1660,7 +1683,7 @@ class TestBlocklistSubmissionAdmin(TestCase):
|
|||
assert doc('#result_list tbody tr').length == 3
|
||||
assert doc('#changelist-filter li.selected a').text() == 'All'
|
||||
assert doc('#changelist-form td.field-state').text() == (
|
||||
'Published to Blocks Approved:Delayed Pending Sign-off'
|
||||
'Published Approved:Delayed Pending Sign-off'
|
||||
)
|
||||
|
||||
def test_blocked_deleted_keeps_addon_status(self):
|
||||
|
|
|
@ -1,36 +1,28 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory
|
||||
from olympia.amo.tests import TestCase, addon_factory, user_factory, version_factory
|
||||
|
||||
from ..models import Block
|
||||
|
||||
|
||||
# This is a fragment of the actual json blocklist file
|
||||
TESTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
blocklist_file = os.path.join(TESTS_DIR, 'blocklists', 'blocklist.json')
|
||||
with open(blocklist_file) as file_object:
|
||||
blocklist_json = json.load(file_object)
|
||||
from ..models import Block, BlockVersion
|
||||
|
||||
|
||||
class TestExportBlocklist(TestCase):
|
||||
def test_command(self):
|
||||
for idx in range(0, 5):
|
||||
user = user_factory()
|
||||
for _idx in range(0, 5):
|
||||
addon_factory()
|
||||
# one version, 0 - *
|
||||
Block.objects.create(
|
||||
addon=addon_factory(file_kw={'is_signed': True}),
|
||||
updated_by=user_factory(),
|
||||
updated_by=user,
|
||||
)
|
||||
# one version, 0 - 9999
|
||||
Block.objects.create(
|
||||
addon=addon_factory(file_kw={'is_signed': True}),
|
||||
updated_by=user_factory(),
|
||||
updated_by=user,
|
||||
max_version='9999',
|
||||
)
|
||||
# one version, 0 - *, unlisted
|
||||
|
@ -39,7 +31,7 @@ class TestExportBlocklist(TestCase):
|
|||
version_kw={'channel': amo.CHANNEL_UNLISTED},
|
||||
file_kw={'is_signed': True},
|
||||
),
|
||||
updated_by=user_factory(),
|
||||
updated_by=user,
|
||||
)
|
||||
|
||||
call_command('export_blocklist', '1')
|
||||
|
@ -47,34 +39,43 @@ class TestExportBlocklist(TestCase):
|
|||
assert os.path.exists(out_path)
|
||||
|
||||
|
||||
class TestBulkAddBlocks(TestCase):
|
||||
class TestCreateBlockversions(TestCase):
|
||||
def test_command(self):
|
||||
user_factory(id=settings.TASK_USER_ID)
|
||||
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)
|
||||
|
||||
with self.assertRaises(CommandError):
|
||||
# no input guids
|
||||
call_command('bulk_add_blocks')
|
||||
assert Block.objects.count() == 0
|
||||
call_command('create_blockversions')
|
||||
|
||||
guids_path = os.path.join(TESTS_DIR, 'blocklists', 'input_guids.txt')
|
||||
call_command('bulk_add_blocks', guids_input=guids_path)
|
||||
# no guids matching addons
|
||||
assert Block.objects.count() == 0
|
||||
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
|
||||
|
||||
addon_factory(guid='{0020bd71-b1ba-4295-86af-db7f4e7eaedc}')
|
||||
addon_factory(guid='{another-random-guid')
|
||||
call_command('bulk_add_blocks', guids_input=guids_path)
|
||||
# this time we have 1 matching guid so one Block created
|
||||
assert Block.objects.count() == 1
|
||||
assert Block.objects.last().guid == ('{0020bd71-b1ba-4295-86af-db7f4e7eaedc}')
|
||||
# we can run it again without a problem
|
||||
call_command('create_blockversions')
|
||||
assert BlockVersion.objects.count() == 3
|
||||
|
||||
addon_factory(guid='{00116dc4-ba1f-42c5-b20c-da7f743f7377}')
|
||||
call_command('bulk_add_blocks', guids_input=guids_path, min_version='44')
|
||||
# The guid before shouldn't be added twice
|
||||
# assert Block.objects.count() == 2
|
||||
prev_block = Block.objects.first()
|
||||
assert prev_block.guid == '{0020bd71-b1ba-4295-86af-db7f4e7eaedc}'
|
||||
assert prev_block.min_version == '44'
|
||||
new_block = Block.objects.last()
|
||||
assert new_block.guid == '{00116dc4-ba1f-42c5-b20c-da7f743f7377}'
|
||||
assert new_block.min_version == '44'
|
||||
# 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
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from olympia.amo.tests import TestCase, addon_factory, user_factory
|
||||
from olympia.amo.urlresolvers import get_outgoing_url
|
||||
from olympia.blocklist.models import Block
|
||||
from olympia.blocklist.serializers import BlockSerializer
|
||||
|
||||
from ..models import Block, BlockVersion
|
||||
from ..serializers import BlockSerializer
|
||||
|
||||
|
||||
class TestBlockSerializer(TestCase):
|
||||
|
@ -27,11 +28,16 @@ class TestBlockSerializer(TestCase):
|
|||
'url': 'https://goo.gol',
|
||||
'outgoing': get_outgoing_url('https://goo.gol'),
|
||||
},
|
||||
'versions': [],
|
||||
'created': self.block.created.isoformat()[:-7] + 'Z',
|
||||
'modified': self.block.modified.isoformat()[:-7] + 'Z',
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
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]
|
||||
|
|
|
@ -143,7 +143,7 @@ def disable_addon_for_block(block):
|
|||
|
||||
|
||||
def save_guids_to_blocks(guids, submission, *, fields_to_set):
|
||||
from .models import Block
|
||||
from .models import Block, BlockVersion
|
||||
|
||||
common_args = {field: getattr(submission, field) for field in fields_to_set}
|
||||
modified_datetime = datetime.now()
|
||||
|
@ -163,6 +163,21 @@ def save_guids_to_blocks(guids, submission, *, fields_to_set):
|
|||
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)
|
||||
)
|
||||
|
||||
if submission.id:
|
||||
block.submission.add(submission)
|
||||
block_activity_log_save(
|
||||
|
|
|
@ -2756,7 +2756,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
|
||||
# We should have set redirect_url to point to the Block admin page
|
||||
if '%s' in redirect_url:
|
||||
redirect_url = redirect_url % (old_version.pk, self.review_version.pk)
|
||||
redirect_url = redirect_url % (self.review_version.pk, old_version.pk)
|
||||
assert self.helper.redirect_url == redirect_url
|
||||
|
||||
def test_pending_blocklistsubmission_multiple_unlisted_versions(self):
|
||||
|
@ -2765,7 +2765,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
)
|
||||
redirect_url = (
|
||||
reverse('admin:blocklist_block_addaddon', args=(self.addon.id,))
|
||||
+ '?min=%s&max=%s'
|
||||
+ '?v=%s&v=%s'
|
||||
)
|
||||
assert Block.objects.count() == 0
|
||||
self._test_block_multiple_unlisted_versions(redirect_url)
|
||||
|
@ -2773,7 +2773,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
def test_new_block_multiple_unlisted_versions(self):
|
||||
redirect_url = (
|
||||
reverse('admin:blocklist_block_addaddon', args=(self.addon.id,))
|
||||
+ '?min=%s&max=%s'
|
||||
+ '?v=%s&v=%s'
|
||||
)
|
||||
assert Block.objects.count() == 0
|
||||
self._test_block_multiple_unlisted_versions(redirect_url)
|
||||
|
@ -2782,7 +2782,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
|||
Block.objects.create(guid=self.addon.guid, updated_by=user_factory())
|
||||
redirect_url = (
|
||||
reverse('admin:blocklist_block_addaddon', args=(self.addon.id,))
|
||||
+ '?min=%s&max=%s'
|
||||
+ '?v=%s&v=%s'
|
||||
)
|
||||
self._test_block_multiple_unlisted_versions(redirect_url)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.db.models import Count, F, Q
|
|||
from django.template import loader
|
||||
from django.urls import reverse
|
||||
from django.utils import translation
|
||||
from django.utils.http import urlencode
|
||||
|
||||
import django_tables2 as tables
|
||||
import markupsafe
|
||||
|
@ -1452,16 +1453,8 @@ class ReviewUnlisted(ReviewBase):
|
|||
log.info('Sending email for %s' % (self.addon))
|
||||
|
||||
def block_multiple_versions(self):
|
||||
min_version = ('0', None)
|
||||
max_version = ('*', None)
|
||||
for version in self.data['versions']:
|
||||
version_str = version.version
|
||||
if not min_version[1] or version_str < min_version[1]:
|
||||
min_version = (version, version_str)
|
||||
if not max_version[1] or version_str > max_version[1]:
|
||||
max_version = (version, version_str)
|
||||
|
||||
params = f'?min={min_version[0].pk}&max={max_version[0].pk}'
|
||||
versions = self.data['versions']
|
||||
params = '?' + urlencode((('v', v.id) for v in versions), doseq=True)
|
||||
self.redirect_url = (
|
||||
reverse('admin:blocklist_block_addaddon', args=(self.addon.pk,)) + params
|
||||
)
|
||||
|
|
Загрузка…
Ссылка в новой задаче