Implement new Discovery Pane API

This commit is contained in:
Mathieu Pillard 2016-05-19 18:45:25 +02:00 коммит произвёл Mathieu Pillard
Родитель deca4d980a
Коммит 92267fa7e0
9 изменённых файлов: 161 добавлений и 12 удалений

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

@ -0,0 +1,24 @@
=========
Discovery
=========
.. note::
These APIs are experimental and are currently being worked on. Endpoints
may change without warning.
-----------------
Discovery Content
-----------------
.. _disco-content:
This endpoint allows you to fetch content for the new Discovery Pane in
Firefox (about:addons).
.. http:get:: /api/v3/discovery/
:>json int count: The number of results for this query.
:>json array results: An array of :ref:`discovery items <discovery-item>`.
:>json string results[].heading: The heading for this item. If present, it contains the following sub-strings, that the client needs to use to format the string as it desires: ``{start_sub_heading}``, ``{end_sub_heading}`` and ``{addon_name}``.
:>json string|null results[].description: The description for this item, if any. May contain some HTML tags.
:>json object results[].addon: The :ref:`add-on <addon-detail-object>` for this item. Only a subset of fields are present: ``id``, ``current_version``, ``icon_url``, ``theme_data``, ``type`` and ``url``.

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

@ -34,6 +34,7 @@ using the API.
auth_internal
accounts
addons
discovery
internal
signing
stats

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

@ -12,6 +12,7 @@ urlpatterns = patterns(
url(r'^v3/accounts/', include('olympia.accounts.urls')),
url(r'^v3/addons/', include('olympia.addons.api_urls')),
url(r'^v3/discovery/', include('olympia.discovery.api_urls')),
url(r'^v3/internal/', include('olympia.internal_tools.urls')),
url(r'^v3/', include('olympia.signing.urls')),
url(r'^v3/statistics/', include('olympia.stats.api_urls')),

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

@ -0,0 +1,14 @@
from django.conf.urls import include, url
from rest_framework.routers import SimpleRouter
from . import views
# Because we're registering things at "/discovery/" we don't want the trailing
# slash that comes with the Router by default.
discovery = SimpleRouter(trailing_slash=False)
discovery.register(r'', views.DiscoveryViewSet, base_name='discovery')
urlpatterns = [
url(r'', include(discovery.urls)),
]

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

@ -15,45 +15,45 @@ discopane_items = [
DiscoItem(
addon_id=1865,
heading=_('Block ads {start_sub_heading}with {addon_name}'
heading=_(u'Block ads {start_sub_heading}with {addon_name}'
'{end_sub_heading}'),
description=string_concat(
'<blockquote>',
_('“This add-on is amazing. Blocks all annoying ads. 5 stars.”'),
_(u'“This add-on is amazing. Blocks all annoying ads. 5 stars.”'),
'<cite>— rany</cite>',
'</blockquote')),
'</blockquote>')),
DiscoItem(
addon_id=287841,
heading=_('Take screenshots {start_sub_heading}with {addon_name}'
heading=_(u'Take screenshots {start_sub_heading}with {addon_name}'
'{end_sub_heading}'),
description=string_concat(
'<blockquote>',
_('“The best. Very easy to use.”'),
_(u'“The best. Very easy to use.”'),
'<cite>— meetdak</cite>',
'</blockquote')),
'</blockquote>')),
DiscoItem(addon_id=111435),
DiscoItem(
addon_id=511962,
heading=_('Up your emoji game {start_sub_heading}with {addon_name}'
heading=_(u'Up your emoji game {start_sub_heading}with {addon_name}'
'{end_sub_heading}'),
description=string_concat(
'<blockquote>',
_('“Very simple interface and easy to use. Thank you.”'),
_(u'“Very simple interface and easy to use. Thank you.”'),
'<cite>— shadypurple</cite>',
'</blockquote')),
'</blockquote>')),
DiscoItem(
addon_id=3006,
heading=_('Download videos {start_sub_heading}with {addon_name}'
heading=_(u'Download videos {start_sub_heading}with {addon_name}'
'{end_sub_heading}'),
description=string_concat(
'<blockquote>',
_('“Download videos in a single click.”'),
_(u'“Download videos in a single click.”'),
'<cite>— Carpe Diem</cite>',
'</blockquote')),
'</blockquote>')),
DiscoItem(addon_id=686505),

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

