Commit 71725bec by Muzaffar yousaf

Merge pull request #665 from edx/muzaffar/student-info-tnl836

Get student info via user service.
parents d11f3139 91fb2874
...@@ -129,15 +129,15 @@ ...@@ -129,15 +129,15 @@
<form id="openassessment_student_info_form"> <form id="openassessment_student_info_form">
<ul> <ul>
<li class="openassessment__student-info_list"> <li class="openassessment__student-info_list">
<label for="openassessment__student_id" class="label">{% trans "Get Student Info" %}</label> <label for="openassessment__student_username" class="label">{% trans "Get Student Info" %}</label>
</li> </li>
<li class="openassessment__student-info_list"> <li class="openassessment__student-info_list">
<input id="openassessment__student_id" type="text" class="value" maxlength="255"> <input id="openassessment__student_username" type="text" class="value" maxlength="255">
</li> </li>
</ul> </ul>
<ul class="list list--actions"> <ul class="list list--actions">
<li class="list--actions__item"> <li class="list--actions__item">
<a aria-role="button" href="" id="submit_student_id" class="action--submit"><span class="copy">{% trans "Submit" %}</span></a> <a aria-role="button" href="" id="submit_student_username" class="action--submit"><span class="copy">{% trans "Submit" %}</span></a>
</li> </li>
</ul> </ul>
</form> </form>
......
...@@ -90,6 +90,7 @@ def load(path): ...@@ -90,6 +90,7 @@ def load(path):
return data.decode("utf8") return data.decode("utf8")
@XBlock.needs("i18n") @XBlock.needs("i18n")
@XBlock.needs("user")
class OpenAssessmentBlock( class OpenAssessmentBlock(
MessageMixin, MessageMixin,
SubmissionMixin, SubmissionMixin,
...@@ -200,21 +201,44 @@ class OpenAssessmentBlock( ...@@ -200,21 +201,44 @@ class OpenAssessmentBlock(
help="Indicates whether or not there are peers to grade." help="Indicates whether or not there are peers to grade."
) )
def get_student_item_dict(self): @property
def course_id(self):
return self._serialize_opaque_key(self.xmodule_runtime.course_id) # pylint:disable=E1101
def get_anonymous_user_id(self, username, course_id):
"""
Get the anonymous user id from Xblock user service.
Args:
username(str): user's name entered by staff to get info.
course_id(str): course id.
Returns:
A unique id for (user, course) pair
"""
return self.runtime.service(self, 'user').get_anonymous_user_id(username, course_id)
def get_student_item_dict(self, anonymous_user_id=None):
"""Create a student_item_dict from our surrounding context. """Create a student_item_dict from our surrounding context.
See also: submissions.api for details. See also: submissions.api for details.
Args:
anonymous_user_id(str): A unique anonymous_user_id for (user, course) pair.
Returns: Returns:
(dict): The student item associated with this XBlock instance. This (dict): The student item associated with this XBlock instance. This
includes the student id, item id, and course id. includes the student id, item id, and course id.
""" """
item_id = self._serialize_opaque_key(self.scope_ids.usage_id) item_id = self._serialize_opaque_key(self.scope_ids.usage_id)
# This is not the real way course_ids should work, but this is a # This is not the real way course_ids should work, but this is a
# temporary expediency for LMS integration # temporary expediency for LMS integration
if hasattr(self, "xmodule_runtime"): if hasattr(self, "xmodule_runtime"):
course_id = self._serialize_opaque_key(self.xmodule_runtime.course_id) # pylint:disable=E1101 course_id = self.course_id # pylint:disable=E1101
if anonymous_user_id:
student_id = anonymous_user_id
else:
student_id = self.xmodule_runtime.anonymous_student_id # pylint:disable=E1101 student_id = self.xmodule_runtime.anonymous_student_id # pylint:disable=E1101
else: else:
course_id = "edX/Enchantment_101/April_1" course_id = "edX/Enchantment_101/April_1"
......
...@@ -208,37 +208,41 @@ class StaffInfoMixin(object): ...@@ -208,37 +208,41 @@ class StaffInfoMixin(object):
""" """
Renders all relative information for a specific student's workflow. Renders all relative information for a specific student's workflow.
Given a student's ID, we can render a staff-only section of the page Given a student's username, we can render a staff-only section of the page
with submissions and assessments specific to the student. with submissions and assessments specific to the student.
Must be course staff to render this view. Must be course staff to render this view.
""" """
try: try:
student_id = data.params.get('student_id', '') student_username = data.params.get('student_username', '')
path, context = self.get_student_info_path_and_context(student_id) path, context = self.get_student_info_path_and_context(student_username)
return self.render_assessment(path, context) return self.render_assessment(path, context)
except PeerAssessmentInternalError: except PeerAssessmentInternalError:
return self.render_error(self._(u"Error finding assessment workflow cancellation.")) return self.render_error(self._(u"Error finding assessment workflow cancellation."))
def get_student_info_path_and_context(self, student_id): def get_student_info_path_and_context(self, student_username):
""" """
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: Args:
student_id (unicode): The ID of the student to report. student_username (unicode): The username of the student to report.
""" """
submission_uuid = None submission_uuid = None
submission = None submission = None
assessment_steps = self.assessment_steps assessment_steps = self.assessment_steps
anonymous_user_id = None
submissions = None
student_item = None
if student_id: if student_username:
student_item = self.get_student_item_dict() anonymous_user_id = self.get_anonymous_user_id(student_username, self.course_id)
student_item['student_id'] = student_id student_item = self.get_student_item_dict(anonymous_user_id=anonymous_user_id)
if anonymous_user_id:
# If there is a submission available for the requested student, present # If there is a submission available for the requested student, present
# it. If not, there will be no other information to collect. # it. If not, there will be no other information to collect.
submissions = submission_api.get_submissions(student_item, 1) submissions = submission_api.get_submissions(student_item, 1)
...@@ -257,8 +261,8 @@ class StaffInfoMixin(object): ...@@ -257,8 +261,8 @@ class StaffInfoMixin(object):
# from being displayed. # from being displayed.
msg = ( msg = (
u"Could not retrieve image URL for staff debug page. " u"Could not retrieve image URL for staff debug page. "
u"The student ID is '{student_id}', and the file key is {file_key}" u"The student username is '{student_username}', and the file key is {file_key}"
).format(student_id=student_id, file_key=file_key) ).format(student_username=student_username, file_key=file_key)
logger.exception(msg) logger.exception(msg)
example_based_assessment = None example_based_assessment = None
......
...@@ -48,8 +48,8 @@ OpenAssessment.StaffInfoView.prototype = { ...@@ -48,8 +48,8 @@ OpenAssessment.StaffInfoView.prototype = {
loadStudentInfo: function() { loadStudentInfo: function() {
var view = this; var view = this;
var sel = $('#openassessment__staff-info', this.element); var sel = $('#openassessment__staff-info', this.element);
var student_id = sel.find('#openassessment__student_id').val(); var student_username = sel.find('#openassessment__student_username').val();
this.server.studentInfo(student_id).done( this.server.studentInfo(student_username).done(
function(html) { function(html) {
// Load the HTML and install event handlers // Load the HTML and install event handlers
$('#openassessment__student-info', view.element).replaceWith(html); $('#openassessment__student-info', view.element).replaceWith(html);
...@@ -96,7 +96,7 @@ OpenAssessment.StaffInfoView.prototype = { ...@@ -96,7 +96,7 @@ OpenAssessment.StaffInfoView.prototype = {
); );
// Install a click handler for requesting student info // Install a click handler for requesting student info
sel.find('#submit_student_id').click( sel.find('#submit_student_username').click(
function(eventObject) { function(eventObject) {
eventObject.preventDefault(); eventObject.preventDefault();
view.loadStudentInfo(); view.loadStudentInfo();
......
...@@ -121,14 +121,14 @@ if (typeof OpenAssessment.Server == "undefined" || !OpenAssessment.Server) { ...@@ -121,14 +121,14 @@ if (typeof OpenAssessment.Server == "undefined" || !OpenAssessment.Server) {
/** /**
Load the Student Info section in Staff Info. Load the Student Info section in Staff Info.
**/ **/
studentInfo: function(student_id) { studentInfo: function(student_username) {
var url = this.url('render_student_info'); var url = this.url('render_student_info');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
url: url, url: url,
type: "POST", type: "POST",
dataType: "html", dataType: "html",
data: {student_id: student_id} data: {student_username: student_username}
}).done(function(data) { }).done(function(data) {
defer.resolveWith(this, [data]); defer.resolveWith(this, [data]);
}).fail(function(data) { }).fail(function(data) {
......
...@@ -5,6 +5,8 @@ import datetime ...@@ -5,6 +5,8 @@ import datetime
import urllib 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
import ddt
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
...@@ -13,6 +15,7 @@ from openassessment.assessment.errors.ai import AIError, AIGradingInternalError ...@@ -13,6 +15,7 @@ from openassessment.assessment.errors.ai import AIError, AIGradingInternalError
from openassessment.fileupload.api import FileUploadInternalError 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
from xblock.core import XBlock
ALGORITHM_ID = 'fake' ALGORITHM_ID = 'fake'
...@@ -43,6 +46,14 @@ ASSESSMENT_DICT = { ...@@ -43,6 +46,14 @@ ASSESSMENT_DICT = {
} }
class NullUserService(object):
"""
A simple implementation of the runtime "user" service.
"""
def get_anonymous_user_id(self, username, course_id):
return username
class TestCourseStaff(XBlockHandlerTestCase): class TestCourseStaff(XBlockHandlerTestCase):
""" """
Tests for course staff debug panel. Tests for course staff debug panel.
...@@ -164,11 +175,12 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -164,11 +175,12 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.xmodule_runtime = self._create_mock_runtime( xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
xblock.runtime._services['user'] = NullUserService()
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text':"Bob Answer"}) submission = sub_api.create_submission(bob_item, {'text': "Bob Answer"})
peer_api.on_start(submission["uuid"]) peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer']) workflow_api.create_workflow(submission["uuid"], ['peer'])
...@@ -201,11 +213,11 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -201,11 +213,11 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.xmodule_runtime = self._create_mock_runtime( xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
xblock.runtime._services['user'] = NullUserService()
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text':"Bob Answer"}) submission = sub_api.create_submission(bob_item, {'text': "Bob Answer"})
peer_api.on_start(submission["uuid"]) peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['self']) workflow_api.create_workflow(submission["uuid"], ['self'])
...@@ -237,6 +249,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -237,6 +249,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.xmodule_runtime = self._create_mock_runtime( xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
xblock.runtime._services['user'] = NullUserService()
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
...@@ -296,13 +309,14 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -296,13 +309,14 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.xmodule_runtime = self._create_mock_runtime( xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
xblock.runtime._services['user'] = NullUserService()
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create an image submission for Bob # Create an image submission for Bob
sub_api.create_submission(bob_item, { sub_api.create_submission(bob_item, {
'text':"Bob Answer", 'text': "Bob Answer",
'file_key': "test_key" 'file_key': "test_key"
}) })
...@@ -318,7 +332,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -318,7 +332,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
self.assertEquals('http://www.example.com/image.jpeg', context['submission']['image_url']) self.assertEquals('http://www.example.com/image.jpeg', context['submission']['image_url'])
# Check the fully rendered template # Check the fully rendered template
payload = urllib.urlencode({"student_id": "Bob"}) payload = urllib.urlencode({"student_username": "Bob"})
resp = self.request(xblock, "render_student_info", payload) resp = self.request(xblock, "render_student_info", payload)
self.assertIn("http://www.example.com/image.jpeg", resp) self.assertIn("http://www.example.com/image.jpeg", resp)
...@@ -328,13 +342,14 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -328,13 +342,14 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.xmodule_runtime = self._create_mock_runtime( xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
xblock.runtime._services['user'] = NullUserService()
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create an image submission for Bob # Create an image submission for Bob
sub_api.create_submission(bob_item, { sub_api.create_submission(bob_item, {
'text':"Bob Answer", 'text': "Bob Answer",
'file_key': "test_key" 'file_key': "test_key"
}) })
...@@ -348,7 +363,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -348,7 +363,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
self.assertNotIn('image_url', context['submission']) self.assertNotIn('image_url', context['submission'])
# Check the fully rendered template # Check the fully rendered template
payload = urllib.urlencode({"student_id": "Bob"}) payload = urllib.urlencode({"student_username": "Bob"})
resp = self.request(xblock, "render_student_info", payload) resp = self.request(xblock, "render_student_info", payload)
self.assertIn("Bob Answer", resp) self.assertIn("Bob Answer", resp)
...@@ -359,6 +374,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -359,6 +374,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.xmodule_runtime = self._create_mock_runtime( xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
xblock.runtime._services['user'] = NullUserService()
# Commonly chosen options for assessments # Commonly chosen options for assessments
options_selected = { options_selected = {
...@@ -377,7 +393,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -377,7 +393,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text':"Bob Answer"}) submission = sub_api.create_submission(bob_item, {'text': "Bob Answer"})
peer_api.on_start(submission["uuid"]) peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer', 'self']) workflow_api.create_workflow(submission["uuid"], ['peer', 'self'])
...@@ -410,7 +426,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -410,7 +426,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') request = namedtuple('Request', 'params')
request.params = {"student_id": "Bob"} request.params = {"student_username": "Bob"}
# Verify that we can render without error # Verify that we can render without error
resp = xblock.render_student_info(request) resp = xblock.render_student_info(request)
self.assertIn("bob answer", resp.body.lower()) self.assertIn("bob answer", resp.body.lower())
......
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