Commit c3d94d5f by Zia Fazal

update gradebook asynchronously

fixed failing tests due to recursive import error
parent 214fc702
......@@ -1471,6 +1471,7 @@ class UsersApiTests(ModuleStoreTestCase):
user_id = self.user.id
course = CourseFactory.create(org='TUCGLG', run='TUCGLG1', display_name="Robot Super Course")
CourseEnrollmentFactory.create(user=self.user, course_id=course.id)
test_data = '<html>{}</html>'.format(str(uuid.uuid4()))
chapter1 = ItemFactory.create(
category="chapter",
......@@ -1642,32 +1643,36 @@ class UsersApiTests(ModuleStoreTestCase):
grade_dict = {'value': points_scored, 'max_value': points_possible, 'user_id': user.id}
module.system.publish(module, 'grade', grade_dict)
test_uri = '{}/{}/courses/{}/grades'.format(self.users_base_uri, user_id, unicode(course.id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
courseware_summary = response.data['courseware_summary']
self.assertEqual(len(courseware_summary), 2)
self.assertEqual(courseware_summary[0]['course'], 'Robot Super Course')
self.assertEqual(courseware_summary[0]['display_name'], 'Chapter 1')
sections = courseware_summary[0]['sections']
self.assertEqual(len(sections), 1)
self.assertEqual(sections[0]['display_name'], 'Sequence 1')
self.assertEqual(sections[0]['graded'], False)
sections = courseware_summary[1]['sections']
self.assertEqual(len(sections), 12)
self.assertEqual(sections[0]['display_name'], 'Sequence 2')
self.assertEqual(sections[0]['graded'], False)
grade_summary = response.data['grade_summary']
self.assertGreater(len(grade_summary['section_breakdown']), 0)
grading_policy = response.data['grading_policy']
self.assertGreater(len(grading_policy['GRADER']), 0)
self.assertIsNotNone(grading_policy['GRADE_CUTOFFS'])
self.assertAlmostEqual(response.data['current_grade'], 0.74, 1)
self.assertAlmostEqual(response.data['proforma_grade'], 0.9375, 1)
with mock.patch('api_manager.users.views._recalculate_grade') as recalculate_grade:
test_uri = '{}/{}/courses/{}/grades'.format(self.users_base_uri, user_id, unicode(course.id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
# grades should be calculate in score_changed signal handler of gradebook app
# make sure grades are not recalculated when calling user grades API
self.assertFalse(recalculate_grade.called)
courseware_summary = response.data['courseware_summary']
self.assertEqual(len(courseware_summary), 2)
self.assertEqual(courseware_summary[0]['course'], 'Robot Super Course')
self.assertEqual(courseware_summary[0]['display_name'], 'Chapter 1')
sections = courseware_summary[0]['sections']
self.assertEqual(len(sections), 1)
self.assertEqual(sections[0]['display_name'], 'Sequence 1')
self.assertEqual(sections[0]['graded'], False)
sections = courseware_summary[1]['sections']
self.assertEqual(len(sections), 12)
self.assertEqual(sections[0]['display_name'], 'Sequence 2')
self.assertEqual(sections[0]['graded'], False)
grade_summary = response.data['grade_summary']
self.assertGreater(len(grade_summary['section_breakdown']), 0)
grading_policy = response.data['grading_policy']
self.assertGreater(len(grading_policy['GRADER']), 0)
self.assertIsNotNone(grading_policy['GRADE_CUTOFFS'])
self.assertAlmostEqual(response.data['current_grade'], 0.74, 1)
self.assertAlmostEqual(response.data['proforma_grade'], 0.9375, 1)
test_uri = '{}/{}/courses/grades'.format(self.users_base_uri, user_id)
......
......@@ -187,10 +187,11 @@ def _manage_role(course_descriptor, user, role, action):
pass
def _recalculate_grade(request, student, course_descriptor):
def _recalculate_grade(request, student, course_id):
"""
Helper method for recalculating gradebook data
"""
course_descriptor, course_key, course_content = get_course(request, student, course_id, depth=None) # pylint: disable=W0612
progress_summary = grades.progress_summary(student, request, course_descriptor, locators_as_strings=True) # pylint: disable=unused-variable
grade_summary = grades.grade(student, request, course_descriptor)
grading_policy = course_descriptor.grading_policy
......@@ -1046,14 +1047,14 @@ class UsersCoursesGradesDetail(SecureAPIView):
except ObjectDoesNotExist:
return Response({}, status=status.HTTP_404_NOT_FOUND)
# @TODO: Add authorization check here once we get caller identity
# Only student can get his/her own information *or* course staff
# can get everyone's grades
# get the full course tree with depth=None which reduces the number of
# round trips to the database
course_descriptor, course_key, course_content = get_course(request, student, course_id, depth=None) # pylint: disable=W0612
if not course_descriptor:
return Response({}, status=status.HTTP_404_NOT_FOUND)
course_key = get_course_key(course_id)
if not CourseEnrollment.is_enrolled(student, course_key):
return Response(
{
'message': _("Student not enrolled in given course")
}, status=status.HTTP_404_NOT_FOUND
)
queryset = StudentGradebook.objects.filter(
user=student,
......@@ -1071,7 +1072,7 @@ class UsersCoursesGradesDetail(SecureAPIView):
grade_summary = json.loads(gradebook_entry.grade_summary)
grading_policy = json.loads(gradebook_entry.grading_policy)
else:
gradebook_values = _recalculate_grade(request, student, course_descriptor)
gradebook_values = _recalculate_grade(request, student, course_id)
current_grade = gradebook_values["current_grade"]
proforma_grade = gradebook_values["proforma_grade"]
progress_summary = gradebook_values["progress_summary"]
......@@ -1084,14 +1085,7 @@ class UsersCoursesGradesDetail(SecureAPIView):
gradebook_entry.grading_policy = json.dumps(grading_policy, cls=EdxJSONEncoder)
gradebook_entry.save()
else:
if not CourseEnrollment.is_enrolled(student, course_key):
return Response(
{
'message': _("Student not enrolled in given course")
}, status=status.HTTP_404_NOT_FOUND
)
gradebook_values = _recalculate_grade(request, student, course_descriptor)
gradebook_values = _recalculate_grade(request, student, course_id)
current_grade = gradebook_values["current_grade"]
proforma_grade = gradebook_values["proforma_grade"]
progress_summary = gradebook_values["progress_summary"]
......
......@@ -3,66 +3,36 @@ Signal handlers supporting various gradebook use cases
"""
import logging
import sys
import json
from django.dispatch import receiver
from django.conf import settings
from django.db.models.signals import post_save, pre_save
from courseware import grades
from courseware.signals import score_changed
from xmodule.modulestore import EdxJSONEncoder
from util.request import RequestMockWithoutMiddleware
from util.signals import course_deleted
from student.roles import get_aggregate_exclusion_user_ids
from gradebook.models import StudentGradebook, StudentGradebookHistory
from edx_notifications.lib.publisher import (
publish_notification_to_user,
get_notification_type
)
from edx_notifications.data import NotificationMessage
from gradebook.models import StudentGradebook, StudentGradebookHistory
from gradebook.tasks import update_user_gradebook
log = logging.getLogger(__name__)
@receiver(score_changed, dispatch_uid="lms.courseware.score_changed")
def on_score_changed(sender, **kwargs):
"""
Listens for a 'score_changed' signal and when observed
recalculates the specified user's gradebook entry
Listens for a 'score_changed' signal invoke grade book update task
"""
from courseware.views import get_course
user = kwargs['user']
course_key = kwargs['course_key']
course_descriptor = get_course(course_key, depth=None)
request = RequestMockWithoutMiddleware().get('/')
request.user = user
progress_summary = grades.progress_summary(user, request, course_descriptor, locators_as_strings=True)
grade_summary = grades.grade(user, request, course_descriptor)
grading_policy = course_descriptor.grading_policy
grade = grade_summary['percent']
proforma_grade = grades.calculate_proforma_grade(grade_summary, grading_policy)
try:
gradebook_entry = StudentGradebook.objects.get(user=user, course_id=course_key)
if gradebook_entry.grade != grade:
gradebook_entry.grade = grade
gradebook_entry.proforma_grade = proforma_grade
gradebook_entry.progress_summary = json.dumps(progress_summary, cls=EdxJSONEncoder)
gradebook_entry.grade_summary = json.dumps(grade_summary, cls=EdxJSONEncoder)
gradebook_entry.grading_policy = json.dumps(grading_policy, cls=EdxJSONEncoder)
gradebook_entry.save()
except StudentGradebook.DoesNotExist:
StudentGradebook.objects.create(
user=user,
course_id=course_key,
grade=grade,
proforma_grade=proforma_grade,
progress_summary=json.dumps(progress_summary, cls=EdxJSONEncoder),
grade_summary=json.dumps(grade_summary, cls=EdxJSONEncoder),
grading_policy=json.dumps(grading_policy, cls=EdxJSONEncoder)
)
update_user_gradebook.delay(course_key, user)
@receiver(course_deleted)
......
"""
This module has implementation of celery tasks for learner gradebook use cases
"""
import json
import logging
from celery.task import task # pylint: disable=import-error,no-name-in-module
from courseware import grades
from xmodule.modulestore import EdxJSONEncoder
from util.request import RequestMockWithoutMiddleware
from gradebook.models import StudentGradebook
log = logging.getLogger('edx.celery.task')
@task(name=u'lms.djangoapps.gradebook.tasks.update_user_gradebook')
def update_user_gradebook(course_key, user):
"""
Taks to recalculate user's gradebook entry
"""
try:
_generate_user_gradebook(course_key, user)
except Exception as ex:
log.exception('An error occurred while generating gradebook: %s', ex.message)
raise
def _generate_user_gradebook(course_key, user):
"""
Recalculates the specified user's gradebook entry
"""
# import is local to avoid recursive import
from courseware.views import get_course
course_descriptor = get_course(course_key, depth=None)
request = RequestMockWithoutMiddleware().get('/')
request.user = user
progress_summary = grades.progress_summary(user, request, course_descriptor, locators_as_strings=True)
grade_summary = grades.grade(user, request, course_descriptor)
grading_policy = course_descriptor.grading_policy
grade = grade_summary['percent']
proforma_grade = grades.calculate_proforma_grade(grade_summary, grading_policy)
try:
gradebook_entry = StudentGradebook.objects.get(user=user, course_id=course_key)
if gradebook_entry.grade != grade:
gradebook_entry.grade = grade
gradebook_entry.proforma_grade = proforma_grade
gradebook_entry.progress_summary = json.dumps(progress_summary, cls=EdxJSONEncoder)
gradebook_entry.grade_summary = json.dumps(grade_summary, cls=EdxJSONEncoder)
gradebook_entry.grading_policy = json.dumps(grading_policy, cls=EdxJSONEncoder)
gradebook_entry.save()
except StudentGradebook.DoesNotExist:
StudentGradebook.objects.create(
user=user,
course_id=course_key,
grade=grade,
proforma_grade=proforma_grade,
progress_summary=json.dumps(progress_summary, cls=EdxJSONEncoder),
grade_summary=json.dumps(grade_summary, cls=EdxJSONEncoder),
grading_policy=json.dumps(grading_policy, cls=EdxJSONEncoder)
)
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