Repository: ucfopen/canvasapi (python)

Issue:
Late Policy
**What resource needs additional coverage?**
Late Policy

**What endpoints need to be covered?**
https://canvas.instructure.com/doc/api/late_policy.html

- [ ] Get a late policy
- [ ] Create a late policy
- [ ] Patch a late policy


Pull request:
Added the create, edit and get late policy endpoints fixes #326
# Adding new endpoints

* Added get a late policy method
* Added create a late policy method
* Added patch a late policy method

Fixes #326


Patch:
diff --git a/canvasapi/course.py b/canvasapi/course.py
index a3850fd..839958a 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -358,6 +358,26 @@ class Course(CanvasObject):
         )
         return GroupCategory(self._requester, response.json())
 
+    def create_late_policy(self, **kwargs):
+        """
+        Create a late policy. If the course already has a late policy, a bad_request
+        is returned since there can only be one late policy per course.
+
+        :calls: `POST /api/v1/courses/:id/late_policy \
+        <https://canvas.instructure.com/doc/api/late_policy.html#method.late_policy.create>`_
+
+        :rtype: :class:`canvasapi.course.LatePolicy`
+        """
+
+        response = self._requester.request(
+            "POST",
+            "courses/{}/late_policy".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+        late_policy_json = response.json()
+
+        return LatePolicy(self._requester, late_policy_json["late_policy"])
+
     def create_module(self, module, **kwargs):
         """
         Create a new module.
@@ -554,6 +574,25 @@ class Course(CanvasObject):
 
         return Page(self._requester, page_json)
 
+    def edit_late_policy(self, **kwargs):
+        """
+        Patch a late policy. No body is returned upon success.
+
+        :calls: `PATCH /api/v1/courses/:id/late_policy \
+        <https://canvas.instructure.com/doc/api/late_policy.html#method.late_policy.update>`_
+
+        :returns: True if Late Policy was updated successfully. False otherwise.
+        :rtype: bool
+        """
+
+        response = self._requester.request(
+            "PATCH",
+            "courses/{}/late_policy".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        return response.status_code == 204
+
     def enroll_user(self, user, enrollment_type, **kwargs):
         """
         Create a new user enrollment for a course or a section.
@@ -1410,6 +1449,25 @@ class Course(CanvasObject):
             _kwargs=combine_kwargs(**kwargs),
         )
 
+    def get_late_policy(self, **kwargs):
+        """
+        Returns the late policy for a course.
+
+        :calls: `GET /api/v1/courses/:id/late_policy \
+        <https://canvas.instructure.com/doc/api/late_policy.html#method.late_policy.show>`_
+
+        :rtype: :class:`canvasapi.course.LatePolicy`
+        """
+
+        response = self._requester.request(
+            "GET",
+            "courses/{}/late_policy".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+        late_policy_json = response.json()
+
+        return LatePolicy(self._requester, late_policy_json["late_policy"])
+
     def get_licenses(self, **kwargs):
         """
         Returns a paginated list of the licenses that can be applied to the
@@ -2748,8 +2806,8 @@ class Course(CanvasObject):
         :calls: `PUT /api/v1/courses/:id \
         <https://canvas.instructure.com/doc/api/courses.html#method.courses.update>`_
 
-        :returns: True if the course was updated, False otherwise.
-        :rtype: bool
+        :returns: `True` if the course was updated, `False` otherwise.
+        :rtype: `bool`
         """
         response = self._requester.request(
             "PUT", "courses/{}".format(self.id), _kwargs=combine_kwargs(**kwargs)
@@ -2901,3 +2959,9 @@ class CourseNickname(CanvasObject):
             "DELETE", "users/self/course_nicknames/{}".format(self.course_id)
         )
         return CourseNickname(self._requester, response.json())
+
+
+@python_2_unicode_compatible
+class LatePolicy(CanvasObject):
+    def __str__(self):
+        return "Late Policy {}".format(self.id)
diff --git a/docs/course-ref.rst b/docs/course-ref.rst
index f2df1d3..3d43f67 100644
--- a/docs/course-ref.rst
+++ b/docs/course-ref.rst
@@ -10,4 +10,11 @@ CourseNickname
 ==============
 
 .. autoclass:: canvasapi.course.CourseNickname
-	:members:
+    :members:
+
+==========
+LatePolicy
+==========
+
+.. autoclass:: canvasapi.course.LatePolicy
+    :members:


Test patch:
diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json
index faf198b..7346d0b 100644
--- a/tests/fixtures/course.json
+++ b/tests/fixtures/course.json
@@ -2088,6 +2088,51 @@
 		},
 		"status_code": 200
 	},
