Commit 425b8c6b by cahrens

Allow course staff and privileged users to create multiple teams.

TNL-3071
parent a345edfa
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
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.privileged = options.privileged;
this.staff = options.staff;
this.server_api = _.extend( this.server_api = _.extend(
{ {
...@@ -26,11 +27,11 @@ ...@@ -26,11 +27,11 @@
model: TeamMembershipModel, model: TeamMembershipModel,
canUserCreateTeam: function() { canUserCreateTeam: function() {
// Note: non-privileged users are automatically added to any team // Note: non-staff and non-privileged users are automatically added to any team
// that they create. This means that if multiple team membership is // that they create. This means that if multiple team membership is
// disabled that they cannot create a new team when they already // disabled that they cannot create a new team when they already
// belong to one. // belong to one.
return this.privileged || this.length === 0; return this.privileged || this.staff || this.length === 0;
} }
}); });
return TeamMembershipCollection; return TeamMembershipCollection;
......
...@@ -15,6 +15,7 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"], ...@@ -15,6 +15,7 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"],
userInfo: { userInfo: {
username: 'test-user', username: 'test-user',
privileged: false, privileged: false,
staff: false,
team_memberships_data: null team_memberships_data: null
} }
}); });
......
...@@ -173,7 +173,7 @@ define([ ...@@ -173,7 +173,7 @@ define([
AjaxHelpers.respondWithError( AjaxHelpers.respondWithError(
requests, requests,
400, 400,
{'error_message': {'user_message': 'User message', 'developer_message': 'Developer message' }} {'user_message': 'User message', 'developer_message': 'Developer message'}
); );
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("User message"); expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("User message");
......
...@@ -130,7 +130,7 @@ define([ ...@@ -130,7 +130,7 @@ define([
it('does not allow access if the user is neither privileged nor a team member', function () { it('does not allow access if the user is neither privileged nor a team member', function () {
var teamsTabView = createTeamsTabView({ var teamsTabView = createTeamsTabView({
userInfo: TeamSpecHelpers.createMockUserInfo({ privileged: false }) userInfo: TeamSpecHelpers.createMockUserInfo({ privileged: false, staff: true })
}); });
expect(teamsTabView.readOnlyDiscussion({ expect(teamsTabView.readOnlyDiscussion({
attributes: { membership: [] } attributes: { membership: [] }
......
...@@ -100,6 +100,15 @@ define([ ...@@ -100,6 +100,15 @@ define([
verifyActions(teamsView); 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 });
verifyActions(teamsView);
});
/* /*
// TODO: make this ready for prime time // TODO: make this ready for prime time
it('refreshes when the team membership changes', function() { it('refreshes when the team membership changes', function() {
......
...@@ -87,7 +87,8 @@ define([ ...@@ -87,7 +87,8 @@ define([
parse: true, parse: true,
url: 'api/teams/team_memberships', url: 'api/teams/team_memberships',
username: testUser, username: testUser,
privileged: false privileged: false,
staff: false
}), }),
options) options)
); );
...@@ -98,6 +99,7 @@ define([ ...@@ -98,6 +99,7 @@ define([
{ {
username: testUser, username: testUser,
privileged: false, privileged: false,
staff: false,
team_memberships_data: createMockTeamMembershipsData(1, 5) team_memberships_data: createMockTeamMembershipsData(1, 5)
}, },
options options
......
...@@ -125,9 +125,9 @@ ...@@ -125,9 +125,9 @@
}) })
.fail(function(data) { .fail(function(data) {
var response = JSON.parse(data.responseText); var response = JSON.parse(data.responseText);
var message = gettext("An error occurred. Please try again.") var message = gettext("An error occurred. Please try again.");
if ('error_message' in response && 'user_message' in response['error_message']){ if ('user_message' in response){
message = response['error_message']['user_message']; message = response.user_message;
} }
view.showMessage(message, message); view.showMessage(message, message);
}); });
......
...@@ -92,6 +92,7 @@ ...@@ -92,6 +92,7 @@
course_id: this.courseID, course_id: this.courseID,
username: this.userInfo.username, username: this.userInfo.username,
privileged: this.userInfo.privileged, privileged: this.userInfo.privileged,
staff: this.userInfo.staff,
parse: true parse: true
} }
).bootstrap(); ).bootstrap();
......
...@@ -224,6 +224,14 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): ...@@ -224,6 +224,14 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
self.test_team_5.add_user(self.users['student_enrolled_both_courses_other_team']) self.test_team_5.add_user(self.users['student_enrolled_both_courses_other_team'])
self.test_team_6.add_user(self.users['student_enrolled_public_profile']) self.test_team_6.add_user(self.users['student_enrolled_public_profile'])
def build_membership_data_raw(self, username, team):
"""Assembles a membership creation payload based on the raw values provided."""
return {'username': username, 'team_id': team}
def build_membership_data(self, username, team):
"""Assembles a membership creation payload based on the username and team model provided."""
return self.build_membership_data_raw(self.users[username].username, team.team_id)
def create_and_enroll_student(self, courses=None, username=None): def create_and_enroll_student(self, courses=None, username=None):
""" Creates a new student and enrolls that student in the course. """ Creates a new student and enrolls that student in the course.
...@@ -510,14 +518,38 @@ class TestCreateTeamAPI(TeamAPITestCase): ...@@ -510,14 +518,38 @@ class TestCreateTeamAPI(TeamAPITestCase):
self.post_create_team(status, data) self.post_create_team(status, data)
def test_student_in_team(self): def test_student_in_team(self):
self.post_create_team( response = self.post_create_team(
400, 400,
{ data=self.build_team_data(
'course_id': str(self.test_course_1.id), name="Doomed team",
'description': "You are already on a team in this course." course=self.test_course_1,
}, description="Overly ambitious student"
),
user='student_enrolled' user='student_enrolled'
) )
self.assertEqual(
"You are already in a team in this course.",
json.loads(response.content)["user_message"]
)
@ddt.data('staff', 'course_staff', 'community_ta')
def test_privileged_create_multiple_teams(self, user):
""" Privileged users can create multiple teams, even if they are already in one. """
# First add the privileged user to a team.
self.post_create_membership(
200,
self.build_membership_data(user, self.test_team_1),
user=user
)
self.post_create_team(
data=self.build_team_data(
name="Another team",
course=self.test_course_1,
description="Privileged users are the best"
),
user=user
)
@ddt.data({'description': ''}, {'name': 'x' * 1000}, {'name': ''}) @ddt.data({'description': ''}, {'name': 'x' * 1000}, {'name': ''})
def test_bad_fields(self, kwargs): def test_bad_fields(self, kwargs):
...@@ -865,14 +897,6 @@ class TestListMembershipAPI(TeamAPITestCase): ...@@ -865,14 +897,6 @@ class TestListMembershipAPI(TeamAPITestCase):
class TestCreateMembershipAPI(TeamAPITestCase): class TestCreateMembershipAPI(TeamAPITestCase):
"""Test cases for the membership creation endpoint.""" """Test cases for the membership creation endpoint."""
def build_membership_data_raw(self, username, team):
"""Assembles a membership creation payload based on the raw values provided."""
return {'username': username, 'team_id': team}
def build_membership_data(self, username, team):
"""Assembles a membership creation payload based on the username and team model provided."""
return self.build_membership_data_raw(self.users[username].username, team.team_id)
@ddt.data( @ddt.data(
(None, 401), (None, 401),
('student_inactive', 401), ('student_inactive', 401),
......
...@@ -96,9 +96,13 @@ class TeamsDashboardView(View): ...@@ -96,9 +96,13 @@ class TeamsDashboardView(View):
context = { context = {
"course": course, "course": course,
"topics": topics_serializer.data, "topics": topics_serializer.data,
# It is necessary to pass both privileged and staff because only privileged users can
# administer discussion threads, but both privileged and staff users are allowed to create
# multiple teams (since they are not automatically added to teams upon creation).
"user_info": { "user_info": {
"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)),
"team_memberships_data": team_memberships_serializer.data, "team_memberships_data": team_memberships_serializer.data,
}, },
"topic_url": reverse( "topic_url": reverse(
...@@ -366,14 +370,16 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -366,14 +370,16 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
'field_errors': field_errors, 'field_errors': field_errors,
}, status=status.HTTP_400_BAD_REQUEST) }, status=status.HTTP_400_BAD_REQUEST)
if CourseTeamMembership.user_in_team_for_course(request.user, course_key): # Course and global staff, as well as discussion "privileged" users, will not automatically
# be added to a team when they create it. They are allowed to create multiple teams.
team_administrator = (has_access(request.user, 'staff', course_key)
or has_discussion_privileges(request.user, course_key))
if not team_administrator and CourseTeamMembership.user_in_team_for_course(request.user, course_key):
error_message = build_api_error( error_message = build_api_error(
ugettext_noop('You are already in a team in this course.'), ugettext_noop('You are already in a team in this course.'),
course_id=course_id course_id=course_id
) )
return Response({ return Response(error_message, status=status.HTTP_400_BAD_REQUEST)
'error_message': error_message,
}, status=status.HTTP_400_BAD_REQUEST)
if course_key and not has_team_api_access(request.user, course_key): if course_key and not has_team_api_access(request.user, course_key):
return Response(status=status.HTTP_403_FORBIDDEN) return Response(status=status.HTTP_403_FORBIDDEN)
...@@ -390,8 +396,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): ...@@ -390,8 +396,7 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView):
}, status=status.HTTP_400_BAD_REQUEST) }, status=status.HTTP_400_BAD_REQUEST)
else: else:
team = serializer.save() team = serializer.save()
if not (has_access(request.user, 'staff', course_key) if not team_administrator:
or has_discussion_privileges(request.user, course_key)):
# Add the creating user to the team. # Add the creating user to the team.
team.add_user(request.user) team.add_user(request.user)
return Response(CourseTeamSerializer(team).data) return Response(CourseTeamSerializer(team).data)
......
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