Merge pull request #3128 from diox/api-expose-authors

Expose author information in add-on search and detail API
This commit is contained in:
Mathieu Pillard 2016-07-21 13:27:45 +02:00 коммит произвёл GitHub
Родитель cf7dab90f0 94ef735850
Коммит 1c273a1dc5
6 изменённых файлов: 79 добавлений и 10 удалений

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

@ -68,6 +68,10 @@ This endpoint allows you to fetch a specific add-on by id, slug or guid.
.. _addon-detail-object:
:>json int id: The add-on id on AMO.
:>json array authors: Array holding information about the authors for this add-on.
:>json int authors[].id: The id for an author.
:>json string authors[].name: The name for an author.
:>json string authors[].url: The link to the profile page for an author.
:>json object compatibility: Object detailing the add-on :ref:`add-on application <addon-detail-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.

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

@ -11,7 +11,7 @@ norecursedirs =
DJANGO_SETTINGS_MODULE = settings_test
[flake8]
ignore = F999
ignore = F999,F405
exclude =
services,
src/olympia/wsgi.py,

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

@ -36,6 +36,11 @@ class AddonIndexer(BaseSearchIndexer):
'app': {'type': 'byte'},
'appversion': {'properties': {app.id: appver
for app in amo.APP_USAGE}},
# FIXME: See issue #3120, the 'authors' property is for
# backwards-compatibility and all code should be switched
# to use 'listed_authors.name' instead. We needed a reindex
# first though, which is why the 2 are present at the
# moment.
'authors': {'type': 'string'},
'average_daily_users': {'type': 'long'},
'bayesian_rating': {'type': 'double'},
@ -74,6 +79,14 @@ class AddonIndexer(BaseSearchIndexer):
'is_disabled': {'type': 'boolean'},
'is_listed': {'type': 'boolean'},
'last_updated': {'type': 'date'},
'listed_authors': {
'type': 'object',
'properties': {
'id': {'type': 'long', 'index': 'no'},
'name': {'type': 'string'},
'username': {'type': 'string', 'index': 'no'},
},
},
'modified': {'type': 'date', 'index': 'no'},
# Adding word-delimiter to split on camelcase and
# punctuation.
@ -176,6 +189,11 @@ class AddonIndexer(BaseSearchIndexer):
'min': min_, 'min_human': min_human,
'max': max_, 'max_human': max_human,
}
# FIXME: See issue #3120, the 'authors' property is for
# backwards-compatibility and all code should be switched
# to use 'listed_authors.name' instead. We needed a reindex
# first though, which is why the 2 are present at the
# moment.
data['authors'] = [a.name for a in obj.listed_authors]
# Quadruple the boost if the add-on is public.
if obj.status == amo.STATUS_PUBLIC and 'boost' in data:
@ -203,6 +221,10 @@ class AddonIndexer(BaseSearchIndexer):
obj.current_version.supported_platforms]
else:
data['has_version'] = None
data['listed_authors'] = [
{'name': a.name, 'id': a.id, 'username': a.username}
for a in obj.listed_authors
]
# We can use all_previews because the indexing code goes through the
# transformer that sets it.

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

@ -10,6 +10,7 @@ from olympia.api.serializers import BaseESSerializer
from olympia.applications.models import AppVersion
from olympia.constants.applications import APPS_ALL
from olympia.files.models import File
from olympia.users.models import UserProfile
from olympia.versions.models import ApplicationsVersions, Version
@ -22,6 +23,17 @@ class AddonFeatureCompatibilitySerializer(serializers.ModelSerializer):
fields = ('e10s', )
class AddonAuthorSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
class Meta:
model = UserProfile
fields = ('name', 'url')
def get_url(self, obj):
return absolutify(obj.get_url_path())
class FileSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
platform = ReverseChoiceField(choices=amo.PLATFORM_CHOICES_API.items())
@ -99,6 +111,7 @@ class VersionSerializer(serializers.ModelSerializer):
class AddonSerializer(serializers.ModelSerializer):
authors = AddonAuthorSerializer(many=True, source='listed_authors')
current_version = VersionSerializer()
description = TranslationSerializerField()
edit_url = serializers.SerializerMethodField()
@ -118,11 +131,12 @@ class AddonSerializer(serializers.ModelSerializer):
class Meta:
model = Addon
fields = ('id', 'current_version', 'default_locale', 'description',
'edit_url', 'guid', 'homepage', 'icon_url', 'is_listed',
'name', 'last_updated', 'previews', 'public_stats',
'review_url', 'slug', 'status', 'summary', 'support_email',
'support_url', 'tags', 'theme_data', 'type', 'url')
fields = ('id', 'authors', 'current_version', 'default_locale',
'description', 'edit_url', 'guid', 'homepage', 'icon_url',
'is_listed', 'name', 'last_updated', 'previews',
'public_stats', 'review_url', 'slug', 'status', 'summary',
'support_email', 'support_url', 'tags', 'theme_data', 'type',
'url')
def to_representation(self, obj):
data = super(AddonSerializer, self).to_representation(obj)
@ -249,6 +263,14 @@ class ESAddonSerializer(BaseESSerializer, AddonSerializer):
obj._current_version.compatible_apps = compatible_apps
data_authors = data.get('listed_authors', [])
obj.listed_authors = [
UserProfile(
id=data_author['id'], display_name=data_author['name'],
username=data_author['username'])
for data_author in data_authors
]
# We set obj.all_previews to the raw preview data because
# ESPreviewSerializer will handle creating the fake Preview object
# for us when its to_representation() method is called.

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

@ -46,8 +46,8 @@ class TestAddonIndexer(TestCase):
complex_fields = [
'app', 'appversion', 'authors', 'boost', 'category',
'current_version', 'description', 'has_theme_rereview',
'has_version', 'name', 'name_sort', 'platforms', 'previews',
'public_stats', 'summary', 'tags',
'has_version', 'listed_authors', 'name', 'name_sort', 'platforms',
'previews', 'public_stats', 'summary', 'tags',
]
# Fields that need to be present in the mapping, but might be skipped
@ -146,6 +146,8 @@ class TestAddonIndexer(TestCase):
assert extracted['boost'] == self.addon.average_daily_users ** .2 * 4
assert extracted['category'] == [22, 23, 24] # From fixture.
assert extracted['has_theme_rereview'] is None
assert extracted['listed_authors'] == [
{'name': u'55021 التطب', 'id': 55021, 'username': '55021'}]
assert extracted['platforms'] == [PLATFORM_ALL.id]
assert extracted['tags'] == []

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

@ -6,10 +6,10 @@ from rest_framework.test import APIRequestFactory
from olympia import amo
from olympia.amo.helpers import absolutify
from olympia.amo.tests import addon_factory, ESTestCase, TestCase
from olympia.amo.tests import addon_factory, ESTestCase, TestCase, user_factory
from olympia.amo.urlresolvers import reverse
from olympia.addons.indexers import AddonIndexer
from olympia.addons.models import Addon, Persona, Preview
from olympia.addons.models import Addon, AddonUser, Persona, Preview
from olympia.addons.serializers import AddonSerializer, ESAddonSerializer
from olympia.addons.utils import generate_addon_guid
@ -39,6 +39,16 @@ class AddonSerializerOutputTestMixin(object):
support_url=u'https://support.example.org/support/my-addon/',
tags=['some_tag', 'some_other_tag'],
)
AddonUser.objects.create(user=user_factory(username='hidden_author'),
addon=self.addon, listed=False)
second_author = user_factory(
username='second_author', display_name=u'Secönd Author')
first_author = user_factory(
username='first_author', display_name=u'First Authôr')
AddonUser.objects.create(
user=second_author, addon=self.addon, position=2)
AddonUser.objects.create(
user=first_author, addon=self.addon, position=1)
second_preview = Preview.objects.create(
addon=self.addon, position=2,
caption={'en-US': u'My câption', 'fr': u'Mön tîtré'})
@ -76,6 +86,15 @@ class AddonSerializerOutputTestMixin(object):
assert result['current_version']['url'] == absolutify(
version.get_url_path())
assert result['authors']
assert len(result['authors']) == 2
assert result['authors'][0] == {
'name': first_author.name,
'url': absolutify(first_author.get_url_path())}
assert result['authors'][1] == {
'name': second_author.name,
'url': absolutify(second_author.get_url_path())}
assert result['edit_url'] == absolutify(self.addon.get_dev_url())
assert result['default_locale'] == self.addon.default_locale
assert result['description'] == {'en-US': self.addon.description}