diff --git a/AUTHORS.md b/AUTHORS.md index d2ac0bfa..67b1d405 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -53,6 +53,7 @@ - Ian Altgilbers [@altgilbers](https://github.com/altgilbers) - Ian Turgeon [@iturgeon](https://github.com/iturgeon) - [@jackrsteiner](https://github.com/jackrsteiner) +- Jasmine Hou [@jsmnhou](https://github.com/jsmnhou) - John Raible [@rebelaide](https://github.com/rebelaide) - Joon Ro [@joonro](https://github.com/joonro) - Jonah Majumder [@jonahmajumder](https://github.com/jonahmajumder) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a7326f..9a2b16d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### New Endpoint Coverage + +- LTI Resource Links (Thanks, [@jsmnhou](https://github.com/jsmnhou)) + ### Backstage - Updated deploy Action to use more modern processes. diff --git a/canvasapi/course.py b/canvasapi/course.py index 523a2713..2b28f810 100644 --- a/canvasapi/course.py +++ b/canvasapi/course.py @@ -23,6 +23,7 @@ from canvasapi.grading_period import GradingPeriod from canvasapi.grading_standard import GradingStandard from canvasapi.license import License +from canvasapi.lti_resource_link import LTIResourceLink from canvasapi.module import Module from canvasapi.new_quiz import NewQuiz from canvasapi.outcome_import import OutcomeImport @@ -438,6 +439,39 @@ def create_late_policy(self, **kwargs): return LatePolicy(self._requester, late_policy_json["late_policy"]) + def create_lti_resource_link(self, url, title=None, custom=None, **kwargs): + """ + Create a new LTI resource link. + + :calls: `POST /api/v1/courses/:course_id/lti_resource_links \ + `_ + + :param url: The launch URL for the resource link. + :type url: `str` + :param title: The title of the resource link. + :type title: `str`, optional + :param custom: Custom parameters to send to the tool. + :type custom: `dict`, optional + + :rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink` + """ + + if not url: + raise RequiredFieldMissing("url is required as a str.") + + kwargs["url"] = url + if title: + kwargs["title"] = title + if custom: + kwargs["custom"] = custom + + response = self._requester.request( + "POST", + f"courses/{self.id}/lti_resource_links", + _kwargs=combine_kwargs(**kwargs), + ) + return LTIResourceLink(self._requester, response.json()) + def create_module(self, module, **kwargs): """ Create a new module. @@ -1645,6 +1679,49 @@ def get_licenses(self, **kwargs): _kwargs=combine_kwargs(**kwargs), ) + def get_lti_resource_link(self, lti_resource_link, **kwargs): + """ + Return details about the specified resource link. + + :calls: `GET /api/v1/courses/:course_id/lti_resource_links/:id \ + `_ + + :param lti_resource_link: The object or ID of the LTI resource link. + :type lti_resource_link: :class:`canvasapi.lti_resource_link.LTIResourceLink` or int + + :rtype: :class:`canvasapi.lti_resource_link.LTIResourceLink` + """ + + lti_resource_link_id = obj_or_id( + lti_resource_link, "lti_resource_link", (LTIResourceLink,) + ) + + response = self._requester.request( + "GET", + f"courses/{self.id}/lti_resource_links/{lti_resource_link_id}", + _kwargs=combine_kwargs(**kwargs), + ) + return LTIResourceLink(self._requester, response.json()) + + def get_lti_resource_links(self, **kwargs): + """ + Returns all LTI resource links for this course as a PaginatedList. + + :calls: `GET /api/v1/courses/:course_id/lti_resource_links \ + `_ + + :rtype: :class:`canvasapi.paginated_list.PaginatedList` of + :class:`canvasapi.lti_resource_link.LTIResourceLink` + """ + + return PaginatedList( + LTIResourceLink, + self._requester, + "GET", + f"courses/{self.id}/lti_resource_links", + kwargs=combine_kwargs(**kwargs), + ) + def get_migration_systems(self, **kwargs): """ Return a list of migration systems. diff --git a/canvasapi/lti_resource_link.py b/canvasapi/lti_resource_link.py new file mode 100644 index 00000000..d951569e --- /dev/null +++ b/canvasapi/lti_resource_link.py @@ -0,0 +1,6 @@ +from canvasapi.canvas_object import CanvasObject + + +class LTIResourceLink(CanvasObject): + def __str__(self): + return "{} ({})".format(self.url, self.title) diff --git a/docs/class-reference.rst b/docs/class-reference.rst index 7420abff..e73ade2c 100644 --- a/docs/class-reference.rst +++ b/docs/class-reference.rst @@ -41,6 +41,7 @@ Class Reference jwt-ref login-ref license-ref + lti-resource-link-ref module-ref outcome-ref outcome-import-ref diff --git a/docs/lti-resource-link-ref.rst b/docs/lti-resource-link-ref.rst new file mode 100644 index 00000000..b14ff90e --- /dev/null +++ b/docs/lti-resource-link-ref.rst @@ -0,0 +1,6 @@ +=============== +LTIResourceLink +=============== + +.. autoclass:: canvasapi.lti_resource_link.LTIResourceLink + :members: diff --git a/tests/fixtures/lti_resource_link.json b/tests/fixtures/lti_resource_link.json new file mode 100644 index 00000000..2de13515 --- /dev/null +++ b/tests/fixtures/lti_resource_link.json @@ -0,0 +1,49 @@ +{ + "create_lti_resource_link": { + "method": "POST", + "endpoint": "courses/1/lti_resource_links", + "data": { + "id": 45, + "context_id": 1, + "context_type": "Course", + "context_external_tool_id": 1, + "resource_type": "assignment", + "canvas_launch_url": "https://example.instructure.com/courses/1/external_tools/retrieve?resource_link_lookup_uuid=ae43ba23-d238-49bc-ab55-ba7f79f77896", + "resource_link_uuid": "ae43ba23-d238-49bc-ab55-ba7f79f77896", + "lookup_uuid": "c522554a-d4be-49ef-b163-9c87fdc6ad6f", + "title": "Test LTI Resource Link", + "url": "https://example.com/lti/launch/content_item/123" + }, + "status_code": 200 + }, + + "get_lti_resource_link": { + "method": "GET", + "endpoint": "courses/1/lti_resource_links/45", + "data": { + "id": 45, + "title": "Test LTI Resource Link", + "url": "https://example.com/lti/launch/content_item/123" + }, + "status_code": 200 + }, + "list_lti_resource_links": { + "method": "GET", + "endpoint": "courses/1/lti_resource_links", + "data": [ + { + "id": 45, + "title": "Test LTI Resource Link" + }, + { + "id": 56, + "title": "Test LTI Resource Link 2" + }, + { + "id": 67, + "title": "Test LTI Resource Link 3" + } + ], + "status_code": 200 + } +} diff --git a/tests/test_course.py b/tests/test_course.py index 2e2669dd..f21eb9fe 100644 --- a/tests/test_course.py +++ b/tests/test_course.py @@ -35,6 +35,7 @@ from canvasapi.grading_standard import GradingStandard from canvasapi.group import Group, GroupCategory from canvasapi.license import License +from canvasapi.lti_resource_link import LTIResourceLink from canvasapi.module import Module from canvasapi.new_quiz import NewQuiz from canvasapi.outcome import OutcomeGroup, OutcomeLink, OutcomeResult @@ -1890,6 +1891,48 @@ def test_resolve_path_null(self, m): self.assertIsInstance(root_folder_list[0], Folder) self.assertEqual("course_files", root_folder_list[0].name) + # create_lti_resource_link() + def test_create_lti_resource_link(self, m): + register_uris({"lti_resource_link": ["create_lti_resource_link"]}, m) + custom_dict = {"hello": "world"} + + evnt = self.course.create_lti_resource_link( + url="https://example.com/lti/launch/content_item/123", + title="Test LTI Resource Link", + custom=custom_dict, + ) + self.assertIsInstance(evnt, LTIResourceLink) + + self.assertEqual(evnt.title, "Test LTI Resource Link") + self.assertEqual(evnt.url, "https://example.com/lti/launch/content_item/123") + + def test_create_lti_resource_link_fail(self, m): + with self.assertRaises(RequiredFieldMissing): + self.course.create_lti_resource_link({}) + + # get_lti_resource_links() + def test_get_lti_resource_links(self, m): + register_uris({"lti_resource_link": ["list_lti_resource_links"]}, m) + + lti_resource_links = self.course.get_lti_resource_links() + lti_resource_link_list = [link for link in lti_resource_links] + self.assertEqual(len(lti_resource_link_list), 3) + self.assertIsInstance(lti_resource_link_list[0], LTIResourceLink) + + # get_lti_resource_link() + def test_get_lti_resource_link(self, m): + register_uris({"lti_resource_link": ["get_lti_resource_link"]}, m) + + lti_resource_link_by_id = self.course.get_lti_resource_link(45) + + self.assertIsInstance(lti_resource_link_by_id, LTIResourceLink) + self.assertEqual(lti_resource_link_by_id.title, "Test LTI Resource Link") + lti_resource_link_by_obj = self.course.get_lti_resource_link( + lti_resource_link_by_id + ) + self.assertIsInstance(lti_resource_link_by_obj, LTIResourceLink) + self.assertEqual(lti_resource_link_by_obj.title, "Test LTI Resource Link") + @requests_mock.Mocker() class TestCourseNickname(unittest.TestCase): diff --git a/tests/test_lti_resource_link.py b/tests/test_lti_resource_link.py new file mode 100644 index 00000000..f1b58901 --- /dev/null +++ b/tests/test_lti_resource_link.py @@ -0,0 +1,30 @@ +import unittest + +import requests_mock + +from canvasapi import Canvas +from tests import settings +from tests.util import register_uris + + +@requests_mock.Mocker() +class TestLTIResourceLink(unittest.TestCase): + def setUp(self): + self.canvas = Canvas(settings.BASE_URL, settings.API_KEY) + + with requests_mock.Mocker() as m: + register_uris( + { + "course": ["get_by_id"], + "lti_resource_link": ["get_lti_resource_link"], + }, + m, + ) + + self.course = self.canvas.get_course(1) + self.resource_link = self.course.get_lti_resource_link(45) + + # __str__() + def test__str__(self, m): + string = str(self.resource_link) + self.assertIsInstance(string, str)