Commit b4ed9a28 by Usman Khalid

My Teams tab.

TNL-1911
parent 96779dfe
......@@ -11,6 +11,7 @@ from .fields import FieldsMixin
TOPIC_CARD_CSS = 'div.wrapper-card-core'
TEAMS_BUTTON_CSS = 'a.nav-item[data-index="0"]'
BROWSE_BUTTON_CSS = 'a.nav-item[data-index="1"]'
TEAMS_LINK_CSS = '.action-view'
TEAMS_HEADER_CSS = '.teams-header'
......@@ -36,11 +37,35 @@ class TeamsPage(CoursePage):
)
return self.q(css=main_page_content_css).text[0]
def active_tab(self):
""" Get the active tab. """
return self.q(css='.is-active').attrs('data-url')[0]
def browse_topics(self):
""" View the Browse tab of the Teams page. """
self.q(css=BROWSE_BUTTON_CSS).click()
class MyTeamsPage(CoursePage, PaginatedUIMixin):
"""
The 'My Teams' tab of the Teams page.
"""
url_path = "teams/#my-teams"
def is_browser_on_page(self):
"""Check if the "My Teams" tab is being viewed."""
button_classes = self.q(css=TEAMS_BUTTON_CSS).attrs('class')
if len(button_classes) == 0:
return False
return 'is-active' in button_classes[0]
@property
def team_cards(self):
"""Get all the team cards on the page."""
return self.q(css='.team-card')
class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
"""
The 'Browse' tab of the Teams page.
......
......@@ -17,7 +17,7 @@ from ...fixtures.discussion import (
from ...pages.lms.auto_auth import AutoAuthPage
from ...pages.lms.course_info import CourseInfoPage
from ...pages.lms.tab_nav import TabNavPage
from ...pages.lms.teams import TeamsPage, BrowseTopicsPage, BrowseTeamsPage, CreateTeamPage, TeamPage
from ...pages.lms.teams import TeamsPage, MyTeamsPage, BrowseTopicsPage, BrowseTeamsPage, CreateTeamPage, TeamPage
class TeamsTabBase(UniqueCourseTest):
......@@ -84,10 +84,33 @@ class TeamsTabBase(UniqueCourseTest):
if present:
self.assertIn("Teams", self.tab_nav.tab_names)
self.teams_page.visit()
self.assertEqual("This is the new Teams tab.", self.teams_page.get_body_text())
self.assertEqual(self.teams_page.active_tab(), 'my-teams')
self.assertEqual("Showing 0 out of 0 total", self.teams_page.get_body_text())
else:
self.assertNotIn("Teams", self.tab_nav.tab_names)
def verify_teams(self, page, expected_teams):
"""Verify that the list of team cards on the current page match the expected teams in order."""
def assert_team_equal(expected_team, team_card_name, team_card_description):
"""
Helper to assert that a single team card has the expected name and
description.
"""
self.assertEqual(expected_team['name'], team_card_name)
self.assertEqual(expected_team['description'], team_card_description)
team_cards = page.team_cards
team_card_names = [
team_card.find_element_by_css_selector('.card-title').text
for team_card in team_cards.results
]
team_card_descriptions = [
team_card.find_element_by_css_selector('.card-description').text
for team_card in team_cards.results
]
map(assert_team_equal, expected_teams, team_card_names, team_card_descriptions)
@ddt.ddt
@attr('shard_5')
......@@ -159,7 +182,7 @@ class TeamsTabTest(TeamsTabBase):
@ddt.data(
('browse', 'div.topics-list'),
('teams', 'p.temp-tab-view'),
('my-teams', 'div.teams-paging-header'),
('teams/{topic_id}/{team_id}', 'div.discussion-module'),
('topics/{topic_id}/create-team', 'div.create-team-instructions'),
('topics/{topic_id}', 'div.teams-list'),
......@@ -192,6 +215,55 @@ class TeamsTabTest(TeamsTabBase):
@attr('shard_5')
class MyTeamsTest(TeamsTabBase):
"""
Tests for the "My Teams" tab of the Teams page.
"""
def setUp(self):
super(MyTeamsTest, self).setUp()
self.topic = {u"name": u"Example Topic", u"id": "example_topic", u"description": "Description"}
self.set_team_configuration({'course_id': self.course_id, 'max_team_size': 10, 'topics': [self.topic]})
self.my_teams_page = MyTeamsPage(self.browser, self.course_id)
def test_not_member_of_any_teams(self):
"""
Scenario: Visiting the My Teams page when user is not a member of any team should not display any teams.
Given I am enrolled in a course with a team configuration and a topic but am not a member of a team
When I visit the My Teams page
Then I should see a pagination header showing no teams
And I should see no teams
And I should not see a pagination footer
"""
self.my_teams_page.visit()
self.assertEqual(self.my_teams_page.get_pagination_header_text(), 'Showing 0 out of 0 total')
self.assertEqual(len(self.my_teams_page.team_cards), 0, msg='Expected to see no team cards')
self.assertFalse(
self.my_teams_page.pagination_controls_visible(),
msg='Expected paging footer to be invisible'
)
def test_member_of_a_team(self):
"""
Scenario: Visiting the My Teams page when user is a member of a team should display the teams.
Given I am enrolled in a course with a team configuration and a topic and am a member of a team
When I visit the My Teams page
Then I should see a pagination header showing the number of teams
And I should see all the expected team cards
And I should not see a pagination footer
"""
teams = self.create_teams(self.topic, 1)
self.create_membership(self.user_info['username'], teams[0]['id'])
self.my_teams_page.visit()
self.assertEqual(self.my_teams_page.get_pagination_header_text(), 'Showing 1 out of 1 total')
self.verify_teams(self.my_teams_page, teams)
self.assertFalse(
self.my_teams_page.pagination_controls_visible(),
msg='Expected paging footer to be invisible'
)
@attr('shard_5')
class BrowseTopicsTest(TeamsTabBase):
"""
Tests for the Browse tab of the Teams page.
......@@ -344,28 +416,6 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
self.assertEqual(self.browse_teams_page.header_topic_name, self.topic['name'])
self.assertEqual(self.browse_teams_page.header_topic_description, self.topic['description'])
def verify_teams(self, expected_teams):
"""Verify that the list of team cards on the current page match the expected teams in order."""
def assert_team_equal(expected_team, team_card_name, team_card_description):
"""
Helper to assert that a single team card has the expected name and
description.
"""
self.assertEqual(expected_team['name'], team_card_name)
self.assertEqual(expected_team['description'], team_card_description)
team_cards = self.browse_teams_page.team_cards
team_card_names = [
team_card.find_element_by_css_selector('.card-title').text
for team_card in team_cards.results
]
team_card_descriptions = [
team_card.find_element_by_css_selector('.card-description').text
for team_card in team_cards.results
]
map(assert_team_equal, expected_teams, team_card_names, team_card_descriptions)
def verify_on_page(self, page_num, total_teams, pagination_header_text, footer_visible):
"""
Verify that we are on the correct team list page.
......@@ -381,7 +431,10 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
"""
alphabetized_teams = sorted(total_teams, key=lambda team: team['name'])
self.assertEqual(self.browse_teams_page.get_pagination_header_text(), pagination_header_text)
self.verify_teams(alphabetized_teams[(page_num - 1) * self.TEAMS_PAGE_SIZE:page_num * self.TEAMS_PAGE_SIZE])
self.verify_teams(
self.browse_teams_page,
alphabetized_teams[(page_num - 1) * self.TEAMS_PAGE_SIZE:page_num * self.TEAMS_PAGE_SIZE]
)
self.assertEqual(
self.browse_teams_page.pagination_controls_visible(),
footer_visible,
......@@ -424,7 +477,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
self.browse_teams_page.visit()
self.verify_page_header()
self.assertEqual(self.browse_teams_page.get_pagination_header_text(), 'Showing 1-10 out of 10 total')
self.verify_teams(teams)
self.verify_teams(self.browse_teams_page, teams)
self.assertFalse(
self.browse_teams_page.pagination_controls_visible(),
msg='Expected paging footer to be invisible'
......@@ -488,7 +541,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
teams = self.create_teams(self.topic, 1)
self.browse_teams_page.visit()
self.verify_page_header()
self.verify_teams(teams)
self.verify_teams(self.browse_teams_page, teams)
self.create_membership(self.user_info['username'], teams[0]['id'])
self.browser.refresh()
self.browse_teams_page.wait_for_ajax()
......
......@@ -88,3 +88,23 @@ class CourseTeamMembership(models.Model):
user = models.ForeignKey(User)
team = models.ForeignKey(CourseTeam, related_name='membership')
date_joined = models.DateTimeField(auto_now_add=True)
@classmethod
def get_memberships(cls, username=None, course_ids=None, team_id=None):
"""
Get a queryset of memberships.
Args:
username (unicode, optional): The username to filter on.
course_ids (list of unicode, optional) Course Ids to filter on.
team_id (unicode, optional): The team_id to filter on.
"""
queryset = cls.objects.all()
if username is not None:
queryset = queryset.filter(user__username=username)
if course_ids is not None:
queryset = queryset.filter(team__course_id__in=course_ids)
if team_id is not None:
queryset = queryset.filter(team__team_id=team_id)
return queryset
......@@ -113,6 +113,13 @@ class MembershipSerializer(serializers.ModelSerializer):
read_only_fields = ("date_joined",)
class PaginatedMembershipSerializer(PaginationSerializer):
"""Serializes team memberships with support for pagination."""
class Meta(object):
"""Defines meta information for the PaginatedMembershipSerializer."""
object_serializer_class = MembershipSerializer
class BaseTopicSerializer(serializers.Serializer):
"""Serializes a topic without team_count."""
description = serializers.CharField()
......
;(function (define) {
'use strict';
define(['common/js/components/collections/paging_collection', 'teams/js/models/team_membership'],
function(PagingCollection, TeamMembershipModel) {
var TeamMembershipCollection = PagingCollection.extend({
initialize: function(team_memberships, options) {
PagingCollection.prototype.initialize.call(this);
this.course_id = options.course_id;
this.username = options.username;
this.perPage = options.per_page || 10;
this.server_api['expand'] = 'team';
this.server_api['course_id'] = function () { return encodeURIComponent(this.course_id); };
this.server_api['username'] = this.username;
delete this.server_api['sort_order']; // Sort order is not specified for the TeamMembership API
delete this.server_api['order_by']; // Order by is not specified for the TeamMembership API
},
model: TeamMembershipModel
});
return TeamMembershipCollection;
});
}).call(this, define || RequireJS.define);
/**
* Model for a team membership.
*/
(function (define) {
'use strict';
define(['backbone', 'teams/js/models/team'], function (Backbone, TeamModel) {
var TeamMembership = Backbone.Model.extend({
defaults: {
date_joined: '',
team: null,
user: null
},
parse: function (response, options) {
response.team = new TeamModel(response.team);
return response;
}
});
return TeamMembership;
});
}).call(this, define || RequireJS.define);
......@@ -21,7 +21,8 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"],
});
it("can load templates", function() {
expect($("body").text()).toContain("This is the new Teams tab");
expect($("body").text()).toContain("My Teams");
expect($("body").text()).toContain("Showing 0 out of 0 total");
});
it("displays a header", function() {
......
define([
'backbone', 'teams/js/collections/team', 'teams/js/views/teams'
], function (Backbone, TeamCollection, TeamsView) {
'backbone',
'teams/js/collections/team',
'teams/js/collections/team_membership',
'teams/js/views/teams'
], function (Backbone, TeamCollection, TeamMembershipCollection, TeamsView) {
'use strict';
describe('Teams View', function () {
var teamsView, teamCollection, initialTeams,
createTeams = function (startIndex, stopIndex) {
initialTeamMemberships, teamMembershipCollection;
var createTeams = function (startIndex, stopIndex) {
return _.map(_.range(startIndex, stopIndex + 1), function (i) {
return {
name: "team " + i,
......@@ -29,6 +34,30 @@ define([
['fr', 'French']
];
var createTeamMemberships = function(startIndex, stopIndex) {
var teams = createTeams(startIndex, stopIndex)
return _.map(_.range(startIndex, stopIndex + 1), function (i) {
return {
user: {
'username': 'andya',
'url': 'https://openedx.example.com/api/user/v1/accounts/andya'
},
team: teams[i-1]
};
});
};
var verifyCards = function(view, teams) {
var teamCards = view.$('.team-card');
_.each(teams, function (team, index) {
var currentCard = teamCards.eq(index);
expect(currentCard.text()).toMatch(team.name);
expect(currentCard.text()).toMatch(_.object(languages)[team.language]);
expect(currentCard.text()).toMatch(_.object(countries)[team.country]);
});
}
beforeEach(function () {
setFixtures('<div class="teams-container"></div>');
initialTeams = createTeams(1, 5);
......@@ -40,8 +69,31 @@ define([
start: 0,
results: initialTeams
},
{course_id: 'my/course/id', parse: true}
{
course_id: 'my/course/id',
parse: true
}
);
initialTeamMemberships = createTeamMemberships(1, 5);
teamMembershipCollection = new TeamMembershipCollection(
{
count: 11,
num_pages: 3,
current_page: 1,
start: 0,
results: initialTeamMemberships
},
{
course_id: 'my/course/id',
parse: true,
url: 'api/teams/team_memberships',
username: 'andya',
}
);
});
it('can render itself with teams collection', function () {
teamsView = new TeamsView({
el: '.teams-container',
collection: teamCollection,
......@@ -50,21 +102,52 @@ define([
languages: languages
}
}).render();
});
it('can render itself', function () {
var footerEl = teamsView.$('.teams-paging-footer'),
teamCards = teamsView.$('.team-card');
expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 6 total');
_.each(initialTeams, function (team, index) {
var currentCard = teamCards.eq(index);
expect(currentCard.text()).toMatch(team.name);
expect(currentCard.text()).toMatch(_.object(languages)[team.language]);
expect(currentCard.text()).toMatch(_.object(countries)[team.country]);
});
var footerEl = teamsView.$('.teams-paging-footer');
expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+2');
expect(footerEl).not.toHaveClass('hidden');
verifyCards(teamsView, initialTeams);
});
it('can render itself with team memberships collection', function () {
teamsView = new TeamsView({
el: '.teams-container',
collection: teamMembershipCollection,
teamParams: {}
}).render();
expect(teamsView.$('.teams-paging-header').text()).toMatch('Showing 1-5 out of 11 total');
var footerEl = teamsView.$('.teams-paging-footer');
expect(footerEl.text()).toMatch('1\\s+out of\\s+\/\\s+3');
expect(footerEl).not.toHaveClass('hidden');
verifyCards(teamsView, initialTeamMemberships);
});
it ('can render the actions view', function () {
teamsView = new TeamsView({
el: '.teams-container',
collection: teamCollection,
teamParams: {},
}).render();
expect(teamsView.$el.text()).not.toContain(
'Are you having trouble finding a team to join?'
);
teamsView = new TeamsView({
el: '.teams-container',
collection: teamCollection,
teamParams: {},
showActions: true
}).render();
expect(teamsView.$el.text()).toContain(
'Are you having trouble finding a team to join?'
);
});
});
});
......@@ -38,6 +38,28 @@ define([
team_count: 0
}]
},
teamMemberships: {
count: 1,
currentPage: 1,
numPages: 1,
next: null,
previous: null,
results: [
{
user: {
username: 'andya',
url: 'https://openedx.example.com/api/user/v1/accounts/andya'
},
team: {
description: '',
name: 'Discrete Maths',
id: 'dm',
topic_id: 'algorithms'
},
date_joined: '2015-04-09T17:31:56Z'
},
]
},
topicsUrl: 'api/topics/',
topicUrl: 'api/topics/topic_id,test/course/id',
teamsUrl: 'api/teams/',
......@@ -51,17 +73,19 @@ define([
Backbone.history.stop();
});
it('shows the teams tab initially', function () {
it('shows the my teams tab initially', function () {
expectHeader('See all teams in your course, organized by topic');
expectContent('This is the new Teams tab.');
expectContent('Showing 1 out of 1 total');
expectContent('Discrete Maths');
});
describe('Navigation', function () {
it('can switch tabs', function () {
teamsTabView.$('a.nav-item[data-url="browse"]').click();
expectContent('test description');
teamsTabView.$('a.nav-item[data-url="teams"]').click();
expectContent('This is the new Teams tab.');
teamsTabView.$('a.nav-item[data-url="my-teams"]').click();
expectContent('Showing 1 out of 1 total');
expectContent('Discrete Maths');
});
it('displays and focuses an error message when trying to navigate to a nonexistent page', function () {
......
......@@ -66,30 +66,35 @@
CardView.prototype.initialize.apply(this, arguments);
// TODO: show last activity detail view
this.detailViews = [
new TeamMembershipView({model: this.model, maxTeamSize: this.maxTeamSize}),
new TeamMembershipView({model: this.teamModel(), maxTeamSize: this.maxTeamSize}),
new TeamCountryLanguageView({
model: this.model,
model: this.teamModel(),
countries: this.countries,
languages: this.languages
})
];
},
teamModel: function () {
if (this.model.has('team')) { return this.model.get('team'); };
return this.model;
},
configuration: 'list_card',
cardClass: 'team-card',
title: function () { return this.model.get('name'); },
description: function () { return this.model.get('description'); },
title: function () { return this.teamModel().get('name'); },
description: function () { return this.teamModel().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: this.model.get('name'), span_end: '</span>'},
{span_start: '<span class="sr">', team_name: this.teamModel().get('name'), span_end: '</span>'},
true
);
},
action: function (event) {
var url = 'teams/' + this.topic.get('id') + '/' + this.model.get('id');
var url = 'teams/' + this.teamModel().get('topic_id') + '/' + this.teamModel().get('id');
event.preventDefault();
this.router.navigate(url, {trigger: true});
}
......
......@@ -20,16 +20,20 @@
});
PaginatedView.prototype.initialize.call(this);
this.teamParams = options.teamParams;
this.showActions = options.showActions;
},
render: function () {
PaginatedView.prototype.render.call(this);
if (this.showActions === true) {
var teamActionsView = new TeamActionsView({
teamParams: this.teamParams
});
this.$el.append(teamActionsView.$el);
teamActionsView.render();
}
return this;
},
......
......@@ -11,13 +11,14 @@
'teams/js/collections/topic',
'teams/js/models/team',
'teams/js/collections/team',
'teams/js/collections/team_membership',
'teams/js/views/topics',
'teams/js/views/team_profile',
'teams/js/views/teams',
'teams/js/views/edit_team',
'text!teams/templates/teams_tab.underscore'],
function (Backbone, _, gettext, HeaderView, HeaderModel, TabbedView,
TopicModel, TopicCollection, TeamModel, TeamCollection,
TopicModel, TopicCollection, TeamModel, TeamCollection, TeamMembershipCollection,
TopicsView, TeamProfileView, TeamsView, TeamEditView,
teamsTemplate) {
var ViewWithHeader = Backbone.View.extend({
......@@ -37,14 +38,17 @@
var TeamTabView = Backbone.View.extend({
initialize: function(options) {
var TempTabView, router;
var router;
this.courseID = options.courseID;
this.topics = options.topics;
this.teamMemberships = options.teamMemberships;
this.topicUrl = options.topicUrl;
this.teamsUrl = options.teamsUrl;
this.teamMembershipsUrl = options.teamMembershipsUrl;
this.maxTeamSize = options.maxTeamSize;
this.languages = options.languages;
this.countries = options.countries;
this.username = options.username;
// This slightly tedious approach is necessary
// to use regular expressions within Backbone
// routes, allowing us to capture which tab
......@@ -56,27 +60,44 @@
['topics/:topic_id/create-team(/)', _.bind(this.newTeam, this)],
['teams/:topic_id/:team_id(/)', _.bind(this.browseTeam, this)],
[new RegExp('^(browse)\/?$'), _.bind(this.goToTab, this)],
[new RegExp('^(teams)\/?$'), _.bind(this.goToTab, this)]
[new RegExp('^(my-teams)\/?$'), _.bind(this.goToTab, this)]
], function (route) {
router.route.apply(router, route);
});
// TODO replace this with actual views!
TempTabView = Backbone.View.extend({
initialize: function (options) {
this.text = options.text;
},
render: function () {
this.$el.html(this.text);
this.teamMembershipsCollection = new TeamMembershipCollection(
this.teamMemberships,
{
url: this.teamMembershipsUrl,
course_id: this.courseID,
username: this.username,
parse: true,
}
).bootstrap();
this.myTeamsView = new TeamsView({
router: this.router,
collection: this.teamMembershipsCollection,
maxTeamSize: this.maxTeamSize,
teamParams: {
courseId: this.courseID,
teamsUrl: this.teamsUrl,
languages: this.languages,
countries: this.countries
}
});
this.topicsCollection = new TopicCollection(
this.topics,
{url: options.topicsUrl, course_id: this.courseID, parse: true}
).bootstrap();
this.topicsView = new TopicsView({
collection: this.topicsCollection,
router: this.router
});
this.mainView = this.tabbedView = new ViewWithHeader({
header: new HeaderView({
model: new HeaderModel({
......@@ -87,8 +108,8 @@
main: new TabbedView({
tabs: [{
title: gettext('My Teams'),
url: 'teams',
view: new TempTabView({text: '<p class="temp-tab-view">This is the new Teams tab.</p>'})
url: 'my-teams',
view: this.myTeamsView
}, {
title: gettext('Browse'),
url: 'browse',
......@@ -170,9 +191,9 @@
.done(function() {
var teamsView = new TeamsView({
router: router,
topic: topic,
collection: collection,
maxTeamSize: self.maxTeamSize,
showActions: true,
teamParams: {
courseId: self.courseID,
teamsUrl: self.teamsUrl,
......@@ -366,7 +387,7 @@
* the main teams tab, and adds an error message.
*/
notFoundError: function (message) {
this.router.navigate('teams', {trigger: true});
this.router.navigate('my-teams', {trigger: true});
this.showWarning(message);
},
......
......@@ -35,12 +35,15 @@
TeamsTabFactory({
courseID: '${ unicode(course.id) }',
topics: ${ json.dumps(topics, cls=EscapedEdxJSONEncoder) },
teamMemberships: ${ json.dumps(team_memberships, cls=EscapedEdxJSONEncoder) },
topicUrl: '${ topic_url }',
topicsUrl: '${ topics_url }',
teamsUrl: '${ teams_url }',
teamMembershipsUrl: '${ team_memberships_url }',
maxTeamSize: ${ course.teams_max_size },
languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) },
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) }
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) },
username: '${ username }'
});
</%static:require_module>
</%block>
......
......@@ -5,7 +5,7 @@ from uuid import uuid4
import factory
from factory.django import DjangoModelFactory
from ..models import CourseTeam
from ..models import CourseTeam, CourseTeamMembership
class CourseTeamFactory(DjangoModelFactory):
......@@ -20,3 +20,8 @@ class CourseTeamFactory(DjangoModelFactory):
discussion_topic_id = factory.LazyAttribute(lambda a: uuid4().hex)
name = "Awesome Team"
description = "A simple description"
class CourseTeamMembershipFactory(DjangoModelFactory):
"""Factory for CourseTeamMemberships."""
FACTORY_FOR = CourseTeamMembership
# -*- coding: utf-8 -*-
"""Tests for the teams API at the HTTP request level."""
import ddt
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from opaque_keys.edx.keys import CourseKey
from student.tests.factories import UserFactory
from .factories import CourseTeamFactory, CourseTeamMembershipFactory
from ..models import CourseTeamMembership
COURSE_KEY1 = CourseKey.from_string('edx/history/1')
COURSE_KEY2 = CourseKey.from_string('edx/history/2')
@ddt.ddt
class TeamMembershipTest(SharedModuleStoreTestCase):
"""Tests for the TeamMembership model."""
def setUp(self):
"""
Set up tests.
"""
super(TeamMembershipTest, self).setUp()
self.user1 = UserFactory.create(username='user1')
self.user2 = UserFactory.create(username='user2')
self.team1 = CourseTeamFactory(course_id=COURSE_KEY1, team_id='team1')
self.team2 = CourseTeamFactory(course_id=COURSE_KEY2, team_id='team2')
self.team_membership11 = CourseTeamMembershipFactory(user=self.user1, team=self.team1)
self.team_membership12 = CourseTeamMembershipFactory(user=self.user2, team=self.team1)
self.team_membership21 = CourseTeamMembershipFactory(user=self.user1, team=self.team2)
@ddt.data(
(None, None, None, 3),
('user1', None, None, 2),
('user1', [COURSE_KEY1], None, 1),
('user1', None, 'team1', 1),
('user2', None, None, 1),
)
@ddt.unpack
def test_get_memberships(self, username, course_ids, team_id, expected_count):
self.assertEqual(
CourseTeamMembership.get_memberships(username=username, course_ids=course_ids, team_id=team_id).count(),
expected_count
)
......@@ -52,12 +52,14 @@ from .serializers import (
BaseTopicSerializer,
TopicSerializer,
PaginatedTopicSerializer,
MembershipSerializer
MembershipSerializer,
PaginatedMembershipSerializer,
)
from .errors import AlreadyOnTeamInCourse, NotEnrolledInCourseForTeam
# Constants
TEAM_MEMBERSHIPS_PER_PAGE = 2
TOPICS_PER_PAGE = 12
......@@ -91,14 +93,24 @@ class TeamsDashboardView(View):
context={'course_id': course.id, 'sort_order': sort_order}
)
user = request.user
team_memberships = CourseTeamMembership.get_memberships(request.user.username, [course.id])
team_memberships_page = Paginator(team_memberships, TEAM_MEMBERSHIPS_PER_PAGE).page(1)
team_memberships_serializer = PaginatedMembershipSerializer(
instance=team_memberships_page,
context={'expand': ('team',)},
)
context = {
"course": course,
"topics": topics_serializer.data,
"topic_url": reverse(
'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request
),
"team_memberships": team_memberships_serializer.data,
"topics_url": reverse('topics_list', request=request),
"teams_url": reverse('teams_list', request=request),
"team_memberships_url": reverse('team_membership_list', request=request),
"languages": settings.ALL_LANGUAGES,
"countries": list(countries),
"username": user.username,
......@@ -789,9 +801,10 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
def get(self, request):
"""GET /api/team/v0/team_membership"""
queryset = CourseTeamMembership.objects.all()
specified_username_or_team = False
username = None
valid_courses = None
team_id = None
if 'team_id' in request.QUERY_PARAMS:
specified_username_or_team = True
......@@ -802,10 +815,10 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
return Response(status=status.HTTP_404_NOT_FOUND)
if not has_team_api_access(request.user, team.course_id):
return Response(status=status.HTTP_404_NOT_FOUND)
queryset = queryset.filter(team__team_id=team_id)
if 'username' in request.QUERY_PARAMS:
specified_username_or_team = True
username = request.QUERY_PARAMS['username']
if not request.user.is_staff:
enrolled_courses = (
CourseEnrollment.enrollments_for_user(request.user).values_list('course_id', flat=True)
......@@ -818,8 +831,6 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
for course_list in [enrolled_courses, staff_courses]
for course_key_string in course_list
]
queryset = queryset.filter(team__course_id__in=valid_courses)
queryset = queryset.filter(user__username=request.QUERY_PARAMS['username'])
if not specified_username_or_team:
return Response(
......@@ -827,6 +838,7 @@ class MembershipListView(ExpandableFieldViewMixin, GenericAPIView):
status=status.HTTP_400_BAD_REQUEST
)
queryset = CourseTeamMembership.get_memberships(username, valid_courses, team_id)
page = self.paginate_queryset(queryset)
serializer = self.get_pagination_serializer(page)
return Response(serializer.data) # pylint: disable=maybe-no-member
......
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