Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 49 additions & 22 deletions common/lib/xmodule/xmodule/seq_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,22 @@ def handle_ajax(self, dispatch, data): # TODO: bounds checking
return json.dumps({
'complete': complete
})
elif dispatch == 'metadata':
context = {'exclude_units': True}
prereq_met = True
prereq_meta_info = {}
banner_text = None
display_items = self.get_display_items()

if self._required_prereq():
if self.runtime.user_is_staff:
banner_text = _('This subsection is unlocked for learners when they meet the prerequisite requirements.')
else:
# check if prerequisite has been met
prereq_met, prereq_meta_info = self._compute_is_prereq_met(True)
meta = self._get_render_metadata(context, display_items, prereq_met, prereq_meta_info, banner_text, STUDENT_VIEW)
meta['display_name'] = self.display_name_with_default
return json.dumps(meta)
raise NotFoundError('Unexpected dispatch type')

@classmethod
Expand Down Expand Up @@ -345,27 +361,18 @@ def is_user_authenticated(self, context):
# NOTE (CCB): We default to true to maintain the behavior in place prior to allowing anonymous access access.
return context.get('user_authenticated', True)

def _student_or_public_view(self, context, prereq_met, prereq_meta_info, banner_text=None, view=STUDENT_VIEW):
"""
Returns the rendered student view of the content of this
sequential. If banner_text is given, it is added to the
content.
"""
_ = self.runtime.service(self, "i18n").ugettext
display_items = self.get_display_items()
self._update_position(context, len(display_items))

def _get_render_metadata(self, context, display_items, prereq_met, prereq_meta_info, banner_text=None, view=STUDENT_VIEW, fragment=None):
if prereq_met and not self._is_gate_fulfilled():
banner_text = _(
'This section is a prerequisite. You must complete this section in order to unlock additional content.'
)

