def is_mode_upsellable(user, enrollment, course=None): """ Return whether the user is enrolled in a mode that can be upselled to another mode, usually audit upselled to verified. The partition code allows this function to more accurately return results for masquerading users. Arguments: user (:class:`.AuthUser`): The user from the request.user property enrollment (:class:`.CourseEnrollment`): The enrollment under consideration. course (:class:`.ModulestoreCourse`): Optional passed in modulestore course. If provided, it is expected to correspond to `enrollment.course.id`. If not provided, the course will be loaded from the modulestore. We use the course to retrieve user partitions when calculating whether the upgrade link will be shown. """ partition_service = PartitionService(enrollment.course.id, course=course) enrollment_track_partition = partition_service.get_user_partition( ENROLLMENT_TRACK_PARTITION_ID) group = partition_service.get_group(user, enrollment_track_partition) current_mode = None if group: try: current_mode = [ mode.get('slug') for mode in settings.COURSE_ENROLLMENT_MODES.values() if mode['id'] == group.id ].pop() except IndexError: pass upsellable_mode = not current_mode or current_mode in CourseMode.UPSELL_TO_VERIFIED_MODES return upsellable_mode
def __init__(self, **kwargs): request_cache_dict = DEFAULT_REQUEST_CACHE.data store = modulestore() services = kwargs.setdefault('services', {}) user = kwargs.get('user') if user and user.is_authenticated: services['completion'] = CompletionService( user=user, context_key=kwargs.get('course_id')) services['fs'] = xblock.reference.plugins.FSService() services['i18n'] = ModuleI18nService services['library_tools'] = LibraryToolsService( store, user_id=user.id if user else None) 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) services['teams'] = TeamsService() services['teams_configuration'] = TeamsConfigurationService() services['call_to_action'] = CallToActionService() super(LmsModuleSystem, self).__init__(**kwargs)
def _get_enrollment_track_groups(course_key): """ Helper method that returns an array of the Groups in the EnrollmentTrackUserPartition for the given course. If no such partition exists on the course, an empty array is returned. """ partition_service = PartitionService(course_key) partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID) return partition.groups if partition else []
def _user_experiment_group_names(self, user, context): """ Returns a list of names of course experiments in which the given user belongs. """ experiment_group_names = [] for partition in context.course_experiments: group = PartitionService(context.course_id).get_group(user, partition, assign=False) experiment_group_names.append(group.name if group else '') return experiment_group_names
def get_group_id_by_course_mode_name(self, course_id, mode_name): """ Get the needed group_id from the Enrollment_Track partition for the specific masquerading track. """ partition_service = PartitionService(course_id) enrollment_track_user_partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID) for group in enrollment_track_user_partition.groups: if group.name == mode_name: return group.id return None
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'] = PartitionService( course_id=kwargs.get('course_id'), cache=request_cache_dict) store = modulestore() 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 get_group_id_for_user(user, course_discussion_settings): """ Given a user, return the group_id for that user according to the course_discussion_settings. If discussions are not divided, this method will return None. It will also return None if the user is in no group within the specified division_scheme. """ division_scheme = _get_course_division_scheme(course_discussion_settings) if division_scheme == CourseDiscussionSettings.COHORT: return get_cohort_id(user, course_discussion_settings.course_id) elif division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK: partition_service = PartitionService(course_discussion_settings.course_id) group_id = partition_service.get_user_group_id_for_partition(user, ENROLLMENT_TRACK_PARTITION_ID) # We negate the group_ids from dynamic partitions so that they will not conflict # with cohort IDs (which are an auto-incrementing integer field, starting at 1). return -1 * group_id if group_id is not None else None else: return None
def can_show_verified_upgrade(user, enrollment, course=None): """ Return whether this user can be shown upgrade message. Arguments: user (:class:`.AuthUser`): The user from the request.user property enrollment (:class:`.CourseEnrollment`): The enrollment under consideration. If None, then the enrollment is considered to be upgradeable. course (:class:`.ModulestoreCourse`): Optional passed in modulestore course. If provided, it is expected to correspond to `enrollment.course.id`. If not provided, the course will be loaded from the modulestore. We use the course to retrieve user partitions when calculating whether the upgrade link will be shown. """ # Return `true` if user is not enrolled in course if enrollment is None: return False partition_service = PartitionService(enrollment.course.id, course=course) enrollment_track_partition = partition_service.get_user_partition( ENROLLMENT_TRACK_PARTITION_ID) group = partition_service.get_group(user, enrollment_track_partition) current_mode = None if group: try: current_mode = [ mode.get('slug') for mode in settings.COURSE_ENROLLMENT_MODES.values() if mode['id'] == group.id ].pop() except IndexError: pass upgradable_mode = not current_mode or current_mode in CourseMode.UPSELL_TO_VERIFIED_MODES if not upgradable_mode: return False upgrade_deadline = enrollment.upgrade_deadline if upgrade_deadline is None: return False if datetime.datetime.now(utc).date() > upgrade_deadline.date(): return False # Show the summary if user enrollment is in which allow user to upsell return enrollment.is_active and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES
def can_show_verified_upgrade(user, course_id, enrollment): """ Check if we are able to show verified upgrade message based on the enrollment and current user partition """ if not enrollment: return False partition_service = PartitionService(course_id) enrollment_track_partition = partition_service.get_user_partition( ENROLLMENT_TRACK_PARTITION_ID) group = partition_service.get_group(user, enrollment_track_partition) current_mode = None if group: try: current_mode = [ mode.get('slug') for mode in settings.COURSE_ENROLLMENT_MODES.values() if mode['id'] == group.id ].pop() except IndexError: pass upgradable_mode = not current_mode or current_mode in CourseMode.UPSELL_TO_VERIFIED_MODES return upgradable_mode and verified_upgrade_link_is_valid(enrollment)
def handle(self, *args, **options): errors = [] module_store = modulestore() print "Starting Swap from Auto Track Cohort Pilot command" verified_track_cohorts_setting = self._latest_settings() if not verified_track_cohorts_setting: raise CommandError("No MigrateVerifiedTrackCohortsSetting found") if not verified_track_cohorts_setting.enabled: raise CommandError("No enabled MigrateVerifiedTrackCohortsSetting found") old_course_key = verified_track_cohorts_setting.old_course_key rerun_course_key = verified_track_cohorts_setting.rerun_course_key audit_cohort_names = verified_track_cohorts_setting.get_audit_cohort_names() # Verify that the MigrateVerifiedTrackCohortsSetting has all required fields if not old_course_key: raise CommandError("No old_course_key set for MigrateVerifiedTrackCohortsSetting with ID: '%s'" % verified_track_cohorts_setting.id) if not rerun_course_key: raise CommandError("No rerun_course_key set for MigrateVerifiedTrackCohortsSetting with ID: '%s'" % verified_track_cohorts_setting.id) if not audit_cohort_names: raise CommandError("No audit_cohort_names set for MigrateVerifiedTrackCohortsSetting with ID: '%s'" % verified_track_cohorts_setting.id) print "Running for MigrateVerifiedTrackCohortsSetting with old_course_key='%s' and rerun_course_key='%s'" % \ (verified_track_cohorts_setting.old_course_key, verified_track_cohorts_setting.rerun_course_key) # Get the CourseUserGroup IDs for the audit course names from the old course audit_course_user_group_ids = CourseUserGroup.objects.filter( name__in=audit_cohort_names, course_id=old_course_key, group_type=CourseUserGroup.COHORT, ).values_list('id', flat=True) if not audit_course_user_group_ids: raise CommandError( "No Audit CourseUserGroup found for course_id='%s' with group_type='%s' for names='%s'" % (old_course_key, CourseUserGroup.COHORT, audit_cohort_names) ) # Get all of the audit CourseCohorts from the above IDs that are RANDOM random_audit_course_user_group_ids = CourseCohort.objects.filter( course_user_group_id__in=audit_course_user_group_ids, assignment_type=CourseCohort.RANDOM ).values_list('course_user_group_id', flat=True) if not random_audit_course_user_group_ids: raise CommandError( "No Audit CourseCohorts found for course_user_group_ids='%s' with assignment_type='%s" % (audit_course_user_group_ids, CourseCohort.RANDOM) ) # Get the CourseUserGroupPartitionGroup for the above IDs, these contain the partition IDs and group IDs # that are set for group_access inside of modulestore random_audit_course_user_group_partition_groups = list(CourseUserGroupPartitionGroup.objects.filter( course_user_group_id__in=random_audit_course_user_group_ids )) if not random_audit_course_user_group_partition_groups: raise CommandError( "No Audit CourseUserGroupPartitionGroup found for course_user_group_ids='%s'" % random_audit_course_user_group_ids ) # Get the single VerifiedTrackCohortedCourse for the old course try: verified_track_cohorted_course = VerifiedTrackCohortedCourse.objects.get(course_key=old_course_key) except VerifiedTrackCohortedCourse.DoesNotExist: raise CommandError("No VerifiedTrackCohortedCourse found for course: '%s'" % old_course_key) if not verified_track_cohorted_course.enabled: raise CommandError("VerifiedTrackCohortedCourse not enabled for course: '%s'" % old_course_key) # Get the single CourseUserGroupPartitionGroup for the verified_track # based on the verified_track name for the old course try: verified_course_user_group = CourseUserGroup.objects.get( course_id=old_course_key, group_type=CourseUserGroup.COHORT, name=verified_track_cohorted_course.verified_cohort_name ) except CourseUserGroup.DoesNotExist: raise CommandError( "No Verified CourseUserGroup found for course_id='%s' with group_type='%s' for names='%s'" % (old_course_key, CourseUserGroup.COHORT, verified_track_cohorted_course.verified_cohort_name) ) try: verified_course_user_group_partition_group = CourseUserGroupPartitionGroup.objects.get( course_user_group_id=verified_course_user_group.id ) except CourseUserGroupPartitionGroup.DoesNotExist: raise CommandError( "No Verified CourseUserGroupPartitionGroup found for course_user_group_ids='%s'" % random_audit_course_user_group_ids ) # Verify the enrollment track CourseModes exist for the new course try: CourseMode.objects.get( course_id=rerun_course_key, mode_slug=CourseMode.AUDIT ) except CourseMode.DoesNotExist: raise CommandError("Audit CourseMode is not defined for course: '%s'" % rerun_course_key) try: CourseMode.objects.get( course_id=rerun_course_key, mode_slug=CourseMode.VERIFIED ) except CourseMode.DoesNotExist: raise CommandError("Verified CourseMode is not defined for course: '%s'" % rerun_course_key) items = module_store.get_items(rerun_course_key) if not items: raise CommandError("Items for Course with key '%s' not found." % rerun_course_key) items_to_update = [] all_cohorted_track_group_ids = set() for audit_course_user_group_partition_group in random_audit_course_user_group_partition_groups: all_cohorted_track_group_ids.add(audit_course_user_group_partition_group.group_id) all_cohorted_track_group_ids.add(verified_course_user_group_partition_group.group_id) for item in items: # Verify that there exists group access for this xblock, otherwise skip these checks if item.group_access: set_audit_enrollment_track = False set_verified_enrollment_track = False # Check the partition and group IDs for the audit course groups, if they exist in # the xblock's access settings then set the audit track flag to true for audit_course_user_group_partition_group in random_audit_course_user_group_partition_groups: audit_partition_group_access = item.group_access.get( audit_course_user_group_partition_group.partition_id, None ) if (audit_partition_group_access and audit_course_user_group_partition_group.group_id in audit_partition_group_access): print "Queueing XBlock at location: '%s' for Audit Content Group update " % item.location set_audit_enrollment_track = True # Check the partition and group IDs for the verified course group, if it exists in # the xblock's access settings then set the verified track flag to true verified_partition_group_access = item.group_access.get( verified_course_user_group_partition_group.partition_id, None ) if verified_partition_group_access: non_verified_track_access_groups = (set(verified_partition_group_access) - all_cohorted_track_group_ids) # If the item has group_access that is not the # verified or audit group IDs then raise an error # This only needs to be checked for this partition_group once if non_verified_track_access_groups: errors.append( "Non audit/verified cohorted content group set for xblock, location '%s' with IDs '%s'" % (item.location, non_verified_track_access_groups) ) if verified_course_user_group_partition_group.group_id in verified_partition_group_access: print "Queueing XBlock at location: '%s' for Verified Content Group update " % item.location set_verified_enrollment_track = True # Add the enrollment track ids to a group access array enrollment_track_group_access = [] if set_audit_enrollment_track: enrollment_track_group_access.append(settings.COURSE_ENROLLMENT_MODES['audit']) if set_verified_enrollment_track: enrollment_track_group_access.append(settings.COURSE_ENROLLMENT_MODES['verified']) # If there are no errors, and either the audit track, or verified # track needed an update, set the access, update and publish if set_verified_enrollment_track or set_audit_enrollment_track: # Sets whether or not an xblock has changes has_changes = module_store.has_changes(item) # Check that the xblock does not have changes and add it to be updated, otherwise add an error if not has_changes: item.group_access = {ENROLLMENT_TRACK_PARTITION_ID: enrollment_track_group_access} items_to_update.append(item) else: errors.append("XBlock '%s' with location '%s' needs access changes, but is a draft" % (item.display_name, item.location)) partitions_to_delete = random_audit_course_user_group_partition_groups partitions_to_delete.append(verified_course_user_group_partition_group) # If there are no errors iterate over and update all of the items that had the access changed if not errors: for item in items_to_update: module_store.update_item(item, ModuleStoreEnum.UserID.mgmt_command) module_store.publish(item.location, ModuleStoreEnum.UserID.mgmt_command) print "Updated and published XBlock at location: '%s'" % item.location # Check if we should delete any partition groups if there are no errors. # If there are errors, none of the xblock items will have been updated, # so this section will throw errors for each partition in use if partitions_to_delete and not errors: partition_service = PartitionService(rerun_course_key) course = partition_service.get_course() for partition_to_delete in partitions_to_delete: # Get the user partition, and the index of that partition in the course partition = partition_service.get_user_partition(partition_to_delete.partition_id) if partition: partition_index = course.user_partitions.index(partition) group_id = int(partition_to_delete.group_id) # Sanity check to verify that all of the groups being deleted are empty, # since they should have been converted to use enrollment tracks instead. # Taken from contentstore/views/course.py.remove_content_or_experiment_group usages = GroupConfiguration.get_partitions_usage_info(module_store, course) used = group_id in usages if used: errors.append("Content group '%s' is in use and cannot be deleted." % partition_to_delete.group_id) # If there are not errors, proceed to update the course and user_partitions if not errors: # Remove the groups that match the group ID of the partition to be deleted # Else if there are no match groups left, remove the user partition matching_groups = [group for group in partition.groups if group.id == group_id] if matching_groups: group_index = partition.groups.index(matching_groups[0]) partition.groups.pop(group_index) # Update the course user partition with the updated groups if partition.groups: course.user_partitions[partition_index] = partition else: course.user_partitions.pop(partition_index) module_store.update_item(course, ModuleStoreEnum.UserID.mgmt_command) # If there are any errors, join them together and raise the CommandError if errors: raise CommandError( ("Error for MigrateVerifiedTrackCohortsSetting with ID='%s'\n" % verified_track_cohorts_setting.id) + "\t\n".join(errors) ) print "Finished for MigrateVerifiedTrackCohortsSetting with ID='%s" % verified_track_cohorts_setting.id
def upload_user_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name): # pylint: disable=too-many-statements """ For a given `course_id`, for given usernames generates a grades CSV file, and store using a `ReportStore`. Once created, the files can be accessed by instantiating another `ReportStore` (via `ReportStore.from_config()`) and calling `link_for()` on it. Unenrolled users and unknown usernames are stored in *_err_*.csv This task is very close to the .upload_grades_csv from instructor_tasks.task_helper The difference is that we filter enrolled students against requested usernames and we push info about this into PLP """ start_time = time() start_date = datetime.now(UTC) status_interval = 100 fmt = u'Task: {task_id}, InstructorTask ID: {entry_id}, Course: {course_id}, Input: {task_input}' task_info_string = fmt.format( task_id=_xmodule_instance_args.get('task_id') if _xmodule_instance_args is not None else None, entry_id=_entry_id, course_id=course_id, task_input=_task_input ) TASK_LOG.info(u'%s, Task type: %s, Starting task execution', task_info_string, action_name) extended_kwargs_id = _task_input.get("extended_kwargs_id") extended_kwargs = InstructorTaskExtendedKwargs.get_kwargs_for_id(extended_kwargs_id) usernames = extended_kwargs.get("usernames", None) err_rows = [["id", "username", "error_msg"]] if usernames is None: message = "Error occured during edx task execution: no usersnames in InstructorTaskExtendedKwargs." TASK_LOG.error(u'%s, Task type: %s, ' + message, task_info_string) err_rows.append(["-1", "__", message]) usernames = [] enrolled_students = CourseEnrollment.objects.users_enrolled_in(course_id) enrolled_students = enrolled_students.filter(username__in=usernames) total_enrolled_students = enrolled_students.count() requester_id = _task_input.get("requester_id") task_progress = TaskProgress(action_name, total_enrolled_students, start_time) course = get_course_by_id(course_id) course_is_cohorted = is_course_cohorted(course.id) teams_enabled = course.teams_enabled cohorts_header = ['Cohort Name'] if course_is_cohorted else [] teams_header = ['Team Name'] if teams_enabled else [] experiment_partitions = get_split_user_partitions(course.user_partitions) group_configs_header = [u'Experiment Group ({})'.format(partition.name) for partition in experiment_partitions] certificate_info_header = ['Certificate Eligible', 'Certificate Delivered', 'Certificate Type'] certificate_whitelist = CertificateWhitelist.objects.filter(course_id=course_id, whitelist=True) whitelisted_user_ids = [entry.user_id for entry in certificate_whitelist] # Loop over all our students and build our CSV lists in memory rows = [] current_step = {'step': 'Calculating Grades'} TASK_LOG.info( u'%s, Task type: %s, Current step: %s, Starting grade calculation for total students: %s', task_info_string, action_name, current_step, total_enrolled_students, ) found_students = User.objects.filter(username__in=usernames) # Check invalid usernames if len(found_students)!= len(usernames): found_students_usernames = [x.username for x in found_students] for u in usernames: if u not in found_students_usernames: err_rows.append([-1, u, "invalid_username"]) # Check not enrolled requested students if found_students != enrolled_students: diff = found_students.exclude(id__in=enrolled_students) for u in diff: if u in diff: err_rows.append([u.id, u.username, "enrollment_for_username_not_found"]) total_enrolled_students = enrolled_students.count() student_counter = 0 TASK_LOG.info( u'%s, Task type: %s, Current step: %s, Starting grade calculation for total students: %s', task_info_string, action_name, current_step, total_enrolled_students ) graded_assignments = course.grading.graded_assignments(course_id) grade_header = course.grading.grade_header(graded_assignments) rows.append( ["Student ID", "Email", "Username", "Last Name", "First Name", "Second Name", "Grade", "Grade Percent"] + grade_header + cohorts_header + group_configs_header + teams_header + ['Enrollment Track', 'Verification Status'] + certificate_info_header ) for student, course_grade, err_msg in CourseGradeFactory().iter(course, enrolled_students): # Periodically update task status (this is a cache write) if task_progress.attempted % status_interval == 0: task_progress.update_task_state(extra_meta=current_step) task_progress.attempted += 1 # Now add a log entry after each student is graded to get a sense # of the task's progress student_counter += 1 TASK_LOG.info( u'%s, Task type: %s, Current step: %s, Grade calculation in-progress for students: %s/%s', task_info_string, action_name, current_step, student_counter, total_enrolled_students ) if not course_grade: # An empty course_grade means we failed to grade a student. task_progress.failed += 1 err_rows.append([student.id, student.username, err_msg]) continue # We were able to successfully grade this student for this course. task_progress.succeeded += 1 cohorts_group_name = [] if course_is_cohorted: group = get_cohort(student, course_id, assign=False) cohorts_group_name.append(group.name if group else '') group_configs_group_names = [] for partition in experiment_partitions: group = PartitionService(course_id).get_group(student, partition, assign=False) group_configs_group_names.append(group.name if group else '') team_name = [] if teams_enabled: try: membership = CourseTeamMembership.objects.get(user=student, team__course_id=course_id) team_name.append(membership.team.name) except CourseTeamMembership.DoesNotExist: team_name.append('') enrollment_mode = CourseEnrollment.enrollment_mode_for_user(student, course_id)[0] verification_status = SoftwareSecurePhotoVerification.verification_status_for_user( student, course_id, enrollment_mode ) certificate_info = certificate_info_for_user( student, course_id, course_grade.letter_grade, student.id in whitelisted_user_ids ) second_name = '' try: up = UserProfile.objects.get(user=student) if up.goals: second_name = json.loads(up.goals).get('second_name', '') except ValueError: pass if certificate_info[0] == 'Y': TASK_LOG.info( u'Student is marked eligible_for_certificate' u'(user=%s, course_id=%s, grade_percent=%s gradecutoffs=%s, allow_certificate=%s, is_whitelisted=%s)', student, course_id, course_grade.percent, course.grade_cutoffs, student.profile.allow_certificate, student.id in whitelisted_user_ids ) grade_results = course.grading.grade_results(graded_assignments, course_grade) grade_results = list(chain.from_iterable(grade_results)) rows.append( [student.id, student.email, student.username, student.last_name, student.first_name, second_name, course_grade.percent, course_grade.percent*100] + grade_results + cohorts_group_name + group_configs_group_names + team_name + [enrollment_mode] + [verification_status] + certificate_info ) TASK_LOG.info( u'%s, Task type: %s, Current step: %s, Grade calculation completed for students: %s/%s', task_info_string, action_name, current_step, student_counter, total_enrolled_students ) # By this point, we've got the rows we're going to stuff into our CSV files. current_step = {'step': 'Uploading CSVs'} task_progress.update_task_state(extra_meta=current_step) TASK_LOG.info(u'%s, Task type: %s, Current step: %s', task_info_string, action_name, current_step) # Perform the actual upload custom_grades_download = get_custom_grade_config() report_hash_unique_hash = hex(random.getrandbits(32))[2:] report_name = 'plp_grade_users_report_{}_id_{}'.format(report_hash_unique_hash, requester_id) err_report_name = 'plp_grade_users_report_err_{}_id_{}'.format(report_hash_unique_hash, requester_id) upload_csv_to_report_store(rows, report_name, course_id, start_date, config_name=custom_grades_download) # If there are any error rows (don't count the header), write them out as well has_errors = len(err_rows) > 1 if has_errors: upload_csv_to_report_store(err_rows, err_report_name, course_id, start_date, config_name=custom_grades_download) callback_url = _task_input.get("callback_url", None) if callback_url: report_store = ReportStore.from_config(config_name=custom_grades_download) files_urls_pairs = report_store.links_for(course_id) find_by_name = lambda name: [url for filename, url in files_urls_pairs if name in filename][0] try: csv_url = find_by_name(report_name) csv_err_url = find_by_name(err_report_name) if has_errors else None PlpApiClient().push_grade_api_result(callback_url, csv_url, csv_err_url) except Exception as e: TASK_LOG.error("Failed push to PLP:{}".format(str(e))) # One last update before we close out... TASK_LOG.info(u'%s, Task type: %s, Finalizing grade task', task_info_string, action_name) return task_progress.update_task_state(extra_meta=current_step)