From cf5ab04bbb7cae95a6dfae72dda3c31cbdf90f80 Mon Sep 17 00:00:00 2001 From: Jonathan Piacenti Date: Thu, 3 Dec 2015 03:56:46 +0000 Subject: [PATCH] Add accomplishments to user profile --- .../collections/paging_collection.js | 42 ++--- .../static/common/js/components/views/list.js | 20 ++- .../js/components/views/paginated_view.js | 13 +- .../js/components/views/paging_footer.js | 4 +- .../js/components}/views/tabbed_view.js | 13 +- .../js/spec/components}/tabbed_view_spec.js | 2 +- .../static/common/js/spec/main_requirejs.js | 1 + .../common/js/spec_helpers/ajax_helpers.js | 4 + .../components/paging-footer.underscore | 8 +- .../templates/components}/tab.underscore | 0 .../components}/tabbed_view.underscore | 2 +- .../templates/components}/tabpanel.underscore | 0 lms/djangoapps/badges/api/serializers.py | 4 +- lms/djangoapps/badges/api/urls.py | 4 +- lms/djangoapps/badges/api/views.py | 18 +- .../badges/migrations/0001_initial.py | 8 +- .../0002_data__migrate_assertions.py | 12 +- .../0003_schema__add_event_configuration.py | 6 +- lms/djangoapps/badges/models.py | 15 +- lms/djangoapps/student_profile/views.py | 15 +- .../teams/static/teams/js/collections/team.js | 4 +- .../static/teams/js/collections/topic.js | 4 +- .../static/teams/js/teams_tab_factory.js | 3 +- .../teams/static/teams/js/views/teams.js | 2 + .../teams/js/views/teams_tabbed_view.js | 2 +- lms/djangoapps/teams/views.py | 30 +--- lms/envs/common.py | 3 + lms/static/js/spec/main.js | 1 - lms/static/js/spec/student_account/helpers.js | 7 +- lms/static/js/spec/student_profile/helpers.js | 117 ++++++++++++- .../learner_profile_factory_spec.js | 165 ++++++++++++++++++ .../learner_profile_view_spec.js | 21 ++- lms/static/js/student_account/account.js | 2 +- .../models/user_account_model.js | 1 + .../js/student_profile/models/badges_model.js | 8 + .../views/badge_list_container.js | 23 +++ .../student_profile/views/badge_list_view.js | 40 +++++ .../js/student_profile/views/badge_view.js | 22 +++ .../views/learner_profile_factory.js | 21 ++- .../views/learner_profile_view.js | 74 +++++++- .../student_profile/views/section_two_tab.js | 29 +++ lms/static/js_test.yml | 1 - lms/static/sass/_build-lms.scss | 1 + lms/static/sass/elements/_navigation.scss | 36 ++++ lms/static/sass/views/_learner-profile.scss | 86 ++++++++- lms/static/sass/views/_teams.scss | 35 +--- .../student_profile/badge.underscore | 16 ++ .../student_profile/badge_list.underscore | 4 + .../badge_placeholder.underscore | 10 ++ .../learner_profile.underscore | 15 +- .../student_profile/section_two.underscore | 10 ++ lms/urls.py | 2 +- .../core/djangoapps/user_api/accounts/api.py | 8 +- .../user_api/accounts/serializers.py | 88 ++++++---- .../user_api/accounts/tests/test_api.py | 3 +- .../user_api/accounts/tests/test_views.py | 17 +- .../djangoapps/user_api/accounts/views.py | 14 +- .../core/djangoapps/user_api/permissions.py | 35 ++++ openedx/core/lib/api/paginators.py | 3 + 59 files changed, 927 insertions(+), 227 deletions(-) rename {lms/static/js/components/tabbed => common/static/common/js/components}/views/tabbed_view.js (93%) rename {lms/static/js/spec/components/tabbed => common/static/common/js/spec/components}/tabbed_view_spec.js (99%) rename {lms/templates/components/tabbed => common/static/common/templates/components}/tab.underscore (100%) rename {lms/templates/components/tabbed => common/static/common/templates/components}/tabbed_view.underscore (50%) rename {lms/templates/components/tabbed => common/static/common/templates/components}/tabpanel.underscore (100%) create mode 100644 lms/static/js/student_profile/models/badges_model.js create mode 100644 lms/static/js/student_profile/views/badge_list_container.js create mode 100644 lms/static/js/student_profile/views/badge_list_view.js create mode 100644 lms/static/js/student_profile/views/badge_view.js create mode 100644 lms/static/js/student_profile/views/section_two_tab.js create mode 100644 lms/templates/student_profile/badge.underscore create mode 100644 lms/templates/student_profile/badge_list.underscore create mode 100644 lms/templates/student_profile/badge_placeholder.underscore create mode 100644 lms/templates/student_profile/section_two.underscore create mode 100644 openedx/core/djangoapps/user_api/permissions.py diff --git a/common/static/common/js/components/collections/paging_collection.js b/common/static/common/js/components/collections/paging_collection.js index a8af9098977c..957bf23620c9 100644 --- a/common/static/common/js/components/collections/paging_collection.js +++ b/common/static/common/js/components/collections/paging_collection.js @@ -21,10 +21,32 @@ define(['backbone.paginator'], function (BackbonePaginator) { var PagingCollection = BackbonePaginator.requestPager.extend({ initialize: function () { + var self = this; // These must be initialized in the constructor because otherwise all PagingCollections would point // to the same object references for sortableFields and filterableFields. this.sortableFields = {}; this.filterableFields = {}; + + this.paginator_core = { + type: 'GET', + dataType: 'json', + url: function () { return this.url; } + }; + this.paginator_ui = { + firstPage: function () { return self.isZeroIndexed ? 0 : 1; }, + // Specifies the initial page during collection initialization + currentPage: self.isZeroIndexed ? 0 : 1, + perPage: function () { return self.perPage; } + }; + + this.currentPage = this.paginator_ui.currentPage; + + this.server_api = { + page: function () { return self.currentPage; }, + page_size: function () { return self.perPage; }, + text_search: function () { return self.searchString ? self.searchString : ''; }, + sort_order: function () { return self.sortField; } + }; }, isZeroIndexed: false, @@ -41,26 +63,6 @@ searchString: null, - paginator_core: { - type: 'GET', - dataType: 'json', - url: function () { return this.url; } - }, - - paginator_ui: { - firstPage: function () { return this.isZeroIndexed ? 0 : 1; }, - // Specifies the initial page during collection initialization - currentPage: function () { return this.isZeroIndexed ? 0 : 1; }, - perPage: function () { return this.perPage; } - }, - - server_api: { - page: function () { return this.currentPage; }, - page_size: function () { return this.perPage; }, - text_search: function () { return this.searchString ? this.searchString : ''; }, - sort_order: function () { return this.sortField; } - }, - parse: function (response) { this.totalCount = response.count; this.currentPage = response.current_page; diff --git a/common/static/common/js/components/views/list.js b/common/static/common/js/components/views/list.js index b8c319afaadb..ed3a64f4af8d 100644 --- a/common/static/common/js/components/views/list.js +++ b/common/static/common/js/components/views/list.js @@ -24,18 +24,26 @@ this.itemViews = []; }, + renderCollection: function() { + /** + * Render every item in the collection. + * This should push each rendered item to this.itemViews + * to ensure garbage collection works. + */ + this.collection.each(function (model) { + var itemView = new this.itemViewClass({model: model}); + this.$el.append(itemView.render().el); + this.itemViews.push(itemView); + }, this); + }, + render: function () { // Remove old children views _.each(this.itemViews, function (childView) { childView.remove(); }); this.itemViews = []; - // Render the collection - this.collection.each(function (model) { - var itemView = new this.itemViewClass({model: model}); - this.$el.append(itemView.render().el); - this.itemViews.push(itemView); - }, this); + this.renderCollection(); return this; } }); diff --git a/common/static/common/js/components/views/paginated_view.js b/common/static/common/js/components/views/paginated_view.js index 0507d52cbdce..0b6b5674e83c 100644 --- a/common/static/common/js/components/views/paginated_view.js +++ b/common/static/common/js/components/views/paginated_view.js @@ -26,7 +26,7 @@ ], function (Backbone, _, PagingHeader, PagingFooter, ListView, paginatedViewTemplate) { var PaginatedView = Backbone.View.extend({ initialize: function () { - var ItemListView = ListView.extend({ + var ItemListView = this.listViewClass.extend({ tagName: 'div', className: this.type + '-container', itemViewClass: this.itemViewClass @@ -39,18 +39,25 @@ }, this); }, + listViewClass: ListView, + + viewTemplate: paginatedViewTemplate, + + paginationLabel: gettext("Pagination"), + createHeaderView: function() { return new PagingHeader({collection: this.options.collection, srInfo: this.srInfo}); }, createFooterView: function() { return new PagingFooter({ - collection: this.options.collection, hideWhenOnePage: true + collection: this.options.collection, hideWhenOnePage: true, + paginationLabel: this.paginationLabel }); }, render: function () { - this.$el.html(_.template(paginatedViewTemplate, {type: this.type})); + this.$el.html(_.template(this.viewTemplate, {type: this.type})); this.assign(this.listView, '.' + this.type + '-list'); if (this.headerView) { this.assign(this.headerView, '.' + this.type + '-paging-header'); diff --git a/common/static/common/js/components/views/paging_footer.js b/common/static/common/js/components/views/paging_footer.js index 36d2c74f2a52..4b2aaddc6757 100644 --- a/common/static/common/js/components/views/paging_footer.js +++ b/common/static/common/js/components/views/paging_footer.js @@ -13,6 +13,7 @@ initialize: function(options) { this.collection = options.collection; this.hideWhenOnePage = options.hideWhenOnePage || false; + this.paginationLabel = options.paginationLabel; this.collection.bind('add', _.bind(this.render, this)); this.collection.bind('remove', _.bind(this.render, this)); this.collection.bind('reset', _.bind(this.render, this)); @@ -32,7 +33,8 @@ } this.$el.html(_.template(paging_footer_template, { current_page: this.collection.getPage(), - total_pages: this.collection.totalPages + total_pages: this.collection.totalPages, + paginationLabel: this.paginationLabel })); this.$(".previous-page-link").toggleClass("is-disabled", onFirstPage).attr('aria-disabled', onFirstPage); this.$(".next-page-link").toggleClass("is-disabled", onLastPage).attr('aria-disabled', onLastPage); diff --git a/lms/static/js/components/tabbed/views/tabbed_view.js b/common/static/common/js/components/views/tabbed_view.js similarity index 93% rename from lms/static/js/components/tabbed/views/tabbed_view.js rename to common/static/common/js/components/views/tabbed_view.js index 70d4afe43ce9..3004c2f1b7d9 100644 --- a/lms/static/js/components/tabbed/views/tabbed_view.js +++ b/common/static/common/js/components/views/tabbed_view.js @@ -3,9 +3,9 @@ define(['backbone', 'underscore', 'jquery', - 'text!templates/components/tabbed/tabbed_view.underscore', - 'text!templates/components/tabbed/tab.underscore', - 'text!templates/components/tabbed/tabpanel.underscore', + 'text!common/templates/components/tabbed_view.underscore', + 'text!common/templates/components/tab.underscore', + 'text!common/templates/components/tabpanel.underscore', ], function ( Backbone, _, @@ -37,8 +37,6 @@ 'click .nav-item.tab': 'switchTab' }, - template: _.template(tabbedViewTemplate), - /** * View for a tabbed interface. Expects a list of tabs * in its options object, each of which should contain the @@ -51,12 +49,13 @@ * If a router is passed in (via options.router), * use that router to keep track of history between * tabs. Backbone.history.start() must be called - * by the router's instatiator after this view is + * by the router's instantiator after this view is * initialized. */ initialize: function (options) { this.router = options.router || null; this.tabs = options.tabs; + this.template = _.template(tabbedViewTemplate, {viewLabel: this.options.viewLabel}); // Convert each view into a TabPanelView _.each(this.tabs, function (tabInfo) { tabInfo.view = new TabPanelView({url: tabInfo.url, view: tabInfo.view}); @@ -69,7 +68,7 @@ render: function () { var self = this; - this.$el.html(this.template({})); + this.$el.html(this.template); _.each(this.tabs, function(tabInfo, index) { var tabEl = $(_.template(tabTemplate, { index: index, diff --git a/lms/static/js/spec/components/tabbed/tabbed_view_spec.js b/common/static/common/js/spec/components/tabbed_view_spec.js similarity index 99% rename from lms/static/js/spec/components/tabbed/tabbed_view_spec.js rename to common/static/common/js/spec/components/tabbed_view_spec.js index cebe44f6d61a..fb87c8f0a02b 100644 --- a/lms/static/js/spec/components/tabbed/tabbed_view_spec.js +++ b/common/static/common/js/spec/components/tabbed_view_spec.js @@ -4,7 +4,7 @@ define(['jquery', 'underscore', 'backbone', - 'js/components/tabbed/views/tabbed_view' + 'common/js/components/views/tabbed_view' ], function($, _, Backbone, TabbedView) { var view, diff --git a/common/static/common/js/spec/main_requirejs.js b/common/static/common/js/spec/main_requirejs.js index 5890a82cd8d3..25919bfbce1b 100644 --- a/common/static/common/js/spec/main_requirejs.js +++ b/common/static/common/js/spec/main_requirejs.js @@ -155,6 +155,7 @@ define([ // Run the common tests that use RequireJS. + 'common-requirejs/include/common/js/spec/components/tabbed_view_spec.js', 'common-requirejs/include/common/js/spec/components/feedback_spec.js', 'common-requirejs/include/common/js/spec/components/list_spec.js', 'common-requirejs/include/common/js/spec/components/paginated_view_spec.js', diff --git a/common/static/common/js/spec_helpers/ajax_helpers.js b/common/static/common/js/spec_helpers/ajax_helpers.js index 665da718fe01..5cf3f0cb1b4a 100644 --- a/common/static/common/js/spec_helpers/ajax_helpers.js +++ b/common/static/common/js/spec_helpers/ajax_helpers.js @@ -72,6 +72,10 @@ define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) { expect(request.readyState).toEqual(XML_HTTP_READY_STATES.OPENED); expect(request.url).toEqual(url); expect(request.method).toEqual(method); + if (typeof body === 'undefined') { + // The contents if this call may not be germane to the current test. + return; + } expect(request.requestBody).toEqual(body); }; diff --git a/common/static/common/templates/components/paging-footer.underscore b/common/static/common/templates/components/paging-footer.underscore index d92eec41dba8..28c34b617806 100644 --- a/common/static/common/templates/components/paging-footer.underscore +++ b/common/static/common/templates/components/paging-footer.underscore @@ -1,8 +1,12 @@ -