diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 7a16e19eb2..0ff78d19c0 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -207,16 +207,30 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): def setUpClass(cls): # pylint: disable=super-method-not-called with super(TeamAPITestCase, cls).setUpClassAndTestData(): + base_topics = [{ + 'id': 'topic_{}'.format(i), 'name': name, + 'description': u'Description for topic {}.'.format(i) + } for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power'])] + base_topics.append( + { + 'id': 'private_topic_1_id', + 'name': 'private_topic_1_name', + 'description': u'Description for topic private topic 1.', + 'type': u'private_managed' + } + ) + base_topics.append( + { + 'id': 'private_topic_2_id', + 'name': 'private_topic_2_name', + 'description': u'Description for topic private topic 2.', + 'type': u'private_managed' + } + ) teams_configuration_1 = TeamsConfig({ - 'topics': - [ - { - 'id': 'topic_{}'.format(i), - 'name': name, - 'description': u'Description for topic {}.'.format(i) - } for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power']) - ] + 'topics': base_topics }) + cls.test_course_1 = CourseFactory.create( org='TestX', course='TS101', @@ -255,12 +269,14 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): @classmethod def setUpTestData(cls): super(TeamAPITestCase, cls).setUpTestData() - cls.topics_count = 4 + cls.topics_count = 6 cls.users = { 'staff': AdminFactory.create(password=cls.test_password), 'course_staff': StaffFactory.create(course_key=cls.test_course_1.id, password=cls.test_password) } cls.create_and_enroll_student(username='student_enrolled') + cls.create_and_enroll_student(username='student_on_team_1_private_set_1', mode=CourseMode.MASTERS) + cls.create_and_enroll_student(username='student_not_member_of_private_teams', mode=CourseMode.MASTERS) cls.create_and_enroll_student(username='student_enrolled_not_on_team') cls.create_and_enroll_student(username='student_unenrolled', courses=[]) @@ -346,6 +362,36 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): organization_protected=True ) + cls.team_1_in_private_teamset_1 = CourseTeamFactory.create( + name='team 1 in private teamset 1', + description='team 1 in private teamset 1 desc', + country='US', + language='EN', + course_id=cls.test_course_1.id, + topic_id='private_topic_1_id', + organization_protected=True + ) + + cls.team_2_in_private_teamset_1 = CourseTeamFactory.create( + name='team 2 in private teamset 1', + description='team 2 in private teamset 1 desc', + country='US', + language='EN', + course_id=cls.test_course_1.id, + topic_id='private_topic_1_id', + organization_protected=True + ) + + cls.team_1_in_private_teamset_2 = CourseTeamFactory.create( + name='team 1 in private teamset 2', + description='team 1 in private teamset 2 desc', + country='US', + language='EN', + course_id=cls.test_course_1.id, + topic_id='private_topic_2_id', + organization_protected=True + ) + cls.test_team_name_id_map = {team.name: team for team in ( cls.solar_team, cls.wind_team, @@ -370,6 +416,7 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): cls.another_team.add_user(cls.users['student_enrolled_both_courses_other_team']) cls.public_profile_team.add_user(cls.users['student_enrolled_public_profile']) cls.masters_only_team.add_user(cls.users['student_masters']) + cls.team_1_in_private_teamset_1.add_user(cls.users['student_on_team_1_private_set_1']) def build_membership_data_raw(self, username, team): """Assembles a membership creation payload based on the raw values provided.""" @@ -581,8 +628,8 @@ class TestListTeamsAPI(EventTestMixin, TeamAPITestCase): ('student_inactive', 401), ('student_unenrolled', 403), ('student_enrolled', 200, 3), - ('staff', 200, 4), - ('course_staff', 200, 4), + ('staff', 200, 7), + ('course_staff', 200, 7), ('community_ta', 200, 3), ('student_masters', 200, 1) ) @@ -662,6 +709,25 @@ class TestListTeamsAPI(EventTestMixin, TeamAPITestCase): result = self.get_teams_list(200, {'page_size': 2}) self.assertEqual(2, result['num_pages']) + def test_non_member_trying_to_get_private_topic(self): + """ + Verifies that when a student that is enrolled in a course, but is NOT a member of + a private team set, asks for information about that team set, an empty list is returned. + """ + result = self.get_teams_list(data={'topic_id': 'private_topic_1_id'}) + self.assertEqual([], result['results']) + + def test_member_trying_to_get_private_topic(self): + """ + Verifies that when a student that is enrolled in a course, and IS a member of + a private team set, asks for information about that team set, information about the teamset is returned. + """ + result = self.get_teams_list(data={'topic_id': 'private_topic_1_id'}, + user='student_on_team_1_private_set_1') + self.assertEqual(1, len(result['results'])) + self.assertEqual('private_topic_1_id', result['results'][0]['topic_id']) + self.assertNotEqual([], result['results']) + def test_page(self): result = self.get_teams_list(200, {'page_size': 1, 'page': 3}) self.assertEqual(3, result['num_pages']) @@ -1141,11 +1207,14 @@ class TestListTopicsAPI(TeamAPITestCase): self.get_topics_list(400) @ddt.data( - (None, 200, ['Coal Power', 'Nuclear Power', u'Sólar power', 'Wind Power'], 'name'), - ('name', 200, ['Coal Power', 'Nuclear Power', u'Sólar power', 'Wind Power'], 'name'), + (None, 200, ['Coal Power', 'Nuclear Power', 'private_topic_1_name', 'private_topic_2_name', + u'Sólar power', 'Wind Power'], 'name'), + ('name', 200, ['Coal Power', 'Nuclear Power', 'private_topic_1_name', 'private_topic_2_name', + u'Sólar power', 'Wind Power'], 'name'), # Note that "Nuclear Power" and "Solar power" both have 2 teams. "Coal Power" and "Window Power" # both have 0 teams. The secondary sort is alphabetical by name. - ('team_count', 200, ['Nuclear Power', u'Sólar power', 'Coal Power', 'Wind Power'], 'team_count'), + ('team_count', 200, ['Nuclear Power', u'Sólar power', 'Coal Power', 'private_topic_1_name', + 'private_topic_2_name', 'Wind Power'], 'team_count'), ('no_such_field', 400, [], None), ) @ddt.unpack diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py index 234a51b78f..efab041c40 100644 --- a/lms/djangoapps/teams/views.py +++ b/lms/djangoapps/teams/views.py @@ -419,6 +419,9 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): topic_id=topic_id ) return Response(error, status=status.HTTP_400_BAD_REQUEST) + + if course_module.teamsets_by_id[topic_id].is_private_managed: + result_filter.update({'membership__user__username': request.user}) result_filter.update({'topic_id': topic_id}) organization_protection_status = user_organization_protection_status( @@ -467,7 +470,19 @@ class TeamsListView(ExpandableFieldViewMixin, GenericAPIView): 'open_slots': ('team_size', '-last_activity_at'), 'last_activity_at': ('-last_activity_at', 'team_size'), } - queryset = CourseTeam.objects.filter(**result_filter) + + if not has_access(request.user, 'staff', course_key): + # hide private_managed courses from non-admin users that aren't members of those teams + private_topic_ids = [ts.teamset_id for ts in course_module.teamsets if + ts.is_private_managed] + public_teams = CourseTeam.objects.filter(**result_filter).exclude( + topic_id__in=private_topic_ids) + private_managed_teams_of_user = CourseTeam.objects.filter(topic_id__in=private_topic_ids, + membership__user__username=request.user) + queryset = public_teams | private_managed_teams_of_user + else: + queryset = CourseTeam.objects.filter(**result_filter) + order_by_input = request.query_params.get('order_by', 'name') if order_by_input not in ordering_schemes: return Response( diff --git a/openedx/core/lib/teams_config.py b/openedx/core/lib/teams_config.py index 9dedb7dbe2..8a292a1011 100644 --- a/openedx/core/lib/teams_config.py +++ b/openedx/core/lib/teams_config.py @@ -308,6 +308,13 @@ class TeamsetConfig(object): except (KeyError, ValueError): return TeamsetType.get_default() + @cached_property + def is_private_managed(self): + """ + Returns true if teamsettype is private_managed + """ + return self.teamset_type == TeamsetType.private_managed + class TeamsetType(Enum): """