Remove CompatOverride models and api (#12086)

This commit is contained in:
Andrew Williamson 2019-08-12 12:36:07 +01:00 коммит произвёл GitHub
Родитель 2963e41224
Коммит 0e7152107c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 38 добавлений и 974 удалений

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

@ -485,38 +485,6 @@ This endpoint returns a list of suggested replacements for legacy add-ons that a
:>json string results[].replacement[]: An array of guids for the replacements add-ons. If there is a direct replacement this will be a list of one add-on guid. The list can be empty if all the replacement add-ons are invalid (e.g. not publicly available on AMO). The list will also be empty if the replacement is to a url that is not an addon or collection.
---------------
Compat Override
---------------
.. _addon-compat-override:
This endpoint allows compatibility overrides specified by AMO admins to be searched.
Compatibilty overrides are used within Firefox i(and other toolkit applications e.g. Thunderbird) to change compatibility of installed add-ons where they have stopped working correctly in new release of Firefox, etc.
.. http:get:: /api/v4/addons/compat-override/
:query string guid: Filter by exact add-on guid. Multiple guids can be specified, separated by comma(s), in which case any add-ons matching any of the guids will be returned. As guids are unique there should be at most one add-on result per guid specified.
:query int page: 1-based page number. Defaults to 1.
:query int page_size: Maximum number of results to return for the requested page. Defaults to 25.
:>json int count: The number of results for this query.
:>json string next: The URL of the next page of results.
:>json string previous: The URL of the previous page of results.
:>json array results: An array of compat overrides.
:>json int|null results[].addon_id: The add-on identifier on AMO, if specified.
:>json string results[].addon_guid: The add-on extension identifier.
:>json string results[].name: A description entered by AMO admins to describe the override.
:>json array results[].version_ranges: An array of affected versions of the add-on.
:>json string results[].version_ranges[].addon_min_version: minimum version of the add-on to be disabled.
:>json string results[].version_ranges[].addon_max_version: maximum version of the add-on to be disabled.
:>json array results[].version_ranges[].applications: An array of affected applications for this range of versions.
:>json string results[].version_ranges[].applications[].name: Application name (e.g. Firefox).
:>json int results[].version_ranges[].applications[].id: Application id on AMO.
:>json string results[].version_ranges[].applications[].min_version: minimum version of the application to be disabled in.
:>json string results[].version_ranges[].applications[].max_version: maximum version of the application to be disabled in.
:>json string results[].version_ranges[].applications[].guid: Application `guid <https://addons.mozilla.org/en-US/firefox/pages/appversions/>`_.
---------------
Recommendations
---------------

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

@ -336,6 +336,8 @@ v4 API changelog
* 2019-08-08: add secondary shelf to /hero/ endpoint. https://github.com/mozilla/addons-server/issues/11779
* 2019-08-15: dropped support for LWT specific statuses.
* 2019-08-15: added promo modules to secondary hero shelves. https://github.com/mozilla/addons-server/issues/11780
* 2019-08-15: removed /addons/compat-override/ from v4 and above. Still exists in /v3/ but will always return an empty response. https://github.com/mozilla/addons-server/issues/12063
----------------
v5 API changelog

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

@ -151,18 +151,6 @@ class Update(object):
data['d2c_min_version'] = version_int(d2c_min)
sql.append("AND appmax.version_int >= %(d2c_min_version)s ")
# Filter out versions found in compat overrides
sql.append("""AND
NOT versions.id IN (
SELECT version_id FROM incompatible_versions
WHERE app_id=%(app_id)s AND
(min_app_version='0' AND
max_app_version_int >= %(version_int)s) OR
(min_app_version_int <= %(version_int)s AND
max_app_version='*') OR
(min_app_version_int <= %(version_int)s AND
max_app_version_int >= %(version_int)s)) """)
else: # Not defined or 'strict'.
sql.append('AND appmax.version_int >= %(version_int)s ')

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

@ -123,26 +123,6 @@ class FrozenAddonAdmin(admin.ModelAdmin):
raw_id_fields = ('addon',)
class CompatOverrideRangeInline(admin.TabularInline):
model = models.CompatOverrideRange
# Exclude type since firefox only supports blocking right now.
exclude = ('type',)
class CompatOverrideAdminForm(forms.ModelForm):
def clean(self):
if '_confirm' in self.data:
raise forms.ValidationError('Click "Save" to confirm changes.')
return self.cleaned_data
class CompatOverrideAdmin(admin.ModelAdmin):
raw_id_fields = ('addon',)
inlines = [CompatOverrideRangeInline]
form = CompatOverrideAdminForm
class ReplacementAddonForm(forms.ModelForm):
def clean_path(self):
path = None
@ -205,5 +185,4 @@ class ReplacementAddonAdmin(admin.ModelAdmin):
admin.site.register(models.DeniedGuid)
admin.site.register(models.Addon, AddonAdmin)
admin.site.register(models.FrozenAddon, FrozenAddonAdmin)
admin.site.register(models.CompatOverride, CompatOverrideAdmin)
admin.site.register(models.ReplacementAddon, ReplacementAddonAdmin)

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

@ -21,7 +21,7 @@ sub_versions = NestedSimpleRouter(sub_addons, r'versions', lookup='version')
sub_versions.register(r'reviewnotes', VersionReviewNotesViewSet,
basename='version-reviewnotes')
urlpatterns = [
urls = [
url(r'', include(addons.urls)),
url(r'', include(sub_addons.urls)),
url(r'', include(sub_versions.urls)),
@ -34,8 +34,13 @@ urlpatterns = [
name='addon-language-tools'),
url(r'^replacement-addon/$', ReplacementAddonView.as_view(),
name='addon-replacement-addon'),
url(r'^compat-override/$', CompatOverrideView.as_view(),
name='addon-compat-override'),
url(r'^recommendations/$', AddonRecommendationView.as_view(),
name='addon-recommendations'),
]
addons_v3 = urls + [
url(r'^compat-override/$', CompatOverrideView.as_view(),
name='addon-compat-override')]
addons_v4 = urls

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

@ -37,7 +37,7 @@ from olympia.amo.models import (
from olympia.amo.templatetags import jinja_helpers
from olympia.amo.urlresolvers import reverse
from olympia.amo.utils import (
StopWatch, attach_trans_dict, chunked,
StopWatch, attach_trans_dict,
find_language, send_mail, slugify, sorted_groupby, timer, to_language)
from olympia.constants.categories import CATEGORIES, CATEGORIES_BY_ID
from olympia.constants.reviewers import REPUTATION_CHOICES
@ -1817,154 +1817,6 @@ def freezer(sender, instance, **kw):
Addon.objects.get(id=instance.addon_id).update(hotness=0)
class CompatOverride(ModelBase):
"""Helps manage compat info for add-ons not hosted on AMO."""
id = PositiveAutoField(primary_key=True)
name = models.CharField(max_length=255, blank=True, null=True)
guid = models.CharField(max_length=255, unique=True)
addon = models.ForeignKey(
Addon, blank=True, null=True, on_delete=models.CASCADE,
help_text='Fill this out to link an override to a hosted add-on')
class Meta:
db_table = 'compat_override'
unique_together = ('addon', 'guid')
def save(self, *args, **kw):
if not self.addon:
qs = Addon.objects.filter(guid=self.guid)
if qs:
self.addon = qs[0]
return super(CompatOverride, self).save(*args, **kw)
def __str__(self):
if self.addon:
return str(self.addon)
elif self.name:
return '%s (%s)' % (self.name, self.guid)
else:
return self.guid
@staticmethod
def transformer(overrides):
if not overrides:
return
id_map = {override.id: override for override in overrides}
qs = CompatOverrideRange.objects.filter(compat__in=id_map)
for compat_id, ranges in sorted_groupby(qs, 'compat_id'):
id_map[compat_id].compat_ranges = list(ranges)
# May be filled in by a transformer for performance.
@cached_property
def compat_ranges(self):
return list(self._compat_ranges.all())
def collapsed_ranges(self):
"""Collapse identical version ranges into one entity."""
Range = collections.namedtuple('Range', 'type min max apps')
AppRange = collections.namedtuple('AppRange', 'app min max')
rv = []
def sort_key(x):
return (x.min_version, x.max_version, x.type)
for key, compats in sorted_groupby(self.compat_ranges, key=sort_key):
compats = list(compats)
first = compats[0]
item = Range(first.override_type(), first.min_version,
first.max_version, [])
for compat in compats:
app = AppRange(amo.APPS_ALL[compat.app],
compat.min_app_version, compat.max_app_version)
item.apps.append(app)
rv.append(item)
return rv
OVERRIDE_TYPES = (
(0, 'Compatible (not supported)'),
(1, 'Incompatible'),
)
class CompatOverrideRange(ModelBase):
"""App compatibility for a certain version range of a RemoteAddon."""
id = PositiveAutoField(primary_key=True)
compat = models.ForeignKey(
CompatOverride, related_name='_compat_ranges',
on_delete=models.CASCADE)
type = models.SmallIntegerField(choices=OVERRIDE_TYPES, default=1)
min_version = models.CharField(
max_length=255, default='0',
help_text=u'If not "0", version is required to exist for the override'
u' to take effect.')
max_version = models.CharField(
max_length=255, default='*',
help_text=u'If not "*", version is required to exist for the override'
u' to take effect.')
app = models.PositiveIntegerField(choices=amo.APPS_CHOICES,
db_column='app_id')
min_app_version = models.CharField(max_length=255, default='0')
max_app_version = models.CharField(max_length=255, default='*')
class Meta:
db_table = 'compat_override_range'
def override_type(self):
"""This is what Firefox wants to see in the XML output."""
return {0: 'compatible', 1: 'incompatible'}[self.type]
class IncompatibleVersions(ModelBase):
"""
Denormalized table to join against for fast compat override filtering.
This was created to be able to join against a specific version record since
the CompatOverrideRange can be wildcarded (e.g. 0 to *, or 1.0 to 1.*), and
addon versioning isn't as consistent as Firefox versioning to trust
`version_int` in all cases. So extra logic needed to be provided for when
a particular version falls within the range of a compatibility override.
"""
id = PositiveAutoField(primary_key=True)
version = models.ForeignKey(
Version, related_name='+', on_delete=models.CASCADE)
app = models.PositiveIntegerField(choices=amo.APPS_CHOICES,
db_column='app_id')
min_app_version = models.CharField(max_length=255, blank=True, default='0')
max_app_version = models.CharField(max_length=255, blank=True, default='*')
min_app_version_int = models.BigIntegerField(blank=True, null=True,
editable=False, db_index=True)
max_app_version_int = models.BigIntegerField(blank=True, null=True,
editable=False, db_index=True)
class Meta:
db_table = 'incompatible_versions'
def __str__(self):
return u'<IncompatibleVersion V:%s A:%s %s-%s>' % (
self.version.id, self.app.id, self.min_app_version,
self.max_app_version)
def save(self, *args, **kw):
self.min_app_version_int = version_int(self.min_app_version)
self.max_app_version_int = version_int(self.max_app_version)
return super(IncompatibleVersions, self).save(*args, **kw)
def update_incompatible_versions(sender, instance, **kw):
if not instance.compat.addon_id:
return
if not instance.compat.addon.type == amo.ADDON_EXTENSION:
return
from . import tasks
versions = instance.compat.addon.versions.values_list('id', flat=True)
for chunk in chunked(versions, 50):
tasks.update_incompatible_appversions.delay(chunk)
class ReplacementAddon(ModelBase):
guid = models.CharField(max_length=255, unique=True, null=True)
path = models.CharField(max_length=255, null=True,
@ -1982,14 +1834,6 @@ class ReplacementAddon(ModelBase):
return self.path_is_external(self.path)
models.signals.post_save.connect(update_incompatible_versions,
sender=CompatOverrideRange,
dispatch_uid='cor_update_incompatible')
models.signals.post_delete.connect(update_incompatible_versions,
sender=CompatOverrideRange,
dispatch_uid='cor_update_incompatible')
def track_new_status(sender, instance, *args, **kw):
if kw.get('raw'):
# The addon is being loaded from a fixure.

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

@ -24,8 +24,7 @@ from olympia.users.models import UserProfile
from olympia.versions.models import (
ApplicationsVersions, License, Version, VersionPreview)
from .models import (
Addon, CompatOverride, Preview, ReplacementAddon, attach_tags)
from .models import Addon, Preview, ReplacementAddon, attach_tags
class FileSerializer(serializers.ModelSerializer):
@ -748,30 +747,3 @@ class ReplacementAddonSerializer(serializers.ModelSerializer):
return self._get_collection_guids(
coll_match.group('user_id'), coll_match.group('coll_slug'))
return []
class CompatOverrideSerializer(serializers.ModelSerializer):
class VersionRangeSerializer(serializers.Serializer):
class ApplicationSerializer(serializers.Serializer):
name = serializers.CharField(source='app.pretty')
id = serializers.IntegerField(source='app.id')
min_version = serializers.CharField(source='min')
max_version = serializers.CharField(source='max')
guid = serializers.CharField(source='app.guid')
addon_min_version = serializers.CharField(source='min')
addon_max_version = serializers.CharField(source='max')
applications = ApplicationSerializer(source='apps', many=True)
addon_id = serializers.IntegerField()
addon_guid = serializers.CharField(source='guid')
version_ranges = VersionRangeSerializer(
source='collapsed_ranges', many=True)
class Meta:
model = CompatOverride
fields = ('addon_id', 'addon_guid', 'name', 'version_ranges')
def get_addon_id(self, obj):
return obj.addon_id

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

@ -15,8 +15,7 @@ import olympia.core
from olympia import amo
from olympia.addons.indexers import AddonIndexer
from olympia.addons.models import (
Addon, AddonApprovalsCounter, AppSupport,
CompatOverride, IncompatibleVersions, MigratedLWT, Preview,
Addon, AddonApprovalsCounter, AppSupport, MigratedLWT, Preview,
attach_tags, attach_translations)
from olympia.amo.celery import pause_all_tasks, resume_all_tasks, task
from olympia.amo.decorators import use_primary_db
@ -28,7 +27,7 @@ from olympia.lib.es.utils import index_objects
from olympia.tags.models import Tag
from olympia.users.models import UserProfile
from olympia.versions.models import (
generate_static_theme_preview, Version, VersionPreview)
generate_static_theme_preview, VersionPreview)
from olympia.versions.utils import (
new_69_theme_properties_from_old, new_theme_version_with_69_properties)
@ -156,86 +155,6 @@ def unindex_addons(ids, **kw):
Addon.unindex(addon)
@task
def update_incompatible_appversions(data, **kw):
"""Updates the incompatible_versions table for this version."""
log.info('Updating incompatible_versions for %s versions.' % len(data))
addon_ids = set()
for version_id in data:
# This is here to handle both post_save and post_delete hooks.
IncompatibleVersions.objects.filter(version=version_id).delete()
try:
version = Version.objects.get(pk=version_id)
except Version.DoesNotExist:
log.info('Version ID [%d] not found. Incompatible versions were '
'cleared.' % version_id)
return
addon_ids.add(version.addon_id)
try:
compat = CompatOverride.objects.get(addon=version.addon)
except CompatOverride.DoesNotExist:
log.info('Compat override for addon with version ID [%d] not '
'found. Incompatible versions were cleared.' % version_id)
return
app_ranges = []
ranges = compat.collapsed_ranges()
for range in ranges:
if range.min == '0' and range.max == '*':
# Wildcard range, add all app ranges
app_ranges.extend(range.apps)
else:
# Since we can't rely on add-on version numbers, get the min
# and max ID values and find versions whose ID is within those
# ranges, being careful with wildcards.
min_id = max_id = None
if range.min == '0':
versions = (Version.objects.filter(addon=version.addon_id)
.order_by('id')
.values_list('id', flat=True)[:1])
if versions:
min_id = versions[0]
else:
try:
min_id = Version.objects.get(addon=version.addon_id,
version=range.min).id
except Version.DoesNotExist:
pass
if range.max == '*':
versions = (Version.objects.filter(addon=version.addon_id)
.order_by('-id')
.values_list('id', flat=True)[:1])
if versions:
max_id = versions[0]
else:
try:
max_id = Version.objects.get(addon=version.addon_id,
version=range.max).id
except Version.DoesNotExist:
pass
if min_id and max_id:
if min_id <= version.id <= max_id:
app_ranges.extend(range.apps)
for app_range in app_ranges:
IncompatibleVersions.objects.create(version=version,
app=app_range.app.id,
min_app_version=app_range.min,
max_app_version=app_range.max)
log.info('Added incompatible version for version ID [%d]: '
'app:%d, %s -> %s' % (version_id, app_range.app.id,
app_range.min, app_range.max))
def make_checksum(header_path):
ls = LocalFileStorage()
raw_checksum = ls._open(header_path).read()

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

@ -17,8 +17,7 @@ from olympia.activity.models import ActivityLog, AddonLog
from olympia.addons import models as addons_models
from olympia.addons.models import (
Addon, AddonApprovalsCounter, AddonCategory, AddonReviewerFlags, AddonUser,
AppSupport, Category, CompatOverride, CompatOverrideRange, DeniedGuid,
DeniedSlug, FrozenAddon, IncompatibleVersions, MigratedLWT,
AppSupport, Category, DeniedGuid, DeniedSlug, FrozenAddon, MigratedLWT,
Preview, ReusedGUID, track_addon_status_change)
from olympia.amo.templatetags.jinja_helpers import absolutify
from olympia.amo.tests import (
@ -1738,7 +1737,6 @@ class TestAddonDelete(TestCase):
AddonUser.objects.create(
addon=addon, user=UserProfile.objects.create())
AppSupport.objects.create(addon=addon, app=1)
CompatOverride.objects.create(addon=addon)
FrozenAddon.objects.create(addon=addon)
AddonLog.objects.create(
@ -2469,201 +2467,6 @@ class TestLanguagePack(TestCase, amo.tests.AMOPaths):
assert self.addon.reload().get_localepicker() == ''
class TestCompatOverride(TestCase):
def setUp(self):
super(TestCompatOverride, self).setUp()
self.app = amo.APP_IDS[1]
one = CompatOverride.objects.create(guid='one')
CompatOverrideRange.objects.create(compat=one, app=self.app.id)
two = CompatOverride.objects.create(guid='two')
CompatOverrideRange.objects.create(compat=two, app=self.app.id,
min_version='1', max_version='2')
CompatOverrideRange.objects.create(compat=two, app=self.app.id,
min_version='1', max_version='2',
min_app_version='3',
max_app_version='4')
def check(self, obj, **kw):
"""Check that key/value pairs in kw match attributes of obj."""
for key, expected in kw.items():
actual = getattr(obj, key)
assert actual == expected
def test_override_type(self):
one = CompatOverride.objects.get(guid='one')
# The default is incompatible.
c = CompatOverrideRange.objects.create(compat=one, app=1)
assert c.override_type() == 'incompatible'
c = CompatOverrideRange.objects.create(compat=one, app=1, type=0)
assert c.override_type() == 'compatible'
def test_guid_match(self):
# We hook up the add-on automatically if we see a matching guid.
addon = Addon.objects.create(id=1, guid='oh yeah', type=1)
c = CompatOverride.objects.create(guid=addon.guid)
assert c.addon_id == addon.id
c = CompatOverride.objects.create(guid='something else')
assert c.addon is None
def test_transformer(self):
compats = list(CompatOverride.objects
.transform(CompatOverride.transformer))
ranges = list(CompatOverrideRange.objects.all())
# If the transformer works then we won't have any more queries.
with self.assertNumQueries(0):
for c in compats:
assert c.compat_ranges == (
[r for r in ranges if r.compat_id == c.id])
def test_collapsed_ranges(self):
# Test that we get back the right structures from collapsed_ranges().
c = CompatOverride.objects.get(guid='one')
r = c.collapsed_ranges()
assert len(r) == 1
compat_range = r[0]
self.check(compat_range, type='incompatible', min='0', max='*')
assert len(compat_range.apps) == 1
self.check(compat_range.apps[0], app=amo.FIREFOX, min='0', max='*')
def test_collapsed_ranges_multiple_versions(self):
c = CompatOverride.objects.get(guid='one')
CompatOverrideRange.objects.create(compat=c, app=1,
min_version='1', max_version='2',
min_app_version='3',
max_app_version='3.*')
r = c.collapsed_ranges()
assert len(r) == 2
self.check(r[0], type='incompatible', min='0', max='*')
assert len(r[0].apps) == 1
self.check(r[0].apps[0], app=amo.FIREFOX, min='0', max='*')
self.check(r[1], type='incompatible', min='1', max='2')
assert len(r[1].apps) == 1
self.check(r[1].apps[0], app=amo.FIREFOX, min='3', max='3.*')
def test_collapsed_ranges_different_types(self):
# If the override ranges have different types they should be separate
# entries.
c = CompatOverride.objects.get(guid='one')
CompatOverrideRange.objects.create(compat=c, app=1, type=0,
min_app_version='3',
max_app_version='3.*')
r = c.collapsed_ranges()
assert len(r) == 2
self.check(r[0], type='compatible', min='0', max='*')
assert len(r[0].apps) == 1
self.check(r[0].apps[0], app=amo.FIREFOX, min='3', max='3.*')
self.check(r[1], type='incompatible', min='0', max='*')
assert len(r[1].apps) == 1
self.check(r[1].apps[0], app=amo.FIREFOX, min='0', max='*')
def test_collapsed_ranges_multiple_apps(self):
c = CompatOverride.objects.get(guid='two')
r = c.collapsed_ranges()
assert len(r) == 1
compat_range = r[0]
self.check(compat_range, type='incompatible', min='1', max='2')
assert len(compat_range.apps) == 2
self.check(compat_range.apps[0], app=amo.FIREFOX, min='0', max='*')
self.check(compat_range.apps[1], app=amo.FIREFOX, min='3', max='4')
def test_collapsed_ranges_multiple_versions_and_apps(self):
c = CompatOverride.objects.get(guid='two')
CompatOverrideRange.objects.create(min_version='5', max_version='6',
compat=c, app=1)
r = c.collapsed_ranges()
assert len(r) == 2
self.check(r[0], type='incompatible', min='1', max='2')
assert len(r[0].apps) == 2
self.check(r[0].apps[0], app=amo.FIREFOX, min='0', max='*')
self.check(r[0].apps[1], app=amo.FIREFOX, min='3', max='4')
self.check(r[1], type='incompatible', min='5', max='6')
assert len(r[1].apps) == 1
self.check(r[1].apps[0], app=amo.FIREFOX, min='0', max='*')
class TestIncompatibleVersions(TestCase):
def setUp(self):
super(TestIncompatibleVersions, self).setUp()
self.app = amo.APP_IDS[amo.FIREFOX.id]
self.addon = Addon.objects.create(guid='r@b', type=amo.ADDON_EXTENSION)
def test_signals_min(self):
assert IncompatibleVersions.objects.count() == 0
c = CompatOverride.objects.create(guid='r@b')
CompatOverrideRange.objects.create(compat=c, app=self.app.id,
min_version='0',
max_version='1.0')
# Test the max version matched.
version1 = Version.objects.create(id=2, addon=self.addon,
version='1.0')
assert IncompatibleVersions.objects.filter(
version=version1).count() == 1
assert IncompatibleVersions.objects.count() == 1
# Test the lower range.
version2 = Version.objects.create(id=1, addon=self.addon,
version='0.5')
assert IncompatibleVersions.objects.filter(
version=version2).count() == 1
assert IncompatibleVersions.objects.count() == 2
# Test delete signals.
version1.delete()
assert IncompatibleVersions.objects.count() == 1
version2.delete()
assert IncompatibleVersions.objects.count() == 0
def test_signals_max(self):
assert IncompatibleVersions.objects.count() == 0
c = CompatOverride.objects.create(guid='r@b')
CompatOverrideRange.objects.create(compat=c, app=self.app.id,
min_version='1.0',
max_version='*')
# Test the min_version matched.
version1 = Version.objects.create(addon=self.addon, version='1.0')
assert IncompatibleVersions.objects.filter(
version=version1).count() == 1
assert IncompatibleVersions.objects.count() == 1
# Test the upper range.
version2 = Version.objects.create(addon=self.addon, version='99.0')
assert IncompatibleVersions.objects.filter(
version=version2).count() == 1
assert IncompatibleVersions.objects.count() == 2
# Test delete signals.
version1.delete()
assert IncompatibleVersions.objects.count() == 1
version2.delete()
assert IncompatibleVersions.objects.count() == 0
class TestAddonApprovalsCounter(TestCase):
def setUp(self):
self.addon = addon_factory()

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

@ -7,11 +7,10 @@ from rest_framework.test import APIRequestFactory
from olympia import amo
from olympia.accounts.tests.test_serializers import TestBaseUserSerializer
from olympia.addons.models import (
Addon, AddonCategory, AddonUser, Category, CompatOverride,
CompatOverrideRange, Preview, ReplacementAddon)
Addon, AddonCategory, AddonUser, Category, Preview, ReplacementAddon)
from olympia.addons.serializers import (
AddonDeveloperSerializer, AddonSerializer, AddonSerializerWithUnlistedData,
CompatOverrideSerializer, ESAddonAutoCompleteSerializer, ESAddonSerializer,
ESAddonAutoCompleteSerializer, ESAddonSerializer,
LanguageToolsSerializer, LicenseSerializer, ReplacementAddonSerializer,
SimpleVersionSerializer, VersionSerializer)
from olympia.addons.utils import generate_addon_guid
@ -1336,144 +1335,3 @@ class TestReplacementAddonSerializer(TestCase):
addon.update(status=amo.STATUS_APPROVED)
result = self.serialize(rep)
assert result['replacement'] == [u'newstuff@mozilla']
class TestCompatOverrideSerializer(TestCase):
def serialize(self, override):
serializer = CompatOverrideSerializer()
return serializer.to_representation(override)
def test_linked_addon(self):
addon = addon_factory(guid='extrabad@thing')
override = CompatOverride.objects.create(
name='override with addon', guid=addon.guid, addon=addon)
CompatOverrideRange.objects.create(
compat=override, app=amo.FIREFOX.id)
result = self.serialize(override)
assert ['addon_guid', 'addon_id', 'name', 'version_ranges'] == sorted(
result.keys())
assert result['addon_guid'] == 'extrabad@thing'
assert result['addon_id'] == addon.id
assert result['name'] == 'override with addon'
version_range = {
'addon_min_version': '0',
'addon_max_version': '*',
'applications': [{
'name': amo.FIREFOX.pretty,
'id': amo.FIREFOX.id,
'min_version': '0',
'max_version': '*',
'guid': amo.FIREFOX.guid
}]
}
assert result['version_ranges'] == [version_range]
def test_no_addon(self):
override = CompatOverride.objects.create(
name='override', guid='foo@baa')
CompatOverrideRange.objects.create(
compat=override, app=amo.FIREFOX.id)
result = self.serialize(override)
assert ['addon_guid', 'addon_id', 'name', 'version_ranges'] == sorted(
result.keys())
assert result['addon_guid'] == 'foo@baa'
assert result['addon_id'] is None
assert result['name'] == 'override'
version_range = {
'addon_min_version': '0',
'addon_max_version': '*',
'applications': [{
'name': amo.FIREFOX.pretty,
'id': amo.FIREFOX.id,
'min_version': '0',
'max_version': '*',
'guid': amo.FIREFOX.guid
}]
}
assert result['version_ranges'] == [version_range]
def test_multiple_ranges(self):
override = CompatOverride.objects.create(
name='override with multiple ranges', guid='foo@baa')
CompatOverrideRange.objects.create(
compat=override, app=amo.FIREFOX.id, min_version='23.4',
max_version='56.7.*')
CompatOverrideRange.objects.create(
compat=override, app=amo.ANDROID.id, min_app_version='1.35',
max_app_version='90.*')
result = self.serialize(override)
assert ['addon_guid', 'addon_id', 'name', 'version_ranges'] == sorted(
result.keys())
assert result['addon_guid'] == 'foo@baa'
assert result['addon_id'] is None
assert result['name'] == 'override with multiple ranges'
assert len(result['version_ranges']) == 2
version_range_firefox = {
'addon_min_version': '23.4',
'addon_max_version': '56.7.*',
'applications': [{
'name': amo.FIREFOX.pretty,
'id': amo.FIREFOX.id,
'min_version': '0',
'max_version': '*',
'guid': amo.FIREFOX.guid
}]
}
assert version_range_firefox in result['version_ranges']
version_range_android = {
'addon_min_version': '0',
'addon_max_version': '*',
'applications': [{
'name': amo.ANDROID.pretty,
'id': amo.ANDROID.id,
'min_version': '1.35',
'max_version': '90.*',
'guid': amo.ANDROID.guid
}]
}
assert version_range_android in result['version_ranges']
def test_collapsed_ranges(self):
"""Collapsed ranges are where there is a single version range of
affected addons, but multiple applications affected."""
override = CompatOverride.objects.create(
name='override with single version range', guid='foo@baa')
CompatOverrideRange.objects.create(
compat=override, app=amo.FIREFOX.id,
min_version='23.4', max_version='56.7.*')
CompatOverrideRange.objects.create(
compat=override, app=amo.ANDROID.id,
min_version='23.4', max_version='56.7.*',
min_app_version='1.35', max_app_version='90.*')
result = self.serialize(override)
assert ['addon_guid', 'addon_id', 'name', 'version_ranges'] == sorted(
result.keys())
assert result['addon_guid'] == 'foo@baa'
assert result['addon_id'] is None
assert result['name'] == 'override with single version range'
assert len(result['version_ranges']) == 1
assert result['version_ranges'][0]['addon_min_version'] == '23.4'
assert result['version_ranges'][0]['addon_max_version'] == '56.7.*'
applications = result['version_ranges'][0]['applications']
assert len(applications) == 2
application_firefox = {
'name': amo.FIREFOX.pretty,
'id': amo.FIREFOX.id,
'min_version': '0',
'max_version': '*',
'guid': amo.FIREFOX.guid
}
assert application_firefox in applications
application_android = {
'name': amo.ANDROID.pretty,
'id': amo.ANDROID.id,
'min_version': '1.35',
'max_version': '90.*',
'guid': amo.ANDROID.guid
}
assert application_android in applications

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

@ -10,8 +10,7 @@ from django.db import connection
from services import update
from olympia import amo
from olympia.addons.models import (
Addon, CompatOverride, CompatOverrideRange, IncompatibleVersions)
from olympia.addons.models import Addon
from olympia.amo.tests import TestCase
from olympia.applications.models import AppVersion
from olympia.files.models import File
@ -373,16 +372,6 @@ class TestDefaultToCompat(VersionCheckMixin, TestCase):
'8.0-ignore': self.ver_1_3,
}
def create_override(self, **kw):
co = CompatOverride.objects.create(
name='test', guid=self.addon.guid, addon=self.addon
)
default = dict(compat=co, app=self.app.id, min_version='0',
max_version='*', min_app_version='0',
max_app_version='*')
default.update(kw)
CompatOverrideRange.objects.create(**default)
def update_files(self, **kw):
for version in self.addon.versions.all():
for file in version.files.all():
@ -460,29 +449,6 @@ class TestDefaultToCompat(VersionCheckMixin, TestCase):
})
self.check(self.expected)
def test_extension_compat_override(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override.
self.create_override(min_version='1.3', max_version='1.3')
self.expected.update({
'6.0-normal': self.ver_1_2,
'7.0-normal': self.ver_1_2,
'8.0-normal': self.ver_1_2,
})
self.check(self.expected)
def test_binary_component_compat_override(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override.
self.update_files(binary_components=True)
self.create_override(min_version='1.3', max_version='1.3')
self.expected.update({
'6.0-normal': self.ver_1_2,
'7.0-normal': self.ver_1_2,
'8.0-normal': None,
})
self.check(self.expected)
def test_strict_opt_in(self):
# Tests add-on with opt-in strict compatibility
self.update_files(strict_compatibility=True)
@ -491,49 +457,6 @@ class TestDefaultToCompat(VersionCheckMixin, TestCase):
})
self.check(self.expected)
def test_compat_override_max_addon_wildcard(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override that contains a max wildcard.
self.create_override(min_version='1.2', max_version='1.3',
min_app_version='5.0', max_app_version='6.*')
self.expected.update({
'5.0-normal': self.ver_1_1,
'6.0-normal': self.ver_1_1,
})
self.check(self.expected)
def test_compat_override_max_app_wildcard(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override that contains a min/max wildcard for the app.
self.create_override(min_version='1.2', max_version='1.3')
self.expected.update({
'5.0-normal': self.ver_1_1,
'6.0-normal': self.ver_1_1,
'7.0-normal': self.ver_1_1,
'8.0-normal': self.ver_1_1,
})
self.check(self.expected)
def test_compat_override_both_wildcards(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override that contains a wildcard for both addon version and app
# version.
self.create_override(min_app_version='7.0', max_app_version='*')
self.expected.update({
'7.0-normal': None,
'8.0-normal': None,
})
self.check(self.expected)
def test_compat_override_invalid_version(self):
# Tests compat override range where version doesn't match our
# versioning scheme. This results in no versions being written to the
# incompatible_versions table.
self.create_override(min_version='ver1', max_version='ver2')
assert IncompatibleVersions.objects.all().count() == 0
def test_min_max_version(self):
# Tests the minimum requirement of the app maxVersion.
av = self.addon.current_version.apps.all()[0]

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

@ -16,9 +16,7 @@ from waffle import switch_is_active
from waffle.testutils import override_switch
from olympia import amo
from olympia.addons.models import (
Addon, AddonUser, Category, CompatOverride,
CompatOverrideRange, ReplacementAddon)
from olympia.addons.models import Addon, AddonUser, Category, ReplacementAddon
from olympia.addons.utils import generate_addon_guid
from olympia.addons.views import (
DEFAULT_FIND_REPLACEMENT_PATH, FIND_REPLACEMENT_SRC,
@ -2557,116 +2555,19 @@ class TestReplacementAddonView(TestCase):
class TestCompatOverrideView(TestCase):
"""This view is used by Firefox directly and queried a lot.
That's why there are performance sensitive tests.
But now we don't have any CompatOverrides we just return an empty response.
"""
client_class = APITestClient
def setUp(self):
self.addon = addon_factory(guid='extrabad@thing')
self.override_addon = CompatOverride.objects.create(
name='override with addon', guid=self.addon.guid, addon=self.addon)
CompatOverrideRange.objects.create(
compat=self.override_addon, app=amo.FIREFOX.id)
self.override_without = CompatOverride.objects.create(
name='override no addon', guid='bad@thing')
CompatOverrideRange.objects.create(
compat=self.override_without, app=amo.FIREFOX.id)
def test_single_guid(self):
def test_response(self):
response = self.client.get(
reverse_ns('addon-compat-override'),
data={'guid': u'extrabad@thing'})
assert response.status_code == 200
data = json.loads(force_text(response.content))
assert len(data['results']) == 1
result = data['results'][0]
assert result['addon_guid'] == 'extrabad@thing'
assert result['addon_id'] == self.addon.id
assert result['name'] == 'override with addon'
def test_multiple_guid(self):
response = self.client.get(
reverse_ns('addon-compat-override'),
reverse_ns('addon-compat-override', api_version='v3'),
data={'guid': u'extrabad@thing,bad@thing'})
assert response.status_code == 200
data = json.loads(force_text(response.content))
results = data['results']
assert len(results) == 2
assert results[0]['addon_guid'] == 'bad@thing'
assert results[0]['addon_id'] is None
assert results[0]['name'] == 'override no addon'
assert results[1]['addon_guid'] == 'extrabad@thing'
assert results[1]['addon_id'] == self.addon.id
assert results[1]['name'] == 'override with addon'
# Throw in some random invalid guids too that will be ignored.
response = self.client.get(
reverse_ns('addon-compat-override'),
data={'guid': (
u'extrabad@thing,invalid@guid,notevenaguid$,bad@thing')})
assert response.status_code == 200
data = json.loads(force_text(response.content))
results = data['results']
assert len(results) == 2
assert results[0]['addon_guid'] == 'bad@thing'
assert results[1]['addon_guid'] == 'extrabad@thing'
def test_no_guid_param(self):
response = self.client.get(
reverse_ns('addon-compat-override'),
data={'guid': u'invalid@thing'})
# Searching for non-matching guids, it should be an empty 200 response.
assert response.status_code == 200
assert len(json.loads(force_text(response.content))['results']) == 0
response = self.client.get(
reverse_ns('addon-compat-override'), data={'guid': ''})
# Empty query is a 400 because a guid is required for overrides.
assert response.status_code == 400
assert b'Empty, or no, guid parameter provided.' in response.content
response = self.client.get(
reverse_ns('addon-compat-override'))
# And no guid param should be a 400 too
assert response.status_code == 400
assert b'Empty, or no, guid parameter provided.' in response.content
def test_performance_no_matching_guid(self):
# There is at least one query from the paginator, counting all objects
# We do not query on `compat_override` though if the count is 0.
with self.assertNumQueries(1):
response = self.client.get(
reverse_ns('addon-compat-override'),
data={'guid': u'unknownguid'})
assert response.status_code == 200
data = json.loads(force_text(response.content))
assert len(data['results']) == 0
def test_performance_matches_one_guid(self):
# 1. Query is querying compat_override
# 2. Query is adding CompatOverrideRange via the transformer
with self.assertNumQueries(2):
response = self.client.get(
reverse_ns('addon-compat-override'),
data={'guid': u'extrabad@thing'})
assert response.status_code == 200
data = json.loads(force_text(response.content))
assert len(data['results']) == 1
def test_performance_matches_multiple_guid(self):
# 1. Query is querying compat_override
# 2. Query is adding CompatOverrideRange via the transformer
with self.assertNumQueries(2):
response = self.client.get(
reverse_ns('addon-compat-override'),
data={'guid': (
u'extrabad@thing,invalid@guid,notevenaguid$,'
u'bad@thing')})
assert response.status_code == 200
data = json.loads(force_text(response.content))
assert len(data['results']) == 2
assert len(results) == 0
class TestAddonRecommendationView(ESTestCase):

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

@ -15,6 +15,7 @@ from rest_framework.generics import GenericAPIView, ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet
import olympia.core.logger
@ -30,8 +31,7 @@ from olympia.api.permissions import (
from olympia.constants.categories import CATEGORIES_BY_ID
from olympia.search.filters import (
AddonAppQueryParam, AddonAppVersionQueryParam, AddonAuthorQueryParam,
AddonCategoryQueryParam, AddonGuidQueryParam, AddonTypeQueryParam,
AutoCompleteSortFilter,
AddonCategoryQueryParam, AddonTypeQueryParam, AutoCompleteSortFilter,
ReviewedContentFilter, SearchParameterFilter, SearchQueryFilter,
SortingFilter)
from olympia.translations.query import order_by_translation
@ -39,10 +39,10 @@ from olympia.versions.models import Version
from .decorators import addon_view_factory
from .indexers import AddonIndexer
from .models import Addon, CompatOverride, ReplacementAddon
from .models import Addon, ReplacementAddon
from .serializers import (
AddonEulaPolicySerializer,
AddonSerializer, AddonSerializerWithUnlistedData, CompatOverrideSerializer,
AddonSerializer, AddonSerializerWithUnlistedData,
ESAddonAutoCompleteSerializer, ESAddonSerializer, LanguageToolsSerializer,
ReplacementAddonSerializer, StaticCategorySerializer, VersionSerializer)
from .utils import (
@ -677,43 +677,17 @@ class ReplacementAddonView(ListAPIView):
serializer_class = ReplacementAddonSerializer
class CompatOverrideView(ListAPIView):
"""This view is used by Firefox so it's performance-critical.
Every firefox client requests the list of overrides approx. once per day.
Firefox requests the overrides via a list of GUIDs which makes caching
hard because the variation of possible GUID combinations prevent us to
simply add some dumb-caching and requires us to resolve cache-misses.
class CompatOverrideView(APIView):
"""This view is used by Firefox but we don't have any more overrides so we
just return an empty response. This api is v3 only.
"""
queryset = CompatOverride.objects.all()
serializer_class = CompatOverrideSerializer
@classmethod
def as_view(cls, **initkwargs):
"""The API is read-only so we can turn off atomic requests."""
return non_atomic_requests(
super(CompatOverrideView, cls).as_view(**initkwargs))
return non_atomic_requests(super().as_view(**initkwargs))
def get_guids(self):
# Use the same Filter we use for AddonSearchView for consistency.
guid_filter = AddonGuidQueryParam(self.request)
return guid_filter.get_value()
def filter_queryset(self, queryset):
guids = self.get_guids()
if not guids:
raise exceptions.ParseError(
'Empty, or no, guid parameter provided.')
# Evaluate the queryset and cast it into a list.
# This will force Django to simply use len(queryset) instead of
# calling .count() on it and avoids an additional COUNT query.
# The amount of GUIDs we should get in real-life won't be paginated
# most of the time so it's safe to simply evaluate the query.
# The advantage here is that we are saving ourselves a `COUNT` query
# and these are expensive.
return list(queryset.filter(guid__in=guids).transform(
CompatOverride.transformer).order_by('-pk'))
def get(self, request, format=None):
return Response({'results': []})
class AddonRecommendationView(AddonSearchView):

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

@ -1,12 +1,13 @@
from django.conf.urls import include, url
from olympia.addons.api_urls import addons_v3, addons_v4
from olympia.ratings.api_urls import ratings_v3, ratings_v4
v3_api_urls = [
url(r'^abuse/', include('olympia.abuse.urls')),
url(r'^accounts/', include('olympia.accounts.urls')),
url(r'^addons/', include('olympia.addons.api_urls')),
url(r'^addons/', include(addons_v3)),
url(r'^', include('olympia.discovery.api_urls')),
url(r'^reviews/', include(ratings_v3.urls)),
url(r'^reviewers/', include('olympia.reviewers.api_urls')),
@ -18,7 +19,7 @@ v4_api_urls = [
url(r'^abuse/', include('olympia.abuse.urls')),
url(r'^accounts/', include('olympia.accounts.urls')),
url(r'^activity/', include('olympia.activity.urls')),
url(r'^addons/', include('olympia.addons.api_urls')),
url(r'^addons/', include(addons_v4)),
url(r'^', include('olympia.discovery.api_urls')),
url(r'^ratings/', include(ratings_v4.urls)),
url(r'^reviewers/', include('olympia.reviewers.api_urls')),

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

@ -1125,8 +1125,6 @@ CELERY_TASK_ROUTES = {
# Addons
'olympia.addons.tasks.delete_preview_files': {'queue': 'addons'},
'olympia.versions.tasks.delete_preview_files': {'queue': 'addons'},
'olympia.addons.tasks.update_incompatible_appversions': {
'queue': 'addons'},
'olympia.addons.tasks.version_changed': {'queue': 'addons'},
# API

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

@ -1,31 +1,2 @@
from addons.models import Addon, CompatOverride, CompatOverrideRange
from olympia import amo
def run():
addons = (
Addon.objects
.filter(type=amo.ADDON_EXTENSION, appsupport__app=amo.FIREFOX.id,
_current_version__files__jetpack_version__isnull=False)
.exclude(_current_version__files__jetpack_version='1.14'))
# Fix invalid compat ranges from last migration
(CompatOverrideRange.objects.filter(
compat__addon__in=addons, type=1, app_id=amo.FIREFOX.id,
min_app_version='0', max_app_version='21.*', min_version='0')
.delete())
count = 0
for addon in addons:
co, created = CompatOverride.objects.get_or_create(addon=addon,
guid=addon.guid,
name=addon.name)
CompatOverrideRange.objects.create(
compat=co, type=1, app_id=amo.FIREFOX.id,
min_app_version='21.*', max_app_version='*',
min_version='0', max_version=addon.current_version.version)
count += 1
print('Overrode compatibility for %d SDK add-ons.' % count)
pass

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

@ -691,21 +691,6 @@ def inherit_nomination(sender, instance, **kw):
instance.inherit_nomination()
def update_incompatible_versions(sender, instance, **kw):
"""
When a new version is added or deleted, send to task to update if it
matches any compat overrides.
"""
try:
if not instance.addon.type == amo.ADDON_EXTENSION:
return
except ObjectDoesNotExist:
return
from olympia.addons import tasks
tasks.update_incompatible_appversions.delay([instance.id])
def cleanup_version(sender, instance, **kw):
"""On delete of the version object call the file delete and signals."""
if kw.get('raw'):
@ -737,17 +722,11 @@ models.signals.post_save.connect(
models.signals.post_save.connect(
inherit_nomination, sender=Version,
dispatch_uid='version_inherit_nomination')
models.signals.post_save.connect(
update_incompatible_versions, sender=Version,
dispatch_uid='version_update_incompat')
models.signals.pre_delete.connect(
cleanup_version, sender=Version, dispatch_uid='cleanup_version')
models.signals.post_delete.connect(
update_status, sender=Version, dispatch_uid='version_update_status')
models.signals.post_delete.connect(
update_incompatible_versions, sender=Version,
dispatch_uid='version_update_incompat')
class LicenseManager(ManagerBase):

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

@ -17,8 +17,7 @@ from waffle.testutils import override_switch
from olympia import amo, core
from olympia.activity.models import ActivityLog
from olympia.addons.models import (
Addon, AddonReviewerFlags, CompatOverride, CompatOverrideRange)
from olympia.addons.models import Addon, AddonReviewerFlags
from olympia.amo.tests import (
TestCase, addon_factory, user_factory, version_factory)
from olympia.amo.tests.test_models import BasePreviewMixin
@ -424,26 +423,6 @@ class TestVersion(TestCase):
# An app that can't do d2c should also be False.
assert not version.is_compatible_app(amo.UNKNOWN_APP)
def test_compat_override_app_versions(self):
addon = Addon.objects.get(id=3615)
version = version_factory(addon=addon)
co = CompatOverride.objects.create(addon=addon)
CompatOverrideRange.objects.create(compat=co, app=1, min_version='0',
max_version=version.version,
min_app_version='10.0a1',
max_app_version='10.*')
assert version.compat_override_app_versions() == [('10.0a1', '10.*')]
def test_compat_override_app_versions_wildcard(self):
addon = Addon.objects.get(id=3615)
version = version_factory(addon=addon)
co = CompatOverride.objects.create(addon=addon)
CompatOverrideRange.objects.create(compat=co, app=1, min_version='0',
max_version='*',
min_app_version='10.0a1',
max_app_version='10.*')
assert version.compat_override_app_versions() == [('10.0a1', '10.*')]
def test_get_url_path(self):
assert self.version.get_url_path() == (
'/en-US/firefox/addon/a3615/versions/')