def test_setting_override(self, is_enabled, override_choice, expected_result): RequestCache.clear_request_cache() self.set_waffle_course_override(override_choice, is_enabled) override_value = WaffleFlagCourseOverrideModel.override_value( self.WAFFLE_TEST_NAME, self.TEST_COURSE_KEY ) self.assertEqual(override_value, expected_result)
def test_grades_csv(self): self.course.enable_ccx = True RequestCache.clear_request_cache() url = reverse( 'ccx_grades_csv', kwargs={'course_id': self.ccx_key} ) response = self.client.get(url) self.assertEqual(response.status_code, 200) # Are the grades downloaded as an attachment? self.assertEqual( response['content-disposition'], 'attachment' ) rows = response.content.strip().split('\r') headers = rows[0] # picking first student records data = dict(zip(headers.strip().split(','), rows[1].strip().split(','))) self.assertNotIn('HW 04', data) self.assertEqual(data['HW 01'], '0.75') self.assertEqual(data['HW 02'], '0.5') self.assertEqual(data['HW 03'], '0.25') self.assertEqual(data['HW Avg'], '0.5')
def instrument_course_progress_render(self, course_width, enable_ccx, queries, reads, xblocks): """ Renders the progress page, instrumenting Mongo reads and SQL queries. """ self.setup_course(course_width, enable_ccx) # Switch to published-only mode to simulate the LMS with self.settings(MODULESTORE_BRANCH='published-only'): # Clear all caches before measuring for cache in settings.CACHES: get_cache(cache).clear() # Refill the metadata inheritance cache modulestore().get_course(self.course.id, depth=None) # We clear the request cache to simulate a new request in the LMS. RequestCache.clear_request_cache() # Reset the list of provider classes, so that our django settings changes # can actually take affect. OverrideFieldData.provider_classes = None with self.assertNumQueries(queries): with check_mongo_calls(reads): with check_sum_of_calls(XBlock, ['__init__'], xblocks, xblocks, include_arguments=False): self.grade_course(self.course)
def test_get_template_path(self): """ Tests to make sure the get_template_path function works as expected. """ # if the current site has associated SiteTheme then get_template_path should return the argument as is. with patch( "openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme", Mock(return_value=True), ): with patch( "openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite", Mock(return_value=True), ): with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend: mock_microsite_backend.get_template = Mock(return_value="/microsite/about.html") self.assertEqual(theming_helpers.get_template_path("about.html"), "about.html") RequestCache.clear_request_cache() # if the current site does not have associated SiteTheme then get_template_path should return microsite override with patch( "openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme", Mock(return_value=False), ): with patch( "openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite", Mock(return_value=True), ): with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend: mock_microsite_backend.get_template_path = Mock(return_value="/microsite/about.html") self.assertEqual(theming_helpers.get_template_path("about.html"), "/microsite/about.html")
def test_request_cached_with_caches_despite_changing_wrapped_result(self): """ Ensure that after caching a result, we always send it back, even if the underlying result changes. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [1, 2, 3] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) direct_result = mock_wrapper() self.assertEqual(direct_result, 2) self.assertEqual(to_be_wrapped.call_count, 2) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 2) direct_result = mock_wrapper() self.assertEqual(direct_result, 3) self.assertEqual(to_be_wrapped.call_count, 3)
def instrument_course_progress_render( self, course_width, enable_ccx, view_as_ccx, sql_queries, mongo_reads, ): """ Renders the progress page, instrumenting Mongo reads and SQL queries. """ course_key = self.setup_course(course_width, enable_ccx, view_as_ccx) # Switch to published-only mode to simulate the LMS with self.settings(MODULESTORE_BRANCH='published-only'): # Clear all caches before measuring for cache in settings.CACHES: caches[cache].clear() # Refill the metadata inheritance cache get_course_in_cache(course_key) # We clear the request cache to simulate a new request in the LMS. RequestCache.clear_request_cache() # Reset the list of provider classes, so that our django settings changes # can actually take affect. OverrideFieldData.provider_classes = None with self.assertNumQueries(sql_queries, using='default'): with self.assertNumQueries(0, using='student_module_history'): with self.assertMongoCallCount(mongo_reads): with self.assertXBlockInstantiations(1): self.grade_course(course_key)
def test_grades_csv(self): self.course.enable_ccx = True RequestCache.clear_request_cache() url = reverse( 'ccx_grades_csv', kwargs={'course_id': self.ccx_key} ) response = self.client.get(url) self.assertEqual(response.status_code, 200) # Are the grades downloaded as an attachment? self.assertEqual( response['content-disposition'], 'attachment' ) rows = response.content.strip().split('\r') headers = rows[0] records = dict() for i in range(1, len(rows)): data = dict(zip(headers.strip().split(','), rows[i].strip().split(','))) records[data['username']] = data student_data = records[self.student.username] # pylint: disable=no-member self.assertNotIn('HW 04', student_data) self.assertEqual(student_data['HW 01'], '0.75') self.assertEqual(student_data['HW 02'], '0.5') self.assertEqual(student_data['HW 03'], '0.25') self.assertEqual(student_data['HW Avg'], '0.5')
def test_undefined_waffle_flag(self, data): """ Test flag with various defaults provided for undefined waffle flags. """ RequestCache.clear_request_cache() test_course_flag = CourseWaffleFlag( self.TEST_NAMESPACE, self.FLAG_NAME, flag_undefined_default=data['flag_undefined_default'] ) with patch.object( WaffleFlagCourseOverrideModel, 'override_value', return_value=WaffleFlagCourseOverrideModel.ALL_CHOICES.unset ): # check twice to test that the result is properly cached self.assertEqual(test_course_flag.is_enabled(self.TEST_COURSE_KEY), data['result']) self.assertEqual(test_course_flag.is_enabled(self.TEST_COURSE_KEY), data['result']) # result is cached, so override check should happen once WaffleFlagCourseOverrideModel.override_value.assert_called_once_with( self.NAMESPACED_FLAG_NAME, self.TEST_COURSE_KEY )
def test_course_waffle_flag(self, data): """ Tests various combinations of a flag being set in waffle and overridden for a course. """ RequestCache.clear_request_cache() with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']): with override_flag(self.NAMESPACED_FLAG_NAME, active=data['waffle_enabled']): # check twice to test that the result is properly cached self.assertEqual(self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_KEY), data['result']) self.assertEqual(self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_KEY), data['result']) # result is cached, so override check should happen once WaffleFlagCourseOverrideModel.override_value.assert_called_once_with( self.NAMESPACED_FLAG_NAME, self.TEST_COURSE_KEY ) # check flag for a second course if data['course_override'] == WaffleFlagCourseOverrideModel.ALL_CHOICES.unset: # When course override wasn't set for the first course, the second course will get the same # cached value from waffle. self.assertEqual(self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_2_KEY), data['waffle_enabled']) else: # When course override was set for the first course, it should not apply to the second # course which should get the default value of False. self.assertEqual(self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_2_KEY), False)
def test_setting_override_multiple_times(self): RequestCache.clear_request_cache() self.set_waffle_course_override(self.OVERRIDE_CHOICES.on) self.set_waffle_course_override(self.OVERRIDE_CHOICES.off) override_value = WaffleFlagCourseOverrideModel.override_value( self.WAFFLE_TEST_NAME, self.TEST_COURSE_KEY ) self.assertEqual(override_value, self.OVERRIDE_CHOICES.off)
def handle(self, *args, **options): # pylint: disable=unused-argument """ Iterates through each course, serializes them into graphs, and saves those graphs to neo4j. """ # first, make sure that there's a valid neo4j configuration if settings.NEO4J_CONFIG is None: raise CommandError( "No neo4j configuration (NEO4J_CONFIG) defined in lms.auth.json." ) auth_params = ["{host}:{https_port}", "{user}", "{password}"] authenticate(*[param.format(**settings.NEO4J_CONFIG) for param in auth_params]) graph = Graph(**settings.NEO4J_CONFIG) mss = ModuleStoreSerializer() total_number_of_courses = len(mss.all_courses) for index, course in enumerate(mss.all_courses): # first, clear the request cache to prevent memory leaks RequestCache.clear_request_cache() log.info( "Now exporting %s to neo4j: course %d of %d total courses", course.id, index + 1, total_number_of_courses ) nodes, relationships = mss.serialize_course(course.id) log.info( "%d nodes and %d relationships in %s", len(nodes), len(relationships), course.id ) transaction = graph.begin() try: # first, delete existing course transaction.run( "MATCH (n:item) WHERE n.course_key='{}' DETACH DELETE n".format( six.text_type(course.id) ) ) # now, re-add it self.add_to_transaction(nodes, transaction) self.add_to_transaction(relationships, transaction) transaction.commit() except Exception: # pylint: disable=broad-except log.exception( "Error trying to dump course %s to neo4j, rolling back", six.text_type(course.id) ) transaction.rollback()
def lti_consumer_fields_editing_flag(course_id, enabled_for_course=False): """ Yields CourseEditLTIFieldsEnabledFlag record for unit tests Arguments: course_id (CourseLocator): course locator to control this feature for. enabled_for_course (bool): whether feature is enabled for 'course_id' """ RequestCache.clear_request_cache() CourseEditLTIFieldsEnabledFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def assert_access_to_gated_content(self, user, expected_access): """ Verifies access to gated content for the given user is as expected. """ # clear the request cache to flush any cached access results RequestCache.clear_request_cache() # access to gating content (seq1) remains constant self.assertTrue(bool(has_access(user, 'load', self.seq1, self.course.id))) # access to gated content (seq2) is as expected self.assertEquals(bool(has_access(user, 'load', self.seq2, self.course.id)), expected_access)
def test_gradebook(self): self.course.enable_ccx = True RequestCache.clear_request_cache() url = reverse("ccx_gradebook", kwargs={"course_id": self.ccx_key}) response = self.client.get(url) self.assertEqual(response.status_code, 200) # Max number of student per page is one. Patched setting MAX_STUDENTS_PER_PAGE_GRADE_BOOK = 1 self.assertEqual(len(response.mako_context["students"]), 1) # pylint: disable=no-member student_info = response.mako_context["students"][0] # pylint: disable=no-member self.assertEqual(student_info["grade_summary"]["percent"], 0.5) self.assertEqual(student_info["grade_summary"]["grade_breakdown"][0]["percent"], 0.5) self.assertEqual(len(student_info["grade_summary"]["section_breakdown"]), 4)
def persistent_grades_feature_flags( global_flag, enabled_for_all_courses=False, course_id=None, enabled_for_course=False ): """ Most test cases will use a single call to this manager, as they need to set the global setting and the course-specific setting for a single course. """ RequestCache.clear_request_cache() PersistentGradesEnabledFlag.objects.create(enabled=global_flag, enabled_for_all_courses=enabled_for_all_courses) if course_id: CoursePersistentGradesFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def link_func(self): """ Returns a function that computes the URL for this tab. """ request = RequestCache.get_current_request() url_name = self.main_course_url_name(request) return link_reverse_func(url_name)
def handle(self, *args, **options): "Execute the command" if len(args) != 2: raise CommandError("clone requires two arguments: <source-course_id> <dest-course_id>") source_course_id = args[0] dest_course_id = args[1] mstore = modulestore('direct') cstore = contentstore() mstore.metadata_inheritance_cache_subsystem = CACHE mstore.request_cache = RequestCache.get_request_cache() org, course_num, run = dest_course_id.split("/") mstore.ignore_write_events_on_courses.append('{0}/{1}'.format(org, course_num)) print("Cloning course {0} to {1}".format(source_course_id, dest_course_id)) source_location = CourseDescriptor.id_to_location(source_course_id) dest_location = CourseDescriptor.id_to_location(dest_course_id) if clone_course(mstore, cstore, source_location, dest_location): # be sure to recompute metadata inheritance after all those updates mstore.refresh_cached_metadata_inheritance_tree(dest_location) print("copying User permissions...") _copy_course_group(source_location, dest_location)
def handle(self, *args, **options): if len(args) != 1 and len(args) != 2: raise CommandError("delete_course requires one or more arguments: <location> |commit|") course_id = args[0] commit = False if len(args) == 2: commit = args[1] == 'commit' if commit: print 'Actually going to delete the course from DB....' ms = modulestore('direct') cs = contentstore() ms.metadata_inheritance_cache_subsystem = CACHE ms.request_cache = RequestCache.get_request_cache() org, course_num, run = course_id.split("/") ms.ignore_write_events_on_courses.append('{0}/{1}'.format(org, course_num)) if query_yes_no("Deleting course {0}. Confirm?".format(course_id), default="no"): if query_yes_no("Are you sure. This action cannot be undone!", default="no"): loc = CourseDescriptor.id_to_location(course_id) if delete_course(ms, cs, loc, commit): print 'removing User permissions from course....' # in the django layer, we need to remove all the user permissions groups associated with this course if commit: _delete_course_group(loc)
def new_assets_page_feature_flags( global_flag, enabled_for_all_courses=False, course_id=None, enabled_for_course=False ): """ Most test cases will use a single call to this manager, as they need to set the global setting and the course-specific setting for a single course. """ RequestCache.clear_request_cache() NewAssetsPageFlag.objects.create(enabled=global_flag, enabled_for_all_courses=enabled_for_all_courses) if course_id: CourseNewAssetsPageFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def get_group_info_for_cohort(cohort, use_cached=False): """ Get the ids of the group and partition to which this cohort has been linked as a tuple of (int, int). If the cohort has not been linked to any group/partition, both values in the tuple will be None. The partition group info is cached for the duration of a request. Pass use_cached=True to use the cached value instead of fetching from the database. """ request_cache = RequestCache.get_request_cache() cache_key = u"cohorts.get_group_info_for_cohort.{}".format(cohort.id) if use_cached and cache_key in request_cache.data: return request_cache.data[cache_key] request_cache.data.pop(cache_key, None) try: partition_group = CourseUserGroupPartitionGroup.objects.get(course_user_group=cohort) return request_cache.data.setdefault(cache_key, (partition_group.group_id, partition_group.partition_id)) except CourseUserGroupPartitionGroup.DoesNotExist: pass return request_cache.data.setdefault(cache_key, (None, None))
def create_modulestore_instance(engine, options): """ This will return a new instance of a modulestore given an engine and options """ class_ = load_function(engine) _options = {} _options.update(options) for key in FUNCTION_KEYS: if key in _options and isinstance(_options[key], basestring): _options[key] = load_function(_options[key]) if HAS_REQUEST_CACHE: request_cache = RequestCache.get_request_cache() else: request_cache = None try: metadata_inheritance_cache = get_cache('mongo_metadata_inheritance') except InvalidCacheBackendError: metadata_inheritance_cache = get_cache('default') return class_( metadata_inheritance_cache_subsystem=metadata_inheritance_cache, request_cache=request_cache, modulestore_update_signal=Signal(providing_args=['modulestore', 'course_id', 'location']), **_options )
def create_modulestore_instance(engine, doc_store_config, options, i18n_service=None): """ This will return a new instance of a modulestore given an engine and options """ class_ = load_function(engine) _options = {} _options.update(options) for key in FUNCTION_KEYS: if key in _options and isinstance(_options[key], basestring): _options[key] = load_function(_options[key]) if HAS_REQUEST_CACHE: request_cache = RequestCache.get_request_cache() else: request_cache = None try: metadata_inheritance_cache = get_cache('mongo_metadata_inheritance') except InvalidCacheBackendError: metadata_inheritance_cache = get_cache('default') return class_( metadata_inheritance_cache_subsystem=metadata_inheritance_cache, request_cache=request_cache, modulestore_update_signal=Signal(providing_args=['modulestore', 'course_id', 'location']), xblock_mixins=getattr(settings, 'XBLOCK_MIXINS', ()), xblock_select=getattr(settings, 'XBLOCK_SELECT_FUNCTION', None), doc_store_config=doc_store_config, i18n_service=i18n_service or ModuleI18nService(), **_options )
def clear_caches(cls): """ Clear all of the caches defined in settings.CACHES. """ # N.B. As of 2016-04-20, Django won't return any caches # from django.core.cache.caches.all() that haven't been # accessed using caches[name] previously, so we loop # over our list of overridden caches, instead. for cache in settings.CACHES: caches[cache].clear() # The sites framework caches in a module-level dictionary. # Clear that. sites.models.SITE_CACHE.clear() RequestCache.clear_request_cache()
def get_template_request_context(): """ Returns the template processing context to use for the current request, or returns None if there is not a current request. """ request = getattr(REQUEST_CONTEXT, "request", None) if not request: return None request_cache_dict = RequestCache.get_request_cache().data cache_key = "edxmako_request_context" if cache_key in request_cache_dict: return request_cache_dict[cache_key] context = RequestContext(request) context['is_secure'] = request.is_secure() context['site'] = safe_get_host(request) # This used to happen when a RequestContext object was initialized but was # moved to a different part of the logic when template engines were introduced. # Since we are not using template engines we do this here. # https://github.com/django/django/commit/37505b6397058bcc3460f23d48a7de9641cd6ef0 for processor in get_template_context_processors(): context.update(processor(request)) request_cache_dict[cache_key] = context return context
def create_modulestore_instance( engine, content_store, doc_store_config, options, i18n_service=None, fs_service=None, user_service=None, signal_handler=None, ): """ This will return a new instance of a modulestore given an engine and options """ class_ = load_function(engine) _options = {} _options.update(options) FUNCTION_KEYS = ["render_template"] for key in FUNCTION_KEYS: if key in _options and isinstance(_options[key], basestring): _options[key] = load_function(_options[key]) if HAS_REQUEST_CACHE: request_cache = RequestCache.get_request_cache() else: request_cache = None try: metadata_inheritance_cache = get_cache("mongo_metadata_inheritance") except InvalidCacheBackendError: metadata_inheritance_cache = get_cache("default") if issubclass(class_, MixedModuleStore): _options["create_modulestore_instance"] = create_modulestore_instance if issubclass(class_, BranchSettingMixin): _options["branch_setting_func"] = _get_modulestore_branch_setting if HAS_USER_SERVICE and not user_service: xb_user_service = DjangoXBlockUserService(get_current_user()) else: xb_user_service = None if "read_preference" in doc_store_config: doc_store_config["read_preference"] = getattr(ReadPreference, doc_store_config["read_preference"]) return class_( contentstore=content_store, metadata_inheritance_cache_subsystem=metadata_inheritance_cache, request_cache=request_cache, xblock_mixins=getattr(settings, "XBLOCK_MIXINS", ()), xblock_select=getattr(settings, "XBLOCK_SELECT_FUNCTION", None), doc_store_config=doc_store_config, i18n_service=i18n_service or ModuleI18nService(), fs_service=fs_service or xblock.reference.plugins.FSService(), user_service=user_service or xb_user_service, signal_handler=signal_handler or SignalHandler(class_), **_options )
def handle(self, *args, **options): if len(args) != 1: raise CommandError("check_course requires one argument: <location>") loc_str = args[0] loc = CourseDescriptor.id_to_location(loc_str) store = modulestore() # setup a request cache so we don't throttle the DB with all the metadata inheritance requests store.set_modulestore_configuration({ 'metadata_inheritance_cache_subsystem': CACHE, 'request_cache': RequestCache.get_request_cache() }) course = store.get_item(loc, depth=3) err_cnt = 0 def _xlint_metadata(module): err_cnt = check_module_metadata_editability(module) for child in module.get_children(): err_cnt = err_cnt + _xlint_metadata(child) return err_cnt err_cnt = err_cnt + _xlint_metadata(course) # we've had a bug where the xml_attributes field can we rewritten as a string rather than a dict def _check_xml_attributes_field(module): err_cnt = 0 if hasattr(module, 'xml_attributes') and isinstance(module.xml_attributes, basestring): print 'module = {0} has xml_attributes as a string. It should be a dict'.format(module.location.url()) err_cnt = err_cnt + 1 for child in module.get_children(): err_cnt = err_cnt + _check_xml_attributes_field(child) return err_cnt err_cnt = err_cnt + _check_xml_attributes_field(course) # check for dangling discussion items, this can cause errors in the forums def _get_discussion_items(module): discussion_items = [] if module.location.category == 'discussion': discussion_items = discussion_items + [module.location.url()] for child in module.get_children(): discussion_items = discussion_items + _get_discussion_items(child) return discussion_items discussion_items = _get_discussion_items(course) # now query all discussion items via get_items() and compare with the tree-traversal queried_discussion_items = store.get_items(['i4x', course.location.org, course.location.course, 'discussion', None, None]) for item in queried_discussion_items: if item.location.url() not in discussion_items: print 'Found dangling discussion module = {0}'.format(item.location.url())
def get_current_request(): """ Return current request instance. Returns: (HttpRequest): returns cirrent request """ return RequestCache.get_current_request()
def test_course_waffle_flag(self, data): """ Tests various combinations of a flag being set in waffle and overridden for a course. """ RequestCache.clear_request_cache() with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']): with override_flag(self.NAMESPACED_FLAG_NAME, active=data['waffle_enabled']): # check twice to test that the result is properly cached self.assertEqual(self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_KEY), data['result']) self.assertEqual(self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_KEY), data['result']) # result is cached, so override check should happen once WaffleFlagCourseOverrideModel.override_value.assert_called_once_with( self.NAMESPACED_FLAG_NAME, self.TEST_COURSE_KEY )
def test_gradebook(self): self.course.enable_ccx = True RequestCache.clear_request_cache() url = reverse( 'ccx_gradebook', kwargs={'course_id': self.ccx_key} ) response = self.client.get(url) self.assertEqual(response.status_code, 200) student_info = response.mako_context['students'][0] # pylint: disable=no-member self.assertEqual(student_info['grade_summary']['percent'], 0.5) self.assertEqual( student_info['grade_summary']['grade_breakdown'][0]['percent'], 0.5) self.assertEqual( len(student_info['grade_summary']['section_breakdown']), 4)
def test_request_cached_with_changing_kwargs(self): """ Ensure that calling a decorated function with different keyword arguments will not use a cached value invoked by a previous call with different arguments. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [1, 2, 3, 4, 5, 6] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) # This will be a miss, and make an underlying call. result = wrapped(1, foo=1) self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) # This will be a miss, and make an underlying call. result = wrapped(2, foo=2) self.assertEqual(result, 2) self.assertEqual(to_be_wrapped.call_count, 2) # This is bypass of the decorator. direct_result = mock_wrapper(3, foo=3) self.assertEqual(direct_result, 3) self.assertEqual(to_be_wrapped.call_count, 3) # These will be hits, and not make an underlying call. result = wrapped(1, foo=1) self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 3) result = wrapped(2, foo=2) self.assertEqual(result, 2) self.assertEqual(to_be_wrapped.call_count, 3) # Since we're changing foo, this will be a miss. result = wrapped(2, foo=5) self.assertEqual(result, 4) self.assertEqual(to_be_wrapped.call_count, 4)
def instrument_course_progress_render( self, course_width, enable_ccx, view_as_ccx, sql_queries, mongo_reads, ): """ Renders the progress page, instrumenting Mongo reads and SQL queries. """ course_key = self.setup_course(course_width, enable_ccx, view_as_ccx) # Switch to published-only mode to simulate the LMS with self.settings(MODULESTORE_BRANCH='published-only'): # Clear all caches before measuring for cache in settings.CACHES: caches[cache].clear() # Refill the metadata inheritance cache get_course_in_cache(course_key) # We clear the request cache to simulate a new request in the LMS. RequestCache.clear_request_cache() # Reset the list of provider classes, so that our django settings changes # can actually take affect. OverrideFieldData.provider_classes = None with self.assertNumQueries( sql_queries, using='default', table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with self.assertNumQueries(0, using='student_module_history'): with self.assertMongoCallCount(mongo_reads): with self.assertXBlockInstantiations(1): self.grade_course(course_key)
def setUp(self, **kwargs): """ Creates a test User if `create_user` is True. Returns the password for the test User. Args: create_user - specifies whether or not to create a test User. Default is True. """ settings_override = override_settings(MODULESTORE=self.MODULESTORE) settings_override.__enter__() self.addCleanup(settings_override.__exit__, None, None, None) # Clear out any existing modulestores, # which will cause them to be re-created clear_existing_modulestores() self.addCleanup(drop_mongo_collections) self.addCleanup(RequestCache().clear_request_cache) # Enable XModuleFactories for the space of this test (and its setUp). self.addCleanup(XMODULE_FACTORY_LOCK.disable) XMODULE_FACTORY_LOCK.enable() # When testing CCX, we should make sure that # OverrideFieldData.provider_classes is always reset to `None` so # that they're recalculated for every test OverrideFieldData.provider_classes = None super(ModuleStoreTestCase, self).setUp() self.store = modulestore() uname = 'testuser' email = '*****@*****.**' password = '******' if kwargs.pop('create_user', True): # Create the user so we can log them in. self.user = User.objects.create_user(uname, email, password) # Note that we do not actually need to do anything # for registration if we directly mark them active. self.user.is_active = True # Staff has access to view all courses self.user.is_staff = True self.user.save() return password
def get_team(commentable_id): """ Returns the team that the commentable_id belongs to if it exists. Returns None otherwise. """ request_cache_dict = RequestCache.get_request_cache().data cache_key = u"django_comment_client.team_commentable.{}".format( commentable_id) if cache_key in request_cache_dict: return request_cache_dict[cache_key] try: team = CourseTeam.objects.get(discussion_topic_id=commentable_id) except CourseTeam.DoesNotExist: team = None request_cache_dict[cache_key] = team return team
def _post_teardown(self): """ Flush the ModuleStore after each test. """ self.drop_mongo_collections() # Clear out the existing modulestores, # which will cause them to be re-created # the next time they are accessed. # We do this at *both* setup and teardown just to be safe. clear_existing_modulestores() # clear RequestCache to emulate its clearance after each http request. RequestCache().clear_request_cache() # Call superclass implementation super(ModuleStoreTestCase, self)._post_teardown()
def test_course_waffle_flag(self, data): """ Tests various combinations of a flag being set in waffle and overridden for a course. """ RequestCache.clear_request_cache() with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']): with override_flag(self.NAMESPACED_FLAG_NAME, active=data['waffle_enabled']): # check twice to test that the result is properly cached self.assertEqual( self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_KEY), data['result']) self.assertEqual( self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_KEY), data['result']) # result is cached, so override check should happen once WaffleFlagCourseOverrideModel.override_value.assert_called_once_with( self.NAMESPACED_FLAG_NAME, self.TEST_COURSE_KEY) # check flag for a second course if data['course_override'] == WaffleFlagCourseOverrideModel.ALL_CHOICES.unset: # When course override wasn't set for the first course, the second course will get the same # cached value from waffle. self.assertEqual( self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_2_KEY), data['waffle_enabled']) else: # When course override was set for the first course, it should not apply to the second # course which should get the default value of False. self.assertEqual( self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_2_KEY), False)
def __init__(self, **kwargs): request_cache_dict = RequestCache.get_request_cache().data services = kwargs.setdefault('services', {}) services['fs'] = xblock.reference.plugins.FSService() services['i18n'] = ModuleI18nService services['library_tools'] = LibraryToolsService(modulestore()) services['partitions'] = LmsPartitionService( user=kwargs.get('user'), course_id=kwargs.get('course_id'), track_function=kwargs.get('track_function', None), cache=request_cache_dict) services['settings'] = SettingsService() services['user_tags'] = UserTagsService(self) self.request_token = kwargs.pop('request_token', None) super(LmsModuleSystem, self).__init__(**kwargs)
def create_modulestore_instance(engine, content_store, doc_store_config, options, i18n_service=None, fs_service=None): """ This will return a new instance of a modulestore given an engine and options """ class_ = load_function(engine) _options = {} _options.update(options) FUNCTION_KEYS = ['render_template'] for key in FUNCTION_KEYS: if key in _options and isinstance(_options[key], basestring): _options[key] = load_function(_options[key]) if HAS_REQUEST_CACHE: request_cache = RequestCache.get_request_cache() else: request_cache = None try: metadata_inheritance_cache = get_cache('mongo_metadata_inheritance') except InvalidCacheBackendError: metadata_inheritance_cache = get_cache('default') if issubclass(class_, MixedModuleStore): _options['create_modulestore_instance'] = create_modulestore_instance if issubclass(class_, BranchSettingMixin): _options['branch_setting_func'] = _get_modulestore_branch_setting return class_( contentstore=content_store, metadata_inheritance_cache_subsystem=metadata_inheritance_cache, request_cache=request_cache, xblock_mixins=getattr(settings, 'XBLOCK_MIXINS', ()), xblock_select=getattr(settings, 'XBLOCK_SELECT_FUNCTION', None), doc_store_config=doc_store_config, i18n_service=i18n_service or ModuleI18nService(), fs_service=fs_service or xblock.reference.plugins.FSService(), **_options)
def handle(self, *args, **options): if len(args) != 1 and len(args) != 2: raise CommandError( "delete_course requires one or more arguments: <location> |commit|" ) course_id = args[0] commit = False if len(args) == 2: commit = args[1] == 'commit' if commit: print 'Actually going to delete the course from DB....' ms = modulestore('direct') cs = contentstore() ms.set_modulestore_configuration({ 'metadata_inheritance_cache_subsystem': CACHE, 'request_cache': RequestCache.get_request_cache() }) org, course_num, run = course_id.split("/") ms.ignore_write_events_on_courses.append('{0}/{1}'.format( org, course_num)) if query_yes_no("Deleting course {0}. Confirm?".format(course_id), default="no"): if query_yes_no("Are you sure. This action cannot be undone!", default="no"): loc = CourseDescriptor.id_to_location(course_id) if delete_course(ms, cs, loc, commit): print 'removing User permissions from course....' # in the django layer, we need to remove all the user permissions groups associated with this course if commit: try: _delete_course_group(loc) except Exception as err: print( "Error in deleting course groups for {0}: {1}". format(loc, err))
def __init__(self, **kwargs): request_cache_dict = RequestCache.get_request_cache().data store = modulestore() services = kwargs.setdefault('services', {}) services['completion'] = CompletionService( user=kwargs.get('user'), course_key=kwargs.get('course_id')) services['fs'] = xblock.reference.plugins.FSService() services['i18n'] = ModuleI18nService services['library_tools'] = LibraryToolsService(store) services['partitions'] = PartitionService( course_id=kwargs.get('course_id'), cache=request_cache_dict) services['settings'] = SettingsService() services['user_tags'] = UserTagsService(self) if badges_enabled(): services['badging'] = BadgingService( course_id=kwargs.get('course_id'), modulestore=store) self.request_token = kwargs.pop('request_token', None) super(LmsModuleSystem, self).__init__(**kwargs)
def _providers_for_course(cls, course): """ Return a filtered list of enabled providers based on the course passed in. Cache this result per request to avoid needing to call the provider filter api hundreds of times. Arguments: course: The course XBlock """ request_cache = RequestCache.get_request_cache() enabled_providers = request_cache.data.get( ENABLED_OVERRIDE_PROVIDERS_KEY, NOTSET ) if enabled_providers == NOTSET: enabled_providers = tuple( (provider_class for provider_class in cls.provider_classes if provider_class.enabled_for(course)) ) request_cache.data[ENABLED_OVERRIDE_PROVIDERS_KEY] = enabled_providers return enabled_providers
def _providers_for_block(cls, block): """ Computes a list of enabled providers based on the given XBlock. The result is cached per request to avoid the overhead incurred by filtering override providers hundreds of times. Arguments: block: An XBlock """ course_id = unicode(block.location.course_key) cache_key = ENABLED_MODULESTORE_OVERRIDE_PROVIDERS_KEY.format(course_id=course_id) request_cache = RequestCache.get_request_cache() enabled_providers = request_cache.data.get(cache_key) if enabled_providers is None: enabled_providers = [ provider_class for provider_class in cls.provider_classes if provider_class.enabled_for(block) ] request_cache.data[cache_key] = enabled_providers return enabled_providers
def check_team_member(user, content): """ If the content has a commentable_id, verifies that either it is not associated with a team, or if it is, that the user is a member of that team. """ if not content: return False try: commentable_id = content['commentable_id'] request_cache_dict = RequestCache.get_request_cache().data cache_key = "django_comment_client.check_team_member.{}.{}".format(user.id, commentable_id) if cache_key in request_cache_dict: return request_cache_dict[cache_key] team = get_team(commentable_id) if team is None: passes_condition = True else: passes_condition = team.users.filter(id=user.id).exists() request_cache_dict[cache_key] = passes_condition except KeyError: # We do not expect KeyError in production-- it usually indicates an improper test mock. logging.warning("Did not find key commentable_id in content.") passes_condition = False return passes_condition
def invalidate_course_mode_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of course modes. """ RequestCache.clear_request_cache(name=CourseMode.CACHE_NAMESPACE)
def create_modulestore_instance( engine, content_store, doc_store_config, options, i18n_service=None, fs_service=None, user_service=None, signal_handler=None, ): """ This will return a new instance of a modulestore given an engine and options """ class_ = load_function(engine) _options = {} _options.update(options) FUNCTION_KEYS = ['render_template'] for key in FUNCTION_KEYS: if key in _options and isinstance(_options[key], basestring): _options[key] = load_function(_options[key]) if HAS_REQUEST_CACHE: request_cache = RequestCache.get_request_cache() else: request_cache = None try: metadata_inheritance_cache = caches['mongo_metadata_inheritance'] except InvalidCacheBackendError: metadata_inheritance_cache = caches['default'] if issubclass(class_, MixedModuleStore): _options['create_modulestore_instance'] = create_modulestore_instance if issubclass(class_, BranchSettingMixin): _options['branch_setting_func'] = _get_modulestore_branch_setting if HAS_USER_SERVICE and not user_service: xb_user_service = DjangoXBlockUserService(get_current_user()) else: xb_user_service = None if 'read_preference' in doc_store_config: doc_store_config['read_preference'] = getattr( ReadPreference, doc_store_config['read_preference']) if XBlockDisableConfig and settings.FEATURES.get( 'ENABLE_DISABLING_XBLOCK_TYPES', False): disabled_xblock_types = XBlockDisableConfig.disabled_block_types() else: disabled_xblock_types = () xblock_field_data_wrappers = [ load_function(path) for path in settings.XBLOCK_FIELD_DATA_WRAPPERS ] return class_( contentstore=content_store, metadata_inheritance_cache_subsystem=metadata_inheritance_cache, request_cache=request_cache, xblock_mixins=getattr(settings, 'XBLOCK_MIXINS', ()), xblock_select=getattr(settings, 'XBLOCK_SELECT_FUNCTION', None), xblock_field_data_wrappers=xblock_field_data_wrappers, disabled_xblock_types=disabled_xblock_types, doc_store_config=doc_store_config, i18n_service=i18n_service or ModuleI18nService(), fs_service=fs_service or xblock.reference.plugins.FSService(), user_service=user_service or xb_user_service, signal_handler=signal_handler or SignalHandler(class_), **_options)
from dogapi import dog_http_api, dog_stats_api from django.conf import settings from xmodule.modulestore.django import modulestore from django.dispatch import Signal from request_cache.middleware import RequestCache from django.core.cache import get_cache CACHE = get_cache('mongo_metadata_inheritance') for store_name in settings.MODULESTORE: store = modulestore(store_name) store.set_modulestore_configuration({ 'metadata_inheritance_cache_subsystem': CACHE, 'request_cache': RequestCache.get_request_cache() }) modulestore_update_signal = Signal(providing_args=['modulestore', 'course_id', 'location']) store.modulestore_update_signal = modulestore_update_signal if hasattr(settings, 'DATADOG_API'): dog_http_api.api_key = settings.DATADOG_API dog_stats_api.start(api_key=settings.DATADOG_API, statsd=True)
def handle(self, *args, **options): if len(args) != 1: raise CommandError( "check_course requires one argument: <location>") loc_str = args[0] loc = CourseDescriptor.id_to_location(loc_str) store = modulestore() # setup a request cache so we don't throttle the DB with all the metadata inheritance requests store.set_modulestore_configuration({ 'metadata_inheritance_cache_subsystem': CACHE, 'request_cache': RequestCache.get_request_cache() }) course = store.get_item(loc, depth=3) err_cnt = 0 def _xlint_metadata(module): err_cnt = check_module_metadata_editability(module) for child in module.get_children(): err_cnt = err_cnt + _xlint_metadata(child) return err_cnt err_cnt = err_cnt + _xlint_metadata(course) # we've had a bug where the xml_attributes field can we rewritten as a string rather than a dict def _check_xml_attributes_field(module): err_cnt = 0 if hasattr(module, 'xml_attributes') and isinstance( module.xml_attributes, basestring): print 'module = {0} has xml_attributes as a string. It should be a dict'.format( module.location.url()) err_cnt = err_cnt + 1 for child in module.get_children(): err_cnt = err_cnt + _check_xml_attributes_field(child) return err_cnt err_cnt = err_cnt + _check_xml_attributes_field(course) # check for dangling discussion items, this can cause errors in the forums def _get_discussion_items(module): discussion_items = [] if module.location.category == 'discussion': discussion_items = discussion_items + [module.location.url()] for child in module.get_children(): discussion_items = discussion_items + _get_discussion_items( child) return discussion_items discussion_items = _get_discussion_items(course) # now query all discussion items via get_items() and compare with the tree-traversal queried_discussion_items = store.get_items([ 'i4x', course.location.org, course.location.course, 'discussion', None, None ]) for item in queried_discussion_items: if item.location.url() not in discussion_items: print 'Found dangling discussion module = {0}'.format( item.location.url())
def get_cohort(user, course_key, assign=True, use_cached=False): """Returns the user's cohort for the specified course. The cohort for the user is cached for the duration of a request. Pass use_cached=True to use the cached value instead of fetching from the database. Arguments: user: a Django User object. course_key: CourseKey assign (bool): if False then we don't assign a group to user use_cached (bool): Whether to use the cached value or fetch from database. Returns: A CourseUserGroup object if the course is cohorted and the User has a cohort, else None. Raises: ValueError if the CourseKey doesn't exist. """ request_cache = RequestCache.get_request_cache() cache_key = u"cohorts.get_cohort.{}.{}".format(user.id, course_key) if use_cached and cache_key in request_cache.data: return request_cache.data[cache_key] request_cache.data.pop(cache_key, None) # First check whether the course is cohorted (users shouldn't be in a cohort # in non-cohorted courses, but settings can change after course starts) course_cohort_settings = get_course_cohort_settings(course_key) if not course_cohort_settings.is_cohorted: return request_cache.data.setdefault(cache_key, None) # If course is cohorted, check if the user already has a cohort. try: membership = CohortMembership.objects.get( course_id=course_key, user_id=user.id, ) return request_cache.data.setdefault(cache_key, membership.course_user_group) except CohortMembership.DoesNotExist: # Didn't find the group. If we do not want to assign, return here. if not assign: # Do not cache the cohort here, because in the next call assign # may be True, and we will have to assign the user a cohort. return None # Otherwise assign the user a cohort. try: with transaction.atomic(): membership = CohortMembership.objects.create( user=user, course_user_group=get_random_cohort(course_key)) return request_cache.data.setdefault(cache_key, membership.course_user_group) except IntegrityError as integrity_error: # An IntegrityError is raised when multiple workers attempt to # create the same row in one of the cohort model entries: # CourseCohort, CohortMembership. log.info( "HANDLING_INTEGRITY_ERROR: IntegrityError encountered for course '%s' and user '%s': %s", course_key, user.id, unicode(integrity_error)) return get_cohort(user, course_key, assign, use_cached)
def invalidate_verified_track_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of VerifiedTrackCohortedCourse. """ RequestCache.clear_request_cache(name=VerifiedTrackCohortedCourse.CACHE_NAMESPACE)
def create_modulestore_instance( engine, content_store, doc_store_config, options, i18n_service=None, fs_service=None, user_service=None, signal_handler=None, ): """ This will return a new instance of a modulestore given an engine and options """ class_ = load_function(engine) _options = {} _options.update(options) FUNCTION_KEYS = ['render_template'] for key in FUNCTION_KEYS: if key in _options and isinstance(_options[key], basestring): _options[key] = load_function(_options[key]) if HAS_REQUEST_CACHE: request_cache = RequestCache.get_request_cache() else: request_cache = None try: metadata_inheritance_cache = caches['mongo_metadata_inheritance'] except InvalidCacheBackendError: metadata_inheritance_cache = caches['default'] if issubclass(class_, MixedModuleStore): _options['create_modulestore_instance'] = create_modulestore_instance if issubclass(class_, BranchSettingMixin): _options['branch_setting_func'] = _get_modulestore_branch_setting if HAS_USER_SERVICE and not user_service: xb_user_service = DjangoXBlockUserService(get_current_user()) else: xb_user_service = None if 'read_preference' in doc_store_config: doc_store_config['read_preference'] = getattr(ReadPreference, doc_store_config['read_preference']) xblock_field_data_wrappers = [load_function(path) for path in settings.XBLOCK_FIELD_DATA_WRAPPERS] def fetch_disabled_xblock_types(): """ Get the disabled xblock names, using the request_cache if possible to avoid hitting a database every time the list is needed. """ # If the import could not be loaded, return an empty list. if disabled_xblocks is None: return [] if request_cache: if 'disabled_xblock_types' not in request_cache.data: request_cache.data['disabled_xblock_types'] = [block.name for block in disabled_xblocks()] return request_cache.data['disabled_xblock_types'] else: disabled_xblock_types = [block.name for block in disabled_xblocks()] return disabled_xblock_types return class_( contentstore=content_store, metadata_inheritance_cache_subsystem=metadata_inheritance_cache, request_cache=request_cache, xblock_mixins=getattr(settings, 'XBLOCK_MIXINS', ()), xblock_select=getattr(settings, 'XBLOCK_SELECT_FUNCTION', None), xblock_field_data_wrappers=xblock_field_data_wrappers, disabled_xblock_types=fetch_disabled_xblock_types, doc_store_config=doc_store_config, i18n_service=i18n_service or ModuleI18nService, fs_service=fs_service or xblock.reference.plugins.FSService(), user_service=user_service or xb_user_service, signal_handler=signal_handler or SignalHandler(class_), **_options )
def setUp(self): super(TestCourseWaffleFlag, self).setUp() request = RequestFactory().request() crum.set_current_request(request) RequestCache.clear_request_cache()
def _clear_cache(self): """ Clears the requestcached values on the is_switch_enabled function. """ cache_key = func_call_cache_key(is_switch_enabled.request_cached_contained_func, self.name) RequestCache.get_request_cache().data.pop(cache_key, None)
def invalidate_credit_requirement_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of credit requirements. """ RequestCache.clear_request_cache(name=CreditRequirement.CACHE_NAMESPACE)
from dogapi import dog_http_api, dog_stats_api from django.conf import settings from xmodule.modulestore.django import modulestore from django.dispatch import Signal from request_cache.middleware import RequestCache from django.core.cache import get_cache CACHE = get_cache('mongo_metadata_inheritance') for store_name in settings.MODULESTORE: store = modulestore(store_name) store.metadata_inheritance_cache_subsystem = CACHE store.request_cache = RequestCache.get_request_cache() modulestore_update_signal = Signal( providing_args=['modulestore', 'course_id', 'location']) store.modulestore_update_signal = modulestore_update_signal if hasattr(settings, 'DATADOG_API'): dog_http_api.api_key = settings.DATADOG_API dog_stats_api.start(api_key=settings.DATADOG_API, statsd=True)
def _create_courseware_context(self): """ Returns and creates the rendering context for the courseware. Also returns the table of contents for the courseware. """ request = RequestCache.get_current_request() courseware_context = { 'csrf': csrf(self.request)['csrf_token'], 'course': self.course, 'init': '', 'fragment': Fragment(), 'staff_access': self.is_staff, 'masquerade': self.masquerade, 'supports_preview_menu': True, 'studio_url': get_studio_url(self.course, 'course'), 'xqa_server': settings.FEATURES.get('XQA_SERVER', "http://your_xqa_server.com"), 'bookmarks_api_url': reverse('bookmarks'), 'language_preference': self._get_language_preference(), 'disable_optimizely': True, 'section_title': None, 'sequence_title': None, 'disable_accordion': waffle.flag_is_active(request, UNIFIED_COURSE_VIEW_FLAG), } table_of_contents = toc_for_course( self.effective_user, self.request, self.course, self.chapter_url_name, self.section_url_name, self.field_data_cache, ) courseware_context['accordion'] = render_accordion( self.request, self.course, table_of_contents['chapters'], ) # entrance exam data self._add_entrance_exam_to_context(courseware_context) # staff masquerading data now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers( self.effective_user, self.course, self.course_key) if not in_preview_mode() and self.is_staff and now < effective_start: # Disable student view button if user is staff and # course is not yet visible to students. courseware_context['disable_student_access'] = True if self.section: # chromeless data if self.section.chrome: chrome = [ s.strip() for s in self.section.chrome.lower().split(",") ] if 'accordion' not in chrome: courseware_context['disable_accordion'] = True if 'tabs' not in chrome: courseware_context['disable_tabs'] = True # default tab if self.section.default_tab: courseware_context['default_tab'] = self.section.default_tab # section data courseware_context[ 'section_title'] = self.section.display_name_with_default section_context = self._create_section_context( table_of_contents['previous_of_active_section'], table_of_contents['next_of_active_section'], ) courseware_context['fragment'] = self.section.render( STUDENT_VIEW, section_context) if self.section.position and self.section.has_children: display_items = self.section.get_display_items() if display_items: try: courseware_context['sequence_title'] = display_items[self.section.position - 1] \ .display_name_with_default except IndexError: log.info( "Course section {} with position {} and total section display items: {}" .format( self.section.display_name_with_default, self.section.position, len(display_items), )) return courseware_context
def get_cohort(user, course_key, assign=True, use_cached=False): """Returns the user's cohort for the specified course. The cohort for the user is cached for the duration of a request. Pass use_cached=True to use the cached value instead of fetching from the database. Arguments: user: a Django User object. course_key: CourseKey assign (bool): if False then we don't assign a group to user use_cached (bool): Whether to use the cached value or fetch from database. Returns: A CourseUserGroup object if the course is cohorted and the User has a cohort, else None. Raises: ValueError if the CourseKey doesn't exist. """ request_cache = RequestCache.get_request_cache() cache_key = u"cohorts.get_cohort.{}.{}".format(user.id, course_key) if use_cached and cache_key in request_cache.data: return request_cache.data[cache_key] request_cache.data.pop(cache_key, None) # First check whether the course is cohorted (users shouldn't be in a cohort # in non-cohorted courses, but settings can change after course starts) course_cohort_settings = get_course_cohort_settings(course_key) if not course_cohort_settings.is_cohorted: return request_cache.data.setdefault(cache_key, None) # If course is cohorted, check if the user already has a cohort. try: cohort = CourseUserGroup.objects.get( course_id=course_key, group_type=CourseUserGroup.COHORT, users__id=user.id, ) return request_cache.data.setdefault(cache_key, cohort) except CourseUserGroup.DoesNotExist: # Didn't find the group. If we do not want to assign, return here. if not assign: # Do not cache the cohort here, because in the next call assign # may be True, and we will have to assign the user a cohort. return None # Otherwise assign the user a cohort. course = courses.get_course(course_key) cohorts = get_course_cohorts(course, assignment_type=CourseCohort.RANDOM) if cohorts: cohort = local_random().choice(cohorts) else: cohort = CourseCohort.create( cohort_name=DEFAULT_COHORT_NAME, course_id=course_key, assignment_type=CourseCohort.RANDOM ).course_user_group user.course_groups.add(cohort) return request_cache.data.setdefault(cache_key, cohort)
def dump_courses_to_neo4j(self, graph, override_cache=False): """ Parameters ---------- graph: py2neo graph object override_cache: serialize the courses even if they'be been recently serialized Returns two lists: one of the courses that were successfully written to neo4j, and one of courses that were not. ------- """ total_number_of_courses = len(self.course_keys) successful_courses = [] unsuccessful_courses = [] for index, course_key in enumerate(self.course_keys): # first, clear the request cache to prevent memory leaks RequestCache.clear_request_cache() log.info( "Now exporting %s to neo4j: course %d of %d total courses", course_key, index + 1, total_number_of_courses, ) if not (override_cache or self.should_dump_course(course_key)): log.info("skipping dumping %s, since it hasn't changed", course_key) continue nodes, relationships = self.serialize_course(course_key) log.info( "%d nodes and %d relationships in %s", len(nodes), len(relationships), course_key, ) transaction = graph.begin() course_string = six.text_type(course_key) try: # first, delete existing course transaction.run( "MATCH (n:item) WHERE n.course_key='{}' DETACH DELETE n". format(course_string)) # now, re-add it self.add_to_transaction(nodes, transaction) self.add_to_transaction(relationships, transaction) transaction.commit() except Exception: # pylint: disable=broad-except log.exception( "Error trying to dump course %s to neo4j, rolling back", course_string) transaction.rollback() unsuccessful_courses.append(course_string) else: COMMAND_LAST_RUN_CACHE.set(course_key) successful_courses.append(course_string) return successful_courses, unsuccessful_courses
def setUp(self): super(OverrideWaffleFlagTests, self).setUp() RequestCache.clear_request_cache()