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
2c86a6dd
Commit
2c86a6dd
authored
Nov 10, 2016
by
Cliff Dyer
Committed by
GitHub
Nov 10, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #13963 from edx/beryl/date-passed
Add date_passed column to PersistentCourseGrade model
parents
781d3c9b
2bc35396
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
108 additions
and
10 deletions
+108
-10
lms/djangoapps/grades/migrations/0007_add_passed_timestamp_column.py
+23
-0
lms/djangoapps/grades/models.py
+20
-8
lms/djangoapps/grades/new/course_grade.py
+4
-1
lms/djangoapps/grades/tests/test_models.py
+54
-1
lms/djangoapps/grades/tests/test_new.py
+7
-0
No files found.
lms/djangoapps/grades/migrations/0007_add_passed_timestamp_column.py
0 → 100644
View file @
2c86a6dd
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'grades'
,
'0006_persistent_course_grades'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'persistentcoursegrade'
,
name
=
'passed_timestamp'
,
field
=
models
.
DateTimeField
(
null
=
True
,
verbose_name
=
'Date learner earned a passing grade'
,
blank
=
True
),
),
migrations
.
AlterIndexTogether
(
name
=
'persistentcoursegrade'
,
index_together
=
set
([(
'passed_timestamp'
,
'course_id'
)]),
),
]
lms/djangoapps/grades/models.py
View file @
2c86a6dd
...
...
@@ -16,6 +16,7 @@ from lazy import lazy
import
logging
from
django.db
import
models
from
django.utils.timezone
import
now
from
model_utils.models
import
TimeStampedModel
from
coursewarehistoryextended.fields
import
UnsignedBigIntAutoField
...
...
@@ -378,9 +379,13 @@ class PersistentCourseGrade(TimeStampedModel):
# (course_id, user_id) for individual grades
# (course_id) for instructors to see all course grades, implicitly created via the unique_together constraint
# (user_id) for course dashboard; explicitly declared as an index below
# (passed_timestamp, course_id) for tracking when users first earned a passing grade.
unique_together
=
[
(
'course_id'
,
'user_id'
),
]
index_together
=
[
(
'passed_timestamp'
,
'course_id'
),
]
# primary key will need to be large for this table
id
=
UnsignedBigIntAutoField
(
primary_key
=
True
)
# pylint: disable=invalid-name
...
...
@@ -396,18 +401,21 @@ class PersistentCourseGrade(TimeStampedModel):
percent_grade
=
models
.
FloatField
(
blank
=
False
)
letter_grade
=
models
.
CharField
(
u'Letter grade for course'
,
blank
=
False
,
max_length
=
255
)
# Information related to course completion
passed_timestamp
=
models
.
DateTimeField
(
u'Date learner earned a passing grade'
,
blank
=
True
,
null
=
True
)
def
__unicode__
(
self
):
"""
Returns a string representation of this model.
"""
return
u
"{} user: {}, course version: {}, grading policy: {}, percent grade {}
%
, letter grade {}"
.
format
(
type
(
self
)
.
__name__
,
self
.
user_id
,
self
.
course_version
,
self
.
grading_policy_hash
,
self
.
percent_grade
,
self
.
letter_grade
,
)
return
u
', '
.
join
([
u"{} user: {}"
.
format
(
type
(
self
)
.
__name__
,
self
.
user_id
)
,
u"course version: {}"
.
format
(
self
.
course_version
)
,
u"grading policy: {}"
.
format
(
self
.
grading_policy_hash
)
,
u"percent grade: {}
%
"
.
format
(
self
.
percent_grade
)
,
u"letter grade: {}"
.
format
(
self
.
letter_grade
)
,
u"passed_timestamp: {}"
.
format
(
self
.
passed_timestamp
)
,
]
)
@classmethod
def
read_course_grade
(
cls
,
user_id
,
course_id
):
...
...
@@ -428,6 +436,7 @@ class PersistentCourseGrade(TimeStampedModel):
Creates a course grade in the database.
Returns a PersistedCourseGrade object.
"""
passed
=
kwargs
.
pop
(
'passed'
)
if
kwargs
.
get
(
'course_version'
,
None
)
is
None
:
kwargs
[
'course_version'
]
=
""
...
...
@@ -436,4 +445,7 @@ class PersistentCourseGrade(TimeStampedModel):
course_id
=
course_id
,
defaults
=
kwargs
)
if
passed
and
not
grade
.
passed_timestamp
:
grade
.
passed_timestamp
=
now
()
grade
.
save
()
return
grade
lms/djangoapps/grades/new/course_grade.py
View file @
2c86a6dd
...
...
@@ -3,10 +3,12 @@ CourseGrade Class
"""
from
collections
import
defaultdict
from
logging
import
getLogger
from
django.conf
import
settings
from
django.core.exceptions
import
PermissionDenied
from
lazy
import
lazy
from
logging
import
getLogger
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
from
lms.djangoapps.grades.config.models
import
PersistentGradesEnabledFlag
from
openedx.core.djangoapps.signals.signals
import
GRADES_UPDATED
...
...
@@ -163,6 +165,7 @@ class CourseGrade(object):
grading_policy_hash
=
grading_policy_hash
,
percent_grade
=
self
.
percent
,
letter_grade
=
self
.
letter_grade
or
""
,
passed
=
self
.
passed
,
)
self
.
_signal_listeners_when_grade_computed
()
...
...
lms/djangoapps/grades/tests/test_models.py
View file @
2c86a6dd
...
...
@@ -10,6 +10,8 @@ import json
from
django.db.utils
import
IntegrityError
from
django.test
import
TestCase
from
django.utils.timezone
import
now
from
freezegun
import
freeze_time
from
opaque_keys.edx.locator
import
CourseLocator
,
BlockUsageLocator
from
lms.djangoapps.grades.models
import
(
...
...
@@ -271,10 +273,11 @@ class PersistentCourseGradesTest(GradesModelTestCase):
),
"percent_grade"
:
77.7
,
"letter_grade"
:
"Great job"
,
"passed"
:
True
}
def
test_update
(
self
):
created_grade
=
PersistentCourseGrade
.
objects
.
creat
e
(
**
self
.
params
)
created_grade
=
PersistentCourseGrade
.
update_or_create_course_grad
e
(
**
self
.
params
)
self
.
params
[
"percent_grade"
]
=
88.8
self
.
params
[
"letter_grade"
]
=
"Better job"
updated_grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
...
...
@@ -282,11 +285,61 @@ class PersistentCourseGradesTest(GradesModelTestCase):
self
.
assertEqual
(
updated_grade
.
letter_grade
,
"Better job"
)
self
.
assertEqual
(
created_grade
.
id
,
updated_grade
.
id
)
def
test_passed_timestamp
(
self
):
# When the user has not passed, passed_timestamp is None
self
.
params
.
update
({
u'percent_grade'
:
25.0
,
u'letter_grade'
:
u''
,
u'passed'
:
False
,
})
grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
self
.
assertIsNone
(
grade
.
passed_timestamp
)
# After the user earns a passing grade, the passed_timestamp is set
self
.
params
.
update
({
u'percent_grade'
:
75.0
,
u'letter_grade'
:
u'C'
,
u'passed'
:
True
,
})
grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
passed_timestamp
=
grade
.
passed_timestamp
self
.
assertEqual
(
grade
.
letter_grade
,
u'C'
)
self
.
assertIsInstance
(
passed_timestamp
,
datetime
)
# After the user improves their score, the new grade is reflected, but
# the passed_timestamp remains the same.
self
.
params
.
update
({
u'percent_grade'
:
95.0
,
u'letter_grade'
:
u'A'
,
u'passed'
:
True
,
})
grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
self
.
assertEqual
(
grade
.
letter_grade
,
u'A'
)
self
.
assertEqual
(
grade
.
passed_timestamp
,
passed_timestamp
)
# If the grade later reverts to a failing grade, they keep their passed_timestamp
self
.
params
.
update
({
u'percent_grade'
:
20.0
,
u'letter_grade'
:
u''
,
u'passed'
:
False
,
})
grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
self
.
assertEqual
(
grade
.
letter_grade
,
u''
)
self
.
assertEqual
(
grade
.
passed_timestamp
,
passed_timestamp
)
@freeze_time
(
now
())
def
test_passed_timestamp_is_now
(
self
):
grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
self
.
assertEqual
(
now
(),
grade
.
passed_timestamp
)
def
test_create_and_read_grade
(
self
):
created_grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
read_grade
=
PersistentCourseGrade
.
read_course_grade
(
self
.
params
[
"user_id"
],
self
.
params
[
"course_id"
])
for
param
in
self
.
params
:
if
param
==
u'passed'
:
continue
# passed/passed_timestamp takes special handling, and is tested separately
self
.
assertEqual
(
self
.
params
[
param
],
getattr
(
created_grade
,
param
))
self
.
assertIsInstance
(
created_grade
.
passed_timestamp
,
datetime
)
self
.
assertEqual
(
created_grade
,
read_grade
)
def
test_course_version_optional
(
self
):
...
...
lms/djangoapps/grades/tests/test_new.py
View file @
2c86a6dd
...
...
@@ -129,6 +129,13 @@ class TestCourseGradeFactory(GradeTestBase):
self
.
assertEqual
(
course_grade
.
letter_grade
,
u'Pass'
)
self
.
assertEqual
(
course_grade
.
percent
,
0.5
)
def
test_zero_course_grade
(
self
):
grade_factory
=
CourseGradeFactory
(
self
.
request
.
user
)
with
mock_get_score
(
0
,
2
):
course_grade
=
grade_factory
.
create
(
self
.
course
)
self
.
assertIsNone
(
course_grade
.
letter_grade
)
self
.
assertEqual
(
course_grade
.
percent
,
0.0
)
def
test_get_persisted
(
self
):
grade_factory
=
CourseGradeFactory
(
self
.
request
.
user
)
# first, create a grade in the database
...
...
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