From 86f0d7a7e2048fb465e7fe1f5529bd8c66c47924 Mon Sep 17 00:00:00 2001 From: kollivier Date: Tue, 14 May 2019 16:05:42 -0700 Subject: [PATCH] [Fixes #1299] Restore thumbnail data to public channels API. Also fix content type value for public JSON endpoints. --- .../contentcuration/serializers.py | 16 +++++++++ .../contentcuration/tests/test_public_api.py | 35 +++++++++++++++++++ .../contentcuration/views/public.py | 12 ++++--- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/contentcuration/contentcuration/serializers.py b/contentcuration/contentcuration/serializers.py index 8bd18ce2ed..2fc41607c0 100644 --- a/contentcuration/contentcuration/serializers.py +++ b/contentcuration/contentcuration/serializers.py @@ -762,11 +762,27 @@ class PublicChannelSerializer(ChannelFieldMixin, serializers.ModelSerializer): """ kind_count = serializers.SerializerMethodField('generate_kind_count') matching_tokens = serializers.SerializerMethodField('match_tokens') + icon_encoding = serializers.SerializerMethodField('get_thumbnail_encoding') def match_tokens(self, channel): tokens = json.loads(channel.tokens) if hasattr(channel, 'tokens') else [] return list(channel.secret_tokens.filter(token__in=tokens).values_list('token', flat=True)) + def get_thumbnail_encoding(self, channel): + """ + Historically, we did not set channel.icon_encoding in the Studio database. We + only set it in the exported Kolibri sqlite db. So when Kolibri asks for the channel + information, fall back to the channel thumbnail data if icon_encoding is not set. + """ + if channel.icon_encoding: + return channel.icon_encoding + elif channel.thumbnail_encoding: + base64 = channel.thumbnail_encoding.get('base64') + if base64: + return base64 + + return None + def generate_kind_count(self, channel): return channel.published_kind_count and json.loads(channel.published_kind_count) diff --git a/contentcuration/contentcuration/tests/test_public_api.py b/contentcuration/contentcuration/tests/test_public_api.py index fbca7c8be1..deb0016e13 100644 --- a/contentcuration/contentcuration/tests/test_public_api.py +++ b/contentcuration/contentcuration/tests/test_public_api.py @@ -1,5 +1,6 @@ from base import BaseAPITestCase from django.core.urlresolvers import reverse +from testdata import generated_base64encoding class PublicAPITestCase(BaseAPITestCase): @@ -11,8 +12,42 @@ class PublicAPITestCase(BaseAPITestCase): def setUp(self): super(PublicAPITestCase, self).setUp() + self.channel_list_url = reverse('get_public_channel_list', kwargs={'version': 'v1'}) def test_info_endpoint(self): + """ + Test that the public info endpoint returns the correct identifying information + about Studio. + """ response = self.client.get(reverse('info')) self.assertEqual(response.data['application'], 'studio') self.assertEqual(response.data['device_name'], 'Kolibri Studio') + + def test_empty_public_channels(self): + """ + Ensure that we get a valid, but empty, JSON response when there are no + public channels. + """ + response = self.get(self.channel_list_url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 0) + + def test_public_channels_endpoint(self): + """ + Test that the public channels endpoint returns information about + public channels and that this information is correct. + """ + self.channel.public = True + self.channel.thumbnail_encoding = {'base64': generated_base64encoding()} + self.channel.main_tree.published = True + self.channel.main_tree.save() + self.channel.save() + + response = self.client.get(self.channel_list_url) + self.assertEqual(response.status_code, 200) + + assert len(response.data) == 1 + first_channel = response.data[0] + self.assertEqual(first_channel['name'], self.channel.name) + self.assertEqual(first_channel['id'], self.channel.id) + self.assertEqual(first_channel['icon_encoding'], generated_base64encoding()) diff --git a/contentcuration/contentcuration/views/public.py b/contentcuration/contentcuration/views/public.py index 3248c7e22c..3da392cec9 100644 --- a/contentcuration/contentcuration/views/public.py +++ b/contentcuration/contentcuration/views/public.py @@ -3,7 +3,6 @@ from django.db.models import Q from django.db.models import TextField from django.db.models import Value -from django.http import HttpResponse from django.http import HttpResponseNotFound from django.utils.translation import ugettext_lazy as _ from rest_framework import viewsets @@ -59,7 +58,7 @@ def get_public_channel_list(request, version): channel_list = _get_channel_list(version, request.query_params) except LookupError: return HttpResponseNotFound(_("Api endpoint {} is not available").format(version)) - return HttpResponse(json.dumps(PublicChannelSerializer(channel_list, many=True).data)) + return Response(PublicChannelSerializer(channel_list, many=True).data) @api_view(['GET']) @@ -72,7 +71,7 @@ def get_public_channel_lookup(request, version, identifier): return HttpResponseNotFound(_("Api endpoint {} is not available").format(version)) if not channel_list.exists(): return HttpResponseNotFound(_("No channel matching {} found").format(identifier)) - return HttpResponse(json.dumps(PublicChannelSerializer(channel_list, many=True).data)) + return Response(PublicChannelSerializer(channel_list, many=True).data) @api_view(['GET']) @@ -82,7 +81,12 @@ def get_channel_name_by_id(request, channel_id): channel = Channel.objects.filter(pk=channel_id).first() if not channel: return HttpResponseNotFound('Channel with id {} not found'.format(channel_id)) - return HttpResponse(json.dumps({"name": channel.name, "description": channel.description, "version": channel.version})) + channel_info = { + "name": channel.name, + "description": channel.description, + "version": channel.version + } + return Response(channel_info) class InfoViewSet(viewsets.ViewSet):