def _update_last_visited_module_id(self, request, course, module_key, modification_date): """ Saves the module id if the found modification_date is less recent than the passed modification date """ field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, request.user, course, depth=2) try: module_descriptor = modulestore().get_item(module_key) except ItemNotFoundError: return Response(errors.ERROR_INVALID_MODULE_ID, status=400) module = get_module_for_descriptor( request.user, request, module_descriptor, field_data_cache, course.id, course=course ) if modification_date: key = KeyValueStore.Key( scope=Scope.user_state, user_id=request.user.id, block_scope_id=course.location, field_name='position' ) original_store_date = field_data_cache.last_modified(key) if original_store_date is not None and modification_date < original_store_date: # old modification date so skip update return self._get_course_info(request, course) save_positions_recursively_up(request.user, request, field_data_cache, module, course=course) return self._get_course_info(request, course)
def _get_course_module(course_descriptor, user): # Adding courseware imports here to insulate other apps (e.g. schedules) to # avoid import errors. from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.module_render import get_module_for_descriptor # Fake a request to fool parts of the courseware that want to inspect it. request = get_request_or_stub() request.user = user # Now evil modulestore magic to inflate our descriptor with user state and # permissions checks. field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_descriptor.id, user, course_descriptor, depth=1, read_only=True, ) return get_module_for_descriptor( user, request, course_descriptor, field_data_cache, course_descriptor.id, course=course_descriptor, )
def edxnotes_visibility(request, course_id): """ Handle ajax call from "Show notes" checkbox. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, "load", course_key) field_data_cache = FieldDataCache([course], course_key, request.user) course_module = get_module_for_descriptor(request.user, request, course, field_data_cache, course_key, course=course) if not is_feature_enabled(course, request.user): raise Http404 try: visibility = json.loads(request.body.decode('utf8'))["visibility"] course_module.edxnotes_visibility = visibility course_module.save() return JsonResponse(status=200) except (ValueError, KeyError): log.warning( u"Could not decode request body as JSON and find a boolean visibility field: '%s'", request.body) return JsonResponseBadRequest()
def _get_course_module(course_descriptor, user): """ Gets course module that takes into account user state and permissions """ # Adding courseware imports here to insulate other apps (e.g. schedules) to # avoid import errors. from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.module_render import get_module_for_descriptor # Fake a request to fool parts of the courseware that want to inspect it. request = get_request_or_stub() request.user = user # Now evil modulestore magic to inflate our descriptor with user state and # permissions checks. field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_descriptor.id, user, course_descriptor, depth=1, read_only=True, ) course_module = get_module_for_descriptor( user, request, course_descriptor, field_data_cache, course_descriptor.id, course=course_descriptor, ) if not course_module: raise CourseUpdateDoesNotExist('Course module {} not found'.format( course_descriptor.id)) return course_module
def _last_visited_module_path(self, request, course): """ Returns the path from the last module visited by the current user in the given course up to the course module. If there is no such visit, the first item deep enough down the course tree is used. """ field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, request.user, course, depth=2) course_module = get_module_for_descriptor(request.user, request, course, field_data_cache, course.id, course=course) path = [course_module] chapter = get_current_child(course_module, min_depth=2) if chapter is not None: path.append(chapter) section = get_current_child(chapter, min_depth=1) if section is not None: path.append(section) path.reverse() return path
def get_course_info_section_module(request, user, course, section_key): """ This returns the course info module for a given section_key. Valid keys: - handouts - guest_handouts - updates - guest_updates """ usage_key = get_course_info_usage_key(course, section_key) # Use an empty cache field_data_cache = FieldDataCache([], course.id, user) return get_module( user, request, usage_key, field_data_cache, log_if_not_found=False, wrap_xmodule_display=False, static_asset_path=course.static_asset_path, course=course )
def test_repeated_course_module_instantiation(self, loops, course_depth): with modulestore().default_store(ModuleStoreEnum.Type.split): course = CourseFactory.create() chapter = ItemFactory(parent=course, category='chapter', graded=True) section = ItemFactory(parent=chapter, category='sequential') __ = ItemFactory(parent=section, category='problem') fake_request = self.factory.get( reverse('progress', kwargs={'course_id': str(course.id)})) course = modulestore().get_course(course.id, depth=course_depth) for _ in range(loops): field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, self.user, course, depth=course_depth) course_module = get_module_for_descriptor(self.user, fake_request, course, field_data_cache, course.id, course=course) for chapter in course_module.get_children(): for section in chapter.get_children(): for item in section.get_children(): assert item.graded
def setUp(self): super(TestInvalidScopes, self).setUp() self.user = UserFactory.create(username='******') self.field_data_cache = FieldDataCache( [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user) self.kvs = DjangoKeyValueStore(self.field_data_cache)
def answer_entrance_exam_problem(course, request, problem, user=None, value=1, max_value=1): """ Takes a required milestone `problem` in a `course` and fulfills it. Args: course (Course): Course object, the course the required problem is in request (Request): request Object problem (xblock): xblock object, the problem to be fulfilled user (User): User object in case it is different from request.user value (int): raw_earned value of the problem max_value (int): raw_possible value of the problem """ if not user: user = request.user grade_dict = {'value': value, 'max_value': max_value, 'user_id': user.id} field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, user, course, depth=2 ) module = get_module( user, request, problem.scope_ids.usage_id, field_data_cache, ) module.system.publish(problem, 'grade', grade_dict)
def _init_field_data_for_block(self, block): """ Initialize the FieldData implementation for the specified XBlock """ if self.user is None: # No user is specified, so we want to throw an error if anything attempts to read/write user-specific fields student_data_store = None elif self.user.is_anonymous: # The user is anonymous. Future work will support saving their state # in a cache or the django session but for now just use a highly # ephemeral dict. student_data_store = KvsFieldData(kvs=DictKeyValueStore()) elif self.system.student_data_mode == XBlockRuntimeSystem.STUDENT_DATA_EPHEMERAL: # We're in an environment like Studio where we want to let the # author test blocks out but not permanently save their state. # This in-memory dict will typically only persist for one # request-response cycle, so we need to soon replace it with a store # that puts the state into a cache or the django session. student_data_store = KvsFieldData(kvs=DictKeyValueStore()) else: # Use database-backed field data (i.e. store user_state in StudentModule) context_key = block.scope_ids.usage_id.context_key if context_key not in self.django_field_data_caches: field_data_cache = FieldDataCache( [block], course_id=context_key, user=self.user, asides=None, read_only=False, ) self.django_field_data_caches[context_key] = field_data_cache else: field_data_cache = self.django_field_data_caches[context_key] field_data_cache.add_descriptors_to_cache([block]) student_data_store = KvsFieldData( kvs=DjangoKeyValueStore(field_data_cache)) return SplitFieldData({ Scope.content: self.system.authored_data_store, Scope.settings: self.system.authored_data_store, Scope.parent: self.system.authored_data_store, Scope.children: self.system.authored_data_store, Scope.user_state_summary: student_data_store, Scope.user_state: student_data_store, Scope.user_info: student_data_store, Scope.preferences: student_data_store, })
def get_course_child_content(request, user, course_key, child_descriptor): """ Returns course child content """ field_data_cache = FieldDataCache([child_descriptor], course_key, user) child_content = module_render.get_module_for_descriptor( user, request, child_descriptor, field_data_cache, course_key) return child_content
def get_course_content(request, user, course_key, course_descriptor): # pylint: disable=W0613 """ Returns course content """ field_data_cache = FieldDataCache([course_descriptor], course_key, user) course_content = module_render.get_module_for_descriptor( user, request, course_descriptor, field_data_cache, course_key) return course_content
def get_course_module(self, course): request = RequestFactory().request() field_data_cache = FieldDataCache([], course.id, self.user) return get_module(self.user, request, course.location, field_data_cache, course=course)
def _init_field_data_for_block(self, block): """ Initialize the FieldData implementation for the specified XBlock """ if self.user is None: # No user is specified, so we want to throw an error if anything attempts to read/write user-specific fields student_data_store = None elif self.user.is_anonymous: # This is an anonymous (non-registered) user: assert self.user_id.startswith("anon") kvs = EphemeralKeyValueStore() student_data_store = KvsFieldData(kvs) elif self.system.student_data_mode == XBlockRuntimeSystem.STUDENT_DATA_EPHEMERAL: # We're in an environment like Studio where we want to let the # author test blocks out but not permanently save their state. kvs = EphemeralKeyValueStore() student_data_store = KvsFieldData(kvs) else: # Use database-backed field data (i.e. store user_state in StudentModule) context_key = block.scope_ids.usage_id.context_key if context_key not in self.django_field_data_caches: field_data_cache = FieldDataCache( [block], course_id=context_key, user=self.user, asides=None, read_only=False, ) self.django_field_data_caches[context_key] = field_data_cache else: field_data_cache = self.django_field_data_caches[context_key] field_data_cache.add_descriptors_to_cache([block]) student_data_store = KvsFieldData( kvs=DjangoKeyValueStore(field_data_cache)) return SplitFieldData({ Scope.content: self.system.authored_data_store, Scope.settings: self.system.authored_data_store, Scope.parent: self.system.authored_data_store, Scope.children: self.system.children_data_store, Scope.user_state_summary: student_data_store, Scope.user_state: student_data_store, Scope.user_info: student_data_store, Scope.preferences: student_data_store, })
def get_module_for_student(student, usage_key, request=None, course=None): """Return the module for the (student, location) using a DummyRequest.""" if request is None: request = DummyRequest() request.user = student descriptor = modulestore().get_item(usage_key, depth=0) field_data_cache = FieldDataCache([descriptor], usage_key.course_key, student) return get_module(student, request, usage_key, field_data_cache, course=course)
def setUp(self): super().setUp() self.user = UserFactory.create(username='******') self.field_data_cache = FieldDataCache( [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], COURSE_KEY, self.user, ) self.kvs = DjangoKeyValueStore(self.field_data_cache)
def setUp(self): super(TestMissingStudentModule, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments self.user = UserFactory.create(username='******') self.assertEqual(self.user.id, 1) # check our assumption hard-coded in the key functions above. # The descriptor has no fields, so FDC shouldn't send any queries with self.assertNumQueries(0): self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user) self.kvs = DjangoKeyValueStore(self.field_data_cache)
def _get_module_instance_for_task(course_id, student, module_descriptor, xmodule_instance_args=None, grade_bucket_type=None, course=None): """ Fetches a StudentModule instance for a given `course_id`, `student` object, and `module_descriptor`. `xmodule_instance_args` is used to provide information for creating a track function and an XQueue callback. These are passed, along with `grade_bucket_type`, to get_module_for_descriptor_internal, which sidesteps the need for a Request object when instantiating an xmodule instance. """ # reconstitute the problem's corresponding XModule: field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_id, student, module_descriptor) student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache)) # get request-related tracking information from args passthrough, and supplement with task-specific # information: request_info = xmodule_instance_args.get( 'request_info', {}) if xmodule_instance_args is not None else {} task_info = { "student": student.username, "task_id": _get_task_id_from_xmodule_args(xmodule_instance_args) } def make_track_function(): ''' Make a tracking function that logs what happened. For insertion into ModuleSystem, and used by CapaModule, which will provide the event_type (as string) and event (as dict) as arguments. The request_info and task_info (and page) are provided here. ''' return lambda event_type, event: task_track( request_info, task_info, event_type, event, page='x_module_task') xqueue_callback_url_prefix = xmodule_instance_args.get('xqueue_callback_url_prefix', '') \ if xmodule_instance_args is not None else '' return get_module_for_descriptor_internal( user=student, descriptor=module_descriptor, student_data=student_data, course_id=course_id, track_function=make_track_function(), xqueue_callback_url_prefix=xqueue_callback_url_prefix, grade_bucket_type=grade_bucket_type, # This module isn't being used for front-end rendering request_token=None, # pass in a loaded course for override enabling course=course)
def setUp(self): super().setUp() self.user = UserFactory.create(username='******') assert self.user.id == 1 # check our assumption hard-coded in the key functions above. # The descriptor has no fields, so FDC shouldn't send any queries with self.assertNumQueries(0): self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user) self.kvs = DjangoKeyValueStore(self.field_data_cache)
def _return_table_of_contents(self): """ Returns table of content for the entrance exam specific to this test Returns the table of contents for course self.course, for chapter self.entrance_exam, and for section self.exam1 """ self.field_data_cache = FieldDataCache.cache_for_descriptor_descendents( # pylint: disable=attribute-defined-outside-init self.course.id, self.request.user, self.entrance_exam) toc = toc_for_course(self.request.user, self.request, self.course, self.entrance_exam.url_name, self.exam_1.url_name, self.field_data_cache) return toc['chapters']
def setUp(self): super(TestStudentModuleStorage, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments student_module = StudentModuleFactory(state=json.dumps({'a_field': 'a_value', 'b_field': 'b_value'})) self.user = student_module.student self.assertEqual(self.user.id, 1) # check our assumption hard-coded in the key functions above. # There should be only one query to load a single descriptor with a single user_state field with self.assertNumQueries(1): self.field_data_cache = FieldDataCache( [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user ) self.kvs = DjangoKeyValueStore(self.field_data_cache)
def get_module_for_user(self, user, course, problem): """Helper function to get useful module at self.location in self.course_id for user""" mock_request = mock.MagicMock() mock_request.user = user field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, user, course, depth=2) module = module_render.get_module( # pylint: disable=protected-access user, mock_request, problem.location, field_data_cache, ) return module
def _get_course_module(course_descriptor, user): # Fake a request to fool parts of the courseware that want to inspect it. request = get_request_or_stub() request.user = user # Now evil modulestore magic to inflate our descriptor with user state and # permissions checks. field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course_descriptor.id, user, course_descriptor, depth=1, read_only=True, ) return get_module_for_descriptor( user, request, course_descriptor, field_data_cache, course_descriptor.id, course=course_descriptor, )
def setUp(self): field_storage = self.factory.create() if hasattr(field_storage, 'student'): self.user = field_storage.student else: self.user = UserFactory.create() self.mock_descriptor = mock_descriptor([ mock_field(self.scope, 'existing_field'), mock_field(self.scope, 'other_existing_field')]) # Each field is stored as a separate row in the table, # but we can query them in a single query with self.assertNumQueries(1): self.field_data_cache = FieldDataCache([self.mock_descriptor], course_id, self.user) self.kvs = DjangoKeyValueStore(self.field_data_cache)
def test_changing_position_works(self): # Make a mock FieldDataCache for this course, so we can get the course module mock_field_data_cache = FieldDataCache([self.course], self.course.id, self.student) course = get_module_for_descriptor(self.student, MagicMock(name='request'), self.course, mock_field_data_cache, self.course.id, course=self.course) # Now that we have the course, change the position and save, nothing should explode! course.position = 2 course.save()
def edxnotes(request, course_id): """ Displays the EdxNotes page. Arguments: request: HTTP request object course_id: course id Returns: Rendered HTTP response. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, "load", course_key) if not is_feature_enabled(course, request.user): raise Http404 notes_info = get_notes(request, course) has_notes = (len(notes_info.get('results')) > 0) context = { "course": course, "notes_endpoint": reverse("notes", kwargs={"course_id": course_id}), "notes": notes_info, "page_size": DEFAULT_PAGE_SIZE, "debug": settings.DEBUG, 'position': None, 'disabled_tabs': settings.NOTES_DISABLED_TABS, 'has_notes': has_notes, } if not has_notes: field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, request.user, course, depth=2) course_module = get_module_for_descriptor(request.user, request, course, field_data_cache, course_key, course=course) position = get_course_position(course_module) if position: context.update({ 'position': position, }) return render_to_response("edxnotes/edxnotes.html", context)
def setUp(self): super().setUp() student_module = StudentModuleFactory( state=json.dumps({ 'a_field': 'a_value', 'b_field': 'b_value' })) self.user = student_module.student assert self.user.id == 1 # check our assumption hard-coded in the key functions above. # There should be only one query to load a single descriptor with a single user_state field with self.assertNumQueries(1): self.field_data_cache = FieldDataCache( [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], COURSE_KEY, self.user, ) self.kvs = DjangoKeyValueStore(self.field_data_cache)
def answer_problem(course, request, problem, score=1, max_value=1): """ Records a correct answer for the given problem. Arguments: course (Course): Course object, the course the required problem is in request (Request): request Object problem (xblock): xblock object, the problem to be answered """ user = request.user grade_dict = {'value': score, 'max_value': max_value, 'user_id': user.id} field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, user, course, depth=2) module = get_module( user, request, problem.scope_ids.usage_id, field_data_cache, ) module.system.publish(problem, 'grade', grade_dict)
def complete_student_attempt_task(user_identifier: str, content_id: str) -> None: """ Marks all completable children of content_id as complete for the user Submits all completable xblocks inside of the content_id block to the Completion Service to mark them as complete. One use case of this function is for special exams (timed/proctored) where regardless of submission status on individual problems, we want to mark the entire exam as complete when the exam is finished. params: user_identifier (str): username or email of a user content_id (str): the block key for a piece of content """ err_msg_prefix = ( 'Error occurred while attempting to complete student attempt for user ' f'{user_identifier} for content_id {content_id}. ' ) err_msg = None try: user = get_user_by_username_or_email(user_identifier) block_key = UsageKey.from_string(content_id) root_descriptor = modulestore().get_item(block_key) except ObjectDoesNotExist: err_msg = err_msg_prefix + 'User does not exist!' except InvalidKeyError: err_msg = err_msg_prefix + 'Invalid content_id!' except ItemNotFoundError: err_msg = err_msg_prefix + 'Block not found in the modulestore!' if err_msg: log.error(err_msg) return # This logic has been copied over from openedx/core/djangoapps/schedules/content_highlights.py # in the _get_course_module function. # I'm not sure if this is an anti-pattern or not, so if you can avoid re-copying this, please do. # We are using it here because we ran into issues with the User service being undefined when we # encountered a split_test xblock. # Fake a request to fool parts of the courseware that want to inspect it. request = get_request_or_stub() request.user = user # Now evil modulestore magic to inflate our descriptor with user state and # permissions checks. field_data_cache = FieldDataCache.cache_for_descriptor_descendents( root_descriptor.course_id, user, root_descriptor, read_only=True, ) root_module = get_module_for_descriptor( user, request, root_descriptor, field_data_cache, root_descriptor.course_id, ) if not root_module: err_msg = err_msg_prefix + 'Module unable to be created from descriptor!' log.error(err_msg) return def _submit_completions(block, user): """ Recursively submits the children for completion to the Completion Service """ mode = XBlockCompletionMode.get_mode(block) if mode == XBlockCompletionMode.COMPLETABLE: block.runtime.publish(block, 'completion', {'completion': 1.0, 'user_id': user.id}) elif mode == XBlockCompletionMode.AGGREGATOR: # I know this looks weird, but at the time of writing at least, there isn't a good # single way to get the children assigned for a partcular user. Some blocks define the # child descriptors method, but others don't and with blocks like Randomized Content # (Library Content), the get_children method returns all children and not just assigned # children. So this is our way around situations like that. See also Split Test Module # for another use case where user state has to be taken into account via get_child_descriptors block_children = ((hasattr(block, 'get_child_descriptors') and block.get_child_descriptors()) or (hasattr(block, 'get_children') and block.get_children()) or []) for child in block_children: _submit_completions(child, user) _submit_completions(root_module, user)
def get_course_about_section(request, course, section_key): """ This returns the snippet of html to be rendered on the course about page, given the key for the section. Valid keys: - overview - about_sidebar_html - short_description - description - key_dates (includes start, end, exams, etc) - video - course_staff_short - course_staff_extended - requirements - syllabus - textbook - faq - effort - more_info - ocw_links """ # Many of these are stored as html files instead of some semantic # markup. This can change without effecting this interface when we find a # good format for defining so many snippets of text/html. html_sections = { 'short_description', 'description', 'key_dates', 'video', 'course_staff_short', 'course_staff_extended', 'requirements', 'syllabus', 'textbook', 'faq', 'more_info', 'overview', 'effort', 'end_date', 'prerequisites', 'about_sidebar_html', 'ocw_links' } if section_key in html_sections: try: loc = course.location.replace(category='about', name=section_key) # Use an empty cache field_data_cache = FieldDataCache([], course.id, request.user) about_module = get_module( request.user, request, loc, field_data_cache, log_if_not_found=False, wrap_xmodule_display=False, static_asset_path=course.static_asset_path, course=course ) html = '' if about_module is not None: try: html = about_module.render(STUDENT_VIEW).content except Exception: # pylint: disable=broad-except html = render_to_string('courseware/error-message.html', None) log.exception( u"Error rendering course=%s, section_key=%s", course, section_key ) return html except ItemNotFoundError: log.warning( u"Missing about section %s in course %s", section_key, text_type(course.location) ) return None raise KeyError("Invalid about key " + str(section_key))