From b4ac83efcb5857b55a2b00e38a774e51dbc52cf4 Mon Sep 17 00:00:00 2001 From: Mathieu Pillard Date: Tue, 16 Aug 2016 17:13:28 +0200 Subject: [PATCH] Add support for beta versions in the add-on APIs - Expose current_beta_version in detail/search responses - Allow filtering add-on versions list to show only betas --- docs/topics/api/addons.rst | 4 +- src/olympia/addons/indexers.py | 128 +++++++++++-------- src/olympia/addons/models.py | 2 +- src/olympia/addons/serializers.py | 65 ++++++---- src/olympia/addons/tests/test_indexers.py | 44 ++++++- src/olympia/addons/tests/test_serializers.py | 84 ++++++++---- src/olympia/addons/tests/test_views.py | 4 + src/olympia/addons/views.py | 15 ++- src/olympia/search/filters.py | 6 +- src/olympia/search/tests/test_filters.py | 6 +- 10 files changed, 238 insertions(+), 120 deletions(-) diff --git a/docs/topics/api/addons.rst b/docs/topics/api/addons.rst index 6b11cec4f6..2f37dc2587 100644 --- a/docs/topics/api/addons.rst +++ b/docs/topics/api/addons.rst @@ -103,6 +103,7 @@ This endpoint allows you to fetch a specific add-on by id, slug or guid. :>json object compatibility: Object detailing the add-on :ref:`add-on application ` and version compatibility. :>json object compatibility[app_name].max: Maximum version of the corresponding app the add-on is compatible with. :>json object compatibility[app_name].min: Minimum version of the corresponding app the add-on is compatible with. + :>json object current_beta_version: Object holding the current beta :ref:`version ` of the add-on, if it exists. For performance reasons the ``license`` and ``release_notes`` fields are omitted. :>json object current_version: Object holding the current :ref:`version ` of the add-on. For performance reasons the ``license`` and ``release_notes`` fields are omitted. :>json string default_locale: The add-on default locale for translations. :>json string|object|null description: The add-on description (See :ref:`translated fields `). @@ -237,7 +238,7 @@ This endpoint allows you to list all versions belonging to a specific add-on. By default, the version list API will only return versions with valid statuses (excluding versions that have incomplete, disabled, deleted, rejected or flagged for further review files) - you can change that with the ``filter`` - query parameter, which requires authentication and specific permissions + query parameter, which may require authentication and specific permissions depending on the value: ================ ======================================================== @@ -248,6 +249,7 @@ This endpoint allows you to list all versions belonging to a specific add-on. a developer of the add-on. all_with_deleted Show all versions attached to this add-on, including deleted ones. Requires admin permissions. + beta_only Show beta versions only. ================ ======================================================== -------------- diff --git a/src/olympia/addons/indexers.py b/src/olympia/addons/indexers.py index 2615d5d55c..2ac14799c1 100644 --- a/src/olympia/addons/indexers.py +++ b/src/olympia/addons/indexers.py @@ -20,7 +20,7 @@ class AddonIndexer(BaseSearchIndexer): @classmethod def get_mapping(cls): doc_name = cls.get_doctype_name() - appver = { + appver_mapping = { 'properties': { 'max': {'type': 'long'}, 'min': {'type': 'long'}, @@ -28,40 +28,47 @@ class AddonIndexer(BaseSearchIndexer): 'min_human': {'type': 'string', 'index': 'no'}, } } + version_mapping = { + 'type': 'object', + 'properties': { + 'compatible_apps': {'properties': {app.id: appver_mapping + for app in amo.APP_USAGE}}, + 'id': {'type': 'long', 'index': 'no'}, + 'reviewed': {'type': 'date', 'index': 'no'}, + 'files': { + 'type': 'object', + 'properties': { + 'id': {'type': 'long', 'index': 'no'}, + 'created': {'type': 'date', 'index': 'no'}, + 'hash': {'type': 'string', 'index': 'no'}, + 'filename': { + 'type': 'string', 'index': 'no'}, + 'platform': { + 'type': 'byte', 'index': 'no'}, + 'size': {'type': 'long', 'index': 'no'}, + 'status': {'type': 'byte'}, + } + }, + 'version': {'type': 'string', 'index': 'no'}, + } + } mapping = { doc_name: { 'properties': { 'id': {'type': 'long'}, 'app': {'type': 'byte'}, - 'appversion': {'properties': {app.id: appver + # FIXME: remove and update the code (search/views.py, + # legacy_api/utils.py) to use the newest mapping that + # replaces this field by current_version.compatible_apps. + 'appversion': {'properties': {app.id: appver_mapping for app in amo.APP_USAGE}}, 'average_daily_users': {'type': 'long'}, 'bayesian_rating': {'type': 'double'}, + 'current_beta_version': version_mapping, 'category': {'type': 'integer'}, 'created': {'type': 'date'}, - 'current_version': { - 'type': 'object', - 'properties': { - 'id': {'type': 'long', 'index': 'no'}, - 'reviewed': {'type': 'date', 'index': 'no'}, - 'files': { - 'type': 'object', - 'properties': { - 'id': {'type': 'long', 'index': 'no'}, - 'created': {'type': 'date', 'index': 'no'}, - 'hash': {'type': 'string', 'index': 'no'}, - 'filename': { - 'type': 'string', 'index': 'no'}, - 'platform': { - 'type': 'byte', 'index': 'no'}, - 'size': {'type': 'long', 'index': 'no'}, - 'status': {'type': 'byte'}, - } - }, - 'version': {'type': 'string', 'index': 'no'}, - } - }, + 'current_version': version_mapping, 'boost': {'type': 'float', 'null_value': 1.0}, 'default_locale': {'type': 'string', 'index': 'no'}, 'description': {'type': 'string', 'analyzer': 'snowball'}, @@ -142,6 +149,41 @@ class AddonIndexer(BaseSearchIndexer): return mapping + @classmethod + def extract_version(cls, obj, version_obj): + return { + 'id': version_obj.pk, + 'compatible_apps': cls.extract_compatibility_info(version_obj), + 'files': [{ + 'id': file_.id, + 'created': file_.created, + 'filename': file_.filename, + 'hash': file_.hash, + 'platform': file_.platform, + 'size': file_.size, + 'status': file_.status, + } for file_ in version_obj.all_files], + 'reviewed': version_obj.reviewed, + 'version': version_obj.version, + } + + @classmethod + def extract_compatibility_info(cls, version_obj): + compatible_apps = {} + for app, appver in version_obj.compatible_apps.items(): + if appver: + min_, max_ = appver.min.version_int, appver.max.version_int + min_human, max_human = appver.min.version, appver.max.version + else: + # Fake wide compatibility for search tools and personas. + min_, max_ = 0, version_int('9999') + min_human, max_human = None, None + compatible_apps[app.id] = { + 'min': min_, 'min_human': min_human, + 'max': max_, 'max_human': max_human, + } + return compatible_apps + @classmethod def extract_document(cls, obj): """Extract indexable attributes from an add-on.""" @@ -182,19 +224,6 @@ class AddonIndexer(BaseSearchIndexer): data['has_theme_rereview'] = None data['app'] = [app.id for app in obj.compatible_apps.keys()] - data['appversion'] = {} - for app, appver in obj.compatible_apps.items(): - if appver: - min_, max_ = appver.min.version_int, appver.max.version_int - min_human, max_human = appver.min.version, appver.max.version - else: - # Fake wide compatibility for search tools and personas. - min_, max_ = 0, version_int('9999') - min_human, max_human = None, None - data['appversion'][app.id] = { - 'min': min_, 'min_human': min_human, - 'max': max_, 'max_human': max_human, - } # Quadruple the boost if the add-on is public. if (obj.status == amo.STATUS_PUBLIC and not obj.is_experimental and 'boost' in data): @@ -203,25 +232,22 @@ class AddonIndexer(BaseSearchIndexer): # transformer that sets it. data['category'] = [cat.id for cat in obj.all_categories] if obj.current_version: - data['current_version'] = { - 'id': obj.current_version.pk, - 'files': [{ - 'id': file_.id, - 'created': file_.created, - 'filename': file_.filename, - 'hash': file_.hash, - 'platform': file_.platform, - 'size': file_.size, - 'status': file_.status, - } for file_ in obj.current_version.all_files], - 'reviewed': obj.current_version.reviewed, - 'version': obj.current_version.version, - } + # FIXME: remove `appversion` once the newest mapping that has + # 'current_version.compatible_apps` is live. + data['appversion'] = cls.extract_compatibility_info( + obj.current_version) + data['current_version'] = cls.extract_version( + obj, obj.current_version) data['has_version'] = True data['platforms'] = [p.id for p in obj.current_version.supported_platforms] else: data['has_version'] = None + if obj.current_beta_version: + data['current_beta_version'] = cls.extract_version( + obj, obj.current_beta_version) + else: + data['current_beta_version'] = None data['listed_authors'] = [ {'name': a.name, 'id': a.id, 'username': a.username} for a in obj.listed_authors diff --git a/src/olympia/addons/models.py b/src/olympia/addons/models.py index e405250385..01623886bd 100644 --- a/src/olympia/addons/models.py +++ b/src/olympia/addons/models.py @@ -1184,7 +1184,7 @@ class Addon(OnChangeMixin, ModelBase): def show_adu(self): return self.type != amo.ADDON_SEARCH - @amo.cached_property + @amo.cached_property(writable=True) def current_beta_version(self): """Retrieves the latest version of an addon, in the beta channel.""" versions = self.versions.filter(files__status=amo.STATUS_BETA)[:1] diff --git a/src/olympia/addons/serializers.py b/src/olympia/addons/serializers.py index b9a28d56da..1e2ce30d7d 100644 --- a/src/olympia/addons/serializers.py +++ b/src/olympia/addons/serializers.py @@ -145,6 +145,7 @@ class AddonEulaPolicySerializer(serializers.ModelSerializer): class AddonSerializer(serializers.ModelSerializer): authors = AddonAuthorSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() + current_beta_version = SimpleVersionSerializer() current_version = SimpleVersionSerializer() description = TranslationSerializerField() edit_url = serializers.SerializerMethodField() @@ -173,6 +174,7 @@ class AddonSerializer(serializers.ModelSerializer): 'authors', 'average_daily_users', 'categories', + 'current_beta_version', 'current_version', 'default_locale', 'description', @@ -289,6 +291,34 @@ class ESAddonSerializer(BaseESSerializer, AddonSerializer): translated_fields = ('name', 'description', 'homepage', 'summary', 'support_email', 'support_url') + def fake_version_object(self, obj, data): + if data: + version = Version( + addon=obj, id=data['id'], + reviewed=self.handle_date(data['reviewed']), + version=data['version']) + version.all_files = [ + File( + id=file_['id'], created=self.handle_date(file_['created']), + hash=file_['hash'], filename=file_['filename'], + platform=file_['platform'], size=file_['size'], + status=file_['status'], version=version) + for file_ in data.get('files', []) + ] + + # In ES we store integers for the appversion info, we need to + # convert it back to strings. + compatible_apps = {} + for app_id, compat_dict in data.get('compatible_apps', {}).items(): + app_name = APPS_ALL[int(app_id)] + compatible_apps[app_name] = ApplicationsVersions( + min=AppVersion(version=compat_dict.get('min_human', '')), + max=AppVersion(version=compat_dict.get('max_human', ''))) + version.compatible_apps = compatible_apps + else: + version = None + return version + def fake_object(self, data): """Create a fake instance of Addon and related models from ES data.""" obj = Addon(id=data['id'], slug=data['slug']) @@ -328,33 +358,14 @@ class ESAddonSerializer(BaseESSerializer, AddonSerializer): # Attach translations (they require special treatment). self._attach_translations(obj, data, self.translated_fields) - # Attach related models (also faking them). - data_version = data.get('current_version') - if data_version: - obj._current_version = Version( - addon=obj, id=data_version['id'], - reviewed=self.handle_date(data_version['reviewed']), - version=data_version['version']) - data_files = data_version.get('files', []) - obj._current_version.all_files = [ - File( - id=file_['id'], created=self.handle_date(file_['created']), - hash=file_['hash'], filename=file_['filename'], - platform=file_['platform'], size=file_['size'], - status=file_['status'], version=obj._current_version) - for file_ in data_files - ] - - # In ES we store integers for the appversion info, we need to - # convert it back to strings. - compatible_apps = {} - for app_id, compat_dict in data['appversion'].items(): - app_name = APPS_ALL[int(app_id)] - compatible_apps[app_name] = ApplicationsVersions( - min=AppVersion(version=compat_dict.get('min_human', '')), - max=AppVersion(version=compat_dict.get('max_human', ''))) - - obj._current_version.compatible_apps = compatible_apps + # Attach related models (also faking them). `current_version` is a + # property we can't write to, so we use the underlying field which + # begins with an underscore. `current_beta_version` is a + # cached_property so we can directly write to it. + obj.current_beta_version = self.fake_version_object( + obj, data.get('current_beta_version')) + obj._current_version = self.fake_version_object( + obj, data.get('current_version')) data_authors = data.get('listed_authors', []) obj.listed_authors = [ diff --git a/src/olympia/addons/tests/test_indexers.py b/src/olympia/addons/tests/test_indexers.py index d168a2a25c..3201bd8a7e 100644 --- a/src/olympia/addons/tests/test_indexers.py +++ b/src/olympia/addons/tests/test_indexers.py @@ -3,7 +3,8 @@ from itertools import chain from olympia import amo from olympia.amo.models import SearchMixin -from olympia.amo.tests import addon_factory, ESTestCase, file_factory, TestCase +from olympia.amo.tests import ( + addon_factory, ESTestCase, file_factory, TestCase, version_factory) from olympia.addons.models import ( Addon, attach_tags, attach_translations, Preview) from olympia.addons.indexers import AddonIndexer @@ -44,8 +45,8 @@ class TestAddonIndexer(TestCase): # exist on the model, or it has a different name, or the value we need # to store in ES differs from the one in the db. complex_fields = [ - 'app', 'appversion', 'boost', 'category', 'current_version', - 'description', 'has_eula', 'has_privacy_policy', + 'app', 'appversion', 'boost', 'category', 'current_beta_version', + 'current_version', 'description', 'has_eula', 'has_privacy_policy', 'has_theme_rereview', 'has_version', 'listed_authors', 'name', 'name_sort', 'platforms', 'previews', 'public_stats', 'ratings', 'summary', 'tags', @@ -105,7 +106,8 @@ class TestAddonIndexer(TestCase): # Make sure current_version mapping is set. assert mapping_properties['current_version']['properties'] version_mapping = mapping_properties['current_version']['properties'] - expected_version_keys = ('id', 'files', 'reviewed', 'version') + expected_version_keys = ( + 'id', 'compatible_apps', 'files', 'reviewed', 'version') assert set(version_mapping.keys()) == set(expected_version_keys) # Make sure files mapping is set inside current_version. @@ -145,6 +147,8 @@ class TestAddonIndexer(TestCase): } assert extracted['boost'] == self.addon.average_daily_users ** .2 * 4 assert extracted['category'] == [1, 22, 71] # From fixture. + assert extracted['current_beta_version'] is None + assert extracted['current_version'] assert extracted['has_theme_rereview'] is None assert extracted['listed_authors'] == [ {'name': u'55021 التطب', 'id': 55021, 'username': '55021'}] @@ -169,12 +173,22 @@ class TestAddonIndexer(TestCase): assert extracted['has_privacy_policy'] is False def test_extract_version_and_files(self): + current_beta_version = version_factory( + addon=self.addon, file_kw={'status': amo.STATUS_BETA}) version = self.addon.current_version file_factory(version=version, platform=PLATFORM_MAC.id) extracted = self._extract() assert extracted['current_version'] assert extracted['current_version']['id'] == version.pk + assert extracted['current_version']['compatible_apps'] == { + FIREFOX.id: { + 'min': 2000000200100L, + 'max': 4000000200100L, + 'max_human': '4.0', + 'min_human': '2.0', + } + } assert extracted['current_version']['reviewed'] == version.reviewed assert extracted['current_version']['version'] == version.version for index, file_ in enumerate(version.all_files): @@ -186,9 +200,31 @@ class TestAddonIndexer(TestCase): assert extracted_file['platform'] == file_.platform assert extracted_file['size'] == file_.size assert extracted_file['status'] == file_.status + assert extracted['has_version'] assert set(extracted['platforms']) == set([PLATFORM_MAC.id, PLATFORM_ALL.id]) + version = current_beta_version + assert extracted['current_beta_version'] + assert extracted['current_beta_version']['id'] == version.pk + assert extracted['current_beta_version']['compatible_apps'] == { + FIREFOX.id: { + 'min': 4009900200100L, + 'max': 5009900200100L, + 'max_human': '5.0.99', + 'min_human': '4.0.99', + } + } + assert extracted['current_beta_version']['version'] == version.version + for index, file_ in enumerate(version.all_files): + extracted_file = extracted['current_beta_version']['files'][index] + assert extracted_file['id'] == file_.pk + assert extracted_file['created'] == file_.created + assert extracted_file['filename'] == file_.filename + assert extracted_file['hash'] == file_.hash + assert extracted_file['platform'] == file_.platform + assert extracted_file['size'] == file_.size + assert extracted_file['status'] == file_.status def test_extract_translations(self): translations_name = { diff --git a/src/olympia/addons/tests/test_serializers.py b/src/olympia/addons/tests/test_serializers.py index 352e18ed10..8d07d1abc7 100644 --- a/src/olympia/addons/tests/test_serializers.py +++ b/src/olympia/addons/tests/test_serializers.py @@ -5,7 +5,8 @@ from rest_framework.test import APIRequestFactory from olympia import amo from olympia.amo.helpers import absolutify from olympia.amo.tests import ( - addon_factory, ESTestCase, file_factory, TestCase, user_factory) + addon_factory, ESTestCase, file_factory, TestCase, version_factory, + user_factory) from olympia.amo.urlresolvers import reverse from olympia.addons.indexers import AddonIndexer from olympia.addons.models import ( @@ -23,6 +24,37 @@ class AddonSerializerOutputTestMixin(object): def setUp(self): self.request = APIRequestFactory().get('/') + def _test_version(self, version, data): + assert data['id'] == version.pk + + assert data['compatibility'] + assert len(data['compatibility']) == len(version.compatible_apps) + for app, compat in version.compatible_apps.items(): + assert data['compatibility'][app.short] == { + 'min': compat.min.version, + 'max': compat.max.version + } + assert data['files'] + assert len(data['files']) == 1 + + result_file = data['files'][0] + file_ = version.files.latest('pk') + assert result_file['id'] == file_.pk + assert result_file['created'] == file_.created.isoformat() + assert result_file['hash'] == file_.hash + assert result_file['platform'] == ( + amo.PLATFORM_CHOICES_API[file_.platform]) + assert result_file['size'] == file_.size + assert result_file['status'] == amo.STATUS_CHOICES_API[file_.status] + assert result_file['url'] == file_.get_url_path(src='') + + assert data['edit_url'] == absolutify( + self.addon.get_dev_url( + 'versions.edit', args=[version.pk], prefix_only=True)) + assert data['reviewed'] == version.reviewed + assert data['version'] == version.version + assert data['url'] == absolutify(version.get_url_path()) + def test_basic(self): self.addon = addon_factory( average_daily_users=4242, @@ -68,6 +100,8 @@ class AddonSerializerOutputTestMixin(object): ApplicationsVersions.objects.get_or_create( application=amo.THUNDERBIRD.id, version=self.addon.current_version, min=av_min, max=av_max) + # Reset current_version.compatible_apps now that we've added an app. + del self.addon.current_version.compatible_apps cat1 = Category.from_static_category( CATEGORIES[amo.FIREFOX.id][amo.ADDON_EXTENSION]['bookmarks']) @@ -83,8 +117,6 @@ class AddonSerializerOutputTestMixin(object): AddonCategory.objects.create(addon=self.addon, category=cat3) result = self.serialize() - version = self.addon.current_version - file_ = version.files.latest('pk') assert result['id'] == self.addon.pk @@ -93,32 +125,11 @@ class AddonSerializerOutputTestMixin(object): 'firefox': ['alerts-updates', 'bookmarks'], 'thunderbird': ['calendar']} + assert result['current_beta_version'] is None + assert result['current_version'] - assert result['current_version']['id'] == version.pk - assert result['current_version']['compatibility'] == { - 'firefox': {'max': u'5.0.99', 'min': u'4.0.99'}, - 'thunderbird': {'max': u'3.0.99', 'min': u'2.0.99'} - } - assert result['current_version']['files'] - assert len(result['current_version']['files']) == 1 - - result_file = result['current_version']['files'][0] - assert result_file['id'] == file_.pk - assert result_file['created'] == file_.created.isoformat() - assert result_file['hash'] == file_.hash - assert result_file['platform'] == 'windows' - assert result_file['size'] == file_.size - assert result_file['status'] == 'public' - assert result_file['url'] == file_.get_url_path(src='') - - assert result['current_version']['edit_url'] == absolutify( - self.addon.get_dev_url( - 'versions.edit', args=[self.addon.current_version.pk], - prefix_only=True)) - assert result['current_version']['reviewed'] == version.reviewed - assert result['current_version']['version'] == version.version - assert result['current_version']['url'] == absolutify( - version.get_url_path()) + self._test_version( + self.addon.current_version, result['current_version']) assert result['authors'] assert len(result['authors']) == 2 @@ -186,6 +197,23 @@ class AddonSerializerOutputTestMixin(object): return result + def test_current_beta_version(self): + self.addon = addon_factory() + + self.beta_version = version_factory( + addon=self.addon, file_kw={'status': amo.STATUS_BETA}, + version='1.1beta') + + result = self.serialize() + assert result['current_beta_version'] + self._test_version(self.beta_version, result['current_beta_version'], ) + + # Just in case, test that current version is still present & different. + assert result['current_version'] + assert result['current_version'] != result['current_beta_version'] + self._test_version( + self.addon.current_version, result['current_version']) + def test_is_disabled(self): self.addon = addon_factory(disabled_by_user=True) result = self.serialize() diff --git a/src/olympia/addons/tests/test_views.py b/src/olympia/addons/tests/test_views.py index 084dd7fdea..9908ad4aa9 100644 --- a/src/olympia/addons/tests/test_views.py +++ b/src/olympia/addons/tests/test_views.py @@ -1944,6 +1944,10 @@ class TestVersionViewSetList(AddonAndVersionViewSetDetailMixin, TestCase): self._test_url_only_contains_old_version(filter='all') self._test_url_only_contains_old_version(filter='all_with_deleted') + def test_beta_version(self): + self.old_version.files.update(status=amo.STATUS_BETA) + self._test_url_only_contains_old_version(filter='beta_only') + class TestAddonViewSetFeatureCompatibility(TestCase): def setUp(self): diff --git a/src/olympia/addons/views.py b/src/olympia/addons/views.py index f79e69ad7e..e2fe04c583 100644 --- a/src/olympia/addons/views.py +++ b/src/olympia/addons/views.py @@ -703,7 +703,7 @@ class AddonVersionViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet): # # When accessing a single version or if requesting it explicitly when # listing, admins can access all versions, including deleted ones. - can_access_all_versions_included_deleted = ( + should_access_all_versions_included_deleted = ( (requested == 'all_with_deleted' or self.action != 'list') and self.request.user.is_authenticated() and self.request.user.is_staff) @@ -711,16 +711,23 @@ class AddonVersionViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet): # When accessing a single version or if requesting it explicitly when # listing, reviewers and add-on authors can access all non-deleted # versions. - can_access_all_versions = ( + should_access_all_versions = ( (requested == 'all' or self.action != 'list') and (AllowReviewer().has_permission(self.request, self) or AllowAddonAuthor().has_object_permission( self.request, self, self.get_addon_object()))) - if can_access_all_versions_included_deleted: + # Everyone can see (non deleted) beta version when they request it + # explicitly. + should_access_only_beta_versions = (requested == 'beta_only') + + if should_access_all_versions_included_deleted: self.queryset = Version.unfiltered.all() - elif can_access_all_versions: + elif should_access_all_versions: self.queryset = Version.objects.all() + elif should_access_only_beta_versions: + self.queryset = Version.objects.filter( + files__status=amo.STATUS_BETA).distinct() # Now that the base queryset has been altered, call super() to use it. qs = super(AddonVersionViewSet, self).get_queryset() diff --git a/src/olympia/search/filters.py b/src/olympia/search/filters.py index 807bc0f940..310e398e2d 100644 --- a/src/olympia/search/filters.py +++ b/src/olympia/search/filters.py @@ -88,8 +88,10 @@ class AddonAppVersionFilterParam(AddonFilterParam): def get_es_filter(self): app_id, low, high = self.get_values() return [ - F('range', **{'appversion.%d.min' % app_id: {'lte': low}}), - F('range', **{'appversion.%d.max' % app_id: {'gte': high}}), + F('range', **{'current_version.compatible_apps.%d.min' % app_id: + {'lte': low}}), + F('range', **{'current_version.compatible_apps.%d.max' % app_id: + {'gte': high}}), ] diff --git a/src/olympia/search/tests/test_filters.py b/src/olympia/search/tests/test_filters.py index 9f14c0d837..32fbe2119b 100644 --- a/src/olympia/search/tests/test_filters.py +++ b/src/olympia/search/tests/test_filters.py @@ -231,8 +231,10 @@ class TestSearchParameterFilter(FilterTestsBase): 'app': 'firefox'}) must = qs['query']['filtered']['filter']['bool']['must'] assert {'term': {'app': amo.FIREFOX.id}} in must - assert {'range': {'appversion.1.min': {'lte': 46000000200100}}} in must - assert {'range': {'appversion.1.max': {'gte': 46000000000100}}} in must + assert {'range': {'current_version.compatible_apps.1.min': + {'lte': 46000000200100}}} in must + assert {'range': {'current_version.compatible_apps.1.max': + {'gte': 46000000000100}}} in must def test_search_by_platform_invalid(self): qs = self._filter(data={'platform': unicode(amo.PLATFORM_WIN.id + 42)})