fragment = Fragment()
items = self._render_student_view_for_items(context, display_items, fragment, view) if prereq_met else []
params = {
'items': items,
'element_id': self.location.html_id(),
'item_id': text_type(self.location),
'is_time_limited': self.is_time_limited,
'position': self.position,
'tag': self.location.block_type,
'ajax_url': self.system.ajax_url,
Expand All @@ -377,6 +384,20 @@ def _student_or_public_view(self, context, prereq_met, prereq_meta_info, banner_
'gated_content': self._get_gated_content_info(prereq_met, prereq_meta_info),
'exclude_units': context.get('exclude_units', False)
}
return params

def _student_or_public_view(self, context, prereq_met, prereq_meta_info, banner_text=None, view=STUDENT_VIEW):
"""
Returns the rendered student view of the content of this
sequential. If banner_text is given, it is added to the
content.
"""
_ = self.runtime.service(self, "i18n").ugettext
display_items = self.get_display_items()
self._update_position(context, len(display_items))

fragment = Fragment()
params = self._get_render_metadata(context, display_items, prereq_met, prereq_meta_info, banner_text, view, fragment)
fragment.add_content(self.system.render_template("seq_module.html", params))

self._capture_full_seq_item_metrics(display_items)
Expand All @@ -388,11 +409,18 @@ def _get_gated_content_info(self, prereq_met, prereq_meta_info):
"""
Returns a dict of information about gated_content context
"""
gated_content = {}
gated_content['gated'] = not prereq_met
gated_content['prereq_url'] = prereq_meta_info['url'] if not prereq_met else None
gated_content['prereq_section_name'] = prereq_meta_info['display_name'] if not prereq_met else None
gated_content['gated_section_name'] = self.display_name
gated_content = {
'prereq_id': None,
'prereq_url': None,
'prereq_section_name': None,
'gated': False,
'gated_section_name': self.display_name,
}
if not prereq_met:
gated_content['gated'] = True
gated_content['prereq_url'] = prereq_meta_info['url']
gated_content['prereq_section_name'] = prereq_meta_info['display_name']
gated_content['prereq_id'] = prereq_meta_info['id']

return gated_content

Expand Down Expand Up @@ -474,11 +502,11 @@ def _render_student_view_for_items(self, context, display_items, fragment, view=
"""
render_items = not context.get('exclude_units', False)
is_user_authenticated = self.is_user_authenticated(context)
completion_service = self.runtime.service(self, 'completion')
if render_items:
bookmarks_service = self.runtime.service(self, 'bookmarks')
completion_service = self.runtime.service(self, 'completion')
else:
bookmarks_service = completion_service = None
bookmarks_service = None
context['username'] = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(
'edx-platform.username')
display_names = [
Expand Down Expand Up @@ -527,10 +555,9 @@ def _render_student_view_for_items(self, context, display_items, fragment, view=
# The item url format can be defined in the template context like so:
# context['item_url'] = '/my/item/path/{usage_key}/whatever'
iteminfo['href'] = context.get('item_url', '').format(usage_key=usage_id)
if is_user_authenticated and render_items:
if item.location.block_type == 'vertical':
if completion_service:
iteminfo['complete'] = completion_service.vertical_is_complete(item)
if is_user_authenticated:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we no longer have a check for render_items here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced the render_items conditional when adding the sequence module UI navigation to Studio. In that case, there's no need for completion and bookmark checks, so I hid them behind the conditional. Now we need the completion info for the Unit, regardless of whether it's rendered.

if item.location.block_type == 'vertical' and completion_service:
iteminfo['complete'] = completion_service.vertical_is_complete(item)

contents.append(iteminfo)

Expand Down
12 changes: 11 additions & 1 deletion common/lib/xmodule/xmodule/tests/test_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def test_gated_content(self):
gating_mock_1_2.return_value.required_prereq.return_value = True
gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [
False,
{'url': 'PrereqUrl', 'display_name': 'PrereqSectionName'}
{'url': 'PrereqUrl', 'display_name': 'PrereqSectionName', 'id': 'mockId'}
]
self.sequence_1_2.xmodule_runtime._services['gating'] = gating_mock_1_2 # pylint: disable=protected-access
self.sequence_1_2.display_name = 'sequence_1_2'
Expand Down Expand Up @@ -343,6 +343,16 @@ def test_handle_ajax_get_completion_return_none(self):
)
self.assertIs(completion_return, None)

def test_handle_ajax_metadata(self):
"""
Test that the sequence metadata is returned from the
metadata ajax handler.
"""
metadata = json.loads(self.sequence_3_1.handle_ajax('metadata', {}))
self.assertEqual(len(metadata['items']), 3)
self.assertEqual(metadata['tag'], 'sequential')
self.assertEqual(metadata['display_name'], self.sequence_3_1.display_name_with_default)

def get_context_dict_from_string(self, data):
"""
Retrieve dictionary from string.
Expand Down
9 changes: 5 additions & 4 deletions openedx/core/lib/gating/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from xblock.completable import XBlockCompletionMode as CompletionMode
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
import six


log = logging.getLogger(__name__)

Expand Down Expand Up @@ -164,7 +164,7 @@ def get_prerequisites(course_key):
milestone = milestones_by_block_id.get(block.location.block_id)
if milestone:
milestone['block_display_name'] = block.display_name
milestone['block_usage_key'] = six.text_type(block.location)
milestone['block_usage_key'] = str(block.location)
result.append(milestone)

return result
Expand All @@ -184,7 +184,7 @@ def add_prerequisite(course_key, prereq_content_key):
"""
milestone = milestones_api.add_milestone(
{
'name': _(u'Gating milestone for {usage_key}').format(usage_key=six.text_type(prereq_content_key)),
'name': _(u'Gating milestone for {usage_key}').format(usage_key=str(prereq_content_key)),
'namespace': "{usage_key}{qualifier}".format(
usage_key=prereq_content_key,
qualifier=GATING_NAMESPACE_QUALIFIER
Expand Down Expand Up @@ -383,7 +383,8 @@ def compute_is_prereq_met(content_id, user_id, recalc_on_unmet=False):
subsection = store.get_item(subsection_usage_key)
prereq_meta_info = {
'url': reverse('jump_to', kwargs={'course_id': course_key, 'location': subsection_usage_key}),
'display_name': subsection.display_name
'display_name': subsection.display_name,
'id': str(subsection_usage_key)
}
prereq_met = update_milestone(milestone, subsection_usage_key, milestone, student)

Expand Down