Commit a38ae447 by Peter Fogg

Fix displaying wrong membership on "My Team" view.

The heart of this fix is to change the "My Team" view to use a
collection of teams instead of memberships. The team card is
refactored to only take a team, rather than attempt to be polymorphic
over teams and team memberships. This change enabled removing a good
amount of old code. This also requires adding a `username` parameter
to the teams list endpoint which allows getting a list of all teams
for a single user, in order to allow refreshing the "My Teams" view
correctly. Currently this list is of length at most one.

TNL-3410
parent 296f75a0
......@@ -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."""
......
......@@ -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
......
;(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 @@
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;
});
......
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('<div class="teams-container"></div>');
});
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, {});
......
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();
......
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);
});
......
......@@ -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,
......
......@@ -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;
......
;(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: '<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
);
},
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;
......
......@@ -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),
......
......@@ -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({
......
......@@ -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': '<a class="browse-teams" href="">',
'search_span_start': '<a class="search-teams" href="">',
'create_span_start': '<a class="create-team" href="">',
'span_end': '</a>'
}
);
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': '<a class="browse-teams" href="">',
'search_span_start': '<a class="search-teams" href="">',
'create_span_start': '<a class="create-team" href="">',
'span_end': '</a>'
}
);
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 () {
......
......@@ -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) },
......
......@@ -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']),
......
......@@ -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']]:
......
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