promoted addon search api filter (#15324)

* promoted addon search api filter

* test all the groups; limit param values to enabled groups
This commit is contained in:
Andrew Williamson 2020-08-18 17:39:01 +01:00 коммит произвёл GitHub
Родитель 8d71c53b1b
Коммит b1fa006974
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 111 добавлений и 2 удалений

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

@ -33,6 +33,7 @@ This endpoint allows you to search through public add-ons.
: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.
:query string platform: Filter by :ref:`add-on platform <addon-detail-platform>` availability.
:query string promoted: Filter to add-ons in a specific :ref:`promoted category <addon-detail-promoted-category>`. Can be combined with `app`.
:query boolean recommended: Filter to only add-ons recommended by Mozilla. Only ``recommended=true`` is supported.
:query string tag: Filter by exact tag name. Multiple tag names can be specified, separated by comma(s), in which case add-ons containing *all* specified tags are returned.
:query string type: Filter by :ref:`add-on type <addon-detail-type>`. Multiple types can be specified, separated by comma(s), in which case add-ons that are any of the matching types are returned.
@ -274,7 +275,7 @@ This endpoint allows you to fetch a specific add-on by id, slug or guid.
============== ==========================================================
Value Description
============== ==========================================================
line Line category
line "By Firefox" category
recommended Recommended category
sponsored Sponsored category
spotlight Spotlight category

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

@ -373,6 +373,7 @@ v4 API changelog
* 2020-03-19: added /blocklist/block endpoint to expose add-on blocks https://github.com/mozilla/addons-server/issues/13706.
* 2020-03-26: added ``addon_name`` to blocklist/block api https://github.com/mozilla/addons-server/issues/13757
* 2020-08-13: added ``applications`` internal API to create new application versions https://github.com/mozilla/addons-server/issues/14649
* 2020-09-03: added ``promoted`` filter to addons search api https://github.com/mozilla/addons-server/issues/15272.
.. _`#11380`: https://github.com/mozilla/addons-server/issues/11380/
.. _`#11379`: https://github.com/mozilla/addons-server/issues/11379/

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

@ -26,7 +26,8 @@ from olympia.amo.tests import (
from olympia.amo.urlresolvers import get_outgoing_url, reverse
from olympia.bandwagon.models import CollectionAddon
from olympia.constants.categories import CATEGORIES, CATEGORIES_BY_ID
from olympia.constants.promoted import RECOMMENDED
from olympia.constants.promoted import (
LINE, SPOTLIGHT, STRATEGIC, RECOMMENDED, VERIFIED_ONE, VERIFIED_TWO)
from olympia.discovery.models import DiscoveryItem
from olympia.users.models import UserProfile
from olympia.versions.models import ApplicationsVersions, AppVersion
@ -1236,6 +1237,62 @@ class TestAddonSearchView(ESTestCase):
assert len(data['results']) == 1
assert data['results'][0]['id'] == addon.pk
def test_filter_by_promoted(self):
av_min, _ = AppVersion.objects.get_or_create(
application=amo.ANDROID.id, version='59.0.0')
av_max, _ = AppVersion.objects.get_or_create(
application=amo.ANDROID.id, version='60.0.0')
addon = addon_factory(name='Recomménded Addôn')
ApplicationsVersions.objects.get_or_create(
application=amo.ANDROID.id, version=addon.current_version,
min=av_min, max=av_max)
self.make_addon_promoted(addon, RECOMMENDED, approve_version=True)
addon2 = addon_factory(name='Fírefox Addôn')
ApplicationsVersions.objects.get_or_create(
application=amo.ANDROID.id, version=addon2.current_version,
min=av_min, max=av_max)
self.make_addon_promoted(addon2, RECOMMENDED, approve_version=True)
addon2.promotedaddon.update(application_id=amo.FIREFOX.id)
addon3 = addon_factory(slug='other-addon', name=u'Other Addôn')
ApplicationsVersions.objects.get_or_create(
application=amo.ANDROID.id, version=addon3.current_version,
min=av_min, max=av_max)
self.reindex(Addon)
data = self.perform_search(
self.url, {'promoted': 'recommended'})
assert data['count'] == 2
assert len(data['results']) == 2
assert {res['id'] for res in data['results']} == {addon.pk, addon2.pk}
# And with app filtering too
data = self.perform_search(
self.url, {'promoted': 'recommended', 'app': 'firefox'})
assert data['count'] == 2
assert len(data['results']) == 2
assert {res['id'] for res in data['results']} == {addon.pk, addon2.pk}
# That will filter out for a different app
data = self.perform_search(
self.url, {'promoted': 'recommended', 'app': 'android'})
assert data['count'] == 1
assert len(data['results']) == 1
assert data['results'][0]['id'] == addon.pk
# addon2 was for Firefox only
# test with other other promotions
for promo in (VERIFIED_ONE, VERIFIED_TWO, LINE, SPOTLIGHT, STRATEGIC):
self.make_addon_promoted(addon, promo, approve_version=True)
self.reindex(Addon)
data = self.perform_search(
self.url, {'promoted': promo.api_name, 'app': 'firefox'})
assert data['count'] == 1
assert len(data['results']) == 1
assert data['results'][0]['id'] == addon.pk
def test_filter_by_platform(self):
# First add-on is available for all platforms.
addon = addon_factory(slug='my-addon', name=u'My Addôn',

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

@ -114,3 +114,4 @@ PROMOTED_GROUPS = [
PRE_REVIEW_GROUPS = [group for group in PROMOTED_GROUPS if group.pre_review]
PROMOTED_GROUPS_BY_ID = {p.id: p for p in PROMOTED_GROUPS}
ENABLED_PROMOTED_GROUPS_BY_ID = {p.id: p for p in PROMOTED_GROUPS if p}

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

@ -14,6 +14,7 @@ from waffle import switch_is_active
from olympia import amo
from olympia.api.utils import is_gate_active
from olympia.constants.categories import CATEGORIES, CATEGORIES_BY_ID
from olympia.constants.promoted import ENABLED_PROMOTED_GROUPS_BY_ID
from olympia.discovery.models import DiscoveryItem
from olympia.versions.compare import version_int
@ -335,6 +336,32 @@ class AddonRecommendedQueryParam(AddonQueryParam):
es_field = 'is_recommended'
class AddonPromotedQueryParam(AddonQueryParam):
query_param = 'promoted'
reverse_dict = {
group.api_name: id_
for (id_, group) in ENABLED_PROMOTED_GROUPS_BY_ID.items()}
valid_values = ENABLED_PROMOTED_GROUPS_BY_ID.keys()
def get_app(self):
return (
AddonAppQueryParam(self.request).get_value()
if AddonAppQueryParam.query_param in self.request.GET
else None)
def get_es_query(self):
query = [Q(
self.operator,
**{'promoted.group_id': self.get_value()})]
if app := self.get_app():
query.append(
Q(self.operator, **{'promoted.application_id': app}) |
~Q('exists', field='promoted.application_id'))
return query
class AddonColorQueryParam(AddonQueryParam):
query_param = 'color'
@ -781,6 +808,7 @@ class SearchParameterFilter(BaseFilterBackend):
AddonFeaturedQueryParam,
AddonGuidQueryParam,
AddonPlatformQueryParam,
AddonPromotedQueryParam,
AddonRecommendedQueryParam,
AddonTagQueryParam,
AddonTypeQueryParam,

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

@ -13,6 +13,7 @@ from rest_framework import serializers
from olympia import amo
from olympia.amo.tests import TestCase
from olympia.constants.categories import CATEGORIES
from olympia.constants.promoted import ENABLED_PROMOTED_GROUPS_BY_ID
from olympia.search.filters import (
ReviewedContentFilter, SearchParameterFilter, SearchQueryFilter,
SortingFilter)
@ -907,6 +908,26 @@ class TestSearchParameterFilter(FilterTestsBase):
self._filter(data={'recommended': 'false'})
assert context.exception.detail == ['Invalid "recommended" parameter.']
def test_search_by_promoted(self):
with self.assertRaises(serializers.ValidationError) as context:
self._filter(data={'promoted': 'foo'})
assert context.exception.detail == ['Invalid "promoted" parameter.']
for promo in ENABLED_PROMOTED_GROUPS_BY_ID.values():
qs = self._filter(data={'promoted': promo.api_name})
filter_ = qs['query']['bool']['filter']
assert [{'term': {'promoted.group_id': promo.id}}] == filter_
qs = self._filter(
data={'promoted': promo.api_name, 'app': 'firefox'})
filter_ = qs['query']['bool']['filter']
assert {'term': {'promoted.group_id': promo.id}} in filter_
app_filter = filter_[-1]['bool']['should']
assert {'term': {'promoted.application_id': amo.FIREFOX.id}} in (
app_filter)
assert {'bool': {'must_not': [{'exists': {
'field': 'promoted.application_id'}}]}} in app_filter
def test_search_by_color(self):
qs = self._filter(data={'color': 'ff0000'})
filter_ = qs['query']['bool']['filter']