diff --git a/common/test/acceptance/pages/lms/teams.py b/common/test/acceptance/pages/lms/teams.py index 3157625128..ea62bfe569 100644 --- a/common/test/acceptance/pages/lms/teams.py +++ b/common/test/acceptance/pages/lms/teams.py @@ -46,6 +46,11 @@ class TeamCardsMixin(object): """Return the names of each team on the page.""" return self.q(css=self._bounded_selector('p.card-description')).map(lambda e: e.text).results + @property + def team_memberships(self): + """Return the team memberships text for each card on the page.""" + return self.q(css=self._bounded_selector('.member-count')).map(lambda e: e.text).results + class BreadcrumbsMixin(object): """Provides common operations on teams page breadcrumb links.""" diff --git a/common/test/acceptance/tests/lms/test_teams.py b/common/test/acceptance/tests/lms/test_teams.py index 33e4c0f0a7..a1a3143d20 100644 --- a/common/test/acceptance/tests/lms/test_teams.py +++ b/common/test/acceptance/tests/lms/test_teams.py @@ -78,6 +78,18 @@ class TeamsTabBase(EventsTestMixin, UniqueCourseTest): self.assertEqual(response.status_code, 200) return json.loads(response.text) + def create_memberships(self, num_memberships, team_id): + """Create `num_memberships` users and assign them to `team_id`. The + last user created becomes the current user.""" + memberships = [] + for __ in xrange(num_memberships): + user_info = AutoAuthPage(self.browser, course_id=self.course_id).visit().user_info + memberships.append(user_info) + self.create_membership(user_info['username'], team_id) + #pylint: disable=attribute-defined-outside-init + self.user_info = memberships[-1] + return memberships + def create_membership(self, username, team_id): """Assign `username` to `team_id`.""" response = self.course_fixture.session.post( @@ -339,6 +351,18 @@ class MyTeamsTest(TeamsTabBase): self.my_teams_page.visit() self.verify_teams(self.my_teams_page, teams) + def test_multiple_team_members(self): + """ + Scenario: Visiting the My Teams page when user is a member of a team should display the teams. + Given I am a member of a team with multiple members + When I visit the My Teams page + Then I should see the correct number of team members on my membership + """ + teams = self.create_teams(self.topic, 1) + self.create_memberships(4, teams[0]['id']) + self.my_teams_page.visit() + self.assertEqual(self.my_teams_page.team_memberships[0], '4 / 10 Members') + @attr('shard_5') @ddt.ddt diff --git a/lms/djangoapps/teams/static/teams/js/collections/my_teams.js b/lms/djangoapps/teams/static/teams/js/collections/my_teams.js new file mode 100644 index 0000000000..1d7f5d297b --- /dev/null +++ b/lms/djangoapps/teams/static/teams/js/collections/my_teams.js @@ -0,0 +1,15 @@ +;(function (define) { + 'use strict'; + define(['teams/js/collections/team'], function (TeamCollection) { + var MyTeamsCollection = TeamCollection.extend({ + initialize: function (teams, options) { + TeamCollection.prototype.initialize.call(this, teams, options); + delete this.server_api.topic_id; + this.server_api = _.extend(this.server_api, { + username: options.username + }); + } + }); + return MyTeamsCollection; + }); +}).call(this, define || RequireJS.define); 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 77f6fa4aa0..3a057707e1 100644 --- a/lms/djangoapps/teams/static/teams/js/collections/team_membership.js +++ b/lms/djangoapps/teams/static/teams/js/collections/team_membership.js @@ -9,8 +9,6 @@ this.perPage = options.per_page || 10; this.username = options.username; - this.privileged = options.privileged; - this.staff = options.staff; this.server_api = _.extend( { @@ -24,15 +22,7 @@ delete this.server_api['order_by']; // Order by is not specified for the TeamMembership API }, - model: TeamMembershipModel, - - canUserCreateTeam: function() { - // Note: non-staff and non-privileged users are automatically added to any team - // that they create. This means that if multiple team membership is - // disabled that they cannot create a new team when they already - // belong to one. - return this.privileged || this.staff || this.length === 0; - } + model: TeamMembershipModel }); return TeamMembershipCollection; }); diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js index beb93e4b80..dd892c4f20 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/views/my_teams_spec.js @@ -1,39 +1,30 @@ define([ 'backbone', - 'teams/js/collections/team', - 'teams/js/collections/team_membership', + 'teams/js/collections/my_teams', 'teams/js/views/my_teams', 'teams/js/spec_helpers/team_spec_helpers', 'common/js/spec_helpers/ajax_helpers' -], function (Backbone, TeamCollection, TeamMembershipCollection, MyTeamsView, TeamSpecHelpers, AjaxHelpers) { +], function (Backbone, MyTeamsCollection, MyTeamsView, TeamSpecHelpers, AjaxHelpers) { 'use strict'; describe('My Teams View', function () { beforeEach(function () { setFixtures('
'); }); - var createMyTeamsView = function(options) { - return new MyTeamsView(_.extend( - { - el: '.teams-container', - collection: options.teams || TeamSpecHelpers.createMockTeams(), - teamMemberships: TeamSpecHelpers.createMockTeamMemberships(), - showActions: true, - context: TeamSpecHelpers.testContext - }, - options - )).render(); + var createMyTeamsView = function(myTeams) { + return new MyTeamsView({ + el: '.teams-container', + collection: myTeams, + showActions: true, + context: TeamSpecHelpers.testContext + }).render(); }; it('can render itself', function () { - var teamMembershipsData = TeamSpecHelpers.createMockTeamMembershipsData(1, 5), - teamMemberships = TeamSpecHelpers.createMockTeamMemberships(teamMembershipsData), - myTeamsView = createMyTeamsView({ - teams: teamMemberships, - teamMemberships: teamMemberships - }); - - TeamSpecHelpers.verifyCards(myTeamsView, teamMembershipsData); + var teamsData = TeamSpecHelpers.createMockTeamData(1, 5), + teams = TeamSpecHelpers.createMockTeams({results: teamsData}), + myTeamsView = createMyTeamsView(teams); + TeamSpecHelpers.verifyCards(myTeamsView, teamsData); // Verify that there is no header or footer expect(myTeamsView.$('.teams-paging-header').text().trim()).toBe(''); @@ -41,36 +32,37 @@ define([ }); it('shows a message when the user is not a member of any teams', function () { - var teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]), - myTeamsView = createMyTeamsView({ - teams: teamMemberships, - teamMemberships: teamMemberships - }); + var teams = TeamSpecHelpers.createMockTeams({results: []}), + myTeamsView = createMyTeamsView(teams); TeamSpecHelpers.verifyCards(myTeamsView, []); expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.'); }); it('refreshes a stale membership collection when rendering', function() { var requests = AjaxHelpers.requests(this), - teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]), - myTeamsView = createMyTeamsView({ - teams: teamMemberships, - teamMemberships: teamMemberships - }); + teams = TeamSpecHelpers.createMockTeams({ + results: [] + }, { + per_page: 2, + url: TeamSpecHelpers.testContext.myTeamsUrl, + username: TeamSpecHelpers.testContext.userInfo.username + }, MyTeamsCollection), + myTeamsView = createMyTeamsView(teams); TeamSpecHelpers.verifyCards(myTeamsView, []); expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.'); - teamMemberships.teamEvents.trigger('teams:update', { action: 'create' }); + TeamSpecHelpers.teamEvents.trigger('teams:update', { action: 'create' }); myTeamsView.render(); AjaxHelpers.expectRequestURL( requests, - TeamSpecHelpers.testContext.teamMembershipsUrl, + TeamSpecHelpers.testContext.myTeamsUrl, { - expand : 'team,user', + expand : 'user', username : TeamSpecHelpers.testContext.userInfo.username, course_id : TeamSpecHelpers.testContext.courseID, page : '1', - page_size : '10', - text_search: '' + page_size : '2', + text_search: '', + order_by: 'last_activity_at' } ); AjaxHelpers.respondWithJson(requests, {}); 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 e392de7185..6474f71f29 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 @@ -1,10 +1,9 @@ define([ 'backbone', 'teams/js/collections/team', - 'teams/js/collections/team_membership', 'teams/js/views/teams', 'teams/js/spec_helpers/team_spec_helpers' -], function (Backbone, TeamCollection, TeamMembershipCollection, TeamsView, TeamSpecHelpers) { +], function (Backbone, TeamCollection, TeamsView, TeamSpecHelpers) { 'use strict'; describe('Teams View', function () { beforeEach(function () { @@ -15,7 +14,6 @@ define([ return new TeamsView({ el: '.teams-container', collection: options.teams || TeamSpecHelpers.createMockTeams(), - teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(), showActions: true, context: TeamSpecHelpers.testContext }).render(); diff --git a/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js b/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js index 51708a9ee9..28edf5768b 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/views/topic_teams_spec.js @@ -1,23 +1,22 @@ define([ 'backbone', - 'teams/js/collections/team', - 'teams/js/collections/team_membership', + 'underscore', 'teams/js/views/topic_teams', 'teams/js/spec_helpers/team_spec_helpers', - 'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/page_helpers' -], function (Backbone, TeamCollection, TeamMembershipCollection, TopicTeamsView, TeamSpecHelpers, - AjaxHelpers, PageHelpers) { +], function (Backbone, _, TopicTeamsView, TeamSpecHelpers, PageHelpers) { 'use strict'; describe('Topic Teams View', function () { var createTopicTeamsView = function(options) { + options = options || {}; + var myTeamsCollection = options.myTeamsCollection || TeamSpecHelpers.createMockTeams({results: []}); return new TopicTeamsView({ el: '.teams-container', model: TeamSpecHelpers.createMockTopic(), collection: options.teams || TeamSpecHelpers.createMockTeams(), - teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(), + myTeamsCollection: myTeamsCollection, showActions: true, - context: TeamSpecHelpers.testContext + context: _.extend({}, TeamSpecHelpers.testContext, options) }).render(); }; @@ -49,8 +48,7 @@ define([ teamsView = createTopicTeamsView({ teams: TeamSpecHelpers.createMockTeams({ results: testTeamData - }), - teamMemberships: TeamSpecHelpers.createMockTeamMemberships([]) + }) }); expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 6 total'); @@ -64,24 +62,21 @@ define([ }); it('can browse all teams', function () { - var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), - teamsView = createTopicTeamsView({ teamMemberships: emptyMembership }); + var teamsView = createTopicTeamsView(); spyOn(Backbone.history, 'navigate'); teamsView.$('.browse-teams').click(); expect(Backbone.history.navigate.calls[0].args).toContain('browse'); }); it('gives the search field focus when clicking on the search teams link', function () { - var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), - teamsView = createTopicTeamsView({ teamMemberships: emptyMembership }); + var teamsView = createTopicTeamsView(); spyOn($.fn, 'focus').andCallThrough(); teamsView.$('.search-teams').click(); expect(teamsView.$('.search-field').first().focus).toHaveBeenCalled(); }); it('can show the create team modal', function () { - var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), - teamsView = createTopicTeamsView({ teamMemberships: emptyMembership }); + var teamsView = createTopicTeamsView(); spyOn(Backbone.history, 'navigate'); teamsView.$('a.create-team').click(); expect(Backbone.history.navigate.calls[0].args).toContain( @@ -90,25 +85,17 @@ define([ }); it('does not show actions for a user already in a team', function () { - var teamsView = createTopicTeamsView({}); + var teamsView = createTopicTeamsView({myTeamsCollection: TeamSpecHelpers.createMockTeams()}); verifyActions(teamsView, {showActions: false}); }); it('shows actions for a privileged user already in a team', function () { - var staffMembership = TeamSpecHelpers.createMockTeamMemberships( - TeamSpecHelpers.createMockTeamMembershipsData(1, 5), - { privileged: true } - ), - teamsView = createTopicTeamsView({ teamMemberships: staffMembership }); + var teamsView = createTopicTeamsView({ privileged: true }); verifyActions(teamsView); }); it('shows actions for a staff user already in a team', function () { - var staffMembership = TeamSpecHelpers.createMockTeamMemberships( - TeamSpecHelpers.createMockTeamMembershipsData(1, 5), - { privileged: false, staff: true } - ), - teamsView = createTopicTeamsView({ teamMemberships: staffMembership }); + var teamsView = createTopicTeamsView({ privileged: false, staff: true }); verifyActions(teamsView); }); diff --git a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js index 94090f9fc8..02b03a8897 100644 --- a/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js +++ b/lms/djangoapps/teams/static/teams/js/spec_helpers/team_spec_helpers.js @@ -56,14 +56,18 @@ define([ ); }; - var createMockTeams = function(options) { - return new TeamCollection( - createMockTeamsResponse(options), - { + var createMockTeams = function(responseOptions, options, collectionType) { + if(_.isUndefined(collectionType)) { + collectionType = TeamCollection; + } + return new collectionType( + createMockTeamsResponse(responseOptions), + _.extend({ teamEvents: teamEvents, course_id: testCourseID, + per_page: 2, parse: true - } + }, options) ); }; @@ -83,35 +87,6 @@ define([ }); }; - var createMockTeamMemberships = function(teamMembershipData, options) { - if (!teamMembershipData) { - teamMembershipData = createMockTeamMembershipsData(1, 5); - } - return new TeamMembershipCollection( - { - count: 11, - num_pages: 3, - current_page: 1, - start: 0, - sort_order: 'last_activity_at', - results: teamMembershipData - }, - _.extend( - {}, - { - teamEvents: teamEvents, - course_id: testCourseID, - parse: true, - url: testContext.teamMembershipsUrl, - username: testUser, - privileged: false, - staff: false - }, - options - ) - ); - }; - var createMockUserInfo = function(options) { return _.extend( { @@ -291,6 +266,7 @@ define([ teamsDetailUrl: '/api/team/v0/teams/team_id', teamMembershipsUrl: '/api/team/v0/team_memberships/', teamMembershipDetailUrl: '/api/team/v0/team_membership/team_id,' + testUser, + myTeamsUrl: '/api/team/v0/teams/', userInfo: createMockUserInfo() }; @@ -331,8 +307,6 @@ define([ createMockTeamData: createMockTeamData, createMockTeamsResponse: createMockTeamsResponse, createMockTeams: createMockTeams, - createMockTeamMembershipsData: createMockTeamMembershipsData, - createMockTeamMemberships: createMockTeamMemberships, createMockUserInfo: createMockUserInfo, createMockContext: createMockContext, createMockTopic: createMockTopic, diff --git a/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js b/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js index dc48ca96e3..7525ed6ee1 100644 --- a/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js +++ b/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js @@ -22,12 +22,11 @@ }, initialize: function(options) { - this.teamMembershipDetailUrl = options.context.teamMembershipDetailUrl; // The URL ends with team_id,request_username. We want to replace // the last occurrence of team_id with the actual team_id, and remove request_username // as the actual user to be removed from the team will be added on before calling DELETE. - this.teamMembershipDetailUrl = this.teamMembershipDetailUrl.substring( - 0, this.teamMembershipDetailUrl.lastIndexOf('team_id') + this.teamMembershipDetailUrl = options.context.teamMembershipDetailUrl.substring( + 0, this.options.context.teamMembershipDetailUrl.lastIndexOf('team_id') ) + this.model.get('id') + ","; this.teamEvents = options.teamEvents; diff --git a/lms/djangoapps/teams/static/teams/js/views/team_card.js b/lms/djangoapps/teams/static/teams/js/views/team_card.js index 770a112814..f8f04cb461 100644 --- a/lms/djangoapps/teams/static/teams/js/views/team_card.js +++ b/lms/djangoapps/teams/static/teams/js/views/team_card.js @@ -1,4 +1,4 @@ -;(function (define) { +(function (define) { 'use strict'; define([ 'jquery', @@ -99,48 +99,34 @@ CardView.prototype.initialize.apply(this, arguments); // TODO: show last activity detail view this.detailViews = [ - new TeamMembershipView({memberships: this.getMemberships(), maxTeamSize: this.maxTeamSize}), + new TeamMembershipView({memberships: this.model.get('membership'), maxTeamSize: this.maxTeamSize}), new TeamCountryLanguageView({ - model: this.teamModel(), + model: this.model, countries: this.countries, languages: this.languages }), - new TeamActivityView({date: this.teamModel().get('last_activity_at')}) + new TeamActivityView({date: this.model.get('last_activity_at')}) ]; this.model.on('change:membership', function () { - this.detailViews[0].memberships = this.getMemberships(); + this.detailViews[0].memberships = this.model.get('membership'); }, this); }, - teamModel: function () { - if (this.model.has('team')) { return this.model.get('team'); } - return this.model; - }, - - getMemberships: function () { - if (this.model.has('team')) { - return [this.model.attributes]; - } - else { - return this.model.get('membership'); - } - }, - configuration: 'list_card', cardClass: 'team-card', - title: function () { return this.teamModel().get('name'); }, - description: function () { return this.teamModel().get('description'); }, + title: function () { return this.model.get('name'); }, + description: function () { return this.model.get('description'); }, details: function () { return this.detailViews; }, actionClass: 'action-view', actionContent: function() { return interpolate( gettext('View %(span_start)s %(team_name)s %(span_end)s'), - {span_start: '', team_name: _.escape(this.teamModel().get('name')), span_end: ''}, + {span_start: '', team_name: _.escape(this.model.get('name')), span_end: ''}, true ); }, actionUrl: function () { - return '#teams/' + this.teamModel().get('topic_id') + '/' + this.teamModel().get('id'); + return '#teams/' + this.model.get('topic_id') + '/' + this.model.get('id'); } }); return TeamCardView; diff --git a/lms/djangoapps/teams/static/teams/js/views/teams.js b/lms/djangoapps/teams/static/teams/js/views/teams.js index be8d8b14f6..d163761a2c 100644 --- a/lms/djangoapps/teams/static/teams/js/views/teams.js +++ b/lms/djangoapps/teams/static/teams/js/views/teams.js @@ -16,12 +16,9 @@ }, initialize: function (options) { - this.topic = options.topic; - this.teamMemberships = options.teamMemberships; this.context = options.context; this.itemViewClass = TeamCardView.extend({ router: options.router, - topic: options.topic, maxTeamSize: this.context.maxTeamSize, srInfo: this.srInfo, countries: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries), 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 d95cc7b428..7aadb4fed2 100644 --- a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js +++ b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js @@ -12,7 +12,7 @@ 'teams/js/collections/topic', 'teams/js/models/team', 'teams/js/collections/team', - 'teams/js/collections/team_membership', + 'teams/js/collections/my_teams', 'teams/js/utils/team_analytics', 'teams/js/views/teams_tabbed_view', 'teams/js/views/topics', @@ -26,7 +26,7 @@ 'teams/js/views/instructor_tools', 'text!teams/templates/teams_tab.underscore'], function (Backbone, $, _, gettext, SearchFieldView, HeaderView, HeaderModel, - TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection, TeamAnalytics, + TopicModel, TopicCollection, TeamModel, TeamCollection, MyTeamsCollection, TeamAnalytics, TeamsTabbedView, TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView, TeamMembersEditView, TeamProfileHeaderActionsView, TeamUtils, InstructorToolsView, teamsTemplate) { var TeamsHeaderModel = HeaderModel.extend({ @@ -95,26 +95,22 @@ // Create an event queue to track team changes this.teamEvents = _.clone(Backbone.Events); - - this.teamMemberships = new TeamMembershipCollection( - this.context.userInfo.team_memberships_data, + this.myTeamsCollection = new MyTeamsCollection( + this.context.userInfo.teams, { teamEvents: this.teamEvents, - url: this.context.teamMembershipsUrl, course_id: this.context.courseID, username: this.context.userInfo.username, - privileged: this.context.userInfo.privileged, - staff: this.context.userInfo.staff, - parse: true + per_page: 2, + parse: true, + url: this.context.myTeamsUrl } - ).bootstrap(); - + ); this.myTeamsView = new MyTeamsView({ router: this.router, teamEvents: this.teamEvents, context: this.context, - collection: this.teamMemberships, - teamMemberships: this.teamMemberships + collection: this.myTeamsCollection }); this.topicsCollection = new TopicCollection( @@ -176,7 +172,7 @@ // 1. If the user belongs to at least one team, jump to the "My Teams" page // 2. If not, then jump to the "Browse" page if (Backbone.history.getFragment() === '') { - if (this.teamMemberships.length > 0) { + if (this.myTeamsCollection.length > 0) { this.router.navigate('my-teams', {trigger: true}); } else { this.router.navigate('browse', {trigger: true}); @@ -351,12 +347,13 @@ createTeamsListView: function(options) { var topic = options.topic, collection = options.collection, + self = this, teamsView = new TopicTeamsView({ router: this.router, context: this.context, model: topic, collection: collection, - teamMemberships: this.teamMemberships, + myTeamsCollection: this.myTeamsCollection, showSortControls: options.showSortControls }), searchFieldView = new SearchFieldView({ diff --git a/lms/djangoapps/teams/static/teams/js/views/topic_teams.js b/lms/djangoapps/teams/static/teams/js/views/topic_teams.js index b545483d5d..1617d8cbb1 100644 --- a/lms/djangoapps/teams/static/teams/js/views/topic_teams.js +++ b/lms/djangoapps/teams/static/teams/js/views/topic_teams.js @@ -16,38 +16,44 @@ initialize: function(options) { this.showSortControls = options.showSortControls; + this.context = options.context; + this.myTeamsCollection = options.myTeamsCollection; TeamsView.prototype.initialize.call(this, options); }, + canUserCreateTeam: function () { + // Note: non-staff and non-privileged users are automatically added to any team + // that they create. This means that if multiple team membership is + // disabled that they cannot create a new team when they already + // belong to one. + return this.context.staff || this.context.privileged || this.myTeamsCollection.length === 0; + }, + render: function() { var self = this; - $.when( - this.collection.refresh(), - this.teamMemberships.refresh() - ).done(function() { - TeamsView.prototype.render.call(self); - - if (self.teamMemberships.canUserCreateTeam()) { - var message = interpolate_text( - // Translators: this string is shown at the bottom of the teams page - // to find a team to join or else to create a new one. There are three - // links that need to be included in the message: - // 1. Browse teams in other topics - // 2. search teams - // 3. create a new team - // Be careful to start each link with the appropriate start indicator - // (e.g. {browse_span_start} for #1) and finish it with {span_end}. - _.escape(gettext("{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. 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': '' - } - ); - self.$el.append(_.template(teamActionsTemplate, {message: message})); - } - }); + this.collection.refresh().done(function() { + TeamsView.prototype.render.call(self); + if (self.canUserCreateTeam()) { + var message = interpolate_text( + // Translators: this string is shown at the bottom of the teams page + // to find a team to join or else to create a new one. There are three + // links that need to be included in the message: + // 1. Browse teams in other topics + // 2. search teams + // 3. create a new team + // Be careful to start each link with the appropriate start indicator + // (e.g. {browse_span_start} for #1) and finish it with {span_end}. + _.escape(gettext("{browse_span_start}Browse teams in other topics{span_end} or {search_span_start}search teams{span_end} in this topic. 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': '' + } + ); + self.$el.append(_.template(teamActionsTemplate, {message: message})); + } + }); return this; }, @@ -68,7 +74,10 @@ showCreateTeamForm: function (event) { event.preventDefault(); - Backbone.history.navigate('topics/' + this.model.id + '/create-team', {trigger: true}); + Backbone.history.navigate( + 'topics/' + this.model.id + '/create-team', + {trigger: true} + ); }, createHeaderView: function () { diff --git a/lms/djangoapps/teams/templates/teams/teams.html b/lms/djangoapps/teams/templates/teams/teams.html index 60e0382eec..25aa714ac1 100644 --- a/lms/djangoapps/teams/templates/teams/teams.html +++ b/lms/djangoapps/teams/templates/teams/teams.html @@ -42,6 +42,7 @@ teamsDetailUrl: '${ teams_detail_url }', teamMembershipsUrl: '${ team_memberships_url }', teamMembershipDetailUrl: '${ team_membership_detail_url }', + myTeamsUrl: '${ my_teams_url }', maxTeamSize: ${ course.teams_max_size }, languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) }, countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) }, diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index b84e63416f..a498a26f30 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -507,6 +507,10 @@ class TestListTeamsAPI(EventTestMixin, TeamAPITestCase): def test_filter_topic_id(self): self.verify_names({'course_id': self.test_course_1.id, 'topic_id': 'topic_0'}, 200, [u'Sólar team']) + def test_filter_username(self): + self.verify_names({'course_id': self.test_course_1.id, 'username': 'student_enrolled'}, 200, [u'Sólar team']) + self.verify_names({'course_id': self.test_course_1.id, 'username': 'staff'}, 200, []) + @ddt.data( (None, 200, ['Nuclear Team', u'Sólar team', 'Wind Team']), ('name', 200, ['Nuclear Team', u'Sólar team', 'Wind Team']), diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py index c0a0775c13..b17300e56c 100644 --- a/lms/djangoapps/teams/views.py +++ b/lms/djangoapps/teams/views.py @@ -107,8 +107,8 @@ class TopicsPagination(TeamAPIPagination): page_size = TOPICS_PER_PAGE -class MembershipPagination(TeamAPIPagination): - """Paginate memberships. """ +class MyTeamsPagination(TeamAPIPagination): + """Paginate the user's teams. """ page_size = TEAM_MEMBERSHIPS_PER_PAGE @@ -153,14 +153,15 @@ class TeamsDashboardView(GenericAPIView): ) topics_data["sort_order"] = sort_order - # Paginate and serialize team membership data. - team_memberships = CourseTeamMembership.get_memberships(user.username, [course.id]) - memberships_data = self._serialize_and_paginate( - MembershipPagination, - team_memberships, + user = request.user + + user_teams = CourseTeam.objects.filter(membership__user=user) + user_teams_data = self._serialize_and_paginate( + MyTeamsPagination, + user_teams, request, - MembershipSerializer, - {'expand': ('team', 'user',)} + CourseTeamSerializer, + {'expand': ('user',)} ) context = { @@ -173,7 +174,7 @@ class TeamsDashboardView(GenericAPIView): "username": user.username, "privileged": has_discussion_privileges(user, course_key), "staff": bool(has_access(user, 'staff', course_key)), - "team_memberships_data": memberships_data, + "teams": user_teams_data }, "topic_url": reverse( 'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request @@ -182,6 +183,7 @@ class TeamsDashboardView(GenericAPIView): "teams_url": reverse('teams_list', request=request), "teams_detail_url": reverse('teams_detail', args=['team_id']), "team_memberships_url": reverse('team_membership_list', request=request), + "my_teams_url": reverse('teams_list', request=request), "team_membership_detail_url": reverse('team_membership_detail', args=['team_id', user.username]), "languages": [[lang[0], _(lang[1])] for lang in settings.ALL_LANGUAGES], # pylint: disable=translation-of-non-string "countries": list(countries), @@ -283,6 +285,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): * last_activity_at: Orders result by team activity, with most active first (for tie-breaking, open_slots is used, with most open slots first). + * username: Return teams whose membership contains the given user. + * page_size: Number of results to return per page. * page: Page number to retrieve. @@ -414,6 +418,10 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): status=status.HTTP_400_BAD_REQUEST ) + username = request.query_params.get('username', None) + if username is not None: + result_filter.update({'membership__user__username': username}) + topic_id = request.query_params.get('topic_id', None) if topic_id is not None: if topic_id not in [topic['id'] for topic in course_module.teams_configuration['topics']]: