Commit 7cab70b6 by Zia Fazal Committed by Jonathan Piacenti

ziafazal/api-fix-bug-progress-tab: fix progress

Fixed progress value by using total modules in the course as
denominator.

variable names changes and login for total_actual_completions

efficient way to load leaf modules

 and added required settings to tests
parent 87b43939
...@@ -48,6 +48,10 @@ def _fake_get_get_course_social_stats(course_id): ...@@ -48,6 +48,10 @@ def _fake_get_get_course_social_stats(course_id):
@mock.patch("lms.lib.comment_client.user.get_course_social_stats", _fake_get_get_course_social_stats) @mock.patch("lms.lib.comment_client.user.get_course_social_stats", _fake_get_get_course_social_stats)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@mock.patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': False,
'ADVANCED_SECURITY': False,
'PREVENT_CONCURRENT_LOGINS': False
})
class CoursesApiTests(TestCase): class CoursesApiTests(TestCase):
""" Test suite for Courses API views """ """ Test suite for Courses API views """
...@@ -133,12 +137,12 @@ class CoursesApiTests(TestCase): ...@@ -133,12 +137,12 @@ class CoursesApiTests(TestCase):
) )
self.sub_section = ItemFactory.create( self.sub_section = ItemFactory.create(
parent_location=self.course_content.location, parent_location=self.chapter.location,
category="sequential", category="sequential",
display_name=u"test subsection", display_name=u"test subsection",
) )
unit = ItemFactory.create( self.unit = ItemFactory.create(
parent_location=self.sub_section.location, parent_location=self.sub_section.location,
category="vertical", category="vertical",
metadata={'graded': True, 'format': 'Homework'}, metadata={'graded': True, 'format': 'Homework'},
...@@ -171,7 +175,7 @@ class CoursesApiTests(TestCase): ...@@ -171,7 +175,7 @@ class CoursesApiTests(TestCase):
module_type = 'group-project' module_type = 'group-project'
self.item = ItemFactory.create( self.item = ItemFactory.create(
parent_location=unit.location, parent_location=self.unit.location,
category=category, category=category,
data=StringResponseXMLFactory().build_xml(answer='foo'), data=StringResponseXMLFactory().build_xml(answer='foo'),
metadata={'rerandomize': 'always'}, metadata={'rerandomize': 'always'},
...@@ -325,7 +329,7 @@ class CoursesApiTests(TestCase): ...@@ -325,7 +329,7 @@ class CoursesApiTests(TestCase):
chapter = response.data['content'][0] chapter = response.data['content'][0]
self.assertEqual(chapter['category'], 'chapter') self.assertEqual(chapter['category'], 'chapter')
self.assertEqual(chapter['name'], 'Overview') self.assertEqual(chapter['name'], 'Overview')
self.assertEqual(len(chapter['children']), 1) self.assertEqual(len(chapter['children']), 2)
sequence = chapter['children'][0] sequence = chapter['children'][0]
self.assertEqual(sequence['category'], 'videosequence') self.assertEqual(sequence['category'], 'videosequence')
...@@ -1597,7 +1601,6 @@ class CoursesApiTests(TestCase): ...@@ -1597,7 +1601,6 @@ class CoursesApiTests(TestCase):
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
def test_courses_completions_leaders_list_get(self): def test_courses_completions_leaders_list_get(self):
completion_uri = '{}/{}/completions/'.format(self.base_courses_uri, unicode(self.course.id)) completion_uri = '{}/{}/completions/'.format(self.base_courses_uri, unicode(self.course.id))
users = [] users = []
for i in xrange(1, 5): for i in xrange(1, 5):
...@@ -1619,7 +1622,7 @@ class CoursesApiTests(TestCase): ...@@ -1619,7 +1622,7 @@ class CoursesApiTests(TestCase):
local_content_name = 'Video_Sequence{}'.format(i) local_content_name = 'Video_Sequence{}'.format(i)
local_content = ItemFactory.create( local_content = ItemFactory.create(
category="videosequence", category="videosequence",
parent_location=self.chapter.location, parent_location=self.unit.location,
data=self.test_data, data=self.test_data,
display_name=local_content_name display_name=local_content_name
) )
...@@ -1647,7 +1650,7 @@ class CoursesApiTests(TestCase): ...@@ -1647,7 +1650,7 @@ class CoursesApiTests(TestCase):
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['leaders']), 4) self.assertEqual(len(response.data['leaders']), 4)
self.assertEqual(response.data['course_avg'], 6.3) self.assertEqual(response.data['course_avg'], 24)
# without count filter and user_id # without count filter and user_id
test_uri = '{}/{}/metrics/completions/leaders/?user_id={}'.format(self.base_courses_uri, self.test_course_id, test_uri = '{}/{}/metrics/completions/leaders/?user_id={}'.format(self.base_courses_uri, self.test_course_id,
...@@ -1656,7 +1659,7 @@ class CoursesApiTests(TestCase): ...@@ -1656,7 +1659,7 @@ class CoursesApiTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['leaders']), 3) self.assertEqual(len(response.data['leaders']), 3)
self.assertEqual(response.data['position'], 1) self.assertEqual(response.data['position'], 1)
self.assertEqual(response.data['completions'], 40) self.assertEqual(response.data['completions'], 38)
# test with bogus course # test with bogus course
test_uri = '{}/{}/metrics/completions/leaders/'.format(self.base_courses_uri, self.test_bogus_course_id) test_uri = '{}/{}/metrics/completions/leaders/'.format(self.base_courses_uri, self.test_bogus_course_id)
......
...@@ -27,7 +27,7 @@ from student.roles import CourseRole, CourseAccessRole, CourseInstructorRole, Co ...@@ -27,7 +27,7 @@ from student.roles import CourseRole, CourseAccessRole, CourseInstructorRole, Co
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from api_manager.courseware_access import get_course, get_course_child from api_manager.courseware_access import get_course, get_course_child, get_course_leaf_nodes
from api_manager.models import CourseGroupRelationship, CourseContentGroupRelationship, GroupProfile, \ from api_manager.models import CourseGroupRelationship, CourseContentGroupRelationship, GroupProfile, \
CourseModuleCompletion CourseModuleCompletion
from api_manager.permissions import SecureAPIView, SecureListAPIView from api_manager.permissions import SecureAPIView, SecureListAPIView
...@@ -1604,31 +1604,33 @@ class CoursesCompletionsLeadersList(SecureAPIView): ...@@ -1604,31 +1604,33 @@ class CoursesCompletionsLeadersList(SecureAPIView):
if not course_descriptor: if not course_descriptor:
return Response({}, status=status.HTTP_404_NOT_FOUND) return Response({}, status=status.HTTP_404_NOT_FOUND)
total_possible_completions = len(get_course_leaf_nodes(course_key,
['discussion-course', 'group-project']))
exclude_users = _get_aggregate_exclusion_user_ids(course_key) exclude_users = _get_aggregate_exclusion_user_ids(course_key)
queryset = CourseModuleCompletion.objects.filter(course_id=course_key)\ queryset = CourseModuleCompletion.objects.filter(course_id=course_key)\
.exclude(user__in=exclude_users) .exclude(user__in=exclude_users)
total_completions = queryset.filter(user__is_active=True).count() total_actual_completions = queryset.filter(user__is_active=True).count()
if user_id: if user_id:
user_completions = queryset.filter(user__id=user_id).count() user_completions = queryset.filter(user__id=user_id).count()
completions_above_user = queryset.filter(user__is_active=True).values('user__id')\ completions_above_user = queryset.filter(user__is_active=True).values('user__id')\
.annotate(completions=Count('content_id')).filter(completions__gt=user_completions).count() .annotate(completions=Count('content_id')).filter(completions__gt=user_completions).count()
data['position'] = completions_above_user + 1 data['position'] = completions_above_user + 1
completion_percentage = 0 completion_percentage = 0
if total_completions > 0: if total_possible_completions > 0:
completion_percentage = int(round(100 * user_completions/total_completions)) completion_percentage = int(round(100 * user_completions/total_possible_completions))
data['completions'] = completion_percentage data['completions'] = completion_percentage
total_users = CourseEnrollment.users_enrolled_in(course_key).exclude(id__in=exclude_users).count() total_users = CourseEnrollment.users_enrolled_in(course_key).exclude(id__in=exclude_users).count()
if total_users: if total_users and total_actual_completions:
course_avg = round(total_completions / float(total_users), 1) course_avg = round(total_actual_completions / float(total_users), 1)
course_avg = int(round(100 * course_avg / total_possible_completions)) # avg in percentage
data['course_avg'] = course_avg data['course_avg'] = course_avg
queryset = queryset.filter(user__is_active=True).values('user__id', 'user__username', 'user__profile__title', queryset = queryset.filter(user__is_active=True).values('user__id', 'user__username', 'user__profile__title',
'user__profile__avatar_url')\ 'user__profile__avatar_url')\
.annotate(completions=Count('content_id')).order_by('-completions')[:count] .annotate(completions=Count('content_id')).order_by('-completions')[:count]
serializer = CourseCompletionsLeadersSerializer(queryset, many=True, serializer = CourseCompletionsLeadersSerializer(queryset, many=True,
context={'total_completions': total_completions}) context={'total_completions': total_possible_completions})
data['leaders'] = serializer.data # pylint: disable=E1101 data['leaders'] = serializer.data # pylint: disable=E1101
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
......
...@@ -80,3 +80,15 @@ def get_course_total_score(course_summary): ...@@ -80,3 +80,15 @@ def get_course_total_score(course_summary):
if section['section_total']: if section['section_total']:
score += section['section_total'][1] score += section['section_total'][1]
return score return score
def get_course_leaf_nodes(course_key, detached_categories):
"""
Get count of the leaf nodes with ability to exclude some categories
"""
nodes = []
verticals = modulestore().get_items(course_key, category='vertical')
for vertical in verticals:
nodes.extend([unit.location for unit in vertical.get_children()
if getattr(unit, 'category') not in detached_categories])
return nodes
...@@ -8,6 +8,7 @@ from datetime import datetime ...@@ -8,6 +8,7 @@ from datetime import datetime
from random import randint from random import randint
import uuid import uuid
import json import json
import mock
from urllib import urlencode from urllib import urlencode
from django.core.cache import cache from django.core.cache import cache
...@@ -31,6 +32,10 @@ class SecureClient(Client): ...@@ -31,6 +32,10 @@ class SecureClient(Client):
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@mock.patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': False,
'ADVANCED_SECURITY': False,
'PREVENT_CONCURRENT_LOGINS': False
})
class GroupsApiTests(ModuleStoreTestCase): class GroupsApiTests(ModuleStoreTestCase):
""" Test suite for Groups API views """ """ Test suite for Groups API views """
......
...@@ -6,6 +6,7 @@ Run these tests @ Devstack: ...@@ -6,6 +6,7 @@ Run these tests @ Devstack:
""" """
import json import json
import uuid import uuid
import mock
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.cache import cache from django.core.cache import cache
...@@ -29,8 +30,11 @@ class SecureClient(Client): ...@@ -29,8 +30,11 @@ class SecureClient(Client):
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@mock.patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': False,
'ADVANCED_SECURITY': False,
'PREVENT_CONCURRENT_LOGINS': False
})
class OrganizationsApiTests(ModuleStoreTestCase): class OrganizationsApiTests(ModuleStoreTestCase):
""" Test suite for Users API views """ """ Test suite for Users API views """
def setUp(self): def setUp(self):
......
...@@ -21,7 +21,8 @@ TEST_API_KEY = str(uuid.uuid4()) ...@@ -21,7 +21,8 @@ TEST_API_KEY = str(uuid.uuid4())
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@patch.dict("django.conf.settings.FEATURES", {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False}) @patch.dict("django.conf.settings.FEATURES", {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False,
'PREVENT_CONCURRENT_LOGINS': False})
class SessionApiRateLimitingProtectionTest(TestCase): class SessionApiRateLimitingProtectionTest(TestCase):
""" """
Test api_manager.session.login.ratelimit Test api_manager.session.login.ratelimit
......
...@@ -20,8 +20,9 @@ TEST_API_KEY = str(uuid.uuid4()) ...@@ -20,8 +20,9 @@ TEST_API_KEY = str(uuid.uuid4())
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': True}) @patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': True,
@patch.dict("django.conf.settings.FEATURES", {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}) 'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True,
'PREVENT_CONCURRENT_LOGINS': False})
class SessionApiSecurityTest(TestCase): class SessionApiSecurityTest(TestCase):
""" """
Test api_manager.session.session_list view Test api_manager.session.session_list view
......
...@@ -7,6 +7,7 @@ Run these tests @ Devstack: ...@@ -7,6 +7,7 @@ Run these tests @ Devstack:
""" """
from random import randint from random import randint
import uuid import uuid
import mock
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.cache import cache from django.core.cache import cache
...@@ -25,6 +26,10 @@ class SecureClient(Client): ...@@ -25,6 +26,10 @@ class SecureClient(Client):
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@mock.patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': False,
'ADVANCED_SECURITY': False,
'PREVENT_CONCURRENT_LOGINS': False
})
class SessionsApiTests(TestCase): class SessionsApiTests(TestCase):
""" Test suite for Sessions API views """ """ Test suite for Sessions API views """
......
...@@ -4,6 +4,7 @@ Run these tests @ Devstack: ...@@ -4,6 +4,7 @@ Run these tests @ Devstack:
""" """
from random import randint from random import randint
import uuid import uuid
import mock
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
...@@ -12,6 +13,10 @@ TEST_API_KEY = "123456ABCDEF" ...@@ -12,6 +13,10 @@ TEST_API_KEY = "123456ABCDEF"
@override_settings(API_ALLOWED_IP_ADDRESSES=['127.0.0.1', '10.0.2.2', '192.168.0.0/24']) @override_settings(API_ALLOWED_IP_ADDRESSES=['127.0.0.1', '10.0.2.2', '192.168.0.0/24'])
@mock.patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': False,
'ADVANCED_SECURITY': False,
'PREVENT_CONCURRENT_LOGINS': False
})
class PermissionsTests(TestCase): class PermissionsTests(TestCase):
""" Test suite for Permissions helper classes """ """ Test suite for Permissions helper classes """
def setUp(self): def setUp(self):
......
...@@ -30,7 +30,6 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory ...@@ -30,7 +30,6 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
TEST_API_KEY = str(uuid.uuid4()) TEST_API_KEY = str(uuid.uuid4())
class SecureClient(Client): class SecureClient(Client):
""" Django test client using a "secure" connection. """ """ Django test client using a "secure" connection. """
...@@ -52,7 +51,7 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -52,7 +51,7 @@ class UsersApiTests(ModuleStoreTestCase):
def setUp(self): def setUp(self):
self.test_server_prefix = 'https://testserver' self.test_server_prefix = 'https://testserver'
self.test_username = str(uuid.uuid4()) self.test_username = str(uuid.uuid4())
self.test_password = str(uuid.uuid4()) self.test_password = 'Test.Me64!'
self.test_email = str(uuid.uuid4()) + '@test.org' self.test_email = str(uuid.uuid4()) + '@test.org'
self.test_first_name = str(uuid.uuid4()) self.test_first_name = str(uuid.uuid4())
self.test_last_name = str(uuid.uuid4()) self.test_last_name = str(uuid.uuid4())
...@@ -79,11 +78,6 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -79,11 +78,6 @@ class UsersApiTests(ModuleStoreTestCase):
due=datetime(2016, 5, 16, 14, 30), due=datetime(2016, 5, 16, 14, 30),
display_name="View_Sequence" display_name="View_Sequence"
) )
self.test_project = Project.objects.create(
course_id=unicode(self.course.id),
content_id=unicode(self.course_content.scope_ids.usage_id)
)
self.course2 = CourseFactory.create(display_name="TEST COURSE2", org='TESTORG2') self.course2 = CourseFactory.create(display_name="TEST COURSE2", org='TESTORG2')
self.course2_content = ItemFactory.create( self.course2_content = ItemFactory.create(
category="videosequence", category="videosequence",
...@@ -92,10 +86,6 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -92,10 +86,6 @@ class UsersApiTests(ModuleStoreTestCase):
due=datetime(2016, 5, 16, 14, 30), due=datetime(2016, 5, 16, 14, 30),
display_name="View_Sequence2" display_name="View_Sequence2"
) )
self.second_test_project = Project.objects.create(
course_id=unicode(self.course2.id),
content_id=unicode(self.course2_content.scope_ids.usage_id)
)
self.user = UserFactory() self.user = UserFactory()
self.client = SecureClient() self.client = SecureClient()
...@@ -163,7 +153,7 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -163,7 +153,7 @@ class UsersApiTests(ModuleStoreTestCase):
data = { data = {
'email': 'test{}@example.com'.format(i), 'email': 'test{}@example.com'.format(i),
'username': 'test_user{}'.format(i), 'username': 'test_user{}'.format(i),
'password': 'test_pass', 'password': self.test_password,
'first_name': 'John{}'.format(i), 'first_name': 'John{}'.format(i),
'last_name': 'Doe{}'.format(i) 'last_name': 'Doe{}'.format(i)
} }
...@@ -228,7 +218,7 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -228,7 +218,7 @@ class UsersApiTests(ModuleStoreTestCase):
data = { data = {
'email': 'test{}@example.com'.format(i), 'email': 'test{}@example.com'.format(i),
'username': 'test_user{}'.format(i), 'username': 'test_user{}'.format(i),
'password': 'test_pass', 'password': self.test_password,
'first_name': 'John{}'.format(i), 'first_name': 'John{}'.format(i),
'last_name': 'Doe{}'.format(i) 'last_name': 'Doe{}'.format(i)
} }
...@@ -1265,12 +1255,24 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -1265,12 +1255,24 @@ class UsersApiTests(ModuleStoreTestCase):
# create anonymous user # create anonymous user
anonymous_id = anonymous_id_for_user(self.user, self.course.id) anonymous_id = anonymous_id_for_user(self.user, self.course.id)
for i in xrange(1, 12): for i in xrange(1, 12):
project_id = self.test_project.id course = CourseFactory.create(
if i > 7: # set to other project display_name="TEST COURSE {}".format(i),
project_id = self.second_test_project.id )
course_content = ItemFactory.create(
category="videosequence",
parent_location=course.location,
data=self.test_course_data,
display_name="View_Sequence"
)
test_project = Project.objects.create(
course_id=unicode(course.id),
content_id=unicode(course_content.scope_ids.usage_id)
)
data = { data = {
'name': 'Workgroup ' + str(i), 'name': 'Workgroup ' + str(i),
'project': project_id 'project': test_project.id
} }
response = self.do_post(test_workgroups_uri, data) response = self.do_post(test_workgroups_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
...@@ -1288,11 +1290,11 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -1288,11 +1290,11 @@ class UsersApiTests(ModuleStoreTestCase):
self.assertEqual(response.data['num_pages'], 2) self.assertEqual(response.data['num_pages'], 2)
# test with course_id filter and integer user id # test with course_id filter and integer user id
course_id = {'course_id': unicode(self.course.id)} course_id = {'course_id': unicode(course.id)}
response = self.do_get('{}/{}/workgroups/?{}'.format(self.users_base_uri, user_id, urlencode(course_id))) response = self.do_get('{}/{}/workgroups/?{}'.format(self.users_base_uri, user_id, urlencode(course_id)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 7) self.assertEqual(response.data['count'], 1)
self.assertEqual(len(response.data['results']), 7) self.assertEqual(len(response.data['results']), 1)
self.assertIsNotNone(response.data['results'][0]['name']) self.assertIsNotNone(response.data['results'][0]['name'])
self.assertIsNotNone(response.data['results'][0]['project']) self.assertIsNotNone(response.data['results'][0]['project'])
......
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