Commit 6ca8a702 by Jillian Vogel Committed by Tim Krones

Mask grades on progress page according to "Show Correctness" setting.

parent e8a36957
......@@ -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=SHOW_CORRECTNESS.ALWAYS,
default=ShowCorrectness.ALWAYS,
values=[
{"display_name": _("Always"), "value": SHOW_CORRECTNESS.ALWAYS},
{"display_name": _("Never"), "value": SHOW_CORRECTNESS.NEVER},
{"display_name": _("Past Due"), "value": SHOW_CORRECTNESS.PAST_DUE},
{"display_name": _("Always"), "value": ShowCorrectness.ALWAYS},
{"display_name": _("Never"), "value": ShowCorrectness.NEVER},
{"display_name": _("Past Due"), "value": ShowCorrectness.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):
"""
......
......@@ -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
......
......@@ -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
......@@ -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
)
......@@ -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'
......
......@@ -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)
......
......@@ -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
......
......@@ -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(ProgressPageBaseTests, 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):
"""
......
......@@ -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,
)
)
"""
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'
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment