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
6ca8a702
Commit
6ca8a702
authored
May 02, 2017
by
Jillian Vogel
Committed by
Tim Krones
May 31, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Mask grades on progress page according to "Show Correctness" setting.
parent
e8a36957
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
481 additions
and
47 deletions
+481
-47
common/lib/xmodule/xmodule/capa_base.py
+11
-16
common/lib/xmodule/xmodule/capa_base_constants.py
+0
-9
common/lib/xmodule/xmodule/graders.py
+37
-1
common/lib/xmodule/xmodule/tests/test_graders.py
+89
-4
lms/djangoapps/course_api/blocks/transformers/__init__.py
+1
-0
lms/djangoapps/course_api/blocks/transformers/blocks_api.py
+1
-1
lms/djangoapps/course_api/blocks/views.py
+3
-0
lms/djangoapps/courseware/tests/test_views.py
+293
-6
lms/djangoapps/grades/new/subsection_grade.py
+10
-2
lms/djangoapps/grades/transformer.py
+12
-2
lms/templates/courseware/progress.html
+24
-6
No files found.
common/lib/xmodule/xmodule/capa_base.py
View file @
6ca8a702
...
...
@@ -25,8 +25,9 @@ from capa.responsetypes import StudentInputError, ResponseError, LoncapaProblemE
from
capa.util
import
convert_files_to_filenames
,
get_inner_html_from_xpath
from
xblock.fields
import
Boolean
,
Dict
,
Float
,
Integer
,
Scope
,
String
,
XMLString
from
xblock.scorable
import
ScorableXBlockMixin
,
Score
from
xmodule.capa_base_constants
import
RANDOMIZATION
,
SHOWANSWER
,
SHOW_CORRECTNESS
from
xmodule.capa_base_constants
import
RANDOMIZATION
,
SHOWANSWER
from
xmodule.exceptions
import
NotFoundError
from
xmodule.graders
import
ShowCorrectness
from
.fields
import
Date
,
Timedelta
from
.progress
import
Progress
...
...
@@ -120,11 +121,11 @@ class CapaFields(object):
help
=
_
(
"Defines when to show whether a learner's answer to the problem is correct. "
"Configured on the subsection."
),
scope
=
Scope
.
settings
,
default
=
S
HOW_CORRECTNESS
.
ALWAYS
,
default
=
S
howCorrectness
.
ALWAYS
,
values
=
[
{
"display_name"
:
_
(
"Always"
),
"value"
:
S
HOW_CORRECTNESS
.
ALWAYS
},
{
"display_name"
:
_
(
"Never"
),
"value"
:
S
HOW_CORRECTNESS
.
NEVER
},
{
"display_name"
:
_
(
"Past Due"
),
"value"
:
S
HOW_CORRECTNESS
.
PAST_DUE
},
{
"display_name"
:
_
(
"Always"
),
"value"
:
S
howCorrectness
.
ALWAYS
},
{
"display_name"
:
_
(
"Never"
),
"value"
:
S
howCorrectness
.
NEVER
},
{
"display_name"
:
_
(
"Past Due"
),
"value"
:
S
howCorrectness
.
PAST_DUE
},
],
)
showanswer
=
String
(
...
...
@@ -921,17 +922,11 @@ class CapaMixin(ScorableXBlockMixin, CapaFields):
Limits access to the correct/incorrect flags, messages, and problem score.
"""
if
self
.
show_correctness
==
SHOW_CORRECTNESS
.
NEVER
:
return
False
elif
self
.
runtime
.
user_is_staff
:
# This is after the 'never' check because admins can see correctness
# unless the problem explicitly prevents it
return
True
elif
self
.
show_correctness
==
SHOW_CORRECTNESS
.
PAST_DUE
:
return
self
.
is_past_due
()
# else: self.show_correctness == SHOW_CORRECTNESS.ALWAYS
return
True
return
ShowCorrectness
.
correctness_available
(
show_correctness
=
self
.
show_correctness
,
due_date
=
self
.
close_date
,
has_staff_access
=
self
.
runtime
.
user_is_staff
,
)
def
update_score
(
self
,
data
):
"""
...
...
common/lib/xmodule/xmodule/capa_base_constants.py
View file @
6ca8a702
...
...
@@ -4,15 +4,6 @@ Constants for capa_base problems
"""
class
SHOW_CORRECTNESS
(
object
):
# pylint: disable=invalid-name
"""
Constants for when to show correctness
"""
ALWAYS
=
"always"
PAST_DUE
=
"past_due"
NEVER
=
"never"
class
SHOWANSWER
(
object
):
"""
Constants for when to show answer
...
...
common/lib/xmodule/xmodule/graders.py
View file @
6ca8a702
...
...
@@ -10,9 +10,10 @@ import logging
import
random
import
sys
from
collections
import
OrderedDict
from
datetime
import
datetime
# Used by pycontracts. pylint: disable=unused-import
from
datetime
import
datetime
from
contracts
import
contract
from
pytz
import
UTC
log
=
logging
.
getLogger
(
"edx.courseware"
)
...
...
@@ -462,3 +463,38 @@ def _min_or_none(itr):
return
min
(
itr
)
except
ValueError
:
return
None
class
ShowCorrectness
(
object
):
"""
Helper class for determining whether correctness is currently hidden for a block.
When correctness is hidden, this limits the user's access to the correct/incorrect flags, messages, problem scores,
and aggregate subsection and course grades.
"""
"""
Constants used to indicate when to show correctness
"""
ALWAYS
=
"always"
PAST_DUE
=
"past_due"
NEVER
=
"never"
@classmethod
def
correctness_available
(
cls
,
show_correctness
=
''
,
due_date
=
None
,
has_staff_access
=
False
):
"""
Returns whether correctness is available now, for the given attributes.
"""
if
show_correctness
==
cls
.
NEVER
:
return
False
elif
has_staff_access
:
# This is after the 'never' check because course staff can see correctness
# unless the sequence/problem explicitly prevents it
return
True
elif
show_correctness
==
cls
.
PAST_DUE
:
# Is it now past the due date?
return
(
due_date
is
None
or
due_date
<
datetime
.
now
(
UTC
))
# else: show_correctness == cls.ALWAYS
return
True
common/lib/xmodule/xmodule/tests/test_graders.py
View file @
6ca8a702
...
...
@@ -2,13 +2,15 @@
Grading tests
"""
from
datetime
import
datetime
import
ddt
import
unittest
from
datetime
import
datetime
,
timedelta
import
ddt
from
pytz
import
UTC
from
xmodule
import
graders
from
xmodule.graders
import
ProblemScore
,
AggregatedScore
,
aggregate_scores
from
xmodule.graders
import
(
AggregatedScore
,
ProblemScore
,
ShowCorrectness
,
aggregate_scores
)
class
GradesheetTest
(
unittest
.
TestCase
):
...
...
@@ -315,3 +317,86 @@ class GraderTest(unittest.TestCase):
with
self
.
assertRaises
(
ValueError
)
as
error
:
graders
.
grader_from_conf
([
invalid_conf
])
self
.
assertIn
(
expected_error_message
,
error
.
exception
.
message
)
@ddt.ddt
class
ShowCorrectnessTest
(
unittest
.
TestCase
):
"""
Tests the correctness_available method
"""
def
setUp
(
self
):
super
(
ShowCorrectnessTest
,
self
)
.
setUp
()
now
=
datetime
.
now
(
UTC
)
day_delta
=
timedelta
(
days
=
1
)
self
.
yesterday
=
now
-
day_delta
self
.
today
=
now
self
.
tomorrow
=
now
+
day_delta
def
test_show_correctness_default
(
self
):
"""
Test that correctness is visible by default.
"""
self
.
assertTrue
(
ShowCorrectness
.
correctness_available
())
@ddt.data
(
(
ShowCorrectness
.
ALWAYS
,
True
),
(
ShowCorrectness
.
ALWAYS
,
False
),
# Any non-constant values behave like "always"
(
''
,
True
),
(
''
,
False
),
(
'other-value'
,
True
),
(
'other-value'
,
False
),
)
@ddt.unpack
def
test_show_correctness_always
(
self
,
show_correctness
,
has_staff_access
):
"""
Test that correctness is visible when show_correctness is turned on.
"""
self
.
assertTrue
(
ShowCorrectness
.
correctness_available
(
show_correctness
=
show_correctness
,
has_staff_access
=
has_staff_access
))
@ddt.data
(
True
,
False
)
def
test_show_correctness_never
(
self
,
has_staff_access
):
"""
Test that show_correctness="never" hides correctness from learners and course staff.
"""
self
.
assertFalse
(
ShowCorrectness
.
correctness_available
(
show_correctness
=
ShowCorrectness
.
NEVER
,
has_staff_access
=
has_staff_access
))
@ddt.data
(
# Correctness not visible to learners if due date in the future
(
'tomorrow'
,
False
,
False
),
# Correctness is visible to learners if due date in the past
(
'yesterday'
,
False
,
True
),
# Correctness is visible to learners if due date in the past (just)
(
'today'
,
False
,
True
),
# Correctness is visible to learners if there is no due date
(
None
,
False
,
True
),
# Correctness is visible to staff if due date in the future
(
'tomorrow'
,
True
,
True
),
# Correctness is visible to staff if due date in the past
(
'yesterday'
,
True
,
True
),
# Correctness is visible to staff if there is no due date
(
None
,
True
,
True
),
)
@ddt.unpack
def
test_show_correctness_past_due
(
self
,
due_date_str
,
has_staff_access
,
expected_result
):
"""
Test show_correctness="past_due" to ensure:
* correctness is always visible to course staff
* correctness is always visible to everyone if there is no due date
* correctness is visible to learners after the due date, when there is a due date.
"""
if
due_date_str
is
None
:
due_date
=
None
else
:
due_date
=
getattr
(
self
,
due_date_str
)
self
.
assertEquals
(
ShowCorrectness
.
correctness_available
(
ShowCorrectness
.
PAST_DUE
,
due_date
,
has_staff_access
),
expected_result
)
lms/djangoapps/course_api/blocks/transformers/__init__.py
View file @
6ca8a702
...
...
@@ -40,6 +40,7 @@ SUPPORTED_FIELDS = [
SupportedFieldType
(
'graded'
),
SupportedFieldType
(
'format'
),
SupportedFieldType
(
'due'
),
SupportedFieldType
(
'show_correctness'
),
# 'student_view_data'
SupportedFieldType
(
StudentViewTransformer
.
STUDENT_VIEW_DATA
,
StudentViewTransformer
),
# 'student_view_multi_device'
...
...
lms/djangoapps/course_api/blocks/transformers/blocks_api.py
View file @
6ca8a702
...
...
@@ -44,7 +44,7 @@ class BlocksAPITransformer(BlockStructureTransformer):
transform method.
"""
# collect basic xblock fields
block_structure
.
request_xblock_fields
(
'graded'
,
'format'
,
'display_name'
,
'category'
,
'due'
)
block_structure
.
request_xblock_fields
(
'graded'
,
'format'
,
'display_name'
,
'category'
,
'due'
,
'show_correctness'
)
# collect data from containing transformers
StudentViewTransformer
.
collect
(
block_structure
)
...
...
lms/djangoapps/course_api/blocks/views.py
View file @
6ca8a702
...
...
@@ -172,6 +172,9 @@ class BlocksView(DeveloperErrorViewMixin, ListAPIView):
* due: The due date of the block. Returned only if "due" is included
in the "requested_fields" parameter.
* show_correctness: Whether to show scores/correctness to learners for the current sequence or problem.
Returned only if "show_correctness" is included in the "requested_fields" parameter.
"""
def
list
(
self
,
request
,
usage_key_string
):
# pylint: disable=arguments-differ
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
6ca8a702
...
...
@@ -29,6 +29,9 @@ from xblock.core import XBlock
from
xblock.fields
import
String
,
Scope
from
xblock.fragment
import
Fragment
from
capa.tests.response_xml_factory
import
MultipleChoiceResponseXMLFactory
from
courseware.model_data
import
FieldDataCache
from
courseware.module_render
import
get_module
import
courseware.views.views
as
views
import
shoppingcart
from
certificates
import
api
as
certs_api
...
...
@@ -56,6 +59,7 @@ from openedx.core.djangoapps.crawlers.models import CrawlersConfig
from
openedx.core.djangoapps.credit.api
import
set_credit_requirements
from
openedx.core.djangoapps.credit.models
import
CreditCourse
,
CreditProvider
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangolib.testing.utils
import
get_mock_request
from
openedx.core.lib.gating
import
api
as
gating_api
from
openedx.features.enterprise_support.tests.mixins.enterprise
import
EnterpriseTestConsentRequired
from
student.models
import
CourseEnrollment
...
...
@@ -63,6 +67,7 @@ from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentF
from
util.tests.test_date_utils
import
fake_ugettext
,
fake_pgettext
from
util.url
import
reload_django_url_config
from
util.views
import
ensure_valid_course_key
from
xmodule.graders
import
ShowCorrectness
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
TEST_DATA_MIXED_MODULESTORE
...
...
@@ -1165,29 +1170,32 @@ class StartDateTests(ModuleStoreTestCase):
# pylint: disable=protected-access, no-member
@attr
(
shard
=
1
)
@override_settings
(
ENABLE_ENTERPRISE_INTEGRATION
=
False
)
@ddt.ddt
class
ProgressPageTests
(
ModuleStoreTestCase
):
class
ProgressPageBaseTests
(
ModuleStoreTestCase
):
"""
Tests that verify that the progress page works correctly
.
Base class for progress page tests
.
"""
ENABLED_CACHES
=
[
'default'
,
'mongo_modulestore_inheritance'
,
'loc_cache'
]
ENABLED_SIGNALS
=
[
'course_published'
]
def
setUp
(
self
):
super
(
ProgressPageTests
,
self
)
.
setUp
()
super
(
ProgressPage
Base
Tests
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
self
.
assertTrue
(
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
'test'
))
self
.
setup_course
()
def
setup
_course
(
self
,
**
options
):
def
create
_course
(
self
,
**
options
):
"""Create the test course."""
self
.
course
=
CourseFactory
.
create
(
start
=
datetime
(
2013
,
9
,
16
,
7
,
17
,
28
),
grade_cutoffs
=
{
u'çü†øƒƒ'
:
0.75
,
'Pass'
:
0.5
},
**
options
)
def
setup_course
(
self
,
**
course_options
):
"""Create the test course and content, and enroll the user."""
self
.
create_course
(
**
course_options
)
with
self
.
store
.
bulk_operations
(
self
.
course
.
id
):
self
.
chapter
=
ItemFactory
.
create
(
category
=
'chapter'
,
parent_location
=
self
.
course
.
location
)
self
.
section
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
self
.
chapter
.
location
)
...
...
@@ -1197,7 +1205,7 @@ class ProgressPageTests(ModuleStoreTestCase):
def
_get_progress_page
(
self
,
expected_status_code
=
200
):
"""
Gets the progress page for the
user in the course
.
Gets the progress page for the
currently logged-in user
.
"""
resp
=
self
.
client
.
get
(
reverse
(
'progress'
,
args
=
[
unicode
(
self
.
course
.
id
)])
...
...
@@ -1215,6 +1223,14 @@ class ProgressPageTests(ModuleStoreTestCase):
self
.
assertEqual
(
resp
.
status_code
,
expected_status_code
)
return
resp
# pylint: disable=protected-access, no-member
@ddt.ddt
class
ProgressPageTests
(
ProgressPageBaseTests
):
"""
Tests that verify that the progress page works correctly.
"""
@ddt.data
(
'"><script>alert(1)</script>'
,
'<script>alert(1)</script>'
,
'</script><script>alert(1)</script>'
)
def
test_progress_page_xss_prevent
(
self
,
malicious_code
):
"""
...
...
@@ -1649,6 +1665,277 @@ class ProgressPageTests(ModuleStoreTestCase):
}
# pylint: disable=protected-access, no-member
@ddt.ddt
class
ProgressPageShowCorrectnessTests
(
ProgressPageBaseTests
):
"""
Tests that verify that the progress page works correctly when displaying subsections where correctness is hidden.
"""
# Constants used in the test data
NOW
=
datetime
.
now
(
UTC
)
DAY_DELTA
=
timedelta
(
days
=
1
)
YESTERDAY
=
NOW
-
DAY_DELTA
TODAY
=
NOW
TOMORROW
=
NOW
+
DAY_DELTA
GRADER_TYPE
=
'Homework'
def
setUp
(
self
):
super
(
ProgressPageShowCorrectnessTests
,
self
)
.
setUp
()
self
.
staff_user
=
UserFactory
.
create
(
is_staff
=
True
)
def
setup_course
(
self
,
show_correctness
=
''
,
due_date
=
None
,
graded
=
False
,
**
course_options
):
"""
Set up course with a subsection with the given show_correctness, due_date, and graded settings.
"""
# Use a simple grading policy
course_options
[
'grading_policy'
]
=
{
"GRADER"
:
[{
"type"
:
self
.
GRADER_TYPE
,
"min_count"
:
2
,
"drop_count"
:
0
,
"short_label"
:
"HW"
,
"weight"
:
1.0
}],
"GRADE_CUTOFFS"
:
{
'A'
:
.
9
,
'B'
:
.
33
}
}
self
.
create_course
(
**
course_options
)
metadata
=
dict
(
show_correctness
=
show_correctness
,
)
if
due_date
is
not
None
:
metadata
[
'due'
]
=
due_date
if
graded
:
metadata
[
'graded'
]
=
True
metadata
[
'format'
]
=
self
.
GRADER_TYPE
with
self
.
store
.
bulk_operations
(
self
.
course
.
id
):
self
.
chapter
=
ItemFactory
.
create
(
category
=
'chapter'
,
parent_location
=
self
.
course
.
location
,
display_name
=
"Section 1"
)
self
.
section
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
self
.
chapter
.
location
,
display_name
=
"Subsection 1"
,
metadata
=
metadata
)
self
.
vertical
=
ItemFactory
.
create
(
category
=
'vertical'
,
parent_location
=
self
.
section
.
location
)
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
,
mode
=
CourseMode
.
HONOR
)
def
add_problem
(
self
):
"""
Add a problem to the subsection
"""
problem_xml
=
MultipleChoiceResponseXMLFactory
()
.
build_xml
(
question_text
=
'The correct answer is Choice 1'
,
choices
=
[
True
,
False
],
choice_names
=
[
'choice_0'
,
'choice_1'
]
)
self
.
problem
=
ItemFactory
.
create
(
category
=
'problem'
,
parent_location
=
self
.
vertical
.
location
,
data
=
problem_xml
,
display_name
=
'Problem 1'
)
# Re-fetch the course from the database
self
.
course
=
self
.
store
.
get_course
(
self
.
course
.
id
)
def
answer_problem
(
self
,
value
=
1
,
max_value
=
1
):
"""
Submit the given score to the problem on behalf of the user
"""
# Get the module for the problem, as viewed by the user
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
self
.
course
.
id
,
self
.
user
,
self
.
course
,
depth
=
2
)
# pylint: disable=protected-access
module
=
get_module
(
self
.
user
,
get_mock_request
(
self
.
user
),
self
.
problem
.
scope_ids
.
usage_id
,
field_data_cache
,
)
.
_xmodule
# Submit the given score/max_score to the problem xmodule
grade_dict
=
{
'value'
:
value
,
'max_value'
:
max_value
,
'user_id'
:
self
.
user
.
id
}
module
.
system
.
publish
(
self
.
problem
,
'grade'
,
grade_dict
)
def
assert_progress_page_show_grades
(
self
,
response
,
show_correctness
,
due_date
,
graded
,
show_grades
,
score
,
max_score
,
avg
):
"""
Ensures that grades and scores are shown or not shown on the progress page as required.
"""
expected_score
=
"<dd>{score}/{max_score}</dd>"
.
format
(
score
=
score
,
max_score
=
max_score
)
percent
=
score
/
float
(
max_score
)
if
show_grades
:
# If grades are shown, we should be able to see the current problem scores.
self
.
assertIn
(
expected_score
,
response
.
content
)
if
graded
:
expected_summary_text
=
"Problem Scores:"
else
:
expected_summary_text
=
"Practice Scores:"
else
:
# If grades are hidden, we should not be able to see the current problem scores.
self
.
assertNotIn
(
expected_score
,
response
.
content
)
if
graded
:
expected_summary_text
=
"Problem scores are hidden"
else
:
expected_summary_text
=
"Practice scores are hidden"
if
show_correctness
==
ShowCorrectness
.
PAST_DUE
and
due_date
:
expected_summary_text
+=
' until the due date.'
else
:
expected_summary_text
+=
'.'
# Ensure that expected text is present
self
.
assertIn
(
expected_summary_text
,
response
.
content
)
@ddt.data
(
(
''
,
None
,
False
),
(
''
,
None
,
True
),
(
ShowCorrectness
.
ALWAYS
,
None
,
False
),
(
ShowCorrectness
.
ALWAYS
,
None
,
True
),
(
ShowCorrectness
.
ALWAYS
,
YESTERDAY
,
False
),
(
ShowCorrectness
.
ALWAYS
,
YESTERDAY
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TODAY
,
False
),
(
ShowCorrectness
.
ALWAYS
,
TODAY
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TOMORROW
,
False
),
(
ShowCorrectness
.
ALWAYS
,
TOMORROW
,
True
),
(
ShowCorrectness
.
NEVER
,
None
,
False
),
(
ShowCorrectness
.
NEVER
,
None
,
True
),
(
ShowCorrectness
.
NEVER
,
YESTERDAY
,
False
),
(
ShowCorrectness
.
NEVER
,
YESTERDAY
,
True
),
(
ShowCorrectness
.
NEVER
,
TODAY
,
False
),
(
ShowCorrectness
.
NEVER
,
TODAY
,
True
),
(
ShowCorrectness
.
NEVER
,
TOMORROW
,
False
),
(
ShowCorrectness
.
NEVER
,
TOMORROW
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
None
,
False
),
(
ShowCorrectness
.
PAST_DUE
,
None
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
YESTERDAY
,
False
),
(
ShowCorrectness
.
PAST_DUE
,
YESTERDAY
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TODAY
,
False
),
(
ShowCorrectness
.
PAST_DUE
,
TODAY
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TOMORROW
,
False
),
(
ShowCorrectness
.
PAST_DUE
,
TOMORROW
,
True
),
)
@ddt.unpack
def
test_progress_page_no_problem_scores
(
self
,
show_correctness
,
due_date
,
graded
):
"""
Test that "no problem scores are present" for a course with no problems,
regardless of the various show correctness settings.
"""
self
.
setup_course
(
show_correctness
=
show_correctness
,
due_date
=
due_date
,
graded
=
graded
)
resp
=
self
.
_get_progress_page
()
# Test that no problem scores are present
self
.
assertIn
(
'No problem scores in this section'
,
resp
.
content
)
@ddt.data
(
(
''
,
None
,
False
,
True
),
(
''
,
None
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
None
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
None
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
YESTERDAY
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
YESTERDAY
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TODAY
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TODAY
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TOMORROW
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TOMORROW
,
True
,
True
),
(
ShowCorrectness
.
NEVER
,
None
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
None
,
True
,
False
),
(
ShowCorrectness
.
NEVER
,
YESTERDAY
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
YESTERDAY
,
True
,
False
),
(
ShowCorrectness
.
NEVER
,
TODAY
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
TODAY
,
True
,
False
),
(
ShowCorrectness
.
NEVER
,
TOMORROW
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
TOMORROW
,
True
,
False
),
(
ShowCorrectness
.
PAST_DUE
,
None
,
False
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
None
,
True
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
YESTERDAY
,
False
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
YESTERDAY
,
True
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TODAY
,
False
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TODAY
,
True
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TOMORROW
,
False
,
False
),
(
ShowCorrectness
.
PAST_DUE
,
TOMORROW
,
True
,
False
),
)
@ddt.unpack
def
test_progress_page_hide_scores_from_learner
(
self
,
show_correctness
,
due_date
,
graded
,
show_grades
):
"""
Test that problem scores are hidden on progress page when correctness is not available to the learner, and that
they are visible when it is.
"""
self
.
setup_course
(
show_correctness
=
show_correctness
,
due_date
=
due_date
,
graded
=
graded
)
self
.
add_problem
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
'test'
)
resp
=
self
.
_get_progress_page
()
# Ensure that expected text is present
self
.
assert_progress_page_show_grades
(
resp
,
show_correctness
,
due_date
,
graded
,
show_grades
,
0
,
1
,
0
)
# Submit answers to the problem, and re-fetch the progress page
self
.
answer_problem
()
resp
=
self
.
_get_progress_page
()
# Test that the expected text is still present.
self
.
assert_progress_page_show_grades
(
resp
,
show_correctness
,
due_date
,
graded
,
show_grades
,
1
,
1
,
.
5
)
@ddt.data
(
(
''
,
None
,
False
,
True
),
(
''
,
None
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
None
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
None
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
YESTERDAY
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
YESTERDAY
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TODAY
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TODAY
,
True
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TOMORROW
,
False
,
True
),
(
ShowCorrectness
.
ALWAYS
,
TOMORROW
,
True
,
True
),
(
ShowCorrectness
.
NEVER
,
None
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
None
,
True
,
False
),
(
ShowCorrectness
.
NEVER
,
YESTERDAY
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
YESTERDAY
,
True
,
False
),
(
ShowCorrectness
.
NEVER
,
TODAY
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
TODAY
,
True
,
False
),
(
ShowCorrectness
.
NEVER
,
TOMORROW
,
False
,
False
),
(
ShowCorrectness
.
NEVER
,
TOMORROW
,
True
,
False
),
(
ShowCorrectness
.
PAST_DUE
,
None
,
False
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
None
,
True
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
YESTERDAY
,
False
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
YESTERDAY
,
True
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TODAY
,
False
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TODAY
,
True
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TOMORROW
,
False
,
True
),
(
ShowCorrectness
.
PAST_DUE
,
TOMORROW
,
True
,
True
),
)
@ddt.unpack
def
test_progress_page_hide_scores_from_staff
(
self
,
show_correctness
,
due_date
,
graded
,
show_grades
):
"""
Test that problem scores are hidden from staff viewing a learner's progress page only if show_correctness=never.
"""
self
.
setup_course
(
show_correctness
=
show_correctness
,
due_date
=
due_date
,
graded
=
graded
)
self
.
add_problem
()
# Login as a course staff user to view the student progress page.
self
.
client
.
login
(
username
=
self
.
staff_user
.
username
,
password
=
'test'
)
resp
=
self
.
_get_student_progress_page
()
# Ensure that expected text is present
self
.
assert_progress_page_show_grades
(
resp
,
show_correctness
,
due_date
,
graded
,
show_grades
,
0
,
1
,
0
)
# Submit answers to the problem, and re-fetch the progress page
self
.
answer_problem
()
resp
=
self
.
_get_student_progress_page
()
# Test that the expected text is still present.
self
.
assert_progress_page_show_grades
(
resp
,
show_correctness
,
due_date
,
graded
,
show_grades
,
1
,
1
,
.
5
)
@attr
(
shard
=
1
)
class
VerifyCourseKeyDecoratorTests
(
TestCase
):
"""
...
...
lms/djangoapps/grades/new/subsection_grade.py
View file @
6ca8a702
...
...
@@ -7,7 +7,7 @@ from logging import getLogger
from
lms.djangoapps.grades.scores
import
get_score
,
possibly_scored
from
lms.djangoapps.grades.models
import
BlockRecord
,
PersistentSubsectionGrade
from
xmodule
import
block_metadata_utils
,
graders
from
xmodule.graders
import
AggregatedScore
from
xmodule.graders
import
AggregatedScore
,
ShowCorrectness
from
..config.waffle
import
waffle
,
WRITE_ONLY_IF_ENGAGED
...
...
@@ -27,6 +27,7 @@ class SubsectionGradeBase(object):
self
.
format
=
getattr
(
subsection
,
'format'
,
''
)
self
.
due
=
getattr
(
subsection
,
'due'
,
None
)
self
.
graded
=
getattr
(
subsection
,
'graded'
,
False
)
self
.
show_correctness
=
getattr
(
subsection
,
'show_correctness'
,
''
)
self
.
course_version
=
getattr
(
subsection
,
'course_version'
,
None
)
self
.
subtree_edited_timestamp
=
getattr
(
subsection
,
'subtree_edited_on'
,
None
)
...
...
@@ -47,6 +48,12 @@ class SubsectionGradeBase(object):
)
return
self
.
all_total
.
attempted
def
show_grades
(
self
,
has_staff_access
):
"""
Returns whether subsection scores are currently available to users with or without staff access.
"""
return
ShowCorrectness
.
correctness_available
(
self
.
show_correctness
,
self
.
due
,
has_staff_access
)
class
ZeroSubsectionGrade
(
SubsectionGradeBase
):
"""
...
...
@@ -224,7 +231,7 @@ class SubsectionGrade(SubsectionGradeBase):
log_func
(
u"Grades: SG.{}, subsection: {}, course: {}, "
u"version: {}, edit: {}, user: {},"
u"total: {}/{}, graded: {}/{}"
.
format
(
u"total: {}/{}, graded: {}/{}
, show_correctness: {}
"
.
format
(
log_statement
,
self
.
location
,
self
.
location
.
course_key
,
...
...
@@ -235,5 +242,6 @@ class SubsectionGrade(SubsectionGradeBase):
self
.
all_total
.
possible
,
self
.
graded_total
.
earned
,
self
.
graded_total
.
possible
,
self
.
show_correctness
,
)
)
lms/djangoapps/grades/transformer.py
View file @
6ca8a702
"""
Grades Transformer
"""
import
json
from
base64
import
b64encode
from
functools
import
reduce
as
functools_reduce
from
hashlib
import
sha1
from
logging
import
getLogger
import
json
from
lms.djangoapps.course_blocks.transformers.utils
import
collect_unioned_set_field
,
get_field_on_block
from
openedx.core.djangoapps.content.block_structure.transformer
import
BlockStructureTransformer
...
...
@@ -29,6 +29,7 @@ class GradesTransformer(BlockStructureTransformer):
graded: (boolean)
has_score: (boolean)
weight: (numeric)
show_correctness: (string) when to show grades (one of 'always', 'past_due', 'never')
Additionally, the following value is calculated and stored as a
transformer_block_field for each block:
...
...
@@ -37,7 +38,16 @@ class GradesTransformer(BlockStructureTransformer):
"""
WRITE_VERSION
=
4
READ_VERSION
=
4
FIELDS_TO_COLLECT
=
[
u'due'
,
u'format'
,
u'graded'
,
u'has_score'
,
u'weight'
,
u'course_version'
,
u'subtree_edited_on'
]
FIELDS_TO_COLLECT
=
[
u'due'
,
u'format'
,
u'graded'
,
u'has_score'
,
u'weight'
,
u'course_version'
,
u'subtree_edited_on'
,
u'show_correctness'
,
]
EXPLICIT_GRADED_FIELD_NAME
=
'explicit_graded'
...
...
lms/templates/courseware/progress.html
View file @
6ca8a702
...
...
@@ -180,12 +180,30 @@ from django.utils.http import urlquote_plus
%endif
</p>
%if len(section.problem_scores.values()) > 0:
<dl
class=
"scores"
>
<dt
class=
"hd hd-6"
>
${ _("Problem Scores: ") if section.graded else _("Practice Scores: ")}
</dt>
%for score in section.problem_scores.values():
<dd>
${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}
</dd>
%endfor
</dl>
%if section.show_grades(staff_access):
<dl
class=
"scores"
>
<dt
class=
"hd hd-6"
>
${ _("Problem Scores: ") if section.graded else _("Practice Scores: ")}
</dt>
%for score in section.problem_scores.values():
<dd>
${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}
</dd>
%endfor
</dl>
%else:
<p
class=
"hide-scores"
>
%if section.show_correctness == 'past_due':
%if section.graded:
${_("Problem scores are hidden until the due date.")}
%else:
${_("Practice scores are hidden until the due date.")}
%endif
%else:
%if section.graded:
${_("Problem scores are hidden.")}
%else:
${_("Practice scores are hidden.")}
%endif
%endif
</p>
%endif
%else:
<p
class=
"no-scores"
>
${_("No problem scores in this section")}
</p>
%endif
...
...
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