Commit 57300f83 by Stephen Sanchez

Adding hooks to new user lookup service.

parent 77ee930e
......@@ -60,6 +60,9 @@ def get_score(submission_uuid, requirements):
workflow = PeerWorkflow.get_by_submission_uuid(submission_uuid)
if workflow is None:
return None
# This query will use the ordering defined by the assessment model
# (descending scored_at, then descending id)
items = workflow.graded_by.filter(
......@@ -420,7 +423,9 @@ def get_submitted_assessments(submission_uuid, scored_only=True, limit=None):
Returns:
list(dict): A list of dictionaries, where each dictionary represents a
separate assessment. Each assessment contains points earned, points
possible, time scored, scorer id, score type, and feedback.
possible, time scored, scorer id, score type, and feedback. If no
workflow is found associated with the given submission_uuid, returns
an empty list.
Raises:
PeerAssessmentRequestError: Raised when the submission_id is invalid.
......@@ -448,6 +453,8 @@ def get_submitted_assessments(submission_uuid, scored_only=True, limit=None):
"""
try:
# If no workflow is found associated with the uuid, this returns None,
# and an empty set of assessments will be returned.
workflow = PeerWorkflow.get_by_submission_uuid(submission_uuid)
items = PeerWorkflowItem.objects.filter(
scorer=workflow,
......@@ -460,7 +467,8 @@ def get_submitted_assessments(submission_uuid, scored_only=True, limit=None):
return serialize_assessments(assessments)
except DatabaseError:
error_message = _(
u"Error getting assessments graded by author of submission {}".format(submission_uuid)
u"Couldn't retrieve the assessments that the author of response {}"
u" completed".format(submission_uuid)
)
logger.exception(error_message)
raise PeerAssessmentInternalError(error_message)
......
......@@ -807,6 +807,10 @@ class TestPeerApi(CacheResetTest):
submitted_assessments = peer_api.get_submitted_assessments(bob_sub["uuid"], scored_only=False)
self.assertEqual(1, len(submitted_assessments))
def test_get_submitted_assessments_with_bad_submission(self):
submitted_assessments = peer_api.get_submitted_assessments("bad-uuid", scored_only=True)
self.assertEqual(0, len(submitted_assessments))
def test_find_active_assessments(self):
buffy_answer, _ = self._create_student_and_submission("Buffy", "Buffy's answer")
xander_answer, _ = self._create_student_and_submission("Xander", "Xander's answer")
......
......@@ -66,7 +66,7 @@
</ol>
{% if is_course_staff %}
<div id="openassessment__staff_info"></div>
<div id="openassessment__staff-info"></div>
{% endif %}
</div>
</div>
......
{% load i18n %}
{% load tz %}
<div id="openassessment__staff_info" class="wrapper--staff-info wrapper--ui-staff">
<div id="openassessment__staff-info" class="wrapper--staff-info wrapper--ui-staff">
<div class="staff-info ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control">
<i class="ico icon-caret-right"></i>
......@@ -82,10 +82,10 @@
<div class="wrapper--input" class="staff-info__student__form">
<form id="openassessment_student_info_form">
<ul>
<li class="label">
<li class="openassessment__student-info_list">
<label for="openassessment__student_id" class="label">{% trans "Get Student Info" %}</label>
</li>
<li>
<li class="openassessment__student-info_list">
<input id="openassessment__student_id" type="text" class="value" maxlength="100">
</li>
</ul>
......@@ -96,7 +96,7 @@
</ul>
</form>
</div>
<div id="openassessment__student_info" class="staff-info__student__report"></div>
<div id="openassessment__student-info" class="staff-info__student__report"></div>
</div>
</div>
</div>
......
{% load i18n %}
{% load tz %}
<div id="openassessment__student_info" class="staff-info__student__report">
<div id="openassessment__student-info" class="staff-info__student__report">
{% if submission %}
<h2 class="title">
<span class="label">{% trans "Student Workflow Information" %}</span>
<span class="label">{% trans "Student Information" %}</span>
</h2>
<div class="staff-info__content ui-staff__content">
......@@ -17,11 +17,11 @@
</div>
<div class="staff-info__status ui-staff__content__section">
<h3 class="title">{% trans "Assessments on Student" %}</h3>
<h3 class="title">{% trans "Peer Assessments for This Student" %}</h3>
{% for assessment in peer_assessments %}
{% with peer_num=forloop.counter %}
<h4 class="title--sub"> {% trans "Peer" %} {{ peer_num }}: </h4>
<table class="staff-info__status__table" summary="{% trans "Assessment from Peer" %}">
<table class="staff-info__status__table" summary="{% trans "Assessment" %}">
<thead>
<tr>
<th abbr="Criterion" scope="col">{% trans "Criterion" %}</th>
......@@ -57,11 +57,11 @@
</div>
<div class="staff-info__status ui-staff__content__section">
<h3 class="title">{% trans "Assessments submitted by Student" %}</h3>
<h3 class="title">{% trans "Peer Assessments Completed by This Student" %}</h3>
{% for assessment in submitted_assessments %}
{% with peer_num=forloop.counter %}
<h4 class="title--sub">{% trans "Peer" %} {{ peer_num }}:</h4>
<table class="staff-info__status__table" summary="{% trans "Assessment from Student" %}">
<table class="staff-info__status__table" summary="{% trans "Assessment" %}">
<thead>
<tr>
<th abbr="Criterion" scope="col">{% trans "Criterion" %}</th>
......@@ -97,7 +97,7 @@
</div>
<div class="staff-info__status ui-staff__content__section">
<h3 class="title">{% trans "Student Self Assessment" %}</h3>
<h3 class="title">{% trans "Student's Self Assessment" %}</h3>
<table class="staff-info__status__table" summary="{% trans "Self Assessment" %}">
<thead>
<tr>
......@@ -126,6 +126,6 @@
</div>
</div>
{% else %}
{% trans "No submission found for student." %}
{% trans "Couldn't find a response for this student." %}
{% endif %}
</div>
\ No newline at end of file
......@@ -29,7 +29,7 @@ class StaffInfoMixin(object):
return self.render_error(_(
u"You do not have permission to access staff information"
))
student_item = self.get_student_item_dict()
context = dict()
path = 'openassessmentblock/staff_debug/staff_debug.html'
......@@ -37,7 +37,7 @@ class StaffInfoMixin(object):
status_counts, num_submissions = self.get_workflow_status_counts()
context['status_counts'] = status_counts
context['num_submissions'] = num_submissions
context['item_id'] = unicode(self.scope_ids.usage_id)
context['item_id'] = student_item["item_id"]
# Include release/due dates for each step in the problem
context['step_dates'] = list()
......@@ -65,15 +65,25 @@ class StaffInfoMixin(object):
with submissions and assessments specific to the student.
Must be course staff to render this view.
"""
# If request does not come from course staff, return nothing.
# This should not be able to happen unless someone attempts to
# explicitly invoke this handler.
if not self.is_course_staff or self.in_studio_preview:
return self.render_error(_(
u"You do not have permission to access student information"
))
u"You do not have permission to access student information."
))
path, context = self.get_student_info_path_and_context(data)
return self.render_assessment(path, context)
def get_student_info_path_and_context(self, data):
"""
Get the proper path and context for rendering the the student info
section of the staff debug panel.
"""
student_id = data.params.get('student_id', '')
submission_uuid = None
submission = None
......@@ -117,4 +127,4 @@ class StaffInfoMixin(object):
criterion["total_value"] = max_scores[criterion["name"]]
path = 'openassessmentblock/staff_debug/student_info.html'
return self.render_assessment(path, context)
\ No newline at end of file
return path, context
\ No newline at end of file
......@@ -2069,25 +2069,31 @@ hr.divider,
background: transparent;
margin: 40px 40px; }
.staff-info__student .label {
color: whitesmoke;
margin-bottom: 10px; }
.staff-info__student .action--submit {
margin-right: 10px;
margin-top: 10px;
margin-bottom: 10px; }
.staff-info__student .title {
text-align: left;
color: whitesmoke;
margin-bottom: 10px; }
.staff-info__student .title--sub {
color: whitesmoke;
margin-top: 10px;
margin-bottom: 10px; }
.staff-info__student .student__answer__display__content {
border: 1px solid rgba(182, 37, 104, 0.25);
padding: 10px 20px 10px 20px;
margin-bottom: 10px; }
.staff-info__student {
/**
* The follow styles are bound for the "shame" file. This is done to override
* LMS specific styles on HTML elements.
*/ }
.staff-info__student .label {
color: whitesmoke;
margin-bottom: 10px; }
.staff-info__student .action--submit {
margin: 10px 10px 10px 10px; }
.staff-info__student .title {
color: whitesmoke;
margin-bottom: 10px; }
.staff-info__student .title--sub {
color: whitesmoke;
margin-top: 10px;
margin-bottom: 10px; }
.staff-info__student .student__answer__display__content {
border: 1px solid rgba(182, 37, 104, 0.25);
padding: 10px 20px 10px 20px;
margin-bottom: 10px; }
.staff-info__student .openassessment__student-info_list {
list-style-type: none; }
.staff-info__student .student__answer__display__content p {
color: inherit; }
.openassessment .self-assessment__display__header, .openassessment .peer-assessment__display__header, .openassessment .step__header {
margin-bottom: 0 !important;
......
......@@ -26,7 +26,7 @@ OpenAssessment.StaffInfoView.prototype = {
this.server.render('staff_info').done(
function(html) {
// Load the HTML and install event handlers
$('#openassessment__staff_info', view.element).replaceWith(html);
$('#openassessment__staff-info', view.element).replaceWith(html);
view.installHandlers();
}
).fail(function(errMsg) {
......@@ -41,12 +41,12 @@ OpenAssessment.StaffInfoView.prototype = {
**/
loadStudentInfo: function() {
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();
this.server.studentInfo(student_id).done(
function(html) {
// Load the HTML and install event handlers
$('#openassessment__student_info', view.element).replaceWith(html);
$('#openassessment__student-info', view.element).replaceWith(html);
}
).fail(function(errMsg) {
view.showLoadError('student_info');
......@@ -57,7 +57,7 @@ OpenAssessment.StaffInfoView.prototype = {
Install event handlers for the view.
**/
installHandlers: function() {
var sel = $('#openassessment__staff_info', this.element);
var sel = $('#openassessment__staff-info', this.element);
var view = this;
if (sel.length <= 0) {
......
......@@ -35,14 +35,11 @@
.action--submit {
@extend %btn--secondary;
@extend %action-2;
margin-right: ($baseline-v/2);
margin-top: ($baseline-v/2);
margin-bottom: ($baseline-v/2);
margin: ($baseline-v/2) ($baseline-v/2) ($baseline-v/2) ($baseline-v/2);
}
.title {
@extend %hd-2;
text-align: left;
color: $heading-staff-color;
margin-bottom: ($baseline-v/2);
}
......@@ -58,4 +55,18 @@
padding: ($baseline-v/2) ($baseline-h/2) ($baseline-v/2) ($baseline-h/2);
margin-bottom: ($baseline-v/2);
}
.openassessment__student-info_list {
list-style-type: none;
}
/**
* The follow styles are bound for the "shame" file. This is done to override
* LMS specific styles on HTML elements.
*/
// 'p' elements in LMS have a color set on them.
.student__answer__display__content p {
color: inherit;
}
}
\ No newline at end of file
<openassessment>
<title>Open Assessment Test</title>
<prompt>
Given the state of the world today, what do you think should be done to
combat poverty? Please answer in a short essay of 200-300 words.
</prompt>
<rubric>
<prompt>Read for conciseness, clarity of thought, and form.</prompt>
<criterion>
<name>Concise</name>
<prompt>How concise is it?</prompt>
<option points="0">
<name>Neal Stephenson (late)</name>
<explanation>Neal Stephenson explanation</explanation>
</option>
<option points="1">
<name>HP Lovecraft</name>
<explanation>HP Lovecraft explanation</explanation>
</option>
<option points="3">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="4">
<name>Neal Stephenson (early)</name>
<explanation>Neal Stephenson (early) explanation</explanation>
</option>
<option points="5">
<name>Earnest Hemingway</name>
<explanation>Earnest Hemingway</explanation>
</option>
</criterion>
<criterion>
<name>Clear-headed</name>
<prompt>How clear is the thinking?</prompt>
<option points="0">
<name>Yogi Berra</name>
<explanation>Yogi Berra explanation</explanation>
</option>
<option points="1">
<name>Hunter S. Thompson</name>
<explanation>Hunter S. Thompson explanation</explanation>
</option>
<option points="2">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="3">
<name>Isaac Asimov</name>
<explanation>Isaac Asimov explanation</explanation>
</option>
<option points="10">
<name>Spock</name>
<explanation>Spock explanation</explanation>
</option>
</criterion>
<criterion>
<name>Form</name>
<prompt>Lastly, how is its form? Punctuation, grammar, and spelling all count.</prompt>
<option points="0">
<name>lolcats</name>
<explanation>lolcats explanation</explanation>
</option>
<option points="1">
<name>Facebook</name>
<explanation>Facebook explanation</explanation>
</option>
<option points="2">
<name>Reddit</name>
<explanation>Reddit explanation</explanation>
</option>
<option points="3">
<name>metafilter</name>
<explanation>metafilter explanation</explanation>
</option>
<option points="4">
<name>Usenet, 1996</name>
<explanation>Usenet, 1996 explanation</explanation>
</option>
<option points="5">
<name>The Elements of Style</name>
<explanation>The Elements of Style explanation</explanation>
</option>
</criterion>
</rubric>
<assessments>
<assessment name="peer-assessment" must_grade="5" must_be_graded_by="3" />
</assessments>
</openassessment>
<openassessment>
<title>Open Assessment Test</title>
<prompt>
Given the state of the world today, what do you think should be done to
combat poverty? Please answer in a short essay of 200-300 words.
</prompt>
<rubric>
<prompt>Read for conciseness, clarity of thought, and form.</prompt>
<criterion>
<name>Concise</name>
<prompt>How concise is it?</prompt>
<option points="0">
<name>Neal Stephenson (late)</name>
<explanation>Neal Stephenson explanation</explanation>
</option>
<option points="1">
<name>HP Lovecraft</name>
<explanation>HP Lovecraft explanation</explanation>
</option>
<option points="3">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="4">
<name>Neal Stephenson (early)</name>
<explanation>Neal Stephenson (early) explanation</explanation>
</option>
<option points="5">
<name>Earnest Hemingway</name>
<explanation>Earnest Hemingway</explanation>
</option>
</criterion>
<criterion>
<name>Clear-headed</name>
<prompt>How clear is the thinking?</prompt>
<option points="0">
<name>Yogi Berra</name>
<explanation>Yogi Berra explanation</explanation>
</option>
<option points="1">
<name>Hunter S. Thompson</name>
<explanation>Hunter S. Thompson explanation</explanation>
</option>
<option points="2">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="3">
<name>Isaac Asimov</name>
<explanation>Isaac Asimov explanation</explanation>
</option>
<option points="10">
<name>Spock</name>
<explanation>Spock explanation</explanation>
</option>
</criterion>
<criterion>
<name>Form</name>
<prompt>Lastly, how is its form? Punctuation, grammar, and spelling all count.</prompt>
<option points="0">
<name>lolcats</name>
<explanation>lolcats explanation</explanation>
</option>
<option points="1">
<name>Facebook</name>
<explanation>Facebook explanation</explanation>
</option>
<option points="2">
<name>Reddit</name>
<explanation>Reddit explanation</explanation>
</option>
<option points="3">
<name>metafilter</name>
<explanation>metafilter explanation</explanation>
</option>
<option points="4">
<name>Usenet, 1996</name>
<explanation>Usenet, 1996 explanation</explanation>
</option>
<option points="5">
<name>The Elements of Style</name>
<explanation>The Elements of Style explanation</explanation>
</option>
</criterion>
</rubric>
<assessments>
<assessment name="self-assessment" />
</assessments>
</openassessment>
......@@ -49,10 +49,8 @@ class TestCourseStaff(XBlockHandlerTestCase):
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_course_staff_debug_info(self, xblock):
# If we're not course staff, we shouldn't see the debug info
xblock.xmodule_runtime = Mock(
course_id='test_course',
anonymous_student_id='test_student',
user_is_staff=False
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, False, "Bob"
)
resp = self.request(xblock, 'render_staff_info', json.dumps({}))
self.assertNotIn("course staff information", resp.decode('utf-8').lower())
......@@ -62,15 +60,28 @@ class TestCourseStaff(XBlockHandlerTestCase):
resp = self.request(xblock, 'render_staff_info', json.dumps({}))
self.assertIn("course staff information", resp.decode('utf-8').lower())
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_course_student_debug_info(self, xblock):
# If we're not course staff, we shouldn't see the debug info
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, False, "Bob"
)
resp = self.request(xblock, 'render_student_info', json.dumps({}))
self.assertIn("you do not have permission", resp.decode('utf-8').lower())
# If we ARE course staff, then we should see the debug info
xblock.xmodule_runtime.user_is_staff = True
resp = self.request(xblock, 'render_student_info', json.dumps({}))
self.assertIn("couldn\'t find a response for this student.", resp.decode('utf-8').lower())
@scenario('data/basic_scenario.xml')
def test_hide_course_staff_debug_info_in_studio_preview(self, xblock):
# If we are in Studio preview mode, don't show the staff debug info
# In this case, the runtime will tell us that we're staff,
# but no user ID will be set.
xblock.xmodule_runtime = Mock(
course_id='test_course',
anonymous_student_id='test_student',
user_is_staff=True
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, "Bob"
)
resp = self.request(xblock, 'render_staff_info', json.dumps({}))
self.assertNotIn("course staff information", resp.decode('utf-8').lower())
......@@ -78,10 +89,8 @@ class TestCourseStaff(XBlockHandlerTestCase):
@scenario('data/staff_dates_scenario.xml', user_id='Bob')
def test_staff_debug_dates_table(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = Mock(
course_id='test_course',
anonymous_student_id='test_student',
user_is_staff=True
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, "Bob"
)
# Verify that we can render without error
......@@ -101,10 +110,8 @@ class TestCourseStaff(XBlockHandlerTestCase):
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_staff_debug_dates_distant_past_and_future(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = Mock(
course_id='test_course',
anonymous_student_id='test_student',
user_is_staff=True
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, "Bob"
)
# Verify that we can render without error
......@@ -115,25 +122,91 @@ class TestCourseStaff(XBlockHandlerTestCase):
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_staff_debug_student_info_no_submission(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = Mock(
course_id='test_course',
anonymous_student_id='test_student',
user_is_staff=True
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, "Bob"
)
request = namedtuple('Request', 'params')
request.params = {"student_id": "test_student"}
# Verify that we can render without error
resp = xblock.render_student_info(request)
self.assertIn("no submission", resp.body.lower())
self.assertIn("couldn\'t find a response for this student.", resp.body.lower())
@scenario('data/peer_only_scenario.xml', user_id='Bob')
def test_staff_debug_student_info_peer_only(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, "Bob"
)
bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text':"Bob Answer"})
peer_api.create_peer_workflow(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer'])
# Create a submission for Tim, and corresponding workflow.
tim_item = bob_item.copy()
tim_item["student_id"] = "Tim"
tim_sub = sub_api.create_submission(tim_item, "Tim Answer")
peer_api.create_peer_workflow(tim_sub["uuid"])
workflow_api.create_workflow(tim_sub["uuid"], ['peer', 'self'])
# Bob assesses Tim.
peer_api.get_submission_to_assess(submission['uuid'], 1)
peer_api.create_assessment(
submission["uuid"],
STUDENT_ITEM["student_id"],
ASSESSMENT_DICT['options_selected'], dict(), "",
{'criteria': xblock.rubric_criteria},
1,
)
# Now Bob should be fully populated in the student info view.
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.assertIsNone(context['self_assessment'])
self.assertEquals("openassessmentblock/staff_debug/student_info.html", path)
@scenario('data/self_only_scenario.xml', user_id='Bob')
def test_staff_debug_student_info_self_only(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, "Bob"
)
bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text':"Bob Answer"})
peer_api.create_peer_workflow(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['self'])
# Bob assesses himself.
self_api.create_assessment(
submission['uuid'],
STUDENT_ITEM["student_id"],
ASSESSMENT_DICT['options_selected'],
{'criteria': xblock.rubric_criteria},
)
# Now Bob should be fully populated in the student info view.
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([], context['peer_assessments'])
self.assertEquals("openassessmentblock/staff_debug/student_info.html", path)
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_staff_debug_student_info_full_workflow(self, xblock):
# Simulate that we are course staff
xblock.xmodule_runtime = Mock(
course_id='test_course',
item_id=xblock.scope_ids.usage_id,
anonymous_student_id='Bob',
user_is_staff=True
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, "Bob"
)
bob_item = STUDENT_ITEM.copy()
......@@ -173,5 +246,16 @@ class TestCourseStaff(XBlockHandlerTestCase):
request.params = {"student_id": "Bob"}
# Verify that we can render without error
resp = xblock.render_student_info(request)
print resp.body.lower()
self.assertIn("bob answer", resp.body.lower())
\ No newline at end of file
self.assertIn("bob answer", resp.body.lower())
def _create_mock_runtime(self, item_id, is_staff, anonymous_user_id):
mock_runtime = Mock(
course_id='test_course',
item_id=item_id,
anonymous_student_id='Bob',
user_is_staff=is_staff,
service=lambda self, service: Mock(
get_anonymous_student_id=lambda user_id, course_id: anonymous_user_id
)
)
return mock_runtime
\ No newline at end of file
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