Merge pull request #24556 from cpennington/ora2-dates-reprise-2

Ora2 dates reprise 2
This commit is contained in:
Calen Pennington
2020-07-22 09:45:00 -04:00
committed by GitHub
11 changed files with 261 additions and 28 deletions

View File

@@ -22,6 +22,7 @@ class DateSummarySerializer(serializers.Serializer):
link = serializers.SerializerMethodField()
link_text = serializers.CharField()
title = serializers.CharField()
extra_info = serializers.CharField()
def get_learner_has_access(self, block):
learner_is_full_access = self.context.get('learner_is_full_access', False)

View File

@@ -10,6 +10,7 @@ from datetime import datetime
import pytz
import six
from crum import get_current_request
from dateutil.parser import parse as parse_date
from django.conf import settings
from django.http import Http404, QueryDict
from django.urls import reverse
@@ -71,7 +72,7 @@ log = logging.getLogger(__name__)
# Used by get_course_assignments below. You shouldn't need to use this type directly.
_Assignment = namedtuple(
'Assignment', ['block_key', 'title', 'url', 'date', 'contains_gated_content', 'complete', 'past_due',
'assignment_type']
'assignment_type', 'extra_info']
)
@@ -508,6 +509,7 @@ def get_course_assignment_date_blocks(course, user, request, num_return=None,
date_block.past_due = assignment.past_due
date_block.link = request.build_absolute_uri(assignment.url) if assignment.url else ''
date_block.set_title(assignment.title, link=assignment.url)
date_block._extra_info = assignment.extra_info # pylint: disable=protected-access
date_blocks.append(date_block)
date_blocks = sorted((b for b in date_blocks if b.is_enabled or include_past_dates), key=date_block_key_fn)
if num_return:
@@ -533,26 +535,92 @@ def get_course_assignments(course_key, user, include_access=False):
for subsection_key in block_data.get_children(section_key):
due = block_data.get_xblock_field(subsection_key, 'due')
graded = block_data.get_xblock_field(subsection_key, 'graded', False)
if not due or not graded:
continue
if due and graded:
contains_gated_content = include_access and block_data.get_xblock_field(
subsection_key, 'contains_gated_content', False)
title = block_data.get_xblock_field(subsection_key, 'display_name', _('Assignment'))
contains_gated_content = include_access and block_data.get_xblock_field(
subsection_key, 'contains_gated_content', False)
title = block_data.get_xblock_field(subsection_key, 'display_name', _('Assignment'))
assignment_type = block_data.get_xblock_field(subsection_key, 'format', None)
assignment_type = block_data.get_xblock_field(subsection_key, 'format', None)
url = None
start = block_data.get_xblock_field(subsection_key, 'start')
assignment_released = not start or start < now
if assignment_released:
url = reverse('jump_to', args=[course_key, subsection_key])
url = None
start = block_data.get_xblock_field(subsection_key, 'start')
assignment_released = not start or start < now
if assignment_released:
url = reverse('jump_to', args=[course_key, subsection_key])
complete = is_block_structure_complete_for_assignments(block_data, subsection_key)
past_due = not complete and due < now
assignments.append(_Assignment(
subsection_key, title, url, due, contains_gated_content, complete, past_due, assignment_type, None
))
complete = is_block_structure_complete_for_assignments(block_data, subsection_key)
past_due = not complete and due < now
assignments.append(_Assignment(
subsection_key, title, url, due, contains_gated_content, complete, past_due, assignment_type
))
# Load all dates for ORA blocks as separate assignments
descendents = block_data.get_children(subsection_key)
while descendents:
descendent = descendents.pop()
descendents.extend(block_data.get_children(descendent))
if block_data.get_xblock_field(descendent, 'category', None) == 'openassessment':
graded = block_data.get_xblock_field(descendent, 'graded', False)
has_score = block_data.get_xblock_field(descendent, 'has_score', False)
weight = block_data.get_xblock_field(descendent, 'weight', 1)
if not (graded and has_score and (weight is None or weight > 0)):
continue
all_assessments = [{
'name': 'submission',
'due': block_data.get_xblock_field(descendent, 'submission_due'),
'start': block_data.get_xblock_field(descendent, 'submission_start'),
'required': True
}]
valid_assessments = block_data.get_xblock_field(descendent, 'valid_assessments')
print(valid_assessments)
if valid_assessments:
all_assessments.extend(valid_assessments)
assignment_type = block_data.get_xblock_field(descendent, 'format', None)
complete = is_block_structure_complete_for_assignments(block_data, descendent)
block_title = block_data.get_xblock_field(descendent, 'title', _('Open Response Assessment'))
for assessment in all_assessments:
due = parse_date(assessment.get('due')).replace(tzinfo=pytz.UTC) if assessment.get('due') else None
if due is None:
continue
assessment_name = assessment.get('name')
if assessment_name is None:
continue
if assessment_name == 'self-assessment':
assessment_type = _("Self Assessment")
elif assessment_name == 'peer-assessment':
assessment_type = _("Peer Assessment")
elif assessment_name == 'staff-assessment':
assessment_type = _("Staff Assessment")
elif assessment_name == 'submission':
assessment_type = _("Submission")
else:
assessment_type = assessment_name
title = "{} ({})".format(block_title, assessment_type)
url = ''
start = parse_date(assessment.get('start')).replace(tzinfo=pytz.UTC) if assessment.get('start') else None
assignment_released = not start or start < now
if assignment_released:
url = reverse('jump_to', args=[course_key, descendent])
past_due = not complete and due and due < now
assignments.append(_Assignment(
descendent,
title,
url,
due,
False,
complete,
past_due,
assignment_type,
_("Open Response Assessment due dates are set by your instructor and can't be shifted.")
))
return assignments

View File

@@ -77,6 +77,11 @@ class DateSummary(object):
"""The detail text displayed by this summary."""
return ''
@property
def extra_info(self):
"""Extra detail to display as a tooltip."""
return None
def register_alerts(self, request, course):
"""
Registers any relevant course alerts given the current request.
@@ -388,6 +393,7 @@ class CourseAssignmentDate(DateSummary):
self.contains_gated_content = False
self.complete = None
self.past_due = None
self._extra_info = None
@property
def date(self):
@@ -405,6 +411,10 @@ class CourseAssignmentDate(DateSummary):
def link(self):
return self.assignment_link
@property
def extra_info(self):
return self._extra_info
@link.setter
def link(self, link):
self.assignment_link = link

View File

@@ -298,6 +298,72 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
for html_tag in assignment_title_html:
self.assertIn(html_tag, assignment_title)
@RELATIVE_DATES_FLAG.override(active=True)
@ddt.data(
([], 3),
([{
'due': None,
'start': None,
'name': 'student-training',
'examples': [
{
'answer': ['Replace this text with your own sample response...'],
'options_selected': [
{'option': 'Fair', 'criterion': 'Ideas'},
{'option': 'Good', 'criterion': 'Content'}
]
}, {
'answer': ['Replace this text with another sample response...'],
'options_selected': [
{'option': 'Poor', 'criterion': 'Ideas'},
{'option': 'Good', 'criterion': 'Content'}
]
}
]
}, {
'due': '2029-01-01T00:00:00+00:00',
'start': '2001-01-01T00:00:00+00:00',
'must_be_graded_by': 3,
'name': 'peer-assessment',
'must_grade': 5
}, {
'due': '2029-01-01T00:00:00+00:00',
'start': '2001-01-01T00:00:00+00:00',
'name': 'self-assessment'
}], 5)
)
@ddt.unpack
def test_dates_with_openassessments(self, rubric_assessments, date_block_count):
course = create_self_paced_course_run(days_till_start=-1, org_id='TestOrg')
user = create_user()
request = self.make_request(user)
CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED)
now = datetime.now(utc)
chapter = ItemFactory.create(
parent=course,
category="chapter",
graded=True,
)
section = ItemFactory.create(
parent=chapter,
category="sequential",
)
vertical = ItemFactory.create(
parent=section,
category="vertical",
)
ItemFactory.create(
parent=vertical,
category="openassessment",
rubric_assessments=rubric_assessments,
submission_start=(now + timedelta(days=1)).isoformat(),
submission_end=(now + timedelta(days=7)).isoformat(),
)
blocks = get_course_date_blocks(course, user, request, include_past_dates=True)
self.assertEqual(len(blocks), date_block_count)
@RELATIVE_DATES_FLAG.override(active=True)
def test_enabled_block_types_with_expired_course(self):
course = create_course_run(days_till_start=-100)

View File

@@ -0,0 +1,45 @@
"""
Courseware BlockTransformer implementations
"""
from openedx.core.djangoapps.content.block_structure.transformer import (
BlockStructureTransformer,
FilteringTransformerMixin,
)
class OpenAssessmentDateTransformer(FilteringTransformerMixin, BlockStructureTransformer):
"""
BlockTransformer to collect all fields related to dates for openassessment problems.
"""
WRITE_VERSION = 1
READ_VERSION = 1
@classmethod
def name(cls):
"""
Unique identifier for the transformer's class;
same identifier used in setup.py.
"""
return "content_type_gate"
@classmethod
def collect(cls, block_structure):
"""
Collects any information that's necessary to execute this
transformer's transform method.
"""
block_structure.request_xblock_fields(
'valid_assessments',
'submission_start',
'submission_due',
'title',
'graded',
'format',
'has_score',
)
def transform_block_filters(self, usage_info, block_structure):
# This Transformer exists only to collect fields needed by other code, so it
# doesn't transform the tree.
return block_structure.create_universal_filter()

View File

@@ -99,10 +99,7 @@
}
.no-access {
// This is too low-contrast for a11y purposes. But since it only applies to pieces of the page that are
// inaccessible to users, and we have a banner explaining the parts that are inaccessible at the top,
// we're OK from an accessibility point of view.
color: #d1d2d4;
color: #767676;
}
.timeline-date-content {
@@ -115,10 +112,14 @@
align-items: center;
&.not-released {
color: #767676;
color: #b1a3a3;
}
}
.timeline-date {
color: #2d323e;
}
.timeline-title {
@include font-size(14);
@@ -134,7 +135,7 @@
}
&.not-released {
color: #d1d2d4;
color: #767676;
}
}
@@ -149,8 +150,30 @@
color: #2d323e;
text-decoration: underline;
}
&.not-released {
color: #767676;
}
}
.timeline-extra-info {
@include font-size(14);
display: flex;
flex: 100%;
line-height: 1.25;
a {
color: #2d323e;
text-decoration: underline;
}
&.not-released {
color: #767676;
}
}
.pill {
@include font-size(12);

View File

@@ -82,6 +82,11 @@ from openedx.core.djangolib.markup import HTML, Text
<div class="timeline-description ${access_class} ${not_released}">
${block.description}
</div>
% if block.extra_info:
<div class="timeline-extra-info ${access_class} ${not_released}">
${block.extra_info}
</div>
% endif
% endif
</div>
</div>

View File

@@ -41,10 +41,20 @@ class TestIcsGeneration(TestCase):
def make_assigment(
self, block_key=None, title=None, url=None, date=None, contains_gated_content=False, complete=False,
past_due=False, assignment_type=None
past_due=False, assignment_type=None, extra_info=None
):
""" Bundles given info into a namedtupled like get_course_assignments returns """
return _Assignment(block_key, title, url, date, contains_gated_content, complete, past_due, assignment_type)
return _Assignment(
block_key,
title,
url,
date,
contains_gated_content,
complete,
past_due,
assignment_type,
extra_info
)
def expected_ics(self, *assignments):
""" Returns hardcoded expected ics strings for given assignments """

View File

@@ -41,10 +41,11 @@ class ContentTypeGateTransformer(BlockStructureTransformer):
inside of it is content gated. `contains_gated_content` can then be used to indicate something
in the blocks subtree is gated.
"""
if block_structure.get_xblock_field(block_key, 'contains_gated_content'):
return
block_structure.override_xblock_field(block_key, 'contains_gated_content', True)
for parent_block_key in block_structure.get_parents(block_key):
if block_structure.get_xblock_field(parent_block_key, 'contains_gated_content'):
continue
block_structure.override_xblock_field(parent_block_key, 'contains_gated_content', True)
self._set_contains_gated_content_on_parents(block_structure, parent_block_key)
def transform(self, usage_info, block_structure):

View File

@@ -21,6 +21,9 @@ from django.utils.translation import ugettext as _
% if course_date.description:
<p class="description">${course_date.description}</p>
% endif
% if course_date.extra_info:
<div class="extra-info">${course_date.extra_info}</div>
% endif
% if course_date.link and course_date.link_text:
<div class="date-summary-link">
<a href="${course_date.link}">${course_date.link_text}</a>

View File

@@ -66,6 +66,7 @@ setup(
"load_override_data = lms.djangoapps.course_blocks.transformers.load_override_data:OverrideDataTransformer",
"content_type_gate = openedx.features.content_type_gating.block_transformers:ContentTypeGateTransformer",
"access_denied_message_filter = lms.djangoapps.course_blocks.transformers.access_denied_filter:AccessDeniedMessageFilterTransformer",
"open_assessment_transformer = lms.djangoapps.courseware.transformers:OpenAssessmentDateTransformer",
],
"openedx.ace.policy": [
"bulk_email_optout = lms.djangoapps.bulk_email.policies:CourseEmailOptout"