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
Show 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
...
@@ -16,6 +16,7 @@ from lazy import lazy
import
logging
import
logging
from
django.db
import
models
from
django.db
import
models
from
django.utils.timezone
import
now
from
model_utils.models
import
TimeStampedModel
from
model_utils.models
import
TimeStampedModel
from
coursewarehistoryextended.fields
import
UnsignedBigIntAutoField
from
coursewarehistoryextended.fields
import
UnsignedBigIntAutoField
...
@@ -378,9 +379,13 @@ class PersistentCourseGrade(TimeStampedModel):
...
@@ -378,9 +379,13 @@ class PersistentCourseGrade(TimeStampedModel):
# (course_id, user_id) for individual grades
# (course_id, user_id) for individual grades
# (course_id) for instructors to see all course grades, implicitly created via the unique_together constraint
# (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
# (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
=
[
unique_together
=
[
(
'course_id'
,
'user_id'
),
(
'course_id'
,
'user_id'
),
]
]
index_together
=
[
(
'passed_timestamp'
,
'course_id'
),
]
# primary key will need to be large for this table
# primary key will need to be large for this table
id
=
UnsignedBigIntAutoField
(
primary_key
=
True
)
# pylint: disable=invalid-name
id
=
UnsignedBigIntAutoField
(
primary_key
=
True
)
# pylint: disable=invalid-name
...
@@ -396,18 +401,21 @@ class PersistentCourseGrade(TimeStampedModel):
...
@@ -396,18 +401,21 @@ class PersistentCourseGrade(TimeStampedModel):
percent_grade
=
models
.
FloatField
(
blank
=
False
)
percent_grade
=
models
.
FloatField
(
blank
=
False
)
letter_grade
=
models
.
CharField
(
u'Letter grade for course'
,
blank
=
False
,
max_length
=
255
)
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
):
def
__unicode__
(
self
):
"""
"""
Returns a string representation of this model.
Returns a string representation of this model.
"""
"""
return
u
"{} user: {}, course version: {}, grading policy: {}, percent grade {}
%
, letter grade {}"
.
format
(
return
u
', '
.
join
([
type
(
self
)
.
__name__
,
u"{} user: {}"
.
format
(
type
(
self
)
.
__name__
,
self
.
user_id
)
,
self
.
user_id
,
u"course version: {}"
.
format
(
self
.
course_version
)
,
self
.
course_version
,
u"grading policy: {}"
.
format
(
self
.
grading_policy_hash
)
,
self
.
grading_policy_hash
,
u"percent grade: {}
%
"
.
format
(
self
.
percent_grade
)
,
self
.
percent_grade
,
u"letter grade: {}"
.
format
(
self
.
letter_grade
)
,
self
.
letter_grade
,
u"passed_timestamp: {}"
.
format
(
self
.
passed_timestamp
)
,
)
]
)
@classmethod
@classmethod
def
read_course_grade
(
cls
,
user_id
,
course_id
):
def
read_course_grade
(
cls
,
user_id
,
course_id
):
...
@@ -428,6 +436,7 @@ class PersistentCourseGrade(TimeStampedModel):
...
@@ -428,6 +436,7 @@ class PersistentCourseGrade(TimeStampedModel):
Creates a course grade in the database.
Creates a course grade in the database.
Returns a PersistedCourseGrade object.
Returns a PersistedCourseGrade object.
"""
"""
passed
=
kwargs
.
pop
(
'passed'
)
if
kwargs
.
get
(
'course_version'
,
None
)
is
None
:
if
kwargs
.
get
(
'course_version'
,
None
)
is
None
:
kwargs
[
'course_version'
]
=
""
kwargs
[
'course_version'
]
=
""
...
@@ -436,4 +445,7 @@ class PersistentCourseGrade(TimeStampedModel):
...
@@ -436,4 +445,7 @@ class PersistentCourseGrade(TimeStampedModel):
course_id
=
course_id
,
course_id
=
course_id
,
defaults
=
kwargs
defaults
=
kwargs
)
)
if
passed
and
not
grade
.
passed_timestamp
:
grade
.
passed_timestamp
=
now
()
grade
.
save
()
return
grade
return
grade
lms/djangoapps/grades/new/course_grade.py
View file @
2c86a6dd
...
@@ -3,10 +3,12 @@ CourseGrade Class
...
@@ -3,10 +3,12 @@ CourseGrade Class
"""
"""
from
collections
import
defaultdict
from
collections
import
defaultdict
from
logging
import
getLogger
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.exceptions
import
PermissionDenied
from
django.core.exceptions
import
PermissionDenied
from
lazy
import
lazy
from
lazy
import
lazy
from
logging
import
getLogger
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
from
lms.djangoapps.course_blocks.api
import
get_course_blocks
from
lms.djangoapps.grades.config.models
import
PersistentGradesEnabledFlag
from
lms.djangoapps.grades.config.models
import
PersistentGradesEnabledFlag
from
openedx.core.djangoapps.signals.signals
import
GRADES_UPDATED
from
openedx.core.djangoapps.signals.signals
import
GRADES_UPDATED
...
@@ -163,6 +165,7 @@ class CourseGrade(object):
...
@@ -163,6 +165,7 @@ class CourseGrade(object):
grading_policy_hash
=
grading_policy_hash
,
grading_policy_hash
=
grading_policy_hash
,
percent_grade
=
self
.
percent
,
percent_grade
=
self
.
percent
,
letter_grade
=
self
.
letter_grade
or
""
,
letter_grade
=
self
.
letter_grade
or
""
,
passed
=
self
.
passed
,
)
)
self
.
_signal_listeners_when_grade_computed
()
self
.
_signal_listeners_when_grade_computed
()
...
...
lms/djangoapps/grades/tests/test_models.py
View file @
2c86a6dd
...
@@ -10,6 +10,8 @@ import json
...
@@ -10,6 +10,8 @@ import json
from
django.db.utils
import
IntegrityError
from
django.db.utils
import
IntegrityError
from
django.test
import
TestCase
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
opaque_keys.edx.locator
import
CourseLocator
,
BlockUsageLocator
from
lms.djangoapps.grades.models
import
(
from
lms.djangoapps.grades.models
import
(
...
@@ -271,10 +273,11 @@ class PersistentCourseGradesTest(GradesModelTestCase):
...
@@ -271,10 +273,11 @@ class PersistentCourseGradesTest(GradesModelTestCase):
),
),
"percent_grade"
:
77.7
,
"percent_grade"
:
77.7
,
"letter_grade"
:
"Great job"
,
"letter_grade"
:
"Great job"
,
"passed"
:
True
}
}
def
test_update
(
self
):
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
[
"percent_grade"
]
=
88.8
self
.
params
[
"letter_grade"
]
=
"Better job"
self
.
params
[
"letter_grade"
]
=
"Better job"
updated_grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
updated_grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
...
@@ -282,11 +285,61 @@ class PersistentCourseGradesTest(GradesModelTestCase):
...
@@ -282,11 +285,61 @@ class PersistentCourseGradesTest(GradesModelTestCase):
self
.
assertEqual
(
updated_grade
.
letter_grade
,
"Better job"
)
self
.
assertEqual
(
updated_grade
.
letter_grade
,
"Better job"
)
self
.
assertEqual
(
created_grade
.
id
,
updated_grade
.
id
)
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
):
def
test_create_and_read_grade
(
self
):
created_grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
created_grade
=
PersistentCourseGrade
.
update_or_create_course_grade
(
**
self
.
params
)
read_grade
=
PersistentCourseGrade
.
read_course_grade
(
self
.
params
[
"user_id"
],
self
.
params
[
"course_id"
])
read_grade
=
PersistentCourseGrade
.
read_course_grade
(
self
.
params
[
"user_id"
],
self
.
params
[
"course_id"
])
for
param
in
self
.
params
:
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
.
assertEqual
(
self
.
params
[
param
],
getattr
(
created_grade
,
param
))
self
.
assertIsInstance
(
created_grade
.
passed_timestamp
,
datetime
)
self
.
assertEqual
(
created_grade
,
read_grade
)
self
.
assertEqual
(
created_grade
,
read_grade
)
def
test_course_version_optional
(
self
):
def
test_course_version_optional
(
self
):
...
...
lms/djangoapps/grades/tests/test_new.py
View file @
2c86a6dd
...
@@ -129,6 +129,13 @@ class TestCourseGradeFactory(GradeTestBase):
...
@@ -129,6 +129,13 @@ class TestCourseGradeFactory(GradeTestBase):
self
.
assertEqual
(
course_grade
.
letter_grade
,
u'Pass'
)
self
.
assertEqual
(
course_grade
.
letter_grade
,
u'Pass'
)
self
.
assertEqual
(
course_grade
.
percent
,
0.5
)
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
):
def
test_get_persisted
(
self
):
grade_factory
=
CourseGradeFactory
(
self
.
request
.
user
)
grade_factory
=
CourseGradeFactory
(
self
.
request
.
user
)
# first, create a grade in the database
# 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