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
6194d195
Commit
6194d195
authored
Feb 24, 2014
by
David Ormsbee
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add docstrings for Peer models.
parent
6c5c9e43
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
89 additions
and
34 deletions
+89
-34
apps/openassessment/peer/models.py
+41
-10
apps/openassessment/peer/serializers.py
+48
-24
No files found.
apps/openassessment/peer/models.py
View file @
6194d195
...
@@ -38,19 +38,24 @@ class Rubric(models.Model):
...
@@ -38,19 +38,24 @@ class Rubric(models.Model):
lookups.
lookups.
"""
"""
# SHA1 hash
# SHA1 hash
content_hash
=
models
.
CharField
(
max_length
=
40
)
content_hash
=
models
.
CharField
(
max_length
=
40
,
unique
=
True
,
db_index
=
True
)
@property
@property
def
points_possible
(
self
):
def
points_possible
(
self
):
"""The total number of points that could be earned in this Rubric."""
criteria_points
=
[
crit
.
points_possible
for
crit
in
self
.
criteria
.
all
()]
criteria_points
=
[
crit
.
points_possible
for
crit
in
self
.
criteria
.
all
()]
return
sum
(
criteria_points
)
if
criteria_points
else
0
return
sum
(
criteria_points
)
if
criteria_points
else
0
@staticmethod
@staticmethod
def
content_hash_for_rubric_dict
(
rubric_dict
):
def
content_hash_for_rubric_dict
(
rubric_dict
):
"""
"""
Given a dict of rubric information, return a unique hash.
This is a static method because we want to provide the `content_hash`
when we create the rubric -- i.e. before the Rubric object could know or
access its child criteria or options.
"""
"""
rubric_dict
=
deepcopy
(
rubric_dict
)
rubric_dict
=
deepcopy
(
rubric_dict
)
# Neither "id" nor "content_hash" would count towards calculating the
# Neither "id" nor "content_hash" would count towards calculating the
# content_hash.
# content_hash.
rubric_dict
.
pop
(
"id"
,
None
)
rubric_dict
.
pop
(
"id"
,
None
)
...
@@ -59,11 +64,24 @@ class Rubric(models.Model):
...
@@ -59,11 +64,24 @@ 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
()
def
options_ids
(
self
,
options_selected
):
"""Given a mapping of selected options, return the option IDs.
We use this to map user selection during assessment to the
:class:`CriterionOption` IDs that are in our database. These IDs are
never shown to the user.
Args:
options_selected (dict): Mapping of criteria names to the names of
the option that was selected for that criterion.
Examples:
>>> options_selected = {"secret": "yes", "safe": "no"}
>>> rubric.options_ids(options_selected)
[10, 12]
def
options_ids
(
self
,
crit_to_opt_names
):
"""
"""
"""
#
C
ache this
#
TODO: c
ache this
crit_to_all_opts
=
{
crit_to_all_opts
=
{
crit
.
name
:
{
crit
.
name
:
{
option
.
name
:
option
.
id
for
option
in
crit
.
options
.
all
()
option
.
name
:
option
.
id
for
option
in
crit
.
options
.
all
()
...
@@ -73,12 +91,18 @@ class Rubric(models.Model):
...
@@ -73,12 +91,18 @@ class Rubric(models.Model):
return
[
return
[
crit_to_all_opts
[
crit
][
opt
]
crit_to_all_opts
[
crit
][
opt
]
for
crit
,
opt
in
crit_to_opt_names
.
items
()
for
crit
,
opt
in
options_selected
.
items
()
]
]
class
Criterion
(
models
.
Model
):
class
Criterion
(
models
.
Model
):
# All Rubrics have at least one Criterion
"""A single aspect of a submission that needs assessment.
As an example, an essay might be assessed separately for accuracy, brevity,
and clarity. Each of those would be separate criteria.
All Rubrics have at least one Criterion.
"""
rubric
=
models
.
ForeignKey
(
Rubric
,
related_name
=
"criteria"
)
rubric
=
models
.
ForeignKey
(
Rubric
,
related_name
=
"criteria"
)
name
=
models
.
CharField
(
max_length
=
100
,
blank
=
False
)
name
=
models
.
CharField
(
max_length
=
100
,
blank
=
False
)
...
@@ -92,13 +116,19 @@ class Criterion(models.Model):
...
@@ -92,13 +116,19 @@ class Criterion(models.Model):
class
Meta
:
class
Meta
:
ordering
=
[
"rubric"
,
"order_num"
]
ordering
=
[
"rubric"
,
"order_num"
]
@property
@property
def
points_possible
(
self
):
def
points_possible
(
self
):
"""The total number of points that could be earned in this Criterion."""
return
max
(
option
.
points
for
option
in
self
.
options
.
all
())
return
max
(
option
.
points
for
option
in
self
.
options
.
all
())
class
CriterionOption
(
models
.
Model
):
class
CriterionOption
(
models
.
Model
):
"""What an assessor chooses when assessing against a Criteria.
CriterionOptions have a name, point value, and explanation associated with
them. When you have to select between "Excellent", "Good", "Fair", "Bad" --
those are options.
"""
# All Criteria must have at least one CriterionOption.
# All Criteria must have at least one CriterionOption.
criterion
=
models
.
ForeignKey
(
Criterion
,
related_name
=
"options"
)
criterion
=
models
.
ForeignKey
(
Criterion
,
related_name
=
"options"
)
...
@@ -121,7 +151,6 @@ class CriterionOption(models.Model):
...
@@ -121,7 +151,6 @@ class CriterionOption(models.Model):
class
Meta
:
class
Meta
:
ordering
=
[
"criterion"
,
"order_num"
]
ordering
=
[
"criterion"
,
"order_num"
]
def
__repr__
(
self
):
def
__repr__
(
self
):
return
(
return
(
"CriterionOption(order_num={0.order_num}, points={0.points}, "
"CriterionOption(order_num={0.order_num}, points={0.points}, "
...
@@ -133,6 +162,7 @@ class CriterionOption(models.Model):
...
@@ -133,6 +162,7 @@ class CriterionOption(models.Model):
class
Assessment
(
models
.
Model
):
class
Assessment
(
models
.
Model
):
"""An evaluation made against a particular Submission and Rubric."""
submission
=
models
.
ForeignKey
(
Submission
)
submission
=
models
.
ForeignKey
(
Submission
)
rubric
=
models
.
ForeignKey
(
Rubric
)
rubric
=
models
.
ForeignKey
(
Rubric
)
...
@@ -164,9 +194,10 @@ class Assessment(models.Model):
...
@@ -164,9 +194,10 @@ class Assessment(models.Model):
class
AssessmentPart
(
models
.
Model
):
class
AssessmentPart
(
models
.
Model
):
"""Part of an Assessment corresponding to a particular Criterion."""
assessment
=
models
.
ForeignKey
(
Assessment
,
related_name
=
'parts'
)
assessment
=
models
.
ForeignKey
(
Assessment
,
related_name
=
'parts'
)
# criterion = models.ForeignKey(Criterion)
# criterion = models.ForeignKey(Criterion)
?
option
=
models
.
ForeignKey
(
CriterionOption
)
# TODO: no reverse
option
=
models
.
ForeignKey
(
CriterionOption
)
# TODO: no reverse
@property
@property
...
...
apps/openassessment/peer/serializers.py
View file @
6194d195
# coding=utf-8
"""
"""
Serializers are created to ensure models do not have to be accessed outside the
Serializers are created to ensure models do not have to be accessed outside the
scope of the Tim APIs.
scope of the Tim APIs.
...
@@ -12,26 +13,27 @@ from openassessment.peer.models import (
...
@@ -12,26 +13,27 @@ from openassessment.peer.models import (
)
)
class
InvalidRubric
(
Exception
):
class
InvalidRubric
(
Exception
):
"""This can be raised during the deserialization process."""
def
__init__
(
self
,
errors
):
def
__init__
(
self
,
errors
):
Exception
.
__init__
(
self
,
repr
(
errors
))
Exception
.
__init__
(
self
,
repr
(
errors
))
self
.
errors
=
deepcopy
(
errors
)
self
.
errors
=
deepcopy
(
errors
)
class
NestedModelSerializer
(
serializers
.
ModelSerializer
):
class
NestedModelSerializer
(
serializers
.
ModelSerializer
):
"""Model Serializer that supports arbitrary nesting.
"""Model Serializer that supports
deserialization with
arbitrary nesting.
The Django REST Framework does not currently support deserialization more
The Django REST Framework does not currently support deserialization more
than one level deep (so a parent and children). We want to be able to
than one level deep (so a parent and children). We want to be able to
create a Rubric -> Criterion -> CriterionOption hierarchy.
create a :class:`Rubric` → :class:`Criterion` → :class:`CriterionOption`
hierarchy.
Much of the base logic already "just works" and serialization of arbritrary
Much of the base logic already "just works" and serialization of arbritrary
depth is supported. So we just override the save_object method to
depth is supported. So we just override the save_object method to
recursively link foreign key relations instead of doing it one level deep.
recursively link foreign key relations instead of doing it one level deep.
We don't touch many-to-many relationships because we don't need to for our
We don't touch many-to-many relationships because we don't need to for our
purposes.
purposes
, so those still only work one level deep
.
"""
"""
def
recursively_link_related
(
self
,
obj
,
**
kwargs
):
def
recursively_link_related
(
self
,
obj
,
**
kwargs
):
if
getattr
(
obj
,
'_related_data'
,
None
):
if
getattr
(
obj
,
'_related_data'
,
None
):
for
accessor_name
,
related
in
obj
.
_related_data
.
items
():
for
accessor_name
,
related
in
obj
.
_related_data
.
items
():
...
@@ -40,25 +42,29 @@ class NestedModelSerializer(serializers.ModelSerializer):
...
@@ -40,25 +42,29 @@ class NestedModelSerializer(serializers.ModelSerializer):
self
.
recursively_link_related
(
related_obj
,
**
kwargs
)
self
.
recursively_link_related
(
related_obj
,
**
kwargs
)
del
(
obj
.
_related_data
)
del
(
obj
.
_related_data
)
def
save_object
(
self
,
obj
,
**
kwargs
):
def
save_object
(
self
,
obj
,
**
kwargs
):
obj
.
save
(
**
kwargs
)
obj
.
save
(
**
kwargs
)
# The code for many-to-many relationships is just copy-pasted from the
# Django REST Framework ModelSerializer
if
getattr
(
obj
,
'_m2m_data'
,
None
):
if
getattr
(
obj
,
'_m2m_data'
,
None
):
for
accessor_name
,
object_list
in
obj
.
_m2m_data
.
items
():
for
accessor_name
,
object_list
in
obj
.
_m2m_data
.
items
():
setattr
(
obj
,
accessor_name
,
object_list
)
setattr
(
obj
,
accessor_name
,
object_list
)
del
(
obj
.
_m2m_data
)
del
(
obj
.
_m2m_data
)
# This is our only real change from ModelSerializer
self
.
recursively_link_related
(
obj
,
**
kwargs
)
self
.
recursively_link_related
(
obj
,
**
kwargs
)
class
CriterionOptionSerializer
(
NestedModelSerializer
):
class
CriterionOptionSerializer
(
NestedModelSerializer
):
"""Serializer for :class:`CriterionOption`"""
class
Meta
:
class
Meta
:
model
=
CriterionOption
model
=
CriterionOption
fields
=
(
'order_num'
,
'points'
,
'name'
,
'explanation'
)
fields
=
(
'order_num'
,
'points'
,
'name'
,
'explanation'
)
class
CriterionSerializer
(
NestedModelSerializer
):
class
CriterionSerializer
(
NestedModelSerializer
):
"""Serializer for :class:`Criterion`"""
options
=
CriterionOptionSerializer
(
required
=
True
,
many
=
True
)
options
=
CriterionOptionSerializer
(
required
=
True
,
many
=
True
)
class
Meta
:
class
Meta
:
...
@@ -67,6 +73,7 @@ class CriterionSerializer(NestedModelSerializer):
...
@@ -67,6 +73,7 @@ class CriterionSerializer(NestedModelSerializer):
def
validate_options
(
self
,
attrs
,
source
):
def
validate_options
(
self
,
attrs
,
source
):
"""Make sure we have at least one CriterionOption in a Criterion."""
options
=
attrs
[
source
]
options
=
attrs
[
source
]
if
not
options
:
if
not
options
:
raise
serializers
.
ValidationError
(
raise
serializers
.
ValidationError
(
...
@@ -76,6 +83,7 @@ class CriterionSerializer(NestedModelSerializer):
...
@@ -76,6 +83,7 @@ class CriterionSerializer(NestedModelSerializer):
class
RubricSerializer
(
NestedModelSerializer
):
class
RubricSerializer
(
NestedModelSerializer
):
"""Serializer for :class:`Rubric`."""
criteria
=
CriterionSerializer
(
required
=
True
,
many
=
True
)
criteria
=
CriterionSerializer
(
required
=
True
,
many
=
True
)
points_possible
=
serializers
.
Field
(
source
=
'points_possible'
)
points_possible
=
serializers
.
Field
(
source
=
'points_possible'
)
...
@@ -85,35 +93,23 @@ class RubricSerializer(NestedModelSerializer):
...
@@ -85,35 +93,23 @@ class RubricSerializer(NestedModelSerializer):
def
validate_criteria
(
self
,
attrs
,
source
):
def
validate_criteria
(
self
,
attrs
,
source
):
"""Make sure we have at least one Criterion in the Rubric."""
criteria
=
attrs
[
source
]
criteria
=
attrs
[
source
]
if
not
criteria
:
if
not
criteria
:
raise
serializers
.
ValidationError
(
"Must have at least one criterion"
)
raise
serializers
.
ValidationError
(
"Must have at least one criterion"
)
return
attrs
return
attrs
#def validate(self, attrs):
#total_possible = sum(
# max(option.get("points", 0) for option in criterion["options"])
# for criterion in attrs["criteria"]
#)
# total_possible = sum(crit.points_possible() for crit in attrs['criteria'])
# if total_possible <= 0:
# raise serializers.ValidationError(
# "Rubric must have > 0 possible points."
# )
class
AssessmentPartSerializer
(
serializers
.
ModelSerializer
):
class
AssessmentPartSerializer
(
serializers
.
ModelSerializer
):
# criterion = CriterionSerializer()
"""Serializer for :class:`AssessmentPart`."""
# option = CriterionOptionSerializer()
class
Meta
:
class
Meta
:
model
=
AssessmentPart
model
=
AssessmentPart
# fields = ('criterion', 'option')
fields
=
(
'option'
,)
# TODO: Direct link to Criterion?
fields
=
(
'option'
,)
class
AssessmentSerializer
(
serializers
.
ModelSerializer
):
class
AssessmentSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for :class:`Assessment`."""
submission_uuid
=
serializers
.
Field
(
source
=
'submission_uuid'
)
submission_uuid
=
serializers
.
Field
(
source
=
'submission_uuid'
)
parts
=
AssessmentPartSerializer
(
required
=
True
,
many
=
True
)
parts
=
AssessmentPartSerializer
(
required
=
True
,
many
=
True
)
...
@@ -138,12 +134,38 @@ class AssessmentSerializer(serializers.ModelSerializer):
...
@@ -138,12 +134,38 @@ class AssessmentSerializer(serializers.ModelSerializer):
'points_possible'
,
'points_possible'
,
)
)
def
rubric_from_dict
(
rubric_dict
):
def
rubric_from_dict
(
rubric_dict
):
"""Given a
rubric_dict, return the rubric ID we're going to submit against.
"""Given a
dict of rubric information, return the corresponding Rubric
This will create the Rubric and its children if it does not exist already.
This will create the Rubric and its children if it does not exist already.
Sample data (one criterion, two options)::
{
"prompt": "Create a plan to deliver edx-tim!",
"criteria": [
{
"order_num": 0,
"name": "realistic",
"prompt": "Is the deadline realistic?",
"options": [
{
"order_num": 0,
"points": 0,
"name": "No",
"explanation": "We need more time!"
},
{
"order_num": 1,
"points": 2,
"name": "Yes",
"explanation": "We got this."
},
]
}
]
}
"""
"""
rubric_dict
=
deepcopy
(
rubric_dict
)
rubric_dict
=
deepcopy
(
rubric_dict
)
...
@@ -155,8 +177,10 @@ def rubric_from_dict(rubric_dict):
...
@@ -155,8 +177,10 @@ def rubric_from_dict(rubric_dict):
except
Rubric
.
DoesNotExist
:
except
Rubric
.
DoesNotExist
:
rubric_dict
[
"content_hash"
]
=
content_hash
rubric_dict
[
"content_hash"
]
=
content_hash
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
:
criterion
[
"order_num"
]
=
crit_idx
criterion
[
"order_num"
]
=
crit_idx
for
opt_idx
,
option
in
enumerate
(
criterion
.
get
(
"options"
,
{})):
for
opt_idx
,
option
in
enumerate
(
criterion
.
get
(
"options"
,
{})):
if
"order_num"
not
in
option
:
option
[
"order_num"
]
=
opt_idx
option
[
"order_num"
]
=
opt_idx
rubric_serializer
=
RubricSerializer
(
data
=
rubric_dict
)
rubric_serializer
=
RubricSerializer
(
data
=
rubric_dict
)
...
...
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