Fixes after rebase to Django 1.8
This commit is contained in:
committed by
muzaffaryousaf
parent
3792943960
commit
8bdc097293
@@ -40,8 +40,6 @@ require.config({
|
||||
"jquery.immediateDescendents": "coffee/src/jquery.immediateDescendents",
|
||||
"datepair": "js/vendor/timepicker/datepair",
|
||||
"date": "js/vendor/date",
|
||||
"text": 'js/vendor/requirejs/text',
|
||||
"moment": "js/vendor/moment-with-locales.min",
|
||||
"moment": "js/vendor/moment.min",
|
||||
"moment-with-locales": "js/vendor/moment-with-locales.min",
|
||||
"text": 'js/vendor/requirejs/text',
|
||||
|
||||
@@ -917,6 +917,14 @@ class XMLModuleStore(ModuleStoreReadBase):
|
||||
log.warning("get_all_asset_metadata request of XML modulestore - not implemented.")
|
||||
return []
|
||||
|
||||
def fill_in_run(self, course_key):
|
||||
"""
|
||||
A no-op.
|
||||
|
||||
Added to simplify tests which use the XML-store directly.
|
||||
"""
|
||||
return course_key
|
||||
|
||||
|
||||
class LibraryXMLModuleStore(XMLModuleStore):
|
||||
"""
|
||||
|
||||
@@ -55,6 +55,7 @@ class SequenceFields(object):
|
||||
scope=Scope.settings,
|
||||
)
|
||||
|
||||
|
||||
class ProctoringFields(object):
|
||||
"""
|
||||
Fields that are specific to Proctored or Timed Exams
|
||||
@@ -120,7 +121,7 @@ class ProctoringFields(object):
|
||||
@XBlock.wants('credit')
|
||||
@XBlock.needs("user")
|
||||
@XBlock.needs("bookmarks")
|
||||
class SequenceModule(SequenceFields, XModule):
|
||||
class SequenceModule(SequenceFields, ProctoringFields, XModule):
|
||||
"""
|
||||
Layout module which lays out content in a temporal sequence
|
||||
"""
|
||||
|
||||
7
common/static/js/vendor/moment.min.js
vendored
Normal file
7
common/static/js/vendor/moment.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -12,11 +12,12 @@ class CoursewareSearchPage(CoursePage):
|
||||
|
||||
url_path = "courseware/"
|
||||
search_bar_selector = '#courseware-search-bar'
|
||||
search_results_selector = '.courseware-results'
|
||||
|
||||
@property
|
||||
def search_results(self):
|
||||
""" search results list showing """
|
||||
return self.q(css='.courseware-results')
|
||||
return self.q(css=self.search_results_selector)
|
||||
|
||||
def is_browser_on_page(self):
|
||||
""" did we find the search bar in the UI """
|
||||
@@ -30,6 +31,7 @@ class CoursewareSearchPage(CoursePage):
|
||||
""" execute the search """
|
||||
self.q(css=self.search_bar_selector + ' [type="submit"]').click()
|
||||
self.wait_for_ajax()
|
||||
self.wait_for_element_visibility(self.search_results_selector, 'Search results are visible')
|
||||
|
||||
def search_for_term(self, text):
|
||||
"""
|
||||
|
||||
@@ -207,14 +207,13 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
self.assertEqual(self.bookmarks_page.get_current_page_number(), current_page_number)
|
||||
self.assertEqual(self.bookmarks_page.get_total_pages, total_pages)
|
||||
|
||||
def _navigate_and_verify_bookmarks_list(self, bookmarks_count):
|
||||
def _navigate_to_bookmarks_list(self):
|
||||
"""
|
||||
Navigates and verifies the bookmarks list page.
|
||||
"""
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
self.assertTrue(self.bookmarks_page.results_present())
|
||||
self.assertEqual(self.bookmarks_page.results_header_text(), 'MY BOOKMARKS')
|
||||
self.assertEqual(self.bookmarks_page.count(), bookmarks_count)
|
||||
|
||||
def _verify_breadcrumbs(self, num_units, modified_name=None):
|
||||
"""
|
||||
@@ -310,6 +309,9 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
self._test_setup()
|
||||
self._bookmark_units(2)
|
||||
|
||||
self._navigate_to_bookmarks_list()
|
||||
self._verify_breadcrumbs(num_units=2)
|
||||
|
||||
self._verify_pagination_info(
|
||||
bookmark_count_on_current_page=2,
|
||||
header_text='Showing 1-2 out of 2 total',
|
||||
@@ -319,9 +321,6 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
total_pages=1
|
||||
)
|
||||
|
||||
self._navigate_and_verify_bookmarks_list(bookmarks_count=2)
|
||||
self._verify_breadcrumbs(num_units=2)
|
||||
|
||||
# get usage ids for units
|
||||
xblocks = self.course_fixture.get_nested_xblocks(category="vertical")
|
||||
xblock_usage_ids = [xblock.locator for xblock in xblocks]
|
||||
@@ -329,7 +328,7 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
for index in range(2):
|
||||
self.bookmarks_page.click_bookmarked_block(index)
|
||||
self.courseware_page.wait_for_page()
|
||||
self.assertTrue(self.courseware_page.active_usage_id() in xblock_usage_ids)
|
||||
self.assertIn(self.courseware_page.active_usage_id(), xblock_usage_ids)
|
||||
self.courseware_page.visit().wait_for_page()
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
|
||||
@@ -352,11 +351,11 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
self._test_setup(num_chapters=1)
|
||||
self._bookmark_units(num_units=1)
|
||||
|
||||
self._navigate_and_verify_bookmarks_list(bookmarks_count=1)
|
||||
self._navigate_to_bookmarks_list()
|
||||
self._verify_breadcrumbs(num_units=1)
|
||||
|
||||
LogoutPage(self.browser).visit()
|
||||
AutoAuthPage(
|
||||
LmsAutoAuthPage(
|
||||
self.browser,
|
||||
username=self.USERNAME,
|
||||
email=self.EMAIL,
|
||||
@@ -368,10 +367,10 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
self.update_and_publish_block_display_name(modified_name)
|
||||
|
||||
LogoutPage(self.browser).visit()
|
||||
AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit()
|
||||
LmsAutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit()
|
||||
self.courseware_page.visit()
|
||||
|
||||
self._navigate_and_verify_bookmarks_list(bookmarks_count=1)
|
||||
self._navigate_to_bookmarks_list()
|
||||
self._verify_breadcrumbs(num_units=1, modified_name=modified_name)
|
||||
|
||||
def test_unreachable_bookmark(self):
|
||||
@@ -387,15 +386,15 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
When I click on deleted bookmark
|
||||
Then I should navigated to 404 page
|
||||
"""
|
||||
self._test_setup()
|
||||
self._bookmark_units(2)
|
||||
self._test_setup(num_chapters=1)
|
||||
self._bookmark_units(1)
|
||||
self._delete_section(0)
|
||||
|
||||
self._navigate_and_verify_bookmarks_list(bookmarks_count=2)
|
||||
self._navigate_to_bookmarks_list()
|
||||
|
||||
self._verify_pagination_info(
|
||||
bookmark_count_on_current_page=2,
|
||||
header_text='Showing 1-2 out of 2 total',
|
||||
bookmark_count_on_current_page=1,
|
||||
header_text='Showing 1 out of 1 total',
|
||||
previous_button_enabled=False,
|
||||
next_button_enabled=False,
|
||||
current_page_number=1,
|
||||
@@ -418,7 +417,7 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
"""
|
||||
self._test_setup(11)
|
||||
self._bookmark_units(11)
|
||||
self._navigate_and_verify_bookmarks_list(bookmarks_count=11)
|
||||
self._navigate_to_bookmarks_list()
|
||||
|
||||
self._verify_pagination_info(
|
||||
bookmark_count_on_current_page=10,
|
||||
|
||||
@@ -68,14 +68,6 @@ class CoursewareTest(UniqueCourseTest):
|
||||
self.problem_page = ProblemPage(self.browser)
|
||||
self.assertEqual(self.problem_page.problem_name, 'TEST PROBLEM 1')
|
||||
|
||||
def _change_problem_release_date_in_studio(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
self.course_outline.q(css=".subsection-header-actions .configure-button").first.click()
|
||||
self.course_outline.q(css="#start_date").fill("01/01/2030")
|
||||
self.course_outline.q(css=".action-save").first.click()
|
||||
|
||||
def _create_breadcrumb(self, index):
|
||||
""" Create breadcrumb """
|
||||
return ['Test Section {}'.format(index), 'Test Subsection {}'.format(index), 'Test Problem {}'.format(index)]
|
||||
@@ -105,9 +97,6 @@ class CoursewareTest(UniqueCourseTest):
|
||||
# Set release date for subsection in future.
|
||||
self.course_outline.change_problem_release_date_in_studio()
|
||||
|
||||
# Wait for 2 seconds to save new date.
|
||||
time.sleep(2)
|
||||
|
||||
# Logout and login as a student.
|
||||
LogoutPage(self.browser).visit()
|
||||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||||
@@ -117,6 +106,23 @@ class CoursewareTest(UniqueCourseTest):
|
||||
# Problem name should be "TEST PROBLEM 2".
|
||||
self.assertEqual(self.problem_page.problem_name, 'TEST PROBLEM 2')
|
||||
|
||||
def test_course_tree_breadcrumb(self):
|
||||
"""
|
||||
Scenario: Correct course tree breadcrumb is shown.
|
||||
|
||||
Given that I am a registered user
|
||||
And I visit my courseware page
|
||||
Then I should see correct course tree breadcrumb
|
||||
"""
|
||||
self.courseware_page.visit()
|
||||
|
||||
xblocks = self.course_fix.get_nested_xblocks(category="problem")
|
||||
for index in range(1, len(xblocks) + 1):
|
||||
self.course_nav.go_to_section('Test Section {}'.format(index), 'Test Subsection {}'.format(index))
|
||||
courseware_page_breadcrumb = self.courseware_page.breadcrumb
|
||||
expected_breadcrumb = self._create_breadcrumb(index) # pylint: disable=no-member
|
||||
self.assertEqual(courseware_page_breadcrumb, expected_breadcrumb)
|
||||
|
||||
|
||||
class ProctoredExamTest(UniqueCourseTest):
|
||||
"""
|
||||
@@ -262,23 +268,6 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
self.courseware_page.start_timed_exam()
|
||||
self.assertTrue(self.courseware_page.is_timer_bar_present)
|
||||
|
||||
def test_course_tree_breadcrumb(self):
|
||||
"""
|
||||
Scenario: Correct course tree breadcrumb is shown.
|
||||
|
||||
Given that I am a registered user
|
||||
And I visit my courseware page
|
||||
Then I should see correct course tree breadcrumb
|
||||
"""
|
||||
self.courseware_page.visit()
|
||||
|
||||
xblocks = self.course_fix.get_nested_xblocks(category="problem")
|
||||
for index in range(1, len(xblocks) + 1):
|
||||
self.course_nav.go_to_section('Test Section {}'.format(index), 'Test Subsection {}'.format(index))
|
||||
courseware_page_breadcrumb = self.courseware_page.breadcrumb
|
||||
expected_breadcrumb = self._create_breadcrumb(index)
|
||||
self.assertEqual(courseware_page_breadcrumb, expected_breadcrumb)
|
||||
|
||||
def test_time_allotted_field_is_not_visible_with_none_exam(self):
|
||||
"""
|
||||
Given that I am a staff member
|
||||
|
||||
@@ -419,6 +419,10 @@ def _index_bulk_op(request, course_key, chapter, section, position):
|
||||
|
||||
studio_url = get_studio_url(course, 'course')
|
||||
|
||||
language_preference = get_user_preference(request.user, LANGUAGE_KEY)
|
||||
if not language_preference:
|
||||
language_preference = settings.LANGUAGE_CODE
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'accordion': render_accordion(user, request, course, chapter, section, field_data_cache),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'moment-with-locales',
|
||||
'teams/js/views/team_card',
|
||||
'teams/js/models/team'],
|
||||
function ($, _, TeamCardView, Team) {
|
||||
function ($, _, moment, TeamCardView, Team) {
|
||||
'use strict';
|
||||
|
||||
describe('TeamCardView', function () {
|
||||
@@ -35,6 +36,7 @@ define(['jquery',
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
moment.locale('en');
|
||||
view = createTeamCardView();
|
||||
view.render();
|
||||
});
|
||||
|
||||
@@ -1176,7 +1176,6 @@ courseware_js = (
|
||||
for pth in ['courseware', 'histogram', 'navigation']
|
||||
] +
|
||||
['js/' + pth + '.js' for pth in ['ajax-error']] +
|
||||
['js/bookmarks/main.js'] +
|
||||
sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/modules/**/*.js'))
|
||||
)
|
||||
|
||||
@@ -1910,7 +1909,6 @@ INSTALLED_APPS = (
|
||||
|
||||
# Bookmarks
|
||||
'openedx.core.djangoapps.bookmarks',
|
||||
'bookmarks',
|
||||
|
||||
# programs support
|
||||
'openedx.core.djangoapps.programs',
|
||||
|
||||
12
lms/static/js/bookmarks/bookmarks_factory.js
Normal file
12
lms/static/js/bookmarks/bookmarks_factory.js
Normal file
@@ -0,0 +1,12 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
define([
|
||||
'js/bookmarks/views/bookmarks_list_button'
|
||||
],
|
||||
function(BookmarksListButton) {
|
||||
return function() {
|
||||
return new BookmarksListButton();
|
||||
};
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -8,8 +8,13 @@
|
||||
PagingCollection.prototype.initialize.call(this);
|
||||
|
||||
this.url = options.url;
|
||||
this.server_api.course_id = function () { return encodeURIComponent(options.course_id); };
|
||||
this.server_api.fields = function () { return encodeURIComponent('display_name,path'); };
|
||||
this.server_api = _.extend(
|
||||
{
|
||||
course_id: function () { return encodeURIComponent(options.course_id); },
|
||||
fields : function () { return encodeURIComponent('display_name,path'); }
|
||||
},
|
||||
PagingCollection.prototype.server_api
|
||||
);
|
||||
delete this.server_api.sort_order; // Sort order is not specified for the Bookmark API
|
||||
},
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
RequireJS.require([
|
||||
'js/bookmarks/views/bookmarks_list_button'
|
||||
], function (BookmarksListButton) {
|
||||
'use strict';
|
||||
|
||||
return new BookmarksListButton();
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define(['gettext', 'jquery', 'underscore', 'backbone', 'js/views/message'],
|
||||
function (gettext, $, _, Backbone, MessageView) {
|
||||
define(['gettext', 'jquery', 'underscore', 'backbone', 'js/views/message_banner'],
|
||||
function (gettext, $, _, Backbone, MessageBannerView) {
|
||||
|
||||
return Backbone.View.extend({
|
||||
|
||||
@@ -81,9 +81,8 @@
|
||||
|
||||
showError: function() {
|
||||
if (!this.messageView) {
|
||||
this.messageView = new MessageView({
|
||||
el: $('.coursewide-message-banner'),
|
||||
templateId: '#message_banner-tpl'
|
||||
this.messageView = new MessageBannerView({
|
||||
el: $('.message-banner')
|
||||
});
|
||||
}
|
||||
this.messageView.showMessage(this.errorMessage, this.errorIcon);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define(['gettext', 'jquery', 'underscore', 'backbone', 'logger', 'moment',
|
||||
'common/js/components/views/paging_header', 'common/js/components/views/paging_footer'],
|
||||
function (gettext, $, _, Backbone, Logger, _moment, PagingHeaderView, PagingFooterView) {
|
||||
'common/js/components/views/paging_header', 'common/js/components/views/paging_footer',
|
||||
'text!templates/bookmarks/bookmarks-list.underscore'
|
||||
],
|
||||
function (gettext, $, _, Backbone, Logger, _moment,
|
||||
PagingHeaderView, PagingFooterView, BookmarksListTemplate) {
|
||||
|
||||
var moment = _moment || window.moment;
|
||||
|
||||
@@ -24,7 +27,7 @@
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
this.template = _.template($('#bookmarks-list-tpl').text());
|
||||
this.template = _.template(BookmarksListTemplate);
|
||||
this.loadingMessageView = options.loadingMessageView;
|
||||
this.errorMessageView = options.errorMessageView;
|
||||
this.langCode = $(this.el).data('langCode');
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
;(function (define, undefined) {
|
||||
'use strict';
|
||||
define(['gettext', 'jquery', 'underscore', 'backbone', 'js/bookmarks/views/bookmarks_list',
|
||||
'js/bookmarks/collections/bookmarks', 'js/views/message'],
|
||||
function (gettext, $, _, Backbone, BookmarksListView, BookmarksCollection, MessageView) {
|
||||
'js/bookmarks/collections/bookmarks', 'js/views/message_banner'],
|
||||
function (gettext, $, _, Backbone, BookmarksListView, BookmarksCollection, MessageBannerView) {
|
||||
|
||||
return Backbone.View.extend({
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
this.bookmarksListView = new BookmarksListView(
|
||||
{
|
||||
collection: bookmarksCollection,
|
||||
loadingMessageView: new MessageView({el: $(this.loadingMessageElement)}),
|
||||
errorMessageView: new MessageView({el: $(this.errorMessageElement)})
|
||||
loadingMessageView: new MessageBannerView({el: $(this.loadingMessageElement)}),
|
||||
errorMessageView: new MessageBannerView({el: $(this.errorMessageElement)})
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
<div class="coursewide-message-banner" aria-live="polite"></div>
|
||||
<div class="message-banner" aria-live="polite"></div>
|
||||
|
||||
<div class="xblock xblock-student_view xblock-student_view-vertical xblock-initialized">
|
||||
<div class="bookmark-button-wrapper">
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
RequireJS.require([
|
||||
'jquery',
|
||||
'backbone',
|
||||
'js/search/course/search_app',
|
||||
'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, SearchApp, SearchRouter, CourseSearchForm, SearchCollection, CourseSearchResultsView) {
|
||||
'use strict';
|
||||
|
||||
var courseId = $('.courseware-results').data('courseId');
|
||||
var app = new SearchApp(
|
||||
courseId,
|
||||
SearchRouter,
|
||||
CourseSearchForm,
|
||||
SearchCollection,
|
||||
CourseSearchResultsView
|
||||
);
|
||||
Backbone.history.start();
|
||||
|
||||
});
|
||||
@@ -68,7 +68,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
};
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
|
||||
_.each([[addBookmarkedData, removeBookmarkData], [removeBookmarkData, addBookmarkedData]], function(actionsData) {
|
||||
var bookmarkedData = [[addBookmarkedData, removeBookmarkData], [removeBookmarkData, addBookmarkedData]];
|
||||
_.each(bookmarkedData, function(actionsData) {
|
||||
var firstActionData = actionsData[0];
|
||||
var secondActionData = actionsData[1];
|
||||
|
||||
@@ -110,13 +111,14 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
expect(secondActionData.event).toHaveBeenTriggeredOn(bookmarkButtonView.$el);
|
||||
|
||||
verifyBookmarkButtonState(bookmarkButtonView, firstActionData.bookmarked);
|
||||
bookmarkButtonView.undelegateEvents();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("shows an error message for HTTP 500", function () {
|
||||
var requests = AjaxHelpers.requests(this),
|
||||
$messageBanner = $('.coursewide-message-banner'),
|
||||
$messageBanner = $('.message-banner'),
|
||||
bookmarkButtonView = createBookmarkButtonView(false);
|
||||
bookmarkButtonView.$el.click();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ define(['backbone',
|
||||
loadFixtures('js/fixtures/bookmarks/bookmarks.html');
|
||||
TemplateHelpers.installTemplates(
|
||||
[
|
||||
'templates/message_view',
|
||||
'templates/fields/message_banner',
|
||||
'templates/bookmarks/bookmarks-list'
|
||||
]
|
||||
);
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
'_split': 'js/split',
|
||||
'mathjax_delay_renderer': 'coffee/src/mathjax_delay_renderer',
|
||||
'MathJaxProcessor': 'coffee/src/customwmd',
|
||||
'moment': 'xmodule_js/common_static/js/src/moment',
|
||||
'moment': 'xmodule_js/common_static/js/vendor/moment-with-locales.min',
|
||||
|
||||
// Manually specify LMS files that are not converted to RequireJS
|
||||
'history': 'js/vendor/history',
|
||||
@@ -75,7 +73,6 @@
|
||||
'js/vendor/jquery.qubit': 'js/vendor/jquery.qubit',
|
||||
'js/utils/navigation': 'js/utils/navigation',
|
||||
|
||||
|
||||
// Backbone classes loaded explicitly until they are converted to use RequireJS
|
||||
'js/models/notification': 'js/models/notification',
|
||||
'js/views/file_uploader': 'js/views/file_uploader',
|
||||
@@ -94,7 +91,7 @@
|
||||
'js/bookmarks/views/bookmarks_list_button': 'js/bookmarks/views/bookmarks_list_button',
|
||||
'js/bookmarks/views/bookmarks_list': 'js/bookmarks/views/bookmarks_list',
|
||||
'js/bookmarks/views/bookmark_button': 'js/bookmarks/views/bookmark_button',
|
||||
'js/views/message': 'js/views/message',
|
||||
'js/views/message_banner': 'js/views/message_banner',
|
||||
|
||||
// edxnotes
|
||||
'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min',
|
||||
@@ -744,11 +741,9 @@
|
||||
'lms/include/teams/js/spec/views/topics_spec.js',
|
||||
'lms/include/teams/js/spec/views/team_profile_header_actions_spec.js',
|
||||
'lms/include/js/spec/financial-assistance/financial_assistance_form_view_spec.js',
|
||||
'lms/include/teams/js/spec/views/team_join_spec.js'
|
||||
'lms/include/js/spec/discovery/discovery_spec.js',
|
||||
'lms/include/js/spec/ccx/schedule_spec.js',
|
||||
'lms/include/js/spec/bookmarks/bookmarks_list_view_spec.js',
|
||||
'lms/include/js/spec/bookmarks/bookmark_button_view_spec.js'
|
||||
'lms/include/js/spec/bookmarks/bookmark_button_view_spec.js',
|
||||
'lms/include/js/spec/views/message_banner_spec.js'
|
||||
]);
|
||||
|
||||
}).call(this, requirejs, define);
|
||||
|
||||
@@ -361,90 +361,20 @@ define([
|
||||
expect($('.search-button')).toBeVisible();
|
||||
}
|
||||
|
||||
function rendersSearchResults () {
|
||||
var searchResults = [{
|
||||
location: ['section', 'subsection', 'unit'],
|
||||
url: '/some/url/to/content',
|
||||
content_type: 'text',
|
||||
course_name: '',
|
||||
excerpt: 'this is a short excerpt'
|
||||
}];
|
||||
this.collection.set(searchResults);
|
||||
this.collection.latestModelsCount = 1;
|
||||
this.collection.totalCount = 1;
|
||||
|
||||
this.resultsView.render();
|
||||
expect(this.resultsView.$el.find('ol')[0]).toExist();
|
||||
expect(this.resultsView.$el.find('li').length).toEqual(1);
|
||||
expect(this.resultsView.$el).toContainHtml('Search Results');
|
||||
expect(this.resultsView.$el).toContainHtml('this is a short excerpt');
|
||||
|
||||
this.collection.set(searchResults);
|
||||
this.collection.totalCount = 2;
|
||||
this.resultsView.renderNext();
|
||||
expect(this.resultsView.$el.find('.search-count')).toContainHtml('2');
|
||||
expect(this.resultsView.$el.find('li').length).toEqual(2);
|
||||
}
|
||||
|
||||
function showsMoreResultsLink () {
|
||||
this.collection.totalCount = 123;
|
||||
this.collection.hasNextPage = function () { return true; };
|
||||
this.resultsView.render();
|
||||
expect(this.resultsView.$el.find('a.search-load-next')[0]).toExist();
|
||||
|
||||
this.collection.totalCount = 123;
|
||||
this.collection.hasNextPage = function () { return false; };
|
||||
this.resultsView.render();
|
||||
expect(this.resultsView.$el.find('a.search-load-next')[0]).not.toExist();
|
||||
}
|
||||
|
||||
function triggersNextPageEvent () {
|
||||
var onNext = jasmine.createSpy('onNext');
|
||||
this.resultsView.on('next', onNext);
|
||||
this.collection.totalCount = 123;
|
||||
this.collection.hasNextPage = function () { return true; };
|
||||
this.resultsView.render();
|
||||
this.resultsView.$el.find('a.search-load-next').click();
|
||||
expect(onNext).toHaveBeenCalled();
|
||||
}
|
||||
|
||||
function showsLoadMoreSpinner () {
|
||||
this.collection.totalCount = 123;
|
||||
this.collection.hasNextPage = function () { return true; };
|
||||
this.resultsView.render();
|
||||
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' });
|
||||
this.resultsView.renderNext();
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
|
||||
}
|
||||
|
||||
function beforeEachHelper(SearchResultsView) {
|
||||
appendSetFixtures(
|
||||
'<section id="courseware-search-results"></section>' +
|
||||
'<section id="course-content"></section>' +
|
||||
'<section id="dashboard-search-results"></section>' +
|
||||
'<section id="my-courses"></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_list',
|
||||
'templates/search/search_loading',
|
||||
'templates/search/search_error'
|
||||
]);
|
||||
|
||||
var MockCollection = Backbone.Collection.extend({
|
||||
hasNextPage: function () {},
|
||||
latestModelsCount: 0,
|
||||
pageSize: 20,
|
||||
latestModels: function () {
|
||||
return SearchCollection.prototype.latestModels.apply(this, arguments);
|
||||
}
|
||||
describe('CourseSearchForm', 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 () {
|
||||
@@ -557,12 +487,12 @@ define([
|
||||
|
||||
function beforeEachHelper(SearchResultsView) {
|
||||
appendSetFixtures(
|
||||
'<section id="courseware-search-results"></section>' +
|
||||
'<div class="courseware-results"></div>' +
|
||||
'<section id="course-content"></section>' +
|
||||
'<section id="dashboard-search-results"></section>' +
|
||||
'<section id="my-courses"></section>'
|
||||
);
|
||||
|
||||
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/search/course_search_item',
|
||||
'templates/search/dashboard_search_item',
|
||||
@@ -573,12 +503,6 @@ define([
|
||||
'templates/search/search_error'
|
||||
]);
|
||||
|
||||
var courseId = 'a/b/c';
|
||||
CourseSearchFactory(courseId);
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
this.$contentElement = $('#course-content');
|
||||
this.$searchResults = $('.courseware-results');
|
||||
|
||||
var MockCollection = Backbone.Collection.extend({
|
||||
hasNextPage: function () {},
|
||||
latestModelsCount: 0,
|
||||
@@ -749,7 +673,7 @@ define([
|
||||
beforeEach(function () {
|
||||
loadFixtures('js/fixtures/search/course_search_form.html');
|
||||
appendSetFixtures(
|
||||
'<section id="courseware-search-results"></section>' +
|
||||
'<div class="courseware-results"></div>' +
|
||||
'<section id="course-content"></section>'
|
||||
);
|
||||
loadTemplates.call(this);
|
||||
@@ -758,7 +682,7 @@ define([
|
||||
CourseSearchFactory(courseId);
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
this.$contentElement = $('#course-content');
|
||||
this.$searchResults = $('#courseware-search-results');
|
||||
this.$searchResults = $('.courseware-results');
|
||||
});
|
||||
|
||||
it('shows loading message on search', showsLoadingMessage);
|
||||
@@ -825,4 +749,4 @@ define([
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
'js/student_profile/views/learner_profile_view',
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_factory',
|
||||
'js/views/message'
|
||||
'js/views/message_banner'
|
||||
],
|
||||
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews,
|
||||
UserAccountModel, UserPreferencesModel, LearnerProfileView, LearnerProfileFields, LearnerProfilePage) {
|
||||
|
||||
@@ -2,10 +2,10 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
'js/spec/student_account/helpers',
|
||||
'js/student_account/models/user_account_model',
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/views/message'
|
||||
'js/views/message_banner'
|
||||
],
|
||||
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, UserAccountModel, LearnerProfileFields,
|
||||
MessageView) {
|
||||
MessageBannerView) {
|
||||
'use strict';
|
||||
|
||||
describe("edx.user.LearnerProfileFields", function () {
|
||||
@@ -31,9 +31,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
|
||||
accountSettingsModel.url = Helpers.USER_ACCOUNTS_API_URL;
|
||||
|
||||
var messageView = new MessageView({
|
||||
el: $('.message-banner'),
|
||||
templateId: '#message_banner-tpl'
|
||||
var messageView = new MessageBannerView({
|
||||
el: $('.message-banner')
|
||||
});
|
||||
|
||||
return new LearnerProfileFields.ProfileImageFieldView({
|
||||
|
||||
@@ -7,11 +7,11 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_view',
|
||||
'js/student_account/views/account_settings_fields',
|
||||
'js/views/message'
|
||||
'js/views/message_banner'
|
||||
],
|
||||
function (Backbone, $, _, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, FieldViews,
|
||||
UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView,
|
||||
AccountSettingsFieldViews, MessageView) {
|
||||
AccountSettingsFieldViews, MessageBannerView) {
|
||||
'use strict';
|
||||
|
||||
describe("edx.user.LearnerProfileView", function () {
|
||||
@@ -45,9 +45,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
accountSettingsPageUrl: '/account/settings/'
|
||||
});
|
||||
|
||||
var messageView = new MessageView({
|
||||
el: $('.message-banner'),
|
||||
templateId: '#message_banner-tpl'
|
||||
var messageView = new MessageBannerView({
|
||||
el: $('.message-banner')
|
||||
});
|
||||
|
||||
var profileImageFieldView = new LearnerProfileFields.ProfileImageFieldView({
|
||||
|
||||
@@ -1,46 +1,28 @@
|
||||
define(['backbone', 'jquery', 'underscore', 'js/views/message', 'js/common_helpers/template_helpers'
|
||||
define(['backbone', 'jquery', 'underscore',
|
||||
'common/js/spec_helpers/template_helpers', 'js/views/message_banner'
|
||||
],
|
||||
function (Backbone, $, _, MessageView, TemplateHelpers) {
|
||||
function (Backbone, $, _, TemplateHelpers, MessageBannerView) {
|
||||
'use strict';
|
||||
|
||||
describe("MessageView", function () {
|
||||
|
||||
var messageEl = '.message-banner';
|
||||
describe("MessageBannerView", function () {
|
||||
|
||||
beforeEach(function () {
|
||||
setFixtures('<div class="message-banner"></div>');
|
||||
TemplateHelpers.installTemplate("templates/fields/message_banner");
|
||||
TemplateHelpers.installTemplate("templates/message_view");
|
||||
});
|
||||
|
||||
var createMessageView = function (messageContainer, templateId) {
|
||||
return new MessageView({
|
||||
el: $(messageContainer),
|
||||
templateId: templateId
|
||||
it('renders message correctly', function() {
|
||||
var messageSelector = '.message-banner';
|
||||
var messageView = new MessageBannerView({
|
||||
el: $(messageSelector)
|
||||
});
|
||||
};
|
||||
|
||||
it('renders correctly with the /fields/message_banner template', function() {
|
||||
var messageView = createMessageView(messageSelector, '#message_banner-tpl');
|
||||
|
||||
messageView.showMessage('I am message view');
|
||||
expect($(messageEl).text().trim()).toBe('I am message view');
|
||||
// Verify error message
|
||||
expect($(messageSelector).text().trim()).toBe('I am message view');
|
||||
|
||||
messageView.hideMessage();
|
||||
expect($(messageEl).text().trim()).toBe('');
|
||||
});
|
||||
|
||||
it('renders correctly with the /message_view template', function() {
|
||||
var messageView = createMessageView(messageEl, '#message-tpl');
|
||||
var icon = '<i class="fa fa-thumbs-up"></i>';
|
||||
|
||||
messageView.showMessage('I am message view', icon);
|
||||
|
||||
expect($(messageEl).text().trim()).toBe('I am message view');
|
||||
expect($(messageEl).html()).toContain(icon);
|
||||
|
||||
messageView.hideMessage();
|
||||
expect($(messageEl).text().trim()).toBe('');
|
||||
expect($(messageSelector).text().trim()).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_view',
|
||||
'js/student_account/views/account_settings_fields',
|
||||
'js/views/message',
|
||||
'js/views/message_banner',
|
||||
'string_utils'
|
||||
], function (gettext, $, _, Backbone, Logger, AccountSettingsModel, AccountPreferencesModel, FieldsView,
|
||||
LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews, MessageView) {
|
||||
LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews, MessageBannerView) {
|
||||
|
||||
return function (options) {
|
||||
|
||||
@@ -36,9 +36,8 @@
|
||||
|
||||
var editable = options.own_profile ? 'toggle' : 'never';
|
||||
|
||||
var messageView = new MessageView({
|
||||
el: $('.message-banner'),
|
||||
templateId: '#message_banner-tpl'
|
||||
var messageView = new MessageBannerView({
|
||||
el: $('.message-banner')
|
||||
});
|
||||
|
||||
var accountPrivacyFieldView = new LearnerProfileFieldsView.AccountPrivacyFieldView({
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
var onVideoFail = function(e) {
|
||||
if(e === 'NO_DEVICES_FOUND') {
|
||||
$('#no-webcam').show();
|
||||
$('#face_capture_button').hide();
|
||||
$('#photo_id_capture_button').hide();
|
||||
}
|
||||
else {
|
||||
console.log('Failed to get camera access!', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if we are capable of video capture (regardless of whether the
|
||||
// user has given permission).
|
||||
function initVideoCapture() {
|
||||
window.URL = window.URL || window.webkitURL;
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
return !(navigator.getUserMedia === undefined);
|
||||
}
|
||||
|
||||
var submitReverificationPhotos = function() {
|
||||
// add photos to the form
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'face_image',
|
||||
value: $("#face_image")[0].src,
|
||||
}).appendTo("#reverify_form");
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'photo_id_image',
|
||||
value: $("#photo_id_image")[0].src,
|
||||
}).appendTo("#reverify_form");
|
||||
|
||||
$("#reverify_form").submit();
|
||||
|
||||
};
|
||||
|
||||
var submitMidcourseReverificationPhotos = function() {
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'face_image',
|
||||
value: $("#face_image")[0].src,
|
||||
}).appendTo("#reverify_form");
|
||||
$("#reverify_form").submit();
|
||||
};
|
||||
|
||||
function showSubmissionError() {
|
||||
if (xhr.status === 400) {
|
||||
$('#order-error .copy p').html(xhr.responseText);
|
||||
}
|
||||
$('#order-error').show();
|
||||
$("html, body").animate({ scrollTop: 0 });
|
||||
}
|
||||
|
||||
function submitForm(data) {
|
||||
for (prop in data) {
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: prop,
|
||||
value: data[prop]
|
||||
}).appendTo('#pay_form');
|
||||
}
|
||||
$("#pay_form").submit();
|
||||
}
|
||||
|
||||
function refereshPageMessage() {
|
||||
$('#photo-error').show();
|
||||
$("html, body").animate({ scrollTop: 0 });
|
||||
}
|
||||
|
||||
var submitToPaymentProcessing = function() {
|
||||
$(".payment-button").addClass('is-disabled').attr('aria-disabled', true);
|
||||
var contribution_input = $("input[name='contribution']:checked");
|
||||
var contribution = 0;
|
||||
if(contribution_input.attr('id') == 'contribution-other') {
|
||||
contribution = $("input[name='contribution-other-amt']").val();
|
||||
}
|
||||
else {
|
||||
contribution = contribution_input.val();
|
||||
}
|
||||
var course_id = $("input[name='course_id']").val();
|
||||
$.ajax({
|
||||
url: "/verify_student/create_order",
|
||||
type: 'POST',
|
||||
data: {
|
||||
"course_id" : course_id,
|
||||
"contribution": contribution,
|
||||
"face_image" : $("#face_image")[0].src,
|
||||
"photo_id_image" : $("#photo_id_image")[0].src
|
||||
},
|
||||
success:function(data) {
|
||||
if (data.success) {
|
||||
submitForm(data);
|
||||
} else {
|
||||
refereshPageMessage();
|
||||
}
|
||||
},
|
||||
error:function() {
|
||||
$(".payment-button").removeClass('is-disabled').attr('aria-disabled', false);
|
||||
showSubmissionError();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function doResetButton(resetButton, captureButton, approveButton, nextButtonNav, nextLink) {
|
||||
approveButton.removeClass('approved');
|
||||
nextButtonNav.addClass('is-not-ready');
|
||||
nextLink.attr('href', "#");
|
||||
|
||||
captureButton.show();
|
||||
resetButton.hide();
|
||||
approveButton.hide();
|
||||
}
|
||||
|
||||
function doApproveButton(approveButton, nextButtonNav, nextLink) {
|
||||
nextButtonNav.removeClass('is-not-ready');
|
||||
approveButton.addClass('approved');
|
||||
nextLink.attr('href', "#next");
|
||||
}
|
||||
|
||||
function doSnapshotButton(captureButton, resetButton, approveButton) {
|
||||
captureButton.hide();
|
||||
resetButton.show();
|
||||
approveButton.show();
|
||||
}
|
||||
|
||||
function submitNameChange(event) {
|
||||
event.preventDefault();
|
||||
$("#lean_overlay").fadeOut(200);
|
||||
$("#edit-name").css({ 'display' : 'none' });
|
||||
var full_name = $('input[name="name"]').val();
|
||||
var xhr = $.post(
|
||||
"/change_name",
|
||||
{
|
||||
"new_name" : full_name,
|
||||
"rationale": "Want to match ID for ID Verified Certificates."
|
||||
},
|
||||
function() {
|
||||
$('#full-name').html(full_name);
|
||||
}
|
||||
)
|
||||
.fail(function(jqXhr) {
|
||||
$('.message-copy').html(jqXhr.responseText);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function initSnapshotHandler(names, hasHtml5CameraSupport) {
|
||||
var name = names.pop();
|
||||
if (name == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var video = $('#' + name + '_video');
|
||||
var canvas = $('#' + name + '_canvas');
|
||||
var image = $('#' + name + "_image");
|
||||
var captureButton = $("#" + name + "_capture_button");
|
||||
var resetButton = $("#" + name + "_reset_button");
|
||||
var approveButton = $("#" + name + "_approve_button");
|
||||
var nextButtonNav = $("#" + name + "_next_button_nav");
|
||||
var nextLink = $("#" + name + "_next_link");
|
||||
var flashCapture = $("#" + name + "_flash");
|
||||
|
||||
var ctx = null;
|
||||
if (hasHtml5CameraSupport) {
|
||||
ctx = canvas[0].getContext('2d');
|
||||
}
|
||||
|
||||
var localMediaStream = null;
|
||||
|
||||
function snapshot(event) {
|
||||
if (hasHtml5CameraSupport) {
|
||||
if (localMediaStream) {
|
||||
ctx.drawImage(video[0], 0, 0);
|
||||
image[0].src = canvas[0].toDataURL('image/png');
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
video[0].pause();
|
||||
}
|
||||
else {
|
||||
if (flashCapture[0].cameraAuthorized()) {
|
||||
image[0].src = flashCapture[0].snap();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
doSnapshotButton(captureButton, resetButton, approveButton);
|
||||
return false;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
image[0].src = "";
|
||||
|
||||
if (hasHtml5CameraSupport) {
|
||||
video[0].play();
|
||||
}
|
||||
else {
|
||||
flashCapture[0].reset();
|
||||
}
|
||||
|
||||
doResetButton(resetButton, captureButton, approveButton, nextButtonNav, nextLink);
|
||||
return false;
|
||||
}
|
||||
|
||||
function approve() {
|
||||
doApproveButton(approveButton, nextButtonNav, nextLink);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize state for this picture taker
|
||||
captureButton.show();
|
||||
resetButton.hide();
|
||||
approveButton.hide();
|
||||
nextButtonNav.addClass('is-not-ready');
|
||||
nextLink.attr('href', "#");
|
||||
|
||||
// Connect event handlers...
|
||||
video.click(snapshot);
|
||||
captureButton.click(snapshot);
|
||||
resetButton.click(reset);
|
||||
approveButton.click(approve);
|
||||
|
||||
// If it's flash-based, we can just immediate initialize the next one.
|
||||
// If it's HTML5 based, we have to do it in the callback from getUserMedia
|
||||
// so that Firefox doesn't eat the second request.
|
||||
if (hasHtml5CameraSupport) {
|
||||
navigator.getUserMedia({video: true}, function(stream) {
|
||||
video[0].src = window.URL.createObjectURL(stream);
|
||||
localMediaStream = stream;
|
||||
|
||||
// We do this in a recursive call on success because Firefox seems to
|
||||
// simply eat the request if you stack up two on top of each other before
|
||||
// the user has a chance to approve the first one.
|
||||
//
|
||||
// This appears to be necessary for older versions of Firefox (before 28).
|
||||
// For more info, see https://github.com/edx/edx-platform/pull/3053
|
||||
initSnapshotHandler(names, hasHtml5CameraSupport);
|
||||
}, onVideoFail);
|
||||
}
|
||||
else {
|
||||
initSnapshotHandler(names, hasHtml5CameraSupport);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function browserHasFlash() {
|
||||
var hasFlash = false;
|
||||
try {
|
||||
var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
|
||||
if(fo) hasFlash = true;
|
||||
} catch(e) {
|
||||
if(navigator.mimeTypes["application/x-shockwave-flash"] != undefined) hasFlash = true;
|
||||
}
|
||||
return hasFlash;
|
||||
}
|
||||
|
||||
function objectTagForFlashCamera(name) {
|
||||
// detect whether or not flash is available
|
||||
if(browserHasFlash()) {
|
||||
// I manually update this to have ?v={2,3,4, etc} to avoid caching of flash
|
||||
// objects on local dev.
|
||||
return '<object type="application/x-shockwave-flash" id="' +
|
||||
name + '" name="' + name + '" data=' +
|
||||
'"/static/js/verify_student/CameraCapture.swf?v=3"' +
|
||||
'width="500" height="375"><param name="quality" ' +
|
||||
'value="high"><param name="allowscriptaccess" ' +
|
||||
'value="sameDomain"></object>';
|
||||
}
|
||||
else {
|
||||
// display a message informing the user to install flash
|
||||
$('#no-flash').show();
|
||||
}
|
||||
}
|
||||
|
||||
function waitForFlashLoad(func, flash_object) {
|
||||
if(!flash_object.hasOwnProperty('percentLoaded') || flash_object.percentLoaded() < 100){
|
||||
setTimeout(function() {
|
||||
waitForFlashLoad(func, flash_object);
|
||||
},
|
||||
50);
|
||||
}
|
||||
else {
|
||||
func(flash_object);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(".carousel-nav").addClass('sr');
|
||||
$(".payment-button").click(function(){
|
||||
analytics.pageview("Payment Form");
|
||||
submitToPaymentProcessing();
|
||||
});
|
||||
|
||||
$("#reverify_button").click(function() {
|
||||
submitReverificationPhotos();
|
||||
});
|
||||
|
||||
$("#midcourse_reverify_button").click(function() {
|
||||
submitMidcourseReverificationPhotos();
|
||||
});
|
||||
|
||||
// prevent browsers from keeping this button checked
|
||||
$("#confirm_pics_good").prop("checked", false);
|
||||
$("#confirm_pics_good").change(function() {
|
||||
$(".payment-button").toggleClass('disabled');
|
||||
$("#reverify_button").toggleClass('disabled');
|
||||
$("#midcourse_reverify_button").toggleClass('disabled');
|
||||
});
|
||||
|
||||
|
||||
// add in handlers to add/remove the correct classes to the body
|
||||
// when moving between steps
|
||||
$('#face_next_link').click(function(){
|
||||
analytics.pageview("Capture ID Photo");
|
||||
$('#photo-error').hide();
|
||||
$('body').addClass('step-photos-id').removeClass('step-photos-cam');
|
||||
});
|
||||
|
||||
$('#photo_id_next_link').click(function(){
|
||||
analytics.pageview("Review Photos");
|
||||
$('body').addClass('step-review').removeClass('step-photos-id');
|
||||
});
|
||||
|
||||
// set up edit information dialog
|
||||
$('#edit-name div[role="alert"]').hide();
|
||||
$('#edit-name .action-save').click(submitNameChange);
|
||||
|
||||
var hasHtml5CameraSupport = initVideoCapture();
|
||||
|
||||
// If HTML5 WebRTC capture is not supported, we initialize jpegcam
|
||||
if (!hasHtml5CameraSupport) {
|
||||
$("#face_capture_div").html(objectTagForFlashCamera("face_flash"));
|
||||
$("#photo_id_capture_div").html(objectTagForFlashCamera("photo_id_flash"));
|
||||
// wait for the flash object to be loaded and then check for a camera
|
||||
if(browserHasFlash()) {
|
||||
waitForFlashLoad(function(flash_object) {
|
||||
if(!flash_object.hasOwnProperty('hasCamera')){
|
||||
onVideoFail('NO_DEVICES_FOUND');
|
||||
}
|
||||
}, $('#face_flash')[0]);
|
||||
}
|
||||
}
|
||||
|
||||
analytics.pageview("Capture Face Photo");
|
||||
initSnapshotHandler(["photo_id", "face"], hasHtml5CameraSupport);
|
||||
|
||||
$('a[rel="external"]').attr({
|
||||
title: gettext('This link will open in a new browser window/tab'),
|
||||
target: '_blank'
|
||||
});
|
||||
|
||||
});
|
||||
@@ -17,17 +17,15 @@
|
||||
if (_.isUndefined(this.message) || _.isNull(this.message)) {
|
||||
this.$el.html('');
|
||||
} else {
|
||||
this.$el.html(this.template({
|
||||
message: this.message,
|
||||
icon: this.icon
|
||||
}));
|
||||
this.$el.html(_.template(messageBannerTemplate, _.extend(this.options, {
|
||||
message: this.message
|
||||
})));
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
showMessage: function (message, icon) {
|
||||
showMessage: function (message) {
|
||||
this.message = message;
|
||||
this.icon = icon;
|
||||
this.render();
|
||||
},
|
||||
|
||||
@@ -62,7 +62,6 @@ lib_paths:
|
||||
- xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min.js
|
||||
- xmodule_js/common_static/js/test/i18n.js
|
||||
- xmodule_js/common_static/js/vendor/date.js
|
||||
- xmodule_js/common_static/js/vendor/moment-with-locales.min.js
|
||||
- xmodule_js/common_static/js/vendor/moment.min.js
|
||||
- xmodule_js/common_static/js/vendor/moment-with-locales.min.js
|
||||
- xmodule_js/common_static/common/js/utils/edx.utils.validate.js
|
||||
@@ -117,7 +116,6 @@ fixture_paths:
|
||||
- support/templates
|
||||
- js/fixtures/bookmarks
|
||||
- templates/bookmarks
|
||||
- templates/message_view.underscore
|
||||
|
||||
requirejs:
|
||||
paths:
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
'teams/js/teams_tab_factory',
|
||||
'support/js/certificates_factory',
|
||||
'support/js/enrollment_factory',
|
||||
'js/bookmarks/bookmarks_factory'
|
||||
]),
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
;(function (require, define) {
|
||||
var paths = {}, config;
|
||||
|
||||
// jquery, underscore, gettext, URI, tinymce, or jquery.tinymce may already
|
||||
// have been loaded and we do not want to load them a second time. Check if
|
||||
// it is the case and use the global var instead.
|
||||
if (window.jQuery) {
|
||||
define("jquery", [], function() {return window.jQuery;});
|
||||
} else {
|
||||
paths.jquery = "js/vendor/jquery.min";
|
||||
}
|
||||
if (window._) {
|
||||
define("underscore", [], function() {return window._;});
|
||||
} else {
|
||||
paths.jquery = "js/vendor/underscore-min";
|
||||
}
|
||||
if (window.gettext) {
|
||||
define("gettext", [], function() {return window.gettext;});
|
||||
} else {
|
||||
paths.gettext = "/i18n";
|
||||
}
|
||||
if (window.Logger) {
|
||||
define("logger", [], function() {return window.Logger;});
|
||||
} else {
|
||||
paths.logger = "js/src/logger";
|
||||
}
|
||||
if (window.URI) {
|
||||
define("URI", [], function() {return window.URI;});
|
||||
} else {
|
||||
paths.URI = "js/vendor/URI.min";
|
||||
}
|
||||
if (window.tinymce) {
|
||||
define('tinymce', [], function() {return window.tinymce;});
|
||||
} else {
|
||||
paths.tinymce = "js/vendor/tinymce/js/tinymce/tinymce.full.min";
|
||||
}
|
||||
if (window.jquery && window.jquery.tinymce) {
|
||||
define("jquery.tinymce", [], function() {return window.jquery.tinymce;});
|
||||
} else {
|
||||
paths.tinymce = "js/vendor/tinymce/js/tinymce/jquery.tinymce.min";
|
||||
}
|
||||
|
||||
config = {
|
||||
// NOTE: baseUrl has been previously set in lms/static/templates/main.html
|
||||
waitSeconds: 60,
|
||||
paths: {
|
||||
"annotator_1.2.9": "js/vendor/edxnotes/annotator-full.min",
|
||||
"date": "js/vendor/date",
|
||||
"text": 'js/vendor/requirejs/text',
|
||||
"backbone": "js/vendor/backbone-min",
|
||||
"backbone-super": "js/vendor/backbone-super",
|
||||
"backbone.paginator": "js/vendor/backbone.paginator.min",
|
||||
"underscore.string": "js/vendor/underscore.string.min",
|
||||
// Files needed by OVA
|
||||
"annotator": "js/vendor/ova/annotator-full",
|
||||
"annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth",
|
||||
"video.dev": "js/vendor/ova/video.dev",
|
||||
"vjs.youtube": 'js/vendor/ova/vjs.youtube',
|
||||
"rangeslider": 'js/vendor/ova/rangeslider',
|
||||
"share-annotator": 'js/vendor/ova/share-annotator',
|
||||
"richText-annotator": 'js/vendor/ova/richText-annotator',
|
||||
"reply-annotator": 'js/vendor/ova/reply-annotator',
|
||||
"grouping-annotator": 'js/vendor/ova/grouping-annotator',
|
||||
"tags-annotator": 'js/vendor/ova/tags-annotator',
|
||||
"diacritic-annotator": 'js/vendor/ova/diacritic-annotator',
|
||||
"flagging-annotator": 'js/vendor/ova/flagging-annotator',
|
||||
"jquery-Watch": 'js/vendor/ova/jquery-Watch',
|
||||
"openseadragon": 'js/vendor/ova/openseadragon',
|
||||
"osda": 'js/vendor/ova/OpenSeaDragonAnnotation',
|
||||
"ova": 'js/vendor/ova/ova',
|
||||
"catch": 'js/vendor/ova/catch/js/catch',
|
||||
"handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2',
|
||||
"moment": "js/vendor/moment-with-locales.min"
|
||||
// end of files needed by OVA
|
||||
},
|
||||
shim: {
|
||||
"annotator_1.2.9": {
|
||||
deps: ["jquery"],
|
||||
exports: "Annotator"
|
||||
},
|
||||
"date": {
|
||||
exports: "Date"
|
||||
},
|
||||
"jquery": {
|
||||
exports: "$"
|
||||
},
|
||||
"underscore": {
|
||||
exports: "_"
|
||||
},
|
||||
"backbone": {
|
||||
deps: ["underscore", "jquery"],
|
||||
exports: "Backbone"
|
||||
},
|
||||
"backbone.paginator": {
|
||||
deps: ["backbone"],
|
||||
exports: "Backbone.Paginator"
|
||||
},
|
||||
"backbone-super": {
|
||||
deps: ["backbone"]
|
||||
},
|
||||
"logger": {
|
||||
exports: "Logger"
|
||||
},
|
||||
// Needed by OVA
|
||||
"video.dev": {
|
||||
exports:"videojs"
|
||||
},
|
||||
"vjs.youtube": {
|
||||
deps: ["video.dev"]
|
||||
},
|
||||
"rangeslider": {
|
||||
deps: ["video.dev"]
|
||||
},
|
||||
"annotator": {
|
||||
exports: "Annotator"
|
||||
},
|
||||
"annotator-harvardx":{
|
||||
deps: ["annotator"]
|
||||
},
|
||||
"share-annotator": {
|
||||
deps: ["annotator"]
|
||||
},
|
||||
"richText-annotator": {
|
||||
deps: ["annotator", "tinymce"]
|
||||
},
|
||||
"reply-annotator": {
|
||||
deps: ["annotator"]
|
||||
},
|
||||
"tags-annotator": {
|
||||
deps: ["annotator"]
|
||||
},
|
||||
"diacritic-annotator": {
|
||||
deps: ["annotator"]
|
||||
},
|
||||
"flagging-annotator": {
|
||||
deps: ["annotator"]
|
||||
},
|
||||
"grouping-annotator": {
|
||||
deps: ["annotator"]
|
||||
},
|
||||
"ova": {
|
||||
exports: "ova",
|
||||
deps: [
|
||||
"annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator",
|
||||
"richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator",
|
||||
"grouping-annotator", "diacritic-annotator", "jquery-Watch", "catch", "handlebars", "URI"
|
||||
]
|
||||
},
|
||||
"osda": {
|
||||
exports: "osda",
|
||||
deps: [
|
||||
"annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator",
|
||||
"richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator",
|
||||
"grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars",
|
||||
"URI"
|
||||
]
|
||||
}
|
||||
// End of needed by OVA
|
||||
}
|
||||
};
|
||||
|
||||
for (var key in paths) {
|
||||
if ({}.hasOwnProperty.call(paths, key)) {
|
||||
config.paths[key] = paths[key];
|
||||
}
|
||||
}
|
||||
require.config(config);
|
||||
}).call(this, require || RequireJS.require, define || RequireJS.define);
|
||||
@@ -25,12 +25,6 @@ ${page_title_breadcrumbs(course_name())}
|
||||
|
||||
<%block name="header_extras">
|
||||
|
||||
% for template_name in ["message_banner"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="fields/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% for template_name in ["image-modal"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="common/templates/${template_name}.underscore" />
|
||||
@@ -53,18 +47,6 @@ ${page_title_breadcrumbs(course_name())}
|
||||
% endfor
|
||||
% endif
|
||||
|
||||
% for template_name in ["bookmarks-list"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="bookmarks/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
% for template_name in ["message_view"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
|
||||
</%block>
|
||||
|
||||
<%block name="headextra">
|
||||
@@ -92,11 +74,15 @@ ${page_title_breadcrumbs(course_name())}
|
||||
<%static:js group='discussion'/>
|
||||
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
|
||||
<%static:require_module module_name="js/search/course/course_search_factory" class_name="CourseSearchFactory">
|
||||
var courseId = $('#courseware-search-results').data('courseId');
|
||||
var courseId = $('.courseware-results').data('courseId');
|
||||
CourseSearchFactory(courseId);
|
||||
</%static:require_module>
|
||||
% endif
|
||||
|
||||
<%static:require_module module_name="js/bookmarks/bookmarks_factory" class_name="BookmarksFactory">
|
||||
BookmarksFactory();
|
||||
</%static:require_module>
|
||||
|
||||
<%include file="../discussion/_js_body_dependencies.html" />
|
||||
% if staff_access:
|
||||
<%include file="xqa_interface.html"/>
|
||||
@@ -131,7 +117,7 @@ ${fragment.foot_html()}
|
||||
|
||||
</%block>
|
||||
|
||||
<div class="coursewide-message-banner" aria-live="polite"></div>
|
||||
<div class="message-banner" aria-live="polite"></div>
|
||||
|
||||
% if default_tab:
|
||||
<%include file="/courseware/course_navigation.html" />
|
||||
@@ -144,17 +130,6 @@ ${fragment.foot_html()}
|
||||
|
||||
% if disable_accordion is UNDEFINED or not disable_accordion:
|
||||
<div class="course-index">
|
||||
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
|
||||
<div id="courseware-search-bar" class="search-bar courseware-search-bar" role="search" aria-label="Course">
|
||||
<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"/>
|
||||
<button type="submit" class="search-button">
|
||||
${_('search')} <i class="icon fa fa-search" aria-hidden="true"></i>
|
||||
<header id="open_close_accordion">
|
||||
<a href="#">${_("close")}</a>
|
||||
</header>
|
||||
|
||||
<div class="wrapper-course-modes">
|
||||
|
||||
@@ -181,12 +156,10 @@ ${fragment.foot_html()}
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<div class="accordion">
|
||||
<nav class="course-navigation" aria-label="${_('Course')}">
|
||||
</div>
|
||||
|
||||
<div id="accordion" style="display: none">
|
||||
<nav aria-label="${_('Course Navigation')}">
|
||||
<div class="accordion">
|
||||
<nav class="course-navigation" aria-label="${_('Course')}">
|
||||
% if accordion.strip():
|
||||
${accordion}
|
||||
% else:
|
||||
@@ -231,11 +204,11 @@ ${fragment.foot_html()}
|
||||
${fragment.body_html()}
|
||||
</section>
|
||||
|
||||
<section class="courseware-results-wrapper">
|
||||
<div id="loading-message" aria-live="polite" aria-relevant="all"></div>
|
||||
<div id="error-message" aria-live="polite"></div>
|
||||
<div class="courseware-results search-results" data-course-id="${course.id}" data-lang-code="${language_preference}"></div>
|
||||
</section>
|
||||
<section class="courseware-results-wrapper">
|
||||
<div id="loading-message" aria-live="polite" aria-relevant="all"></div>
|
||||
<div id="error-message" aria-live="polite"></div>
|
||||
<div class="courseware-results search-results" data-course-id="${course.id}" data-lang-code="${language_preference}"></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<%= icon %><%= message %>
|
||||
@@ -1,80 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import model_utils.fields
|
||||
import xmodule_django.models
|
||||
import jsonfield.fields
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'Bookmark'
|
||||
db.create_table('bookmarks_bookmark', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)),
|
||||
('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('course_key', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
|
||||
('usage_key', self.gf('xmodule_django.models.LocationKeyField')(max_length=255, db_index=True)),
|
||||
('display_name', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
|
||||
('path', self.gf('jsonfield.fields.JSONField')()),
|
||||
))
|
||||
db.send_create_signal('bookmarks', ['Bookmark'])
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'Bookmark'
|
||||
db.delete_table('bookmarks_bookmark')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'bookmarks.bookmark': {
|
||||
'Meta': {'object_name': 'Bookmark'},
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'display_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'path': ('jsonfield.fields.JSONField', [], {}),
|
||||
'usage_key': ('xmodule_django.models.LocationKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['bookmarks']
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Bookmark',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('course_key', xmodule_django.models.CourseKeyField(max_length=255, db_index=True)),
|
||||
('usage_key', xmodule_django.models.LocationKeyField(max_length=255, db_index=True)),
|
||||
('_path', jsonfield.fields.JSONField(help_text=b'Path in course tree to the block', db_column=b'path')),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='XBlockCache',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
|
||||
('course_key', xmodule_django.models.CourseKeyField(max_length=255, db_index=True)),
|
||||
('usage_key', xmodule_django.models.LocationKeyField(unique=True, max_length=255, db_index=True)),
|
||||
('display_name', models.CharField(default=b'', max_length=255)),
|
||||
('_paths', jsonfield.fields.JSONField(default=[], help_text=b'All paths in course tree to the corresponding block.', db_column=b'paths')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='bookmark',
|
||||
name='xblock_cache',
|
||||
field=models.ForeignKey(to='bookmarks.XBlockCache'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='bookmark',
|
||||
unique_together=set([('user', 'usage_key')]),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'XBlockCache'
|
||||
db.create_table('bookmarks_xblockcache', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)),
|
||||
('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)),
|
||||
('course_key', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
|
||||
('usage_key', self.gf('xmodule_django.models.LocationKeyField')(unique=True, max_length=255, db_index=True)),
|
||||
('display_name', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
|
||||
('_paths', self.gf('jsonfield.fields.JSONField')(default=[], db_column='paths')),
|
||||
))
|
||||
db.send_create_signal('bookmarks', ['XBlockCache'])
|
||||
|
||||
# Deleting field 'Bookmark.display_name'
|
||||
db.delete_column('bookmarks_bookmark', 'display_name')
|
||||
|
||||
# Deleting field 'Bookmark.path'
|
||||
db.delete_column('bookmarks_bookmark', 'path')
|
||||
|
||||
# Adding field 'Bookmark._path'
|
||||
db.add_column('bookmarks_bookmark', '_path',
|
||||
self.gf('jsonfield.fields.JSONField')(default='', db_column='path'),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Bookmark.xblock_cache'
|
||||
db.add_column('bookmarks_bookmark', 'xblock_cache',
|
||||
self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['bookmarks.XBlockCache']),
|
||||
keep_default=False)
|
||||
|
||||
# Adding unique constraint on 'Bookmark', fields ['user', 'usage_key']
|
||||
db.create_unique('bookmarks_bookmark', ['user_id', 'usage_key'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'Bookmark', fields ['user', 'usage_key']
|
||||
db.delete_unique('bookmarks_bookmark', ['user_id', 'usage_key'])
|
||||
|
||||
# Deleting model 'XBlockCache'
|
||||
db.delete_table('bookmarks_xblockcache')
|
||||
|
||||
# Adding field 'Bookmark.display_name'
|
||||
db.add_column('bookmarks_bookmark', 'display_name',
|
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=255),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Bookmark.path'
|
||||
db.add_column('bookmarks_bookmark', 'path',
|
||||
self.gf('jsonfield.fields.JSONField')(default=''),
|
||||
keep_default=False)
|
||||
|
||||
# Deleting field 'Bookmark._path'
|
||||
db.delete_column('bookmarks_bookmark', 'path')
|
||||
|
||||
# Deleting field 'Bookmark.xblock_cache'
|
||||
db.delete_column('bookmarks_bookmark', 'xblock_cache_id')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'bookmarks.bookmark': {
|
||||
'Meta': {'unique_together': "(('user', 'usage_key'),)", 'object_name': 'Bookmark'},
|
||||
'_path': ('jsonfield.fields.JSONField', [], {'db_column': "'path'"}),
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'usage_key': ('xmodule_django.models.LocationKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'xblock_cache': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['bookmarks.XBlockCache']"})
|
||||
},
|
||||
'bookmarks.xblockcache': {
|
||||
'Meta': {'object_name': 'XBlockCache'},
|
||||
'_paths': ('jsonfield.fields.JSONField', [], {'default': '[]', 'db_column': "'paths'"}),
|
||||
'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'display_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'usage_key': ('xmodule_django.models.LocationKeyField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['bookmarks']
|
||||
@@ -56,6 +56,9 @@ class Bookmark(TimeStampedModel):
|
||||
"""
|
||||
unique_together = ('user', 'usage_key')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.resource_id
|
||||
|
||||
@classmethod
|
||||
def create(cls, data):
|
||||
"""
|
||||
@@ -197,6 +200,9 @@ class XBlockCache(TimeStampedModel):
|
||||
db_column='paths', default=[], help_text='All paths in course tree to the corresponding block.'
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.usage_key)
|
||||
|
||||
@property
|
||||
def paths(self):
|
||||
"""
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Serializers for Bookmarks.
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from openedx.core.lib.api.serializers import CourseKeyField, UsageKeyField
|
||||
|
||||
from . import DEFAULT_FIELDS
|
||||
from .models import Bookmark
|
||||
@@ -11,12 +12,12 @@ class BookmarkSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for the Bookmark model.
|
||||
"""
|
||||
id = serializers.Field(source='resource_id') # pylint: disable=invalid-name
|
||||
course_id = serializers.Field(source='course_key')
|
||||
usage_id = serializers.Field(source='usage_key')
|
||||
block_type = serializers.Field(source='usage_key.block_type')
|
||||
display_name = serializers.Field(source='display_name')
|
||||
path = serializers.SerializerMethodField('path_data')
|
||||
id = serializers.SerializerMethodField() # pylint: disable=invalid-name
|
||||
course_id = CourseKeyField(source='course_key')
|
||||
usage_id = UsageKeyField(source='usage_key')
|
||||
block_type = serializers.ReadOnlyField(source='usage_key.block_type')
|
||||
display_name = serializers.ReadOnlyField()
|
||||
path = serializers.SerializerMethodField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Don't pass the 'fields' arg up to the superclass
|
||||
@@ -46,14 +47,17 @@ class BookmarkSerializer(serializers.ModelSerializer):
|
||||
'created',
|
||||
)
|
||||
|
||||
def resource_id(self, bookmark):
|
||||
def get_id(self, bookmark):
|
||||
"""
|
||||
Return the REST resource id: {username,usage_id}.
|
||||
"""
|
||||
return "{0},{1}".format(bookmark.user.username, bookmark.usage_key)
|
||||
|
||||
def path_data(self, bookmark):
|
||||
def get_path(self, bookmark):
|
||||
"""
|
||||
Serialize and return the path data of the bookmark.
|
||||
"""
|
||||
return [path_item._asdict() for path_item in bookmark.path]
|
||||
path_items = [path_item._asdict() for path_item in bookmark.path]
|
||||
for path_item in path_items:
|
||||
path_item['usage_key'] = unicode(path_item['usage_key'])
|
||||
return path_items
|
||||
|
||||
@@ -10,7 +10,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
from request_cache.middleware import RequestCache
|
||||
|
||||
from . import DEFAULT_FIELDS, OPTIONAL_FIELDS, api
|
||||
from . import DEFAULT_FIELDS, api
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,8 +42,7 @@ class BookmarksService(object):
|
||||
fetch (Bool): if the bookmarks should be fetched and cached if they already aren't.
|
||||
"""
|
||||
store = modulestore()
|
||||
if hasattr(store, 'fill_in_run'):
|
||||
course_key = store.fill_in_run(course_key)
|
||||
course_key = store.fill_in_run(course_key)
|
||||
if course_key.run is None:
|
||||
return []
|
||||
cache_key = CACHE_KEY_TEMPLATE.format(self._user.id, course_key)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Tasks for bookmarks.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from celery.task import task # pylint: disable=import-error,no-name-in-module
|
||||
@@ -120,7 +119,7 @@ def _update_xblocks_cache(course_key):
|
||||
block_cache.paths = paths
|
||||
block_cache.save()
|
||||
|
||||
with transaction.commit_on_success():
|
||||
with transaction.atomic():
|
||||
block_caches = XBlockCache.objects.filter(course_key=course_key)
|
||||
for block_cache in block_caches:
|
||||
block_data = blocks_data.pop(unicode(block_cache.usage_key), None)
|
||||
@@ -128,7 +127,7 @@ def _update_xblocks_cache(course_key):
|
||||
update_block_cache_if_needed(block_cache, block_data)
|
||||
|
||||
for block_data in blocks_data.values():
|
||||
with transaction.commit_on_success():
|
||||
with transaction.atomic():
|
||||
paths = _paths_from_data(block_data['paths'])
|
||||
log.info(u'Creating XBlockCache with usage_key: %s', unicode(block_data['usage_key']))
|
||||
block_cache, created = XBlockCache.objects.get_or_create(usage_key=block_data['usage_key'], defaults={
|
||||
|
||||
@@ -16,7 +16,9 @@ LOCATION = partial(COURSE_KEY.make_usage_key, u'problem')
|
||||
|
||||
class BookmarkFactory(DjangoModelFactory):
|
||||
""" Simple factory class for generating Bookmark """
|
||||
FACTORY_FOR = Bookmark
|
||||
|
||||
class Meta(object):
|
||||
model = Bookmark
|
||||
|
||||
user = factory.SubFactory(UserFactory)
|
||||
course_key = COURSE_KEY
|
||||
@@ -31,7 +33,9 @@ class BookmarkFactory(DjangoModelFactory):
|
||||
|
||||
class XBlockCacheFactory(DjangoModelFactory):
|
||||
""" Simple factory class for generating XblockCache. """
|
||||
FACTORY_FOR = XBlockCache
|
||||
|
||||
class Meta(object):
|
||||
model = XBlockCache
|
||||
|
||||
course_key = COURSE_KEY
|
||||
usage_key = factory.Sequence(u'4x://edx/100/block/{0}'.format)
|
||||
|
||||
@@ -120,7 +120,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
|
||||
"""
|
||||
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
|
||||
|
||||
with self.assertNumQueries(4):
|
||||
with self.assertNumQueries(8):
|
||||
bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_2.location)
|
||||
|
||||
self.assert_bookmark_event_emitted(
|
||||
@@ -141,7 +141,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
|
||||
"""
|
||||
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
|
||||
|
||||
with self.assertNumQueries(4):
|
||||
with self.assertNumQueries(8):
|
||||
bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_2.location)
|
||||
|
||||
self.assert_bookmark_event_emitted(
|
||||
|
||||
@@ -68,7 +68,7 @@ class BookmarksServiceTests(BookmarksTestsBase):
|
||||
self.bookmark_service.set_bookmarked(usage_key=UsageKey.from_string("i4x://ed/ed/ed/interactive"))
|
||||
)
|
||||
|
||||
with self.assertNumQueries(4):
|
||||
with self.assertNumQueries(8):
|
||||
self.assertTrue(self.bookmark_service.set_bookmarked(usage_key=self.vertical_2.location))
|
||||
|
||||
def test_unset_bookmarked(self):
|
||||
|
||||
@@ -140,8 +140,8 @@ class XBlockCacheTaskTests(BookmarksTestsBase):
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
('course', 19),
|
||||
('other_course', 13)
|
||||
('course', 47),
|
||||
('other_course', 34)
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_update_xblocks_cache(self, course_attr, expected_sql_queries):
|
||||
@@ -162,5 +162,5 @@ class XBlockCacheTaskTests(BookmarksTestsBase):
|
||||
path_item.usage_key, expected_cache_data[usage_key][path_index][path_item_index + 1]
|
||||
)
|
||||
|
||||
with self.assertNumQueries(1):
|
||||
with self.assertNumQueries(3):
|
||||
_update_xblocks_cache(course.id)
|
||||
|
||||
@@ -95,7 +95,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
if check_all_fields:
|
||||
query_parameters += '&fields=path,display_name'
|
||||
|
||||
with self.assertNumQueries(7): # 2 queries for bookmark table.
|
||||
with self.assertNumQueries(9): # 2 queries for bookmark table.
|
||||
response = self.send_get(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -138,7 +138,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
page_size = 5
|
||||
query_parameters = 'course_id={}&page_size={}'.format(urllib.quote(unicode(course.id)), page_size)
|
||||
|
||||
with self.assertNumQueries(7): # 2 queries for bookmark table.
|
||||
with self.assertNumQueries(9): # 2 queries for bookmark table.
|
||||
response = self.send_get(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -171,7 +171,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
Test that requesting bookmarks with invalid data returns 0 records.
|
||||
"""
|
||||
# Invalid course id.
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
response = self.send_get(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -189,7 +189,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
# Without course id we would return all the bookmarks for that user.
|
||||
|
||||
with self.assertNumQueries(7): # 2 queries for bookmark table.
|
||||
with self.assertNumQueries(9): # 2 queries for bookmark table.
|
||||
response = self.send_get(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks')
|
||||
@@ -214,7 +214,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
Test that an anonymous client (not logged in) cannot call GET or POST.
|
||||
"""
|
||||
query_parameters = 'course_id={}'.format(self.course_id)
|
||||
with self.assertNumQueries(1): # No queries for bookmark table.
|
||||
with self.assertNumQueries(4): # No queries for bookmark table.
|
||||
self.send_get(
|
||||
client=self.anonymous_client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -222,7 +222,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
expected_status=401
|
||||
)
|
||||
|
||||
with self.assertNumQueries(1): # No queries for bookmark table.
|
||||
with self.assertNumQueries(4): # No queries for bookmark table.
|
||||
self.send_post(
|
||||
client=self.anonymous_client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -234,7 +234,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that posting a bookmark successfully returns newly created data with 201 code.
|
||||
"""
|
||||
with self.assertNumQueries(9):
|
||||
with self.assertNumQueries(15):
|
||||
response = self.send_post(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -255,10 +255,10 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
Scenarios:
|
||||
1) Invalid usage id.
|
||||
2) Without usage id.
|
||||
3) With empty request.DATA
|
||||
3) With empty request.data
|
||||
"""
|
||||
# Send usage_id with invalid format.
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
response = self.send_post(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -268,7 +268,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
self.assertEqual(response.data['user_message'], u'Invalid usage_id: invalid.')
|
||||
|
||||
# Send data without usage_id.
|
||||
with self.assertNumQueries(4): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
response = self.send_post(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -279,7 +279,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
self.assertEqual(response.data['developer_message'], u'Parameter usage_id not provided.')
|
||||
|
||||
# Send empty data dictionary.
|
||||
with self.assertNumQueries(4): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
response = self.send_post(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -293,7 +293,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that posting a bookmark for a block that does not exist returns a 400.
|
||||
"""
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
response = self.send_post(
|
||||
client=self.client,
|
||||
url=reverse('bookmarks'),
|
||||
@@ -322,7 +322,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
|
||||
@ddt.data(
|
||||
{'page_size': -1, 'expected_bookmarks_count': 2, 'expected_page_size': 10, 'expected_page_number': 1},
|
||||
{'page_size': 0, 'expected_bookmarks_count': 2, 'expected_page_size': 10, 'expected_page_number': 1},
|
||||
{'page_size': 999, 'expected_bookmarks_count': 2, 'expected_page_size': 500, 'expected_page_number': 1}
|
||||
{'page_size': 999, 'expected_bookmarks_count': 2, 'expected_page_size': 100, 'expected_page_number': 1}
|
||||
)
|
||||
def test_listed_event_for_different_page_size_values(self, mock_tracker, page_size, expected_bookmarks_count,
|
||||
expected_page_size, expected_page_number):
|
||||
@@ -371,7 +371,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that requesting bookmark returns data with 200 code.
|
||||
"""
|
||||
with self.assertNumQueries(6): # 1 query for bookmark table.
|
||||
with self.assertNumQueries(8): # 1 query for bookmark table.
|
||||
response = self.send_get(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -388,7 +388,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that requesting bookmark that belongs to other user returns 404 status code.
|
||||
"""
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(8): # No queries for bookmark table.
|
||||
self.send_get(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -402,7 +402,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that requesting bookmark that does not exist returns 404 status code.
|
||||
"""
|
||||
with self.assertNumQueries(6): # 1 query for bookmark table.
|
||||
with self.assertNumQueries(8): # 1 query for bookmark table.
|
||||
response = self.send_get(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -424,7 +424,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that requesting bookmark with invalid usage id returns 400.
|
||||
"""
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
response = self.send_get(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -440,14 +440,14 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
Test that an anonymous client (not logged in) cannot call GET or DELETE.
|
||||
"""
|
||||
url = reverse('bookmarks_detail', kwargs={'username': self.user.username, 'usage_id': 'i4x'})
|
||||
with self.assertNumQueries(4): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
self.send_get(
|
||||
client=self.anonymous_client,
|
||||
url=url,
|
||||
expected_status=401
|
||||
)
|
||||
|
||||
with self.assertNumQueries(1):
|
||||
with self.assertNumQueries(4):
|
||||
self.send_delete(
|
||||
client=self.anonymous_client,
|
||||
url=url,
|
||||
@@ -463,7 +463,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
bookmarks_data = response.data['results']
|
||||
self.assertEqual(len(bookmarks_data), 2)
|
||||
|
||||
with self.assertNumQueries(7): # 2 queries for bookmark table.
|
||||
with self.assertNumQueries(10): # 2 queries for bookmark table.
|
||||
self.send_delete(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -480,7 +480,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that delete bookmark that belongs to other user returns 404.
|
||||
"""
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(8): # No queries for bookmark table.
|
||||
self.send_delete(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -494,7 +494,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that delete bookmark that does not exist returns 404.
|
||||
"""
|
||||
with self.assertNumQueries(6): # 1 query for bookmark table.
|
||||
with self.assertNumQueries(8): # 1 query for bookmark table.
|
||||
response = self.send_delete(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -516,7 +516,7 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
Test that delete bookmark with invalid usage id returns 400.
|
||||
"""
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(7): # No queries for bookmark table.
|
||||
response = self.send_delete(
|
||||
client=self.client,
|
||||
url=reverse(
|
||||
@@ -533,8 +533,8 @@ class BookmarksDetailViewTests(BookmarksViewsTestsBase):
|
||||
"""
|
||||
url = reverse('bookmarks_detail', kwargs={'username': self.user.username, 'usage_id': 'i4x'})
|
||||
self.client.login(username=self.user.username, password=self.TEST_PASSWORD)
|
||||
with self.assertNumQueries(5): # No queries for bookmark table.
|
||||
with self.assertNumQueries(8): # No queries for bookmark table.
|
||||
self.assertEqual(405, self.client.put(url).status_code)
|
||||
|
||||
with self.assertNumQueries(4):
|
||||
with self.assertNumQueries(8):
|
||||
self.assertEqual(405, self.client.post(url).status_code)
|
||||
|
||||
@@ -12,20 +12,21 @@ from django.utils.translation import ugettext as _, ugettext_noop
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework import permissions
|
||||
from rest_framework.authentication import OAuth2Authentication, SessionAuthentication
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.generics import ListCreateAPIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_oauth.authentication import OAuth2Authentication
|
||||
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
|
||||
from openedx.core.lib.api.permissions import IsUserInUrl
|
||||
from openedx.core.lib.api.serializers import PaginationSerializer
|
||||
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
from lms.djangoapps.lms_xblock.runtime import unquote_slashes
|
||||
from openedx.core.lib.api.paginators import DefaultPagination
|
||||
|
||||
from . import DEFAULT_FIELDS, OPTIONAL_FIELDS, api
|
||||
from .serializers import BookmarkSerializer
|
||||
@@ -33,6 +34,28 @@ from .serializers import BookmarkSerializer
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BookmarksPagination(DefaultPagination):
|
||||
"""
|
||||
Paginator for bookmarks API.
|
||||
"""
|
||||
page_size = 10
|
||||
max_page_size = 100
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
"""
|
||||
Annotate the response with pagination information.
|
||||
"""
|
||||
response = super(BookmarksPagination, self).get_paginated_response(data)
|
||||
|
||||
# Add `current_page` value, it's needed for pagination footer.
|
||||
response.data["current_page"] = self.page.number
|
||||
|
||||
# Add `start` value, it's needed for the pagination header.
|
||||
response.data["start"] = (self.page.number - 1) * self.get_page_size(self.request)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class BookmarksViewMixin(object):
|
||||
"""
|
||||
Shared code for bookmarks views.
|
||||
@@ -129,12 +152,8 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
|
||||
|
||||
"""
|
||||
authentication_classes = (OAuth2Authentication, SessionAuthentication)
|
||||
pagination_class = BookmarksPagination
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
paginate_by = 10
|
||||
max_paginate_by = 500
|
||||
paginate_by_param = 'page_size'
|
||||
pagination_serializer_class = PaginationSerializer
|
||||
serializer_class = BookmarkSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
@@ -143,7 +162,7 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
|
||||
"""
|
||||
context = super(BookmarksListView, self).get_serializer_context()
|
||||
if self.request.method == 'GET':
|
||||
context['fields'] = self.fields_to_return(self.request.QUERY_PARAMS)
|
||||
context['fields'] = self.fields_to_return(self.request.query_params)
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -154,7 +173,7 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
|
||||
If the course_id is specified in the request parameters,
|
||||
the queryset will only include bookmarks from that course.
|
||||
"""
|
||||
course_id = self.request.QUERY_PARAMS.get('course_id', None)
|
||||
course_id = self.request.query_params.get('course_id', None)
|
||||
|
||||
if course_id:
|
||||
try:
|
||||
@@ -167,14 +186,14 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
|
||||
|
||||
return api.get_bookmarks(
|
||||
user=self.request.user, course_key=course_key,
|
||||
fields=self.fields_to_return(self.request.QUERY_PARAMS), serialized=False
|
||||
fields=self.fields_to_return(self.request.query_params), serialized=False
|
||||
)
|
||||
|
||||
def paginate_queryset(self, queryset, page_size=None):
|
||||
def paginate_queryset(self, queryset):
|
||||
""" Override GenericAPIView.paginate_queryset for the purpose of eventing """
|
||||
page = super(BookmarksListView, self).paginate_queryset(queryset, page_size)
|
||||
page = super(BookmarksListView, self).paginate_queryset(queryset)
|
||||
|
||||
course_id = self.request.QUERY_PARAMS.get('course_id')
|
||||
course_id = self.request.query_params.get('course_id')
|
||||
if course_id:
|
||||
try:
|
||||
CourseKey.from_string(course_id)
|
||||
@@ -183,9 +202,9 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
|
||||
|
||||
event_data = {
|
||||
'list_type': 'all_courses',
|
||||
'bookmarks_count': page.paginator.count,
|
||||
'page_size': self.get_paginate_by(),
|
||||
'page_number': page.number,
|
||||
'bookmarks_count': self.paginator.page.paginator.count,
|
||||
'page_size': self.paginator.page.paginator.per_page,
|
||||
'page_number': self.paginator.page.number,
|
||||
}
|
||||
if course_id is not None:
|
||||
event_data['list_type'] = 'per_course'
|
||||
@@ -200,10 +219,10 @@ class BookmarksListView(ListCreateAPIView, BookmarksViewMixin):
|
||||
POST /api/bookmarks/v1/bookmarks/
|
||||
Request data: {"usage_id": "<usage-id>"}
|
||||
"""
|
||||
if not request.DATA:
|
||||
if not request.data:
|
||||
return self.error_response(ugettext_noop(u'No data provided.'))
|
||||
|
||||
usage_id = request.DATA.get('usage_id', None)
|
||||
usage_id = request.data.get('usage_id', None)
|
||||
if not usage_id:
|
||||
return self.error_response(ugettext_noop(u'Parameter usage_id not provided.'))
|
||||
|
||||
@@ -297,7 +316,7 @@ class BookmarksDetailView(APIView, BookmarksViewMixin):
|
||||
bookmark_data = api.get_bookmark(
|
||||
user=request.user,
|
||||
usage_key=usage_key_or_response,
|
||||
fields=self.fields_to_return(request.QUERY_PARAMS)
|
||||
fields=self.fields_to_return(request.query_params)
|
||||
)
|
||||
except ObjectDoesNotExist:
|
||||
error_message = ugettext_noop(
|
||||
|
||||
@@ -6,33 +6,18 @@ import logging
|
||||
|
||||
from django.conf import settings
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
import pytz
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider, CreditEligibility, CreditRequest
|
||||
from openedx.core.djangoapps.credit.signature import get_shared_secret_key, signature
|
||||
from openedx.core.lib.api.serializers import CourseKeyField
|
||||
from util.date_utils import from_timestamp
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CourseKeyField(serializers.Field):
|
||||
""" Serializer field for a model CourseKey field. """
|
||||
|
||||
def to_representation(self, data):
|
||||
"""Convert a course key to unicode. """
|
||||
return unicode(data)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
"""Convert unicode to a course key. """
|
||||
try:
|
||||
return CourseKey.from_string(data)
|
||||
except InvalidKeyError as ex:
|
||||
raise serializers.ValidationError("Invalid course key: {msg}".format(msg=ex.msg))
|
||||
|
||||
|
||||
class CreditCourseSerializer(serializers.ModelSerializer):
|
||||
""" CreditCourse Serializer """
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ from ..profile_images.views import ProfileImageView
|
||||
from .accounts.views import AccountView
|
||||
from .preferences.views import PreferencesView, PreferencesDetailView
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
USERNAME_PATTERN = r'(?P<username>[\w.+-]+)'
|
||||
|
||||
urlpatterns = patterns(
|
||||
|
||||
@@ -3,6 +3,8 @@ Serializers to be used in APIs.
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
|
||||
|
||||
class CollapsedReferenceSerializer(serializers.HyperlinkedModelSerializer):
|
||||
@@ -37,3 +39,33 @@ class CollapsedReferenceSerializer(serializers.HyperlinkedModelSerializer):
|
||||
|
||||
class Meta(object):
|
||||
fields = ("url",)
|
||||
|
||||
|
||||
class CourseKeyField(serializers.Field):
|
||||
""" Serializer field for a model CourseKey field. """
|
||||
|
||||
def to_representation(self, data):
|
||||
"""Convert a course key to unicode. """
|
||||
return unicode(data)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
"""Convert unicode to a course key. """
|
||||
try:
|
||||
return CourseKey.from_string(data)
|
||||
except InvalidKeyError as ex:
|
||||
raise serializers.ValidationError("Invalid course key: {msg}".format(msg=ex.msg))
|
||||
|
||||
|
||||
class UsageKeyField(serializers.Field):
|
||||
""" Serializer field for a model UsageKey field. """
|
||||
|
||||
def to_representation(self, data):
|
||||
"""Convert a usage key to unicode. """
|
||||
return unicode(data)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
"""Convert unicode to a usage key. """
|
||||
try:
|
||||
return UsageKey.from_string(data)
|
||||
except InvalidKeyError as ex:
|
||||
raise serializers.ValidationError("Invalid usage key: {msg}".format(msg=ex.msg))
|
||||
|
||||
Reference in New Issue
Block a user