def test_get_value_for_org_2(self): """ Test that get_value_for_org returns correct value for any given key. """ test_org = test_config['course_org_filter'] with with_site_configuration_context(configuration=test_config): # Make sure if ORG is not present in site configuration then microsite configuration is used instead self.assertEqual( configuration_helpers.get_value_for_org( "TestSiteX", "email_from_address"), "*****@*****.**") # Make sure 'default' is returned if org is present but key is not self.assertEqual( configuration_helpers.get_value_for_org( test_org, "email_from_address"), None) # Make sure if ORG is not present in site configuration then microsite configuration is used instead self.assertEqual( configuration_helpers.get_value_for_org( "LogistrationX", "email_from_address"), "*****@*****.**") # This test must come after the above test with with_site_configuration_context(configuration={ "course_org_filter": "TestSiteX", "university": "Test" }): # Make sure site configuration gets preference over microsite configuration self.assertEqual( configuration_helpers.get_value_for_org( "TestSiteX", "university"), "Test")
def test_get_value_for_org_2(self): """ Test that get_value_for_org returns correct value for any given key. """ test_org = test_config['course_org_filter'] with with_site_configuration_context(configuration=test_config): # Make sure if ORG is not present in site configuration then microsite configuration is used instead self.assertEqual( configuration_helpers.get_value_for_org("TestSiteX", "email_from_address"), "*****@*****.**" ) # Make sure 'default' is returned if org is present but key is not self.assertEqual( configuration_helpers.get_value_for_org(test_org, "email_from_address"), None ) # Make sure if ORG is not present in site configuration then microsite configuration is used instead self.assertEqual( configuration_helpers.get_value_for_org("LogistrationX", "email_from_address"), "*****@*****.**" ) # This test must come after the above test with with_site_configuration_context(configuration={"course_org_filter": "TestSiteX", "university": "Test"}): # Make sure site configuration gets preference over microsite configuration self.assertEqual( configuration_helpers.get_value_for_org("TestSiteX", "university"), "Test" )
def test_get_value_for_org_2(self): """ Test that get_value_for_org returns correct value for any given key. """ test_org = test_config['course_org_filter'] with with_site_configuration_context(configuration=test_config): # Make sure if ORG is not present in site configuration then default is used instead assert configuration_helpers.get_value_for_org( 'TestSiteX', 'email_from_address') is None # Make sure 'default' is returned if org is present but key is not assert configuration_helpers.get_value_for_org( test_org, 'email_from_address') is None
def get_course_authoring_url(course_locator): """ Gets course authoring microfrontend URL """ return configuration_helpers.get_value_for_org( course_locator.org, 'COURSE_AUTHORING_MICROFRONTEND_URL', settings.COURSE_AUTHORING_MICROFRONTEND_URL)
def get_internal_api_url_for_org(org): """ Internally-accessible API URL root, looked up by org rather than the current request. """ root = helpers.get_value_for_org(org, 'CREDENTIALS_INTERNAL_SERVICE_URL', settings.CREDENTIALS_INTERNAL_SERVICE_URL) return urljoin(root, '/api/{}/'.format(API_VERSION))
def site_prefix(org): """ Get the prefix for the site URL-- protocol and server name. """ scheme = u"https" if settings.HTTPS == "on" else u"http" return u'{}://{}'.format( scheme, configuration_helpers.get_value_for_org(org, "SITE_NAME", settings.SITE_NAME))
def get_proctored_exam_settings_url(course_module): """ Gets course authoring microfrontend URL for links to proctored exam settings page """ course_authoring_microfrontend_url = '' if settings.FEATURES.get('ENABLE_EXAM_SETTINGS_HTML_VIEW'): course_authoring_microfrontend_url = configuration_helpers.get_value_for_org( course_module.location.org, 'COURSE_AUTHORING_MICROFRONTEND_URL', settings.COURSE_AUTHORING_MICROFRONTEND_URL) return course_authoring_microfrontend_url
def get_edflex_configuration_for_org(org): client_id = configuration_helpers.get_value_for_org( org, 'EDFLEX_CLIENT_ID') client_secret = configuration_helpers.get_value_for_org( org, 'EDFLEX_CLIENT_SECRET') locale = configuration_helpers.get_value_for_org(org, 'EDFLEX_LOCALE', EDFLEX_LOCALE) base_api_url = configuration_helpers.get_value_for_org( org, 'EDFLEX_BASE_API_URL') if not (client_id and client_secret and base_api_url): configuration = get_edflex_configuration() return configuration return { 'client_id': client_id, 'client_secret': client_secret, 'locale': locale, 'base_api_url': base_api_url }
def test_get_value_for_org(self): """ Test that get_value_for_org returns correct value for any given key. """ test_org = test_config['course_org_filter'] with with_site_configuration_context(configuration=test_config): self.assertEqual( configuration_helpers.get_value_for_org(test_org, "university"), test_config['university'] ) self.assertEqual( configuration_helpers.get_value_for_org(test_org, "css_overrides_file"), test_config['css_overrides_file'] ) self.assertItemsEqual( configuration_helpers.get_value_for_org(test_org, "REGISTRATION_EXTRA_FIELDS"), test_config['REGISTRATION_EXTRA_FIELDS'] ) # Test default value of key is not present in configuration self.assertEqual( configuration_helpers.get_value_for_org(test_org, "non_existent_key"), None ) self.assertEqual( configuration_helpers.get_value_for_org(test_org, "non_existent_key", "default for non existent"), "default for non existent" ) self.assertEqual( configuration_helpers.get_value_for_org("missing_org", "university", "default for non existent"), "default for non existent" )
def test_get_value_for_org(self): """ Test that get_value_for_org returns correct value for any given key. """ test_org = test_config['course_org_filter'] with with_site_configuration_context(configuration=test_config): self.assertEqual( configuration_helpers.get_value_for_org( test_org, "university"), test_config['university']) self.assertEqual( configuration_helpers.get_value_for_org( test_org, "css_overrides_file"), test_config['css_overrides_file']) six.assertCountEqual( self, configuration_helpers.get_value_for_org( test_org, "REGISTRATION_EXTRA_FIELDS"), test_config['REGISTRATION_EXTRA_FIELDS']) # Test default value of key is not present in configuration self.assertEqual( configuration_helpers.get_value_for_org( test_org, "non_existent_key"), None) self.assertEqual( configuration_helpers.get_value_for_org( test_org, "non_existent_key", "default for non existent"), "default for non existent") self.assertEqual( configuration_helpers.get_value_for_org( "missing_org", "university", "default for non existent"), "default for non existent")
def handle_course_cert_changed(sender, user, course_key, mode, status, **kwargs): # pylint: disable=unused-argument """ If a learner is awarded a course certificate, schedule a celery task to process that course certificate Args: sender: class of the object instance that sent this signal user: django.contrib.auth.User - the user to whom a cert was awarded course_key: refers to the course run for which the cert was awarded mode: mode / certificate type, e.g. "verified" status: "downloadable" Returns: None """ # Import here instead of top of file since this module gets imported before # the credentials app is loaded, resulting in a Django deprecation warning. from openedx.core.djangoapps.credentials.models import CredentialsApiConfig # Avoid scheduling new tasks if certification is disabled. if not CredentialsApiConfig.current().is_learner_issuance_enabled: return # Avoid scheduling new tasks if learner records are disabled for this site (right now, course certs are only # used for learner records -- when that changes, we can remove this bit and always send course certs). if not helpers.get_value_for_org(course_key.org, 'ENABLE_LEARNER_RECORDS', True): return # schedule background task to process LOGGER.debug( 'handling COURSE_CERT_CHANGED: username=%s, course_key=%s, mode=%s, status=%s', user, course_key, mode, status, ) # import here, because signal is registered at startup, but items in tasks are not yet able to be loaded from openedx.core.djangoapps.programs.tasks.v1.tasks import award_course_certificate award_course_certificate.delay(user.username, str(course_key))
def send_grade_if_interesting(user, course_run_key, mode, status, letter_grade, percent_grade): """ Checks if grade is interesting to Credentials and schedules a Celery task if so. """ # Avoid scheduling new tasks if certification is disabled. (Grades are a part of the records/cert story) if not CredentialsApiConfig.current().is_learner_issuance_enabled: return # Avoid scheduling new tasks if learner records are disabled for this site. if not helpers.get_value_for_org(course_run_key.org, 'ENABLE_LEARNER_RECORDS', True): return # Grab mode/status if we don't have them in hand if mode is None or status is None: try: cert = GeneratedCertificate.objects.get(user=user, course_id=course_run_key) # pylint: disable=no-member mode = cert.mode status = cert.status except GeneratedCertificate.DoesNotExist: # We only care about grades for which there is a certificate. return # Don't worry about whether it's available as well as awarded. Just awarded is good enough to record a verified # attempt at a course. We want even the grades that didn't pass the class because Credentials wants to know about # those too. if mode not in INTERESTING_MODES or status not in INTERESTING_STATUSES: return # If the course isn't in any program, don't bother telling Credentials about it. When Credentials grows support # for course records as well as program records, we'll need to open this up. if not is_course_run_in_a_program(course_run_key): return # Grab grades if we don't have them in hand if letter_grade is None or percent_grade is None: grade = CourseGradeFactory().read(user, course_key=course_run_key, create_if_needed=False) if grade is None: return letter_grade = grade.letter_grade percent_grade = grade.percent send_grade_to_credentials.delay(user.username, str(course_run_key), True, letter_grade, percent_grade)
def test_get_value_for_org(self): """ Test that get_value_for_org returns correct value for any given key. """ test_org = test_config['course_org_filter'] with with_site_configuration_context(configuration=test_config): assert configuration_helpers.get_value_for_org( test_org, 'university') == test_config['university'] assert configuration_helpers.get_value_for_org(test_org, 'css_overrides_file') ==\ test_config['css_overrides_file'] self.assertCountEqual( configuration_helpers.get_value_for_org( test_org, "REGISTRATION_EXTRA_FIELDS"), test_config['REGISTRATION_EXTRA_FIELDS']) # Test default value of key is not present in configuration assert configuration_helpers.get_value_for_org( test_org, 'non_existent_key') is None assert configuration_helpers.get_value_for_org(test_org, 'non_existent_key', 'default for non existent') ==\ 'default for non existent' assert configuration_helpers.get_value_for_org('missing_org', 'university', 'default for non existent') ==\ 'default for non existent'
def send_grade_if_interesting(user, course_run_key, mode, status, letter_grade, percent_grade, verbose=False): """ Checks if grade is interesting to Credentials and schedules a Celery task if so. """ if verbose: msg = u"Starting send_grade_if_interesting with params: "\ "user [{username}], "\ "course_run_key [{key}], "\ "mode [{mode}], "\ "status [{status}], "\ "letter_grade [{letter_grade}], "\ "percent_grade [{percent_grade}], "\ "verbose [{verbose}]"\ .format( username=getattr(user, 'username', None), key=str(course_run_key), mode=mode, status=status, letter_grade=letter_grade, percent_grade=percent_grade, verbose=verbose ) log.info(msg) # Avoid scheduling new tasks if certification is disabled. (Grades are a part of the records/cert story) if not CredentialsApiConfig.current().is_learner_issuance_enabled: if verbose: log.info("Skipping send grade: is_learner_issuance_enabled False") return # Avoid scheduling new tasks if learner records are disabled for this site. if not helpers.get_value_for_org(course_run_key.org, 'ENABLE_LEARNER_RECORDS', True): if verbose: log.info( u"Skipping send grade: ENABLE_LEARNER_RECORDS False for org [{org}]" .format(org=course_run_key.org)) return # Grab mode/status if we don't have them in hand if mode is None or status is None: try: cert = GeneratedCertificate.objects.get(user=user, course_id=course_run_key) # pylint: disable=no-member mode = cert.mode status = cert.status except GeneratedCertificate.DoesNotExist: # We only care about grades for which there is a certificate. if verbose: log.info( u"Skipping send grade: no cert for user [{username}] & course_id [{course_id}]" .format(username=getattr(user, 'username', None), course_id=str(course_run_key))) return # Don't worry about whether it's available as well as awarded. Just awarded is good enough to record a verified # attempt at a course. We want even the grades that didn't pass the class because Credentials wants to know about # those too. if mode not in INTERESTING_MODES or status not in INTERESTING_STATUSES: if verbose: log.info( u"Skipping send grade: mode/status uninteresting for mode [{mode}] & status [{status}]" .format(mode=mode, status=status)) return # If the course isn't in any program, don't bother telling Credentials about it. When Credentials grows support # for course records as well as program records, we'll need to open this up. if not is_course_run_in_a_program(course_run_key): if verbose: log.info( u"Skipping send grade: course run not in a program. [{course_id}]" .format(course_id=str(course_run_key))) return # Grab grades if we don't have them in hand if letter_grade is None or percent_grade is None: grade = CourseGradeFactory().read(user, course_key=course_run_key, create_if_needed=False) if grade is None: if verbose: log.info( u"Skipping send grade: No grade found for user [{username}] & course_id [{course_id}]" .format(username=getattr(user, 'username', None), course_id=str(course_run_key))) return letter_grade = grade.letter_grade percent_grade = grade.percent send_grade_to_credentials.delay(user.username, str(course_run_key), True, letter_grade, percent_grade)
def enrolled_students_features(course_key, features): """ Return list of student features as dictionaries. enrolled_students_features(course_key, ['username', 'first_name']) would return [ {'username': '******', 'first_name': 'firstname1'} {'username': '******', 'first_name': 'firstname2'} {'username': '******', 'first_name': 'firstname3'} ] """ include_cohort_column = 'cohort' in features include_team_column = 'team' in features include_enrollment_mode = 'enrollment_mode' in features include_verification_status = 'verification_status' in features students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1, ).order_by('username').select_related('profile') if include_cohort_column: students = students.prefetch_related('course_groups') if include_team_column: students = students.prefetch_related('teams') STUDENT_FEATURES_WITH_CUSTOM = STUDENT_FEATURES + tuple( configuration_helpers.get_value_for_org( course_key.org, 'student_profile_download_fields_custom_student_attributes', getattr( settings, "STUDENT_PROFILE_DOWNLOAD_FIELDS_CUSTOM_STUDENT_ATTRIBUTES", ()))) def extract_attr(student, feature): """Evaluate a student attribute that is ready for JSON serialization""" attr = getattr(student, feature) try: DjangoJSONEncoder().default(attr) return attr except TypeError: return six.text_type(attr) def extract_student(student, features): """ convert student to dictionary """ student_features = [ x for x in STUDENT_FEATURES_WITH_CUSTOM if x in features ] profile_features = [x for x in PROFILE_FEATURES if x in features] # For data extractions on the 'meta' field # the feature name should be in the format of 'meta.foo' where # 'foo' is the keyname in the meta dictionary meta_features = [] for feature in features: if 'meta.' in feature: meta_key = feature.split('.')[1] meta_features.append((feature, meta_key)) student_dict = dict((feature, extract_attr(student, feature)) for feature in student_features) profile = student.profile if profile is not None: profile_dict = dict((feature, extract_attr(profile, feature)) for feature in profile_features) student_dict.update(profile_dict) # now fetch the requested meta fields meta_dict = json.loads(profile.meta) if profile.meta else {} for meta_feature, meta_key in meta_features: student_dict[meta_feature] = meta_dict.get(meta_key) if include_cohort_column: # Note that we use student.course_groups.all() here instead of # student.course_groups.filter(). The latter creates a fresh query, # therefore negating the performance gain from prefetch_related(). student_dict['cohort'] = next( (cohort.name for cohort in student.course_groups.all() if cohort.course_id == course_key), "[unassigned]") if include_team_column: student_dict['team'] = next( (team.name for team in student.teams.all() if team.course_id == course_key), UNAVAILABLE) if include_enrollment_mode or include_verification_status: enrollment_mode = CourseEnrollment.enrollment_mode_for_user( student, course_key)[0] if include_verification_status: student_dict[ 'verification_status'] = IDVerificationService.verification_status_for_user( student, enrollment_mode) if include_enrollment_mode: student_dict['enrollment_mode'] = enrollment_mode return student_dict return [extract_student(student, features) for student in students]
def send_grade_if_interesting(user, course_run_key, mode, status, letter_grade, percent_grade, verbose=False): """ Checks if grade is interesting to Credentials and schedules a Celery task if so. """ if verbose: msg = u"Starting send_grade_if_interesting with params: "\ u"user [{username}], "\ u"course_run_key [{key}], "\ u"mode [{mode}], "\ u"status [{status}], "\ u"letter_grade [{letter_grade}], "\ u"percent_grade [{percent_grade}], "\ u"verbose [{verbose}]"\ .format( username=getattr(user, 'username', None), key=str(course_run_key), mode=mode, status=status, letter_grade=letter_grade, percent_grade=percent_grade, verbose=verbose ) log.info(msg) # Avoid scheduling new tasks if certification is disabled. (Grades are a part of the records/cert story) if not CredentialsApiConfig.current().is_learner_issuance_enabled: if verbose: log.info("Skipping send grade: is_learner_issuance_enabled False") return # Avoid scheduling new tasks if learner records are disabled for this site. if not helpers.get_value_for_org(course_run_key.org, 'ENABLE_LEARNER_RECORDS', True): if verbose: log.info( u"Skipping send grade: ENABLE_LEARNER_RECORDS False for org [{org}]".format( org=course_run_key.org ) ) return # Grab mode/status if we don't have them in hand if mode is None or status is None: try: cert = GeneratedCertificate.objects.get(user=user, course_id=course_run_key) # pylint: disable=no-member mode = cert.mode status = cert.status except GeneratedCertificate.DoesNotExist: # We only care about grades for which there is a certificate. if verbose: log.info( u"Skipping send grade: no cert for user [{username}] & course_id [{course_id}]".format( username=getattr(user, 'username', None), course_id=str(course_run_key) ) ) return # Don't worry about whether it's available as well as awarded. Just awarded is good enough to record a verified # attempt at a course. We want even the grades that didn't pass the class because Credentials wants to know about # those too. if mode not in INTERESTING_MODES or status not in INTERESTING_STATUSES: if verbose: log.info( u"Skipping send grade: mode/status uninteresting for mode [{mode}] & status [{status}]".format( mode=mode, status=status ) ) return # If the course isn't in any program, don't bother telling Credentials about it. When Credentials grows support # for course records as well as program records, we'll need to open this up. if not is_course_run_in_a_program(course_run_key): if verbose: log.info( u"Skipping send grade: course run not in a program. [{course_id}]".format(course_id=str(course_run_key)) ) return # Grab grades if we don't have them in hand if letter_grade is None or percent_grade is None: grade = CourseGradeFactory().read(user, course_key=course_run_key, create_if_needed=False) if grade is None: if verbose: log.info( u"Skipping send grade: No grade found for user [{username}] & course_id [{course_id}]".format( username=getattr(user, 'username', None), course_id=str(course_run_key) ) ) return letter_grade = grade.letter_grade percent_grade = grade.percent send_grade_to_credentials.delay(user.username, str(course_run_key), True, letter_grade, percent_grade)
def is_learner_records_enabled_for_org(org): return config_helpers.get_value_for_org( org, "ENABLE_LEARNER_RECORDS", ENABLE_LEARNER_RECORDS.is_enabled())
def handle_course_cert_changed(sender, user, course_key, mode, status, **kwargs): """ If a learner is awarded a course certificate, schedule a celery task to process that course certificate Args: sender: class of the object instance that sent this signal user: django.contrib.auth.User - the user to whom a cert was awarded course_key: refers to the course run for which the cert was awarded mode: mode / certificate type, e.g. "verified" status: "downloadable" Returns: None """ # Import here instead of top of file since this module gets imported before # the credentials app is loaded, resulting in a Django deprecation warning. from openedx.core.djangoapps.credentials.models import CredentialsApiConfig verbose = kwargs.get('verbose', False) if verbose: msg = u"Starting handle_course_cert_changed with params: "\ u"sender [{sender}], "\ u"user [{username}], "\ u"course_key [{course_key}], "\ u"mode [{mode}], "\ u"status [{status}], "\ u"kwargs [{kw}]"\ .format( sender=sender, username=getattr(user, 'username', None), course_key=str(course_key), mode=mode, status=status, kw=kwargs ) LOGGER.info(msg) # Avoid scheduling new tasks if certification is disabled. if not CredentialsApiConfig.current().is_learner_issuance_enabled: if verbose: LOGGER.info( "Skipping send cert: is_learner_issuance_enabled False") return # Avoid scheduling new tasks if learner records are disabled for this site (right now, course certs are only # used for learner records -- when that changes, we can remove this bit and always send course certs). if not helpers.get_value_for_org(course_key.org, 'ENABLE_LEARNER_RECORDS', True): if verbose: LOGGER.info( u"Skipping send cert: ENABLE_LEARNER_RECORDS False for org [{org}]" .format(org=course_key.org)) return # schedule background task to process LOGGER.debug( u'handling COURSE_CERT_CHANGED: username=%s, course_key=%s, mode=%s, status=%s', user, course_key, mode, status, ) # import here, because signal is registered at startup, but items in tasks are not yet able to be loaded from openedx.core.djangoapps.programs.tasks.v1.tasks import award_course_certificate award_course_certificate.delay(user.username, str(course_key))
def handle_course_cert_changed(sender, user, course_key, mode, status, **kwargs): """ If a learner is awarded a course certificate, schedule a celery task to process that course certificate Args: sender: class of the object instance that sent this signal user: django.contrib.auth.User - the user to whom a cert was awarded course_key: refers to the course run for which the cert was awarded mode: mode / certificate type, e.g. "verified" status: "downloadable" Returns: None """ # Import here instead of top of file since this module gets imported before # the credentials app is loaded, resulting in a Django deprecation warning. from openedx.core.djangoapps.credentials.models import CredentialsApiConfig verbose = kwargs.get('verbose', False) if verbose: msg = "Starting handle_course_cert_changed with params: "\ "sender [{sender}], "\ "user [{username}], "\ "course_key [{course_key}], "\ "mode [{mode}], "\ "status [{status}], "\ "kwargs [{kw}]"\ .format( sender=sender, username=getattr(user, 'username', None), course_key=str(course_key), mode=mode, status=status, kw=kwargs ) LOGGER.info(msg) # Avoid scheduling new tasks if certification is disabled. if not CredentialsApiConfig.current().is_learner_issuance_enabled: if verbose: LOGGER.info("Skipping send cert: is_learner_issuance_enabled False") return # Avoid scheduling new tasks if learner records are disabled for this site (right now, course certs are only # used for learner records -- when that changes, we can remove this bit and always send course certs). if not helpers.get_value_for_org(course_key.org, 'ENABLE_LEARNER_RECORDS', True): if verbose: LOGGER.info( "Skipping send cert: ENABLE_LEARNER_RECORDS False for org [{org}]".format( org=course_key.org ) ) return # schedule background task to process LOGGER.debug( 'handling COURSE_CERT_CHANGED: username=%s, course_key=%s, mode=%s, status=%s', user, course_key, mode, status, ) # import here, because signal is registered at startup, but items in tasks are not yet able to be loaded from openedx.core.djangoapps.programs.tasks.v1.tasks import award_course_certificate award_course_certificate.delay(user.username, str(course_key))