Commit e5080f43 by Will Daly

Display an image response in the course staff debug page

parent 21aa930e
...@@ -13,6 +13,14 @@ ...@@ -13,6 +13,14 @@
<div class="student__answer__display__content"> <div class="student__answer__display__content">
{{ submission.answer.text|linebreaks }} {{ submission.answer.text|linebreaks }}
</div> </div>
{% if submission.image_url %}
<img
class="submission--image"
alt="{% trans "The image associated with this response" %}"
src="{{ submission.image_url }}"
/>
{% endif %}
</div> </div>
</div> </div>
......
...@@ -4,6 +4,7 @@ determine the flow of the problem. ...@@ -4,6 +4,7 @@ determine the flow of the problem.
""" """
import copy import copy
from functools import wraps from functools import wraps
import logging
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
...@@ -17,6 +18,10 @@ from submissions import api as submission_api ...@@ -17,6 +18,10 @@ from submissions import api as submission_api
from openassessment.assessment.api import peer as peer_api from openassessment.assessment.api import peer as peer_api
from openassessment.assessment.api import self as self_api from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api from openassessment.assessment.api import ai as ai_api
from openassessment.fileupload import api as file_api
logger = logging.getLogger(__name__)
def require_global_admin(error_msg): def require_global_admin(error_msg):
...@@ -189,16 +194,19 @@ class StaffInfoMixin(object): ...@@ -189,16 +194,19 @@ class StaffInfoMixin(object):
Must be course staff to render this view. Must be course staff to render this view.
""" """
path, context = self.get_student_info_path_and_context(data) student_id = data.params.get('student_id', '')
path, context = self.get_student_info_path_and_context(student_id)
return self.render_assessment(path, context) return self.render_assessment(path, context)
def get_student_info_path_and_context(self, data): def get_student_info_path_and_context(self, student_id):
""" """
Get the proper path and context for rendering the the student info Get the proper path and context for rendering the the student info
section of the staff debug panel. section of the staff debug panel.
Args:
student_id (unicode): The ID of the student to report.
""" """
student_id = data.params.get('student_id', '')
submission_uuid = None submission_uuid = None
submission = None submission = None
assessment_steps = self.assessment_steps assessment_steps = self.assessment_steps
...@@ -212,8 +220,22 @@ class StaffInfoMixin(object): ...@@ -212,8 +220,22 @@ class StaffInfoMixin(object):
submissions = submission_api.get_submissions(student_item, 1) submissions = submission_api.get_submissions(student_item, 1)
if submissions: if submissions:
submission = submissions[0]
submission_uuid = submissions[0]['uuid'] submission_uuid = submissions[0]['uuid']
submission = submissions[0]
if 'file_key' in submission.get('answer', {}):
file_key = submission['answer']['file_key']
try:
submission['image_url'] = file_api.get_download_url(file_key)
except file_api.FileUploadError:
# Log the error, but do not prevent the rest of the student info
# from being displayed.
msg = (
u"Could not retrieve image URL for staff debug page. "
u"The student ID is '{student_id}', and the file key is {file_key}"
).format(student_id=student_id, file_key=file_key)
logger.exception(msg)
example_based_assessment = None example_based_assessment = None
self_assessment = None self_assessment = None
......
...@@ -100,7 +100,7 @@ class XBlockHandlerTestCase(CacheResetTest): ...@@ -100,7 +100,7 @@ class XBlockHandlerTestCase(CacheResetTest):
) )
return self.runtime.get_block(block_id) return self.runtime.get_block(block_id)
def request(self, xblock, handler_name, content, response_format=None): def request(self, xblock, handler_name, content, request_method="POST", response_format=None):
""" """
Make a request to an XBlock handler. Make a request to an XBlock handler.
...@@ -110,6 +110,7 @@ class XBlockHandlerTestCase(CacheResetTest): ...@@ -110,6 +110,7 @@ class XBlockHandlerTestCase(CacheResetTest):
content (unicode): Content of the request. content (unicode): Content of the request.
Keyword Arguments: Keyword Arguments:
request_method (str): The HTTP method of the request (defaults to POST)
response_format (None or str): Expected format of the response string. response_format (None or str): Expected format of the response string.
If `None`, return the raw response content; if 'json', parse the If `None`, return the raw response content; if 'json', parse the
response as JSON and return the result. response as JSON and return the result.
...@@ -122,6 +123,7 @@ class XBlockHandlerTestCase(CacheResetTest): ...@@ -122,6 +123,7 @@ class XBlockHandlerTestCase(CacheResetTest):
""" """
# Create a fake request # Create a fake request
request = webob.Request(dict()) request = webob.Request(dict())
request.method = request_method
request.body = content request.body = content
# Send the request to the XBlock handler # Send the request to the XBlock handler
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
from collections import namedtuple from collections import namedtuple
import json import json
import datetime import datetime
import urllib
from mock import Mock, patch from mock import Mock, patch
from django.test.utils import override_settings from django.test.utils import override_settings
from openassessment.assessment.api import peer as peer_api from openassessment.assessment.api import peer as peer_api
...@@ -9,6 +10,7 @@ from openassessment.assessment.api import self as self_api ...@@ -9,6 +10,7 @@ from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api from openassessment.assessment.api import ai as ai_api
from openassessment.workflow import api as workflow_api from openassessment.workflow import api as workflow_api
from openassessment.assessment.errors.ai import AIError, AIGradingInternalError from openassessment.assessment.errors.ai import AIError, AIGradingInternalError
from openassessment.fileupload.api import FileUploadInternalError
from submissions import api as sub_api from submissions import api as sub_api
from openassessment.xblock.test.base import scenario, XBlockHandlerTestCase from openassessment.xblock.test.base import scenario, XBlockHandlerTestCase
...@@ -188,10 +190,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -188,10 +190,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
) )
# Now Bob should be fully populated in the student info view. # Now Bob should be fully populated in the student info view.
request = namedtuple('Request', 'params') path, context = xblock.get_student_info_path_and_context("Bob")
request.params = {"student_id": "Bob"}
# Verify that we can render without error
path, context = xblock.get_student_info_path_and_context(request)
self.assertEquals("Bob Answer", context['submission']['answer']['text']) self.assertEquals("Bob Answer", context['submission']['answer']['text'])
self.assertIsNone(context['self_assessment']) self.assertIsNone(context['self_assessment'])
self.assertEquals("openassessmentblock/staff_debug/student_info.html", path) self.assertEquals("openassessmentblock/staff_debug/student_info.html", path)
...@@ -220,15 +219,73 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -220,15 +219,73 @@ class TestCourseStaff(XBlockHandlerTestCase):
{'criteria': xblock.rubric_criteria}, {'criteria': xblock.rubric_criteria},
) )
# Now Bob should be fully populated in the student info view. path, context = xblock.get_student_info_path_and_context("Bob")
request = namedtuple('Request', 'params')
request.params = {"student_id": "Bob"}
# Verify that we can render without error
path, context = xblock.get_student_info_path_and_context(request)
self.assertEquals("Bob Answer", context['submission']['answer']['text']) self.assertEquals("Bob Answer", context['submission']['answer']['text'])
self.assertEquals([], context['peer_assessments']) self.assertEquals([], context['peer_assessments'])
self.assertEquals("openassessmentblock/staff_debug/student_info.html", path) self.assertEquals("openassessmentblock/staff_debug/student_info.html", path)
@scenario('data/self_only_scenario.xml', user_id='Bob')
def test_staff_debug_student_info_image_submission(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob"
)
bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id
# Create an image submission for Bob
sub_api.create_submission(bob_item, {
'text':"Bob Answer",
'file_key': "test_key"
})
# Mock the file upload API to avoid hitting S3
with patch("openassessment.xblock.staff_info_mixin.file_api") as file_api:
file_api.get_download_url.return_value = "http://www.example.com/image.jpeg"
__, context = xblock.get_student_info_path_and_context("Bob")
# Check that the right file key was passed to generate the download url
file_api.get_download_url.assert_called_with("test_key")
# Check the context passed to the template
self.assertEquals('http://www.example.com/image.jpeg', context['submission']['image_url'])
# Check the fully rendered template
payload = urllib.urlencode({"student_id": "Bob"})
resp = self.request(xblock, "render_student_info", payload)
self.assertIn("http://www.example.com/image.jpeg", resp)
@scenario('data/self_only_scenario.xml', user_id='Bob')
def test_staff_debug_student_info_file_download_url_error(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob"
)
bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id
# Create an image submission for Bob
sub_api.create_submission(bob_item, {
'text':"Bob Answer",
'file_key': "test_key"
})
# Mock the file upload API to simulate an error
with patch("openassessment.xblock.staff_info_mixin.file_api.get_download_url") as file_api_call:
file_api_call.side_effect = FileUploadInternalError("Error!")
__, context = xblock.get_student_info_path_and_context("Bob")
# Expect that the page still renders, but without the image url
self.assertIn('submission', context)
self.assertNotIn('image_url', context['submission'])
# Check the fully rendered template
payload = urllib.urlencode({"student_id": "Bob"})
resp = self.request(xblock, "render_student_info", payload)
self.assertIn("Bob Answer", resp)
@override_settings(ORA2_AI_ALGORITHMS=AI_ALGORITHMS) @override_settings(ORA2_AI_ALGORITHMS=AI_ALGORITHMS)
@scenario('data/example_based_assessment.xml', user_id='Bob') @scenario('data/example_based_assessment.xml', user_id='Bob')
def test_staff_debug_student_info_full_workflow(self, xblock): def test_staff_debug_student_info_full_workflow(self, xblock):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment