Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #182 Quiz Extensions #185

Merged
merged 5 commits into from
Jun 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

- Content Migrations (Thanks, [@qwertynerd97](https://github.com/qwertynerd97))
- Copy a File (Thanks, [@qwertynerd97](https://github.com/qwertynerd97))
- Course Quiz Extensions
- List Announcements (Thanks, [@rmanbaird](https://github.com/rmanbaird))
- Grade/Comment on Multiple Submissions (Thanks, [@rmanbaird](https://github.com/rmanbaird))
- List Announcements
- Quiz Extensions

### General

Expand Down
53 changes: 53 additions & 0 deletions canvasapi/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from canvasapi.page import Page
from canvasapi.paginated_list import PaginatedList
from canvasapi.progress import Progress
from canvasapi.quiz import QuizExtension
from canvasapi.tab import Tab
from canvasapi.submission import Submission
from canvasapi.upload import Uploader
Expand Down Expand Up @@ -2144,6 +2145,58 @@ def get_migration_systems(self, **kwargs):
_kwargs=combine_kwargs(**kwargs)
)

def set_quiz_extensions(self, quiz_extensions, **kwargs):
"""
Set extensions for student all quiz submissions in a course.

:calls: `POST /api/v1/courses/:course_id/quizzes/:quiz_id/extensions
<https://canvas.instructure.com/doc/api/quiz_extensions.html#method.quizzes/quiz_extensions.create>`_

:param quiz_extensions: List of dictionaries representing extensions.
:type quiz_extensions: list

:rtype: list of :class:`canvasapi.quiz.QuizExtension`

Example Usage:

>>> course.set_quiz_extensions([
... {
... 'user_id': 1,
... 'extra_time': 60,
... 'extra_attempts': 1
... },
... {
... 'user_id': 2,
... 'extra_attempts': 3
... },
... {
... 'user_id': 3,
... 'extra_time': 20
... }
... ])
"""

if not isinstance(quiz_extensions, list) or not quiz_extensions:
raise ValueError('Param `quiz_extensions` must be a non-empty list.')

if any(not isinstance(extension, dict) for extension in quiz_extensions):
raise ValueError('Param `quiz_extensions` must only contain dictionaries')

if any('user_id' not in extension for extension in quiz_extensions):
raise RequiredFieldMissing(
'Dictionaries in `quiz_extensions` must contain key `user_id`'
)

kwargs['quiz_extensions'] = quiz_extensions

response = self._requester.request(
'POST',
'courses/{}/quiz_extensions'.format(self.id),
_kwargs=combine_kwargs(**kwargs)
)
extension_list = response.json()['quiz_extensions']
return [QuizExtension(self._requester, extension) for extension in extension_list]

def submissions_bulk_update(self, **kwargs):
"""
Update the grading and comments on multiple student's assignment
Expand Down
59 changes: 59 additions & 0 deletions canvasapi/quiz.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,65 @@ def get_questions(self, **kwargs):
_kwargs=combine_kwargs(**kwargs)
)

def set_extensions(self, quiz_extensions, **kwargs):
"""
Set extensions for student quiz submissions.

:calls: `POST /api/v1/courses/:course_id/quizzes/:quiz_id/extensions
<https://canvas.instructure.com/doc/api/quiz_extensions.html#method.quizzes/quiz_extensions.create>`_

:param quiz_extensions: List of dictionaries representing extensions.
:type quiz_extensions: list

:rtype: list of :class:`canvasapi.quiz.QuizExtension`

Example Usage:

>>> quiz.set_extensions([
... {
... 'user_id': 1,
... 'extra_time': 60,
... 'extra_attempts': 1
... },
... {
... 'user_id': 2,
... 'extra_attempts': 3
... },
... {
... 'user_id': 3,
... 'extra_time': 20
... }
... ])
"""

if not isinstance(quiz_extensions, list) or not quiz_extensions:
raise ValueError('Param `quiz_extensions` must be a non-empty list.')

if any(not isinstance(extension, dict) for extension in quiz_extensions):
raise ValueError('Param `quiz_extensions` must only contain dictionaries')

if any('user_id' not in extension for extension in quiz_extensions):
raise RequiredFieldMissing(
'Dictionaries in `quiz_extensions` must contain key `user_id`'
)

kwargs['quiz_extensions'] = quiz_extensions

response = self._requester.request(
'POST',
'courses/{}/quizzes/{}/extensions'.format(self.course_id, self.id),
_kwargs=combine_kwargs(**kwargs)
)
extension_list = response.json()['quiz_extensions']
return [QuizExtension(self._requester, extension) for extension in extension_list]


@python_2_unicode_compatible
class QuizExtension(CanvasObject):

def __str__(self):
return "{}-{}".format(self.quiz_id, self.user_id)


@python_2_unicode_compatible
class QuizQuestion(CanvasObject):
Expand Down
24 changes: 24 additions & 0 deletions tests/fixtures/course.json
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,30 @@
],
"status_code": 200
},
"set_quiz_extensions": {
"method": "POST",
"endpoint": "courses/1/quiz_extensions",
"data": {
"quiz_extensions": [
{
"user_id": "1",
"quiz_id": "1",
"extra_attempts": null,
"extra_time": 60,
"manually_unlocked": null,
"end_at": null
},
{
"user_id": "2",
"quiz_id": "1",
"extra_attempts": 3,
"extra_time": null,
"manually_unlocked": null,
"end_at": null
}
]
}
},
"update_submissions": {
"method": "POST",
"endpoint": "courses/1/submissions/update_grades",
Expand Down
24 changes: 24 additions & 0 deletions tests/fixtures/quiz.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,29 @@
}
],
"status_code": 200
},
"set_extensions": {
"method": "POST",
"endpoint": "courses/1/quizzes/1/extensions",
"data": {
"quiz_extensions": [
{
"user_id": "1",
"quiz_id": "1",
"extra_attempts": null,
"extra_time": 60,
"manually_unlocked": null,
"end_at": null
},
{
"user_id": "2",
"quiz_id": "1",
"extra_attempts": 3,
"extra_time": null,
"manually_unlocked": null,
"end_at": null
}
]
}
}
}
46 changes: 45 additions & 1 deletion tests/test_course.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from canvasapi.module import Module
from canvasapi.outcome import OutcomeGroup, OutcomeLink
from canvasapi.progress import Progress
from canvasapi.quiz import Quiz
from canvasapi.quiz import Quiz, QuizExtension
from canvasapi.rubric import Rubric
from canvasapi.section import Section
from canvasapi.submission import Submission
Expand Down Expand Up @@ -1388,6 +1388,50 @@ def test_get_migration_systems(self, m):
self.assertEqual(migration_systems[1].requires_file_upload, False)
self.assertEqual(migration_systems[1].name, "Dummy Importer 02")

# set_quiz_extensions
def test_set_quiz_extensions(self, m):
register_uris({'course': ['set_quiz_extensions']}, m)

extension = self.course.set_quiz_extensions([
{
'user_id': 1,
'extra_time': 60
},
{
'user_id': 2,
'extra_attempts': 3
}
])

self.assertIsInstance(extension, list)
self.assertEqual(len(extension), 2)

self.assertIsInstance(extension[0], QuizExtension)
self.assertEqual(extension[0].user_id, "1")
self.assertTrue(hasattr(extension[0], 'extra_time'))
self.assertEqual(extension[0].extra_time, 60)

self.assertIsInstance(extension[1], QuizExtension)
self.assertEqual(extension[1].user_id, "2")
self.assertTrue(hasattr(extension[1], 'extra_attempts'))
self.assertEqual(extension[1].extra_attempts, 3)

def test_set_extensions_not_list(self, m):
with self.assertRaises(ValueError):
self.course.set_quiz_extensions({'user_id': 1, 'extra_time': 60})

def test_set_extensions_empty_list(self, m):
with self.assertRaises(ValueError):
self.course.set_quiz_extensions([])

def test_set_extensions_non_dicts(self, m):
with self.assertRaises(ValueError):
self.course.set_quiz_extensions([('user_id', 1), ('extra_time', 60)])

def test_set_extensions_missing_key(self, m):
with self.assertRaises(RequiredFieldMissing):
self.course.set_quiz_extensions([{'extra_time': 60, 'extra_attempts': 3}])

# submissions_bulk_update()
def test_submissions_bulk_update(self, m):
register_uris({'course': ['update_submissions']}, m)
Expand Down
66 changes: 65 additions & 1 deletion tests/test_quiz.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from canvasapi import Canvas
from canvasapi.exceptions import RequiredFieldMissing
from canvasapi.quiz import Quiz, QuizQuestion
from canvasapi.quiz import Quiz, QuizQuestion, QuizExtension
from canvasapi.quiz_group import QuizGroup
from tests import settings
from tests.util import register_uris
Expand Down Expand Up @@ -150,6 +150,70 @@ def test_get_questions(self, m):
self.assertTrue(hasattr(question_list[1], 'id'))
self.assertEqual(question_list[1].id, 2)

# set_extensions()
def test_set_extensions(self, m):
register_uris({'quiz': ['set_extensions']}, m)

extension = self.quiz.set_extensions([
{
'user_id': 1,
'extra_time': 60
},
{
'user_id': 2,
'extra_attempts': 3
}
])

self.assertIsInstance(extension, list)
self.assertEqual(len(extension), 2)

self.assertIsInstance(extension[0], QuizExtension)
self.assertEqual(extension[0].user_id, "1")
self.assertTrue(hasattr(extension[0], 'extra_time'))
self.assertEqual(extension[0].extra_time, 60)

self.assertIsInstance(extension[1], QuizExtension)
self.assertEqual(extension[1].user_id, "2")
self.assertTrue(hasattr(extension[1], 'extra_attempts'))
self.assertEqual(extension[1].extra_attempts, 3)

def test_set_extensions_not_list(self, m):
with self.assertRaises(ValueError):
self.quiz.set_extensions({'user_id': 1, 'extra_time': 60})

def test_set_extensions_empty_list(self, m):
with self.assertRaises(ValueError):
self.quiz.set_extensions([])

def test_set_extensions_non_dicts(self, m):
with self.assertRaises(ValueError):
self.quiz.set_extensions([('user_id', 1), ('extra_time', 60)])

def test_set_extensions_missing_key(self, m):
with self.assertRaises(RequiredFieldMissing):
self.quiz.set_extensions([{'extra_time': 60, 'extra_attempts': 3}])


@requests_mock.Mocker()
class TestQuizExtension(unittest.TestCase):
def setUp(self):
self.canvas = Canvas(settings.BASE_URL, settings.API_KEY)

self.extension = QuizExtension(self.canvas._Canvas__requester, {
'user_id': 1,
'quiz_id': 1,
'extra_time': 60,
'extra_attempts': 3,
'manually_unlocked': None,
'end_at': None
})

# __str__()
def test__str__(self, m):
string = str(self.extension)
self.assertIsInstance(string, str)


@requests_mock.Mocker()
class TestQuizQuestion(unittest.TestCase):
Expand Down