Skip to content
Snippets Groups Projects
Commit ffe2aef4 authored by Peter Fogg's avatar Peter Fogg
Browse files

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 772b0bff
No related merge requests found
Showing
with 181 additions and 195 deletions
......@@ -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']]:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment