Repository: ucfopen/canvasapi

Issue:
Implement course TODO items
**What resource needs additional coverage?**
Courses

**What endpoints need to be covered?**
Get course TODO items
[Canvas API: Courses](https://canvas.instructure.com/doc/api/courses.html#method.courses.todo_items)


Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/34084501/badge)](https://coveralls.io/builds/34084501)

Coverage remained the same at 100.0% when pulling **482d989863d75dd13b7dca8f13c4d4b201200430 on onomou:issue/425-course-todo-items** into **ec88f10a1f0f4a2f02fba0dc6055c71c6f720700 on ucfopen:develop**.

jessemcbride: We recently got nipped by [a bug with a Course Analytics endpoint that returned JSON](https://github.com/ucfopen/canvasapi/issues/429) instead of a PaginatedList. While I hope Canvas TODOs won't exceed a hundred elements, I don't know if we can guarantee that after looking at the API docs (https://canvas.instructure.com/doc/api/users.html#method.users.todo_items returns a paginated list).

Can we update this to include a new `CanvasObject` type for `TodoItem`, returning results wrapped in a `PaginatedList` to make sure pagination is handled correctly in case there ever are more than a hundred elements?
onomou: I hope I wrapped `course.get_todo_items()` in a `PaginatedList` correctly. It seems to work on my computer, at least. This will also need to be done with `canvas.get_todo_items()`.
Thetwam: Looks good, @onomou! Thanks for the contribution! 😁 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 124daaa..c52c9d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@
 
 - Added missing documentation for the get_current_user method and clarifications to the CurrentUser class. (Thanks, [@Xx-Ashutosh-xX](https://github.com/Xx-Ashutosh-xX))
 
+### New Endpoint Coverage
+
+- Course TODO items (Thanks, [@onomou](https://github.com/onomou))
+
 ## [2.0.0] - 2020-08-14
 
 ### General
diff --git a/canvasapi/course.py b/canvasapi/course.py
index 374ed77..46fecde 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -25,6 +25,7 @@ from canvasapi.quiz import QuizExtension
 from canvasapi.tab import Tab
 from canvasapi.rubric import RubricAssociation, Rubric
 from canvasapi.submission import GroupedSubmission, Submission
+from canvasapi.todo import Todo
 from canvasapi.upload import Uploader
 from canvasapi.usage_rights import UsageRights
 from canvasapi.util import (
@@ -2087,6 +2088,25 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
+    def get_todo_items(self, **kwargs):
+        """
+        Returns the current user's course-specific todo items.
+
+        :calls: `GET /api/v1/courses/:course_id/todo \
+        <https://canvas.instructure.com/doc/api/courses.html#method.courses.todo_items>`_
+
+        :rtype: :class:`canvasapi.paginated_list.PaginatedList` of
+            :class:`canvasapi.todo.Todo`
+        """
+
+        return PaginatedList(
+            Todo,
+            self._requester,
+            "GET",
+            "courses/{}/todo".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
     def get_uncollated_submissions(self, **kwargs):
         """
         Gives a paginated, uncollated list of submission versions for all matching
diff --git a/canvasapi/todo.py b/canvasapi/todo.py
new file mode 100644
index 0000000..d20d384
--- /dev/null
+++ b/canvasapi/todo.py
@@ -0,0 +1,6 @@
+from canvasapi.canvas_object import CanvasObject
+
+
+class Todo(CanvasObject):
+    def __str__(self):
+        return "Todo Item ({})".format(self.type)
diff --git a/docs/class-reference.rst b/docs/class-reference.rst
index e95326d..87a46b2 100644
--- a/docs/class-reference.rst
+++ b/docs/class-reference.rst
@@ -54,6 +54,7 @@ Class Reference
     sis-import-ref
     submission-ref
     tab-ref
+    todo-ref
     upload-ref
     user-ref
     usage-rights-ref
diff --git a/docs/todo-ref.rst b/docs/todo-ref.rst
new file mode 100644
index 0000000..b55351c
--- /dev/null
+++ b/docs/todo-ref.rst
@@ -0,0 +1,6 @@
+====
+Todo
+====
+
+.. autoclass:: canvasapi.todo.Todo
+    :members:


Test patch:
diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json
index 8242170..32db324 100644
--- a/tests/fixtures/course.json
+++ b/tests/fixtures/course.json
@@ -641,6 +641,32 @@
             }
         ]
     },
+	"todo_items": {
+		"method": "GET",
+		"endpoint": "courses/1/todo",
+		"data": [
+			{
+				"type": "grading",
+				"assignment": {},
+				"ignore": ".. url ..",
+				"ignore_permanently": ".. url ..",
+				"html_url": ".. url ..",
+				"needs_grading_count": 3,
+				"context_type": "course",
+				"course_id": 1,
+				"group_id": null
+			},
+			{
+				"type": "submitting",
+				"assignment": {},
+				"ignore": ".. url ..",
+				"ignore_permanently": ".. url ..",
+				"html_url": ".. url ..",
+				"context_type": "course",
+				"course_id": 1
+			}
+		]
+	},
 	"get_uncollated_submissions": {
 		"method": "GET",
 		"endpoint": "courses/1/gradebook_history/feed",
diff --git a/tests/test_course.py b/tests/test_course.py
index 01ed1ea..898f515 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -39,6 +39,7 @@ from canvasapi.rubric import Rubric, RubricAssociation
 from canvasapi.section import Section
 from canvasapi.submission import GroupedSubmission, Submission
 from canvasapi.tab import Tab
+from canvasapi.todo import Todo
 from canvasapi.user import User
 from canvasapi.usage_rights import UsageRights
 from canvasapi.content_migration import ContentMigration, Migrator
@@ -1141,6 +1142,15 @@ class TestCourse(unittest.TestCase):
         self.assertEqual(len(tab_list), 2)
         self.assertIsInstance(tab_list[0], Tab)
 
+    # get_todo_items()
+    def test_get_todo_items(self, m):
+        register_uris({"course": ["todo_items"]}, m)
+
+        todo_items = self.course.get_todo_items()
+        todo_list = [todo for todo in todo_items]
+
+        self.assertIsInstance(todo_list[0], Todo)
+
     # get_rubric
     def test_get_rubric(self, m):
         register_uris({"course": ["get_rubric_single"]}, m)
diff --git a/tests/test_todo.py b/tests/test_todo.py
new file mode 100644
index 0000000..f72e81a
--- /dev/null
+++ b/tests/test_todo.py
@@ -0,0 +1,33 @@
+import unittest
+
+import requests_mock
+
+from canvasapi import Canvas
+from canvasapi.todo import Todo
+from tests import settings
+
+
+@requests_mock.Mocker()
+class TestTodo(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        self.todo = Todo(
+            self.canvas._Canvas__requester,
+            {
+                "type": "grading",
+                "assignment": {},
+                "ignore": ".. url ..",
+                "ignore_permanently": ".. url ..",
+                "html_url": ".. url ..",
+                "needs_grading_count": 3,
+                "context_type": "course",
+                "course_id": 1,
+                "group_id": None,
+            },
+        )
+
+    def test_str(self, m):
+        test_str = str(self.todo)
+        self.assertIsInstance(test_str, str)
+        self.assertEqual(test_str, "Todo Item (grading)")