Commit a495bb5f by Martyn James

Addresses problem with 'up' and 'down' errors within SOL-289

parent 59ad3ccb
''' useful functions for finding content and its position '''
from logging import getLogger
from .exceptions import (ItemNotFoundError, NoPathToItem)
LOGGER = getLogger(__name__)
def path_to_location(modulestore, usage_key):
'''
......@@ -105,3 +110,30 @@ def path_to_location(modulestore, usage_key):
position = "_".join(position_list)
return (course_id, chapter, section, position)
def navigation_index(position):
"""
Get the navigation index from the position argument (where the position argument was recieved from a call to
path_to_location)
Argument:
position - result of position returned from call to path_to_location. This is an underscore (_) separated string of
vertical 1-indexed positions. If the course is built in Studio then you'll never see verticals as children of
verticals, and so extremely often one will only see the first vertical as an integer position. This specific action
is to allow navigation / breadcrumbs to locate the topmost item because this is the location actually required by
the LMS code
Returns:
1-based integer of the position of the desired item within the vertical
"""
if position is None:
return None
try:
navigation_position = int(position.split('_', 1)[0])
except (ValueError, TypeError):
LOGGER.exception(u'Bad position %r passed to navigation_index, will assume first position', position)
navigation_position = 1
return navigation_position
......@@ -38,7 +38,7 @@ from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.draft_and_published import UnsupportedRevisionError, DIRECT_ONLY_CATEGORIES
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError, ReferentialIntegrityError, NoPathToItem
from xmodule.modulestore.mixed import MixedModuleStore
from xmodule.modulestore.search import path_to_location
from xmodule.modulestore.search import path_to_location, navigation_index
from xmodule.modulestore.tests.factories import check_mongo_calls, check_exact_number_of_calls, \
mongo_uses_error_check
from xmodule.modulestore.tests.utils import create_modulestore_instance, LocationMixin
......@@ -1153,6 +1153,18 @@ class TestMixedModuleStore(CourseComparisonTest):
with self.assertRaises(ItemNotFoundError):
path_to_location(self.store, location)
def test_navigation_index(self):
"""
Make sure that navigation_index correctly parses the various position values that we might get from calls to
path_to_location
"""
self.assertEqual(1, navigation_index("1"))
self.assertEqual(10, navigation_index("10"))
self.assertEqual(None, navigation_index(None))
self.assertEqual(1, navigation_index("1_2"))
self.assertEqual(5, navigation_index("5_2"))
self.assertEqual(7, navigation_index("7_3_5_6_"))
@ddt.data('draft', 'split')
def test_revert_to_published_root_draft(self, default_ms):
"""
......
......@@ -71,6 +71,85 @@ class TestJumpTo(ModuleStoreTestCase):
response = self.client.get(jumpto_url)
self.assertRedirects(response, expected, status_code=302, target_status_code=302)
def test_jumpto_from_section(self):
course = CourseFactory.create()
chapter = ItemFactory.create(category='chapter', parent_location=course.location)
section = ItemFactory.create(category='sequential', parent_location=chapter.location)
expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/'.format(
course_id=unicode(course.id),
chapter_id=chapter.url_name,
section_id=section.url_name,
)
jumpto_url = '{0}/{1}/jump_to/{2}'.format(
'/courses',
unicode(course.id),
unicode(section.location),
)
response = self.client.get(jumpto_url)
self.assertRedirects(response, expected, status_code=302, target_status_code=302)
def test_jumpto_from_module(self):
course = CourseFactory.create()
chapter = ItemFactory.create(category='chapter', parent_location=course.location)
section = ItemFactory.create(category='sequential', parent_location=chapter.location)
vertical1 = ItemFactory.create(category='vertical', parent_location=section.location)
vertical2 = ItemFactory.create(category='vertical', parent_location=section.location)
module1 = ItemFactory.create(category='html', parent_location=vertical1.location)
module2 = ItemFactory.create(category='html', parent_location=vertical2.location)
expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1'.format(
course_id=unicode(course.id),
chapter_id=chapter.url_name,
section_id=section.url_name,
)
jumpto_url = '{0}/{1}/jump_to/{2}'.format(
'/courses',
unicode(course.id),
unicode(module1.location),
)
response = self.client.get(jumpto_url)
self.assertRedirects(response, expected, status_code=302, target_status_code=302)
expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/2'.format(
course_id=unicode(course.id),
chapter_id=chapter.url_name,
section_id=section.url_name,
)
jumpto_url = '{0}/{1}/jump_to/{2}'.format(
'/courses',
unicode(course.id),
unicode(module2.location),
)
response = self.client.get(jumpto_url)
self.assertRedirects(response, expected, status_code=302, target_status_code=302)
def test_jumpto_from_nested_module(self):
course = CourseFactory.create()
chapter = ItemFactory.create(category='chapter', parent_location=course.location)
section = ItemFactory.create(category='sequential', parent_location=chapter.location)
vertical = ItemFactory.create(category='vertical', parent_location=section.location)
nested_section = ItemFactory.create(category='sequential', parent_location=vertical.location)
nested_vertical1 = ItemFactory.create(category='vertical', parent_location=nested_section.location)
# put a module into nested_vertical1 for completeness
ItemFactory.create(category='html', parent_location=nested_vertical1.location)
nested_vertical2 = ItemFactory.create(category='vertical', parent_location=nested_section.location)
module2 = ItemFactory.create(category='html', parent_location=nested_vertical2.location)
# internal position of module2 will be 1_2 (2nd item withing 1st item)
expected = 'courses/{course_id}/courseware/{chapter_id}/{section_id}/1'.format(
course_id=unicode(course.id),
chapter_id=chapter.url_name,
section_id=section.url_name,
)
jumpto_url = '{0}/{1}/jump_to/{2}'.format(
'/courses',
unicode(course.id),
unicode(module2.location),
)
response = self.client.get(jumpto_url)
self.assertRedirects(response, expected, status_code=302, target_status_code=302)
def test_jumpto_id_invalid_location(self):
location = Location('edX', 'toy', 'NoSuchPlace', None, None, None)
jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
......
......@@ -50,7 +50,7 @@ from util.cache import cache, cache_if_anonymous
from xblock.fragment import Fragment
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from xmodule.modulestore.search import path_to_location
from xmodule.modulestore.search import path_to_location, navigation_index
from xmodule.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab
from xmodule.x_module import STUDENT_VIEW
import shoppingcart
......@@ -61,7 +61,7 @@ from util.milestones_helpers import get_prerequisite_courses_display
from microsite_configuration import microsite
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.keys import CourseKey, UsageKey
from instructor.enrollment import uses_shib
from util.db import commit_on_success_with_read_committed
......@@ -619,8 +619,8 @@ def jump_to(_request, course_id, location):
has access, and what they should see.
"""
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
usage_key = course_key.make_usage_key_from_deprecated_string(location)
course_key = CourseKey.from_string(course_id)
usage_key = UsageKey.from_string(location).replace(course_key=course_key)
except InvalidKeyError:
raise Http404(u"Invalid course_key or usage_key")
try:
......@@ -634,13 +634,27 @@ def jump_to(_request, course_id, location):
# args provided by the redirect.
# Rely on index to do all error handling and access control.
if chapter is None:
return redirect('courseware', course_id=course_key.to_deprecated_string())
return redirect('courseware', course_id=unicode(course_key))
elif section is None:
return redirect('courseware_chapter', course_id=course_key.to_deprecated_string(), chapter=chapter)
return redirect('courseware_chapter', course_id=unicode(course_key), chapter=chapter)
elif position is None:
return redirect('courseware_section', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section)
return redirect(
'courseware_section',
course_id=unicode(course_key),
chapter=chapter,
section=section
)
else:
return redirect('courseware_position', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section, position=position)
# Here we use the navigation_index from the position returned from
# path_to_location - we can only navigate to the topmost vertical at the
# moment
return redirect(
'courseware_position',
course_id=unicode(course_key),
chapter=chapter,
section=section,
position=navigation_index(position)
)
@ensure_csrf_cookie
......
......@@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from search.result_processor import SearchResultProcessor
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.search import path_to_location
from xmodule.modulestore.search import path_to_location, navigation_index
from courseware.access import has_access
......@@ -77,10 +77,10 @@ class LmsSearchResultProcessor(SearchResultProcessor):
def get_position_name(section, position):
""" helper to fetch name corresponding to the position therein """
pos = int(position)
section_item = self.get_item(course_key.make_usage_key("sequential", section))
if section_item.has_children and len(section_item.children) >= pos:
return get_display_name(section_item.children[pos - 1])
if position:
section_item = self.get_item(course_key.make_usage_key("sequential", section))
if section_item.has_children and len(section_item.children) >= position:
return get_display_name(section_item.children[position - 1])
return None
location_description = []
......@@ -89,7 +89,10 @@ class LmsSearchResultProcessor(SearchResultProcessor):
if section:
location_description.append(get_display_name(course_key.make_usage_key("sequential", section)))
if position:
location_description.append(get_position_name(section, position))
# We're only wanting to show the first vertical, so we use the
# navigation_index function to display the same location to which one
# would be sent if navigating
location_description.append(get_position_name(section, navigation_index(position)))
return location_description
......
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