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
4815181a
Commit
4815181a
authored
Nov 13, 2013
by
David Ormsbee
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1669 from edx/hotfix/revert_grading_manual_tx
Revert "more granular transactions in grading [LMS-1480]"
parents
371f4c9d
a1778a17
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
125 additions
and
173 deletions
+125
-173
lms/djangoapps/courseware/grades.py
+80
-117
lms/djangoapps/courseware/tests/test_submitting_problems.py
+16
-10
lms/djangoapps/courseware/views.py
+29
-46
No files found.
lms/djangoapps/courseware/grades.py
View file @
4815181a
...
@@ -4,11 +4,9 @@ from __future__ import division
...
@@ -4,11 +4,9 @@ from __future__ import division
import
random
import
random
import
logging
import
logging
from
contextlib
import
contextmanager
from
collections
import
defaultdict
from
collections
import
defaultdict
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.db
import
transaction
from
courseware.model_data
import
FieldDataCache
,
DjangoKeyValueStore
from
courseware.model_data
import
FieldDataCache
,
DjangoKeyValueStore
from
xblock.fields
import
Scope
from
xblock.fields
import
Scope
...
@@ -123,20 +121,8 @@ def answer_distributions(request, course):
...
@@ -123,20 +121,8 @@ def answer_distributions(request, course):
return
counts
return
counts
@transaction.commit_manually
def
grade
(
student
,
request
,
course
,
field_data_cache
=
None
,
keep_raw_scores
=
False
):
def
grade
(
student
,
request
,
course
,
keep_raw_scores
=
False
):
"""
"""
Wraps "_grade" with the manual_transaction context manager just in case
there are unanticipated errors.
"""
with
manual_transaction
():
return
_grade
(
student
,
request
,
course
,
keep_raw_scores
)
def
_grade
(
student
,
request
,
course
,
keep_raw_scores
):
"""
Unwrapped version of "grade"
This grades a student as quickly as possible. It returns the
This grades a student as quickly as possible. It returns the
output from the course grader, augmented with the final letter
output from the course grader, augmented with the final letter
grade. The keys in the output are:
grade. The keys in the output are:
...
@@ -146,17 +132,20 @@ def _grade(student, request, course, keep_raw_scores):
...
@@ -146,17 +132,20 @@ def _grade(student, request, course, keep_raw_scores):
- grade : A final letter grade.
- grade : A final letter grade.
- percent : The final percent for the class (rounded up).
- percent : The final percent for the class (rounded up).
- section_breakdown : A breakdown of each section that makes
- section_breakdown : A breakdown of each section that makes
up the grade. (For display)
up the grade. (For display)
- grade_breakdown : A breakdown of the major components that
- grade_breakdown : A breakdown of the major components that
make up the final grade. (For display)
make up the final grade. (For display)
- keep_raw_scores : if True, then value for key 'raw_scores' contains scores
- keep_raw_scores : if True, then value for key 'raw_scores' contains scores for every graded module
for every graded module
More information on the format is in the docstring for CourseGrader.
More information on the format is in the docstring for CourseGrader.
"""
"""
grading_context
=
course
.
grading_context
grading_context
=
course
.
grading_context
raw_scores
=
[]
raw_scores
=
[]
if
field_data_cache
is
None
:
field_data_cache
=
FieldDataCache
(
grading_context
[
'all_descriptors'
],
course
.
id
,
student
)
totaled_scores
=
{}
totaled_scores
=
{}
# This next complicated loop is just to collect the totaled_scores, which is
# This next complicated loop is just to collect the totaled_scores, which is
# passed to the grader
# passed to the grader
...
@@ -166,22 +155,26 @@ def _grade(student, request, course, keep_raw_scores):
...
@@ -166,22 +155,26 @@ def _grade(student, request, course, keep_raw_scores):
section_descriptor
=
section
[
'section_descriptor'
]
section_descriptor
=
section
[
'section_descriptor'
]
section_name
=
section_descriptor
.
display_name_with_default
section_name
=
section_descriptor
.
display_name_with_default
# some problems have state that is updated independently of interaction
should_grade_section
=
False
# with the LMS, so they need to always be scored. (E.g. foldit.)
should_grade_section
=
any
(
descriptor
.
always_recalculate_grades
for
descriptor
in
section
[
'xmoduledescriptors'
]
)
# If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0%
# If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0%
if
not
should_grade_section
:
for
moduledescriptor
in
section
[
'xmoduledescriptors'
]:
with
manual_transaction
():
# some problems have state that is updated independently of interaction
should_grade_section
=
StudentModule
.
objects
.
filter
(
# with the LMS, so they need to always be scored. (E.g. foldit.)
student
=
student
,
if
moduledescriptor
.
always_recalculate_grades
:
module_type
=
'problem'
,
should_grade_section
=
True
module_state_key__in
=
[
break
descriptor
.
location
for
descriptor
in
section
[
'xmoduledescriptors'
]
]
# Create a fake key to pull out a StudentModule object from the FieldDataCache
)
.
exists
()
key
=
DjangoKeyValueStore
.
Key
(
Scope
.
user_state
,
student
.
id
,
moduledescriptor
.
location
,
None
)
if
field_data_cache
.
find
(
key
):
should_grade_section
=
True
break
if
should_grade_section
:
if
should_grade_section
:
scores
=
[]
scores
=
[]
...
@@ -190,13 +183,11 @@ def _grade(student, request, course, keep_raw_scores):
...
@@ -190,13 +183,11 @@ def _grade(student, request, course, keep_raw_scores):
'''creates an XModule instance given a descriptor'''
'''creates an XModule instance given a descriptor'''
# TODO: We need the request to pass into here. If we could forego that, our arguments
# TODO: We need the request to pass into here. If we could forego that, our arguments
# would be simpler
# would be simpler
with
manual_transaction
():
field_data_cache
=
FieldDataCache
([
descriptor
],
course
.
id
,
student
)
return
get_module_for_descriptor
(
student
,
request
,
descriptor
,
field_data_cache
,
course
.
id
)
return
get_module_for_descriptor
(
student
,
request
,
descriptor
,
field_data_cache
,
course
.
id
)
for
module_descriptor
in
yield_dynamic_descriptor_descendents
(
section_descriptor
,
create_module
):
for
module_descriptor
in
yield_dynamic_descriptor_descendents
(
section_descriptor
,
create_module
):
(
correct
,
total
)
=
get_score
(
course
.
id
,
student
,
module_descriptor
,
create_module
)
(
correct
,
total
)
=
get_score
(
course
.
id
,
student
,
module_descriptor
,
create_module
,
field_data_cache
)
if
correct
is
None
and
total
is
None
:
if
correct
is
None
and
total
is
None
:
continue
continue
...
@@ -265,23 +256,11 @@ def grade_for_percentage(grade_cutoffs, percentage):
...
@@ -265,23 +256,11 @@ def grade_for_percentage(grade_cutoffs, percentage):
return
letter_grade
return
letter_grade
@transaction.commit_manually
def
progress_summary
(
student
,
request
,
course
):
"""
Wraps "_progress_summary" with the manual_transaction context manager just
in case there are unanticipated errors.
"""
with
manual_transaction
():
return
_progress_summary
(
student
,
request
,
course
)
# TODO: This method is not very good. It was written in the old course style and
# TODO: This method is not very good. It was written in the old course style and
# then converted over and performance is not good. Once the progress page is redesigned
# then converted over and performance is not good. Once the progress page is redesigned
# to not have the progress summary this method should be deleted (so it won't be copied).
# to not have the progress summary this method should be deleted (so it won't be copied).
def
_progress_summary
(
student
,
request
,
cours
e
):
def
progress_summary
(
student
,
request
,
course
,
field_data_cach
e
):
"""
"""
Unwrapped version of "progress_summary".
This pulls a summary of all problems in the course.
This pulls a summary of all problems in the course.
Returns
Returns
...
@@ -294,21 +273,20 @@ def _progress_summary(student, request, course):
...
@@ -294,21 +273,20 @@ def _progress_summary(student, request, course):
Arguments:
Arguments:
student: A User object for the student to grade
student: A User object for the student to grade
course: A Descriptor containing the course to grade
course: A Descriptor containing the course to grade
field_data_cache: A FieldDataCache initialized with all
instance_modules for the student
If the student does not have access to load the course module, this function
If the student does not have access to load the course module, this function
will return None.
will return None.
"""
"""
with
manual_transaction
():
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
# TODO: We need the request to pass into here. If we could forego that, our arguments
course
.
id
,
student
,
course
,
depth
=
None
# would be simpler
)
course_module
=
get_module_for_descriptor
(
student
,
request
,
course
,
field_data_cache
,
course
.
id
)
# TODO: We need the request to pass into here. If we could
if
not
course_module
:
# forego that, our arguments would be simpler
# This student must not have access to the course.
course_module
=
get_module_for_descriptor
(
student
,
request
,
course
,
field_data_cache
,
course
.
id
)
return
None
if
not
course_module
:
# This student must not have access to the course.
return
None
chapters
=
[]
chapters
=
[]
# Don't include chapters that aren't displayable (e.g. due to error)
# Don't include chapters that aren't displayable (e.g. due to error)
...
@@ -318,52 +296,50 @@ def _progress_summary(student, request, course):
...
@@ -318,52 +296,50 @@ def _progress_summary(student, request, course):
continue
continue
sections
=
[]
sections
=
[]
for
section_module
in
chapter_module
.
get_display_items
():
for
section_module
in
chapter_module
.
get_display_items
():
# Skip if the section is hidden
# Skip if the section is hidden
with
manual_transaction
():
if
section_module
.
hide_from_toc
:
if
section_module
.
hide_from_toc
:
continue
continue
graded
=
section_module
.
graded
# Same for sections
scores
=
[]
graded
=
section_module
.
graded
scores
=
[]
module_creator
=
section_module
.
xmodule_runtime
.
get_module
module_creator
=
section_module
.
xmodule_runtime
.
get_module
for
module_descriptor
in
yield_dynamic_descriptor_descendents
(
section_module
,
module_creator
):
for
module_descriptor
in
yield_dynamic_descriptor_descendents
(
section_module
,
module_creator
):
course_id
=
course
.
id
course_id
=
course
.
id
(
correct
,
total
)
=
get_score
(
course_id
,
student
,
module_descriptor
,
module_creator
)
(
correct
,
total
)
=
get_score
(
course_id
,
student
,
module_descriptor
,
module_creator
,
field_data_cache
)
if
correct
is
None
and
total
is
None
:
if
correct
is
None
and
total
is
None
:
continue
continue
scores
.
append
(
Score
(
correct
,
total
,
graded
,
module_descriptor
.
display_name_with_default
))
scores
.
append
(
Score
(
correct
,
total
,
graded
,
module_descriptor
.
display_name_with_default
))
scores
.
reverse
()
section_total
,
_
=
graders
.
aggregate_scores
(
scores
,
section_module
.
display_name_with_default
)
scores
.
reverse
()
module_format
=
section_module
.
format
if
section_module
.
format
is
not
None
else
''
section_total
,
_
=
graders
.
aggregate_scores
(
sections
.
append
({
scores
,
section_module
.
display_name_with_default
)
'display_name'
:
section_module
.
display_name_with_default
,
'url_name'
:
section_module
.
url_name
,
module_format
=
section_module
.
format
if
section_module
.
format
is
not
None
else
''
'scores'
:
scores
,
sections
.
append
({
'section_total'
:
section_total
,
'display_name'
:
section_module
.
display_name_with_default
,
'format'
:
module_format
,
'url_name'
:
section_module
.
url_name
,
'due'
:
section_module
.
due
,
'scores'
:
scores
,
'graded'
:
graded
,
'section_total'
:
section_total
,
})
'format'
:
module_format
,
'due'
:
section_module
.
due
,
chapters
.
append
({
'course'
:
course
.
display_name_with_default
,
'graded'
:
graded
,
'display_name'
:
chapter_module
.
display_name_with_default
,
})
'url_name'
:
chapter_module
.
url_name
,
'sections'
:
sections
})
chapters
.
append
({
'course'
:
course
.
display_name_with_default
,
'display_name'
:
chapter_module
.
display_name_with_default
,
'url_name'
:
chapter_module
.
url_name
,
'sections'
:
sections
})
return
chapters
return
chapters
def
get_score
(
course_id
,
user
,
problem_descriptor
,
module_creator
):
def
get_score
(
course_id
,
user
,
problem_descriptor
,
module_creator
,
field_data_cache
):
"""
"""
Return the score for a user on a problem, as a tuple (correct, total).
Return the score for a user on a problem, as a tuple (correct, total).
e.g. (5,7) if you got 5 out of 7 points.
e.g. (5,7) if you got 5 out of 7 points.
...
@@ -396,15 +372,15 @@ def get_score(course_id, user, problem_descriptor, module_creator):
...
@@ -396,15 +372,15 @@ def get_score(course_id, user, problem_descriptor, module_creator):
# These are not problems, and do not have a score
# These are not problems, and do not have a score
return
(
None
,
None
)
return
(
None
,
None
)
try
:
# Create a fake KeyValueStore key to pull out the StudentModule
student_module
=
StudentModule
.
objects
.
get
(
key
=
DjangoKeyValueStore
.
Key
(
student
=
user
,
Scope
.
user_state
,
course_id
=
course_
id
,
user
.
id
,
module_type
=
'problem'
,
problem_descriptor
.
location
,
module_state_key
=
problem_descriptor
.
location
None
)
)
except
StudentModule
.
DoesNotExist
:
student_module
=
None
student_module
=
field_data_cache
.
find
(
key
)
if
student_module
is
not
None
and
student_module
.
max_grade
is
not
None
:
if
student_module
is
not
None
and
student_module
.
max_grade
is
not
None
:
correct
=
student_module
.
grade
if
student_module
.
grade
is
not
None
else
0
correct
=
student_module
.
grade
if
student_module
.
grade
is
not
None
else
0
...
@@ -435,16 +411,3 @@ def get_score(course_id, user, problem_descriptor, module_creator):
...
@@ -435,16 +411,3 @@ def get_score(course_id, user, problem_descriptor, module_creator):
total
=
weight
total
=
weight
return
(
correct
,
total
)
return
(
correct
,
total
)
@contextmanager
def
manual_transaction
():
"""A context manager for managing manual transactions"""
try
:
yield
except
Exception
:
transaction
.
rollback
()
log
.
exception
(
'Due to an error, this transaction has been rolled back'
)
raise
else
:
transaction
.
commit
()
lms/djangoapps/courseware/tests/test_submitting_problems.py
View file @
4815181a
...
@@ -236,11 +236,14 @@ class TestCourseGrader(TestSubmittingProblems):
...
@@ -236,11 +236,14 @@ class TestCourseGrader(TestSubmittingProblems):
make up the final grade. (For display)
make up the final grade. (For display)
"""
"""
fake_request
=
self
.
factory
.
get
(
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
reverse
(
'progress'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
})
self
.
course
.
id
,
self
.
student_user
,
self
.
course
)
)
fake_request
=
self
.
factory
.
get
(
reverse
(
'progress'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
}))
return
grades
.
grade
(
self
.
student_user
,
fake_request
,
self
.
course
)
return
grades
.
grade
(
self
.
student_user
,
fake_request
,
self
.
course
,
field_data_cache
)
def
get_progress_summary
(
self
):
def
get_progress_summary
(
self
):
"""
"""
...
@@ -254,13 +257,16 @@ class TestCourseGrader(TestSubmittingProblems):
...
@@ -254,13 +257,16 @@ class TestCourseGrader(TestSubmittingProblems):
etc.
etc.
"""
"""
fake_request
=
self
.
factory
.
get
(
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
reverse
(
'progress'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
})
self
.
course
.
id
,
self
.
student_user
,
self
.
course
)
)
progress_summary
=
grades
.
progress_summary
(
fake_request
=
self
.
factory
.
get
(
reverse
(
'progress'
,
self
.
student_user
,
fake_request
,
self
.
course
kwargs
=
{
'course_id'
:
self
.
course
.
id
}))
)
progress_summary
=
grades
.
progress_summary
(
self
.
student_user
,
fake_request
,
self
.
course
,
field_data_cache
)
return
progress_summary
return
progress_summary
def
check_grade_percent
(
self
,
percent
):
def
check_grade_percent
(
self
,
percent
):
...
...
lms/djangoapps/courseware/views.py
View file @
4815181a
...
@@ -14,7 +14,6 @@ from django.shortcuts import redirect
...
@@ -14,7 +14,6 @@ from django.shortcuts import redirect
from
mitxmako.shortcuts
import
render_to_response
,
render_to_string
from
mitxmako.shortcuts
import
render_to_response
,
render_to_string
from
django_future.csrf
import
ensure_csrf_cookie
from
django_future.csrf
import
ensure_csrf_cookie
from
django.views.decorators.cache
import
cache_control
from
django.views.decorators.cache
import
cache_control
from
django.db
import
transaction
from
markupsafe
import
escape
from
markupsafe
import
escape
from
courseware
import
grades
from
courseware
import
grades
...
@@ -644,9 +643,8 @@ def mktg_course_about(request, course_id):
...
@@ -644,9 +643,8 @@ def mktg_course_about(request, course_id):
except
(
ValueError
,
Http404
)
as
e
:
except
(
ValueError
,
Http404
)
as
e
:
# if a course does not exist yet, display a coming
# if a course does not exist yet, display a coming
# soon button
# soon button
return
render_to_response
(
return
render_to_response
(
'courseware/mktg_coming_soon.html'
,
'courseware/mktg_coming_soon.html'
,
{
'course_id'
:
course_id
}
{
'course_id'
:
course_id
})
)
registered
=
registered_for_course
(
course
,
request
.
user
)
registered
=
registered_for_course
(
course
,
request
.
user
)
...
@@ -661,36 +659,21 @@ def mktg_course_about(request, course_id):
...
@@ -661,36 +659,21 @@ def mktg_course_about(request, course_id):
settings
.
MITX_FEATURES
.
get
(
'ENABLE_LMS_MIGRATION'
))
settings
.
MITX_FEATURES
.
get
(
'ENABLE_LMS_MIGRATION'
))
course_modes
=
CourseMode
.
modes_for_course
(
course
.
id
)
course_modes
=
CourseMode
.
modes_for_course
(
course
.
id
)
return
render_to_response
(
return
render_to_response
(
'courseware/mktg_course_about.html'
,
'courseware/mktg_course_about.html'
,
{
{
'course'
:
course
,
'course'
:
course
,
'registered'
:
registered
,
'registered'
:
registered
,
'allow_registration'
:
allow_registration
,
'allow_registration'
:
allow_registration
,
'course_target'
:
course_target
,
'course_target'
:
course_target
,
'show_courseware_link'
:
show_courseware_link
,
'show_courseware_link'
:
show_courseware_link
,
'course_modes'
:
course_modes
,
'course_modes'
:
course_modes
,
})
}
)
@login_required
@login_required
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@transaction.commit_manually
def
progress
(
request
,
course_id
,
student_id
=
None
):
def
progress
(
request
,
course_id
,
student_id
=
None
):
"""
""" User progress. We show the grade bar and every problem score.
Wraps "_progress" with the manual_transaction context manager just in case
there are unanticipated errors.
"""
with
grades
.
manual_transaction
():
return
_progress
(
request
,
course_id
,
student_id
)
def
_progress
(
request
,
course_id
,
student_id
):
"""
Unwrapped version of "progress".
User progress. We show the grade bar and every problem score.
Course staff are allowed to see the progress of students in their class.
Course staff are allowed to see the progress of students in their class.
"""
"""
...
@@ -706,33 +689,33 @@ def _progress(request, course_id, student_id):
...
@@ -706,33 +689,33 @@ def _progress(request, course_id, student_id):
raise
Http404
raise
Http404
student
=
User
.
objects
.
get
(
id
=
int
(
student_id
))
student
=
User
.
objects
.
get
(
id
=
int
(
student_id
))
# NOTE: To make sure impersonation by instructor works, use
# NOTE: To make sure impersonation by instructor works, use
# student instead of request.user in the rest of the function.
# student instead of request.user in the rest of the function.
# The pre-fetching of groups is done to make auth checks not require an
# The pre-fetching of groups is done to make auth checks not require an
# additional DB lookup (this kills the Progress page in particular).
# additional DB lookup (this kills the Progress page in particular).
student
=
User
.
objects
.
prefetch_related
(
"groups"
)
.
get
(
id
=
student
.
id
)
student
=
User
.
objects
.
prefetch_related
(
"groups"
)
.
get
(
id
=
student
.
id
)
courseware_summary
=
grades
.
progress_summary
(
student
,
request
,
course
)
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
course_id
,
student
,
course
,
depth
=
None
)
grade_summary
=
grades
.
grade
(
student
,
request
,
course
)
courseware_summary
=
grades
.
progress_summary
(
student
,
request
,
course
,
field_data_cache
)
grade_summary
=
grades
.
grade
(
student
,
request
,
course
,
field_data_cache
)
if
courseware_summary
is
None
:
if
courseware_summary
is
None
:
#This means the student didn't have access to the course (which the instructor requested)
#This means the student didn't have access to the course (which the instructor requested)
raise
Http404
raise
Http404
context
=
{
context
=
{
'course'
:
course
,
'course'
:
course
,
'courseware_summary'
:
courseware_summary
,
'courseware_summary'
:
courseware_summary
,
'grade_summary'
:
grade_summary
,
'grade_summary'
:
grade_summary
,
'staff_access'
:
staff_access
,
'staff_access'
:
staff_access
,
'student'
:
student
,
'student'
:
student
,
}
}
context
.
update
()
with
grades
.
manual_transaction
():
response
=
render_to_response
(
'courseware/progress.html'
,
context
)
return
re
sponse
return
re
nder_to_response
(
'courseware/progress.html'
,
context
)
@login_required
@login_required
...
...
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