diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index ac7b72520b..0572d385c9 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -25,6 +25,7 @@ from model_utils.models import TimeStampedModel from student.models import user_by_anonymous_id from submissions.models import score_set, score_reset +from openedx.core.djangoapps.call_stack_manager import CallStackManager, CallStackMixin from xmodule_django.models import CourseKeyField, LocationKeyField, BlockTypeKeyField # pylint: disable=import-error log = logging.getLogger(__name__) @@ -68,11 +69,20 @@ class ChunkingManager(models.Manager): return res -class StudentModule(models.Model): +class ChunkingCallStackManager(CallStackManager, ChunkingManager): + """ + A derived class of ChunkingManager, and CallStackManager + """ + pass + + +class StudentModule(CallStackMixin, models.Model): """ Keeps student state for a particular module in a particular course. """ - objects = ChunkingManager() + # uses both ChunkingManager and CallStackManager in ChuckingCallStackManager + objects = ChunkingCallStackManager() + MODEL_TAGS = ['course_id', 'module_type'] # For a homework problem, contains a JSON @@ -84,7 +94,7 @@ class StudentModule(models.Model): ('chapter', 'Section'), ('sequential', 'Subsection'), ('library_content', 'Library Content')) - ## These three are the key for the object + # These three are the key for the object module_type = models.CharField(max_length=32, choices=MODULE_TYPES, default='problem', db_index=True) # Key used to share state. This is the XBlock usage_id @@ -142,10 +152,13 @@ class StudentModule(models.Model): return unicode(repr(self)) -class StudentModuleHistory(models.Model): +class StudentModuleHistory(CallStackMixin, models.Model): """Keeps a complete history of state changes for a given XModule for a given Student. Right now, we restrict this to problems so that the table doesn't explode in size.""" + + # Add call stack manager as default Manager + objects = CallStackManager() HISTORY_SAVING_TYPES = {'problem'} class Meta(object): # pylint: disable=missing-docstring diff --git a/lms/djangoapps/courseware/user_state_client.py b/lms/djangoapps/courseware/user_state_client.py index bb18d1d7ab..d96979f1b0 100644 --- a/lms/djangoapps/courseware/user_state_client.py +++ b/lms/djangoapps/courseware/user_state_client.py @@ -18,6 +18,8 @@ from courseware.models import StudentModule, StudentModuleHistory from contracts import contract, new_contract from opaque_keys.edx.keys import UsageKey +from openedx.core.djangoapps.call_stack_manager import donottrack + new_contract('UsageKey', UsageKey) @@ -25,7 +27,6 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): """ An interface that uses the Django ORM StudentModule as a backend. """ - class ServiceUnavailable(XBlockUserStateClient.ServiceUnavailable): """ This error is raised if the service backing this client is currently unavailable. @@ -53,6 +54,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): """ self.user = user + @donottrack(StudentModule, StudentModuleHistory) @contract( username="basestring", block_key=UsageKey, @@ -82,7 +84,11 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): return state - @contract(username="basestring", block_key=UsageKey, state="dict(basestring: *)", scope=ScopeBase) + @donottrack(StudentModule, StudentModuleHistory) + @contract(username="basestring", + block_key=UsageKey, + state="dict(basestring: *)", + scope=ScopeBase) def set(self, username, block_key, state, scope=Scope.user_state): """ Set fields for a particular XBlock. @@ -95,6 +101,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): """ self.set_many(username, {block_key: state}, scope) + @donottrack(StudentModule, StudentModuleHistory) @contract( username="basestring", block_key=UsageKey, @@ -113,6 +120,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): """ return self.delete_many(username, [block_key], scope, fields=fields) + @donottrack(StudentModule, StudentModuleHistory) @contract( username="basestring", block_key=UsageKey, @@ -139,6 +147,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): field: date for (_, field, date) in results } + @donottrack(StudentModule, StudentModuleHistory) @contract(username="basestring", block_keys="seq(UsageKey)|set(UsageKey)") def _get_student_modules(self, username, block_keys): """ @@ -166,6 +175,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): usage_key = student_module.module_state_key.map_into_course(student_module.course_id) yield (student_module, usage_key) + @donottrack(StudentModule, StudentModuleHistory) @contract( username="basestring", block_keys="seq(UsageKey)|set(UsageKey)", @@ -197,6 +207,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): state = json.loads(module.state) yield (usage_key, state) + @donottrack(StudentModule, StudentModuleHistory) @contract(username="basestring", block_keys_to_state="dict(UsageKey: dict(basestring: *))", scope=ScopeBase) def set_many(self, username, block_keys_to_state, scope=Scope.user_state): """ @@ -243,6 +254,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): # We just read this object, so we know that we can do an update student_module.save(force_update=True) + @donottrack(StudentModule, StudentModuleHistory) @contract( username="basestring", block_keys="seq(UsageKey)|set(UsageKey)", @@ -276,6 +288,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): # We just read this object, so we know that we can do an update student_module.save(force_update=True) + @donottrack(StudentModule, StudentModuleHistory) @contract( username="basestring", block_keys="seq(UsageKey)|set(UsageKey)", @@ -308,6 +321,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient): for field in json.loads(student_module.state): yield (usage_key, field, student_module.modified) + @donottrack(StudentModule, StudentModuleHistory) @contract(username="basestring", block_key=UsageKey, scope=ScopeBase) def get_history(self, username, block_key, scope=Scope.user_state): """ diff --git a/openedx/core/djangoapps/call_stack_manager/core.py b/openedx/core/djangoapps/call_stack_manager/core.py index 98a63e8dbd..42f5d8c2c2 100644 --- a/openedx/core/djangoapps/call_stack_manager/core.py +++ b/openedx/core/djangoapps/call_stack_manager/core.py @@ -27,7 +27,7 @@ How to use- 4. Decorator is a parameterized decorator with class name/s as argument How to use - 1. Import following - import from openedx.core.djangoapps.call_stack_manager import donottrack + from openedx.core.djangoapps.call_stack_manager import donottrack """ import logging @@ -132,13 +132,15 @@ def donottrack(*classes_not_to_be_tracked): global TRACK_FLAG # pylint: disable=W0603 current_flag = TRACK_FLAG TRACK_FLAG = False - function(*args, **kwargs) + return_value = function(*args, **kwargs) TRACK_FLAG = current_flag + return return_value else: global HALT_TRACKING # pylint: disable=W0603 current_halt_track = HALT_TRACKING HALT_TRACKING = classes_not_to_be_tracked - function(*args, **kwargs) + return_value = function(*args, **kwargs) HALT_TRACKING = current_halt_track + return return_value return wrapper return real_donottrack diff --git a/openedx/core/djangoapps/call_stack_manager/tests.py b/openedx/core/djangoapps/call_stack_manager/tests.py index 7112e01ec5..9fda64bf1e 100644 --- a/openedx/core/djangoapps/call_stack_manager/tests.py +++ b/openedx/core/djangoapps/call_stack_manager/tests.py @@ -109,6 +109,13 @@ def donottrack_func_child(): ModelMixin.objects.all() +@donottrack() +def donottrack_check_with_return(): + """ function that returns something i.e. a wrapped function returning some value + """ + return 42 + + @patch('openedx.core.djangoapps.call_stack_manager.core.log.info') @patch('openedx.core.djangoapps.call_stack_manager.core.REGULAR_EXPS', []) class TestingCallStackManager(TestCase): @@ -213,3 +220,11 @@ class TestingCallStackManager(TestCase): for __ in range(1, 5): ModelMixinCallStckMngr(id_field=1).save() self.assertEqual(len(log_capt.call_args_list), 1) + + def test_donottrack_with_return(self, log_capt): + """ Test for @donottrack + Checks if wrapper function returns the same value as wrapped function + """ + everything = donottrack_check_with_return() + self.assertEqual(everything, 42) + self.assertEqual(len(log_capt.call_args_list), 0)