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
4c353d93
Commit
4c353d93
authored
Jul 25, 2017
by
Tyler Hallada
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create GradesService to override persistent grades
parent
f86f5998
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
191 additions
and
1 deletions
+191
-1
lms/djangoapps/grades/models.py
+35
-0
lms/djangoapps/grades/services.py
+51
-0
lms/djangoapps/grades/tests/test_models.py
+9
-0
lms/djangoapps/grades/tests/test_services.py
+91
-0
lms/startup.py
+5
-1
No files found.
lms/djangoapps/grades/models.py
View file @
4c353d93
...
...
@@ -411,6 +411,24 @@ class PersistentSubsectionGrade(DeleteGradesMixin, TimeStampedModel):
user_id
=
params
.
pop
(
'user_id'
)
usage_key
=
params
.
pop
(
'usage_key'
)
# apply grade override if one exists before saving model
try
:
override
=
PersistentSubsectionGradeOverride
.
objects
.
get
(
grade__user_id
=
user_id
,
grade__course_id
=
usage_key
.
course_key
,
grade__usage_key
=
usage_key
,
)
if
override
.
earned_all_override
is
not
None
:
params
[
'earned_all'
]
=
override
.
earned_all_override
if
override
.
possible_all_override
is
not
None
:
params
[
'possible_all'
]
=
override
.
possible_all_override
if
override
.
earned_graded_override
is
not
None
:
params
[
'earned_graded'
]
=
override
.
earned_graded_override
if
override
.
possible_graded_override
is
not
None
:
params
[
'possible_graded'
]
=
override
.
possible_graded_override
except
PersistentSubsectionGradeOverride
.
DoesNotExist
:
pass
grade
,
_
=
cls
.
objects
.
update_or_create
(
user_id
=
user_id
,
course_id
=
usage_key
.
course_key
,
...
...
@@ -666,3 +684,20 @@ class PersistentCourseGrade(DeleteGradesMixin, TimeStampedModel):
'grading_policy_hash'
:
unicode
(
grade
.
grading_policy_hash
),
}
)
class
PersistentSubsectionGradeOverride
(
models
.
Model
):
"""
A django model tracking persistent grades overrides at the subsection level.
"""
class
Meta
(
object
):
app_label
=
"grades"
grade
=
models
.
OneToOneField
(
PersistentSubsectionGrade
,
related_name
=
'override'
)
# earned/possible refers to the number of points achieved and available to achieve.
# graded refers to the subset of all problems that are marked as being graded.
earned_all_override
=
models
.
FloatField
(
null
=
True
,
blank
=
True
)
possible_all_override
=
models
.
FloatField
(
null
=
True
,
blank
=
True
)
earned_graded_override
=
models
.
FloatField
(
null
=
True
,
blank
=
True
)
possible_graded_override
=
models
.
FloatField
(
null
=
True
,
blank
=
True
)
lms/djangoapps/grades/services.py
0 → 100644
View file @
4c353d93
from
lms.djangoapps.grades.models
import
PersistentSubsectionGrade
,
PersistentSubsectionGradeOverride
class
GradesService
(
object
):
"""
Course grade service
Provides various functions related to getting, setting, and overriding user grades.
"""
def
get_subsection_grade
(
self
,
user_id
,
course_key_or_id
,
subsection
):
"""
Finds and returns the earned subsection grade for user
Result is a dict of two key value pairs with keys: earned_all and earned_graded.
"""
grade
=
PersistentSubsectionGrade
.
objects
.
get
(
user_id
=
user_id
,
course_id
=
course_key_or_id
,
usage_key
=
subsection
)
return
{
'earned_all'
:
grade
.
earned_all
,
'earned_graded'
:
grade
.
earned_graded
}
def
override_subsection_grade
(
self
,
user_id
,
course_key_or_id
,
subsection
,
earned_all
=
None
,
earned_graded
=
None
):
"""
Override subsection grade (the PersistentSubsectionGrade model must already exist)
Will not override earned_all or earned_graded value if they are None. Both default to None.
"""
grade
=
PersistentSubsectionGrade
.
objects
.
get
(
user_id
=
user_id
,
course_id
=
course_key_or_id
,
usage_key
=
subsection
)
# Create override that will prevent any future updates to grade
PersistentSubsectionGradeOverride
.
objects
.
create
(
grade
=
grade
,
earned_all_override
=
earned_all
,
earned_graded_override
=
earned_graded
)
# Change the grade as it is now
if
earned_all
is
not
None
:
grade
.
earned_all
=
earned_all
if
earned_graded
is
not
None
:
grade
.
earned_graded
=
earned_graded
grade
.
save
()
lms/djangoapps/grades/tests/test_models.py
View file @
4c353d93
...
...
@@ -23,6 +23,7 @@ from lms.djangoapps.grades.models import (
BlockRecordList
,
PersistentCourseGrade
,
PersistentSubsectionGrade
,
PersistentSubsectionGradeOverride
,
VisibleBlocks
)
from
track.event_transaction_utils
import
get_event_transaction_id
,
get_event_transaction_type
...
...
@@ -306,6 +307,14 @@ class PersistentSubsectionGradeTest(GradesModelTestCase):
grade
=
PersistentSubsectionGrade
.
create_grade
(
**
self
.
params
)
self
.
_assert_tracker_emitted_event
(
tracker_mock
,
grade
)
def
test_grade_override
(
self
):
grade
=
PersistentSubsectionGrade
.
create_grade
(
**
self
.
params
)
override
=
PersistentSubsectionGradeOverride
(
grade
=
grade
,
earned_all_override
=
0.0
,
earned_graded_override
=
0.0
)
override
.
save
()
grade
=
PersistentSubsectionGrade
.
update_or_create_grade
(
**
self
.
params
)
self
.
assertEqual
(
grade
.
earned_all
,
0.0
)
self
.
assertEqual
(
grade
.
earned_graded
,
0.0
)
def
_assert_tracker_emitted_event
(
self
,
tracker_mock
,
grade
):
"""
Helper function to ensure that the mocked event tracker
...
...
lms/djangoapps/grades/tests/test_services.py
0 → 100644
View file @
4c353d93
import
ddt
from
lms.djangoapps.grades.models
import
PersistentSubsectionGrade
,
PersistentSubsectionGradeOverride
from
lms.djangoapps.grades.services
import
GradesService
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
@ddt.ddt
class
GradesServiceTests
(
ModuleStoreTestCase
):
"""
Tests for the Grades service
"""
def
setUp
(
self
,
**
kwargs
):
super
(
GradesServiceTests
,
self
)
.
setUp
()
self
.
service
=
GradesService
()
self
.
course
=
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'DemoX'
,
display_name
=
'Demo_Course'
)
self
.
subsection
=
ItemFactory
.
create
(
parent
=
self
.
course
,
category
=
"subsection"
,
display_name
=
"Subsection"
)
self
.
user
=
UserFactory
()
self
.
grade
=
PersistentSubsectionGrade
.
update_or_create_grade
(
user_id
=
self
.
user
.
id
,
course_id
=
self
.
course
.
id
,
usage_key
=
self
.
subsection
.
location
,
first_attempted
=
None
,
visible_blocks
=
[],
earned_all
=
6.0
,
possible_all
=
6.0
,
earned_graded
=
5.0
,
possible_graded
=
5.0
)
def
test_get_subsection_grade
(
self
):
self
.
assertDictEqual
(
self
.
service
.
get_subsection_grade
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
self
.
course
.
id
,
subsection
=
self
.
subsection
.
location
),
{
'earned_all'
:
6.0
,
'earned_graded'
:
5.0
})
@ddt.data
(
[{
'earned_all'
:
0.0
,
'earned_graded'
:
0.0
},
{
'earned_all'
:
0.0
,
'earned_graded'
:
0.0
}],
[{
'earned_all'
:
0.0
,
'earned_graded'
:
None
},
{
'earned_all'
:
0.0
,
'earned_graded'
:
5.0
}],
[{
'earned_all'
:
None
,
'earned_graded'
:
None
},
{
'earned_all'
:
6.0
,
'earned_graded'
:
5.0
}],
[{
'earned_all'
:
3.0
,
'earned_graded'
:
2.0
},
{
'earned_all'
:
3.0
,
'earned_graded'
:
2.0
}],
)
@ddt.unpack
def
test_override_subsection_grade
(
self
,
override
,
expected
):
PersistentSubsectionGradeOverride
.
objects
.
all
()
.
delete
()
# clear out all previous overrides
self
.
service
.
override_subsection_grade
(
user_id
=
self
.
user
.
id
,
course_key_or_id
=
self
.
course
.
id
,
subsection
=
self
.
subsection
.
location
,
earned_all
=
override
[
'earned_all'
],
earned_graded
=
override
[
'earned_graded'
]
)
grade
=
PersistentSubsectionGrade
.
objects
.
get
(
user_id
=
self
.
user
.
id
,
course_id
=
self
.
course
.
id
,
usage_key
=
self
.
subsection
.
location
)
self
.
assertEqual
(
grade
.
earned_all
,
expected
[
'earned_all'
])
self
.
assertEqual
(
grade
.
earned_graded
,
expected
[
'earned_graded'
])
lms/startup.py
View file @
4c353d93
...
...
@@ -63,18 +63,22 @@ def run():
analytics
.
write_key
=
settings
.
LMS_SEGMENT_KEY
# register any dependency injections that we need to support in edx_proctoring
# right now edx_proctoring is dependent on the openedx.core.djangoapps.credit
# right now edx_proctoring is dependent on the openedx.core.djangoapps.credit and
# lms.djangoapps.grades
if
settings
.
FEATURES
.
get
(
'ENABLE_SPECIAL_EXAMS'
):
# Import these here to avoid circular dependencies of the form:
# edx-platform app --> DRF --> django translation --> edx-platform app
from
edx_proctoring.runtime
import
set_runtime_service
from
lms.djangoapps.instructor.services
import
InstructorService
from
openedx.core.djangoapps.credit.services
import
CreditService
from
lms.djangoapps.grades.services
import
GradesService
set_runtime_service
(
'credit'
,
CreditService
())
# register InstructorService (for deleting student attempts and user staff access roles)
set_runtime_service
(
'instructor'
,
InstructorService
())
set_runtime_service
(
'grades'
,
GradesService
())
# In order to allow modules to use a handler url, we need to
# monkey-patch the x_module library.
# TODO: Remove this code when Runtimes are no longer created by modulestores
...
...
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