+	"get_late_policy": {
+		"method": "GET",
+		"endpoint": "courses/1/late_policy",
+		"data": {
+			"late_policy": {
+				"id": 123,
+				"course_id": 123,
+				"missing_submission_deduction_enabled": true,
+				"missing_submission_deduction": 12.34,
+				"late_submission_deduction_enabled": true,
+				"late_submission_deduction": 12.34,
+				"late_submission_interval": "hour",
+				"late_submission_minimum_percent_enabled": true,
+				"late_submission_minimum_percent": 12.34,
+				"created_at": "2012-07-01T23:59:00-06:00",
+				"updated_at": "2012-07-01T23:59:00-06:00"
+			}
+		},
+		"status_code": 200
+	},
+	"edit_late_policy": {
+		"method": "PATCH",
+		"endpoint": "courses/1/late_policy",
+		"status_code": 204
+	},
+	"create_late_policy": {
+		"method": "POST",
+		"endpoint": "courses/1/late_policy",
+		"data": {
+			"late_policy": {
+				"id": 123,
+				"course_id": 123,
+				"missing_submission_deduction_enabled": true,
+				"missing_submission_deduction": 12.34,
+				"late_submission_deduction_enabled": true,
+				"late_submission_deduction": 12.34,
+				"late_submission_interval": "hour",
+				"late_submission_minimum_percent_enabled": true,
+				"late_submission_minimum_percent": 12.34,
+				"created_at": "2012-07-01T23:59:00-06:00",
+				"updated_at": "2012-07-01T23:59:00-06:00"
+			}
+		},
+		"status_code": 200
+	},
 	"get_licenses": {
 		"method": "GET",
 		"endpoint": "courses/1/content_licenses",
diff --git a/tests/test_course.py b/tests/test_course.py
index 287b35e..d78219d 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -12,7 +12,7 @@ from canvasapi import Canvas
 from canvasapi.assignment import Assignment, AssignmentGroup, AssignmentOverride
 from canvasapi.blueprint import BlueprintSubscription
 from canvasapi.blueprint import BlueprintTemplate
-from canvasapi.course import Course, CourseNickname, Page
+from canvasapi.course import Course, CourseNickname, Page, LatePolicy
 from canvasapi.discussion_topic import DiscussionTopic
 from canvasapi.gradebook_history import (
     Day,
@@ -487,7 +487,7 @@ class TestCourse(unittest.TestCase):
         self.assertTrue(hasattr(front_page, "url"))
         self.assertTrue(hasattr(front_page, "title"))
 
-    # create_front_page()
+    # edit_front_page()
     def test_edit_front_page(self, m):
         register_uris({"course": ["edit_front_page"]}, m)
 
@@ -497,6 +497,75 @@ class TestCourse(unittest.TestCase):
         self.assertTrue(hasattr(new_front_page, "url"))
         self.assertTrue(hasattr(new_front_page, "title"))
 
+    # edit_late_policy()
+    def test_edit_late_policy(self, m):
+        register_uris({"course": ["edit_late_policy"]}, m)
+
+        late_policy_result = self.course.edit_late_policy(
+            late_policy={"missing_submission_deduction": 5}
+        )
+
+        self.assertTrue(late_policy_result)
+
+    # get_late_policy
+    def test_get_late_policy(self, m):
+        register_uris({"course": ["get_late_policy"]}, m)
+
+        late_policy = self.course.get_late_policy()
+
+        self.assertIsInstance(late_policy, LatePolicy)
+
+        attributes = (
+            "id",
+            "course_id",
+            "missing_submission_deduction_enabled",
+            "missing_submission_deduction",
+            "late_submission_deduction_enabled",
+            "late_submission_deduction",
+            "late_submission_interval",
+            "late_submission_minimum_percent_enabled",
+            "late_submission_minimum_percent",
+            "created_at",
+            "updated_at",
+        )
+
+        for attribute in attributes:
+            self.assertTrue(hasattr(late_policy, attribute))
+
+    def test_create_late_policy(self, m):
+        register_uris({"course": ["create_late_policy"]}, m)
+
+        late_policy = self.course.create_late_policy(
+            late_policy={
+                "missing_submission_deduction_enabled": True,
+                "missing_submission_deduction": 12.34,
+                "late_submission_deduction_enabled": True,
+                "late_submission_deduction": 12.34,
+                "late_submission_interval": "hour",
+                "late_submission_minimum_percent_enabled": True,
+                "late_submission_minimum_percent": 12.34,
+            }
+        )
+
+        self.assertIsInstance(late_policy, LatePolicy)
+
+        attributes = (
+            "id",
+            "course_id",
+            "missing_submission_deduction_enabled",
+            "missing_submission_deduction",
+            "late_submission_deduction_enabled",
+            "late_submission_deduction",
+            "late_submission_interval",
+            "late_submission_minimum_percent_enabled",
+            "late_submission_minimum_percent",
+            "created_at",
+            "updated_at",
+        )
+
+        for attribute in attributes:
+            self.assertTrue(hasattr(late_policy, attribute))
+
     # get_page()
     def test_get_page(self, m):
         register_uris({"course": ["get_page"]}, m)
@@ -1954,3 +2023,31 @@ class TestCourseNickname(unittest.TestCase):
 
         self.assertIsInstance(deleted_nick, CourseNickname)
         self.assertTrue(hasattr(deleted_nick, "nickname"))
+
+
+@requests_mock.Mocker()
+class TestLatePolicy(unittest.TestCase):
+    def setUp(self):
+        self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)
+
+        self.late_policy = LatePolicy(
+            self.canvas._Canvas__requester,
+            {
+                "id": 123,
+                "course_id": 123,
+                "missing_submission_deduction_enabled": True,
+                "missing_submission_deduction": 12.34,
+                "late_submission_deduction_enabled": True,
+                "late_submission_deduction": 12.34,
+                "late_submission_interval": "hour",
+                "late_submission_minimum_percent_enabled": True,
+                "late_submission_minimum_percent": 12.34,
+                "created_at": "2012-07-01T23:59:00-06:00",
+                "updated_at": "2012-07-01T23:59:00-06:00",
+            },
+        )
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.late_policy)
+        self.assertIsInstance(string, str)