Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
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-platform
Commits
fe7d4aed
Commit
fe7d4aed
authored
Apr 17, 2012
by
Bridger Maxwell
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Put graders into their own file. Incorporated other feedback from pull request.
parent
16c8d008
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
319 additions
and
304 deletions
+319
-304
djangoapps/courseware/__init__.py
+6
-2
djangoapps/courseware/global_course_settings.py
+6
-6
djangoapps/courseware/graders.py
+271
-0
djangoapps/courseware/grades.py
+7
-269
djangoapps/courseware/tests.py
+29
-27
No files found.
djangoapps/courseware/__init__.py
View file @
fe7d4aed
...
...
@@ -15,15 +15,15 @@ from courseware.course_settings import GRADER # This won't work.
"""
import
courseware
import
imp
import
logging
import
sys
import
types
from
django.conf
import
settings
from
django.utils.functional
import
SimpleLazyObject
from
courseware
import
global_course_settings
from
courseware
import
graders
_log
=
logging
.
getLogger
(
"mitx.courseware"
)
...
...
@@ -49,5 +49,8 @@ class Settings(object):
if
setting
==
setting
.
upper
():
setting_value
=
getattr
(
mod
,
setting
)
setattr
(
self
,
setting
,
setting_value
)
# Here is where we should parse any configurations, so that we can fail early
self
.
GRADER
=
graders
.
grader_from_conf
(
self
.
GRADER
)
course_settings
=
Settings
()
\ No newline at end of file
djangoapps/courseware/global_course_settings.py
View file @
fe7d4aed
GRADER
=
[
{
'
course_format
'
:
"Homework"
,
'
type
'
:
"Homework"
,
'min_count'
:
12
,
'drop_count'
:
2
,
'short_label'
:
"HW"
,
'weight'
:
0.15
,
},
{
'
course_format
'
:
"Lab"
,
'
type
'
:
"Lab"
,
'min_count'
:
12
,
'drop_count'
:
2
,
'category'
:
"Labs"
,
'weight'
:
0.15
},
{
'
section_format'
:
"Examination
"
,
'
section_
name'
:
"Midterm Exam"
,
'
type'
:
"Midterm
"
,
'name'
:
"Midterm Exam"
,
'short_label'
:
"Midterm"
,
'weight'
:
0.3
,
},
{
'
section_format'
:
"Examination
"
,
'
section_
name'
:
"Final Exam"
,
'
type'
:
"Final
"
,
'name'
:
"Final Exam"
,
'short_label'
:
"Final"
,
'weight'
:
0.4
,
}
...
...
djangoapps/courseware/graders.py
0 → 100644
View file @
fe7d4aed
import
logging
from
django.conf
import
settings
from
collections
import
namedtuple
log
=
logging
.
getLogger
(
"mitx.courseware"
)
# This is a tuple for holding scores, either from problems or sections.
# Section either indicates the name of the problem or the name of the section
Score
=
namedtuple
(
"Score"
,
"earned possible graded section"
)
def
grader_from_conf
(
conf
):
"""
This creates a CourseGrader from a configuration (such as in course_settings.py).
The conf can simply be an instance of CourseGrader, in which case no work is done.
More commonly, the conf is a list of dictionaries. A WeightedSubsectionsGrader
with AssignmentFormatGrader's or SingleSectionGrader's as subsections will be
generated. Every dictionary should contain the parameters for making either a
AssignmentFormatGrader or SingleSectionGrader, in addition to a 'weight' key.
"""
if
isinstance
(
conf
,
CourseGrader
):
return
conf
subgraders
=
[]
for
subgraderconf
in
conf
:
subgraderconf
=
subgraderconf
.
copy
()
weight
=
subgraderconf
.
pop
(
"weight"
,
0
)
try
:
if
'min_count'
in
subgraderconf
:
#This is an AssignmentFormatGrader
subgrader
=
AssignmentFormatGrader
(
**
subgraderconf
)
subgraders
.
append
(
(
subgrader
,
subgrader
.
category
,
weight
)
)
elif
'name'
in
subgraderconf
:
#This is an SingleSectionGrader
subgrader
=
SingleSectionGrader
(
**
subgraderconf
)
subgraders
.
append
(
(
subgrader
,
subgrader
.
category
,
weight
)
)
else
:
raise
ValueError
(
"Configuration has no appropriate grader class."
)
except
(
TypeError
,
ValueError
)
as
error
:
errorString
=
"Unable to parse grader configuration:
\n
"
+
str
(
subgraderconf
)
+
"
\n
Error was:
\n
"
+
str
(
error
)
log
.
critical
(
errorString
)
raise
ValueError
(
errorString
)
return
WeightedSubsectionsGrader
(
subgraders
)
class
CourseGrader
(
object
):
"""
A course grader takes the totaled scores for each graded section (that a student has
started) in the course. From these scores, the grader calculates an overall percentage
grade. The grader should also generate information about how that score was calculated,
to be displayed in graphs or charts.
A grader has one required method, grade(), which is passed a grade_sheet. The grade_sheet
contains scores for all graded section that the student has started. If a student has
a score of 0 for that section, it may be missing from the grade_sheet. The grade_sheet
is keyed by section format. Each value is a list of Score namedtuples for each section
that has the matching section format.
The grader outputs a dictionary with the following keys:
- percent: Contaisn a float value, which is the final percentage score for the student.
- section_breakdown: This is a list of dictionaries which provide details on sections
that were graded. These are used for display in a graph or chart. The format for a
section_breakdown dictionary is explained below.
- grade_breakdown: This is a list of dictionaries which provide details on the contributions
of the final percentage grade. This is a higher level breakdown, for when the grade is constructed
of a few very large sections (such as Homeworks, Labs, a Midterm, and a Final). The format for
a grade_breakdown is explained below. This section is optional.
A dictionary in the section_breakdown list has the following keys:
percent: A float percentage for the section.
label: A short string identifying the section. Preferably fixed-length. E.g. "HW 3".
detail: A string explanation of the score. E.g. "Homework 1 - Ohms Law - 83
%
(5/6)"
category: A string identifying the category. Items with the same category are grouped together
in the display (for example, by color).
prominent: A boolean value indicating that this section should be displayed as more prominent
than other items.
A dictionary in the grade_breakdown list has the following keys:
percent: A float percentage in the breakdown. All percents should add up to the final percentage.
detail: A string explanation of this breakdown. E.g. "Homework - 10
%
of a possible 15
%
"
category: A string identifying the category. Items with the same category are grouped together
in the display (for example, by color).
"""
def
grade
(
self
,
grade_sheet
):
raise
NotImplementedError
class
WeightedSubsectionsGrader
(
CourseGrader
):
"""
This grader takes a list of tuples containing (grader, category_name, weight) and computes
a final grade by totalling the contribution of each sub grader and multiplying it by the
given weight. For example, the sections may be
[ (homeworkGrader, "Homework", 0.15), (labGrader, "Labs", 0.15), (midtermGrader, "Midterm", 0.30), (finalGrader, "Final", 0.40) ]
All items in section_breakdown for each subgrader will be combined. A grade_breakdown will be
composed using the score from each grader.
Note that the sum of the weights is not take into consideration. If the weights add up to
a value > 1, the student may end up with a percent > 100
%
. This allows for sections that
are extra credit.
"""
def
__init__
(
self
,
sections
):
self
.
sections
=
sections
def
grade
(
self
,
grade_sheet
):
total_percent
=
0.0
section_breakdown
=
[]
grade_breakdown
=
[]
for
subgrader
,
category
,
weight
in
self
.
sections
:
subgrade_result
=
subgrader
.
grade
(
grade_sheet
)
weightedPercent
=
subgrade_result
[
'percent'
]
*
weight
section_detail
=
"{0} = {1:.1
%
} of a possible {2:.0
%
}"
.
format
(
category
,
weightedPercent
,
weight
)
total_percent
+=
weightedPercent
section_breakdown
+=
subgrade_result
[
'section_breakdown'
]
grade_breakdown
.
append
(
{
'percent'
:
weightedPercent
,
'detail'
:
section_detail
,
'category'
:
category
}
)
return
{
'percent'
:
total_percent
,
'section_breakdown'
:
section_breakdown
,
'grade_breakdown'
:
grade_breakdown
}
class
SingleSectionGrader
(
CourseGrader
):
"""
This grades a single section with the format 'type' and the name 'name'.
If the name is not appropriate for the short short_label or category, they each may
be specified individually.
"""
def
__init__
(
self
,
type
,
name
,
short_label
=
None
,
category
=
None
):
self
.
type
=
type
self
.
name
=
name
self
.
short_label
=
short_label
or
name
self
.
category
=
category
or
name
def
grade
(
self
,
grade_sheet
):
foundScore
=
None
if
self
.
type
in
grade_sheet
:
for
score
in
grade_sheet
[
self
.
type
]:
if
score
.
section
==
self
.
name
:
foundScore
=
score
break
if
foundScore
:
percent
=
foundScore
.
earned
/
float
(
foundScore
.
possible
)
detail
=
"{name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
name
=
self
.
name
,
percent
=
percent
,
earned
=
float
(
foundScore
.
earned
),
possible
=
float
(
foundScore
.
possible
))
else
:
percent
=
0.0
detail
=
"{name} - 0
%
(?/?)"
.
format
(
name
=
self
.
name
)
if
settings
.
GENERATE_PROFILE_SCORES
:
points_possible
=
random
.
randrange
(
50
,
100
)
points_earned
=
random
.
randrange
(
40
,
points_possible
)
percent
=
points_earned
/
float
(
points_possible
)
detail
=
"{name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
name
=
self
.
name
,
percent
=
percent
,
earned
=
float
(
points_earned
),
possible
=
float
(
points_possible
))
breakdown
=
[{
'percent'
:
percent
,
'label'
:
self
.
short_label
,
'detail'
:
detail
,
'category'
:
self
.
category
,
'prominent'
:
True
}]
return
{
'percent'
:
percent
,
'section_breakdown'
:
breakdown
,
#No grade_breakdown here
}
class
AssignmentFormatGrader
(
CourseGrader
):
"""
Grades all sections matching the format 'type' with an equal weight. A specified
number of lowest scores can be dropped from the calculation. The minimum number of
sections in this format must be specified (even if those sections haven't been
written yet).
min_count defines how many assignments are expected throughout the course. Placeholder
scores (of 0) will be inserted if the number of matching sections in the course is < min_count.
If there number of matching sections in the course is > min_count, min_count will be ignored.
category should be presentable to the user, but may not appear. When the grade breakdown is
displayed, scores from the same category will be similar (for example, by color).
section_type is a string that is the type of a singular section. For example, for Labs it
would be "Lab". This defaults to be the same as category.
short_label is similar to section_type, but shorter. For example, for Homework it would be
"HW".
"""
def
__init__
(
self
,
type
,
min_count
,
drop_count
,
category
=
None
,
section_type
=
None
,
short_label
=
None
):
self
.
type
=
type
self
.
min_count
=
min_count
self
.
drop_count
=
drop_count
self
.
category
=
category
or
self
.
type
self
.
section_type
=
section_type
or
self
.
type
self
.
short_label
=
short_label
or
self
.
type
def
grade
(
self
,
grade_sheet
):
def
totalWithDrops
(
breakdown
,
drop_count
):
#create an array of tuples with (index, mark), sorted by mark['percent'] descending
sorted_breakdown
=
sorted
(
enumerate
(
breakdown
),
key
=
lambda
x
:
-
x
[
1
][
'percent'
]
)
# A list of the indices of the dropped scores
dropped_indices
=
[]
if
drop_count
>
0
:
dropped_indices
=
[
x
[
0
]
for
x
in
sorted_breakdown
[
-
drop_count
:]]
aggregate_score
=
0
for
index
,
mark
in
enumerate
(
breakdown
):
if
index
not
in
dropped_indices
:
aggregate_score
+=
mark
[
'percent'
]
if
(
len
(
breakdown
)
-
drop_count
>
0
):
aggregate_score
/=
len
(
breakdown
)
-
drop_count
return
aggregate_score
,
dropped_indices
#Figure the homework scores
scores
=
grade_sheet
.
get
(
self
.
type
,
[])
breakdown
=
[]
for
i
in
range
(
max
(
self
.
min_count
,
len
(
scores
))
):
if
i
<
len
(
scores
):
percentage
=
scores
[
i
]
.
earned
/
float
(
scores
[
i
]
.
possible
)
summary
=
"{section_type} {index} - {name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
index
=
i
+
1
,
section_type
=
self
.
section_type
,
name
=
scores
[
i
]
.
section
,
percent
=
percentage
,
earned
=
float
(
scores
[
i
]
.
earned
),
possible
=
float
(
scores
[
i
]
.
possible
)
)
else
:
percentage
=
0
summary
=
"{section_type} {index} Unreleased - 0
%
(?/?)"
.
format
(
index
=
i
+
1
,
section_type
=
self
.
section_type
)
if
settings
.
GENERATE_PROFILE_SCORES
:
points_possible
=
random
.
randrange
(
10
,
50
)
points_earned
=
random
.
randrange
(
5
,
points_possible
)
percentage
=
points_earned
/
float
(
points_possible
)
summary
=
"{section_type} {index} - {name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
index
=
i
+
1
,
section_type
=
self
.
section_type
,
name
=
"Randomly Generated"
,
percent
=
percentage
,
earned
=
float
(
points_earned
),
possible
=
float
(
points_possible
)
)
short_label
=
"{short_label} {index:02d}"
.
format
(
index
=
i
+
1
,
short_label
=
self
.
short_label
)
breakdown
.
append
(
{
'percent'
:
percentage
,
'label'
:
short_label
,
'detail'
:
summary
,
'category'
:
self
.
category
}
)
total_percent
,
dropped_indices
=
totalWithDrops
(
breakdown
,
self
.
drop_count
)
for
dropped_index
in
dropped_indices
:
breakdown
[
dropped_index
][
'mark'
]
=
{
'detail'
:
"The lowest {drop_count} {section_type} scores are dropped."
.
format
(
drop_count
=
self
.
drop_count
,
section_type
=
self
.
section_type
)
}
total_detail
=
"{section_type} Average = {percent:.0
%
}"
.
format
(
percent
=
total_percent
,
section_type
=
self
.
section_type
)
total_label
=
"{short_label} Avg"
.
format
(
short_label
=
self
.
short_label
)
breakdown
.
append
(
{
'percent'
:
total_percent
,
'label'
:
total_label
,
'detail'
:
total_detail
,
'category'
:
self
.
category
,
'prominent'
:
True
}
)
return
{
'percent'
:
total_percent
,
'section_breakdown'
:
breakdown
,
#No grade_breakdown here
}
djangoapps/courseware/grades.py
View file @
fe7d4aed
import
courseware.content_parser
as
content_parser
import
courseware.modules
import
logging
from
lxml
import
etree
import
random
import
urllib
from
collections
import
namedtuple
from
courseware
import
course_settings
from
django.conf
import
settings
from
lxml
import
etree
from
models
import
StudentModule
from
student.models
import
UserProfile
log
=
logging
.
getLogger
(
"mitx.courseware"
)
Score
=
namedtuple
(
"Score"
,
"earned possible graded section"
)
SectionPercentage
=
namedtuple
(
"SectionPercentage"
,
"percentage label summary"
)
class
CourseGrader
(
object
):
"""
A course grader takes the totaled scores for each graded section (that a student has
started) in the course. From these scores, the grader calculates an overall percentage
grade. The grader should also generate information about how that score was calculated,
to be displayed in graphs or charts.
A grader has one required method, grade(), which is passed a grade_sheet. The grade_sheet
contains scores for all graded section that the student has started. If a student has
a score of 0 for that section, it may be missing from the grade_sheet. The grade_sheet
is keyed by section format. Each value is a list of Score namedtuples for each section
that has the matching section format.
The grader outputs a dictionary with the following keys:
- percent: Contaisn a float value, which is the final percentage score for the student.
- section_breakdown: This is a list of dictionaries which provide details on sections
that were graded. These are used for display in a graph or chart. The format for a
section_breakdown dictionary is explained below.
- grade_breakdown: This is a list of dictionaries which provide details on the contributions
of the final percentage grade. This is a higher level breakdown, for when the grade is constructed
of a few very large sections (such as Homeworks, Labs, a Midterm, and a Final). The format for
a grade_breakdown is explained below. This section is optional.
A dictionary in the section_breakdown list has the following keys:
percent: A float percentage for the section.
label: A short string identifying the section. Preferably fixed-length. E.g. "HW 3".
detail: A string explanation of the score. E.g. "Homework 1 - Ohms Law - 83
%
(5/6)"
category: A string identifying the category. Items with the same category are grouped together
in the display (for example, by color).
prominent: A boolean value indicating that this section should be displayed as more prominent
than other items.
A dictionary in the grade_breakdown list has the following keys:
percent: A float percentage in the breakdown. All percents should add up to the final percentage.
detail: A string explanation of this breakdown. E.g. "Homework - 10
%
of a possible 15
%
"
category: A string identifying the category. Items with the same category are grouped together
in the display (for example, by color).
"""
def
grade
(
self
,
grade_sheet
):
raise
NotImplementedError
@classmethod
def
graderFromConf
(
cls
,
conf
):
if
isinstance
(
conf
,
CourseGrader
):
return
conf
subgraders
=
[]
for
subgraderconf
in
conf
:
subgraderconf
=
subgraderconf
.
copy
()
weight
=
subgraderconf
.
pop
(
"weight"
,
0
)
try
:
if
'min_count'
in
subgraderconf
:
#This is an AssignmentFormatGrader
subgrader
=
AssignmentFormatGrader
(
**
subgraderconf
)
subgraders
.
append
(
(
subgrader
,
subgrader
.
category
,
weight
)
)
elif
'section_name'
in
subgraderconf
:
#This is an SingleSectionGrader
subgrader
=
SingleSectionGrader
(
**
subgraderconf
)
subgraders
.
append
(
(
subgrader
,
subgrader
.
category
,
weight
)
)
else
:
raise
ValueError
(
"Configuration has no appropriate grader class."
)
except
TypeError
as
error
:
log
.
error
(
"Unable to parse grader configuration:
\n
"
+
subgraderconf
+
"
\n
Error was:
\n
"
+
error
)
except
ValueError
as
error
:
log
.
error
(
"Unable to parse grader configuration:
\n
"
+
subgraderconf
+
"
\n
Error was:
\n
"
+
error
)
return
WeightedSubsectionsGrader
(
subgraders
)
class
WeightedSubsectionsGrader
(
CourseGrader
):
"""
This grader takes a list of tuples containing (grader, category_name, weight) and computes
a final grade by totalling the contribution of each sub grader and multiplying it by the
given weight. For example, the sections may be
[ (homeworkGrader, "Homework", 0.15), (labGrader, "Labs", 0.15), (midtermGrader, "Midterm", 0.30), (finalGrader, "Final", 0.40) ]
All items in section_breakdown for each subgrader will be combined. A grade_breakdown will be
composed using the score from each grader.
Note that the sum of the weights is not take into consideration. If the weights add up to
a value > 1, the student may end up with a percent > 100
%
. This allows for sections that
are extra credit.
"""
def
__init__
(
self
,
sections
):
self
.
sections
=
sections
def
grade
(
self
,
grade_sheet
):
total_percent
=
0.0
section_breakdown
=
[]
grade_breakdown
=
[]
for
subgrader
,
section_name
,
weight
in
self
.
sections
:
subgrade_result
=
subgrader
.
grade
(
grade_sheet
)
weightedPercent
=
subgrade_result
[
'percent'
]
*
weight
section_detail
=
"{0} = {1:.1
%
} of a possible {2:.0
%
}"
.
format
(
section_name
,
weightedPercent
,
weight
)
total_percent
+=
weightedPercent
section_breakdown
+=
subgrade_result
[
'section_breakdown'
]
grade_breakdown
.
append
(
{
'percent'
:
weightedPercent
,
'detail'
:
section_detail
,
'category'
:
section_name
}
)
return
{
'percent'
:
total_percent
,
'section_breakdown'
:
section_breakdown
,
'grade_breakdown'
:
grade_breakdown
}
class
SingleSectionGrader
(
CourseGrader
):
"""
This grades a single section with the format section_format and the name section_name.
If the section_name is not appropriate for the short short_label or category, they each may
be specified individually.
"""
def
__init__
(
self
,
section_format
,
section_name
,
short_label
=
None
,
category
=
None
):
self
.
section_format
=
section_format
self
.
section_name
=
section_name
self
.
short_label
=
short_label
or
section_name
self
.
category
=
category
or
section_name
def
grade
(
self
,
grade_sheet
):
foundScore
=
None
if
self
.
section_format
in
grade_sheet
:
for
score
in
grade_sheet
[
self
.
section_format
]:
if
score
.
section
==
self
.
section_name
:
foundScore
=
score
break
if
foundScore
:
percent
=
foundScore
.
earned
/
float
(
foundScore
.
possible
)
detail
=
"{name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
name
=
self
.
section_name
,
percent
=
percent
,
earned
=
float
(
foundScore
.
earned
),
possible
=
float
(
foundScore
.
possible
))
else
:
percent
=
0.0
detail
=
"{name} - 0
%
(?/?)"
.
format
(
name
=
self
.
section_name
)
if
settings
.
GENERATE_PROFILE_SCORES
:
points_possible
=
random
.
randrange
(
50
,
100
)
points_earned
=
random
.
randrange
(
40
,
points_possible
)
percent
=
points_earned
/
float
(
points_possible
)
detail
=
"{name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
name
=
self
.
section_name
,
percent
=
percent
,
earned
=
float
(
points_earned
),
possible
=
float
(
points_possible
))
breakdown
=
[{
'percent'
:
percent
,
'label'
:
self
.
short_label
,
'detail'
:
detail
,
'category'
:
self
.
category
,
'prominent'
:
True
}]
return
{
'percent'
:
percent
,
'section_breakdown'
:
breakdown
,
#No grade_breakdown here
}
class
AssignmentFormatGrader
(
CourseGrader
):
"""
Grades all sections specified in course_format with an equal weight. A specified
number of lowest scores can be dropped from the calculation. The minimum number of
sections in this format must be specified (even if those sections haven't been
written yet).
min_count defines how many assignments are expected throughout the course. Placeholder
scores (of 0) will be inserted if the number of matching sections in the course is < min_count.
If there number of matching sections in the course is > min_count, min_count will be ignored.
category should be presentable to the user, but may not appear. When the grade breakdown is
displayed, scores from the same category will be similar (for example, by color).
section_type is a string that is the type of a singular section. For example, for Labs it
would be "Lab". This defaults to be the same as category.
short_label is similar to section_type, but shorter. For example, for Homework it would be
"HW".
"""
def
__init__
(
self
,
course_format
,
min_count
,
drop_count
,
category
=
None
,
section_type
=
None
,
short_label
=
None
):
self
.
course_format
=
course_format
self
.
min_count
=
min_count
self
.
drop_count
=
drop_count
self
.
category
=
category
or
self
.
course_format
self
.
section_type
=
section_type
or
self
.
course_format
self
.
short_label
=
short_label
or
self
.
course_format
def
grade
(
self
,
grade_sheet
):
def
totalWithDrops
(
breakdown
,
drop_count
):
#create an array of tuples with (index, mark), sorted by mark['percent'] descending
sorted_breakdown
=
sorted
(
enumerate
(
breakdown
),
key
=
lambda
x
:
-
x
[
1
][
'percent'
]
)
# A list of the indices of the dropped scores
dropped_indices
=
[]
if
drop_count
>
0
:
dropped_indices
=
[
x
[
0
]
for
x
in
sorted_breakdown
[
-
drop_count
:]]
aggregate_score
=
0
for
index
,
mark
in
enumerate
(
breakdown
):
if
index
not
in
dropped_indices
:
aggregate_score
+=
mark
[
'percent'
]
if
(
len
(
breakdown
)
-
drop_count
>
0
):
aggregate_score
/=
len
(
breakdown
)
-
drop_count
return
aggregate_score
,
dropped_indices
#Figure the homework scores
scores
=
grade_sheet
.
get
(
self
.
course_format
,
[])
breakdown
=
[]
for
i
in
range
(
max
(
self
.
min_count
,
len
(
scores
))
):
if
i
<
len
(
scores
):
percentage
=
scores
[
i
]
.
earned
/
float
(
scores
[
i
]
.
possible
)
summary
=
"{section_type} {index} - {name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
index
=
i
+
1
,
section_type
=
self
.
section_type
,
name
=
scores
[
i
]
.
section
,
percent
=
percentage
,
earned
=
float
(
scores
[
i
]
.
earned
),
possible
=
float
(
scores
[
i
]
.
possible
)
)
else
:
percentage
=
0
summary
=
"{section_type} {index} Unreleased - 0
%
(?/?)"
.
format
(
index
=
i
+
1
,
section_type
=
self
.
section_type
)
if
settings
.
GENERATE_PROFILE_SCORES
:
points_possible
=
random
.
randrange
(
10
,
50
)
points_earned
=
random
.
randrange
(
5
,
points_possible
)
percentage
=
points_earned
/
float
(
points_possible
)
summary
=
"{section_type} {index} - {name} - {percent:.0
%
} ({earned:.3n}/{possible:.3n})"
.
format
(
index
=
i
+
1
,
section_type
=
self
.
section_type
,
name
=
"Randomly Generated"
,
percent
=
percentage
,
earned
=
float
(
points_earned
),
possible
=
float
(
points_possible
)
)
short_label
=
"{short_label} {index:02d}"
.
format
(
index
=
i
+
1
,
short_label
=
self
.
short_label
)
breakdown
.
append
(
{
'percent'
:
percentage
,
'label'
:
short_label
,
'detail'
:
summary
,
'category'
:
self
.
category
}
)
total_percent
,
dropped_indices
=
totalWithDrops
(
breakdown
,
self
.
drop_count
)
for
dropped_index
in
dropped_indices
:
breakdown
[
dropped_index
][
'mark'
]
=
{
'detail'
:
"The lowest {drop_count} {section_type} scores are dropped."
.
format
(
drop_count
=
self
.
drop_count
,
section_type
=
self
.
section_type
)
}
total_detail
=
"{section_type} Average = {percent:.0
%
}"
.
format
(
percent
=
total_percent
,
section_type
=
self
.
section_type
)
total_label
=
"{short_label} Avg"
.
format
(
short_label
=
self
.
short_label
)
breakdown
.
append
(
{
'percent'
:
total_percent
,
'label'
:
total_label
,
'detail'
:
total_detail
,
'category'
:
self
.
category
,
'prominent'
:
True
}
)
return
{
'percent'
:
total_percent
,
'section_breakdown'
:
breakdown
,
#No grade_breakdown here
}
from
courseware
import
course_settings
import
courseware.content_parser
as
content_parser
from
courseware.graders
import
Score
import
courseware.modules
from
models
import
StudentModule
def
grade_sheet
(
student
):
"""
...
...
@@ -343,8 +82,7 @@ def grade_sheet(student):
'sections'
:
sections
,})
grader
=
CourseGrader
.
graderFromConf
(
course_settings
.
GRADER
)
#TODO: We should cache this grader object
grader
=
course_settings
.
GRADER
grade_summary
=
grader
.
grade
(
totaled_scores
)
return
{
'courseware_summary'
:
chapters
,
...
...
djangoapps/courseware/tests.py
View file @
fe7d4aed
...
...
@@ -4,7 +4,9 @@ import numpy
import
courseware.modules
import
courseware.capa.calc
as
calc
from
grades
import
Score
,
aggregate_scores
,
CourseGrader
,
WeightedSubsectionsGrader
,
SingleSectionGrader
,
AssignmentFormatGrader
import
courseware.graders
as
graders
from
courseware.graders
import
Score
,
CourseGrader
,
WeightedSubsectionsGrader
,
SingleSectionGrader
,
AssignmentFormatGrader
from
courseware.grades
import
aggregate_scores
class
ModelsTest
(
unittest
.
TestCase
):
def
setUp
(
self
):
...
...
@@ -107,9 +109,9 @@ class GraderTest(unittest.TestCase):
}
def
test_SingleSectionGrader
(
self
):
midtermGrader
=
SingleSectionGrader
(
"Midterm"
,
"Midterm Exam"
)
lab4Grader
=
SingleSectionGrader
(
"Lab"
,
"lab4"
)
badLabGrader
=
SingleSectionGrader
(
"Lab"
,
"lab42"
)
midtermGrader
=
graders
.
SingleSectionGrader
(
"Midterm"
,
"Midterm Exam"
)
lab4Grader
=
graders
.
SingleSectionGrader
(
"Lab"
,
"lab4"
)
badLabGrader
=
graders
.
SingleSectionGrader
(
"Lab"
,
"lab42"
)
for
graded
in
[
midtermGrader
.
grade
(
self
.
empty_gradesheet
),
midtermGrader
.
grade
(
self
.
incomplete_gradesheet
),
...
...
@@ -125,12 +127,12 @@ class GraderTest(unittest.TestCase):
self
.
assertAlmostEqual
(
graded
[
'percent'
],
0.2
)
self
.
assertEqual
(
len
(
graded
[
'section_breakdown'
]),
1
)
def
test_
a
ssignmentFormatGrader
(
self
):
homeworkGrader
=
AssignmentFormatGrader
(
"Homework"
,
12
,
2
)
noDropGrader
=
AssignmentFormatGrader
(
"Homework"
,
12
,
0
)
def
test_
A
ssignmentFormatGrader
(
self
):
homeworkGrader
=
graders
.
AssignmentFormatGrader
(
"Homework"
,
12
,
2
)
noDropGrader
=
graders
.
AssignmentFormatGrader
(
"Homework"
,
12
,
0
)
#Even though the minimum number is 3, this should grade correctly when 7 assignments are found
overflowGrader
=
AssignmentFormatGrader
(
"Lab"
,
3
,
2
)
labGrader
=
AssignmentFormatGrader
(
"Lab"
,
7
,
3
)
overflowGrader
=
graders
.
AssignmentFormatGrader
(
"Lab"
,
3
,
2
)
labGrader
=
graders
.
AssignmentFormatGrader
(
"Lab"
,
7
,
3
)
#Test the grading of an empty gradesheet
...
...
@@ -162,25 +164,25 @@ class GraderTest(unittest.TestCase):
def
test_WeightedSubsectionsGrader
(
self
):
#First, a few sub graders
homeworkGrader
=
AssignmentFormatGrader
(
"Homework"
,
12
,
2
)
labGrader
=
AssignmentFormatGrader
(
"Lab"
,
7
,
3
)
midtermGrader
=
SingleSectionGrader
(
"Midterm"
,
"Midterm Exam"
)
homeworkGrader
=
graders
.
AssignmentFormatGrader
(
"Homework"
,
12
,
2
)
labGrader
=
graders
.
AssignmentFormatGrader
(
"Lab"
,
7
,
3
)
midtermGrader
=
graders
.
SingleSectionGrader
(
"Midterm"
,
"Midterm Exam"
)
weightedGrader
=
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.25
),
(
labGrader
,
labGrader
.
category
,
0.25
),
weightedGrader
=
graders
.
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.25
),
(
labGrader
,
labGrader
.
category
,
0.25
),
(
midtermGrader
,
midtermGrader
.
category
,
0.5
)]
)
overOneWeightsGrader
=
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.5
),
(
labGrader
,
labGrader
.
category
,
0.5
),
overOneWeightsGrader
=
graders
.
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.5
),
(
labGrader
,
labGrader
.
category
,
0.5
),
(
midtermGrader
,
midtermGrader
.
category
,
0.5
)]
)
#The midterm should have all weight on this one
zeroWeightsGrader
=
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.0
),
(
labGrader
,
labGrader
.
category
,
0.0
),
zeroWeightsGrader
=
graders
.
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.0
),
(
labGrader
,
labGrader
.
category
,
0.0
),
(
midtermGrader
,
midtermGrader
.
category
,
0.5
)]
)
#This should always have a final percent of zero
allZeroWeightsGrader
=
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.0
),
(
labGrader
,
labGrader
.
category
,
0.0
),
allZeroWeightsGrader
=
graders
.
WeightedSubsectionsGrader
(
[(
homeworkGrader
,
homeworkGrader
.
category
,
0.0
),
(
labGrader
,
labGrader
.
category
,
0.0
),
(
midtermGrader
,
midtermGrader
.
category
,
0.0
)]
)
emptyGrader
=
WeightedSubsectionsGrader
(
[]
)
emptyGrader
=
graders
.
WeightedSubsectionsGrader
(
[]
)
graded
=
weightedGrader
.
grade
(
self
.
test_gradesheet
)
self
.
assertAlmostEqual
(
graded
[
'percent'
],
0.5106547619047619
)
...
...
@@ -221,33 +223,33 @@ class GraderTest(unittest.TestCase):
def
test_graderFromConf
(
self
):
#Confs always produce a WeightedSubsectionsGrader, so we test this by repeating the test
#in test_WeightedSubsectionsGrader, but generate the graders with confs.
#Confs always produce a
graders.
WeightedSubsectionsGrader, so we test this by repeating the test
#in test_
graders.
WeightedSubsectionsGrader, but generate the graders with confs.
weightedGrader
=
CourseGrader
.
graderFromC
onf
([
weightedGrader
=
graders
.
grader_from_c
onf
([
{
'
course_format
'
:
"Homework"
,
'
type
'
:
"Homework"
,
'min_count'
:
12
,
'drop_count'
:
2
,
'short_label'
:
"HW"
,
'weight'
:
0.25
,
},
{
'
course_format
'
:
"Lab"
,
'
type
'
:
"Lab"
,
'min_count'
:
7
,
'drop_count'
:
3
,
'category'
:
"Labs"
,
'weight'
:
0.25
},
{
'
section_format
'
:
"Midterm"
,
'
section_
name'
:
"Midterm Exam"
,
'
type
'
:
"Midterm"
,
'name'
:
"Midterm Exam"
,
'short_label'
:
"Midterm"
,
'weight'
:
0.5
,
},
])
emptyGrader
=
CourseGrader
.
graderFromC
onf
([])
emptyGrader
=
graders
.
grader_from_c
onf
([])
graded
=
weightedGrader
.
grade
(
self
.
test_gradesheet
)
self
.
assertAlmostEqual
(
graded
[
'percent'
],
0.5106547619047619
)
...
...
@@ -260,8 +262,8 @@ class GraderTest(unittest.TestCase):
self
.
assertEqual
(
len
(
graded
[
'grade_breakdown'
]),
0
)
#Test that graders can also be used instead of lists of dictionaries
homeworkGrader
=
AssignmentFormatGrader
(
"Homework"
,
12
,
2
)
homeworkGrader2
=
CourseGrader
.
graderFromC
onf
(
homeworkGrader
)
homeworkGrader
=
graders
.
AssignmentFormatGrader
(
"Homework"
,
12
,
2
)
homeworkGrader2
=
graders
.
grader_from_c
onf
(
homeworkGrader
)
graded
=
homeworkGrader2
.
grade
(
self
.
test_gradesheet
)
self
.
assertAlmostEqual
(
graded
[
'percent'
],
0.11
)
...
...
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