Merge branch 'master' into ri/EDUCATOR-394-disable-self-generation-certificates
This commit is contained in:
@@ -282,6 +282,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
selected_groups_label = get_visibility_partition_info(xblock)['selected_groups_label']
|
||||
if selected_groups_label:
|
||||
selected_groups_label = _('Access restricted to: {list_of_groups}').format(list_of_groups=selected_groups_label)
|
||||
course = modulestore().get_course(xblock.location.course_key)
|
||||
template_context = {
|
||||
'xblock_context': context,
|
||||
'xblock': xblock,
|
||||
@@ -293,8 +294,10 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
'can_edit_visibility': context.get('can_edit_visibility', True),
|
||||
'selected_groups_label': selected_groups_label,
|
||||
'can_add': context.get('can_add', True),
|
||||
'can_move': context.get('can_move', True)
|
||||
'can_move': context.get('can_move', True),
|
||||
'language': getattr(course, 'language', None)
|
||||
}
|
||||
|
||||
html = render_to_string('studio_xblock_wrapper.html', template_context)
|
||||
frag = wrap_fragment(frag, html)
|
||||
return frag
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# below are the sub-paths to the documentation for the various pages
|
||||
# NOTE: If any of these page settings change, then their corresponding test should be updated
|
||||
# NOTE: If any of these page settings change, their corresponding test must be updated
|
||||
# in edx-platform/common/test/acceptance/tests/studio/test_studio.help.py
|
||||
[pages]
|
||||
default = course_author:index.html
|
||||
@@ -12,12 +12,12 @@ updates = course_author:course_assets/handouts_updates.html
|
||||
pages = course_author:course_assets/pages.html
|
||||
files = course_author:course_assets/course_files.html
|
||||
textbooks = course_author:course_assets/textbooks.html
|
||||
schedule = course_author:set_up_course/setting_up_student_view.html
|
||||
schedule = course_author:set_up_course/studio_add_course_information/index.html
|
||||
grading = course_author:grading/index.html
|
||||
team_course = course_author:set_up_course/course_staffing.html#add-course-team-members
|
||||
team_course = course_author:set_up_course/studio_add_course_information/studio_course_staffing.html
|
||||
team_library = course_author:course_components/libraries.html#give-other-users-access-to-your-library
|
||||
advanced = course_author:index.html
|
||||
checklist = course_author:set_up_course/creating_new_course.html
|
||||
checklist = course_author:set_up_course/index.html
|
||||
import_library = course_author:course_components/libraries.html#import-a-library
|
||||
import_course = course_author:releasing_course/export_import_course.html#import-a-course
|
||||
export_library = course_author:course_components/libraries.html#export-a-library
|
||||
@@ -27,11 +27,11 @@ login = course_author:getting_started/index.html
|
||||
register = course_author:getting_started/index.html
|
||||
content_libraries = course_author:course_components/libraries.html
|
||||
content_groups = course_author:course_features/cohorts/cohorted_courseware.html
|
||||
enrollment_tracks = course_author:course_features/cohorts/cohorted_courseware.html
|
||||
enrollment_tracks = course_author:course_features/diff_content/enroll_track_courseware.html
|
||||
group_configurations = course_author:course_features/content_experiments/content_experiments_configure.html#set-up-group-configurations-in-edx-studio
|
||||
container = course_author:developing_course/course_components.html#components-that-contain-other-components
|
||||
video = course_author:video/video_uploads.html
|
||||
certificates = course_author:set_up_course/creating_course_certificates.html
|
||||
certificates = course_author:set_up_course/studio_add_course_information/studio_creating_certificates.html
|
||||
|
||||
# below are the language directory names for the different locales
|
||||
[locales]
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
.wrapper-header {
|
||||
|
||||
.wrapper-l {
|
||||
width: flex-grid(8,12);
|
||||
width: flex-grid(9,12);
|
||||
}
|
||||
|
||||
.wrapper-r {
|
||||
width: flex-grid(4,12);
|
||||
width: flex-grid(3,12);
|
||||
}
|
||||
|
||||
.branding {
|
||||
|
||||
@@ -62,7 +62,11 @@ from openedx.core.djangolib.js_utils import (
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<div class="course-info-wrapper">
|
||||
<div class="course-info-wrapper"
|
||||
% if getattr(context_course, 'language'):
|
||||
lang="${context_course.language}"
|
||||
% endif
|
||||
>
|
||||
<div class="main-column window">
|
||||
<article class="course-updates" id="course-update-view">
|
||||
<ol class="update-list" id="course-update-list"></ol>
|
||||
|
||||
@@ -175,8 +175,11 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
<div class="wrapper-dnd">
|
||||
<div class="wrapper-dnd"
|
||||
% if getattr(context_course, 'language'):
|
||||
lang="${context_course.language}"
|
||||
% endif
|
||||
>
|
||||
<%
|
||||
course_locator = context_course.location
|
||||
%>
|
||||
|
||||
@@ -158,7 +158,11 @@ messages = xblock.validate().to_json()
|
||||
|
||||
% if show_preview:
|
||||
% if is_root or not xblock_url:
|
||||
<article class="xblock-render">
|
||||
% if not is_root and language:
|
||||
<article class="xblock-render" lang="${language}">
|
||||
% else:
|
||||
<article class="xblock-render">
|
||||
% endif
|
||||
${content | n, decode.utf8}
|
||||
</article>
|
||||
% else:
|
||||
|
||||
@@ -87,9 +87,8 @@ class PartitionService(object):
|
||||
with a given course.
|
||||
"""
|
||||
|
||||
def __init__(self, course_id, track_function=None, cache=None):
|
||||
def __init__(self, course_id, cache=None):
|
||||
self._course_id = course_id
|
||||
self._track_function = track_function
|
||||
self._cache = cache
|
||||
|
||||
def get_course(self):
|
||||
@@ -165,7 +164,7 @@ class PartitionService(object):
|
||||
the partition's scheme.
|
||||
"""
|
||||
return user_partition.scheme.get_group_for_user(
|
||||
self._course_id, user, user_partition, assign=assign, track_function=self._track_function
|
||||
self._course_id, user, user_partition, assign=assign,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ class MockUserPartitionScheme(object):
|
||||
self.name = name
|
||||
self.current_group = current_group
|
||||
|
||||
def get_group_for_user(self, course_id, user, user_partition, assign=True, track_function=None): # pylint: disable=unused-argument
|
||||
def get_group_for_user(self, course_id, user, user_partition, assign=True): # pylint: disable=unused-argument
|
||||
"""
|
||||
Returns the current group if set, else the first group from the specified user partition.
|
||||
"""
|
||||
@@ -446,7 +446,6 @@ class PartitionServiceBaseClass(PartitionTestCase):
|
||||
return MockPartitionService(
|
||||
self.course,
|
||||
course_id=self.course.id,
|
||||
track_function=Mock(),
|
||||
cache=cache
|
||||
)
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@ class SplitTestModuleTest(XModuleXmlImportTest, PartitionTestCase):
|
||||
partitions_service = MockPartitionService(
|
||||
self.course,
|
||||
course_id=self.course.id,
|
||||
track_function=Mock(name='track_function'),
|
||||
)
|
||||
self.module_system._services['partitions'] = partitions_service # pylint: disable=protected-access
|
||||
|
||||
|
||||
@@ -46,6 +46,14 @@ class CourseHomePage(CoursePage):
|
||||
courseware_page = CoursewarePage(self.browser, self.course_id)
|
||||
courseware_page.wait_for_page()
|
||||
|
||||
def search_for_term(self, search_term):
|
||||
"""
|
||||
Search within a class for a particular term.
|
||||
"""
|
||||
self.q(css='.search-form > .search-input').fill(search_term)
|
||||
self.q(css='.search-form > .search-button').click()
|
||||
return CourseSearchResultsPage(self.browser, self.course_id)
|
||||
|
||||
|
||||
class CourseOutlinePage(PageObject):
|
||||
"""
|
||||
@@ -225,3 +233,22 @@ class CourseOutlinePage(PageObject):
|
||||
promise_check_func=lambda: courseware_page.nav.is_on_section(section_title, subsection_title),
|
||||
description="Waiting for course page with section '{0}' and subsection '{1}'".format(section_title, subsection_title)
|
||||
)
|
||||
|
||||
|
||||
class CourseSearchResultsPage(CoursePage):
|
||||
"""
|
||||
Course search page
|
||||
"""
|
||||
|
||||
# url = "courses/{course_id}/search/?query={query_string}"
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='.page-content > .search-results').present
|
||||
|
||||
def __init__(self, browser, course_id):
|
||||
super(CourseSearchResultsPage, self).__init__(browser, course_id)
|
||||
self.course_id = course_id
|
||||
|
||||
@property
|
||||
def search_results(self):
|
||||
return self.q(css='.search-results-item')
|
||||
|
||||
@@ -18,7 +18,7 @@ class DashboardSearchPage(PageObject):
|
||||
@property
|
||||
def search_results(self):
|
||||
""" search results list showing """
|
||||
return self.q(css='#dashboard-search-results')
|
||||
return self.q(css='.search-results')
|
||||
|
||||
def is_browser_on_page(self):
|
||||
""" did we find the search bar in the UI """
|
||||
|
||||
@@ -285,7 +285,7 @@ class DiscussionNavigationTest(BaseDiscussionTestCase):
|
||||
|
||||
def test_breadcrumbs_clear_search(self):
|
||||
self.thread_page.q(css=".search-input").fill("search text")
|
||||
self.thread_page.q(css=".search-btn").click()
|
||||
self.thread_page.q(css=".search-button").click()
|
||||
|
||||
# Verify that clicking the first breadcrumb clears your search
|
||||
self.thread_page.q(css=".breadcrumbs .nav-item")[0].click()
|
||||
|
||||
@@ -10,7 +10,7 @@ from nose.plugins.attrib import attr
|
||||
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
|
||||
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.pages.common.logout import LogoutPage
|
||||
from common.test.acceptance.pages.lms.courseware_search import CoursewareSearchPage
|
||||
from common.test.acceptance.pages.lms.course_home import CourseHomePage
|
||||
from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage
|
||||
from common.test.acceptance.pages.lms.staff_view import StaffCoursewarePage
|
||||
from common.test.acceptance.pages.studio.component_editor import ComponentVisibilityEditorView
|
||||
@@ -73,7 +73,7 @@ class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin):
|
||||
email=self.cohort_default_student_email, no_login=True
|
||||
).visit()
|
||||
|
||||
self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id)
|
||||
self.course_home_page = CourseHomePage(self.browser, self.course_id)
|
||||
|
||||
# Enable Cohorting and assign cohorts and content groups
|
||||
self._auto_auth(self.staff_user["username"], self.staff_user["email"], True)
|
||||
@@ -105,11 +105,21 @@ class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin):
|
||||
"""
|
||||
Open staff page with assertion
|
||||
"""
|
||||
self.courseware_search_page.visit()
|
||||
self.course_home_page.visit()
|
||||
self.course_home_page.resume_course_from_header()
|
||||
staff_page = StaffCoursewarePage(self.browser, self.course_id)
|
||||
self.assertEqual(staff_page.staff_view_mode, 'Staff')
|
||||
return staff_page
|
||||
|
||||
def _search_for_term(self, term):
|
||||
"""
|
||||
Search for term in course and return results.
|
||||
"""
|
||||
self.course_home_page.visit()
|
||||
course_search_results_page = self.course_home_page.search_for_term(term)
|
||||
results = course_search_results_page.search_results.html
|
||||
return results[0] if len(results) > 0 else []
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""
|
||||
Populate the children of the test course fixture.
|
||||
@@ -195,48 +205,21 @@ class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin):
|
||||
add_cohort_with_student("Cohort B", self.content_group_b, self.cohort_b_student_username)
|
||||
cohort_management_page.wait_for_ajax()
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that the page is accessible.
|
||||
"""
|
||||
self._auto_auth(self.cohort_default_student_username, self.cohort_default_student_email, False)
|
||||
self.courseware_search_page.visit()
|
||||
|
||||
def test_cohorted_search_user_a_a_content(self):
|
||||
"""
|
||||
Test user can search content restricted to his cohort.
|
||||
"""
|
||||
self._auto_auth(self.cohort_a_student_username, self.cohort_a_student_email, False)
|
||||
self.courseware_search_page.visit()
|
||||
self.courseware_search_page.search_for_term(self.group_a_html)
|
||||
assert self.group_a_html in self.courseware_search_page.search_results.html[0]
|
||||
search_results = self._search_for_term(self.group_a_html)
|
||||
assert self.group_a_html in search_results
|
||||
|
||||
def test_cohorted_search_user_b_a_content(self):
|
||||
"""
|
||||
Test user can not search content restricted to his cohort.
|
||||
"""
|
||||
self._auto_auth(self.cohort_b_student_username, self.cohort_b_student_email, False)
|
||||
self.courseware_search_page.visit()
|
||||
self.courseware_search_page.search_for_term(self.group_a_html)
|
||||
assert self.group_a_html not in self.courseware_search_page.search_results.html[0]
|
||||
|
||||
def test_cohorted_search_user_default_ab_content(self):
|
||||
"""
|
||||
Test user not enrolled in any cohorts can't see any of restricted content.
|
||||
"""
|
||||
self._auto_auth(self.cohort_default_student_username, self.cohort_default_student_email, False)
|
||||
self.courseware_search_page.visit()
|
||||
self.courseware_search_page.search_for_term(self.group_a_and_b_html)
|
||||
assert self.group_a_and_b_html not in self.courseware_search_page.search_results.html[0]
|
||||
|
||||
def test_cohorted_search_user_default_all_content(self):
|
||||
"""
|
||||
Test user can search public content if cohorts used on course.
|
||||
"""
|
||||
self._auto_auth(self.cohort_default_student_username, self.cohort_default_student_email, False)
|
||||
self.courseware_search_page.visit()
|
||||
self.courseware_search_page.search_for_term(self.visible_to_all_html)
|
||||
assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0]
|
||||
search_results = self._search_for_term(self.group_a_html)
|
||||
assert self.group_a_html not in search_results
|
||||
|
||||
def test_cohorted_search_user_staff_all_content(self):
|
||||
"""
|
||||
@@ -244,17 +227,14 @@ class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin):
|
||||
"""
|
||||
self._auto_auth(self.staff_user["username"], self.staff_user["email"], False)
|
||||
self._goto_staff_page().set_staff_view_mode('Staff')
|
||||
self.courseware_search_page.search_for_term(self.visible_to_all_html)
|
||||
assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_a_and_b_html)
|
||||
assert self.group_a_and_b_html in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_a_html)
|
||||
assert self.group_a_html in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_b_html)
|
||||
assert self.group_b_html in self.courseware_search_page.search_results.html[0]
|
||||
search_results = self._search_for_term(self.visible_to_all_html)
|
||||
assert self.visible_to_all_html in search_results
|
||||
search_results = self._search_for_term(self.group_a_and_b_html)
|
||||
assert self.group_a_and_b_html in search_results
|
||||
search_results = self._search_for_term(self.group_a_html)
|
||||
assert self.group_a_html in search_results
|
||||
search_results = self._search_for_term(self.group_b_html)
|
||||
assert self.group_b_html in search_results
|
||||
|
||||
def test_cohorted_search_user_staff_masquerade_student_content(self):
|
||||
"""
|
||||
@@ -262,17 +242,14 @@ class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin):
|
||||
"""
|
||||
self._auto_auth(self.staff_user["username"], self.staff_user["email"], False)
|
||||
self._goto_staff_page().set_staff_view_mode('Learner')
|
||||
self.courseware_search_page.search_for_term(self.visible_to_all_html)
|
||||
assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_a_and_b_html)
|
||||
assert self.group_a_and_b_html not in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_a_html)
|
||||
assert self.group_a_html not in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_b_html)
|
||||
assert self.group_b_html not in self.courseware_search_page.search_results.html[0]
|
||||
search_results = self._search_for_term(self.visible_to_all_html)
|
||||
assert self.visible_to_all_html in search_results
|
||||
search_results = self._search_for_term(self.group_a_and_b_html)
|
||||
assert self.group_a_and_b_html not in search_results
|
||||
search_results = self._search_for_term(self.group_a_html)
|
||||
assert self.group_a_html not in search_results
|
||||
search_results = self._search_for_term(self.group_b_html)
|
||||
assert self.group_b_html not in search_results
|
||||
|
||||
def test_cohorted_search_user_staff_masquerade_cohort_content(self):
|
||||
"""
|
||||
@@ -280,14 +257,11 @@ class CoursewareSearchCohortTest(ContainerBase, CohortTestMixin):
|
||||
"""
|
||||
self._auto_auth(self.staff_user["username"], self.staff_user["email"], False)
|
||||
self._goto_staff_page().set_staff_view_mode('Learner in ' + self.content_group_a)
|
||||
self.courseware_search_page.search_for_term(self.visible_to_all_html)
|
||||
assert self.visible_to_all_html in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_a_and_b_html)
|
||||
assert self.group_a_and_b_html in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_a_html)
|
||||
assert self.group_a_html in self.courseware_search_page.search_results.html[0]
|
||||
self.courseware_search_page.clear_search()
|
||||
self.courseware_search_page.search_for_term(self.group_b_html)
|
||||
assert self.group_b_html not in self.courseware_search_page.search_results.html[0]
|
||||
search_results = self._search_for_term(self.visible_to_all_html)
|
||||
assert self.visible_to_all_html in search_results
|
||||
search_results = self._search_for_term(self.group_a_and_b_html)
|
||||
assert self.group_a_and_b_html in search_results
|
||||
search_results = self._search_for_term(self.group_a_html)
|
||||
assert self.group_a_html in search_results
|
||||
search_results = self._search_for_term(self.group_b_html)
|
||||
assert self.group_b_html not in search_results
|
||||
|
||||
@@ -7,7 +7,7 @@ from nose.plugins.attrib import attr
|
||||
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from ...pages.lms.bookmarks import BookmarksPage
|
||||
from ...pages.lms.course_home import CourseHomePage
|
||||
from ...pages.lms.course_home import CourseHomePage, CourseSearchResultsPage
|
||||
from ...pages.lms.courseware import CoursewarePage
|
||||
from ..helpers import UniqueCourseTest, auto_auth, load_data_str
|
||||
|
||||
@@ -134,3 +134,12 @@ class CourseHomeA11yTest(CourseHomeBaseTest):
|
||||
course_home_page = CourseHomePage(self.browser, self.course_id)
|
||||
course_home_page.visit()
|
||||
course_home_page.a11y_audit.check_for_accessibility_errors()
|
||||
|
||||
def test_course_search_a11y(self):
|
||||
"""
|
||||
Test the accessibility of the search results page.
|
||||
"""
|
||||
course_home_page = CourseHomePage(self.browser, self.course_id)
|
||||
course_home_page.visit()
|
||||
course_search_results_page = course_home_page.search_for_term("Test Search")
|
||||
course_search_results_page.a11y_audit.check_for_accessibility_errors()
|
||||
|
||||
@@ -10,6 +10,7 @@ from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureD
|
||||
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.pages.common.logout import LogoutPage
|
||||
from common.test.acceptance.pages.common.utils import click_css
|
||||
from common.test.acceptance.pages.lms.course_home import CourseHomePage
|
||||
from common.test.acceptance.pages.lms.courseware_search import CoursewareSearchPage
|
||||
from common.test.acceptance.pages.studio.container import ContainerPage
|
||||
from common.test.acceptance.pages.studio.overview import CourseOutlinePage as StudioCourseOutlinePage
|
||||
@@ -52,7 +53,8 @@ class CoursewareSearchTest(UniqueCourseTest):
|
||||
self.addCleanup(remove_file, self.TEST_INDEX_FILENAME)
|
||||
|
||||
super(CoursewareSearchTest, self).setUp()
|
||||
self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id)
|
||||
|
||||
self.course_home_page = CourseHomePage(self.browser, self.course_id)
|
||||
|
||||
self.studio_course_outline = StudioCourseOutlinePage(
|
||||
self.browser,
|
||||
@@ -149,17 +151,31 @@ class CoursewareSearchTest(UniqueCourseTest):
|
||||
(bool) True if search term is found in resulting content; False if not found
|
||||
"""
|
||||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||||
self.course_home_page.visit()
|
||||
course_search_results_page = self.course_home_page.search_for_term(search_term)
|
||||
if len(course_search_results_page.search_results.html) > 0:
|
||||
search_string = course_search_results_page.search_results.html[0]
|
||||
else:
|
||||
search_string = ""
|
||||
return search_term in search_string
|
||||
|
||||
# TODO: TNL-6546: Remove usages of sidebar search
|
||||
def _search_for_content_in_sidebar(self, search_term, perform_auto_auth=True):
|
||||
"""
|
||||
Login and search for specific content in the legacy sidebar search
|
||||
Arguments:
|
||||
search_term - term to be searched for
|
||||
perform_auto_auth - if False, skip auto_auth call.
|
||||
Returns:
|
||||
(bool) True if search term is found in resulting content; False if not found
|
||||
"""
|
||||
if perform_auto_auth:
|
||||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||||
self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id)
|
||||
self.courseware_search_page.visit()
|
||||
self.courseware_search_page.search_for_term(search_term)
|
||||
return search_term in self.courseware_search_page.search_results.html[0]
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that the page is accessible.
|
||||
"""
|
||||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||||
self.courseware_search_page.visit()
|
||||
|
||||
def test_search(self):
|
||||
"""
|
||||
Make sure that you can search for something.
|
||||
@@ -171,12 +187,18 @@ class CoursewareSearchTest(UniqueCourseTest):
|
||||
# Do a search, there should be no results shown.
|
||||
self.assertFalse(self._search_for_content(self.SEARCH_STRING))
|
||||
|
||||
# Do a search in the legacy sidebar, there should be no results shown.
|
||||
self.assertFalse(self._search_for_content_in_sidebar(self.SEARCH_STRING, False))
|
||||
|
||||
# Publish in studio to trigger indexing.
|
||||
self._studio_publish_content(0)
|
||||
|
||||
# Do the search again, this time we expect results.
|
||||
self.assertTrue(self._search_for_content(self.SEARCH_STRING))
|
||||
|
||||
# Do the search again in the legacy sidebar, this time we expect results.
|
||||
self.assertTrue(self._search_for_content_in_sidebar(self.SEARCH_STRING, False))
|
||||
|
||||
@flaky # TNL-5771
|
||||
def test_reindex(self):
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,7 @@ from nose.plugins.attrib import attr
|
||||
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
|
||||
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.pages.common.logout import LogoutPage
|
||||
from common.test.acceptance.pages.lms.courseware_search import CoursewareSearchPage
|
||||
from common.test.acceptance.pages.lms.course_home import CourseHomePage
|
||||
from common.test.acceptance.pages.studio.overview import CourseOutlinePage as StudioCourseOutlinePage
|
||||
from common.test.acceptance.tests.helpers import create_user_partition_json, remove_file
|
||||
from common.test.acceptance.tests.studio.base_studio_test import ContainerBase
|
||||
@@ -38,7 +38,7 @@ class SplitTestCoursewareSearchTest(ContainerBase):
|
||||
super(SplitTestCoursewareSearchTest, self).setUp(is_staff=is_staff)
|
||||
self.staff_user = self.user
|
||||
|
||||
self.courseware_search_page = CoursewareSearchPage(self.browser, self.course_id)
|
||||
self.course_home_page = CourseHomePage(self.browser, self.course_id)
|
||||
self.studio_course_outline = StudioCourseOutlinePage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
@@ -112,19 +112,12 @@ class SplitTestCoursewareSearchTest(ContainerBase):
|
||||
)
|
||||
)
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that the page is accessible.
|
||||
"""
|
||||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||||
self.courseware_search_page.visit()
|
||||
|
||||
def test_search_for_experiment_content_user_assigned_to_one_group(self):
|
||||
"""
|
||||
Test user can search for experiment content restricted to his group
|
||||
when assigned to just one experiment group
|
||||
"""
|
||||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||||
self.courseware_search_page.visit()
|
||||
self.courseware_search_page.search_for_term("VISIBLE TO")
|
||||
assert "1 result" in self.courseware_search_page.search_results.html[0]
|
||||
self.course_home_page.visit()
|
||||
course_search_results_page = self.course_home_page.search_for_term("VISIBLE TO")
|
||||
assert "result-excerpt" in course_search_results_page.search_results.html[0]
|
||||
|
||||
@@ -808,7 +808,7 @@ class SettingsHelpTest(StudioCourseTest):
|
||||
Then Help link should open.
|
||||
And help url should be correct
|
||||
"""
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/setting_up_student_view.html')
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/studio_add_course_information/index.html')
|
||||
|
||||
# Assert that help link is correct.
|
||||
assert_nav_help_link(
|
||||
@@ -880,7 +880,7 @@ class CourseTeamSettingsHelpTest(StudioCourseTest):
|
||||
Then Help link should open.
|
||||
And help url should be correct
|
||||
"""
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/course_staffing.html#add-course-team-members')
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/studio_add_course_information/studio_course_staffing.html')
|
||||
|
||||
# Assert that help link is correct.
|
||||
assert_nav_help_link(
|
||||
@@ -1009,7 +1009,7 @@ class CertificatePageHelpTest(StudioCourseTest):
|
||||
Then Help link should open.
|
||||
And help url should be correct
|
||||
"""
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/creating_course_certificates.html')
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/studio_add_course_information/studio_creating_certificates.html')
|
||||
|
||||
# Assert that help link is correct.
|
||||
assert_nav_help_link(
|
||||
@@ -1027,7 +1027,7 @@ class CertificatePageHelpTest(StudioCourseTest):
|
||||
Then Help link should open.
|
||||
And help url should be correct
|
||||
"""
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/creating_course_certificates.html')
|
||||
expected_url = _get_expected_documentation_url('/set_up_course/studio_add_course_information/studio_creating_certificates.html')
|
||||
|
||||
# Assert that help link is correct.
|
||||
assert_side_bar_help_link(
|
||||
|
||||
@@ -30,7 +30,7 @@ class MemoryUserPartitionScheme(object):
|
||||
"""
|
||||
self.current_group.setdefault(user.id, {})[user_partition.id] = group
|
||||
|
||||
def get_group_for_user(self, course_id, user, user_partition, track_function=None): # pylint: disable=unused-argument
|
||||
def get_group_for_user(self, course_id, user, user_partition): # pylint: disable=unused-argument
|
||||
"""
|
||||
Fetch the group to which this user is linked in this partition, or None.
|
||||
"""
|
||||
|
||||
@@ -348,8 +348,10 @@ class CoursewareIndex(View):
|
||||
'section_title': None,
|
||||
'sequence_title': None,
|
||||
'disable_accordion': waffle.flag_is_active(request, UNIFIED_COURSE_VIEW_FLAG),
|
||||
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
'upgrade_link': check_and_get_upgrade_link(request, self.effective_user, self.course.id),
|
||||
'upgrade_price': get_cosmetic_verified_display_price(self.course),
|
||||
# ENDTODO
|
||||
}
|
||||
table_of_contents = toc_for_course(
|
||||
self.effective_user,
|
||||
|
||||
@@ -336,8 +336,10 @@ def course_info(request, course_id):
|
||||
'show_enroll_banner': show_enroll_banner,
|
||||
'dates_fragment': dates_fragment,
|
||||
'url_to_enroll': url_to_enroll,
|
||||
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
'upgrade_link': check_and_get_upgrade_link(request, user, course.id),
|
||||
'upgrade_price': get_cosmetic_verified_display_price(course),
|
||||
# ENDTODO
|
||||
}
|
||||
|
||||
# Get the URL of the user's last position in order to display the 'where you were last' message
|
||||
@@ -360,6 +362,7 @@ def course_info(request, course_id):
|
||||
UPGRADE_COOKIE_NAME = 'show_upgrade_notification'
|
||||
|
||||
|
||||
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
def check_and_get_upgrade_link(request, user, course_id):
|
||||
upgrade_link = None
|
||||
|
||||
@@ -370,6 +373,7 @@ def check_and_get_upgrade_link(request, user, course_id):
|
||||
request.need_to_set_upgrade_cookie = True
|
||||
|
||||
return upgrade_link
|
||||
# ENDTODO
|
||||
|
||||
|
||||
class StaticCourseTabView(EdxFragmentView):
|
||||
@@ -494,8 +498,10 @@ class CourseTabView(EdxFragmentView):
|
||||
'supports_preview_menu': supports_preview_menu,
|
||||
'uses_pattern_library': True,
|
||||
'disable_courseware_js': True,
|
||||
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
'upgrade_link': check_and_get_upgrade_link(request, request.user, course.id),
|
||||
'upgrade_price': get_cosmetic_verified_display_price(course),
|
||||
# ENDTODO
|
||||
}
|
||||
|
||||
def render_to_fragment(self, request, course=None, page_context=None, **kwargs):
|
||||
@@ -901,8 +907,10 @@ def _progress(request, course_key, student_id):
|
||||
'passed': is_course_passed(course, grade_summary),
|
||||
'credit_course_requirements': _credit_course_requirements(course_key, student),
|
||||
'certificate_data': _get_cert_data(student, course, course_key, is_active, enrollment_mode),
|
||||
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
'upgrade_link': check_and_get_upgrade_link(request, student, course.id),
|
||||
'upgrade_price': get_cosmetic_verified_display_price(course),
|
||||
# ENDTODO
|
||||
}
|
||||
|
||||
with outer_atomic():
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
discussionBoardView.render();
|
||||
threadListView = discussionBoardView.discussionThreadListView;
|
||||
spyOn(threadListView, 'performSearch');
|
||||
discussionBoardView.$el.find('.search-btn').click();
|
||||
discussionBoardView.$el.find('.search-button').click();
|
||||
expect(threadListView.performSearch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
'keydown .forum-nav-browse-filter-input': 'keyboardBinding',
|
||||
'click .forum-nav-browse-menu-wrapper': 'ignoreClick',
|
||||
'keydown .search-input': 'performSearch',
|
||||
'click .search-btn': 'performSearch',
|
||||
'click .search-button': 'performSearch',
|
||||
'topic:selected': 'clearSearch'
|
||||
},
|
||||
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
id="search"
|
||||
placeholder="<%- gettext("Search all posts") %>"
|
||||
/>
|
||||
<button class="btn btn-small search-btn" type="button"><%- gettext("Search") %></button>
|
||||
<button class="btn btn-small search-button" type="button"><%- gettext("Search") %></button>
|
||||
|
||||
@@ -41,7 +41,11 @@ from openedx.core.djangolib.markup import HTML
|
||||
<div class="forum-search"></div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div class="page-content"
|
||||
% if getattr(course, 'language'):
|
||||
lang="${course.language}"
|
||||
% endif
|
||||
>
|
||||
<div class="discussion-body layout layout-1t2t">
|
||||
<aside class="forum-nav layout-col layout-col-a" role="complementary" aria-label="${_("Discussion thread list")}">
|
||||
<%include file="_filter_dropdown.html" />
|
||||
|
||||
@@ -443,8 +443,10 @@ def _create_discussion_board_context(request, course_key, discussion_id=None, th
|
||||
'category_map': course_settings["category_map"],
|
||||
'course_settings': course_settings,
|
||||
'is_commentable_divided': is_commentable_divided(course_key, discussion_id, course_discussion_settings),
|
||||
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
'upgrade_link': check_and_get_upgrade_link(request, user, course.id),
|
||||
'upgrade_price': get_cosmetic_verified_display_price(course),
|
||||
# ENDTODO
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
@@ -600,7 +600,7 @@ class TestGradeReportConditionalContent(TestReportMixin, TestConditionalContent,
|
||||
group_config_hdr_tpl = 'Experiment Group ({})'
|
||||
return {
|
||||
group_config_hdr_tpl.format(self.partition.name): self.partition.scheme.get_group_for_user(
|
||||
self.course.id, user, self.partition, track_function=None
|
||||
self.course.id, user, self.partition
|
||||
).name
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,6 @@ class LmsModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
services['library_tools'] = LibraryToolsService(modulestore())
|
||||
services['partitions'] = PartitionService(
|
||||
course_id=kwargs.get('course_id'),
|
||||
track_function=kwargs.get('track_function', None),
|
||||
cache=request_cache_dict
|
||||
)
|
||||
store = modulestore()
|
||||
|
||||
@@ -2230,6 +2230,7 @@ INSTALLED_APPS = (
|
||||
# Features
|
||||
'openedx.features.course_bookmarks',
|
||||
'openedx.features.course_experience',
|
||||
'openedx.features.course_search',
|
||||
'openedx.features.enterprise_support',
|
||||
)
|
||||
|
||||
|
||||
1
lms/static/course_search
Symbolic link
1
lms/static/course_search
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openedx/features/course_search/static/course_search
|
||||
@@ -1,10 +0,0 @@
|
||||
<div id="courseware-search-bar" class="search-bar" role="search" aria-label="Course">
|
||||
<form>
|
||||
<label for="course-search-input" class="sr">Course Search</label>
|
||||
<input id="course-search-input" type="text" class="search-field"/>
|
||||
<button type="submit" class="search-button">Search</button>
|
||||
<button type="button" class="cancel-button" title="Clear search">
|
||||
<span class="icon fa fa-remove" aria-hidden="true"></span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
<div id="dashboard-search-bar" class="search-bar" role="search" aria-label="Dashboard">
|
||||
<form>
|
||||
<label for="dashboard-search-input">Search Your Courses</label>
|
||||
<div>
|
||||
<input id="dashboard-search-input" type="text" class="search-field"/>
|
||||
<button type="submit" class="search-button" aria-label="Search">
|
||||
<span class="icon fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="cancel-button" aria-label="Clear search">
|
||||
<span class="icon fa fa-remove" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -7,11 +7,15 @@
|
||||
function OpenResponseAssessmentBlock($section) {
|
||||
this.$section = $section;
|
||||
this.$section.data('wrapper', this);
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
OpenResponseAssessmentBlock.prototype.onClickTitle = function() {
|
||||
var block = this.$section.find('.open-response-assessment');
|
||||
XBlock.initializeBlock($(block).find('.xblock')[0]);
|
||||
if (!this.initialized) {
|
||||
this.initialized = true;
|
||||
XBlock.initializeBlock($(block).find('.xblock')[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return OpenResponseAssessmentBlock;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_form'
|
||||
], function(SearchForm) {
|
||||
'use strict';
|
||||
|
||||
return SearchForm.extend({
|
||||
el: '#courseware-search-bar'
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,11 +0,0 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_item_view'
|
||||
], function(SearchItemView) {
|
||||
'use strict';
|
||||
|
||||
return SearchItemView.extend({
|
||||
templateId: '#course_search_item-tpl'
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,11 +0,0 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_form'
|
||||
], function(SearchForm) {
|
||||
'use strict';
|
||||
|
||||
return SearchForm.extend({
|
||||
el: '#dashboard-search-bar'
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,11 +0,0 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_item_view'
|
||||
], function(SearchItemView) {
|
||||
'use strict';
|
||||
|
||||
return SearchItemView.extend({
|
||||
templateId: '#dashboard_search_item-tpl'
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,29 +0,0 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_results_view',
|
||||
'js/search/dashboard/views/search_item_view'
|
||||
], function(SearchResultsView, DashSearchItemView) {
|
||||
'use strict';
|
||||
|
||||
return SearchResultsView.extend({
|
||||
|
||||
el: '#dashboard-search-results',
|
||||
contentElement: '#my-courses, #profile-sidebar',
|
||||
resultsTemplateId: '#dashboard_search_results-tpl',
|
||||
loadingTemplateId: '#search_loading-tpl',
|
||||
errorTemplateId: '#search_error-tpl',
|
||||
events: {
|
||||
'click .search-load-next': 'loadNext',
|
||||
'click .search-back-to-courses': 'backToCourses'
|
||||
},
|
||||
SearchItemView: DashSearchItemView,
|
||||
|
||||
backToCourses: function() {
|
||||
this.clear();
|
||||
this.trigger('reset');
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
@@ -28,6 +28,7 @@ var options = {
|
||||
sourceFiles: [
|
||||
{pattern: 'coffee/src/**/!(*spec).js'},
|
||||
{pattern: 'course_bookmarks/**/!(*spec).js'},
|
||||
{pattern: 'course_search/**/!(*spec).js'},
|
||||
{pattern: 'discussion/js/**/!(*spec).js'},
|
||||
{pattern: 'js/**/!(*spec|djangojs).js'},
|
||||
{pattern: 'lms/js/**/!(*spec).js'},
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
*/
|
||||
modules: getModulesList([
|
||||
'course_bookmarks/js/course_bookmarks_factory',
|
||||
'course_search/js/course_search_factory',
|
||||
'course_search/js/dashboard_search_factory',
|
||||
'discussion/js/discussion_board_factory',
|
||||
'discussion/js/discussion_profile_page_factory',
|
||||
'js/api_admin/catalog_preview_factory',
|
||||
@@ -32,8 +34,6 @@
|
||||
'js/header_factory',
|
||||
'js/learner_dashboard/program_details_factory',
|
||||
'js/learner_dashboard/program_list_factory',
|
||||
'js/search/course/course_search_factory',
|
||||
'js/search/dashboard/dashboard_search_factory',
|
||||
'js/student_account/logistration_factory',
|
||||
'js/student_account/views/account_settings_factory',
|
||||
'js/student_account/views/finish_auth_factory',
|
||||
|
||||
@@ -676,6 +676,7 @@
|
||||
'course_bookmarks/js/spec/bookmark_button_view_spec.js',
|
||||
'course_bookmarks/js/spec/bookmarks_list_view_spec.js',
|
||||
'course_bookmarks/js/spec/course_bookmarks_factory_spec.js',
|
||||
'course_search/js/spec/course_search_spec.js',
|
||||
'discussion/js/spec/discussion_board_factory_spec.js',
|
||||
'discussion/js/spec/discussion_profile_page_factory_spec.js',
|
||||
'discussion/js/spec/discussion_board_view_spec.js',
|
||||
@@ -749,7 +750,6 @@
|
||||
'js/spec/markdown_editor_spec.js',
|
||||
'js/spec/dateutil_factory_spec.js',
|
||||
'js/spec/navigation_spec.js',
|
||||
'js/spec/search/search_spec.js',
|
||||
'js/spec/shoppingcart/shoppingcart_spec.js',
|
||||
'js/spec/staff_debug_actions_spec.js',
|
||||
'js/spec/student_account/access_spec.js',
|
||||
|
||||
@@ -26,3 +26,4 @@
|
||||
// Features
|
||||
@import 'features/bookmarks';
|
||||
@import 'features/course-experience';
|
||||
@import 'features/course-search';
|
||||
|
||||
70
lms/static/sass/features/_course-search.scss
Normal file
70
lms/static/sass/features/_course-search.scss
Normal file
@@ -0,0 +1,70 @@
|
||||
// Styles for course search results
|
||||
|
||||
.search-results {
|
||||
.search-result-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.search-results-title {
|
||||
display: inline-block;
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.search-count {
|
||||
@include float(right);
|
||||
color: $lms-gray;
|
||||
}
|
||||
|
||||
.search-results-item {
|
||||
@include padding-right(140px);
|
||||
position: relative;
|
||||
border-top: 1px solid $border-color;
|
||||
padding: $baseline ($baseline/2);
|
||||
list-style-type: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $lms-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
.result-excerpt {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.result-location {
|
||||
display: block;
|
||||
color: $lms-gray;
|
||||
font-size: 14px;
|
||||
padding-top: ($baseline/2);
|
||||
}
|
||||
|
||||
.result-link {
|
||||
@include right($baseline/2);
|
||||
position: absolute;
|
||||
top: $baseline;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.result-type {
|
||||
@include right($baseline/2);
|
||||
position: absolute;
|
||||
color: $lms-gray;
|
||||
font-size: 14px;
|
||||
bottom: $baseline;
|
||||
}
|
||||
|
||||
.search-load-next {
|
||||
display: block;
|
||||
margin-top: $baseline;
|
||||
}
|
||||
}
|
||||
@@ -62,12 +62,8 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.search-field {
|
||||
transition: all $tmg-f2 ease-in-out;
|
||||
border: 1px solid $lms-border-color;
|
||||
border-radius: 3px;
|
||||
padding: $baseline/4 $baseline*1.5;
|
||||
font-family: inherit;
|
||||
.search-input {
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.action-search {
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
vertical-align: text-bottom;
|
||||
|
||||
.form-actions {
|
||||
@include margin-left($baseline/2);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,6 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
|
||||
% for template_name in ["course_search_item", "course_search_results", "search_loading", "search_error"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="search/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
% endif
|
||||
|
||||
% if include_special_exams is not UNDEFINED and include_special_exams:
|
||||
% for template_name in ["proctored-exam-status"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
@@ -81,9 +73,12 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C
|
||||
<%include file="/mathjax_include.html" args="disable_fast_preview=True"/>
|
||||
|
||||
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
|
||||
<%static:require_module module_name="js/search/course/course_search_factory" class_name="CourseSearchFactory">
|
||||
<%static:require_module module_name="course_search/js/course_search_factory" class_name="CourseSearchFactory">
|
||||
var courseId = $('.courseware-results').data('courseId');
|
||||
CourseSearchFactory(courseId);
|
||||
CourseSearchFactory({
|
||||
courseId: courseId,
|
||||
searchHeader: $('.search-bar')
|
||||
});
|
||||
</%static:require_module>
|
||||
% endif
|
||||
|
||||
@@ -111,7 +106,11 @@ ${HTML(fragment.foot_html())}
|
||||
<%include file="/courseware/course_navigation.html" args="active_page='courseware'" />
|
||||
% endif
|
||||
|
||||
<div class="container">
|
||||
<div class="container"
|
||||
% if getattr(course, 'language'):
|
||||
lang="${course.language}"
|
||||
% endif
|
||||
>
|
||||
<div class="course-wrapper" role="presentation">
|
||||
|
||||
% if disable_accordion is UNDEFINED or not disable_accordion:
|
||||
@@ -127,7 +126,7 @@ ${HTML(fragment.foot_html())}
|
||||
|
||||
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
|
||||
<div id="courseware-search-bar" class="search-bar courseware-search-bar" role="search" aria-label="Course">
|
||||
<form>
|
||||
<form class="search-form">
|
||||
<label for="course-search-input" class="sr">${_('Course Search')}</label>
|
||||
<div class="search-field-wrapper">
|
||||
<input id="course-search-input" type="text" class="search-field"/>
|
||||
|
||||
@@ -58,7 +58,11 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
<%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}</%block>
|
||||
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
<div class="container">
|
||||
<div class="container"
|
||||
% if getattr(course, 'language'):
|
||||
lang="${course.language}"
|
||||
% endif
|
||||
>
|
||||
<div class="home">
|
||||
<div class="page-header-main">
|
||||
<h2 class="hd hd-2 page-title">${_("Welcome to {org}'s {course_name}!").format(org=course.display_org_with_default, course_name=course.display_number_with_default)}
|
||||
|
||||
@@ -42,7 +42,11 @@ from django.utils.http import urlquote_plus
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
<div class="container">
|
||||
<div class="profile-wrapper">
|
||||
<section class="course-info" id="course-info-progress">
|
||||
<section class="course-info" id="course-info-progress"
|
||||
% if getattr(course, 'language'):
|
||||
lang="${course.language}"
|
||||
% endif
|
||||
>
|
||||
% if staff_access and studio_url is not None:
|
||||
<div class="wrap-instructor-info">
|
||||
<a class="instructor-info-action studio-view" href="${studio_url}">${_("View Grading in studio")}</a>
|
||||
|
||||
@@ -25,6 +25,11 @@ ${HTML(fragment.foot_html())}
|
||||
<%include file="/courseware/course_navigation.html" args="active_page=active_page" />
|
||||
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
<section class="container"
|
||||
% if getattr(course, 'language'):
|
||||
lang=${course.language}
|
||||
% endif
|
||||
>
|
||||
<section class="container">
|
||||
<div class="static_tab_wrapper">
|
||||
${HTML(fragment.body_html())}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
## TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
<%page expression_filter="h"/>
|
||||
|
||||
% if upgrade_link:
|
||||
@@ -7,3 +8,4 @@
|
||||
data-price="${upgrade_price}">
|
||||
</script>
|
||||
% endif
|
||||
## ENDTODO
|
||||
|
||||
@@ -28,12 +28,6 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
<%static:include path="dashboard/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% for template_name in ["dashboard_search_item", "dashboard_search_results", "search_loading", "search_error"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="search/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
@@ -49,7 +43,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
});
|
||||
</script>
|
||||
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
|
||||
<%static:require_module module_name="js/search/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory">
|
||||
<%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory">
|
||||
DashboardSearchFactory();
|
||||
</%static:require_module>
|
||||
% endif
|
||||
@@ -165,7 +159,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
|
||||
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
|
||||
<div id="dashboard-search-bar" class="search-bar dashboard-search-bar" role="search" aria-label="Dashboard">
|
||||
<form>
|
||||
<form class="search-form">
|
||||
<label for="dashboard-search-input">${_('Search Your Courses')}</label>
|
||||
<div class="search-field-wrapper">
|
||||
<input id="dashboard-search-input" type="text" class="search-field"/>
|
||||
|
||||
@@ -54,7 +54,11 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
|
||||
% else:
|
||||
<% mode_class = '' %>
|
||||
% endif
|
||||
<div class="course-container">
|
||||
<div class="course-container"
|
||||
% if getattr(course_overview, 'language'):
|
||||
lang="${course_overview.language}"
|
||||
% endif
|
||||
>
|
||||
<article class="course${mode_class}">
|
||||
<% course_target = reverse(course_home_url_name(course_overview.id), args=[unicode(course_overview.id)]) %>
|
||||
<section class="details" aria-labelledby="details-heading-${course_overview.number}">
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<div class="search-info">
|
||||
<%= gettext("Search Results") %>
|
||||
<div class="search-count"><%= totalCountMsg %></div>
|
||||
</div>
|
||||
|
||||
<% if (totalCount > 0 ) { %>
|
||||
|
||||
<ol class='search-result-list'></ol>
|
||||
|
||||
<% if (hasMoreResults) { %>
|
||||
<a class="search-load-next" href="#">
|
||||
<%= interpolate(
|
||||
ngettext("Load next %(num_items)s result", "Load next %(num_items)s results", pageSize),
|
||||
{ num_items: pageSize },
|
||||
true
|
||||
) %>
|
||||
<span class="icon fa fa-spinner fa-spin" aria-hidden="true"></span>
|
||||
</a>
|
||||
<% } %>
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<p><%= gettext("Sorry, no results were found.") %></p>
|
||||
|
||||
<% } %>
|
||||
@@ -1,26 +0,0 @@
|
||||
<header class="search-info">
|
||||
<a class="search-back-to-courses" href="#"><%= gettext("Back to Dashboard") %></a>
|
||||
<h2><%= gettext("Search Results") %></h2>
|
||||
<div class="search-count"><%= totalCountMsg %></div>
|
||||
</header>
|
||||
|
||||
<% if (totalCount > 0 ) { %>
|
||||
|
||||
<ol class='search-result-list'></ol>
|
||||
|
||||
<% if (hasMoreResults) { %>
|
||||
<a class="search-load-next" href="#">
|
||||
<%= interpolate(
|
||||
ngettext("Load next %(num_items)s result", "Load next %(num_items)s results", pageSize),
|
||||
{ num_items: pageSize },
|
||||
true
|
||||
) %>
|
||||
<span class="icon fa fa-spinner fa-spin" aria-hidden="true"></span>
|
||||
</a>
|
||||
<% } %>
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<p><%= gettext("Sorry, no results were found.") %></p>
|
||||
|
||||
<% } %>
|
||||
@@ -56,7 +56,10 @@
|
||||
id="search"
|
||||
placeholder="Search all the things"
|
||||
/>
|
||||
<button class="btn btn-small search-btn" type="button">Search</button>
|
||||
<button type="button" class="action action-clear" aria-label="Clear search">
|
||||
<span class="icon fa fa-times-circle" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button class="btn btn-small search-button" type="button">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,11 @@
|
||||
<div class="container">
|
||||
<div class="wiki-wrapper">
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
<section class="wiki {{ selected_tab }}" id="wiki-content">
|
||||
<section class="wiki {{ selected_tab }}" id="wiki-content"
|
||||
{% if request.course.language %}
|
||||
lang="{{ request.course.language }}"
|
||||
{% endif %}
|
||||
>
|
||||
{% block wiki_body %}
|
||||
|
||||
{% block wiki_breadcrumbs %}{% endblock %}
|
||||
|
||||
@@ -636,6 +636,14 @@ urlpatterns += (
|
||||
),
|
||||
include('openedx.features.course_bookmarks.urls'),
|
||||
),
|
||||
|
||||
# Course search
|
||||
url(
|
||||
r'^courses/{}/search/'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
include('openedx.features.course_search.urls'),
|
||||
),
|
||||
)
|
||||
|
||||
if settings.FEATURES["ENABLE_TEAMS"]:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('course_overviews', '0012_courseoverview_eligible_for_financial_aid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='courseoverview',
|
||||
name='language',
|
||||
field=models.TextField(null=True),
|
||||
),
|
||||
]
|
||||
@@ -101,6 +101,8 @@ class CourseOverview(TimeStampedModel):
|
||||
marketing_url = TextField(null=True)
|
||||
eligible_for_financial_aid = BooleanField(default=True)
|
||||
|
||||
language = TextField(null=True)
|
||||
|
||||
@classmethod
|
||||
def _create_or_update(cls, course):
|
||||
"""
|
||||
@@ -190,6 +192,8 @@ class CourseOverview(TimeStampedModel):
|
||||
course_overview.course_video_url = CourseDetails.fetch_video_url(course.id)
|
||||
course_overview.self_paced = course.self_paced
|
||||
|
||||
course_overview.language = course.language
|
||||
|
||||
return course_overview
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -25,7 +25,7 @@ class CohortPartitionScheme(object):
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@classmethod
|
||||
def get_group_for_user(cls, course_key, user, user_partition, track_function=None, use_cached=True):
|
||||
def get_group_for_user(cls, course_key, user, user_partition, use_cached=True):
|
||||
"""
|
||||
Returns the Group from the specified user partition to which the user
|
||||
is assigned, via their cohort membership and any mappings from cohorts
|
||||
|
||||
@@ -5,6 +5,8 @@ import logging
|
||||
import random
|
||||
import course_tag.api as course_tag_api
|
||||
|
||||
from eventtracking import tracker
|
||||
|
||||
from xmodule.partitions.partitions import UserPartitionError, NoSuchUserPartitionGroupError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -17,7 +19,7 @@ class NotImplementedPartitionScheme(object):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_group_for_user(cls, course_key, user, user_partition, assign=True, track_function=None): # pylint: disable=unused-argument
|
||||
def get_group_for_user(cls, course_key, user, user_partition, assign=True): # pylint: disable=unused-argument
|
||||
"""
|
||||
Returning None is equivalent to saying "This user is not in any groups
|
||||
using this partition scheme", be sure the scheme you're removing is
|
||||
@@ -31,7 +33,7 @@ class ReturnGroup1PartitionScheme(object):
|
||||
This scheme is needed to allow verification partitions to be killed, see EDUCATOR-199
|
||||
"""
|
||||
@classmethod
|
||||
def get_group_for_user(cls, course_key, user, user_partition, assign=True, track_function=None): # pylint: disable=unused-argument
|
||||
def get_group_for_user(cls, course_key, user, user_partition, assign=True): # pylint: disable=unused-argument
|
||||
"""
|
||||
The previous "allow" definition for verification was defined as 1, so return that.
|
||||
Details at https://github.com/edx/edx-platform/pull/14913/files#diff-feff1466ec4d1b8c38894310d8342a80
|
||||
@@ -46,7 +48,7 @@ class RandomUserPartitionScheme(object):
|
||||
RANDOM = random.Random()
|
||||
|
||||
@classmethod
|
||||
def get_group_for_user(cls, course_key, user, user_partition, assign=True, track_function=None):
|
||||
def get_group_for_user(cls, course_key, user, user_partition, assign=True):
|
||||
"""
|
||||
Returns the group from the specified user position to which the user is assigned.
|
||||
If the user has not yet been assigned, a group will be randomly chosen for them if assign flag is True.
|
||||
@@ -83,20 +85,24 @@ class RandomUserPartitionScheme(object):
|
||||
# persist the value as a course tag
|
||||
course_tag_api.set_course_tag(user, course_key, partition_key, group.id)
|
||||
|
||||
if track_function:
|
||||
# emit event for analytics
|
||||
# FYI - context is always user ID that is logged in, NOT the user id that is
|
||||
# being operated on. If instructor can move user explicitly, then we should
|
||||
# put in event_info the user id that is being operated on.
|
||||
event_info = {
|
||||
'group_id': group.id,
|
||||
'group_name': group.name,
|
||||
'partition_id': user_partition.id,
|
||||
'partition_name': user_partition.name
|
||||
}
|
||||
# pylint: disable=fixme
|
||||
# TODO: Use the XBlock publish api instead
|
||||
track_function('xmodule.partitions.assigned_user_to_partition', event_info)
|
||||
# emit event for analytics
|
||||
# FYI - context is always user ID that is logged in, NOT the user id that is
|
||||
# being operated on. If instructor can move user explicitly, then we should
|
||||
# put in event_info the user id that is being operated on.
|
||||
event_name = 'xmodule.partitions.assigned_user_to_partition'
|
||||
event_info = {
|
||||
'group_id': group.id,
|
||||
'group_name': group.name,
|
||||
'partition_id': user_partition.id,
|
||||
'partition_name': user_partition.name
|
||||
}
|
||||
# pylint: disable=fixme
|
||||
# TODO: Use the XBlock publish api instead
|
||||
with tracker.get_tracker().context(event_name, {}):
|
||||
tracker.emit(
|
||||
event_name,
|
||||
event_info,
|
||||
)
|
||||
|
||||
return group
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Defines URLs for the course experience.
|
||||
Defines URLs for course bookmarks.
|
||||
"""
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
@@ -28,16 +28,16 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG
|
||||
<div class="page-header-secondary">
|
||||
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
|
||||
<div class="page-header-search">
|
||||
<form class="search-form" role="search">
|
||||
<form class="search-form" role="search" action="${reverse('openedx.course_search.course_search_results', args=[course_key])}">
|
||||
<label class="field-label sr-only" for="search" id="search-hint">${_('Search the course')}</label>
|
||||
<input
|
||||
class="field-input input-text search-input"
|
||||
type="search"
|
||||
name="search"
|
||||
name="query"
|
||||
id="search"
|
||||
placeholder="${_('Search the course')}"
|
||||
/>
|
||||
<button class="btn btn-small search-btn" type="button">${_('Search')}</button>
|
||||
<button class="btn btn-small search-button" type="submit">${_('Search')}</button>
|
||||
</form>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
@@ -113,7 +113,6 @@ class CourseHomeFragmentView(EdxFragmentView):
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
'course_key': course_key,
|
||||
'course': course,
|
||||
'outline_fragment': outline_fragment,
|
||||
'handouts_html': handouts_html,
|
||||
'has_visited_course': has_visited_course,
|
||||
|
||||
@@ -32,8 +32,10 @@ class CourseOutlineFragmentView(EdxFragmentView):
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course_overview,
|
||||
'blocks': course_block_tree,
|
||||
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
|
||||
'upgrade_link': check_and_get_upgrade_link(request, request.user, course_key),
|
||||
'upgrade_price': get_cosmetic_verified_display_price(course_overview),
|
||||
# ENDTODO
|
||||
}
|
||||
html = render_to_string('course_experience/course-outline-fragment.html', context)
|
||||
return Fragment(html)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="course-view container" id="course-container">
|
||||
<div id="courseware-search-bar" class="search-bar" role="search" aria-label="Course">
|
||||
<form class="search-form">
|
||||
<label for="course-search-input" class="sr">Course Search</label>
|
||||
<input id="course-search-input" type="text" class="search-field"/>
|
||||
<button type="submit" class="search-button">Search</button>
|
||||
<button type="button" class="cancel-button" title="Clear search">
|
||||
<span class="icon fa fa-remove" aria-hidden="true"></span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="search-results courseware-results">
|
||||
<section id="course-content"></section>
|
||||
</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
<div class="course-view container" id="course-container">
|
||||
<header class="page-header has-secondary">
|
||||
<div class="page-header-main">
|
||||
<nav aria-label="My Bookmarks" class="sr-is-focusable" tabindex="-1">
|
||||
<div class="has-breadcrumbs">
|
||||
<div class="breadcrumbs">
|
||||
<span class="nav-item">
|
||||
<a href="/course">Course</a>
|
||||
</span>
|
||||
<span class="icon fa fa-angle-right" aria-hidden="true"></span>
|
||||
<span class="nav-item">Search Results</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="page-header-secondary">
|
||||
<div class="page-header-search">
|
||||
<form class="search-form" role="search" action="/search">
|
||||
<label class="field-label sr-only" for="search" id="search-hint">Search the course</label>
|
||||
<input
|
||||
class="field-input input-text search-field"
|
||||
type="search"
|
||||
name="query"
|
||||
id="search"
|
||||
value="query"
|
||||
placeholder="Search the course"
|
||||
/>
|
||||
<button class="btn btn-small search-button" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<main class="search-results">
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="course-view container" id="course-container">
|
||||
<div id="dashboard-search-bar" class="search-bar" role="search" aria-label="Dashboard">
|
||||
<form class="search-form">
|
||||
<label for="dashboard-search-input">Search Your Courses</label>
|
||||
<div>
|
||||
<input id="dashboard-search-input" type="text" class="search-field"/>
|
||||
<button type="submit" class="search-button" aria-label="Search">
|
||||
<span class="icon fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="cancel-button" aria-label="Clear search">
|
||||
<span class="icon fa fa-remove" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<section id="dashboard-search-results" class="search-results dashboard-search-results"></section>
|
||||
<section id="my-courses" tabindex="-1"></section>
|
||||
</div>
|
||||
@@ -1,10 +1,11 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'backbone',
|
||||
'js/search/base/models/search_result'
|
||||
], function(Backbone, SearchResult) {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'underscore',
|
||||
'backbone',
|
||||
'course_search/js/models/search_result'
|
||||
], function(_, Backbone, SearchResult) {
|
||||
return Backbone.Collection.extend({
|
||||
|
||||
model: SearchResult,
|
||||
@@ -26,7 +27,9 @@
|
||||
},
|
||||
|
||||
performSearch: function(searchTerm) {
|
||||
this.fetchXhr && this.fetchXhr.abort();
|
||||
if (this.fetchXhr) {
|
||||
this.fetchXhr.abort();
|
||||
}
|
||||
this.searchTerm = searchTerm || '';
|
||||
this.resetState();
|
||||
this.fetchXhr = this.fetch({
|
||||
@@ -36,17 +39,19 @@
|
||||
page_index: 0
|
||||
},
|
||||
type: 'POST',
|
||||
success: function(self, xhr) {
|
||||
success: function(self) {
|
||||
self.trigger('search');
|
||||
},
|
||||
error: function(self, xhr) {
|
||||
error: function(self) {
|
||||
self.trigger('error');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadNextPage: function() {
|
||||
this.fetchXhr && this.fetchXhr.abort();
|
||||
if (this.fetchXhr) {
|
||||
this.fetchXhr.abort();
|
||||
}
|
||||
this.fetchXhr = this.fetch({
|
||||
data: {
|
||||
search_string: this.searchTerm,
|
||||
@@ -54,11 +59,11 @@
|
||||
page_index: this.page + 1
|
||||
},
|
||||
type: 'POST',
|
||||
success: function(self, xhr) {
|
||||
self.page += 1;
|
||||
success: function(self) {
|
||||
self.page += 1; // eslint-disable-line no-param-reassign
|
||||
self.trigger('next');
|
||||
},
|
||||
error: function(self, xhr) {
|
||||
error: function(self) {
|
||||
self.trigger('error');
|
||||
},
|
||||
add: true,
|
||||
@@ -68,7 +73,9 @@
|
||||
},
|
||||
|
||||
cancelSearch: function() {
|
||||
this.fetchXhr && this.fetchXhr.abort();
|
||||
if (this.fetchXhr) {
|
||||
this.fetchXhr.abort();
|
||||
}
|
||||
this.resetState();
|
||||
},
|
||||
|
||||
@@ -101,4 +108,4 @@
|
||||
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,14 +1,22 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone', 'js/search/base/routers/search_router', 'js/search/course/views/search_form',
|
||||
'js/search/base/collections/search_collection', 'js/search/course/views/search_results_view'],
|
||||
function(Backbone, SearchRouter, CourseSearchForm, SearchCollection, SearchResultsView) {
|
||||
return function(courseId) {
|
||||
define([
|
||||
'underscore', 'backbone', 'course_search/js/search_router', 'course_search/js/views/search_form',
|
||||
'course_search/js/collections/search_collection', 'course_search/js/views/course_search_results_view'
|
||||
],
|
||||
function(_, Backbone, SearchRouter, CourseSearchForm, SearchCollection, CourseSearchResultsView) {
|
||||
return function(options) {
|
||||
var courseId = options.courseId;
|
||||
var requestedQuery = options.query;
|
||||
var supportsActive = options.supportsActive;
|
||||
var router = new SearchRouter();
|
||||
var form = new CourseSearchForm();
|
||||
var form = new CourseSearchForm({
|
||||
el: options.searchHeader,
|
||||
supportsActive: supportsActive
|
||||
});
|
||||
var collection = new SearchCollection([], {courseId: courseId});
|
||||
var results = new SearchResultsView({collection: collection});
|
||||
var results = new CourseSearchResultsView({collection: collection});
|
||||
var dispatcher = _.clone(Backbone.Events);
|
||||
|
||||
dispatcher.listenTo(router, 'search', function(query) {
|
||||
@@ -42,6 +50,11 @@
|
||||
dispatcher.listenTo(collection, 'error', function() {
|
||||
results.showErrorMessage();
|
||||
});
|
||||
|
||||
// Perform a search if an initial query has been provided.
|
||||
if (requestedQuery) {
|
||||
router.trigger('search', requestedQuery);
|
||||
}
|
||||
};
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,14 +1,18 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone', 'js/search/base/routers/search_router', 'js/search/dashboard/views/search_form',
|
||||
'js/search/base/collections/search_collection', 'js/search/dashboard/views/search_results_view'],
|
||||
function(Backbone, SearchRouter, SearchForm, SearchCollection, SearchListView) {
|
||||
define([
|
||||
'underscore', 'backbone', 'course_search/js/search_router', 'course_search/js/views/search_form',
|
||||
'course_search/js/collections/search_collection', 'course_search/js/views/dashboard_search_results_view'
|
||||
],
|
||||
function(_, Backbone, SearchRouter, SearchForm, SearchCollection, DashboardSearchResultsView) {
|
||||
return function() {
|
||||
var router = new SearchRouter();
|
||||
var form = new SearchForm();
|
||||
var form = new SearchForm({
|
||||
el: $('#dashboard-search-bar')
|
||||
});
|
||||
var collection = new SearchCollection([]);
|
||||
var results = new SearchListView({collection: collection});
|
||||
var results = new DashboardSearchResultsView({collection: collection});
|
||||
var dispatcher = _.clone(Backbone.Events);
|
||||
|
||||
dispatcher.listenTo(router, 'search', function(query) {
|
||||
@@ -48,4 +52,4 @@
|
||||
});
|
||||
};
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(define) {
|
||||
define(['backbone'], function(Backbone) {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
define(['backbone'], function(Backbone) {
|
||||
return Backbone.Model.extend({
|
||||
defaults: {
|
||||
location: [],
|
||||
@@ -11,4 +11,4 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(define) {
|
||||
define(['backbone'], function(Backbone) {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
define(['backbone'], function(Backbone) {
|
||||
return Backbone.Router.extend({
|
||||
routes: {
|
||||
'search/:query': 'search'
|
||||
@@ -11,4 +11,4 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -5,17 +5,16 @@ define([
|
||||
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
|
||||
'common/js/spec_helpers/page_helpers',
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'js/search/base/models/search_result',
|
||||
'js/search/base/collections/search_collection',
|
||||
'js/search/base/routers/search_router',
|
||||
'js/search/course/views/search_item_view',
|
||||
'js/search/dashboard/views/search_item_view',
|
||||
'js/search/course/views/search_form',
|
||||
'js/search/dashboard/views/search_form',
|
||||
'js/search/course/views/search_results_view',
|
||||
'js/search/dashboard/views/search_results_view',
|
||||
'js/search/course/course_search_factory',
|
||||
'js/search/dashboard/dashboard_search_factory'
|
||||
'course_search/js/models/search_result',
|
||||
'course_search/js/collections/search_collection',
|
||||
'course_search/js/search_router',
|
||||
'course_search/js/views/search_form',
|
||||
'course_search/js/views/search_item_view',
|
||||
'course_search/js/views/course_search_results_view',
|
||||
'course_search/js/views/dashboard_search_results_view',
|
||||
'course_search/js/course_search_factory',
|
||||
'course_search/js/dashboard_search_factory',
|
||||
'text!course_search/templates/course_search_item.underscore'
|
||||
], function(
|
||||
$,
|
||||
Backbone,
|
||||
@@ -26,18 +25,17 @@ define([
|
||||
SearchResult,
|
||||
SearchCollection,
|
||||
SearchRouter,
|
||||
CourseSearchItemView,
|
||||
DashSearchItemView,
|
||||
CourseSearchForm,
|
||||
DashSearchForm,
|
||||
SearchForm,
|
||||
SearchItemView,
|
||||
CourseSearchResultsView,
|
||||
DashSearchResultsView,
|
||||
CourseSearchFactory,
|
||||
DashboardSearchFactory
|
||||
DashboardSearchFactory,
|
||||
courseSearchItemTemplate
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
describe('Search', function() {
|
||||
describe('Course Search', function() {
|
||||
beforeEach(function() {
|
||||
PageHelpers.preventBackboneChangingUrl();
|
||||
});
|
||||
@@ -86,7 +84,6 @@ define([
|
||||
|
||||
it('sends a request and parses the json result', function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.collection.performSearch('search string');
|
||||
var response = {
|
||||
total: 2,
|
||||
access_denied_count: 1,
|
||||
@@ -99,6 +96,7 @@ define([
|
||||
}
|
||||
}]
|
||||
};
|
||||
this.collection.performSearch('search string');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
|
||||
expect(this.onSearch).toHaveBeenCalled();
|
||||
@@ -221,12 +219,7 @@ define([
|
||||
|
||||
|
||||
describe('SearchItemView', function() {
|
||||
function beforeEachHelper(SearchItemView) {
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/search/course_search_item',
|
||||
'templates/search/dashboard_search_item'
|
||||
]);
|
||||
|
||||
beforeEach(function() {
|
||||
this.model = new SearchResult({
|
||||
location: ['section', 'subsection', 'unit'],
|
||||
content_type: 'Video',
|
||||
@@ -243,31 +236,37 @@ define([
|
||||
url: 'path/to/content'
|
||||
});
|
||||
|
||||
this.item = new SearchItemView({model: this.model});
|
||||
this.item = new SearchItemView({
|
||||
model: this.model,
|
||||
template: courseSearchItemTemplate
|
||||
});
|
||||
this.item.render();
|
||||
this.seqItem = new SearchItemView({model: this.seqModel});
|
||||
this.seqItem = new SearchItemView({
|
||||
model: this.seqModel,
|
||||
template: courseSearchItemTemplate
|
||||
});
|
||||
this.seqItem.render();
|
||||
}
|
||||
});
|
||||
|
||||
function rendersItem() {
|
||||
it('rendersItem', function() {
|
||||
expect(this.item.$el).toHaveAttr('role', 'region');
|
||||
expect(this.item.$el).toHaveAttr('aria-label', 'search result');
|
||||
expect(this.item.$el).toContainElement('a[href="' + this.model.get('url') + '"]');
|
||||
expect(this.item.$el.find('.result-type')).toContainHtml(this.model.get('content_type'));
|
||||
expect(this.item.$el.find('.result-excerpt')).toContainHtml(this.model.get('excerpt'));
|
||||
expect(this.item.$el.find('.result-location')).toContainHtml('section ▸ subsection ▸ unit');
|
||||
}
|
||||
});
|
||||
|
||||
function rendersSequentialItem() {
|
||||
it('rendersSequentialItem', function() {
|
||||
expect(this.seqItem.$el).toHaveAttr('role', 'region');
|
||||
expect(this.seqItem.$el).toHaveAttr('aria-label', 'search result');
|
||||
expect(this.seqItem.$el).toContainElement('a[href="' + this.seqModel.get('url') + '"]');
|
||||
expect(this.seqItem.$el.find('.result-type')).toBeEmpty();
|
||||
expect(this.seqItem.$el.find('.result-excerpt')).toBeEmpty();
|
||||
expect(this.seqItem.$el.find('.result-location')).toContainHtml('section ▸ subsection');
|
||||
}
|
||||
});
|
||||
|
||||
function logsSearchItemViewEvent() {
|
||||
it('logsSearchItemViewEvent', function() {
|
||||
this.model.collection = new SearchCollection([this.model], {course_id: 'edx101'});
|
||||
this.item.render();
|
||||
// Mock the redirect call
|
||||
@@ -277,27 +276,6 @@ define([
|
||||
expect(this.item.redirect).toHaveBeenCalled();
|
||||
this.item.$el.trigger('click');
|
||||
expect(this.item.redirect).toHaveBeenCalled();
|
||||
}
|
||||
|
||||
describe('CourseSearchItemView', function() {
|
||||
beforeEach(function() {
|
||||
beforeEachHelper.call(this, CourseSearchItemView);
|
||||
});
|
||||
it('renders items correctly', rendersItem);
|
||||
it('renders Sequence items correctly', rendersSequentialItem);
|
||||
it('logs view event', logsSearchItemViewEvent);
|
||||
});
|
||||
|
||||
describe('DashSearchItemView', function() {
|
||||
beforeEach(function() {
|
||||
beforeEachHelper.call(this, DashSearchItemView);
|
||||
});
|
||||
it('renders items correctly', rendersItem);
|
||||
it('renders Sequence items correctly', rendersSequentialItem);
|
||||
it('displays course name in breadcrumbs', function() {
|
||||
expect(this.seqItem.$el.find('.result-course-name')).toContainHtml(this.model.get('course_name'));
|
||||
});
|
||||
it('logs view event', logsSearchItemViewEvent);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -315,7 +293,7 @@ define([
|
||||
$('.search-field').val(term);
|
||||
this.form.doSearch(term);
|
||||
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
|
||||
expect($('.search-field').val()).toEqual(term);
|
||||
expect($('.search-field').val()).toEqual(term.trim());
|
||||
expect($('.search-field')).toHaveClass('is-active');
|
||||
expect($('.search-button')).toBeHidden();
|
||||
expect($('.cancel-button')).toBeVisible();
|
||||
@@ -350,26 +328,12 @@ define([
|
||||
expect($('.search-button')).toBeVisible();
|
||||
}
|
||||
|
||||
describe('CourseSearchForm', function() {
|
||||
describe('SearchForm', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/course_search_form.html');
|
||||
this.form = new CourseSearchForm();
|
||||
this.onClear = jasmine.createSpy('onClear');
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.form.on('clear', this.onClear);
|
||||
this.form.on('search', this.onSearch);
|
||||
});
|
||||
it('trims input string', trimsInputString);
|
||||
it('handles calls to doSearch', doesSearch);
|
||||
it('triggers a search event and changes to active state', triggersSearchEvent);
|
||||
it('clears search when clicking on cancel button', clearsSearchOnCancel);
|
||||
it('clears search when search box is empty', clearsSearchOnEmpty);
|
||||
});
|
||||
|
||||
describe('DashSearchForm', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/dashboard_search_form.html');
|
||||
this.form = new DashSearchForm();
|
||||
loadFixtures('course_search/fixtures/course_content_page.html');
|
||||
this.form = new SearchForm({
|
||||
el: '.search-bar'
|
||||
});
|
||||
this.onClear = jasmine.createSpy('onClear');
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.form.on('clear', this.onClear);
|
||||
@@ -401,7 +365,9 @@ define([
|
||||
|
||||
function returnsToContent() {
|
||||
this.resultsView.clear();
|
||||
expect(this.resultsView.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
|
||||
expect(this.resultsView.$contentElement).toHaveCss({
|
||||
display: this.contentElementDisplayValue
|
||||
});
|
||||
expect(this.resultsView.$el).toBeHidden();
|
||||
expect(this.resultsView.$el).toBeEmpty();
|
||||
}
|
||||
@@ -467,28 +433,14 @@ define([
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
|
||||
this.resultsView.loadNext();
|
||||
// toBeVisible does not work with inline
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toHaveCss({'display': 'inline'});
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toHaveCss({
|
||||
display: 'inline'
|
||||
});
|
||||
this.resultsView.renderNext();
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
|
||||
}
|
||||
|
||||
function beforeEachHelper(SearchResultsView) {
|
||||
appendSetFixtures(
|
||||
'<div class="courseware-results"></div>' +
|
||||
'<section id="course-content"></section>' +
|
||||
'<section id="dashboard-search-results"></section>' +
|
||||
'<section id="my-courses" tabindex="-1"></section>'
|
||||
);
|
||||
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/search/course_search_item',
|
||||
'templates/search/dashboard_search_item',
|
||||
'templates/search/course_search_results',
|
||||
'templates/search/dashboard_search_results',
|
||||
'templates/search/search_loading',
|
||||
'templates/search/search_error'
|
||||
]);
|
||||
|
||||
var MockCollection = Backbone.Collection.extend({
|
||||
hasNextPage: function() {},
|
||||
latestModelsCount: 0,
|
||||
@@ -497,18 +449,20 @@ define([
|
||||
return SearchCollection.prototype.latestModels.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
this.collection = new MockCollection();
|
||||
this.resultsView = new SearchResultsView({collection: this.collection});
|
||||
}
|
||||
|
||||
describe('CourseSearchResultsView', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('course_search/fixtures/course_content_page.html');
|
||||
beforeEachHelper.call(this, CourseSearchResultsView);
|
||||
this.contentElementDisplayValue = 'table-cell';
|
||||
});
|
||||
it('shows loading message', showsLoadingMessage);
|
||||
it('shows error message', showsErrorMessage);
|
||||
it('returns to content', returnsToContent);
|
||||
xit('returns to content', returnsToContent);
|
||||
it('shows a message when there are no results', showsNoResultsMessage);
|
||||
it('renders search results', rendersSearchResults);
|
||||
it('shows a link to load more results', showsMoreResultsLink);
|
||||
@@ -518,6 +472,7 @@ define([
|
||||
|
||||
describe('DashSearchResultsView', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('course_search/fixtures/dashboard_search_page.html');
|
||||
beforeEachHelper.call(this, DashSearchResultsView);
|
||||
this.contentElementDisplayValue = 'block';
|
||||
});
|
||||
@@ -547,7 +502,9 @@ define([
|
||||
function showsLoadingMessage() {
|
||||
$('.search-field').val('search string');
|
||||
$('.search-button').trigger('click');
|
||||
expect(this.$contentElement).toBeHidden();
|
||||
if (this.$contentElement) {
|
||||
expect(this.$contentElement).toBeHidden();
|
||||
}
|
||||
expect(this.$searchResults).toBeVisible();
|
||||
expect(this.$searchResults).not.toBeEmpty();
|
||||
}
|
||||
@@ -599,13 +556,13 @@ define([
|
||||
$('.cancel-button').trigger('click');
|
||||
AjaxHelpers.skipResetRequest(requests);
|
||||
// there should be no results
|
||||
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
|
||||
expect(this.$contentElement).toHaveCss({display: this.contentElementDisplayValue});
|
||||
expect(this.$searchResults).toBeHidden();
|
||||
}
|
||||
|
||||
function clearsResults() {
|
||||
$('.cancel-button').trigger('click');
|
||||
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
|
||||
expect(this.$contentElement).toHaveCss({display: this.contentElementDisplayValue});
|
||||
expect(this.$searchResults).toBeHidden();
|
||||
}
|
||||
|
||||
@@ -624,13 +581,14 @@ define([
|
||||
}
|
||||
}]
|
||||
};
|
||||
var body;
|
||||
$('.search-field').val('query');
|
||||
$('.search-button').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
expect(this.$searchResults.find('li').length).toEqual(1);
|
||||
expect($('.search-load-next')).toBeVisible();
|
||||
$('.search-load-next').trigger('click');
|
||||
var body = requests[1].requestBody;
|
||||
body = requests[1].requestBody;
|
||||
expect(body).toContain('search_string=query');
|
||||
expect(body).toContain('page_index=1');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
@@ -644,32 +602,18 @@ define([
|
||||
expect(requests[0].requestBody).toContain('search_string=query');
|
||||
}
|
||||
|
||||
function loadTemplates() {
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/search/course_search_item',
|
||||
'templates/search/dashboard_search_item',
|
||||
'templates/search/search_loading',
|
||||
'templates/search/search_error',
|
||||
'templates/search/course_search_results',
|
||||
'templates/search/dashboard_search_results'
|
||||
]);
|
||||
}
|
||||
|
||||
describe('CourseSearchApp', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/course_search_form.html');
|
||||
appendSetFixtures(
|
||||
'<div class="courseware-results"></div>' +
|
||||
'<section id="course-content"></section>'
|
||||
);
|
||||
loadTemplates.call(this);
|
||||
|
||||
var courseId = 'a/b/c';
|
||||
CourseSearchFactory(courseId);
|
||||
loadFixtures('course_search/fixtures/course_content_page.html');
|
||||
CourseSearchFactory({
|
||||
courseId: courseId,
|
||||
searchHeader: $('.search-bar')
|
||||
});
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
this.$contentElement = $('#course-content');
|
||||
this.contentElementDisplayValue = 'table-cell';
|
||||
this.$searchResults = $('.courseware-results');
|
||||
this.$searchResults = $('.search-results');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -680,26 +624,21 @@ define([
|
||||
it('performs search', performsSearch);
|
||||
it('shows an error message', showsErrorMessage);
|
||||
it('updates navigation history', updatesNavigationHistory);
|
||||
it('cancels search request', cancelsSearchRequest);
|
||||
it('clears results', clearsResults);
|
||||
xit('cancels search request', cancelsSearchRequest);
|
||||
xit('clears results', clearsResults);
|
||||
it('loads next page', loadsNextPage);
|
||||
it('navigates to search', navigatesToSearch);
|
||||
});
|
||||
|
||||
describe('DashSearchApp', function() {
|
||||
describe('DashboardSearchApp', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/dashboard_search_form.html');
|
||||
appendSetFixtures(
|
||||
'<section id="dashboard-search-results"></section>' +
|
||||
'<section id="my-courses" tabindex="-1"></section>'
|
||||
);
|
||||
loadTemplates.call(this);
|
||||
loadFixtures('course_search/fixtures/dashboard_search_page.html');
|
||||
DashboardSearchFactory();
|
||||
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
this.$contentElement = $('#my-courses');
|
||||
this.contentElementDisplayValue = 'block';
|
||||
this.$searchResults = $('#dashboard-search-results');
|
||||
this.$searchResults = $('.search-results');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -738,6 +677,30 @@ define([
|
||||
expect(this.$searchResults).toBeEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Course Search Results Page', function() {
|
||||
beforeEach(function() {
|
||||
var courseId = 'a/b/c';
|
||||
loadFixtures('course_search/fixtures/course_search_results_page.html');
|
||||
CourseSearchFactory({
|
||||
courseId: courseId,
|
||||
searchHeader: $('.page-header-search')
|
||||
});
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
this.$contentElement = null; // The search results page does not show over a content element
|
||||
this.contentElementDisplayValue = 'table-cell';
|
||||
this.$searchResults = $('.search-results');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Backbone.history.stop();
|
||||
});
|
||||
|
||||
it('shows loading message on search', showsLoadingMessage);
|
||||
it('performs search', performsSearch);
|
||||
it('shows an error message', showsErrorMessage);
|
||||
it('loads next page', loadsNextPage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,24 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'js/search/base/views/search_results_view',
|
||||
'js/search/course/views/search_item_view'
|
||||
], function(SearchResultsView, CourseSearchItemView) {
|
||||
'use strict';
|
||||
|
||||
'course_search/js/views/search_results_view',
|
||||
'text!course_search/templates/course_search_results.underscore',
|
||||
'text!course_search/templates/course_search_item.underscore'
|
||||
], function(
|
||||
SearchResultsView,
|
||||
courseSearchResultsTemplate,
|
||||
courseSearchItemTemplate
|
||||
) {
|
||||
return SearchResultsView.extend({
|
||||
|
||||
el: '.courseware-results',
|
||||
el: '.search-results',
|
||||
contentElement: '#course-content',
|
||||
coursewareResultsWrapperElement: '.courseware-results-wrapper',
|
||||
resultsTemplateId: '#course_search_results-tpl',
|
||||
loadingTemplateId: '#search_loading-tpl',
|
||||
errorTemplateId: '#search_error-tpl',
|
||||
resultsTemplate: courseSearchResultsTemplate,
|
||||
itemTemplate: courseSearchItemTemplate,
|
||||
events: {
|
||||
'click .search-load-next': 'loadNext'
|
||||
},
|
||||
SearchItemView: CourseSearchItemView,
|
||||
|
||||
clear: function() {
|
||||
SearchResultsView.prototype.clear.call(this);
|
||||
@@ -31,4 +33,4 @@
|
||||
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -0,0 +1,31 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'course_search/js/views/search_results_view',
|
||||
'text!course_search/templates/dashboard_search_results.underscore',
|
||||
'text!course_search/templates/dashboard_search_item.underscore'
|
||||
], function(
|
||||
SearchResultsView,
|
||||
dashboardSearchResultsTemplate,
|
||||
dashboardSearchItemTemplate
|
||||
) {
|
||||
return SearchResultsView.extend({
|
||||
el: '.search-results',
|
||||
contentElement: '#my-courses, #profile-sidebar',
|
||||
resultsTemplate: dashboardSearchResultsTemplate,
|
||||
itemTemplate: dashboardSearchItemTemplate,
|
||||
events: {
|
||||
'click .search-load-next': 'loadNext',
|
||||
'click .search-back-to-courses': 'backToCourses'
|
||||
},
|
||||
|
||||
backToCourses: function() {
|
||||
this.clear();
|
||||
this.trigger('reset');
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,19 +1,20 @@
|
||||
(function(define) {
|
||||
define(['jquery', 'backbone'], function($, Backbone) {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
define(['jquery', 'backbone'], function($, Backbone) {
|
||||
return Backbone.View.extend({
|
||||
|
||||
el: '',
|
||||
events: {
|
||||
'submit form': 'submitForm',
|
||||
'submit .search-form': 'submitForm',
|
||||
'click .cancel-button': 'clearSearch'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
initialize: function(options) {
|
||||
this.$searchField = this.$el.find('.search-field');
|
||||
this.$searchButton = this.$el.find('.search-button');
|
||||
this.$cancelButton = this.$el.find('.cancel-button');
|
||||
this.supportsActive = options.supportsActive === undefined ? true : options.supportsActive;
|
||||
},
|
||||
|
||||
submitForm: function(event) {
|
||||
@@ -22,19 +23,17 @@
|
||||
},
|
||||
|
||||
doSearch: function(term) {
|
||||
var trimmedTerm;
|
||||
if (term) {
|
||||
this.$searchField.val(term);
|
||||
trimmedTerm = term.trim();
|
||||
this.$searchField.val(trimmedTerm);
|
||||
} else {
|
||||
trimmedTerm = this.$searchField.val().trim();
|
||||
}
|
||||
else {
|
||||
term = this.$searchField.val();
|
||||
}
|
||||
|
||||
var trimmed = $.trim(term);
|
||||
if (trimmed) {
|
||||
if (trimmedTerm) {
|
||||
this.setActiveStyle();
|
||||
this.trigger('search', trimmed);
|
||||
}
|
||||
else {
|
||||
this.trigger('search', trimmedTerm);
|
||||
} else {
|
||||
this.clearSearch();
|
||||
}
|
||||
},
|
||||
@@ -50,17 +49,20 @@
|
||||
},
|
||||
|
||||
setActiveStyle: function() {
|
||||
this.$searchField.addClass('is-active');
|
||||
this.$searchButton.hide();
|
||||
this.$cancelButton.show();
|
||||
if (this.supportsActive) {
|
||||
this.$searchField.addClass('is-active');
|
||||
this.$searchButton.hide();
|
||||
this.$cancelButton.show();
|
||||
}
|
||||
},
|
||||
|
||||
setInitialStyle: function() {
|
||||
this.$searchField.removeClass('is-active');
|
||||
this.$searchButton.show();
|
||||
this.$cancelButton.hide();
|
||||
if (this.supportsActive) {
|
||||
this.$searchField.removeClass('is-active');
|
||||
this.$searchButton.show();
|
||||
this.$cancelButton.hide();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,40 +1,43 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext',
|
||||
'logger'
|
||||
], function($, _, Backbone, gettext, Logger) {
|
||||
'use strict';
|
||||
|
||||
'logger',
|
||||
'edx-ui-toolkit/js/utils/html-utils'
|
||||
], function($, _, Backbone, gettext, Logger, HtmlUtils) {
|
||||
return Backbone.View.extend({
|
||||
|
||||
tagName: 'li',
|
||||
templateId: '',
|
||||
className: 'search-results-item',
|
||||
attributes: {
|
||||
'role': 'region',
|
||||
role: 'region',
|
||||
'aria-label': 'search result'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click': 'logSearchItem'
|
||||
click: 'logSearchItem'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.tpl = _.template($(this.templateId).html());
|
||||
initialize: function(options) {
|
||||
this.template = options.template;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = _.clone(this.model.attributes);
|
||||
// Drop the preview text and result type if the search term is found
|
||||
// in the title/location in the course hierarchy
|
||||
|
||||
// Drop the preview text and result type if the search term is found
|
||||
// in the title/location in the course hierarchy
|
||||
if (this.model.get('content_type') === 'Sequence') {
|
||||
data.excerpt = '';
|
||||
data.content_type = '';
|
||||
}
|
||||
this.$el.html(this.tpl(data));
|
||||
data.excerptHtml = HtmlUtils.HTML(data.excerpt);
|
||||
delete data.excerpt;
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.template)(data));
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -47,7 +50,6 @@
|
||||
},
|
||||
|
||||
logSearchItem: function(event) {
|
||||
event.preventDefault();
|
||||
var self = this;
|
||||
var target = this.model.id;
|
||||
var link = this.model.get('url');
|
||||
@@ -56,10 +58,13 @@
|
||||
var pageSize = collection.pageSize;
|
||||
var searchTerm = collection.searchTerm;
|
||||
var index = collection.indexOf(this.model);
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
Logger.log('edx.course.search.result_selected', {
|
||||
'search_term': searchTerm,
|
||||
'result_position': (page * pageSize + index),
|
||||
'result_link': target
|
||||
search_term: searchTerm,
|
||||
result_position: (page * pageSize) + index,
|
||||
result_link: target
|
||||
}).always(function() {
|
||||
self.redirect(link);
|
||||
});
|
||||
@@ -67,4 +72,4 @@
|
||||
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,33 +1,34 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext'
|
||||
], function($, _, Backbone, gettext) {
|
||||
'use strict';
|
||||
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'edx-ui-toolkit/js/utils/string-utils',
|
||||
'course_search/js/views/search_item_view',
|
||||
'text!course_search/templates/search_loading.underscore',
|
||||
'text!course_search/templates/search_error.underscore'
|
||||
], function($, _, Backbone, HtmlUtils, StringUtils, SearchItemView, searchLoadingTemplate, searchErrorTemplate) {
|
||||
return Backbone.View.extend({
|
||||
|
||||
// these should be defined by subclasses
|
||||
// these should be defined by subclasses
|
||||
el: '',
|
||||
contentElement: '',
|
||||
resultsTemplateId: '',
|
||||
loadingTemplateId: '',
|
||||
errorTemplateId: '',
|
||||
resultsTemplate: null,
|
||||
itemTemplate: null,
|
||||
loadingTemplate: searchLoadingTemplate,
|
||||
errorTemplate: searchErrorTemplate,
|
||||
events: {},
|
||||
spinner: '.search-load-next .icon',
|
||||
SearchItemView: function() {},
|
||||
|
||||
initialize: function() {
|
||||
this.$contentElement = $(this.contentElement);
|
||||
this.resultsTemplate = _.template($(this.resultsTemplateId).html());
|
||||
this.loadingTemplate = _.template($(this.loadingTemplateId).html());
|
||||
this.errorTemplate = _.template($(this.errorTemplateId).html());
|
||||
this.$contentElement = this.contentElement ? $(this.contentElement) : $([]);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.resultsTemplate({
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.resultsTemplate)({
|
||||
totalCount: this.collection.totalCount,
|
||||
totalCountMsg: this.totalCountMsg(),
|
||||
pageSize: this.collection.pageSize,
|
||||
@@ -40,10 +41,10 @@
|
||||
},
|
||||
|
||||
renderNext: function() {
|
||||
// total count may have changed
|
||||
// total count may have changed
|
||||
this.$el.find('.search-count').text(this.totalCountMsg());
|
||||
this.renderItems();
|
||||
if (! this.collection.hasNextPage()) {
|
||||
if (!this.collection.hasNextPage()) {
|
||||
this.$el.find('.search-load-next').remove();
|
||||
}
|
||||
this.$el.find(this.spinner).hide();
|
||||
@@ -52,15 +53,21 @@
|
||||
renderItems: function() {
|
||||
var latest = this.collection.latestModels();
|
||||
var items = latest.map(function(result) {
|
||||
var item = new this.SearchItemView({model: result});
|
||||
var item = new SearchItemView({
|
||||
model: result,
|
||||
template: this.itemTemplate
|
||||
});
|
||||
return item.render().el;
|
||||
}, this);
|
||||
// safe-lint: disable=javascript-jquery-append
|
||||
this.$el.find('ol').append(items);
|
||||
},
|
||||
|
||||
totalCountMsg: function() {
|
||||
var fmt = ngettext('%s result', '%s results', this.collection.totalCount);
|
||||
return interpolate(fmt, [this.collection.totalCount]);
|
||||
var fmt = ngettext('{total_results} result', '{total_results} results', this.collection.totalCount);
|
||||
return StringUtils.interpolate(fmt, {
|
||||
total_results: this.collection.totalCount
|
||||
});
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
@@ -74,27 +81,26 @@
|
||||
},
|
||||
|
||||
showLoadingMessage: function() {
|
||||
this.doCleanup();
|
||||
this.$el.html(this.loadingTemplate());
|
||||
// Empty any previous loading/error message
|
||||
$('#loading-message').html('');
|
||||
$('#error-message').html('');
|
||||
|
||||
// Show the loading message
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.loadingTemplate)());
|
||||
|
||||
// Show the results
|
||||
this.showResults();
|
||||
},
|
||||
|
||||
showErrorMessage: function() {
|
||||
this.$el.html(this.errorTemplate());
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.errorTemplate)());
|
||||
this.showResults();
|
||||
},
|
||||
|
||||
doCleanup: function() {
|
||||
// Empty any loading/error message and empty the el
|
||||
// Bookmarks share the same container element, So we are doing
|
||||
// this to ensure that elements are in clean/initial state
|
||||
$('#loading-message').html('');
|
||||
$('#error-message').html('');
|
||||
this.$el.html('');
|
||||
},
|
||||
|
||||
loadNext: function(event) {
|
||||
event && event.preventDefault();
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
this.$el.find(this.spinner).show();
|
||||
this.trigger('next');
|
||||
return false;
|
||||
@@ -102,4 +108,4 @@
|
||||
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="result-excerpt"><%= excerpt %></div>
|
||||
<a class="result-link" href="<%- url %>"><%= gettext("View") %> <span class="icon fa fa-arrow-right" aria-hidden="true"></span></a>
|
||||
<div class="result-excerpt"><%= HtmlUtils.ensureHtml(excerptHtml) %></div>
|
||||
<a class="result-link" href="<%- url %>"><%- gettext("View") %> <span class="icon fa fa-arrow-right" aria-hidden="true"></span></a>
|
||||
<span class="result-location"><%- location.join(' ▸ ') %></span>
|
||||
<span class="result-type"><%- content_type %></span>
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="search-info">
|
||||
<h2 class="search-results-title"><%- gettext("Search Results") %></h2>
|
||||
<div class="search-count"><%- totalCountMsg %></div>
|
||||
</div>
|
||||
|
||||
<% if (totalCount > 0 ) { %>
|
||||
|
||||
<ol class='search-result-list'></ol>
|
||||
|
||||
<% if (hasMoreResults) { %>
|
||||
<a class="search-load-next" href="#">
|
||||
<%-
|
||||
StringUtils.interpolate(
|
||||
ngettext("Load next {num_items} result", "Load next {num_items} results", pageSize),
|
||||
{
|
||||
num_items: pageSize
|
||||
}
|
||||
)
|
||||
%>
|
||||
<span class="icon fa fa-spinner fa-spin" aria-hidden="true"></span>
|
||||
</a>
|
||||
<% } %>
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<p><%- gettext("Sorry, no results were found.") %></p>
|
||||
|
||||
<% } %>
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="result-excerpt"><%= excerpt %></div>
|
||||
<a class="result-link" href="<%- url %>"><%= gettext("View") %> <span class="icon fa fa-arrow-right" aria-hidden="true"></span></a>
|
||||
<div class="result-excerpt"><%= HtmlUtils.ensureHtml(excerptHtml) %></div>
|
||||
<a class="result-link" href="<%- url %>"><%- gettext("View") %> <span class="icon fa fa-arrow-right" aria-hidden="true"></span></a>
|
||||
<span class="result-course-name"><%- course_name %>:</span>
|
||||
<span class="result-location"><%- location.join(' ▸ ') %></span>
|
||||
<span class="result-type"><%- content_type %></span>
|
||||
@@ -0,0 +1,29 @@
|
||||
<header class="search-info">
|
||||
<a class="search-back-to-courses" href="#"><%- gettext("Back to Dashboard") %></a>
|
||||
<h2><%- gettext("Search Results") %></h2>
|
||||
<div class="search-count"><%- totalCountMsg %></div>
|
||||
</header>
|
||||
|
||||
<% if (totalCount > 0 ) { %>
|
||||
|
||||
<ol class='search-result-list'></ol>
|
||||
|
||||
<% if (hasMoreResults) { %>
|
||||
<a class="search-load-next" href="#">
|
||||
<%-
|
||||
StringUtils.interpolate(
|
||||
ngettext("Load next {num_items} result", "Load next {num_items} results", pageSize),
|
||||
{
|
||||
num_items: pageSize
|
||||
}
|
||||
)
|
||||
%>
|
||||
<span class="icon fa fa-spinner fa-spin" aria-hidden="true"></span>
|
||||
</a>
|
||||
<% } %>
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<p><%- gettext("Sorry, no results were found.") %></p>
|
||||
|
||||
<% } %>
|
||||
@@ -1,2 +1,2 @@
|
||||
<span class="icon fa fa-spinner fa-spin" aria-hidden="true"></span> <%= gettext("Loading") %>
|
||||
<span class="icon fa fa-spinner fa-spin" aria-hidden="true"></span> <%- gettext("Loading") %>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
## mako
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<%!
|
||||
import json
|
||||
import waffle
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.template.defaultfilters import escapejs
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from django_comment_client.permissions import has_permission
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
from openedx.features.course_experience import course_home_page_title
|
||||
%>
|
||||
|
||||
<%block name="content">
|
||||
<div class="course-view container" id="course-container">
|
||||
<header class="page-header has-secondary">
|
||||
<div class="page-header-main">
|
||||
<nav aria-label="${_('My Bookmarks')}" class="sr-is-focusable" tabindex="-1">
|
||||
<div class="has-breadcrumbs">
|
||||
<div class="breadcrumbs">
|
||||
<span class="nav-item">
|
||||
<a href="${course_url}">${course_home_page_title(course)}</a>
|
||||
</span>
|
||||
<span class="icon fa fa-angle-right" aria-hidden="true"></span>
|
||||
<span class="nav-item">${_('Search Results')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="page-header-secondary">
|
||||
<div class="page-header-search">
|
||||
<form class="search-form" role="search">
|
||||
<label class="field-label sr-only" for="search" id="search-hint">${_('Search the course')}</label>
|
||||
<input
|
||||
class="field-input input-text search-field"
|
||||
type="search"
|
||||
name="query"
|
||||
id="search"
|
||||
value="${query}"
|
||||
placeholder="${_('Search the course')}"
|
||||
/>
|
||||
<button class="btn btn-small search-button" type="submit">${_('Search')}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<main role="main" class="search-results" id="main" tabindex="-1">
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<%static:require_module module_name="course_search/js/course_search_factory" class_name="CourseSearchFactory">
|
||||
var courseId = '${course_key | n, js_escaped_string}';
|
||||
CourseSearchFactory({
|
||||
courseId: courseId,
|
||||
searchHeader: $('.page-header-search'),
|
||||
supportsActive: false,
|
||||
query: '${query | n, js_escaped_string}'
|
||||
});
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
20
openedx/features/course_search/urls.py
Normal file
20
openedx/features/course_search/urls.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Defines URLs for course search.
|
||||
"""
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from views.course_search import CourseSearchView, CourseSearchFragmentView
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^$',
|
||||
CourseSearchView.as_view(),
|
||||
name='openedx.course_search.course_search_results',
|
||||
),
|
||||
url(
|
||||
r'^home_fragment$',
|
||||
CourseSearchFragmentView.as_view(),
|
||||
name='openedx.course_search.course_search_results_fragment_view',
|
||||
),
|
||||
]
|
||||
0
openedx/features/course_search/views/__init__.py
Normal file
0
openedx/features/course_search/views/__init__.py
Normal file
66
openedx/features/course_search/views/course_search.py
Normal file
66
openedx/features/course_search/views/course_search.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Views for the course search page.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
|
||||
from courseware.courses import get_course_overview_with_access
|
||||
from lms.djangoapps.courseware.views.views import CourseTabView
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
|
||||
from openedx.features.course_experience import default_course_url_name
|
||||
from util.views import ensure_valid_course_key
|
||||
from web_fragments.fragment import Fragment
|
||||
|
||||
|
||||
class CourseSearchView(CourseTabView):
|
||||
"""
|
||||
The home page for a course.
|
||||
"""
|
||||
@method_decorator(login_required)
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
|
||||
@method_decorator(ensure_valid_course_key)
|
||||
def get(self, request, course_id, **kwargs):
|
||||
"""
|
||||
Displays the home page for the specified course.
|
||||
"""
|
||||
return super(CourseSearchView, self).get(request, course_id, 'courseware', **kwargs)
|
||||
|
||||
def render_to_fragment(self, request, course=None, tab=None, **kwargs):
|
||||
course_id = unicode(course.id)
|
||||
home_fragment_view = CourseSearchFragmentView()
|
||||
return home_fragment_view.render_to_fragment(request, course_id=course_id, **kwargs)
|
||||
|
||||
|
||||
class CourseSearchFragmentView(EdxFragmentView):
|
||||
"""
|
||||
A fragment to render the home page for a course.
|
||||
"""
|
||||
def render_to_fragment(self, request, course_id=None, **kwargs):
|
||||
"""
|
||||
Renders the course's home page as a fragment.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_overview_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
course_url_name = default_course_url_name(request)
|
||||
course_url = reverse(course_url_name, kwargs={'course_id': unicode(course.id)})
|
||||
|
||||
# Render the course home fragment
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
'course_key': course_key,
|
||||
'course_url': course_url,
|
||||
'query': request.GET.get('query', ''),
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True,
|
||||
}
|
||||
html = render_to_string('course_search/course-search-fragment.html', context)
|
||||
return Fragment(html)
|
||||
@@ -57,7 +57,7 @@ edx-oauth2-provider==1.2.0
|
||||
edx-opaque-keys==0.4.0
|
||||
edx-organizations==0.4.4
|
||||
edx-rest-api-client==1.7.1
|
||||
edx-search==0.1.2
|
||||
edx-search==1.0.1
|
||||
facebook-sdk==0.4.0
|
||||
feedparser==5.1.3
|
||||
firebase-token-generator==1.3.2
|
||||
|
||||
@@ -75,7 +75,7 @@ git+https://github.com/edx/lettuce.git@0.2.20.002#egg=lettuce==0.2.20.002
|
||||
-e git+https://github.com/edx/event-tracking.git@0.2.1#egg=event-tracking==0.2.1
|
||||
-e git+https://github.com/edx/django-splash.git@v0.2#egg=django-splash==0.2
|
||||
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
|
||||
git+https://github.com/edx/edx-ora2.git@1.4.1#egg=ora2==1.4.1
|
||||
git+https://github.com/edx/edx-ora2.git@1.4.2#egg=ora2==1.4.2
|
||||
-e git+https://github.com/edx/edx-submissions.git@2.0.0#egg=edx-submissions==2.0.0
|
||||
git+https://github.com/edx/ease.git@release-2015-07-14#egg=ease==0.1.3
|
||||
git+https://github.com/edx/edx-val.git@0.0.13#egg=edxval==0.0.13
|
||||
|
||||
@@ -29,12 +29,6 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
<%static:include path="dashboard/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% for template_name in ["dashboard_search_item", "dashboard_search_results", "search_loading", "search_error"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="search/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
@@ -50,7 +44,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
});
|
||||
</script>
|
||||
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
|
||||
<%static:require_module module_name="js/search/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory">
|
||||
<%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory">
|
||||
DashboardSearchFactory();
|
||||
</%static:require_module>
|
||||
% endif
|
||||
@@ -164,7 +158,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
|
||||
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
|
||||
<div id="dashboard-search-bar" class="search-bar dashboard-search-bar" role="search" aria-label="Dashboard">
|
||||
<form>
|
||||
<form class="search-form">
|
||||
<label for="dashboard-search-input">${_('Search Your Courses')}</label>
|
||||
<div class="search-field-wrapper">
|
||||
<input id="dashboard-search-input" type="text" class="search-field"/>
|
||||
|
||||
@@ -94,23 +94,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
% if include_dependencies:
|
||||
<%static:js group='base_vendor'/>
|
||||
<%static:css group='style-vendor'/>
|
||||
<%include file="widgets/segment-io.html" />
|
||||
<%include file="widgets/segment-io-footer.html" />
|
||||
% endif
|
||||
|
||||
% if bidi == 'rtl':
|
||||
<%static:css group='style-lms-footer-edx-rtl'/>
|
||||
% else:
|
||||
<%static:css group='style-lms-footer-edx'/>
|
||||
% endif
|
||||
|
||||
% if footer_css_urls:
|
||||
% for url in footer_css_urls:
|
||||
<link rel="stylesheet" type="text/css" href="${url}"></link>
|
||||
% endfor
|
||||
% endif
|
||||
% if footer_js_url:
|
||||
<script type="text/javascript" src="${footer_js_url}"></script>
|
||||
% endif
|
||||
|
||||
Reference in New Issue
Block a user