diff --git a/contentcuration/contentcuration/tests/test_exportchannel.py b/contentcuration/contentcuration/tests/test_exportchannel.py index 57599c0942..95d75e7886 100644 --- a/contentcuration/contentcuration/tests/test_exportchannel.py +++ b/contentcuration/contentcuration/tests/test_exportchannel.py @@ -692,14 +692,77 @@ def test_nonexistent_prerequisites(self): class ChannelExportPublishedData(StudioTestCase): - def test_fill_published_fields(self): + def setUp(self): + super().setUpBase() + + self.license1 = cc.License.objects.create( + id=100, + license_name="License 1", + license_description="Description 1", + license_url="http://example.com/license1", + ) + self.license2 = cc.License.objects.create( + id=101, + license_name="License 2", + license_description="Description 2", + license_url="http://example.com/license2", + ) + + self.category1 = "Category 1" + self.category2 = "Category 2" + + self.node1 = self.channel.main_tree.get_root() + self.node2 = self.channel.main_tree.get_children().first() + self.node3 = self.node2.get_children().first() + self.node4 = self.node3.get_next_sibling() + + self.node1.license = self.license1 + self.node1.categories = {self.category1: True} + self.node1.published = True + self.node1.save() + + self.node2.license = self.license2 + self.node2.categories = {self.category2: True} + self.node2.published = True + self.node2.save() + + self.node3.license = self.license1 + self.node3.categories = {self.category1: True} + self.node3.published = True + self.node3.save() + + self.node4.license = None + self.node4.categories = None + self.node4.published = True + self.node4.save() + + def test_fill_published_fields__version_notes(self): version_notes = description() - channel = cc.Channel.objects.create(actor_id=self.admin_user.id) - channel.last_published - fill_published_fields(channel, version_notes) - self.assertTrue(channel.published_data) - self.assertIsNotNone(channel.published_data.get(0)) - self.assertEqual(channel.published_data[0]["version_notes"], version_notes) + self.channel.last_published + fill_published_fields(self.channel, version_notes) + self.assertTrue(self.channel.published_data) + self.assertIsNotNone(self.channel.published_data.get(0)) + self.assertEqual(self.channel.published_data[0]["version_notes"], version_notes) + + def test_fill_published_fields__included_licenses(self): + version_notes = description() + fill_published_fields(self.channel, version_notes) + + returned_license_ids = self.channel.published_data[0]["included_licenses"] + expected_license_ids = [self.license1.id, self.license2.id] + + self.assertEqual(len(returned_license_ids), len(expected_license_ids)) + self.assertSetEqual(set(returned_license_ids), set(expected_license_ids)) + + def test_fill_published_fields__included_categories(self): + version_notes = description() + fill_published_fields(self.channel, version_notes) + + expected_categories = [self.category1, self.category2] + returned_categories = self.channel.published_data[0]["included_categories"] + + self.assertEqual(len(returned_categories), len(expected_categories)) + self.assertSetEqual(set(returned_categories), set(expected_categories)) class PublishFailCleansUpTaskObjects(StudioTestCase): diff --git a/contentcuration/contentcuration/tests/viewsets/test_channel.py b/contentcuration/contentcuration/tests/viewsets/test_channel.py index 8309f47c8c..067e442ede 100644 --- a/contentcuration/contentcuration/tests/viewsets/test_channel.py +++ b/contentcuration/contentcuration/tests/viewsets/test_channel.py @@ -940,3 +940,49 @@ def _perform_action(self, url_path, channel_id): reverse(url_path, kwargs={"pk": channel_id}), format="json" ) return response + + +class GetPublishedDataTestCase(StudioAPITestCase): + def setUp(self): + super().setUp() + + self.editor_user = testdata.user(email="editor@user.com") + self.forbidden_user = testdata.user(email="forbidden@user.com") + + self.channel = testdata.channel() + self.channel.editors.add(self.editor_user) + + self.channel.published_data = { + "key1": "value1", + "key2": "value2", + } + self.channel.save() + + def test_get_published_data__is_editor(self): + self.client.force_authenticate(user=self.editor_user) + + response = self.client.get( + reverse("channel-published-data", kwargs={"pk": self.channel.id}), + format="json", + ) + self.assertEqual(response.status_code, 200, response.content) + self.assertEqual(response.json(), self.channel.published_data) + + def test_get_published_data__is_admin(self): + self.client.force_authenticate(user=self.admin_user) + + response = self.client.get( + reverse("channel-published-data", kwargs={"pk": self.channel.id}), + format="json", + ) + self.assertEqual(response.status_code, 200, response.content) + self.assertEqual(response.json(), self.channel.published_data) + + def test_get_published_data__is_forbidden_user(self): + self.client.force_authenticate(user=self.forbidden_user) + + response = self.client.get( + reverse("channel-published-data", kwargs={"pk": self.channel.id}), + format="json", + ) + self.assertEqual(response.status_code, 404, response.content) diff --git a/contentcuration/contentcuration/utils/publish.py b/contentcuration/contentcuration/utils/publish.py index 901f9aaa32..841f440134 100644 --- a/contentcuration/contentcuration/utils/publish.py +++ b/contentcuration/contentcuration/utils/publish.py @@ -1184,6 +1184,25 @@ def fill_published_fields(channel, version_notes): if lang: channel.included_languages.add(lang) + included_licenses = published_nodes.exclude(license=None).values_list( + "license", flat=True + ) + license_list = sorted(set(included_licenses)) + + included_categories_dicts = published_nodes.exclude(categories=None).values_list( + "categories", flat=True + ) + category_list = sorted( + set( + chain.from_iterable( + ( + node_categories_dict.keys() + for node_categories_dict in included_categories_dicts + ) + ) + ) + ) + # TODO: Eventually, consolidate above operations to just use this field for storing historical data channel.published_data.update( { @@ -1196,6 +1215,8 @@ def fill_published_fields(channel, version_notes): ), "version_notes": version_notes, "included_languages": language_list, + "included_licenses": license_list, + "included_categories": category_list, } } ) diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index c2462836d1..1fd6625030 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -814,6 +814,27 @@ def get_languages_in_channel( langs_in_content = self._get_channel_content_languages(pk, main_tree_id) return JsonResponse({"languages": langs_in_content}) + @action( + detail=True, + methods=["get"], + url_path="published_data", + url_name="published-data", + ) + def get_published_data(self, request, pk=None) -> Response: + """ + Get the published data for a channel. + + :param request: The request object + :param pk: The ID of the channel + :return: Response with the published data of the channel + :rtype: Response + """ + # Allow exactly users with permission to edit the channel to + # access the published data. + channel = self.get_edit_object() + + return Response(channel.published_data) + def _channel_exists(self, channel_id) -> bool: """ Check if a channel exists.