@ -0,0 +1,37 @@
from rest_framework import serializers
from olympia.addons.models import Addon
from olympia.addons.serializers import AddonSerializer, VersionSerializer
from olympia.versions.models import Version
class DiscoveryVersionSerializer(VersionSerializer):
class Meta:
fields = ('compatibility', 'files',)
model = Version
class DiscoveryAddonSerializer(AddonSerializer):
current_version = DiscoveryVersionSerializer()
class Meta:
fields = ('id', 'current_version', 'icon_url',
'theme_data', 'type', 'url',)
model = Addon
class DiscoverySerializer(serializers.Serializer):
heading = serializers.CharField()
description = serializers.CharField()
addon = DiscoveryAddonSerializer()
def to_representation(self, instance):
data = super(DiscoverySerializer, self).to_representation(instance)
if data['heading'] is None:
if instance.addon.listed_authors:
data['heading'] = u'%s by %s' % (
unicode(instance.addon.name),
instance.addon.listed_authors[0].name)
else:
data['heading'] = unicode(instance.addon.name)
return data

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

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

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
from olympia import amo
from olympia.discovery.data import discopane_items
from olympia.amo.urlresolvers import reverse
from olympia.amo.tests import addon_factory, TestCase, user_factory
class TestDiscoveryViewList(TestCase):
def setUp(self):
super(TestDiscoveryViewList, self).setUp()
self.url = reverse('discovery-list')
def test_reverse(self):
assert self.url == '/api/v3/discovery/'
def test_list(self):
addons = {}
for item in discopane_items:
type_ = amo.ADDON_EXTENSION
if not item.heading and not item.description:
type_ = amo.ADDON_PERSONA
author = user_factory()
addons[item.addon_id] = addon_factory(id=item.addon_id, type=type_)
if type_ == amo.ADDON_PERSONA:
addons[item.addon_id].addonuser_set.create(user=author)
response = self.client.get(self.url)
assert response.data
assert response.data['count'] == len(discopane_items)
assert response.data['next'] is None
assert response.data['previous'] is None
assert response.data['results']
for i, item in enumerate(discopane_items):
result = response.data['results'][i]
assert result['addon']['id'] == item.addon_id
if item.heading:
assert result['heading'] == item.heading
else:
assert result['heading'] == u'%s by %s' % (
unicode(addons[item.addon_id].name),
addons[item.addon_id].listed_authors[0].name)
assert result['description'] == item.description
assert result['current_version']

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

@ -0,0 +1,27 @@
from rest_framework.mixins import ListModelMixin
from rest_framework.viewsets import GenericViewSet
from olympia.addons.models import Addon
from olympia.discovery.data import discopane_items
from olympia.discovery.serializers import DiscoverySerializer
class DiscoveryViewSet(ListModelMixin, GenericViewSet):
permission_classes = []
serializer_class = DiscoverySerializer
queryset = Addon.objects.public()
def get_queryset(self):
ids = [item.addon_id for item in discopane_items]
# FIXME: Implement using ES. It would look like something like this,
# with a specific serializer that inherits from the ES one + code to
# build the dict:
# es = amo.search.get_es()
# es.mget({'ids': ids}, index=AddonIndexer.get_index_alias(),
# doc_type=AddonIndexer.get_doctype_name())
addons = self.queryset.in_bulk(ids)
# Patch items to add addons.
for item in discopane_items:
item.addon = addons[item.addon_id]
return discopane_items