-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add management command for course prompts
- Loading branch information
Showing
14 changed files
with
303 additions
and
49 deletions.
There are no files selected for viewing
Empty file.
Empty file.
121 changes: 121 additions & 0 deletions
121
learning_assistant/management/commands/set_course_prompts.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
""" | ||
Django management command to generate course prompts. | ||
""" | ||
import json | ||
import logging | ||
from posixpath import join as urljoin | ||
|
||
import requests | ||
from django.conf import settings | ||
from django.contrib.auth import get_user_model | ||
from django.core.management.base import BaseCommand | ||
from edx_rest_api_client.auth import SuppliedJwtAuth | ||
from edx_rest_api_client.client import USER_AGENT | ||
from opaque_keys.edx.keys import CourseKey | ||
|
||
try: | ||
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user | ||
except ImportError: | ||
create_jwt_for_user = None | ||
|
||
from learning_assistant.models import CoursePrompt | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class Command(BaseCommand): | ||
""" | ||
Django Management command to create a set of course prompts | ||
""" | ||
|
||
def add_arguments(self, parser): | ||
|
||
# list of course ids | ||
parser.add_argument( | ||
'--course_ids', | ||
dest='course_ids', | ||
help='comma separated list of course_ids to generate', | ||
) | ||
|
||
# pre-message | ||
parser.add_argument( | ||
'--pre_message', | ||
dest='pre_message', | ||
help='message to prepend to course topics', | ||
) | ||
|
||
parser.add_argument( | ||
'--skills_descriptor', | ||
dest='skills_descriptor', | ||
help='message that describes skill structure' | ||
) | ||
|
||
# post-message | ||
parser.add_argument( | ||
'--post_message', | ||
dest='post_message', | ||
help='message to append to course topics', | ||
) | ||
|
||
# service username | ||
parser.add_argument( | ||
'-u', | ||
'--username', | ||
dest='username', | ||
help='username for authenticating calls to discovery API' | ||
) | ||
|
||
@staticmethod | ||
def _get_discovery_api_client(username): | ||
""" | ||
Returns an API client which can be used to make Catalog API requests. | ||
""" | ||
User = get_user_model() | ||
service_user = User.objects.get(username=username) | ||
jwt = create_jwt_for_user(service_user) | ||
client = requests.Session() | ||
client.headers.update({'User-Agent': USER_AGENT}) | ||
client.auth = SuppliedJwtAuth(jwt) | ||
|
||
return client | ||
|
||
def handle(self, *args, **options): | ||
""" | ||
Management command entry point | ||
""" | ||
course_ids = options['course_ids'] | ||
pre_message = options['pre_message'] | ||
skills_descriptor = options['skills_descriptor'] | ||
post_message = options['post_message'] | ||
service_username = options['username'] | ||
|
||
client = self._get_discovery_api_client(service_username) | ||
|
||
course_ids_list = course_ids.split(',') | ||
for course_run_id in course_ids_list: | ||
course_key = CourseKey.from_string(course_run_id) | ||
|
||
# discovery API requires course keys, not course run keys | ||
course_id = f'{course_key.org}+{course_key.course}' | ||
|
||
url = urljoin( | ||
settings.DISCOVERY_BASE_URL, | ||
'api/v1/courses/{course_id}'.format(course_id=course_id) | ||
) | ||
response_data = client.get(url).data | ||
title = response_data['title'] | ||
skill_names = response_data['skill_names'] | ||
|
||
# create restructured dictionary with data | ||
course_dict = {'title': title, 'topics': skill_names} | ||
|
||
# append descriptor message and decode json dict into a string | ||
skills_message = skills_descriptor + json.dumps(course_dict) | ||
|
||
# finally, create list of prompt messages and save | ||
prompt_messages = [pre_message, skills_message, post_message] | ||
CoursePrompt.objects.update_or_create( | ||
course_id=course_run_id, json_prompt_content=prompt_messages | ||
) | ||
|
||
logger.info('Updated course prompt for course_run_id=%s', course_run_id) |
Empty file.
75 changes: 75 additions & 0 deletions
75
learning_assistant/management/commands/tests/test_set_course_prompts.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
""" | ||
Tests for the set_course_prompts management command. | ||
""" | ||
import json | ||
from posixpath import join as urljoin | ||
from unittest.mock import MagicMock, patch | ||
|
||
from django.conf import settings | ||
from django.core.management import call_command | ||
from django.test import TestCase | ||
from rest_framework.response import Response | ||
|
||
from learning_assistant.models import CoursePrompt | ||
|
||
|
||
class SetCoursePromptsTests(TestCase): | ||
"""Test set_course_prompts command""" | ||
command = 'set_course_prompts' | ||
|
||
def setUp(self): | ||
self.pre_message = 'This is the first message' | ||
self.skills_descriptor = 'These are the skills: ' | ||
self.post_message = 'This message comes after' | ||
self.service_username = 'lms_worker' | ||
self.course_ids = 'course-v1:edx+test+23,course-v1:edx+test+24' | ||
self.course_title = 'Intro to Testing' | ||
self.skill_names = ['Testing', 'Computers', 'Coding'] | ||
|
||
def get_mock_discovery_response(self): | ||
""" | ||
Create scaled down mock of discovery response | ||
""" | ||
response_data = { | ||
'title': self.course_title, | ||
'skill_names': self.skill_names | ||
} | ||
return Response(data=response_data) | ||
|
||
@patch('learning_assistant.management.commands.set_course_prompts.Command._get_discovery_api_client') | ||
def test_course_prompts_created(self, mock_get_discovery_client): | ||
""" | ||
Assert that course prompts are created by calling management command. | ||
""" | ||
mock_client = MagicMock() | ||
mock_get_discovery_client.return_value = mock_client | ||
mock_client.get.return_value = self.get_mock_discovery_response() | ||
|
||
call_command( | ||
self.command, | ||
course_ids=self.course_ids, | ||
pre_message=self.pre_message, | ||
skills_descriptor=self.skills_descriptor, | ||
post_message=self.post_message, | ||
username=self.service_username, | ||
) | ||
|
||
# assert that discovery api was called with course id, not course run id | ||
expected_url = urljoin( | ||
settings.DISCOVERY_BASE_URL, | ||
'api/v1/courses/{course_id}'.format(course_id='edx+test') | ||
) | ||
mock_client.get.assert_any_call(expected_url) | ||
mock_client.get.assert_called() | ||
|
||
# assert that number of prompts created is equivalent to number of courses passed in to command | ||
prompts = CoursePrompt.objects.filter() | ||
self.assertEqual(len(prompts), len(self.course_ids.split(','))) | ||
|
||
# assert structure of prompt | ||
course_prompt = prompts[0].json_prompt_content | ||
self.assertEqual(len(course_prompt), 3) | ||
|
||
skills_message = self.skills_descriptor + json.dumps({'title': self.course_title, 'topics': self.skill_names}) | ||
expected_response = [self.pre_message, skills_message, self.post_message] | ||
self.assertEqual(course_prompt, expected_response) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.