Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-ora2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-ora2
Commits
3f5924a3
Commit
3f5924a3
authored
Mar 27, 2014
by
Will Daly
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Course staff can see counts
parent
c2509762
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
309 additions
and
2 deletions
+309
-2
apps/openassessment/templates/openassessmentblock/oa_base.html
+19
-0
apps/openassessment/workflow/api.py
+30
-0
apps/openassessment/workflow/models.py
+4
-2
apps/openassessment/workflow/test/test_api.py
+65
-0
apps/openassessment/xblock/openassessmentblock.py
+20
-0
apps/openassessment/xblock/test/test_openassessment.py
+31
-0
apps/openassessment/xblock/workflow_mixin.py
+30
-0
apps/submissions/api.py
+55
-0
apps/submissions/tests/test_api.py
+55
-0
No files found.
apps/openassessment/templates/openassessmentblock/oa_base.html
View file @
3f5924a3
...
...
@@ -64,6 +64,25 @@
</li>
{% endfor %}
</ol>
{% if is_course_staff %}
<div>
<h2>
Course Staff Debug Information
</h2>
<p><b>
Total number of submissions
</b>
: {{ num_submissions }}
</p>
<table
border=
"1"
>
<tr>
<th>
Step
</th>
<th>
Number of students
</th>
</tr>
{% for item in status_counts %}
<tr>
<td>
{{ item.status }}
</td>
<td>
{{ item.count }}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
</div>
</div>
</div>
...
...
apps/openassessment/workflow/api.py
View file @
3f5924a3
...
...
@@ -292,6 +292,36 @@ def update_from_assessments(submission_uuid, assessment_requirements):
return
_serialized_with_details
(
workflow
,
assessment_requirements
)
def
get_status_counts
(
**
kwargs
):
"""
Count how many workflows have each status, for a given item in a course.
Kwargs:
course_id (unicode): The ID of the course.
item_id (unicode): The ID of the item in the course.
item_type (unicode): The type of the item.
Returns:
list of dictionaries with keys "status" (str) and "count" (int)
Example usage:
>>> get_status_counts("ora2/1/1", "peer-assessment-problem")
[
{"status": "peer", "count": 5},
{"status": "self", "count": 10},
{"status": "waiting", "count": 43},
{"status": "done", "count": 12},
]
"""
submission_uuids
=
sub_api
.
get_submission_uuids
(
**
kwargs
)
workflows
=
AssessmentWorkflow
.
objects
.
filter
(
submission_uuid__in
=
submission_uuids
)
return
[
{
"status"
:
status
,
"count"
:
workflows
.
filter
(
status
=
status
)
.
count
()}
for
status
in
AssessmentWorkflow
.
STATUS_VALUES
]
def
_get_workflow_model
(
submission_uuid
):
"""Return the `AssessmentWorkflow` model for a given `submission_uuid`.
...
...
apps/openassessment/workflow/models.py
View file @
3f5924a3
...
...
@@ -30,14 +30,16 @@ class AssessmentWorkflow(TimeStampedModel, StatusModel):
an after the fact recording of the last known state of that information so
we can search easily.
"""
STATUS
=
Choices
(
# implicit "status" field
STATUS
_VALUES
=
[
"peer"
,
# User needs to assess peer submissions
"self"
,
# User needs to assess themselves
"waiting"
,
# User has done all necessary assessment but hasn't been
# graded yet -- we're waiting for assessments of their
# submission by others.
"done"
,
# Complete
)
]
STATUS
=
Choices
(
*
STATUS_VALUES
)
# implicit "status" field
submission_uuid
=
models
.
CharField
(
max_length
=
36
,
db_index
=
True
,
unique
=
True
)
uuid
=
UUIDField
(
version
=
1
,
db_index
=
True
,
unique
=
True
)
...
...
apps/openassessment/workflow/test/test_api.py
View file @
3f5924a3
...
...
@@ -81,3 +81,68 @@ class TestAssessmentWorkflowApi(TestCase):
submission
=
sub_api
.
create_submission
(
ITEM_1
,
"We talk TV!"
)
workflow
=
workflow_api
.
create_workflow
(
submission
[
"uuid"
])
workflow_api
.
get_workflow_for_submission
(
workflow
[
"uuid"
],
REQUIREMENTS
)
def
test_get_status_counts
(
self
):
# Initially, the counts should all be zero
counts
=
workflow_api
.
get_status_counts
()
self
.
assertEqual
(
counts
,
[
{
"status"
:
"peer"
,
"count"
:
0
},
{
"status"
:
"self"
,
"count"
:
0
},
{
"status"
:
"waiting"
,
"count"
:
0
},
{
"status"
:
"done"
,
"count"
:
0
},
])
# Create assessments with each status
# We're going to cheat a little bit by using the model objects
# directly, since the API does not provide access to the status directly.
self
.
_create_workflow_with_status
(
"user 1"
,
"test/1/1"
,
"peer-problem"
,
"peer"
)
self
.
_create_workflow_with_status
(
"user 2"
,
"test/1/1"
,
"peer-problem"
,
"self"
)
self
.
_create_workflow_with_status
(
"user 3"
,
"test/1/1"
,
"peer-problem"
,
"self"
)
self
.
_create_workflow_with_status
(
"user 4"
,
"test/1/1"
,
"peer-problem"
,
"waiting"
)
self
.
_create_workflow_with_status
(
"user 5"
,
"test/1/1"
,
"peer-problem"
,
"waiting"
)
self
.
_create_workflow_with_status
(
"user 6"
,
"test/1/1"
,
"peer-problem"
,
"waiting"
)
self
.
_create_workflow_with_status
(
"user 7"
,
"test/1/1"
,
"peer-problem"
,
"done"
)
self
.
_create_workflow_with_status
(
"user 8"
,
"test/1/1"
,
"peer-problem"
,
"done"
)
self
.
_create_workflow_with_status
(
"user 9"
,
"test/1/1"
,
"peer-problem"
,
"done"
)
self
.
_create_workflow_with_status
(
"user 10"
,
"test/1/1"
,
"peer-problem"
,
"done"
)
# Now the counts should be updated
counts
=
workflow_api
.
get_status_counts
()
self
.
assertEqual
(
counts
,
[
{
"status"
:
"peer"
,
"count"
:
1
},
{
"status"
:
"self"
,
"count"
:
2
},
{
"status"
:
"waiting"
,
"count"
:
3
},
{
"status"
:
"done"
,
"count"
:
4
},
])
def
_create_workflow_with_status
(
self
,
student_id
,
course_id
,
item_id
,
status
,
item_type
=
"openassessment"
,
answer
=
"answer"
):
"""
Create a submission and workflow with a given status.
Args:
student_id (unicode): Student ID for the submission.
course_id (unicode): Course ID for the submission.
item_id (unicode): Item ID for the submission
status (unicode): One of acceptable status values (e.g. "peer", "self", "waiting", "done")
Kwargs:
item_type (unicode): Type of item for the submission.
answer (unicode): Submission answer.
Returns:
None
"""
submission
=
sub_api
.
create_submission
({
"student_id"
:
student_id
,
"course_id"
:
course_id
,
"item_id"
:
item_id
,
"item_type"
:
item_type
},
answer
)
AssessmentWorkflow
.
objects
.
create
(
submission_uuid
=
submission
[
'uuid'
],
status
=
status
)
apps/openassessment/xblock/openassessmentblock.py
View file @
3f5924a3
...
...
@@ -290,8 +290,15 @@ class OpenAssessmentBlock(
"question"
:
self
.
prompt
,
"rubric_criteria"
:
self
.
rubric_criteria
,
"rubric_assessments"
:
ui_models
,
"is_course_staff"
:
False
,
}
if
self
.
is_course_staff
:
status_counts
,
num_submissions
=
self
.
get_workflow_status_counts
()
context_dict
[
'is_course_staff'
]
=
True
context_dict
[
'status_counts'
]
=
status_counts
context_dict
[
'num_submissions'
]
=
num_submissions
template
=
get_template
(
"openassessmentblock/oa_base.html"
)
context
=
Context
(
context_dict
)
frag
=
Fragment
(
template
.
render
(
context
))
...
...
@@ -300,6 +307,19 @@ class OpenAssessmentBlock(
frag
.
initialize_js
(
'OpenAssessmentBlock'
)
return
frag
@property
def
is_course_staff
(
self
):
"""
Check whether the user has course staff permissions for this XBlock.
Returns:
bool
"""
if
hasattr
(
self
,
'xmodule_runtime'
):
return
getattr
(
self
.
xmodule_runtime
,
'user_is_staff'
,
False
)
else
:
return
False
def
_create_ui_models
(
self
):
"""Combine UI attributes and XBlock configuration into a UI model.
...
...
apps/openassessment/xblock/test/test_openassessment.py
View file @
3f5924a3
...
...
@@ -333,6 +333,37 @@ class TestDates(XBlockHandlerTestCase):
# If the runtime doesn't provide a published_date field, assume we've been published
self
.
assertTrue
(
xblock
.
is_released
())
@scenario
(
'data/basic_scenario.xml'
)
def
test_is_course_staff
(
self
,
xblock
):
# By default, we shouldn't be course staff
self
.
assertFalse
(
xblock
.
is_course_staff
)
# If the LMS runtime tells us we're not course staff,
# we shouldn't be course staff.
xblock
.
xmodule_runtime
=
Mock
(
user_is_staff
=
False
)
self
.
assertFalse
(
xblock
.
is_course_staff
)
# If the LMS runtime tells us that we ARE course staff,
# then we're course staff.
xblock
.
xmodule_runtime
.
user_is_staff
=
True
self
.
assertTrue
(
xblock
.
is_course_staff
)
@scenario
(
'data/basic_scenario.xml'
)
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_fragment
=
self
.
runtime
.
render
(
xblock
,
"student_view"
)
self
.
assertNotIn
(
"course staff debug"
,
xblock_fragment
.
body_html
()
.
lower
())
# If we ARE course staff, then we should see the debug info
xblock
.
xmodule_runtime
.
user_is_staff
=
True
xblock_fragment
=
self
.
runtime
.
render
(
xblock
,
"student_view"
)
self
.
assertIn
(
"course staff debug"
,
xblock_fragment
.
body_html
()
.
lower
())
def
assert_is_open
(
self
,
xblock
,
now
,
step
,
expected_is_open
,
expected_reason
,
released
=
None
):
"""
Assert whether the XBlock step is open/closed.
...
...
apps/openassessment/xblock/workflow_mixin.py
View file @
3f5924a3
...
...
@@ -67,3 +67,33 @@ class WorkflowMixin(object):
return
workflow_api
.
get_workflow_for_submission
(
self
.
submission_uuid
,
self
.
workflow_requirements
()
)
def
get_workflow_status_counts
(
self
):
"""
Retrieve the counts of students in each step of the workflow.
Returns:
tuple of (list, int), where the list contains dicts with keys
"status" (unicode value) and "count" (int value), and the
integer represents the total number of submissions.
Example Usage:
>>> status_counts, num_submissions = xblock.get_workflow_status_counts()
>>> num_submissions
12
>>> status_counts
[
{"status": "peer", "count": 2},
{"status": "self", "count": 1},
{"status": "waiting": "count": 4},
{"status": "done", "count": 5}
]
"""
student_item
=
self
.
get_student_item_dict
()
status_counts
=
workflow_api
.
get_status_counts
(
course_id
=
student_item
[
'course_id'
],
item_id
=
student_item
[
'item_id'
],
item_type
=
student_item
[
'item_type'
],
)
num_submissions
=
sum
(
item
[
'count'
]
for
item
in
status_counts
)
return
status_counts
,
num_submissions
apps/submissions/api.py
View file @
3f5924a3
...
...
@@ -4,6 +4,7 @@ Public interface for the submissions app.
"""
import
copy
import
logging
from
collections
import
defaultdict
from
django.core.cache
import
cache
from
django.db
import
IntegrityError
,
DatabaseError
...
...
@@ -465,6 +466,60 @@ def set_score(submission_uuid, score, points_possible):
pass
def
get_submission_uuids
(
course_id
=
None
,
item_id
=
None
,
item_type
=
None
):
"""
Return a list of submission UUIDs that have a particular course_id, item_id, and/or item_type.
Kwargs:
course_id (unicode): The ID of the course.
item_id (unicode): The ID of the item in the course.
item_type (unicode): The type of the item.
Returns:
list of submission UUIDs
Example Usage:
>>> get_submissions_for_course_item(course_id="ora2/1/1", item_id="peer-assessment-problem")
[
'de97b11169ab4761b80b56cc42f6cb4a',
'bcb9fcae309a4628859c72f1d33eb89c',
...
]
"""
kwargs
=
{}
if
course_id
is
not
None
:
kwargs
[
'student_item__course_id'
]
=
course_id
if
item_id
is
not
None
:
kwargs
[
'student_item__item_id'
]
=
item_id
if
item_type
is
not
None
:
kwargs
[
'student_item__item_type'
]
=
item_type
return
Submission
.
objects
.
filter
(
**
kwargs
)
.
values_list
(
'uuid'
,
flat
=
True
)
def
get_course_items
():
"""
Retrieve the IDs of all items with workflows, grouped by course.
Returns:
dict
Example Usage:
>>> get_course_items()
{
"test/course/1": ["item-1", "item-2", "item-3"],
"test/course/2": ["item-4", "item-5", "item-6"],
...
}
"""
courses_dict
=
defaultdict
(
list
)
items
=
StudentItem
.
objects
.
values_list
(
'course_id'
,
'item_id'
)
.
distinct
()
for
course_id
,
item_id
in
items
:
courses_dict
[
course_id
]
.
append
(
item_id
)
return
courses_dict
def
_get_or_create_student_item
(
student_item_dict
):
"""Gets or creates a Student Item that matches the values specified.
...
...
apps/submissions/tests/test_api.py
View file @
3f5924a3
...
...
@@ -177,6 +177,61 @@ class TestSubmissionsApi(TestCase):
submissions
=
api
.
get_submissions
(
STUDENT_ITEM
,
1
)
self
.
assertEqual
(
u"Testing unicode answers."
,
submissions
[
0
][
"answer"
])
def
test_get_course_items
(
self
):
# Initially, no course items available
self
.
assertEqual
(
api
.
get_course_items
(),
dict
())
# Same course, same item
api
.
create_submission
({
'student_id'
:
'Tim'
,
'course_id'
:
'Foo'
,
'item_id'
:
'Bar'
,
'item_type'
:
'openassessment'
},
"answer 1"
)
api
.
create_submission
({
'student_id'
:
'Alice'
,
'course_id'
:
'Foo'
,
'item_id'
:
'Bar'
,
'item_type'
:
'openassessment'
},
"answer 2"
)
self
.
assertItemsEqual
(
api
.
get_course_items
(),
{
"Foo"
:
[
"Bar"
]})
# Same course, different items
api
.
create_submission
({
'student_id'
:
'Alice'
,
'course_id'
:
'Foo'
,
'item_id'
:
'Different'
,
'item_type'
:
'openassessment'
},
"answer 3"
)
course_items
=
api
.
get_course_items
()
self
.
assertItemsEqual
(
course_items
.
keys
(),
[
"Foo"
])
self
.
assertItemsEqual
(
api
.
get_course_items
()[
"Foo"
],
[
"Bar"
,
"Different"
])
# Different course, same item
api
.
create_submission
({
'student_id'
:
'Alice'
,
'course_id'
:
'Lorem'
,
'item_id'
:
'Bar'
,
'item_type'
:
'openassessment'
},
"answer 3"
)
course_items
=
api
.
get_course_items
()
self
.
assertItemsEqual
(
course_items
.
keys
(),
[
"Foo"
,
"Lorem"
])
self
.
assertItemsEqual
(
api
.
get_course_items
()[
"Foo"
],
[
"Bar"
,
"Different"
])
self
.
assertItemsEqual
(
api
.
get_course_items
()[
"Lorem"
],
[
"Bar"
])
# Different course, different item
api
.
create_submission
({
'student_id'
:
'Alice'
,
'course_id'
:
'Ipsum'
,
'item_id'
:
'Dolar'
,
'item_type'
:
'openassessment'
},
"answer 3"
)
course_items
=
api
.
get_course_items
()
self
.
assertItemsEqual
(
course_items
.
keys
(),
[
"Foo"
,
"Lorem"
,
"Ipsum"
])
self
.
assertItemsEqual
(
api
.
get_course_items
()[
"Foo"
],
[
"Bar"
,
"Different"
])
self
.
assertItemsEqual
(
api
.
get_course_items
()[
"Lorem"
],
[
"Bar"
])
self
.
assertItemsEqual
(
api
.
get_course_items
()[
"Ipsum"
],
[
"Dolar"
])
def
_assert_submission
(
self
,
submission
,
expected_answer
,
expected_item
,
expected_attempt
):
self
.
assertIsNotNone
(
submission
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment