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
db615fab
Commit
db615fab
authored
Mar 17, 2014
by
Stephen Sanchez
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Setting additional metadata for completing a peer workflow
parent
ad1a8f86
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
320 additions
and
198 deletions
+320
-198
apps/openassessment/assessment/migrations/0004_auto__add_field_peerworkflow_completed_at__add_field_peerworkflowitem_.py
+99
-0
apps/openassessment/assessment/models.py
+18
-6
apps/openassessment/assessment/peer_api.py
+53
-56
apps/openassessment/assessment/self_api.py
+8
-22
apps/openassessment/assessment/serializers.py
+36
-60
apps/openassessment/assessment/test/test_peer.py
+70
-4
apps/openassessment/assessment/test/test_self.py
+6
-13
apps/openassessment/management/tests/test_create_oa_submissions.py
+3
-6
apps/openassessment/xblock/grade_mixin.py
+20
-24
apps/openassessment/xblock/self_assessment_mixin.py
+4
-3
apps/openassessment/xblock/test/test_peer.py
+1
-2
apps/openassessment/xblock/test/test_self.py
+2
-2
No files found.
apps/openassessment/assessment/migrations/0004_auto__add_field_peerworkflow_completed_at__add_field_peerworkflowitem_.py
0 → 100644
View file @
db615fab
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding field 'PeerWorkflow.completed_at'
db
.
add_column
(
'assessment_peerworkflow'
,
'completed_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
null
=
True
),
keep_default
=
False
)
# Adding field 'PeerWorkflowItem.scored'
db
.
add_column
(
'assessment_peerworkflowitem'
,
'scored'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
False
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'PeerWorkflow.completed_at'
db
.
delete_column
(
'assessment_peerworkflow'
,
'completed_at'
)
# Deleting field 'PeerWorkflowItem.scored'
db
.
delete_column
(
'assessment_peerworkflowitem'
,
'scored'
)
models
=
{
'assessment.assessment'
:
{
'Meta'
:
{
'ordering'
:
"['-scored_at', '-id']"
,
'object_name'
:
'Assessment'
},
'feedback'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'max_length'
:
'10000'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.Rubric']"
}),
'score_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'2'
}),
'scored_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
'db_index'
:
'True'
}),
'scorer_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'submission_uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
})
},
'assessment.assessmentfeedback'
:
{
'Meta'
:
{
'object_name'
:
'AssessmentFeedback'
},
'assessments'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'assessment_feedback'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['assessment.Assessment']"
}),
'feedback'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'max_length'
:
'10000'
}),
'helpfulness'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'2'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'submission_uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'128'
,
'db_index'
:
'True'
})
},
'assessment.assessmentpart'
:
{
'Meta'
:
{
'object_name'
:
'AssessmentPart'
},
'assessment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'parts'"
,
'to'
:
"orm['assessment.Assessment']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'option'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.CriterionOption']"
})
},
'assessment.criterion'
:
{
'Meta'
:
{
'ordering'
:
"['rubric', 'order_num']"
,
'object_name'
:
'Criterion'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'order_num'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{}),
'prompt'
:
(
'django.db.models.fields.TextField'
,
[],
{
'max_length'
:
'10000'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'criteria'"
,
'to'
:
"orm['assessment.Rubric']"
})
},
'assessment.criterionoption'
:
{
'Meta'
:
{
'ordering'
:
"['criterion', 'order_num']"
,
'object_name'
:
'CriterionOption'
},
'criterion'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'options'"
,
'to'
:
"orm['assessment.Criterion']"
}),
'explanation'
:
(
'django.db.models.fields.TextField'
,
[],
{
'max_length'
:
'10000'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'order_num'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{}),
'points'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{})
},
'assessment.peerworkflow'
:
{
'Meta'
:
{
'ordering'
:
"['created_at', 'id']"
,
'object_name'
:
'PeerWorkflow'
},
'completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'item_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'student_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'submission_uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'128'
,
'db_index'
:
'True'
})
},
'assessment.peerworkflowitem'
:
{
'Meta'
:
{
'ordering'
:
"['started_at', 'id']"
,
'object_name'
:
'PeerWorkflowItem'
},
'assessment'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'-1'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'scored'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'scorer_id'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'items'"
,
'to'
:
"orm['assessment.PeerWorkflow']"
}),
'started_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
'db_index'
:
'True'
}),
'submission_uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
})
},
'assessment.rubric'
:
{
'Meta'
:
{
'object_name'
:
'Rubric'
},
'content_hash'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
}
}
complete_apps
=
[
'assessment'
]
\ No newline at end of file
apps/openassessment/assessment/models.py
View file @
db615fab
...
@@ -307,7 +307,7 @@ class Assessment(models.Model):
...
@@ -307,7 +307,7 @@ class Assessment(models.Model):
return
median_score
return
median_score
@classmethod
@classmethod
def
scores_by_criterion
(
cls
,
submission_uuid
,
must_be_graded_by
):
def
scores_by_criterion
(
cls
,
assessments
):
"""Create a dictionary of lists for scores associated with criterion
"""Create a dictionary of lists for scores associated with criterion
Create a key value in a dict with a list of values, for every criterion
Create a key value in a dict with a list of values, for every criterion
...
@@ -318,18 +318,17 @@ class Assessment(models.Model):
...
@@ -318,18 +318,17 @@ class Assessment(models.Model):
of scores.
of scores.
Args:
Args:
submission_uuid (str): Obtain assessments associated with this submission.
assessments (list): List of assessments to sort scores by their
must_be_graded_by (int): The number of assessments to include in this score analysis
.
associated criteria
.
Examples:
Examples:
>>> Assessment.scores_by_criterion('abcd', 3)
>>> assessments = Assessment.objects.all()
>>> Assessment.scores_by_criterion(assessments)
{
{
"foo": [1, 2, 3],
"foo": [1, 2, 3],
"bar": [6, 7, 8]
"bar": [6, 7, 8]
}
}
"""
"""
assessments
=
cls
.
objects
.
filter
(
submission_uuid
=
submission_uuid
)
.
order_by
(
"scored_at"
)[:
must_be_graded_by
]
scores
=
defaultdict
(
list
)
scores
=
defaultdict
(
list
)
for
assessment
in
assessments
:
for
assessment
in
assessments
:
for
part
in
assessment
.
parts
.
all
():
for
part
in
assessment
.
parts
.
all
():
...
@@ -401,6 +400,7 @@ class PeerWorkflow(models.Model):
...
@@ -401,6 +400,7 @@ class PeerWorkflow(models.Model):
course_id
=
models
.
CharField
(
max_length
=
40
,
db_index
=
True
)
course_id
=
models
.
CharField
(
max_length
=
40
,
db_index
=
True
)
submission_uuid
=
models
.
CharField
(
max_length
=
128
,
db_index
=
True
,
unique
=
True
)
submission_uuid
=
models
.
CharField
(
max_length
=
128
,
db_index
=
True
,
unique
=
True
)
created_at
=
models
.
DateTimeField
(
default
=
now
,
db_index
=
True
)
created_at
=
models
.
DateTimeField
(
default
=
now
,
db_index
=
True
)
completed_at
=
models
.
DateTimeField
(
null
=
True
)
class
Meta
:
class
Meta
:
ordering
=
[
"created_at"
,
"id"
]
ordering
=
[
"created_at"
,
"id"
]
...
@@ -433,6 +433,18 @@ class PeerWorkflowItem(models.Model):
...
@@ -433,6 +433,18 @@ class PeerWorkflowItem(models.Model):
started_at
=
models
.
DateTimeField
(
default
=
now
,
db_index
=
True
)
started_at
=
models
.
DateTimeField
(
default
=
now
,
db_index
=
True
)
assessment
=
models
.
IntegerField
(
default
=-
1
)
assessment
=
models
.
IntegerField
(
default
=-
1
)
# This WorkflowItem was used to determine the final score for the Workflow.
scored
=
models
.
BooleanField
(
default
=
False
)
@classmethod
def
get_scored_assessments
(
cls
,
submission_uuid
):
workflow_items
=
PeerWorkflowItem
.
objects
.
filter
(
submission_uuid
=
submission_uuid
,
scored
=
True
)
return
Assessment
.
objects
.
filter
(
pk__in
=
[
item
.
pk
for
item
in
workflow_items
]
)
class
Meta
:
class
Meta
:
ordering
=
[
"started_at"
,
"id"
]
ordering
=
[
"started_at"
,
"id"
]
...
...
apps/openassessment/assessment/peer_api.py
View file @
db615fab
This diff is collapsed.
Click to expand it.
apps/openassessment/assessment/self_api.py
View file @
db615fab
...
@@ -92,35 +92,22 @@ def create_assessment(submission_uuid, user_id, options_selected, rubric_dict, s
...
@@ -92,35 +92,22 @@ def create_assessment(submission_uuid, user_id, options_selected, rubric_dict, s
return
serializer
.
data
return
serializer
.
data
def
get_
submission_and_
assessment
(
submission_uuid
):
def
get_assessment
(
submission_uuid
):
"""
"""
Retrieve a s
ubmission and self-assessment for a student item
.
Retrieve a s
elf-assessment for a submission_uuid
.
Args:
Args:
submission_uuid (str): The submission
uuid
for we want information for
submission_uuid (str): The submission
UUID
for we want information for
regarding self assessment.
regarding self assessment.
Returns:
Returns:
A tuple `(submission, assessment)` where:
assessment (dict) is a serialized Assessment model, or None (if the user has not yet self-assessed)
submission (dict) is a serialized Submission model, or None (if the user has not yet made a submission)
assessment (dict) is a serialized Assessment model, or None (if the user has not yet self-assessed)
If multiple submissions or self-assessments are found, returns the most recent one.
If multiple submissions or self-assessments are found, returns the most recent one.
Raises:
Raises:
SelfAssessmentRequestError:
Student item dict
was invalid.
SelfAssessmentRequestError:
submission_uuid
was invalid.
"""
"""
# Look up the most recent submission from the student item
# Retrieve assessments for the submission UUID
try
:
submission
=
get_submission
(
submission_uuid
)
if
not
submission
:
return
(
None
,
None
)
except
SubmissionNotFoundError
:
return
(
None
,
None
)
except
SubmissionRequestError
:
raise
SelfAssessmentRequestError
(
_
(
'Could not retrieve submission'
))
# Retrieve assessments for the submission
# We weakly enforce that number of self-assessments per submission is <= 1,
# We weakly enforce that number of self-assessments per submission is <= 1,
# but not at the database level. Someone could take advantage of the race condition
# but not at the database level. Someone could take advantage of the race condition
# between checking the number of self-assessments and creating a new self-assessment.
# between checking the number of self-assessments and creating a new self-assessment.
...
@@ -131,9 +118,8 @@ def get_submission_and_assessment(submission_uuid):
...
@@ -131,9 +118,8 @@ def get_submission_and_assessment(submission_uuid):
if
assessments
.
exists
():
if
assessments
.
exists
():
assessment_dict
=
full_assessment_dict
(
assessments
[
0
])
assessment_dict
=
full_assessment_dict
(
assessments
[
0
])
return
submission
,
assessment_dict
return
assessment_dict
else
:
return
None
return
submission
,
None
def
is_complete
(
submission_uuid
):
def
is_complete
(
submission_uuid
):
...
...
apps/openassessment/assessment/serializers.py
View file @
db615fab
...
@@ -8,8 +8,8 @@ from copy import deepcopy
...
@@ -8,8 +8,8 @@ from copy import deepcopy
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
rest_framework
import
serializers
from
rest_framework
import
serializers
from
openassessment.assessment.models
import
(
from
openassessment.assessment.models
import
(
Assessment
,
AssessmentFeedback
,
AssessmentPart
,
Criterion
,
CriterionOption
,
Rubric
Assessment
,
AssessmentFeedback
,
AssessmentPart
,
Criterion
,
CriterionOption
,
Rubric
,
)
PeerWorkflowItem
,
PeerWorkflow
)
class
InvalidRubric
(
Exception
):
class
InvalidRubric
(
Exception
):
...
@@ -132,61 +132,6 @@ class AssessmentSerializer(serializers.ModelSerializer):
...
@@ -132,61 +132,6 @@ class AssessmentSerializer(serializers.ModelSerializer):
)
)
def
get_assessment_review
(
submission_uuid
):
"""Get all information pertaining to an assessment for review.
Given an assessment serializer, return a serializable formatted model of
the assessment, all assessment parts, all criterion options, and the
associated rubric.
Args:
submission_uuid (str): The UUID of the submission whose assessment reviews we want to retrieve.
Returns:
(list): A list of assessment reviews, combining assessments with
rubrics and assessment parts, to allow a cohesive object for
rendering the complete peer grading workflow.
Examples:
>>> get_assessment_review(submission, score_type)
[{
'submission': 1,
'rubric': {
'id': 1,
'content_hash': u'45cc932c4da12a1c2b929018cd6f0785c1f8bc07',
'criteria': [{
'order_num': 0,
'name': u'Spelling',
'prompt': u'Did the student have spelling errors?',
'options': [{
'order_num': 0,
'points': 2,
'name': u'No spelling errors',
'explanation': u'No spelling errors were found in this submission.',
}]
}]
},
'scored_at': datetime.datetime(2014, 2, 25, 19, 50, 7, 290464, tzinfo=<UTC>),
'scorer_id': u'Bob',
'score_type': u'PE',
'parts': [{
'option': {
'order_num': 0,
'points': 2,
'name': u'No spelling errors',
'explanation': u'No spelling errors were found in this submission.'}
}],
'submission_uuid': u'0a600160-be7f-429d-a853-1283d49205e7',
'points_earned': 9,
'points_possible': 20,
}]
"""
return
[
full_assessment_dict
(
assessment
)
for
assessment
in
Assessment
.
objects
.
filter
(
submission_uuid
=
submission_uuid
)
]
def
full_assessment_dict
(
assessment
):
def
full_assessment_dict
(
assessment
):
"""
"""
Return a dict representation of the Assessment model,
Return a dict representation of the Assessment model,
...
@@ -278,10 +223,41 @@ class AssessmentFeedbackSerializer(serializers.ModelSerializer):
...
@@ -278,10 +223,41 @@ class AssessmentFeedbackSerializer(serializers.ModelSerializer):
class
Meta
:
class
Meta
:
model
=
AssessmentFeedback
model
=
AssessmentFeedback
fields
=
(
'submission_uuid'
,
'helpfulness'
,
'feedback'
,
'assessments'
,)
class
PeerWorkflowSerializer
(
serializers
.
ModelSerializer
):
"""Representation of the PeerWorkflow.
A PeerWorkflow should not be exposed to the front end of any question. This
model should only be exposed externally for administrative views, in order
to visualize the Peer Workflow.
"""
class
Meta
:
model
=
PeerWorkflow
fields
=
(
fields
=
(
'student_id'
,
'item_id'
,
'course_id'
,
'submission_uuid'
,
'submission_uuid'
,
'helpfulness'
,
'created_at'
,
'feedback'
,
'completed_at'
'assessments'
,
)
)
class
PeerWorkflowItemSerializer
(
serializers
.
ModelSerializer
):
"""Representation of the PeerWorkflowItem
As with the PeerWorkflow, this should not be exposed to the front end. This
should only be used to visualize the Peer Workflow in an administrative
view.
"""
class
Meta
:
model
=
PeerWorkflowItem
fields
=
(
'scorer_id'
,
'submission_uuid'
,
'started_at'
,
'assessment'
,
'scored'
)
apps/openassessment/assessment/test/test_peer.py
View file @
db615fab
...
@@ -10,7 +10,7 @@ from mock import patch
...
@@ -10,7 +10,7 @@ from mock import patch
from
nose.tools
import
raises
from
nose.tools
import
raises
from
openassessment.assessment
import
peer_api
from
openassessment.assessment
import
peer_api
from
openassessment.assessment.models
import
Assessment
,
PeerWorkflow
,
PeerWorkflowItem
from
openassessment.assessment.models
import
Assessment
,
PeerWorkflow
,
PeerWorkflowItem
,
AssessmentFeedback
from
openassessment.workflow
import
api
as
workflow_api
from
openassessment.workflow
import
api
as
workflow_api
from
submissions
import
api
as
sub_api
from
submissions
import
api
as
sub_api
from
submissions.models
import
Submission
from
submissions.models
import
Submission
...
@@ -136,7 +136,7 @@ class TestPeerApi(TestCase):
...
@@ -136,7 +136,7 @@ class TestPeerApi(TestCase):
assessment_dict
,
assessment_dict
,
RUBRIC_DICT
,
RUBRIC_DICT
,
)
)
assessments
=
peer_api
.
get_assessments
(
sub
[
"uuid"
])
assessments
=
peer_api
.
get_assessments
(
sub
[
"uuid"
]
,
scored_only
=
False
)
self
.
assertEqual
(
1
,
len
(
assessments
))
self
.
assertEqual
(
1
,
len
(
assessments
))
@file_data
(
'valid_assessments.json'
)
@file_data
(
'valid_assessments.json'
)
...
@@ -151,7 +151,7 @@ class TestPeerApi(TestCase):
...
@@ -151,7 +151,7 @@ class TestPeerApi(TestCase):
RUBRIC_DICT
,
RUBRIC_DICT
,
MONDAY
MONDAY
)
)
assessments
=
peer_api
.
get_assessments
(
sub
[
"uuid"
])
assessments
=
peer_api
.
get_assessments
(
sub
[
"uuid"
]
,
scored_only
=
False
)
self
.
assertEqual
(
1
,
len
(
assessments
))
self
.
assertEqual
(
1
,
len
(
assessments
))
self
.
assertEqual
(
assessments
[
0
][
"scored_at"
],
MONDAY
)
self
.
assertEqual
(
assessments
[
0
][
"scored_at"
],
MONDAY
)
...
@@ -480,6 +480,45 @@ class TestPeerApi(TestCase):
...
@@ -480,6 +480,45 @@ class TestPeerApi(TestCase):
submission_uuid
=
peer_api
.
_get_submission_for_over_grading
(
xander_workflow
)
submission_uuid
=
peer_api
.
_get_submission_for_over_grading
(
xander_workflow
)
self
.
assertEqual
(
buffy_answer
[
"uuid"
],
submission_uuid
)
self
.
assertEqual
(
buffy_answer
[
"uuid"
],
submission_uuid
)
def
test_create_assessment_feedback
(
self
):
tim_sub
,
tim
=
self
.
_create_student_and_submission
(
"Tim"
,
"Tim's answer"
)
bob_sub
,
bob
=
self
.
_create_student_and_submission
(
"Bob"
,
"Bob's answer"
)
sub
=
peer_api
.
get_submission_to_assess
(
bob
,
1
)
assessment
=
peer_api
.
create_assessment
(
sub
[
"uuid"
],
bob
[
"student_id"
],
ASSESSMENT_DICT
,
RUBRIC_DICT
,
)
sub
=
peer_api
.
get_submission_to_assess
(
tim
,
1
)
peer_api
.
create_assessment
(
sub
[
"uuid"
],
tim
[
"student_id"
],
ASSESSMENT_DICT
,
RUBRIC_DICT
,
)
peer_api
.
get_score
(
tim_sub
[
"uuid"
],
{
'must_grade'
:
1
,
'must_be_graded_by'
:
1
}
)
feedback
=
peer_api
.
get_assessment_feedback
(
tim_sub
[
'uuid'
])
self
.
assertIsNone
(
feedback
)
feedback
=
peer_api
.
set_assessment_feedback
(
{
'submission_uuid'
:
tim_sub
[
'uuid'
],
'helpfulness'
:
0
,
'feedback'
:
'Bob is a jerk!'
}
)
self
.
assertIsNotNone
(
feedback
)
self
.
assertEquals
(
feedback
[
"assessments"
][
0
][
"submission_uuid"
],
assessment
[
"submission_uuid"
])
saved_feedback
=
peer_api
.
get_assessment_feedback
(
tim_sub
[
'uuid'
])
self
.
assertEquals
(
feedback
,
saved_feedback
)
def
test_close_active_assessment
(
self
):
def
test_close_active_assessment
(
self
):
buffy_answer
,
buffy
=
self
.
_create_student_and_submission
(
"Buffy"
,
"Buffy's answer"
)
buffy_answer
,
buffy
=
self
.
_create_student_and_submission
(
"Buffy"
,
"Buffy's answer"
)
xander_answer
,
xander
=
self
.
_create_student_and_submission
(
"Xander"
,
"Xander's answer"
)
xander_answer
,
xander
=
self
.
_create_student_and_submission
(
"Xander"
,
"Xander's answer"
)
...
@@ -512,6 +551,33 @@ class TestPeerApi(TestCase):
...
@@ -512,6 +551,33 @@ class TestPeerApi(TestCase):
mock_filter
.
side_effect
=
DatabaseError
(
"Oh no."
)
mock_filter
.
side_effect
=
DatabaseError
(
"Oh no."
)
peer_api
.
_get_submission_for_review
(
tim_workflow
,
3
)
peer_api
.
_get_submission_for_review
(
tim_workflow
,
3
)
@patch.object
(
AssessmentFeedback
.
objects
,
'get'
)
@raises
(
peer_api
.
PeerAssessmentInternalError
)
def
test_get_assessment_feedback_error
(
self
,
mock_filter
):
mock_filter
.
side_effect
=
DatabaseError
(
"Oh no."
)
tim_answer
,
tim
=
self
.
_create_student_and_submission
(
"Tim"
,
"Tim's answer"
,
MONDAY
)
peer_api
.
get_assessment_feedback
(
tim_answer
[
'uuid'
])
@patch.object
(
PeerWorkflowItem
,
'get_scored_assessments'
)
@raises
(
peer_api
.
PeerAssessmentInternalError
)
def
test_set_assessment_feedback_error
(
self
,
mock_filter
):
mock_filter
.
side_effect
=
DatabaseError
(
"Oh no."
)
tim_answer
,
tim
=
self
.
_create_student_and_submission
(
"Tim"
,
"Tim's answer"
,
MONDAY
)
peer_api
.
set_assessment_feedback
({
'submission_uuid'
:
tim_answer
[
'uuid'
]})
@patch.object
(
AssessmentFeedback
,
'save'
)
@raises
(
peer_api
.
PeerAssessmentInternalError
)
def
test_set_assessment_feedback_error_on_save
(
self
,
mock_filter
):
mock_filter
.
side_effect
=
DatabaseError
(
"Oh no."
)
tim_answer
,
tim
=
self
.
_create_student_and_submission
(
"Tim"
,
"Tim's answer"
,
MONDAY
)
peer_api
.
set_assessment_feedback
(
{
'submission_uuid'
:
tim_answer
[
'uuid'
],
'helpfulness'
:
0
,
'feedback'
:
'Boo'
,
}
)
@patch.object
(
PeerWorkflow
.
objects
,
'filter'
)
@patch.object
(
PeerWorkflow
.
objects
,
'filter'
)
@raises
(
peer_api
.
PeerAssessmentWorkflowError
)
@raises
(
peer_api
.
PeerAssessmentWorkflowError
)
def
test_failure_to_get_latest_workflow
(
self
,
mock_filter
):
def
test_failure_to_get_latest_workflow
(
self
,
mock_filter
):
...
@@ -581,7 +647,7 @@ class TestPeerApi(TestCase):
...
@@ -581,7 +647,7 @@ class TestPeerApi(TestCase):
def
test_median_score_db_error
(
self
,
mock_filter
):
def
test_median_score_db_error
(
self
,
mock_filter
):
mock_filter
.
side_effect
=
DatabaseError
(
"Bad things happened"
)
mock_filter
.
side_effect
=
DatabaseError
(
"Bad things happened"
)
tim
,
_
=
self
.
_create_student_and_submission
(
"Tim"
,
"Tim's answer"
)
tim
,
_
=
self
.
_create_student_and_submission
(
"Tim"
,
"Tim's answer"
)
peer_api
.
get_assessment_median_scores
(
tim
[
"uuid"
]
,
3
)
peer_api
.
get_assessment_median_scores
(
tim
[
"uuid"
])
@patch.object
(
Assessment
.
objects
,
'filter'
)
@patch.object
(
Assessment
.
objects
,
'filter'
)
@raises
(
peer_api
.
PeerAssessmentInternalError
)
@raises
(
peer_api
.
PeerAssessmentInternalError
)
...
...
apps/openassessment/assessment/test/test_self.py
View file @
db615fab
...
@@ -9,8 +9,7 @@ import pytz
...
@@ -9,8 +9,7 @@ import pytz
from
django.test
import
TestCase
from
django.test
import
TestCase
from
submissions.api
import
create_submission
from
submissions.api
import
create_submission
from
openassessment.assessment.self_api
import
(
from
openassessment.assessment.self_api
import
(
create_assessment
,
get_submission_and_assessment
,
is_complete
,
create_assessment
,
is_complete
,
SelfAssessmentRequestError
,
get_assessment
SelfAssessmentRequestError
)
)
...
@@ -53,16 +52,13 @@ class TestSelfApi(TestCase):
...
@@ -53,16 +52,13 @@ class TestSelfApi(TestCase):
def
test_create_assessment
(
self
):
def
test_create_assessment
(
self
):
# Initially, there should be no submission or self assessment
# Initially, there should be no submission or self assessment
self
.
assertEqual
(
get_
submission_and_assessment
(
"5"
),
(
None
,
None
)
)
self
.
assertEqual
(
get_
assessment
(
"5"
),
None
)
# Create a submission to self-assess
# Create a submission to self-assess
submission
=
create_submission
(
self
.
STUDENT_ITEM
,
"Test answer"
)
submission
=
create_submission
(
self
.
STUDENT_ITEM
,
"Test answer"
)
# Now there should be a submission, but no self-assessment
# Now there should be a submission, but no self-assessment
received_submission
,
assessment
=
get_submission_and_assessment
(
assessment
=
get_assessment
(
submission
[
"uuid"
])
submission
[
"uuid"
]
)
self
.
assertItemsEqual
(
received_submission
,
submission
)
self
.
assertIs
(
assessment
,
None
)
self
.
assertIs
(
assessment
,
None
)
self
.
assertFalse
(
is_complete
(
submission
[
'uuid'
]))
self
.
assertFalse
(
is_complete
(
submission
[
'uuid'
]))
...
@@ -77,10 +73,7 @@ class TestSelfApi(TestCase):
...
@@ -77,10 +73,7 @@ class TestSelfApi(TestCase):
self
.
assertTrue
(
is_complete
(
submission
[
'uuid'
]))
self
.
assertTrue
(
is_complete
(
submission
[
'uuid'
]))
# Retrieve the self-assessment
# Retrieve the self-assessment
received_submission
,
retrieved
=
get_submission_and_assessment
(
retrieved
=
get_assessment
(
submission
[
"uuid"
])
submission
[
"uuid"
]
)
self
.
assertItemsEqual
(
received_submission
,
submission
)
# Check that the assessment we created matches the assessment we retrieved
# Check that the assessment we created matches the assessment we retrieved
# and that both have the correct values
# and that both have the correct values
...
@@ -175,7 +168,7 @@ class TestSelfApi(TestCase):
...
@@ -175,7 +168,7 @@ class TestSelfApi(TestCase):
)
)
# Retrieve the self-assessment
# Retrieve the self-assessment
_
,
retrieved
=
get_submission_and
_assessment
(
submission
[
"uuid"
])
retrieved
=
get
_assessment
(
submission
[
"uuid"
])
# Expect that both the created and retrieved assessments have the same
# Expect that both the created and retrieved assessments have the same
# timestamp, and it's >= our recorded time.
# timestamp, and it's >= our recorded time.
...
@@ -200,7 +193,7 @@ class TestSelfApi(TestCase):
...
@@ -200,7 +193,7 @@ class TestSelfApi(TestCase):
)
)
# Expect that we still have the original assessment
# Expect that we still have the original assessment
_
,
retrieved
=
get_submission_and
_assessment
(
submission
[
"uuid"
])
retrieved
=
get
_assessment
(
submission
[
"uuid"
])
self
.
assertItemsEqual
(
assessment
,
retrieved
)
self
.
assertItemsEqual
(
assessment
,
retrieved
)
def
test_is_complete_no_submission
(
self
):
def
test_is_complete_no_submission
(
self
):
...
...
apps/openassessment/management/tests/test_create_oa_submissions.py
View file @
db615fab
...
@@ -29,20 +29,17 @@ class CreateSubmissionsTest(TestCase):
...
@@ -29,20 +29,17 @@ class CreateSubmissionsTest(TestCase):
self
.
assertGreater
(
len
(
submissions
[
0
][
'answer'
]),
0
)
self
.
assertGreater
(
len
(
submissions
[
0
][
'answer'
]),
0
)
# Check that peer and self assessments were created
# Check that peer and self assessments were created
assessments
=
peer_api
.
get_assessments
(
submissions
[
0
][
'uuid'
])
assessments
=
peer_api
.
get_assessments
(
submissions
[
0
][
'uuid'
]
,
scored_only
=
False
)
# Verify that the assessments exist and have content
# Verify that the assessments exist and have content
# TODO: currently peer_api.get_assessments() returns both peer- and self-assessments
self
.
assertEqual
(
len
(
assessments
),
cmd
.
NUM_PEER_ASSESSMENTS
)
# When this call gets split, we'll need to update the test
self
.
assertEqual
(
len
(
assessments
),
cmd
.
NUM_PEER_ASSESSMENTS
+
1
)
for
assessment
in
assessments
:
for
assessment
in
assessments
:
self
.
assertGreater
(
assessment
[
'points_possible'
],
0
)
self
.
assertGreater
(
assessment
[
'points_possible'
],
0
)
# Check that a self-assessment was created
# Check that a self-assessment was created
submission
,
assessment
=
self_api
.
get_submission_and
_assessment
(
submissions
[
0
][
'uuid'
])
assessment
=
self_api
.
get
_assessment
(
submissions
[
0
][
'uuid'
])
# Verify that the assessment exists and has content
# Verify that the assessment exists and has content
self
.
assertIsNot
(
submission
,
None
)
self
.
assertIsNot
(
assessment
,
None
)
self
.
assertIsNot
(
assessment
,
None
)
self
.
assertGreater
(
assessment
[
'points_possible'
],
0
)
self
.
assertGreater
(
assessment
[
'points_possible'
],
0
)
apps/openassessment/xblock/grade_mixin.py
View file @
db615fab
...
@@ -4,6 +4,8 @@ from django.utils.translation import ugettext as _
...
@@ -4,6 +4,8 @@ from django.utils.translation import ugettext as _
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
openassessment.assessment
import
peer_api
from
openassessment.assessment
import
peer_api
from
openassessment.assessment
import
self_api
from
submissions
import
api
as
submission_api
class
GradeMixin
(
object
):
class
GradeMixin
(
object
):
...
@@ -23,28 +25,24 @@ class GradeMixin(object):
...
@@ -23,28 +25,24 @@ class GradeMixin(object):
status
=
workflow
.
get
(
'status'
)
status
=
workflow
.
get
(
'status'
)
context
=
{}
context
=
{}
if
status
==
"done"
:
if
status
==
"done"
:
feedback
=
peer_api
.
get_assessment_feedback
(
self
.
submission_uuid
)
try
:
feedback_text
=
feedback
.
get
(
'feedback'
,
''
)
if
feedback
else
''
feedback
=
peer_api
.
get_assessment_feedback
(
self
.
submission_uuid
)
max_scores
=
peer_api
.
get_rubric_max_scores
(
self
.
submission_uuid
)
feedback_text
=
feedback
.
get
(
'feedback'
,
''
)
if
feedback
else
''
path
=
'openassessmentblock/grade/oa_grade_complete.html'
max_scores
=
peer_api
.
get_rubric_max_scores
(
self
.
submission_uuid
)
assessment_ui_model
=
self
.
get_assessment_module
(
'peer-assessment'
)
path
=
'openassessmentblock/grade/oa_grade_complete.html'
student_submission
=
self
.
get_user_submission
(
student_submission
=
submission_api
.
get_submission
(
workflow
[
"submission_uuid"
])
workflow
[
"submission_uuid"
]
student_score
=
workflow
[
"score"
]
)
peer_assessments
=
peer_api
.
get_assessments
(
student_submission
[
"uuid"
])
student_score
=
workflow
[
"score"
]
self_assessment
=
self_api
.
get_assessment
(
student_submission
[
"uuid"
])
assessments
=
peer_api
.
get_assessments
(
student_submission
[
"uuid"
])
median_scores
=
peer_api
.
get_assessment_median_scores
(
peer_assessments
=
[]
student_submission
[
"uuid"
]
self_assessment
=
None
)
for
assessment
in
assessments
:
except
(
if
assessment
[
"score_type"
]
==
"PE"
:
submission_api
.
SubmissionError
,
peer_assessments
.
append
(
assessment
)
peer_api
.
PeerAssessmentError
,
else
:
self_api
.
SelfAssessmentRequestError
self_assessment
=
assessment
):
peer_assessments
=
peer_assessments
[:
assessment_ui_model
[
"must_grade"
]]
return
self
.
render_error
(
_
(
u"An unexpected error occurred."
))
median_scores
=
peer_api
.
get_assessment_median_scores
(
student_submission
[
"uuid"
],
assessment_ui_model
[
"must_be_graded_by"
]
)
context
[
"feedback_text"
]
=
feedback_text
context
[
"feedback_text"
]
=
feedback_text
context
[
"student_submission"
]
=
student_submission
context
[
"student_submission"
]
=
student_submission
context
[
"peer_assessments"
]
=
peer_assessments
context
[
"peer_assessments"
]
=
peer_assessments
...
@@ -75,7 +73,6 @@ class GradeMixin(object):
...
@@ -75,7 +73,6 @@ class GradeMixin(object):
@XBlock.json_handler
@XBlock.json_handler
def
feedback_submit
(
self
,
data
,
suffix
=
''
):
def
feedback_submit
(
self
,
data
,
suffix
=
''
):
"""Attach the Assessment Feedback text to some submission."""
"""Attach the Assessment Feedback text to some submission."""
assessment_ui_model
=
self
.
get_assessment_module
(
'peer-assessment'
)
or
{}
assessment_feedback
=
data
.
get
(
'feedback'
,
''
)
assessment_feedback
=
data
.
get
(
'feedback'
,
''
)
if
not
assessment_feedback
:
if
not
assessment_feedback
:
return
{
return
{
...
@@ -84,7 +81,6 @@ class GradeMixin(object):
...
@@ -84,7 +81,6 @@ class GradeMixin(object):
}
}
try
:
try
:
peer_api
.
set_assessment_feedback
(
peer_api
.
set_assessment_feedback
(
assessment_ui_model
[
'must_grade'
],
{
{
'submission_uuid'
:
self
.
submission_uuid
,
'submission_uuid'
:
self
.
submission_uuid
,
'feedback'
:
assessment_feedback
,
'feedback'
:
assessment_feedback
,
...
...
apps/openassessment/xblock/self_assessment_mixin.py
View file @
db615fab
...
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _
...
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
openassessment.assessment
import
self_api
from
openassessment.assessment
import
self_api
from
submissions
import
api
as
submission_api
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -29,10 +29,11 @@ class SelfAssessmentMixin(object):
...
@@ -29,10 +29,11 @@ class SelfAssessmentMixin(object):
if
not
workflow
:
if
not
workflow
:
return
self
.
render_assessment
(
path
,
context
)
return
self
.
render_assessment
(
path
,
context
)
try
:
try
:
submission
,
assessment
=
self_api
.
get_submission_and_assessment
(
submission
=
submission_api
.
get_submission
(
self
.
submission_uuid
)
assessment
=
self_api
.
get_assessment
(
workflow
[
"submission_uuid"
]
workflow
[
"submission_uuid"
]
)
)
except
self_api
.
SelfAssessmentRequestError
:
except
(
submission_api
.
SubmissionError
,
self_api
.
SelfAssessmentRequestError
)
:
logger
.
exception
(
logger
.
exception
(
u"Could not retrieve self assessment for submission {}"
u"Could not retrieve self assessment for submission {}"
.
format
(
workflow
[
"submission_uuid"
])
.
format
(
workflow
[
"submission_uuid"
])
...
...
apps/openassessment/xblock/test/test_peer.py
View file @
db615fab
...
@@ -7,7 +7,6 @@ from collections import namedtuple
...
@@ -7,7 +7,6 @@ from collections import namedtuple
import
copy
import
copy
import
json
import
json
from
openassessment.assessment
import
peer_api
from
openassessment.assessment
import
peer_api
from
submissions
import
api
as
submission_api
from
.base
import
XBlockHandlerTestCase
,
scenario
from
.base
import
XBlockHandlerTestCase
,
scenario
...
@@ -97,7 +96,7 @@ class TestPeerAssessment(XBlockHandlerTestCase):
...
@@ -97,7 +96,7 @@ class TestPeerAssessment(XBlockHandlerTestCase):
self
.
assertTrue
(
resp
[
'success'
])
self
.
assertTrue
(
resp
[
'success'
])
# Retrieve the assessment and check that it matches what we sent
# Retrieve the assessment and check that it matches what we sent
actual
=
peer_api
.
get_assessments
(
submission
[
'uuid'
])
actual
=
peer_api
.
get_assessments
(
submission
[
'uuid'
]
,
scored_only
=
False
)
self
.
assertEqual
(
len
(
actual
),
1
)
self
.
assertEqual
(
len
(
actual
),
1
)
self
.
assertEqual
(
actual
[
0
][
'submission_uuid'
],
assessment
[
'submission_uuid'
])
self
.
assertEqual
(
actual
[
0
][
'submission_uuid'
],
assessment
[
'submission_uuid'
])
self
.
assertEqual
(
actual
[
0
][
'points_earned'
],
5
)
self
.
assertEqual
(
actual
[
0
][
'points_earned'
],
5
)
...
...
apps/openassessment/xblock/test/test_self.py
View file @
db615fab
...
@@ -35,7 +35,7 @@ class TestSelfAssessment(XBlockHandlerTestCase):
...
@@ -35,7 +35,7 @@ class TestSelfAssessment(XBlockHandlerTestCase):
self
.
assertTrue
(
resp
[
'success'
])
self
.
assertTrue
(
resp
[
'success'
])
# Expect that a self-assessment was created
# Expect that a self-assessment was created
_
,
assessment
=
self_api
.
get_submission_and
_assessment
(
submission
[
"uuid"
])
assessment
=
self_api
.
get
_assessment
(
submission
[
"uuid"
])
self
.
assertEqual
(
assessment
[
'submission_uuid'
],
submission
[
'uuid'
])
self
.
assertEqual
(
assessment
[
'submission_uuid'
],
submission
[
'uuid'
])
self
.
assertEqual
(
assessment
[
'points_earned'
],
5
)
self
.
assertEqual
(
assessment
[
'points_earned'
],
5
)
self
.
assertEqual
(
assessment
[
'points_possible'
],
6
)
self
.
assertEqual
(
assessment
[
'points_possible'
],
6
)
...
@@ -117,7 +117,7 @@ class TestSelfAssessment(XBlockHandlerTestCase):
...
@@ -117,7 +117,7 @@ class TestSelfAssessment(XBlockHandlerTestCase):
# Simulate an error and expect a failure response
# Simulate an error and expect a failure response
with
mock
.
patch
(
'openassessment.xblock.self_assessment_mixin.self_api'
)
as
mock_api
:
with
mock
.
patch
(
'openassessment.xblock.self_assessment_mixin.self_api'
)
as
mock_api
:
mock_api
.
SelfAssessmentRequestError
=
self_api
.
SelfAssessmentRequestError
mock_api
.
SelfAssessmentRequestError
=
self_api
.
SelfAssessmentRequestError
mock_api
.
get_
submission_and_
assessment
.
side_effect
=
self_api
.
SelfAssessmentRequestError
mock_api
.
get_assessment
.
side_effect
=
self_api
.
SelfAssessmentRequestError
resp
=
self
.
request
(
xblock
,
'render_self_assessment'
,
json
.
dumps
(
dict
()))
resp
=
self
.
request
(
xblock
,
'render_self_assessment'
,
json
.
dumps
(
dict
()))
self
.
assertIn
(
"error"
,
resp
.
lower
())
self
.
assertIn
(
"error"
,
resp
.
lower
())
...
...
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