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
cfbd98bd
Commit
cfbd98bd
authored
Jun 04, 2014
by
Will Daly
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #402 from edx/will/TIM-624
Rubric structure hash
parents
4fdfcc02
afe39aa0
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
463 additions
and
5 deletions
+463
-5
apps/openassessment/assessment/admin.py
+2
-1
apps/openassessment/assessment/migrations/0016_auto__add_field_rubric_structure_hash.py
+167
-0
apps/openassessment/assessment/migrations/0017_rubric_structure_hash.py
+188
-0
apps/openassessment/assessment/models/base.py
+36
-1
apps/openassessment/assessment/serializers/base.py
+2
-1
apps/openassessment/assessment/test/test_rubric.py
+67
-2
apps/openassessment/xblock/test/data/student_training_mixin.json
+1
-0
logs/dev/.gitkeep
+0
-0
logs/vagrant/.gitkeep
+0
-0
No files found.
apps/openassessment/assessment/admin.py
View file @
cfbd98bd
...
@@ -18,7 +18,8 @@ class RubricAdmin(admin.ModelAdmin):
...
@@ -18,7 +18,8 @@ class RubricAdmin(admin.ModelAdmin):
list_display_links
=
(
'id'
,
'content_hash'
)
list_display_links
=
(
'id'
,
'content_hash'
)
search_fields
=
(
'id'
,
'content_hash'
)
search_fields
=
(
'id'
,
'content_hash'
)
readonly_fields
=
(
readonly_fields
=
(
'id'
,
'content_hash'
,
'points_possible'
,
'criteria_summary'
,
'data'
'id'
,
'content_hash'
,
'structure_hash'
,
'points_possible'
,
'criteria_summary'
,
'data'
)
)
def
criteria_summary
(
self
,
rubric_obj
):
def
criteria_summary
(
self
,
rubric_obj
):
...
...
apps/openassessment/assessment/migrations/0016_auto__add_field_rubric_structure_hash.py
0 → 100644
View file @
cfbd98bd
# -*- 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 'Rubric.structure_hash'
db
.
add_column
(
'assessment_rubric'
,
'structure_hash'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
default
=
''
,
max_length
=
40
,
db_index
=
True
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'Rubric.structure_hash'
db
.
delete_column
(
'assessment_rubric'
,
'structure_hash'
)
models
=
{
'assessment.aiclassifier'
:
{
'Meta'
:
{
'object_name'
:
'AIClassifier'
},
'classifier_data'
:
(
'django.db.models.fields.files.FileField'
,
[],
{
'max_length'
:
'100'
}),
'classifier_set'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'classifiers'"
,
'to'
:
"orm['assessment.AIClassifierSet']"
}),
'criterion'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'to'
:
"orm['assessment.Criterion']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'assessment.aiclassifierset'
:
{
'Meta'
:
{
'ordering'
:
"['-created_at', '-id']"
,
'object_name'
:
'AIClassifierSet'
},
'algorithm_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'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'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'to'
:
"orm['assessment.Rubric']"
})
},
'assessment.aigradingworkflow'
:
{
'Meta'
:
{
'object_name'
:
'AIGradingWorkflow'
},
'algorithm_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'assessment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'+'"
,
'null'
:
'True'
,
'to'
:
"orm['assessment.Assessment']"
}),
'classifier_set'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'+'"
,
'null'
:
'True'
,
'to'
:
"orm['assessment.AIClassifierSet']"
}),
'completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'db_index'
:
'True'
}),
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'essay_text'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'item_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'to'
:
"orm['assessment.Rubric']"
}),
'scheduled_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
'db_index'
:
'True'
}),
'student_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'
}),
'uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'unique'
:
'True'
,
'max_length'
:
'36'
,
'blank'
:
'True'
})
},
'assessment.aitrainingworkflow'
:
{
'Meta'
:
{
'object_name'
:
'AITrainingWorkflow'
},
'algorithm_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'classifier_set'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'+'"
,
'null'
:
'True'
,
'to'
:
"orm['assessment.AIClassifierSet']"
}),
'completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'db_index'
:
'True'
}),
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'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'
}),
'scheduled_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
'db_index'
:
'True'
}),
'training_examples'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"'+'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['assessment.TrainingExample']"
}),
'uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'unique'
:
'True'
,
'max_length'
:
'36'
,
'blank'
:
'True'
})
},
'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_text'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'max_length'
:
'10000'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'options'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'assessment_feedback'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['assessment.AssessmentFeedbackOption']"
}),
'submission_uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'128'
,
'db_index'
:
'True'
})
},
'assessment.assessmentfeedbackoption'
:
{
'Meta'
:
{
'object_name'
:
'AssessmentFeedbackOption'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'text'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
})
},
'assessment.assessmentpart'
:
{
'Meta'
:
{
'object_name'
:
'AssessmentPart'
},
'assessment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'parts'"
,
'to'
:
"orm['assessment.Assessment']"
}),
'feedback'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'option'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'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'
,
'db_index'
:
'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'
}),
'grading_completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'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.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.Assessment']"
,
'null'
:
'True'
}),
'author'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'graded_by'"
,
'to'
:
"orm['assessment.PeerWorkflow']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'scored'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'scorer'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'graded'"
,
'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'
}),
'structure_hash'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'db_index'
:
'True'
})
},
'assessment.studenttrainingworkflow'
:
{
'Meta'
:
{
'object_name'
:
'StudentTrainingWorkflow'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'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.studenttrainingworkflowitem'
:
{
'Meta'
:
{
'ordering'
:
"['workflow', 'order_num']"
,
'unique_together'
:
"(('workflow', 'order_num'),)"
,
'object_name'
:
'StudentTrainingWorkflowItem'
},
'completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'order_num'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{}),
'started_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'training_example'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.TrainingExample']"
}),
'workflow'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'items'"
,
'to'
:
"orm['assessment.StudentTrainingWorkflow']"
})
},
'assessment.trainingexample'
:
{
'Meta'
:
{
'object_name'
:
'TrainingExample'
},
'content_hash'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'options_selected'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['assessment.CriterionOption']"
,
'symmetrical'
:
'False'
}),
'raw_answer'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.Rubric']"
})
}
}
complete_apps
=
[
'assessment'
]
\ No newline at end of file
apps/openassessment/assessment/migrations/0017_rubric_structure_hash.py
0 → 100644
View file @
cfbd98bd
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
DataMigration
from
django.db
import
models
from
openassessment.assessment.models
import
Rubric
class
Migration
(
DataMigration
):
def
forwards
(
self
,
orm
):
"""Calculate the structure hash for each rubric that doesn't have one."""
# The default value for the structure hash is an empty string
# so rubrics created before the scheme migration will have
# an empty string for the structure hash.
for
rubric
in
orm
.
Rubric
.
objects
.
filter
(
structure_hash
=
""
)
.
select_related
():
rubric_dict
=
{
"criteria"
:
[
{
"name"
:
criterion
.
name
,
"order_num"
:
criterion
.
order_num
,
"options"
:
[
{
"name"
:
option
.
name
,
"order_num"
:
option
.
order_num
,
"points"
:
option
.
points
}
for
option
in
criterion
.
options
.
all
()
]
}
for
criterion
in
rubric
.
criteria
.
all
()
]
}
# Ordinarily, we would use `orm.Rubric`, but we need access to the static method,
# which South doesn't seem to provide.
rubric
.
structure_hash
=
Rubric
.
structure_hash_from_dict
(
rubric_dict
)
rubric
.
save
()
def
backwards
(
self
,
orm
):
"Backwards migration is a no-op."
pass
models
=
{
'assessment.aiclassifier'
:
{
'Meta'
:
{
'object_name'
:
'AIClassifier'
},
'classifier_data'
:
(
'django.db.models.fields.files.FileField'
,
[],
{
'max_length'
:
'100'
}),
'classifier_set'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'classifiers'"
,
'to'
:
"orm['assessment.AIClassifierSet']"
}),
'criterion'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'to'
:
"orm['assessment.Criterion']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'assessment.aiclassifierset'
:
{
'Meta'
:
{
'ordering'
:
"['-created_at', '-id']"
,
'object_name'
:
'AIClassifierSet'
},
'algorithm_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'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'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'to'
:
"orm['assessment.Rubric']"
})
},
'assessment.aigradingworkflow'
:
{
'Meta'
:
{
'object_name'
:
'AIGradingWorkflow'
},
'algorithm_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'assessment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'+'"
,
'null'
:
'True'
,
'to'
:
"orm['assessment.Assessment']"
}),
'classifier_set'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'+'"
,
'null'
:
'True'
,
'to'
:
"orm['assessment.AIClassifierSet']"
}),
'completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'db_index'
:
'True'
}),
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'essay_text'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'item_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'to'
:
"orm['assessment.Rubric']"
}),
'scheduled_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
'db_index'
:
'True'
}),
'student_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'
}),
'uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'unique'
:
'True'
,
'max_length'
:
'36'
,
'blank'
:
'True'
})
},
'assessment.aitrainingworkflow'
:
{
'Meta'
:
{
'object_name'
:
'AITrainingWorkflow'
},
'algorithm_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'classifier_set'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'+'"
,
'null'
:
'True'
,
'to'
:
"orm['assessment.AIClassifierSet']"
}),
'completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'db_index'
:
'True'
}),
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'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'
}),
'scheduled_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
,
'db_index'
:
'True'
}),
'training_examples'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'related_name'
:
"'+'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['assessment.TrainingExample']"
}),
'uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'unique'
:
'True'
,
'max_length'
:
'36'
,
'blank'
:
'True'
})
},
'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_text'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'max_length'
:
'10000'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'options'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'default'
:
'None'
,
'related_name'
:
"'assessment_feedback'"
,
'symmetrical'
:
'False'
,
'to'
:
"orm['assessment.AssessmentFeedbackOption']"
}),
'submission_uuid'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'128'
,
'db_index'
:
'True'
})
},
'assessment.assessmentfeedbackoption'
:
{
'Meta'
:
{
'object_name'
:
'AssessmentFeedbackOption'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'text'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'255'
})
},
'assessment.assessmentpart'
:
{
'Meta'
:
{
'object_name'
:
'AssessmentPart'
},
'assessment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'parts'"
,
'to'
:
"orm['assessment.Assessment']"
}),
'feedback'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'option'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'+'"
,
'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'
,
'db_index'
:
'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'
}),
'grading_completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'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.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.Assessment']"
,
'null'
:
'True'
}),
'author'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'graded_by'"
,
'to'
:
"orm['assessment.PeerWorkflow']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'scored'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'scorer'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'graded'"
,
'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'
}),
'structure_hash'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'db_index'
:
'True'
})
},
'assessment.studenttrainingworkflow'
:
{
'Meta'
:
{
'object_name'
:
'StudentTrainingWorkflow'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'40'
,
'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.studenttrainingworkflowitem'
:
{
'Meta'
:
{
'ordering'
:
"['workflow', 'order_num']"
,
'unique_together'
:
"(('workflow', 'order_num'),)"
,
'object_name'
:
'StudentTrainingWorkflowItem'
},
'completed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'None'
,
'null'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'order_num'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{}),
'started_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'training_example'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.TrainingExample']"
}),
'workflow'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'items'"
,
'to'
:
"orm['assessment.StudentTrainingWorkflow']"
})
},
'assessment.trainingexample'
:
{
'Meta'
:
{
'object_name'
:
'TrainingExample'
},
'content_hash'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'40'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'options_selected'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['assessment.CriterionOption']"
,
'symmetrical'
:
'False'
}),
'raw_answer'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'rubric'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['assessment.Rubric']"
})
}
}
complete_apps
=
[
'assessment'
]
symmetrical
=
True
apps/openassessment/assessment/models/base.py
View file @
cfbd98bd
...
@@ -57,9 +57,12 @@ class Rubric(models.Model):
...
@@ -57,9 +57,12 @@ class Rubric(models.Model):
creating a new Rubric instead. This makes it easy to cache and do hash-based
creating a new Rubric instead. This makes it easy to cache and do hash-based
lookups.
lookups.
"""
"""
# SHA1 hash
# SHA1 hash
, including prompts and explanations
content_hash
=
models
.
CharField
(
max_length
=
40
,
unique
=
True
,
db_index
=
True
)
content_hash
=
models
.
CharField
(
max_length
=
40
,
unique
=
True
,
db_index
=
True
)
# SHA1 hash of just the rubric structure (criteria / options / points)
structure_hash
=
models
.
CharField
(
max_length
=
40
,
db_index
=
True
)
class
Meta
:
class
Meta
:
app_label
=
"assessment"
app_label
=
"assessment"
...
@@ -91,6 +94,38 @@ class Rubric(models.Model):
...
@@ -91,6 +94,38 @@ class Rubric(models.Model):
canonical_form
=
json
.
dumps
(
rubric_dict
,
sort_keys
=
True
)
canonical_form
=
json
.
dumps
(
rubric_dict
,
sort_keys
=
True
)
return
sha1
(
canonical_form
)
.
hexdigest
()
return
sha1
(
canonical_form
)
.
hexdigest
()
@staticmethod
def
structure_hash_from_dict
(
rubric_dict
):
"""
Generate a hash of the rubric that includes only structural information:
* Criteria names and order
* Option names / points / order number
We do NOT include prompt text or option explanations.
NOTE: currently, we use the criterion and option names as unique identifiers,
so we include them in the structure. In the future, we plan to assign
criteria/options unique IDs -- when we do that, we will need to update
this method and create a data migration for existing rubrics.
"""
structure
=
[
{
"criterion_name"
:
criterion
.
get
(
'name'
),
"criterion_order"
:
criterion
.
get
(
'order_num'
),
"options"
:
[
{
"option_name"
:
option
.
get
(
'name'
),
"option_points"
:
option
.
get
(
'points'
),
"option_order"
:
option
.
get
(
'order_num'
)
}
for
option
in
criterion
.
get
(
'options'
,
[])
]
}
for
criterion
in
rubric_dict
.
get
(
'criteria'
,
[])
]
canonical_form
=
json
.
dumps
(
structure
,
sort_keys
=
True
)
return
sha1
(
canonical_form
)
.
hexdigest
()
def
options_ids
(
self
,
options_selected
):
def
options_ids
(
self
,
options_selected
):
"""Given a mapping of selected options, return the option IDs.
"""Given a mapping of selected options, return the option IDs.
...
...
apps/openassessment/assessment/serializers/base.py
View file @
cfbd98bd
...
@@ -92,7 +92,7 @@ class RubricSerializer(NestedModelSerializer):
...
@@ -92,7 +92,7 @@ class RubricSerializer(NestedModelSerializer):
class
Meta
:
class
Meta
:
model
=
Rubric
model
=
Rubric
fields
=
(
'id'
,
'content_hash'
,
'criteria'
,
'points_possible'
)
fields
=
(
'id'
,
'content_hash'
,
'
structure_hash'
,
'
criteria'
,
'points_possible'
)
def
validate_criteria
(
self
,
attrs
,
source
):
def
validate_criteria
(
self
,
attrs
,
source
):
"""Make sure we have at least one Criterion in the Rubric."""
"""Make sure we have at least one Criterion in the Rubric."""
...
@@ -283,6 +283,7 @@ def rubric_from_dict(rubric_dict):
...
@@ -283,6 +283,7 @@ def rubric_from_dict(rubric_dict):
rubric
=
Rubric
.
objects
.
get
(
content_hash
=
content_hash
)
rubric
=
Rubric
.
objects
.
get
(
content_hash
=
content_hash
)
except
Rubric
.
DoesNotExist
:
except
Rubric
.
DoesNotExist
:
rubric_dict
[
"content_hash"
]
=
content_hash
rubric_dict
[
"content_hash"
]
=
content_hash
rubric_dict
[
"structure_hash"
]
=
Rubric
.
structure_hash_from_dict
(
rubric_dict
)
for
crit_idx
,
criterion
in
enumerate
(
rubric_dict
.
get
(
"criteria"
,
{})):
for
crit_idx
,
criterion
in
enumerate
(
rubric_dict
.
get
(
"criteria"
,
{})):
if
"order_num"
not
in
criterion
:
if
"order_num"
not
in
criterion
:
criterion
[
"order_num"
]
=
crit_idx
criterion
[
"order_num"
]
=
crit_idx
...
...
apps/openassessment/assessment/test/test_rubric.py
View file @
cfbd98bd
...
@@ -3,10 +3,12 @@
...
@@ -3,10 +3,12 @@
Tests for assessment models.
Tests for assessment models.
"""
"""
import
copy
from
openassessment.test_utils
import
CacheResetTest
from
openassessment.test_utils
import
CacheResetTest
from
openassessment.assessment.models
import
(
from
openassessment.assessment.models
import
(
Rubric
,
Criterion
,
CriterionOption
,
InvalidOptionSelection
Rubric
,
Criterion
,
CriterionOption
,
InvalidOptionSelection
)
)
from
openassessment.assessment.test.constants
import
RUBRIC
class
TestRubricOptionIds
(
CacheResetTest
):
class
TestRubricOptionIds
(
CacheResetTest
):
...
@@ -122,7 +124,7 @@ class TestRubricOptionIds(CacheResetTest):
...
@@ -122,7 +124,7 @@ class TestRubricOptionIds(CacheResetTest):
def
test_options_ids_points_caching
(
self
):
def
test_options_ids_points_caching
(
self
):
# First call: the dict is not cached
# First call: the dict is not cached
with
self
.
assertNumQueries
(
1
):
with
self
.
assertNumQueries
(
1
):
options_ids
=
self
.
rubric
.
options_ids_for_points
({
self
.
rubric
.
options_ids_for_points
({
'test criterion 0'
:
0
,
'test criterion 0'
:
0
,
'test criterion 1'
:
1
,
'test criterion 1'
:
1
,
'test criterion 2'
:
2
,
'test criterion 2'
:
2
,
...
@@ -131,7 +133,7 @@ class TestRubricOptionIds(CacheResetTest):
...
@@ -131,7 +133,7 @@ class TestRubricOptionIds(CacheResetTest):
# Second call: the dict is not cached
# Second call: the dict is not cached
with
self
.
assertNumQueries
(
0
):
with
self
.
assertNumQueries
(
0
):
options_ids
=
self
.
rubric
.
options_ids_for_points
({
self
.
rubric
.
options_ids_for_points
({
'test criterion 0'
:
1
,
'test criterion 0'
:
1
,
'test criterion 1'
:
2
,
'test criterion 1'
:
2
,
'test criterion 2'
:
1
,
'test criterion 2'
:
1
,
...
@@ -163,3 +165,66 @@ class TestRubricOptionIds(CacheResetTest):
...
@@ -163,3 +165,66 @@ class TestRubricOptionIds(CacheResetTest):
'test criterion 2'
:
1
,
'test criterion 2'
:
1
,
'test criterion 3'
:
0
'test criterion 3'
:
0
})
})
def
test_structure_hash_identical
(
self
):
first_hash
=
Rubric
.
structure_hash_from_dict
(
RUBRIC
)
# Same structure, but different text should have the same structure hash
altered_rubric
=
copy
.
deepcopy
(
RUBRIC
)
altered_rubric
[
'prompt'
]
=
'altered!'
for
criterion
in
altered_rubric
[
'criteria'
]:
criterion
[
'prompt'
]
=
'altered!'
for
option
in
criterion
[
'options'
]:
option
[
'explanation'
]
=
'altered!'
second_hash
=
Rubric
.
structure_hash_from_dict
(
altered_rubric
)
# Expect that the two hashes are the same
self
.
assertEqual
(
first_hash
,
second_hash
)
def
test_structure_hash_extra_keys
(
self
):
first_hash
=
Rubric
.
structure_hash_from_dict
(
RUBRIC
)
# Same structure, add some extra keys
altered_rubric
=
copy
.
deepcopy
(
RUBRIC
)
altered_rubric
[
'extra'
]
=
'extra!'
altered_rubric
[
'criteria'
][
0
][
'extra'
]
=
'extra!'
altered_rubric
[
'criteria'
][
0
][
'options'
][
0
][
'extra'
]
=
'extra!'
second_hash
=
Rubric
.
structure_hash_from_dict
(
altered_rubric
)
# Expect that the two hashes are the same
self
.
assertEqual
(
first_hash
,
second_hash
)
def
test_structure_hash_criterion_order_changed
(
self
):
first_hash
=
Rubric
.
structure_hash_from_dict
(
RUBRIC
)
altered_rubric
=
copy
.
deepcopy
(
RUBRIC
)
altered_rubric
[
'criteria'
][
0
][
'order_num'
]
=
5
second_hash
=
Rubric
.
structure_hash_from_dict
(
altered_rubric
)
self
.
assertNotEqual
(
first_hash
,
second_hash
)
def
test_structure_hash_criterion_name_changed
(
self
):
first_hash
=
Rubric
.
structure_hash_from_dict
(
RUBRIC
)
altered_rubric
=
copy
.
deepcopy
(
RUBRIC
)
altered_rubric
[
'criteria'
][
0
][
'name'
]
=
'altered!'
second_hash
=
Rubric
.
structure_hash_from_dict
(
altered_rubric
)
self
.
assertNotEqual
(
first_hash
,
second_hash
)
def
test_structure_hash_option_order_changed
(
self
):
first_hash
=
Rubric
.
structure_hash_from_dict
(
RUBRIC
)
altered_rubric
=
copy
.
deepcopy
(
RUBRIC
)
altered_rubric
[
'criteria'
][
0
][
'options'
][
0
][
'order_num'
]
=
5
second_hash
=
Rubric
.
structure_hash_from_dict
(
altered_rubric
)
self
.
assertNotEqual
(
first_hash
,
second_hash
)
def
test_structure_hash_option_name_changed
(
self
):
first_hash
=
Rubric
.
structure_hash_from_dict
(
RUBRIC
)
altered_rubric
=
copy
.
deepcopy
(
RUBRIC
)
altered_rubric
[
'criteria'
][
0
][
'options'
][
0
][
'name'
]
=
'altered!'
second_hash
=
Rubric
.
structure_hash_from_dict
(
altered_rubric
)
self
.
assertNotEqual
(
first_hash
,
second_hash
)
def
test_structure_hash_option_points_changed
(
self
):
first_hash
=
Rubric
.
structure_hash_from_dict
(
RUBRIC
)
altered_rubric
=
copy
.
deepcopy
(
RUBRIC
)
altered_rubric
[
'criteria'
][
0
][
'options'
][
0
][
'points'
]
=
'altered!'
second_hash
=
Rubric
.
structure_hash_from_dict
(
altered_rubric
)
self
.
assertNotEqual
(
first_hash
,
second_hash
)
apps/openassessment/xblock/test/data/student_training_mixin.json
View file @
cfbd98bd
...
@@ -9,6 +9,7 @@
...
@@ -9,6 +9,7 @@
"training_rubric"
:
{
"training_rubric"
:
{
"id"
:
2
,
"id"
:
2
,
"content_hash"
:
"de2bb2b7e2c6e3df014e53b8c65f37d511cc4344"
,
"content_hash"
:
"de2bb2b7e2c6e3df014e53b8c65f37d511cc4344"
,
"structure_hash"
:
"a513b20d93487d6d80e31e1d974bf22519332567"
,
"criteria"
:
[
"criteria"
:
[
{
{
"order_num"
:
0
,
"order_num"
:
0
,
...
...
logs/dev/.gitkeep
0 → 100644
View file @
cfbd98bd
logs/vagrant/.gitkeep
0 → 100644
View file @
cfbd98bd
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