Repository: ucfopen/canvasapi

Issue:
Create Rubric
Add coverage for "Create a single rubric" endpoint:
https://canvas.instructure.com/doc/api/rubrics.html#method.rubrics.create

Discussion:

Patch:
diff --git a/canvasapi/course.py b/canvasapi/course.py
index fccd687..03a0401 100644
--- a/canvasapi/course.py
+++ b/canvasapi/course.py
@@ -18,6 +18,7 @@ from canvasapi.paginated_list import PaginatedList
 from canvasapi.progress import Progress
 from canvasapi.quiz import QuizExtension
 from canvasapi.tab import Tab
+from canvasapi.rubric import RubricAssociation, Rubric
 from canvasapi.submission import GroupedSubmission, Submission
 from canvasapi.upload import Uploader
 from canvasapi.util import (
@@ -27,7 +28,6 @@ from canvasapi.util import (
     obj_or_id,
     normalize_bool,
 )
-from canvasapi.rubric import Rubric
 
 
 @python_2_unicode_compatible
@@ -430,6 +430,40 @@ class Course(CanvasObject):
 
         return Quiz(self._requester, quiz_json)
 
+    def create_rubric(self, **kwargs):
+        """
+        Create a new rubric.
+
+        :calls: `POST /api/v1/courses/:course_id/rubrics \
+        <https://canvas.instructure.com/doc/api/rubrics.html#method.rubrics.create>`_
+
+        :returns: Returns a dictionary with rubric and rubric association.
+        :rtype: `dict`
+        """
+        response = self._requester.request(
+            "POST",
+            "courses/{}/rubrics".format(self.id),
+            _kwargs=combine_kwargs(**kwargs),
+        )
+
+        dictionary = response.json()
+
+        rubric_dict = {}
+
+        if "rubric" in dictionary:
+            r_dict = dictionary["rubric"]
+            rubric = Rubric(self._requester, r_dict)
+
+            rubric_dict = {"rubric": rubric}
+
+        if "rubric_association" in dictionary:
+            ra_dict = dictionary["rubric_association"]
+            rubric_association = RubricAssociation(self._requester, ra_dict)
+
+            rubric_dict.update({"rubric_association": rubric_association})
+
+        return rubric_dict
+
     def delete(self):
         """
         Permanently delete this course.
diff --git a/canvasapi/rubric.py b/canvasapi/rubric.py
index 4ff44ab..4b58436 100644
--- a/canvasapi/rubric.py
+++ b/canvasapi/rubric.py
@@ -9,3 +9,9 @@ from canvasapi.canvas_object import CanvasObject
 class Rubric(CanvasObject):
     def __str__(self):
         return "{} ({})".format(self.title, self.id)
+
+
+@python_2_unicode_compatible
+class RubricAssociation(CanvasObject):
+    def __str__(self):
+        return "{}, {}".format(self.id, self.association_type)
diff --git a/docs/rubric-ref.rst b/docs/rubric-ref.rst
index 8753332..b02aaa2 100644
--- a/docs/rubric-ref.rst
+++ b/docs/rubric-ref.rst
@@ -4,3 +4,10 @@ Rubric
 
 .. autoclass:: canvasapi.rubric.Rubric
     :members:
+
+==================
+Rubric Association
+==================
+
+.. autoclass:: canvasapi.rubric.RubricAssociation
+    :members:


Test patch:
diff --git a/tests/fixtures/course.json b/tests/fixtures/course.json
index 8b0df0d..c6595e9 100644
--- a/tests/fixtures/course.json
+++ b/tests/fixtures/course.json
@@ -1759,5 +1759,49 @@
 			"export_type": "d"
 		},
 		"status_code": 200
+	},
+	"create_rubric": {
+		"method": "POST",
+		"endpoint": "courses/1/rubrics",
+		"data": {
+			"rubric": {
+				"id": 1,
+				"title": "Course Rubric 1",
+				"context_id": 1,
+				"context_type": "Course",
+				"points_possible": 10.0,
+				"reusable": false,
+				"read_only": true,
+				"free_form_critereon_comments": true,
+				"hide_score_total": true
+			}
+		},
+		"status_code": 200
+	},
+	"create_rubric_with_association": {
+		"method": "POST",
+		"endpoint": "courses/1/rubrics",
+		"data": {
+			"rubric": {
+				"id": 1,
+				"title": "Course Rubric 1",
+				"context_id": 1,
+				"context_type": "Course",
+				"points_possible": 10.0,
+				"reusable": false,
+				"read_only": true,
+				"free_form_critereon_comments": true,
+				"hide_score_total": true
+			},
+			"rubric_association": {
+				"id": 1,
+				"rubric_id": 1,
+				"association_id": 1,
+				"association_type": "Course",
+				"used_for_grading": true,
+				"hide_score_total": true
+			}
+		},
+		"status_code": 200
 	}
 }
diff --git a/tests/test_course.py b/tests/test_course.py
index 9f2480d..6aefd3a 100644
--- a/tests/test_course.py
+++ b/tests/test_course.py
@@ -1700,6 +1700,29 @@ class TestCourse(unittest.TestCase):
         self.assertIsInstance(content_export, ContentExport)
         self.assertTrue(hasattr(content_export, "export_type"))
 
+    # create_rubric()
+    def test_create_rubric_no_association(self, m):
+        register_uris({"course": ["create_rubric"]}, m)
+
+        rubric = self.course.create_rubric()
+
+        self.assertIsInstance(rubric, dict)
+        self.assertEqual(rubric["rubric"].title, "Course Rubric 1")
+        self.assertEqual(rubric["rubric"].id, 1)
+
+    def test_create_rubric_with_association(self, m):
+        register_uris({"course": ["create_rubric_with_association"]}, m)
+
+        rubric = self.course.create_rubric()
+
+        self.assertIsInstance(rubric, dict)
+        self.assertEqual(rubric["rubric"].title, "Course Rubric 1")
+        self.assertEqual(rubric["rubric"].id, 1)
+
+        self.assertEqual(rubric["rubric_association"].id, 1)
+        self.assertEqual(rubric["rubric_association"].rubric_id, 1)
+        self.assertEqual(rubric["rubric_association"].association_type, "Course")
+
 
 @requests_mock.Mocker()
 class TestCourseNickname(unittest.TestCase):
diff --git a/tests/test_rubric.py b/tests/test_rubric.py
index 285ee7c..c1e4959 100644
--- a/tests/test_rubric.py
+++ b/tests/test_rubric.py
@@ -23,3 +23,22 @@ class TestGradingStandard(unittest.TestCase):
     def test__str__(self, m):
         string = str(self.rubric)
         self.assertIsInstance(string, str)
+
+
+@requests_mock.Mocker()
+class TestRubricAssociation(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", "create_rubric_with_association"]}, m
+            )
+
+            self.course = self.canvas.get_course(1)
+            self.rubric = self.course.create_rubric()
+
+    # __str__()
+    def test__str__(self, m):
+        string = str(self.rubric["rubric_association"])
+        self.assertIsInstance(string, str)