Commit c67bcaef by Peter Fogg

Merge pull request #9948 from edx/peter-fogg/fix-team-membership-count

Fix displaying wrong membership on "My Team" view.
parents 82b199b6 ffe2aef4
...@@ -46,6 +46,11 @@ class TeamCardsMixin(object): ...@@ -46,6 +46,11 @@ class TeamCardsMixin(object):
"""Return the names of each team on the page.""" """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 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): class BreadcrumbsMixin(object):
"""Provides common operations on teams page breadcrumb links.""" """Provides common operations on teams page breadcrumb links."""
......
...@@ -78,6 +78,18 @@ class TeamsTabBase(EventsTestMixin, UniqueCourseTest): ...@@ -78,6 +78,18 @@ class TeamsTabBase(EventsTestMixin, UniqueCourseTest):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
return json.loads(response.text) 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): def create_membership(self, username, team_id):
"""Assign `username` to `team_id`.""" """Assign `username` to `team_id`."""
response = self.course_fixture.session.post( response = self.course_fixture.session.post(
...@@ -339,6 +351,18 @@ class MyTeamsTest(TeamsTabBase): ...@@ -339,6 +351,18 @@ class MyTeamsTest(TeamsTabBase):
self.my_teams_page.visit() self.my_teams_page.visit()
self.verify_teams(self.my_teams_page, teams) 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') @attr('shard_5')
@ddt.ddt @ddt.ddt
......
;(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);
...@@ -9,8 +9,6 @@ ...@@ -9,8 +9,6 @@
this.perPage = options.per_page || 10; this.perPage = options.per_page || 10;
this.username = options.username; this.username = options.username;
this.privileged = options.privileged;
this.staff = options.staff;
this.server_api = _.extend( this.server_api = _.extend(
{ {
...@@ -24,15 +22,7 @@ ...@@ -24,15 +22,7 @@
delete this.server_api['order_by']; // Order by 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: 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;
}
}); });
return TeamMembershipCollection; return TeamMembershipCollection;
}); });
......
define([ define([
'backbone', 'backbone',
'teams/js/collections/team', 'teams/js/collections/my_teams',
'teams/js/collections/team_membership',
'teams/js/views/my_teams', 'teams/js/views/my_teams',
'teams/js/spec_helpers/team_spec_helpers', 'teams/js/spec_helpers/team_spec_helpers',
'common/js/spec_helpers/ajax_helpers' 'common/js/spec_helpers/ajax_helpers'
], function (Backbone, TeamCollection, TeamMembershipCollection, MyTeamsView, TeamSpecHelpers, AjaxHelpers) { ], function (Backbone, MyTeamsCollection, MyTeamsView, TeamSpecHelpers, AjaxHelpers) {
'use strict'; 'use strict';
describe('My Teams View', function () { describe('My Teams View', function () {
beforeEach(function () { beforeEach(function () {
setFixtures('<div class="teams-container"></div>'); setFixtures('<div class="teams-container"></div>');
}); });
var createMyTeamsView = function(options) { var createMyTeamsView = function(myTeams) {
return new MyTeamsView(_.extend( return new MyTeamsView({
{
el: '.teams-container', el: '.teams-container',
collection: options.teams || TeamSpecHelpers.createMockTeams(), collection: myTeams,
teamMemberships: TeamSpecHelpers.createMockTeamMemberships(),
showActions: true, showActions: true,
context: TeamSpecHelpers.testContext context: TeamSpecHelpers.testContext
}, }).render();
options
)).render();
}; };
it('can render itself', function () { it('can render itself', function () {
var teamMembershipsData = TeamSpecHelpers.createMockTeamMembershipsData(1, 5), var teamsData = TeamSpecHelpers.createMockTeamData(1, 5),
teamMemberships = TeamSpecHelpers.createMockTeamMemberships(teamMembershipsData), teams = TeamSpecHelpers.createMockTeams({results: teamsData}),
myTeamsView = createMyTeamsView({ myTeamsView = createMyTeamsView(teams);
teams: teamMemberships, TeamSpecHelpers.verifyCards(myTeamsView, teamsData);
teamMemberships: teamMemberships
});
TeamSpecHelpers.verifyCards(myTeamsView, teamMembershipsData);
// Verify that there is no header or footer // Verify that there is no header or footer
expect(myTeamsView.$('.teams-paging-header').text().trim()).toBe(''); expect(myTeamsView.$('.teams-paging-header').text().trim()).toBe('');
...@@ -41,36 +32,37 @@ define([ ...@@ -41,36 +32,37 @@ define([
}); });
it('shows a message when the user is not a member of any teams', function () { it('shows a message when the user is not a member of any teams', function () {
var teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]), var teams = TeamSpecHelpers.createMockTeams({results: []}),
myTeamsView = createMyTeamsView({ myTeamsView = createMyTeamsView(teams);
teams: teamMemberships,
teamMemberships: teamMemberships
});
TeamSpecHelpers.verifyCards(myTeamsView, []); TeamSpecHelpers.verifyCards(myTeamsView, []);
expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.'); expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.');
}); });
it('refreshes a stale membership collection when rendering', function() { it('refreshes a stale membership collection when rendering', function() {
var requests = AjaxHelpers.requests(this), var requests = AjaxHelpers.requests(this),
teamMemberships = TeamSpecHelpers.createMockTeamMemberships([]), teams = TeamSpecHelpers.createMockTeams({
myTeamsView = createMyTeamsView({ results: []
teams: teamMemberships, }, {
teamMemberships: teamMemberships per_page: 2,
}); url: TeamSpecHelpers.testContext.myTeamsUrl,
username: TeamSpecHelpers.testContext.userInfo.username
}, MyTeamsCollection),
myTeamsView = createMyTeamsView(teams);
TeamSpecHelpers.verifyCards(myTeamsView, []); TeamSpecHelpers.verifyCards(myTeamsView, []);
expect(myTeamsView.$el.text().trim()).toBe('You are not currently a member of any team.'); 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(); myTeamsView.render();
AjaxHelpers.expectRequestURL( AjaxHelpers.expectRequestURL(
requests, requests,
TeamSpecHelpers.testContext.teamMembershipsUrl, TeamSpecHelpers.testContext.myTeamsUrl,
{ {
expand : 'team,user', expand : 'user',
username : TeamSpecHelpers.testContext.userInfo.username, username : TeamSpecHelpers.testContext.userInfo.username,
course_id : TeamSpecHelpers.testContext.courseID, course_id : TeamSpecHelpers.testContext.courseID,
page : '1', page : '1',
page_size : '10', page_size : '2',
text_search: '' text_search: '',
order_by: 'last_activity_at'
} }
); );
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
......
define([ define([
'backbone', 'backbone',
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/collections/team_membership',
'teams/js/views/teams', 'teams/js/views/teams',
'teams/js/spec_helpers/team_spec_helpers' 'teams/js/spec_helpers/team_spec_helpers'
], function (Backbone, TeamCollection, TeamMembershipCollection, TeamsView, TeamSpecHelpers) { ], function (Backbone, TeamCollection, TeamsView, TeamSpecHelpers) {
'use strict'; 'use strict';
describe('Teams View', function () { describe('Teams View', function () {
beforeEach(function () { beforeEach(function () {
...@@ -15,7 +14,6 @@ define([ ...@@ -15,7 +14,6 @@ define([
return new TeamsView({ return new TeamsView({
el: '.teams-container', el: '.teams-container',
collection: options.teams || TeamSpecHelpers.createMockTeams(), collection: options.teams || TeamSpecHelpers.createMockTeams(),
teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(),
showActions: true, showActions: true,
context: TeamSpecHelpers.testContext context: TeamSpecHelpers.testContext
}).render(); }).render();
......
define([ define([
'backbone', 'backbone',
'teams/js/collections/team', 'underscore',
'teams/js/collections/team_membership',
'teams/js/views/topic_teams', 'teams/js/views/topic_teams',
'teams/js/spec_helpers/team_spec_helpers', 'teams/js/spec_helpers/team_spec_helpers',
'common/js/spec_helpers/ajax_helpers',
'common/js/spec_helpers/page_helpers' 'common/js/spec_helpers/page_helpers'
], function (Backbone, TeamCollection, TeamMembershipCollection, TopicTeamsView, TeamSpecHelpers, ], function (Backbone, _, TopicTeamsView, TeamSpecHelpers, PageHelpers) {
AjaxHelpers, PageHelpers) {
'use strict'; 'use strict';
describe('Topic Teams View', function () { describe('Topic Teams View', function () {
var createTopicTeamsView = function(options) { var createTopicTeamsView = function(options) {
options = options || {};
var myTeamsCollection = options.myTeamsCollection || TeamSpecHelpers.createMockTeams({results: []});
return new TopicTeamsView({ return new TopicTeamsView({
el: '.teams-container', el: '.teams-container',
model: TeamSpecHelpers.createMockTopic(), model: TeamSpecHelpers.createMockTopic(),
collection: options.teams || TeamSpecHelpers.createMockTeams(), collection: options.teams || TeamSpecHelpers.createMockTeams(),
teamMemberships: options.teamMemberships || TeamSpecHelpers.createMockTeamMemberships(), myTeamsCollection: myTeamsCollection,
showActions: true, showActions: true,
context: TeamSpecHelpers.testContext context: _.extend({}, TeamSpecHelpers.testContext, options)
}).render(); }).render();
}; };
...@@ -49,8 +48,7 @@ define([ ...@@ -49,8 +48,7 @@ define([
teamsView = createTopicTeamsView({ teamsView = createTopicTeamsView({
teams: TeamSpecHelpers.createMockTeams({ teams: TeamSpecHelpers.createMockTeams({
results: testTeamData results: testTeamData
}), })
teamMemberships: TeamSpecHelpers.createMockTeamMemberships([])
}); });
expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 6 total'); expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 6 total');
...@@ -64,24 +62,21 @@ define([ ...@@ -64,24 +62,21 @@ define([
}); });
it('can browse all teams', function () { it('can browse all teams', function () {
var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), var teamsView = createTopicTeamsView();
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
teamsView.$('.browse-teams').click(); teamsView.$('.browse-teams').click();
expect(Backbone.history.navigate.calls[0].args).toContain('browse'); expect(Backbone.history.navigate.calls[0].args).toContain('browse');
}); });
it('gives the search field focus when clicking on the search teams link', function () { it('gives the search field focus when clicking on the search teams link', function () {
var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), var teamsView = createTopicTeamsView();
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn($.fn, 'focus').andCallThrough(); spyOn($.fn, 'focus').andCallThrough();
teamsView.$('.search-teams').click(); teamsView.$('.search-teams').click();
expect(teamsView.$('.search-field').first().focus).toHaveBeenCalled(); expect(teamsView.$('.search-field').first().focus).toHaveBeenCalled();
}); });
it('can show the create team modal', function () { it('can show the create team modal', function () {
var emptyMembership = TeamSpecHelpers.createMockTeamMemberships([]), var teamsView = createTopicTeamsView();
teamsView = createTopicTeamsView({ teamMemberships: emptyMembership });
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
teamsView.$('a.create-team').click(); teamsView.$('a.create-team').click();
expect(Backbone.history.navigate.calls[0].args).toContain( expect(Backbone.history.navigate.calls[0].args).toContain(
...@@ -90,25 +85,17 @@ define([ ...@@ -90,25 +85,17 @@ define([
}); });
it('does not show actions for a user already in a team', function () { 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}); verifyActions(teamsView, {showActions: false});
}); });
it('shows actions for a privileged user already in a team', function () { it('shows actions for a privileged user already in a team', function () {
var staffMembership = TeamSpecHelpers.createMockTeamMemberships( var teamsView = createTopicTeamsView({ privileged: true });
TeamSpecHelpers.createMockTeamMembershipsData(1, 5),
{ privileged: true }
),
teamsView = createTopicTeamsView({ teamMemberships: staffMembership });
verifyActions(teamsView); verifyActions(teamsView);
}); });
it('shows actions for a staff user already in a team', function () { it('shows actions for a staff user already in a team', function () {
var staffMembership = TeamSpecHelpers.createMockTeamMemberships( var teamsView = createTopicTeamsView({ privileged: false, staff: true });
TeamSpecHelpers.createMockTeamMembershipsData(1, 5),
{ privileged: false, staff: true }
),
teamsView = createTopicTeamsView({ teamMemberships: staffMembership });
verifyActions(teamsView); verifyActions(teamsView);
}); });
......
...@@ -56,14 +56,18 @@ define([ ...@@ -56,14 +56,18 @@ define([
); );
}; };
var createMockTeams = function(options) { var createMockTeams = function(responseOptions, options, collectionType) {
return new TeamCollection( if(_.isUndefined(collectionType)) {
createMockTeamsResponse(options), collectionType = TeamCollection;
{ }
return new collectionType(
createMockTeamsResponse(responseOptions),
_.extend({
teamEvents: teamEvents, teamEvents: teamEvents,
course_id: testCourseID, course_id: testCourseID,
per_page: 2,
parse: true parse: true
} }, options)
); );
}; };
...@@ -83,35 +87,6 @@ define([ ...@@ -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) { var createMockUserInfo = function(options) {
return _.extend( return _.extend(
{ {
...@@ -291,6 +266,7 @@ define([ ...@@ -291,6 +266,7 @@ define([
teamsDetailUrl: '/api/team/v0/teams/team_id', teamsDetailUrl: '/api/team/v0/teams/team_id',
teamMembershipsUrl: '/api/team/v0/team_memberships/', teamMembershipsUrl: '/api/team/v0/team_memberships/',
teamMembershipDetailUrl: '/api/team/v0/team_membership/team_id,' + testUser, teamMembershipDetailUrl: '/api/team/v0/team_membership/team_id,' + testUser,
myTeamsUrl: '/api/team/v0/teams/',
userInfo: createMockUserInfo() userInfo: createMockUserInfo()
}; };
...@@ -331,8 +307,6 @@ define([ ...@@ -331,8 +307,6 @@ define([
createMockTeamData: createMockTeamData, createMockTeamData: createMockTeamData,
createMockTeamsResponse: createMockTeamsResponse, createMockTeamsResponse: createMockTeamsResponse,
createMockTeams: createMockTeams, createMockTeams: createMockTeams,
createMockTeamMembershipsData: createMockTeamMembershipsData,
createMockTeamMemberships: createMockTeamMemberships,
createMockUserInfo: createMockUserInfo, createMockUserInfo: createMockUserInfo,
createMockContext: createMockContext, createMockContext: createMockContext,
createMockTopic: createMockTopic, createMockTopic: createMockTopic,
......
...@@ -22,12 +22,11 @@ ...@@ -22,12 +22,11 @@
}, },
initialize: function(options) { initialize: function(options) {
this.teamMembershipDetailUrl = options.context.teamMembershipDetailUrl;
// The URL ends with team_id,request_username. We want to replace // 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 // 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. // as the actual user to be removed from the team will be added on before calling DELETE.
this.teamMembershipDetailUrl = this.teamMembershipDetailUrl.substring( this.teamMembershipDetailUrl = options.context.teamMembershipDetailUrl.substring(
0, this.teamMembershipDetailUrl.lastIndexOf('team_id') 0, this.options.context.teamMembershipDetailUrl.lastIndexOf('team_id')
) + this.model.get('id') + ","; ) + this.model.get('id') + ",";
this.teamEvents = options.teamEvents; this.teamEvents = options.teamEvents;
......
;(function (define) { (function (define) {
'use strict'; 'use strict';
define([ define([
'jquery', 'jquery',
...@@ -99,48 +99,34 @@ ...@@ -99,48 +99,34 @@
CardView.prototype.initialize.apply(this, arguments); CardView.prototype.initialize.apply(this, arguments);
// TODO: show last activity detail view // TODO: show last activity detail view
this.detailViews = [ this.detailViews = [
new TeamMembershipView({memberships: this.getMemberships(), maxTeamSize: this.maxTeamSize}), new TeamMembershipView({memberships: this.model.get('membership'), maxTeamSize: this.maxTeamSize}),
new TeamCountryLanguageView({ new TeamCountryLanguageView({
model: this.teamModel(), model: this.model,
countries: this.countries, countries: this.countries,
languages: this.languages 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.model.on('change:membership', function () {
this.detailViews[0].memberships = this.getMemberships(); this.detailViews[0].memberships = this.model.get('membership');
}, this); }, 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', configuration: 'list_card',
cardClass: 'team-card', cardClass: 'team-card',
title: function () { return this.teamModel().get('name'); }, title: function () { return this.model.get('name'); },
description: function () { return this.teamModel().get('description'); }, description: function () { return this.model.get('description'); },
details: function () { return this.detailViews; }, details: function () { return this.detailViews; },
actionClass: 'action-view', actionClass: 'action-view',
actionContent: function() { actionContent: function() {
return interpolate( return interpolate(
gettext('View %(span_start)s %(team_name)s %(span_end)s'), gettext('View %(span_start)s %(team_name)s %(span_end)s'),
{span_start: '<span class="sr">', team_name: _.escape(this.teamModel().get('name')), span_end: '</span>'}, {span_start: '<span class="sr">', team_name: _.escape(this.model.get('name')), span_end: '</span>'},
true true
); );
}, },
actionUrl: function () { 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; return TeamCardView;
......
...@@ -16,12 +16,9 @@ ...@@ -16,12 +16,9 @@
}, },
initialize: function (options) { initialize: function (options) {
this.topic = options.topic;
this.teamMemberships = options.teamMemberships;
this.context = options.context; this.context = options.context;
this.itemViewClass = TeamCardView.extend({ this.itemViewClass = TeamCardView.extend({
router: options.router, router: options.router,
topic: options.topic,
maxTeamSize: this.context.maxTeamSize, maxTeamSize: this.context.maxTeamSize,
srInfo: this.srInfo, srInfo: this.srInfo,
countries: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries), countries: TeamUtils.selectorOptionsArrayToHashWithBlank(this.context.countries),
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
'teams/js/collections/topic', 'teams/js/collections/topic',
'teams/js/models/team', 'teams/js/models/team',
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/collections/team_membership', 'teams/js/collections/my_teams',
'teams/js/utils/team_analytics', 'teams/js/utils/team_analytics',
'teams/js/views/teams_tabbed_view', 'teams/js/views/teams_tabbed_view',
'teams/js/views/topics', 'teams/js/views/topics',
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
'teams/js/views/instructor_tools', 'teams/js/views/instructor_tools',
'text!teams/templates/teams_tab.underscore'], 'text!teams/templates/teams_tab.underscore'],
function (Backbone, $, _, gettext, SearchFieldView, HeaderView, HeaderModel, function (Backbone, $, _, gettext, SearchFieldView, HeaderView, HeaderModel,
TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection, TeamAnalytics, TopicModel, TopicCollection, TeamModel, TeamCollection, MyTeamsCollection, TeamAnalytics,
TeamsTabbedView, TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView, TeamsTabbedView, TopicsView, TeamProfileView, MyTeamsView, TopicTeamsView, TeamEditView,
TeamMembersEditView, TeamProfileHeaderActionsView, TeamUtils, InstructorToolsView, teamsTemplate) { TeamMembersEditView, TeamProfileHeaderActionsView, TeamUtils, InstructorToolsView, teamsTemplate) {
var TeamsHeaderModel = HeaderModel.extend({ var TeamsHeaderModel = HeaderModel.extend({
...@@ -95,26 +95,22 @@ ...@@ -95,26 +95,22 @@
// Create an event queue to track team changes // Create an event queue to track team changes
this.teamEvents = _.clone(Backbone.Events); this.teamEvents = _.clone(Backbone.Events);
this.myTeamsCollection = new MyTeamsCollection(
this.teamMemberships = new TeamMembershipCollection( this.context.userInfo.teams,
this.context.userInfo.team_memberships_data,
{ {
teamEvents: this.teamEvents, teamEvents: this.teamEvents,
url: this.context.teamMembershipsUrl,
course_id: this.context.courseID, course_id: this.context.courseID,
username: this.context.userInfo.username, username: this.context.userInfo.username,
privileged: this.context.userInfo.privileged, per_page: 2,
staff: this.context.userInfo.staff, parse: true,
parse: true url: this.context.myTeamsUrl
} }
).bootstrap(); );
this.myTeamsView = new MyTeamsView({ this.myTeamsView = new MyTeamsView({
router: this.router, router: this.router,
teamEvents: this.teamEvents, teamEvents: this.teamEvents,
context: this.context, context: this.context,
collection: this.teamMemberships, collection: this.myTeamsCollection
teamMemberships: this.teamMemberships
}); });
this.topicsCollection = new TopicCollection( this.topicsCollection = new TopicCollection(
...@@ -176,7 +172,7 @@ ...@@ -176,7 +172,7 @@
// 1. If the user belongs to at least one team, jump to the "My Teams" page // 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 // 2. If not, then jump to the "Browse" page
if (Backbone.history.getFragment() === '') { if (Backbone.history.getFragment() === '') {
if (this.teamMemberships.length > 0) { if (this.myTeamsCollection.length > 0) {
this.router.navigate('my-teams', {trigger: true}); this.router.navigate('my-teams', {trigger: true});
} else { } else {
this.router.navigate('browse', {trigger: true}); this.router.navigate('browse', {trigger: true});
...@@ -351,12 +347,13 @@ ...@@ -351,12 +347,13 @@
createTeamsListView: function(options) { createTeamsListView: function(options) {
var topic = options.topic, var topic = options.topic,
collection = options.collection, collection = options.collection,
self = this,
teamsView = new TopicTeamsView({ teamsView = new TopicTeamsView({
router: this.router, router: this.router,
context: this.context, context: this.context,
model: topic, model: topic,
collection: collection, collection: collection,
teamMemberships: this.teamMemberships, myTeamsCollection: this.myTeamsCollection,
showSortControls: options.showSortControls showSortControls: options.showSortControls
}), }),
searchFieldView = new SearchFieldView({ searchFieldView = new SearchFieldView({
......
...@@ -16,18 +16,24 @@ ...@@ -16,18 +16,24 @@
initialize: function(options) { initialize: function(options) {
this.showSortControls = options.showSortControls; this.showSortControls = options.showSortControls;
this.context = options.context;
this.myTeamsCollection = options.myTeamsCollection;
TeamsView.prototype.initialize.call(this, options); 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() { render: function() {
var self = this; var self = this;
$.when( this.collection.refresh().done(function() {
this.collection.refresh(),
this.teamMemberships.refresh()
).done(function() {
TeamsView.prototype.render.call(self); TeamsView.prototype.render.call(self);
if (self.canUserCreateTeam()) {
if (self.teamMemberships.canUserCreateTeam()) {
var message = interpolate_text( var message = interpolate_text(
// Translators: this string is shown at the bottom of the teams page // 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 // to find a team to join or else to create a new one. There are three
...@@ -68,7 +74,10 @@ ...@@ -68,7 +74,10 @@
showCreateTeamForm: function (event) { showCreateTeamForm: function (event) {
event.preventDefault(); 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 () { createHeaderView: function () {
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
teamsDetailUrl: '${ teams_detail_url }', teamsDetailUrl: '${ teams_detail_url }',
teamMembershipsUrl: '${ team_memberships_url }', teamMembershipsUrl: '${ team_memberships_url }',
teamMembershipDetailUrl: '${ team_membership_detail_url }', teamMembershipDetailUrl: '${ team_membership_detail_url }',
myTeamsUrl: '${ my_teams_url }',
maxTeamSize: ${ course.teams_max_size }, maxTeamSize: ${ course.teams_max_size },
languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) }, languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) },
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) }, countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) },
......
...@@ -507,6 +507,10 @@ class TestListTeamsAPI(EventTestMixin, TeamAPITestCase): ...@@ -507,6 +507,10 @@ class TestListTeamsAPI(EventTestMixin, TeamAPITestCase):
def test_filter_topic_id(self): 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']) 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( @ddt.data(
(None, 200, ['Nuclear Team', u'Sólar team', 'Wind Team']), (None, 200, ['Nuclear Team', u'Sólar team', 'Wind Team']),
('name', 200, ['Nuclear Team', u'Sólar team', 'Wind Team']), ('name', 200, ['Nuclear Team', u'Sólar team', 'Wind Team']),
......
...@@ -107,8 +107,8 @@ class TopicsPagination(TeamAPIPagination): ...@@ -107,8 +107,8 @@ class TopicsPagination(TeamAPIPagination):
page_size = TOPICS_PER_PAGE page_size = TOPICS_PER_PAGE
class MembershipPagination(TeamAPIPagination): class MyTeamsPagination(TeamAPIPagination):
"""Paginate memberships. """ """Paginate the user's teams. """
page_size = TEAM_MEMBERSHIPS_PER_PAGE page_size = TEAM_MEMBERSHIPS_PER_PAGE
...@@ -153,14 +153,15 @@ class TeamsDashboardView(GenericAPIView): ...@@ -153,14 +153,15 @@ class TeamsDashboardView(GenericAPIView):
) )
topics_data["sort_order"] = sort_order topics_data["sort_order"] = sort_order
# Paginate and serialize team membership data. user = request.user
team_memberships = CourseTeamMembership.get_memberships(user.username, [course.id])
memberships_data = self._serialize_and_paginate( user_teams = CourseTeam.objects.filter(membership__user=user)
MembershipPagination, user_teams_data = self._serialize_and_paginate(
team_memberships, MyTeamsPagination,
user_teams,
request, request,
MembershipSerializer, CourseTeamSerializer,
{'expand': ('team', 'user',)} {'expand': ('user',)}
) )
context = { context = {
...@@ -173,7 +174,7 @@ class TeamsDashboardView(GenericAPIView): ...@@ -173,7 +174,7 @@ class TeamsDashboardView(GenericAPIView):
"username": user.username, "username": user.username,
"privileged": has_discussion_privileges(user, course_key), "privileged": has_discussion_privileges(user, course_key),
"staff": bool(has_access(user, 'staff', course_key)), "staff": bool(has_access(user, 'staff', course_key)),
"team_memberships_data": memberships_data, "teams": user_teams_data
}, },
"topic_url": reverse( "topic_url": reverse(
'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request 'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request
...@@ -182,6 +183,7 @@ class TeamsDashboardView(GenericAPIView): ...@@ -182,6 +183,7 @@ class TeamsDashboardView(GenericAPIView):
"teams_url": reverse('teams_list', request=request), "teams_url": reverse('teams_list', request=request),
"teams_detail_url": reverse('teams_detail', args=['team_id']), "teams_detail_url": reverse('teams_detail', args=['team_id']),
"team_memberships_url": reverse('team_membership_list', request=request), "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]), "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 "languages": [[lang[0], _(lang[1])] for lang in settings.ALL_LANGUAGES], # pylint: disable=translation-of-non-string
"countries": list(countries), "countries": list(countries),
...@@ -283,6 +285,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -283,6 +285,8 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
* last_activity_at: Orders result by team activity, with most active first * last_activity_at: Orders result by team activity, with most active first
(for tie-breaking, open_slots is used, with most open slots 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_size: Number of results to return per page.
* page: Page number to retrieve. * page: Page number to retrieve.
...@@ -414,6 +418,10 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -414,6 +418,10 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
status=status.HTTP_400_BAD_REQUEST 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) topic_id = request.query_params.get('topic_id', None)
if topic_id is not None: if topic_id is not None:
if topic_id not in [topic['id'] for topic in course_module.teams_configuration['topics']]: if topic_id not in [topic['id'] for topic in course_module.teams_configuration['topics']]:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment