From 8bd1f69821fa1c9d0b2e6ad0cbdc0a10136edb6c Mon Sep 17 00:00:00 2001 From: Andy Armstrong Date: Tue, 11 Aug 2015 00:03:26 -0400 Subject: [PATCH] Govern team creation for non-privileged users TNL-2960 Non-privileged users can only be in one team, so don't allow them to create new teams if they are already members of another. --- .../teams/js/collections/team_membership.js | 14 +- .../static/teams/js/spec/team_actions_spec.js | 40 ---- ...tory_spec.js => teams_tab_factory_spec.js} | 24 +- .../js/spec/{ => views}/edit_team_spec.js | 0 .../static/teams/js/spec/views/teams_spec.js | 223 ++++++++++-------- .../teams/js/spec/views/teams_tab_spec.js | 159 ++++++++----- .../spec_helpers/team_discussion_helpers.js | 4 +- .../teams/static/teams/js/views/edit_team.js | 4 +- .../static/teams/js/views/team_actions.js | 52 ---- .../teams/static/teams/js/views/teams.js | 49 +++- .../teams/static/teams/js/views/teams_tab.js | 27 ++- .../teams/templates/teams/teams.html | 7 +- lms/djangoapps/teams/views.py | 8 +- lms/static/js/spec/main.js | 5 +- 14 files changed, 321 insertions(+), 295 deletions(-) delete mode 100644 lms/djangoapps/teams/static/teams/js/spec/team_actions_spec.js rename lms/djangoapps/teams/static/teams/js/spec/{teams_factory_spec.js => teams_tab_factory_spec.js} (51%) rename lms/djangoapps/teams/static/teams/js/spec/{ => views}/edit_team_spec.js (100%) delete mode 100644 lms/djangoapps/teams/static/teams/js/views/team_actions.js diff --git a/lms/djangoapps/teams/static/teams/js/collections/team_membership.js b/lms/djangoapps/teams/static/teams/js/collections/team_membership.js index 226f591d61..ad679fb070 100644 --- a/lms/djangoapps/teams/static/teams/js/collections/team_membership.js +++ b/lms/djangoapps/teams/static/teams/js/collections/team_membership.js @@ -8,15 +8,25 @@ this.course_id = options.course_id; this.username = options.username; + this.privileged = options.privileged; this.perPage = options.per_page || 10; this.server_api['expand'] = 'team'; - this.server_api['course_id'] = function () { return encodeURIComponent(this.course_id); }; + this.server_api['course_id'] = function () { return encodeURIComponent(options.course_id); }; this.server_api['username'] = this.username; delete this.server_api['sort_order']; // Sort order is not specified for the TeamMembership API delete this.server_api['order_by']; // Order by is not specified for the TeamMembership API }, - model: TeamMembershipModel + model: TeamMembershipModel, + + canUserCreateTeam: function() { + // Note: users can only belong to one team at a time, and + // non-privileged users are automatically added to any team + // that they create. This means that non-privileged users + // are not allowed to create a new team when they already + // belong to a different one. + return this.privileged || this.length === 0; + } }); return TeamMembershipCollection; }); diff --git a/lms/djangoapps/teams/static/teams/js/spec/team_actions_spec.js b/lms/djangoapps/teams/static/teams/js/spec/team_actions_spec.js deleted file mode 100644 index 7f32b4dfac..0000000000 --- a/lms/djangoapps/teams/static/teams/js/spec/team_actions_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -define([ - 'jquery', - 'backbone', - 'teams/js/views/team_actions' -], function ($, Backbone, TeamActionsView) { - 'use strict'; - - describe('TeamActions', function () { - var teamActionsView; - - beforeEach(function () { - setFixtures('
'); - spyOn(Backbone.history, 'navigate'); - teamActionsView = new TeamActionsView({ - el: $('.teams-content'), - teamParams: {topicId: 'awesomeness'} - }).render(); - }); - - it('can render itself correctly', function () { - expect(teamActionsView.$('.title').text()).toBe('Are you having trouble finding a team to join?'); - expect(teamActionsView.$('.copy').text()).toBe( - "Try browsing all teams or searching team descriptions. If you " + - "still can't find a team to join, create a new team in this topic." - ); - }); - - it('can navigate to correct routes', function () { - teamActionsView.$('a.browse-teams').click(); - expect(Backbone.history.navigate.calls[0].args).toContain('browse'); - - teamActionsView.$('a.search-team-descriptions').click(); - // TODO! Should be updated once team description search feature is available - expect(Backbone.history.navigate.calls[1].args).toContain('browse'); - - teamActionsView.$('a.create-team').click(); - expect(Backbone.history.navigate.calls[2].args).toContain('topics/awesomeness/create-team'); - }); - }); -}); diff --git a/lms/djangoapps/teams/static/teams/js/spec/teams_factory_spec.js b/lms/djangoapps/teams/static/teams/js/spec/teams_tab_factory_spec.js similarity index 51% rename from lms/djangoapps/teams/static/teams/js/spec/teams_factory_spec.js rename to lms/djangoapps/teams/static/teams/js/spec/teams_tab_factory_spec.js index fa79a194e9..c18b372f55 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/teams_factory_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/teams_tab_factory_spec.js @@ -5,15 +5,23 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"], describe("Teams Tab Factory", function() { var teamsTab; - beforeEach(function() { - setFixtures('
'); - teamsTab = new TeamsTabFactory({ + var initializeTeamsTabFactory = function() { + TeamsTabFactory({ topics: {results: []}, topicsUrl: '', teamsUrl: '', maxTeamSize: 9999, - courseID: 'edX/DemoX/Demo_Course' + courseID: 'edX/DemoX/Demo_Course', + userInfo: { + username: 'test-user', + privileged: false, + teamMembershipData: null + } }); + }; + + beforeEach(function() { + setFixtures('
'); }); afterEach(function() { @@ -21,12 +29,14 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"], }); it("can load templates", function() { - expect($("body").text()).toContain("My Teams"); - expect($("body").text()).toContain("Showing 0 out of 0 total"); + initializeTeamsTabFactory(); + expect($('.teams-content').text()).toContain("My Teams"); + expect($('.teams-content').text()).toContain("Showing 0 out of 0 total"); }); it("displays a header", function() { - expect($("body").html()).toContain("See all teams in your course, organized by topic"); + initializeTeamsTabFactory(); + expect($('.teams-content').html()).toContain("See all teams in your course, organized by topic"); }); }); } diff --git a/lms/djangoapps/teams/static/teams/js/spec/edit_team_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js similarity index 100% rename from lms/djangoapps/teams/static/teams/js/spec/edit_team_spec.js rename to lms/djangoapps/teams/static/teams/js/spec/views/edit_team_spec.js diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js index 085d92b46f..5b141f241a 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/views/teams_spec.js @@ -6,36 +6,50 @@ define([ ], function (Backbone, TeamCollection, TeamMembershipCollection, TeamsView) { 'use strict'; describe('Teams View', function () { - var teamsView, teamCollection, initialTeams, - initialTeamMemberships, teamMembershipCollection; + var countries = [ + ['', ''], + ['US', 'United States'], + ['CA', 'Canada'], + ['MX', 'Mexico'] + ]; + var languages = [ + ['', ''], + ['en', 'English'], + ['es', 'Spanish'], + ['fr', 'French'] + ]; - var createTeams = function (startIndex, stopIndex) { - return _.map(_.range(startIndex, stopIndex + 1), function (i) { - return { - name: "team " + i, - id: "id " + i, - language: languages[i%4][0], - country: countries[i%4][0], - is_active: true, - membership: [] - }; - }); - }, - countries = [ - ['', ''], - ['US', 'United States'], - ['CA', 'Canada'], - ['MX', 'Mexico'] - ], - languages = [ - ['', ''], - ['en', 'English'], - ['es', 'Spanish'], - ['fr', 'French'] - ]; + var createTeamData = function (startIndex, stopIndex) { + return _.map(_.range(startIndex, stopIndex + 1), function (i) { + return { + name: "team " + i, + id: "id " + i, + language: languages[i%4][0], + country: countries[i%4][0], + is_active: true, + membership: [] + }; + }); + }; - var createTeamMemberships = function(startIndex, stopIndex) { - var teams = createTeams(startIndex, stopIndex) + var createTeams = function(teamData) { + return new TeamCollection( + { + count: 6, + num_pages: 2, + current_page: 1, + start: 0, + results: teamData + }, + { + course_id: 'my/course/id', + parse: true + } + ); + }; + + var createTeamMembershipsData = function(startIndex, stopIndex) { + var teams = createTeamData(startIndex, stopIndex); return _.map(_.range(startIndex, stopIndex + 1), function (i) { return { user: { @@ -47,6 +61,26 @@ define([ }); }; + var createTeamMemberships = function(teamMembershipData, options) { + return new TeamMembershipCollection( + { + count: 11, + num_pages: 3, + current_page: 1, + start: 0, + results: teamMembershipData + }, + _.extend(_.extend({}, { + course_id: 'my/course/id', + parse: true, + url: 'api/teams/team_memberships', + username: 'andya', + privileged: false + }), + options) + ); + }; + var verifyCards = function(view, teams) { var teamCards = view.$('.team-card'); _.each(teams, function (team, index) { @@ -55,53 +89,30 @@ define([ expect(currentCard.text()).toMatch(_.object(languages)[team.language]); expect(currentCard.text()).toMatch(_.object(countries)[team.country]); }); + }; - } - - beforeEach(function () { - setFixtures('
'); - initialTeams = createTeams(1, 5); - teamCollection = new TeamCollection( - { - count: 6, - num_pages: 2, - current_page: 1, - start: 0, - results: initialTeams - }, - { - course_id: 'my/course/id', - parse: true - } - ); - - initialTeamMemberships = createTeamMemberships(1, 5); - teamMembershipCollection = new TeamMembershipCollection( - { - count: 11, - num_pages: 3, - current_page: 1, - start: 0, - results: initialTeamMemberships - }, - { - course_id: 'my/course/id', - parse: true, - url: 'api/teams/team_memberships', - username: 'andya', - } - ); - }); - - it('can render itself with teams collection', function () { - teamsView = new TeamsView({ + var createTeamsView = function(options) { + return new TeamsView({ el: '.teams-container', - collection: teamCollection, + collection: options.teams || createTeams(createTeamData(1, 5)), + teamMemberships: options.teamMemberships || createTeamMemberships(createTeamMembershipsData(1, 5)), teamParams: { + topicID: 'test-topic', countries: countries, languages: languages } }).render(); + }; + + beforeEach(function () { + setFixtures('
'); + }); + + it('can render itself with team collection', function () { + var testTeamData = createTeamData(1, 5), + teamsView = createTeamsView({ + teams: createTeams(testTeamData) + }); expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 6 total'); @@ -109,45 +120,69 @@ define([ expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+2'); expect(footerEl).not.toHaveClass('hidden'); - verifyCards(teamsView, initialTeams); + verifyCards(teamsView, testTeamData); }); - it('can render itself with team memberships collection', function () { - teamsView = new TeamsView({ - el: '.teams-container', - collection: teamMembershipCollection, - teamParams: {} - }).render(); + it('can render itself with team membership collection', function () { + var teamMembershipsData = createTeamMembershipsData(1, 5), + teamMemberships = createTeamMemberships(teamMembershipsData), + teamsView = createTeamsView({ + teams: teamMemberships, + teamMemberships: teamMemberships + }); expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 11 total'); var footerEl = teamsView.$('.teams-paging-footer'); expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+3'); expect(footerEl).not.toHaveClass('hidden'); - verifyCards(teamsView, initialTeamMemberships); + verifyCards(teamsView, teamMembershipsData); }); - it ('can render the actions view', function () { - teamsView = new TeamsView({ - el: '.teams-container', - collection: teamCollection, - teamParams: {}, - }).render(); + describe("Team Actions View", function() { + it('can render itself correctly', function () { + var emptyMembership = createTeamMemberships([]), + teamsView = createTeamsView({ teamMemberships: emptyMembership }); + expect(teamsView.$('.title').text()).toBe('Are you having trouble finding a team to join?'); + expect(teamsView.$('.copy').text()).toBe( + "Try browsing all teams or searching team descriptions. If you " + + "still can't find a team to join, create a new team in this topic." + ); + }); - expect(teamsView.$el.text()).not.toContain( - 'Are you having trouble finding a team to join?' - ); - teamsView = new TeamsView({ - el: '.teams-container', - collection: teamCollection, - teamParams: {}, - showActions: true - }).render(); + it('can navigate to correct routes', function () { + var emptyMembership = createTeamMemberships([]), + teamsView = createTeamsView({ teamMemberships: emptyMembership }); + spyOn(Backbone.history, 'navigate'); + teamsView.$('a.browse-teams').click(); + expect(Backbone.history.navigate.calls[0].args).toContain('browse'); - expect(teamsView.$el.text()).toContain( - 'Are you having trouble finding a team to join?' - ); + teamsView.$('a.search-teams').click(); + // TODO! Should be updated once team description search feature is available + expect(Backbone.history.navigate.calls[1].args).toContain('browse'); + + teamsView.$('a.create-team').click(); + expect(Backbone.history.navigate.calls[2].args).toContain('topics/test-topic/create-team'); + }); + + it('does not show for a user already in a team', function () { + var teamsView = createTeamsView({}); + expect(teamsView.$el.text()).not.toContain( + 'Are you having trouble finding a team to join?' + ); + }); + + it('shows for a privileged user already in a team', function () { + var staffMembership = createTeamMemberships( + createTeamMembershipsData(1, 5), + { privileged: true } + ), + teamsView = createTeamsView({ teamMemberships: staffMembership }); + expect(teamsView.$el.text()).toContain( + 'Are you having trouble finding a team to join?' + ); + }); }); }); }); diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js index 2a8c21e89f..26104ac865 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/views/teams_tab_spec.js @@ -2,31 +2,62 @@ define([ 'jquery', 'backbone', 'common/js/spec_helpers/ajax_helpers', - 'teams/js/views/teams_tab', - 'URI' -], function ($, Backbone, AjaxHelpers, TeamsTabView, URI) { + 'teams/js/views/teams_tab' +], function ($, Backbone, AjaxHelpers, TeamsTabView) { 'use strict'; describe('TeamsTab', function () { - var teamsTabView, - expectContent = function (text) { - expect(teamsTabView.$('.page-content-main').text()).toContain(text); - }, - expectHeader = function (text) { - expect(teamsTabView.$('.teams-header').text()).toContain(text); - }, - expectError = function (text) { - expect(teamsTabView.$('.warning').text()).toContain(text); - }, - expectFocus = function (element) { - expect(element.focus).toHaveBeenCalled(); - }; + var expectContent = function (teamsTabView, text) { + expect(teamsTabView.$('.page-content-main').text()).toContain(text); + }; - beforeEach(function () { - setFixtures('
'); - teamsTabView = new TeamsTabView({ - el: $('.teams-content'), - topics: { + var expectHeader = function (teamsTabView, text) { + expect(teamsTabView.$('.teams-header').text()).toContain(text); + }; + + var expectError = function (teamsTabView, text) { + expect(teamsTabView.$('.warning').text()).toContain(text); + }; + + var expectFocus = function (element) { + expect(element.focus).toHaveBeenCalled(); + }; + + var createUserInfo = function(options) { + var defaultTeamMembershipData = { + count: 1, + currentPage: 1, + numPages: 1, + next: null, + previous: null, + results: [ + { + user: { + username: 'andya', + url: 'https://openedx.example.com/api/user/v1/accounts/andya' + }, + team: { + description: '', + name: 'Discrete Maths', + id: 'dm', + topic_id: 'algorithms' + }, + date_joined: '2015-04-09T17:31:56Z' + } + ] + }; + return _.extend( + { + username: 'andya', + privileged: false, + teamMembershipData: defaultTeamMembershipData + }, + options + ); + }; + + var createTeamsTabView = function(options) { + var defaultTopics = { count: 1, num_pages: 1, current_page: 1, @@ -38,33 +69,25 @@ define([ team_count: 0 }] }, - teamMemberships: { - count: 1, - currentPage: 1, - numPages: 1, - next: null, - previous: null, - results: [ - { - user: { - username: 'andya', - url: 'https://openedx.example.com/api/user/v1/accounts/andya' + teamsTabView = new TeamsTabView( + _.extend( + { + el: $('.teams-content'), + topics: defaultTopics, + userInfo: createUserInfo(), + topicsUrl: 'api/topics/', + topicUrl: 'api/topics/topic_id,test/course/id', + teamsUrl: 'api/teams/', + courseID: 'test/course/id' }, - team: { - description: '', - name: 'Discrete Maths', - id: 'dm', - topic_id: 'algorithms' - }, - date_joined: '2015-04-09T17:31:56Z' - }, - ] - }, - topicsUrl: 'api/topics/', - topicUrl: 'api/topics/topic_id,test/course/id', - teamsUrl: 'api/teams/', - courseID: 'test/course/id' - }).render(); + options || {} + ) + ).render(); + return teamsTabView; + }; + + beforeEach(function () { + setFixtures('
'); Backbone.history.start(); spyOn($.fn, 'focus'); }); @@ -74,48 +97,55 @@ define([ }); it('shows the my teams tab initially', function () { - expectHeader('See all teams in your course, organized by topic'); - expectContent('Showing 1 out of 1 total'); - expectContent('Discrete Maths'); + var teamsTabView = createTeamsTabView(); + expectHeader(teamsTabView, 'See all teams in your course, organized by topic'); + expectContent(teamsTabView, 'Showing 1 out of 1 total'); + expectContent(teamsTabView, 'Discrete Maths'); }); describe('Navigation', function () { it('can switch tabs', function () { + var teamsTabView = createTeamsTabView(); teamsTabView.$('a.nav-item[data-url="browse"]').click(); - expectContent('test description'); + expectContent(teamsTabView, 'test description'); teamsTabView.$('a.nav-item[data-url="my-teams"]').click(); - expectContent('Showing 1 out of 1 total'); - expectContent('Discrete Maths'); + expectContent(teamsTabView, 'Showing 1 out of 1 total'); + expectContent(teamsTabView, 'Discrete Maths'); }); it('displays and focuses an error message when trying to navigate to a nonexistent page', function () { + var teamsTabView = createTeamsTabView(); teamsTabView.router.navigate('no_such_page', {trigger: true}); - expectError('The page "no_such_page" could not be found.'); + expectError(teamsTabView, 'The page "no_such_page" could not be found.'); expectFocus(teamsTabView.$('.warning')); }); it('displays and focuses an error message when trying to navigate to a nonexistent topic', function () { - var requests = AjaxHelpers.requests(this); + var requests = AjaxHelpers.requests(this), + teamsTabView = createTeamsTabView(); teamsTabView.router.navigate('topics/no_such_topic', {trigger: true}); AjaxHelpers.expectRequest(requests, 'GET', 'api/topics/no_such_topic,test/course/id', null); AjaxHelpers.respondWithError(requests, 404); - expectError('The topic "no_such_topic" could not be found.'); + expectError(teamsTabView, 'The topic "no_such_topic" could not be found.'); expectFocus(teamsTabView.$('.warning')); }); it('displays and focuses an error message when trying to navigate to a nonexistent team', function () { - var requests = AjaxHelpers.requests(this); + var requests = AjaxHelpers.requests(this), + teamsTabView = createTeamsTabView(); teamsTabView.router.navigate('teams/test_topic/no_such_team', {trigger: true}); AjaxHelpers.expectRequest(requests, 'GET', 'api/teams/no_such_team', null); AjaxHelpers.respondWithError(requests, 404); - expectError('The team "no_such_team" could not be found.'); + expectError(teamsTabView, 'The team "no_such_team" could not be found.'); expectFocus(teamsTabView.$('.warning')); }); }); describe('Discussion privileges', function () { it('allows privileged access to any team', function () { - teamsTabView.$el.data('privileged', true); + var teamsTabView = createTeamsTabView({ + userInfo: createUserInfo({ privileged: true }) + }); // Note: using `undefined` here to ensure that we // don't even look at the team when the user is // privileged @@ -123,7 +153,12 @@ define([ }); it('allows access to a team which an unprivileged user is a member of', function () { - teamsTabView.$el.data('privileged', false).data('username', 'test-user'); + var teamsTabView = createTeamsTabView({ + userInfo: createUserInfo({ + username: 'test-user', + privileged: false + }) + }); expect(teamsTabView.readOnlyDiscussion({ attributes: { membership: [{ @@ -136,7 +171,9 @@ define([ }); it('does not allow access if the user is neither privileged nor a team member', function () { - teamsTabView.$el.data('privileged', false).data('username', 'test-user'); + var teamsTabView = createTeamsTabView({ + userInfo: createUserInfo({ privileged: false }) + }); expect(teamsTabView.readOnlyDiscussion({ attributes: { membership: [] } })).toBe(true); diff --git a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_discussion_helpers.js b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_discussion_helpers.js index 665b48cbc7..b18fdf5e27 100644 --- a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_discussion_helpers.js +++ b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_discussion_helpers.js @@ -1,6 +1,4 @@ -define([ - 'underscore', 'common/js/spec_helpers/ajax_helpers' -], function (_, AjaxHelpers) { +define(['underscore'], function (_) { 'use strict'; var createMockPostResponse, createMockDiscussionResponse, createAnnotatedContentInfo, createMockThreadResponse, testCourseID = 'course/1', diff --git a/lms/djangoapps/teams/static/teams/js/views/edit_team.js b/lms/djangoapps/teams/static/teams/js/views/edit_team.js index 0776f7c90a..5049a027cc 100644 --- a/lms/djangoapps/teams/static/teams/js/views/edit_team.js +++ b/lms/djangoapps/teams/static/teams/js/views/edit_team.js @@ -7,7 +7,7 @@ 'js/views/fields', 'teams/js/models/team', 'text!teams/templates/edit-team.underscore'], - function (Backbone, _, gettext, FieldViews, TeamModel, edit_team_template) { + function (Backbone, _, gettext, FieldViews, TeamModel, editTeamTemplate) { return Backbone.View.extend({ maxTeamNameLength: 255, @@ -79,7 +79,7 @@ }, render: function() { - this.$el.html(_.template(edit_team_template)({primaryButtonTitle: this.primaryButtonTitle})); + this.$el.html(_.template(editTeamTemplate)({primaryButtonTitle: this.primaryButtonTitle})); this.set(this.teamNameField, '.team-required-fields'); this.set(this.teamDescriptionField, '.team-required-fields'); this.set(this.optionalDescriptionField, '.team-optional-fields'); diff --git a/lms/djangoapps/teams/static/teams/js/views/team_actions.js b/lms/djangoapps/teams/static/teams/js/views/team_actions.js deleted file mode 100644 index 01927c1404..0000000000 --- a/lms/djangoapps/teams/static/teams/js/views/team_actions.js +++ /dev/null @@ -1,52 +0,0 @@ -;(function (define) { - 'use strict'; - define([ - 'gettext', - 'underscore', - 'backbone', - 'text!teams/templates/team-actions.underscore' - ], function (gettext, _, Backbone, team_actions_template) { - return Backbone.View.extend({ - events: { - 'click a.browse-teams': 'browseTeams', - 'click a.search-team-descriptions': 'searchTeamDescriptions', - 'click a.create-team': 'showCreateTeamForm' - }, - - initialize: function (options) { - this.template = _.template(team_actions_template); - this.teamParams = options.teamParams; - }, - - render: function () { - var message = interpolate_text( - _.escape(gettext("Try {browse_span_start}browsing all teams{span_end} or {search_span_start}searching team descriptions{span_end}. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.")), - { - 'browse_span_start': '', - 'search_span_start': '', - 'create_span_start': '', - 'span_end': '' - } - ); - this.$el.html(this.template({message: message})); - return this; - }, - - browseTeams: function (event) { - event.preventDefault(); - Backbone.history.navigate('browse', {trigger: true}); - }, - - searchTeamDescriptions: function (event) { - event.preventDefault(); - // TODO! Will navigate to correct place once required functionality is available - Backbone.history.navigate('browse', {trigger: true}); - }, - - showCreateTeamForm: function (event) { - event.preventDefault(); - Backbone.history.navigate('topics/' + this.teamParams.topicId + '/create-team', {trigger: true}); - } - }); - }); -}).call(this, define || RequireJS.define); diff --git a/lms/djangoapps/teams/static/teams/js/views/teams.js b/lms/djangoapps/teams/static/teams/js/views/teams.js index 06ab4fe334..0ccc764a23 100644 --- a/lms/djangoapps/teams/static/teams/js/views/teams.js +++ b/lms/djangoapps/teams/static/teams/js/views/teams.js @@ -4,34 +4,45 @@ 'backbone', 'teams/js/views/team_card', 'common/js/components/views/paginated_view', - 'teams/js/views/team_actions' - ], function (Backbone, TeamCardView, PaginatedView, TeamActionsView) { + 'text!teams/templates/team-actions.underscore' + ], function (Backbone, TeamCardView, PaginatedView, teamActionsTemplate) { var TeamsView = PaginatedView.extend({ type: 'teams', + events: { + 'click a.browse-teams': 'browseTeams', + 'click a.search-teams': 'searchTeams', + 'click a.create-team': 'showCreateTeamForm' + }, + initialize: function (options) { this.topic = options.topic; + this.teamMemberships = options.teamMemberships; + this.teamParams = options.teamParams; this.itemViewClass = TeamCardView.extend({ router: options.router, topic: options.topic, maxTeamSize: options.maxTeamSize, countries: this.selectorOptionsArrayToHashWithBlank(options.teamParams.countries), - languages: this.selectorOptionsArrayToHashWithBlank(options.teamParams.languages), + languages: this.selectorOptionsArrayToHashWithBlank(options.teamParams.languages) }); PaginatedView.prototype.initialize.call(this); - this.teamParams = options.teamParams; - this.showActions = options.showActions; }, render: function () { PaginatedView.prototype.render.call(this); - if (this.showActions === true) { - var teamActionsView = new TeamActionsView({ - teamParams: this.teamParams - }); - this.$el.append(teamActionsView.$el); - teamActionsView.render(); + if (this.teamMemberships.canUserCreateTeam()) { + var message = interpolate_text( + _.escape(gettext("Try {browse_span_start}browsing all teams{span_end} or {search_span_start}searching team descriptions{span_end}. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.")), + { + 'browse_span_start': '', + 'search_span_start': '', + 'create_span_start': '', + 'span_end': '' + } + ); + this.$el.append(_.template(teamActionsTemplate, {message: message})); } return this; @@ -49,6 +60,22 @@ var map = _.object(options); map[""] = ""; return map; + }, + + browseTeams: function (event) { + event.preventDefault(); + Backbone.history.navigate('browse', {trigger: true}); + }, + + searchTeamDescriptions: function (event) { + event.preventDefault(); + // TODO! Will navigate to correct place once required functionality is available + Backbone.history.navigate('browse', {trigger: true}); + }, + + showCreateTeamForm: function (event) { + event.preventDefault(); + Backbone.history.navigate('topics/' + this.teamParams.topicID + '/create-team', {trigger: true}); } }); return TeamsView; diff --git a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js index b985225d66..47be8d3c31 100644 --- a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js +++ b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js @@ -41,18 +41,18 @@ var router; this.courseID = options.courseID; this.topics = options.topics; - this.teamMemberships = options.teamMemberships; this.topicUrl = options.topicUrl; this.teamsUrl = options.teamsUrl; this.teamMembershipsUrl = options.teamMembershipsUrl; this.maxTeamSize = options.maxTeamSize; this.languages = options.languages; this.countries = options.countries; - this.username = options.username; + this.userInfo = options.userInfo; + // This slightly tedious approach is necessary // to use regular expressions within Backbone // routes, allowing us to capture which tab - // name is being routed to + // name is being routed to. router = this.router = new Backbone.Router(); _.each([ [':default', _.bind(this.routeNotFound, this)], @@ -65,20 +65,21 @@ router.route.apply(router, route); }); - this.teamMembershipsCollection = new TeamMembershipCollection( - this.teamMemberships, + this.teamMemberships = new TeamMembershipCollection( + this.userInfo.teamMembershipData, { url: this.teamMembershipsUrl, course_id: this.courseID, - username: this.username, - parse: true, - + username: this.userInfo.username, + privileged: this.userInfo.privileged, + parse: true } ).bootstrap(); this.myTeamsView = new TeamsView({ router: this.router, - collection: this.teamMembershipsCollection, + collection: this.teamMemberships, + teamMemberships: this.teamMemberships, maxTeamSize: this.maxTeamSize, teamParams: { courseId: this.courseID, @@ -186,14 +187,14 @@ url: self.teamsUrl, per_page: 10 }); - this.teamsCollection = collection; + self.teamsCollection = collection; collection.goTo(1) .done(function() { var teamsView = new TeamsView({ router: router, collection: collection, + teamMemberships: self.teamMemberships, maxTeamSize: self.maxTeamSize, - showActions: true, teamParams: { courseId: self.courseID, teamsUrl: self.teamsUrl, @@ -412,9 +413,9 @@ readOnlyDiscussion: function (team) { var self = this; return !( - this.$el.data('privileged') || + self.userInfo.privileged || _.any(team.attributes.membership, function (membership) { - return membership.user.username === self.$el.data('username'); + return membership.user.username === self.userInfo.username; }) ); } diff --git a/lms/djangoapps/teams/templates/teams/teams.html b/lms/djangoapps/teams/templates/teams/teams.html index 6232294a65..e524e5703a 100644 --- a/lms/djangoapps/teams/templates/teams/teams.html +++ b/lms/djangoapps/teams/templates/teams/teams.html @@ -18,7 +18,7 @@
-
+
@@ -35,15 +35,14 @@ TeamsTabFactory({ courseID: '${ unicode(course.id) }', topics: ${ json.dumps(topics, cls=EscapedEdxJSONEncoder) }, - teamMemberships: ${ json.dumps(team_memberships, cls=EscapedEdxJSONEncoder) }, + userInfo: ${ json.dumps(user_info, cls=EscapedEdxJSONEncoder) }, topicUrl: '${ topic_url }', topicsUrl: '${ topics_url }', teamsUrl: '${ teams_url }', teamMembershipsUrl: '${ team_memberships_url }', maxTeamSize: ${ course.teams_max_size }, languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) }, - countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) }, - username: '${ username }' + countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) } }); diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py index 6dbab0faec..ac7cabe4b9 100644 --- a/lms/djangoapps/teams/views.py +++ b/lms/djangoapps/teams/views.py @@ -97,17 +97,19 @@ class TeamsDashboardView(View): context = { "course": course, "topics": topics_serializer.data, + "user_info": { + "username": user.username, + "privileged": has_discussion_privileges(user, course_key), + "team_memberships_data": team_memberships_serializer.data, + }, "topic_url": reverse( 'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request ), - "team_memberships": team_memberships_serializer.data, "topics_url": reverse('topics_list', request=request), "teams_url": reverse('teams_list', request=request), "team_memberships_url": reverse('team_membership_list', request=request), "languages": settings.ALL_LANGUAGES, "countries": list(countries), - "username": user.username, - "privileged": has_discussion_privileges(user, course_key), "disable_courseware_js": True, } return render_to_response("teams/teams.html", context) diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index a6a42c97aa..ea84c36d7f 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -790,9 +790,8 @@ 'lms/include/js/spec/discovery/discovery_factory_spec.js', 'lms/include/js/spec/ccx/schedule_spec.js', 'lms/include/teams/js/spec/collections/topic_collection_spec.js', - 'lms/include/teams/js/spec/edit_team_spec.js', - 'lms/include/teams/js/spec/team_actions_spec.js', - 'lms/include/teams/js/spec/teams_factory_spec.js', + 'lms/include/teams/js/spec/teams_tab_factory_spec.js', + 'lms/include/teams/js/spec/views/edit_team_spec.js', 'lms/include/teams/js/spec/views/team_discussion_spec.js', 'lms/include/teams/js/spec/views/team_profile_spec.js', 'lms/include/teams/js/spec/views/teams_spec.js',