Commit 17a0444a by Chris Dodge

tie into the xblock publish API point for progress and grading events

parent 4a9cc1e3
...@@ -211,7 +211,7 @@ def _has_access_course_desc(user, action, course): ...@@ -211,7 +211,7 @@ def _has_access_course_desc(user, action, course):
'enroll': can_enroll, 'enroll': can_enroll,
'see_exists': see_exists, 'see_exists': see_exists,
'staff': lambda: _has_staff_access_to_descriptor(user, course, course.id), 'staff': lambda: _has_staff_access_to_descriptor(user, course, course.id),
'instructor': lambda: _has_instructor_access_to_descriptor(user, course, course.id), 'instructor': lambda: _has_instructor_access_to_descriptor(user, course, course.id)
} }
return _dispatch(checkers, action, user, course) return _dispatch(checkers, action, user, course)
...@@ -258,7 +258,6 @@ def _has_access_descriptor(user, action, descriptor, course_key=None): ...@@ -258,7 +258,6 @@ def _has_access_descriptor(user, action, descriptor, course_key=None):
if descriptor.visible_to_staff_only and not _has_staff_access_to_descriptor(user, descriptor, course_key): if descriptor.visible_to_staff_only and not _has_staff_access_to_descriptor(user, descriptor, course_key):
return False return False
# If start dates are off, can always load
if settings.FEATURES['DISABLE_START_DATES'] and not is_masquerading_as_student(user): if settings.FEATURES['DISABLE_START_DATES'] and not is_masquerading_as_student(user):
debug("Allow: DISABLE_START_DATES") debug("Allow: DISABLE_START_DATES")
return True return True
...@@ -285,7 +284,7 @@ def _has_access_descriptor(user, action, descriptor, course_key=None): ...@@ -285,7 +284,7 @@ def _has_access_descriptor(user, action, descriptor, course_key=None):
checkers = { checkers = {
'load': can_load, 'load': can_load,
'staff': lambda: _has_staff_access_to_descriptor(user, descriptor, course_key), 'staff': lambda: _has_staff_access_to_descriptor(user, descriptor, course_key),
'instructor': lambda: _has_instructor_access_to_descriptor(user, descriptor, course_key) 'instructor': lambda: _has_instructor_access_to_descriptor(user, descriptor, course_key),
} }
return _dispatch(checkers, action, user, descriptor) return _dispatch(checkers, action, user, descriptor)
......
...@@ -7,6 +7,9 @@ import mimetypes ...@@ -7,6 +7,9 @@ import mimetypes
import static_replace import static_replace
import xblock.reference.plugins import xblock.reference.plugins
from datetime import datetime
from django.utils.timezone import UTC
from collections import OrderedDict from collections import OrderedDict
from functools import partial from functools import partial
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
...@@ -348,6 +351,16 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -348,6 +351,16 @@ def get_module_system_for_user(user, field_data_cache,
) )
def handle_grade_event(block, event_type, event): def handle_grade_event(block, event_type, event):
# check permissions, unfortunately this has to be done on the course descriptor
# since problems don't have 'end' dates
if not settings.FEATURES.get("ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE", True):
# if a course has ended, don't register grading events
course = modulestore().get_course(course_id, depth=0)
now = datetime.now(UTC())
if course.end is not None and now > course.end:
return
user_id = event.get('user_id', user.id) user_id = event.get('user_id', user.id)
# Construct the key for the module # Construct the key for the module
...@@ -388,6 +401,14 @@ def get_module_system_for_user(user, field_data_cache, ...@@ -388,6 +401,14 @@ def get_module_system_for_user(user, field_data_cache,
""" """
tie into the CourseCompletions datamodels that are exposed in the api_manager djangoapp tie into the CourseCompletions datamodels that are exposed in the api_manager djangoapp
""" """
if not settings.FEATURES.get("ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE", True):
# if a course has ended, don't register progress events
course = modulestore().get_course(course_id, depth=0)
now = datetime.now(UTC())
if course.end is not None and now > course.end:
return
user_id = event.get('user_id', user.id) user_id = event.get('user_id', user.id)
if not user_id: if not user_id:
return return
......
...@@ -3,9 +3,13 @@ ...@@ -3,9 +3,13 @@
Run these tests @ Devstack: Run these tests @ Devstack:
paver test_system -s lms --test_id=lms/djangoapps/gradebook/tests.py paver test_system -s lms --test_id=lms/djangoapps/gradebook/tests.py
""" """
from mock import MagicMock from mock import MagicMock, patch
import uuid import uuid
from datetime import datetime
from django.utils.timezone import UTC
from django.conf import settings
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
...@@ -13,7 +17,8 @@ from capa.tests.response_xml_factory import StringResponseXMLFactory ...@@ -13,7 +17,8 @@ from capa.tests.response_xml_factory import StringResponseXMLFactory
from courseware import module_render from courseware import module_render
from courseware.model_data import FieldDataCache from courseware.model_data import FieldDataCache
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from student.tests.factories import UserFactory from student.tests.factories import UserFactory, AdminFactory
from courseware.tests.factories import StaffFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from gradebook.models import StudentGradebook, StudentGradebookHistory from gradebook.models import StudentGradebook, StudentGradebookHistory
...@@ -44,7 +49,11 @@ class GradebookTests(TestCase): ...@@ -44,7 +49,11 @@ class GradebookTests(TestCase):
self.user = UserFactory() self.user = UserFactory()
self.score = 0.75 self.score = 0.75
self.course = CourseFactory.create() def _create_course(self, start=None, end=None):
self.course = CourseFactory.create(
start=start,
end=end
)
self.course.always_recalculate_grades = True self.course.always_recalculate_grades = True
test_data = '<html>{}</html>'.format(str(uuid.uuid4())) test_data = '<html>{}</html>'.format(str(uuid.uuid4()))
chapter1 = ItemFactory.create( chapter1 = ItemFactory.create(
...@@ -116,6 +125,7 @@ class GradebookTests(TestCase): ...@@ -116,6 +125,7 @@ class GradebookTests(TestCase):
) )
def test_receiver_on_score_changed(self): def test_receiver_on_score_changed(self):
self._create_course()
module = self.get_module_for_user(self.user, self.course, self.problem) module = self.get_module_for_user(self.user, self.course, self.problem)
grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id} grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict) module.system.publish(module, 'grade', grade_dict)
...@@ -141,3 +151,101 @@ class GradebookTests(TestCase): ...@@ -141,3 +151,101 @@ class GradebookTests(TestCase):
history = StudentGradebookHistory.objects.all() history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 5) self.assertEqual(len(history), 5)
@patch.dict(settings.FEATURES, {'ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE': False})
def test_open_course(self):
self._create_course(start=datetime(2010,1,1, tzinfo=UTC()), end=datetime(3000, 1, 1, tzinfo=UTC()))
module = self.get_module_for_user(self.user, self.course, self.problem)
grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
module = self.get_module_for_user(self.user, self.course, self.problem2)
grade_dict = {'value': 0.95, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
gradebook = StudentGradebook.objects.all()
self.assertEqual(len(gradebook), 1)
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 2)
@patch.dict(settings.FEATURES, {'ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE': False})
def test_not_yet_started_course(self):
self._create_course(start=datetime(3000,1,1, tzinfo=UTC()), end=datetime(3000, 1, 1, tzinfo=UTC()))
module = self.get_module_for_user(self.user, self.course, self.problem)
grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
module = self.get_module_for_user(self.user, self.course, self.problem2)
grade_dict = {'value': 0.95, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
gradebook = StudentGradebook.objects.all()
self.assertEqual(len(gradebook), 1)
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 2)
@patch.dict(settings.FEATURES, {'ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE': False})
def test_closed_course_student(self):
self._create_course(start=datetime(2010,1,1, tzinfo=UTC()), end=datetime(2011, 1, 1, tzinfo=UTC()))
module = self.get_module_for_user(self.user, self.course, self.problem)
grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
module = self.get_module_for_user(self.user, self.course, self.problem2)
grade_dict = {'value': 0.95, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
gradebook = StudentGradebook.objects.all()
self.assertEqual(len(gradebook), 0)
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 0)
@patch.dict(settings.FEATURES, {'ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE': False})
def test_closed_course_admin(self):
"""
Users marked as Admin should be able to submit grade events to a closed course
"""
self.user = AdminFactory()
self._create_course(start=datetime(2010,1,1, tzinfo=UTC()), end=datetime(2011, 1, 1, tzinfo=UTC()))
module = self.get_module_for_user(self.user, self.course, self.problem)
grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
module = self.get_module_for_user(self.user, self.course, self.problem2)
grade_dict = {'value': 0.95, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
gradebook = StudentGradebook.objects.all()
self.assertEqual(len(gradebook), 0)
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 0)
@patch.dict(settings.FEATURES, {'ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE': False})
def test_closed_course_staff(self):
"""
Users marked as course staff should be able to submit grade events to a closed course
"""
self._create_course(start=datetime(2010,1,1, tzinfo=UTC()), end=datetime(2011, 1, 1, tzinfo=UTC()))
self.user = StaffFactory(course_key=self.course.id)
module = self.get_module_for_user(self.user, self.course, self.problem)
grade_dict = {'value': 0.75, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
module = self.get_module_for_user(self.user, self.course, self.problem2)
grade_dict = {'value': 0.95, 'max_value': 1, 'user_id': self.user.id}
module.system.publish(module, 'grade', grade_dict)
gradebook = StudentGradebook.objects.all()
self.assertEqual(len(gradebook), 0)
history = StudentGradebookHistory.objects.all()
self.assertEqual(len(history), 0)
...@@ -2,13 +2,19 @@ ...@@ -2,13 +2,19 @@
Django database models supporting the progress app Django database models supporting the progress app
""" """
from datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models import Sum, Q from django.db.models import Sum, Q
from django.utils.timezone import UTC
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from xmodule_django.models import CourseKeyField from xmodule_django.models import CourseKeyField
from xmodule.modulestore.django import modulestore
from opaque_keys.edx.keys import CourseKey
class StudentProgress(TimeStampedModel): class StudentProgress(TimeStampedModel):
......
...@@ -57,6 +57,7 @@ FEATURES['EMBARGO'] = True ...@@ -57,6 +57,7 @@ FEATURES['EMBARGO'] = True
# Toggles API on for testing # Toggles API on for testing
FEATURES['API'] = True FEATURES['API'] = True
FEATURES['ALLOW_STUDENT_STATE_UPDATES_ON_CLOSED_COURSE'] = False
# Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it. # Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it.
WIKI_ENABLED = True WIKI_ENABLED = True
......
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