Implement new Discovery Pane API
This commit is contained in:
Родитель
deca4d980a
Коммит
92267fa7e0
|
@ -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
|
Загрузка…
Ссылка в новой задаче