Exemplo n.º 1
0
def courses_custom(request):
    """
    Render "find courses" page.  The course selection work is done in courseware.courses.
    """
    courses_list = []
    programs_list = []
    course_discovery_meanings = getattr(settings, 'COURSE_DISCOVERY_MEANINGS',
                                        {})
    if not settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
        current_date = datetime.now(utc)
        courses_list = get_courses(
            request.user,
            filter_={'end__gte': current_date.date() - timedelta(days=1)})

        if configuration_helpers.get_value(
                "ENABLE_COURSE_SORTING_BY_START_DATE",
                settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"]):
            courses_list = sort_by_start_date(courses_list)
        else:
            courses_list = sort_by_announcement(courses_list)

    # Getting all the programs from course-catalog service. The programs_list is being added to the context but it's
    # not being used currently in courseware/courses.html. To use this list, you need to create a custom theme that
    # overrides courses.html. The modifications to courses.html to display the programs will be done after the support
    # for edx-pattern-library is added.
    if configuration_helpers.get_value(
            "DISPLAY_PROGRAMS_ON_MARKETING_PAGES",
            settings.FEATURES.get("DISPLAY_PROGRAMS_ON_MARKETING_PAGES")):
        programs_list = get_programs_data(request.user)

    if request.user.is_authenticated():
        add_tag_to_enrolled_courses(request.user, courses_list)

    for course in courses_list:
        course_key = SlashSeparatedCourseKey.from_deprecated_string(
            course.id.to_deprecated_string())
        with modulestore().bulk_operations(course_key):
            if has_access(request.user, 'load', course):
                access_link = get_last_accessed_courseware(
                    get_course_by_id(course_key, 0), request, request.user)

                first_chapter_url, first_section = get_course_related_keys(
                    request, get_course_by_id(course_key, 0))
                first_target = reverse('courseware_section',
                                       args=[
                                           course.id.to_deprecated_string(),
                                           first_chapter_url, first_section
                                       ])

                course.course_target = access_link if access_link != None else first_target
            else:
                course.course_target = '/courses/' + course.id.to_deprecated_string(
                )

    return render_to_response(
        "courseware/courses.html", {
            'courses': courses_list,
            'course_discovery_meanings': course_discovery_meanings,
            'programs_list': programs_list
        })
    def confirmation(self, request, suffix=''):

        uid = request.params.get('uid')
        user = self.runtime.get_real_user(uid)

        try:
            if user and self.is_for_external_course:
                submissions_api.create_submission(self.student_item,
                                                  {"survey_completed": True})
        except Exception:
            LOG.info(
                "Error creating a submission for the survey %s related to course %s",
                self.survey_name,
                self.course_id.html_id(),
            )

        course = get_course_by_id(self.course_id)
        context = {
            "completed_survey":
            self.verify_completion(),
            "css":
            self.resource_string("static/css/surveymonkey.css"),
            "course_link":
            course.other_course_settings.get("external_course_target"),
        }
        return Response(
            LOADER.render_template(
                "static/html/surveymonkey_confirmation_page.html", context))
Exemplo n.º 3
0
def add_cohort(course_key, name, assignment_type):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if is_cohort_exists(course_key, name):
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")  # lint-amnesty, pylint: disable=raise-missing-from

    cohort = CourseCohort.create(
        cohort_name=name,
        course_id=course.id,
        assignment_type=assignment_type
    ).course_user_group

    tracker.emit(
        "edx.cohort.creation_requested",
        {"cohort_name": cohort.name, "cohort_id": cohort.id}
    )
    return cohort
Exemplo n.º 4
0
    def setUp(self):
        """
        Set up tests
        """
        super(TestFieldOverrides, self).setUp()

        self.ccx = ccx = CustomCourseForEdX(course_id=self.course.id,
                                            display_name='Test CCX',
                                            coach=AdminFactory.create())
        ccx.save()

        patch = mock.patch('ccx.overrides.get_current_ccx')
        self.get_ccx = get_ccx = patch.start()
        get_ccx.return_value = ccx
        self.addCleanup(patch.stop)

        self.addCleanup(RequestCache.clear_all_namespaces)

        inject_field_overrides(iter_blocks(ccx.course), self.course,
                               AdminFactory.create())

        self.ccx_key = CCXLocator.from_course_locator(self.course.id, ccx.id)
        self.ccx_course = get_course_by_id(self.ccx_key, depth=None)

        def cleanup_provider_classes():
            """
            After everything is done, clean up by un-doing the change to the
            OverrideFieldData object that is done during the wrap method.
            """
            OverrideFieldData.provider_classes = None

        self.addCleanup(cleanup_provider_classes)
Exemplo n.º 5
0
def recalculate_subsection_grade_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the SCORE_CHANGED signal and trigger an update.
    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of SCORE_CHANGED):
       - points_possible: Maximum score available for the exercise
       - points_earned: Score obtained by the user
       - user: User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    student = kwargs['user']
    course_key = CourseLocator.from_string(kwargs['course_id'])
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

    scored_block_usage_key = UsageKey.from_string(
        kwargs['usage_id']).replace(course_key=course_key)
    collected_block_structure = get_course_in_cache(course_key)
    course = get_course_by_id(course_key, depth=0)

    subsections_to_update = collected_block_structure.get_transformer_block_field(
        scored_block_usage_key, GradesTransformer, 'subsections', set())
    subsection_grade_factory = SubsectionGradeFactory(
        student, course, collected_block_structure)
    for subsection_usage_key in subsections_to_update:
        transformed_subsection_structure = get_course_blocks(
            student,
            subsection_usage_key,
            collected_block_structure=collected_block_structure,
        )
        subsection_grade_factory.update(
            transformed_subsection_structure[subsection_usage_key],
            transformed_subsection_structure)
Exemplo n.º 6
0
def change_existing_ccx_coaches_to_staff(apps, schema_editor):
    """
    Modify all coaches of CCX courses so that they have the staff role on the
    CCX course they coach, but retain the CCX Coach role on the parent course.

    Arguments:
        apps (Applications): Apps in edX platform.
        schema_editor (SchemaEditor): For editing database schema (unused)

    """
    CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX')
    db_alias = schema_editor.connection.alias
    if not db_alias == 'default':
        # This migration is not intended to run against the student_module_history database and
        # will fail if it does. Ensure that it'll only run against the default database.
        return
    list_ccx = CustomCourseForEdX.objects.using(db_alias).all()
    for ccx in list_ccx:
        ccx_locator = CCXLocator.from_course_locator(ccx.course_id,
                                                     six.text_type(ccx.id))
        try:
            course = get_course_by_id(ccx_locator)
        except Http404:
            log.error('Could not migrate access for CCX course: %s',
                      six.text_type(ccx_locator))
        else:
            coach = User.objects.get(id=ccx.coach.id)
            allow_access(course, coach, 'staff', send_email=False)
            revoke_access(course, coach, 'ccx_coach', send_email=False)
            log.info(
                'The CCX coach of CCX %s has been switched from "CCX Coach" to "Staff".',
                six.text_type(ccx_locator))
Exemplo n.º 7
0
def recalculate_subsection_grade_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the SCORE_CHANGED signal and trigger an update.
    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of SCORE_CHANGED):
       - points_possible: Maximum score available for the exercise
       - points_earned: Score obtained by the user
       - user: User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    student = kwargs['user']
    course_key = CourseLocator.from_string(kwargs['course_id'])
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

    scored_block_usage_key = UsageKey.from_string(kwargs['usage_id']).replace(course_key=course_key)
    collected_block_structure = get_course_in_cache(course_key)
    course = get_course_by_id(course_key, depth=0)

    subsections_to_update = collected_block_structure.get_transformer_block_field(
        scored_block_usage_key,
        GradesTransformer,
        'subsections',
        set()
    )
    for subsection_usage_key in subsections_to_update:
        transformed_subsection_structure = get_course_blocks(
            student,
            subsection_usage_key,
            collected_block_structure=collected_block_structure,
        )
        SubsectionGradeFactory(student).update(subsection_usage_key, transformed_subsection_structure, course)
Exemplo n.º 8
0
def save_display_name(apps, schema_editor):
    '''
    Add override for `display_name` for CCX courses that don't have one yet.
    '''
    CcxFieldOverride = apps.get_model('ccx', 'CcxFieldOverride')
    CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX')

    # Build list of CCX courses that don't have an override for `display_name` yet
    ccx_display_name_present_ids = list(
        CcxFieldOverride.objects.filter(field='display_name').values_list(
            'ccx__id', flat=True))
    ccx_list = CustomCourseForEdX.objects.exclude(
        id__in=ccx_display_name_present_ids)

    # Create `display_name` overrides for these CCX courses
    for ccx in ccx_list:
        try:
            course = get_course_by_id(ccx.course_id, depth=None)
        except Http404:
            log.error(
                "Root course %s not found. Can't create display_name override for %s.",
                ccx.course_id, ccx.display_name)
            continue
        display_name = course.fields['display_name']
        display_name_json = display_name.to_json(ccx.display_name)
        serialized_display_name = json.dumps(display_name_json)

        CcxFieldOverride.objects.get_or_create(
            ccx=ccx,
            location=course.location,
            field='display_name',
            defaults={'value': serialized_display_name},
        )
def remove_master_course_staff_from_ccx_for_existing_ccx(apps, schema_editor):
    """
    Remove all staff and instructors of master course from respective CCX(s).

    Arguments:
        apps (Applications): Apps in edX platform.
        schema_editor (SchemaEditor): For editing database schema i.e create, delete field (column)

    """
    CustomCourseForEdX = apps.get_model("ccx", "CustomCourseForEdX")
    list_ccx = CustomCourseForEdX.objects.all()
    for ccx in list_ccx:
        if not ccx.course_id or ccx.course_id.deprecated:
            # prevent migration for deprecated course ids or invalid ids.
            continue
        ccx_locator = CCXLocator.from_course_locator(ccx.course_id, six.text_type(ccx.id))
        try:
            course = get_course_by_id(ccx.course_id)
            remove_master_course_staff_from_ccx(
                course,
                ccx_locator,
                ccx.display_name,
                send_email=False
            )
        except Http404:
            log.warning(
                "Unable to remove instructors and staff of master course %s from ccx %s.",
                ccx.course_id,
                ccx_locator
            )
Exemplo n.º 10
0
    def _generate(self, context):
        """
        Generate a CSV containing all students' problem grades within a given
        `course_id`.
        """
        context.update_status(
            'ProblemGradeReport Step 1: Starting problem grades')
        course = get_course_by_id(context.course_id)
        header_row = OrderedDict([('id', 'Student ID'), ('email', 'Email'),
                                  ('username', 'Username')])
        context.update_status(
            'ProblemGradeReport Step 2: Retrieving scorable grade blocks for course'
        )
        graded_scorable_blocks = self._graded_scorable_blocks_to_header(course)
        context.update_status(
            'ProblemGradeReport Step 3: Setting up headers for problem grade report'
        )
        success_headers = self._success_headers(header_row,
                                                graded_scorable_blocks)
        error_headers = self._error_headers(header_row)

        context.update_status(
            'ProblemGradeReport Step 4: Retrieving problem scores for course for enrolled users'
        )
        generated_rows = self._batched_rows(context, header_row,
                                            graded_scorable_blocks)
        success_rows, error_rows = self._compile(context, generated_rows)
        context.update_status(
            'ProblemGradeReport Step 5: Uploading data to CSV report file')
        self._upload(context, [success_headers] + success_rows,
                     [error_headers] + error_rows)

        return context.update_status(
            'ProblemGradeReport Step 6: Completed problem grades report')
Exemplo n.º 11
0
def generate_email(email, redirect_url):
    """"
        Generate html/plain message and send email to each user
    """
    platform_name = configuration_helpers.get_value(
            'PLATFORM_NAME', settings.PLATFORM_NAME)
    # course_key = CourseKey.from_string(course_id)
    course = get_course_by_id(email.course_id)
    context = {
        "course_name": course.display_name_with_default,
        "platform_name": platform_name,
        "redirect_url": redirect_url,
        "message": email.message,
        "sender_name": email.sender_user.profile.name.title()
    }
    from_email = configuration_helpers.get_value(
        'email_from_address',
        settings.BULK_EMAIL_DEFAULT_FROM_EMAIL
    )
    html_message = render_to_string(
        'eol_course_email/email.txt', context)
    plain_message = strip_tags(html_message)
    email_subject = "{} - {}".format(email.subject, course.display_name_with_default)
    files = json.dumps(list(email.files.all().values('file_name', 'file_path', 'content_type')), default=json_util.default)
    for u in email.receiver_users.all():
        send_email.delay(
            from_email, 
            email.sender_user.email, 
            u.email, 
            email_subject, 
            html_message, 
            plain_message, 
            files
        )
Exemplo n.º 12
0
def recalculate_subsection_grade(user_id, course_id, usage_id):
    """
    Updates a saved subsection grade.
    This method expects the following parameters:
       - user_id: serialized id of applicable User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    course_key = CourseLocator.from_string(course_id)
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

    student = User.objects.get(id=user_id)
    scored_block_usage_key = UsageKey.from_string(usage_id).replace(
        course_key=course_key)

    collected_block_structure = get_course_in_cache(course_key)
    course = get_course_by_id(course_key, depth=0)
    subsection_grade_factory = SubsectionGradeFactory(
        student, course, collected_block_structure)
    subsections_to_update = collected_block_structure.get_transformer_block_field(
        scored_block_usage_key, GradesTransformer, 'subsections', set())

    for subsection_usage_key in subsections_to_update:
        transformed_subsection_structure = get_course_blocks(
            student,
            subsection_usage_key,
            collected_block_structure=collected_block_structure,
        )
        subsection_grade_factory.update(
            transformed_subsection_structure[subsection_usage_key],
            transformed_subsection_structure)
Exemplo n.º 13
0
 def __init__(self, _xmodule_instance_args, _entry_id, course_id,
              _task_input, action_name):
     task_id = _xmodule_instance_args.get(
         'task_id') if _xmodule_instance_args is not None else None
     self.task_info_string = ('Task: {task_id}, '
                              'InstructorTask ID: {entry_id}, '
                              'Course: {course_id}, '
                              'Input: {task_input}').format(
                                  task_id=task_id,
                                  entry_id=_entry_id,
                                  course_id=course_id,
                                  task_input=_task_input,
                              )
     self.task_id = task_id
     self.entry_id = _entry_id
     self.task_input = _task_input
     self.action_name = action_name
     self.course_id = course_id
     self.report_for_verified_only = generate_grade_report_for_verified_only(
     )
     self.task_progress = TaskProgress(self.action_name,
                                       total=None,
                                       start_time=time())
     self.course = get_course_by_id(self.course_id)
     self.file_name = 'problem_grade_report'
Exemplo n.º 14
0
 def user_has_passing_grade(self):
     """ Returns a boolean on if the effective_user has a passing grade in the course """
     if not self.effective_user.is_anonymous:
         course = get_course_by_id(self.course_key)
         user_grade = CourseGradeFactory().read(self.effective_user, course).percent
         return user_grade >= course.lowest_passing_grade
     return False
Exemplo n.º 15
0
def revert_ccx_staff_to_coaches(apps, schema_editor):
    """
    Modify all staff on CCX courses so that they no longer have the staff role
    on the course that they coach.

    Arguments:
        apps (Applications): Apps in edX platform.
        schema_editor (SchemaEditor): For editing database schema (unused)

    """
    CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX')
    db_alias = schema_editor.connection.alias
    if not db_alias == 'default':
        return
    list_ccx = CustomCourseForEdX.objects.using(db_alias).all()
    for ccx in list_ccx:
        ccx_locator = CCXLocator.from_course_locator(ccx.course_id,
                                                     six.text_type(ccx.id))
        try:
            course = get_course_by_id(ccx_locator)
        except Http404:
            log.error('Could not migrate access for CCX course: %s',
                      six.text_type(ccx_locator))
        else:
            coach = User.objects.get(id=ccx.coach.id)
            allow_access(course, coach, 'ccx_coach', send_email=False)
            revoke_access(course, coach, 'staff', send_email=False)
            log.info(
                'The CCX coach of CCX %s has been switched from "Staff" to "CCX Coach".',
                six.text_type(ccx_locator))
Exemplo n.º 16
0
    def handle(self, *args, **options):
        course = get_course_by_id(CourseKey.from_string(options['course']))

        print(
            'Warning: this command directly edits the list of course tabs in mongo.'
        )
        print('Tabs before any changes:')
        print_course(course)

        try:
            if options['delete']:
                num = int(options['delete'][0])
                if num < 3:
                    raise CommandError("Tabs 1 and 2 cannot be changed.")

                if query_yes_no(f'Deleting tab {num} Confirm?', default='no'):
                    tabs.primitive_delete(course,
                                          num - 1)  # -1 for 0-based indexing
            elif options['insert']:
                num, tab_type, name = options['insert']
                num = int(num)
                if num < 3:
                    raise CommandError("Tabs 1 and 2 cannot be changed.")

                if query_yes_no(
                        f'Inserting tab {num} "{tab_type}" "{name}" Confirm?',
                        default='no'):
                    tabs.primitive_insert(course, num - 1, tab_type,
                                          name)  # -1 as above
        except ValueError as e:
            # Cute: translate to CommandError so the CLI error prints nicely.
            raise CommandError(e)  # lint-amnesty, pylint: disable=raise-missing-from
Exemplo n.º 17
0
def verify_for_closed_enrollment(user, cart=None):
    """
    A multi-output helper function.
    inputs:
        user: a user object
        cart: If a cart is provided it uses the same object, otherwise fetches the user's cart.
    Returns:
        is_any_course_expired: True if any of the items in the cart has it's enrollment period closed. False otherwise.
        expired_cart_items: List of courses with enrollment period closed.
        expired_cart_item_names: List of names of the courses with enrollment period closed.
        valid_cart_item_tuples: List of courses which are still open for enrollment.
    """
    if cart is None:
        cart = Order.get_cart_for_user(user)
    expired_cart_items = []
    expired_cart_item_names = []
    valid_cart_item_tuples = []
    course_ids = []
    cart_items = cart.orderitem_set.all().select_subclasses()
    is_any_course_expired = False
    for cart_item in cart_items:
        course_key = getattr(cart_item, 'course_id', None)
        if course_key is not None:
            course_ids.append(course_key)
            course = get_course_by_id(course_key, depth=0)
            if CourseEnrollment.is_enrollment_closed(user, course):
                is_any_course_expired = True
                expired_cart_items.append(cart_item)
                expired_cart_item_names.append(course.display_name)
            else:
                valid_cart_item_tuples.append((cart_item, course))

    return is_any_course_expired, expired_cart_items, expired_cart_item_names, valid_cart_item_tuples, course_ids
Exemplo n.º 18
0
def meeting_start_email(block_id, user_email):
    """
        Send mail to specific user at meeting start
    """
    platform_name = configuration_helpers.get_value('PLATFORM_NAME',
                                                    settings.PLATFORM_NAME)
    usage_key = UsageKey.from_string(block_id)
    course = get_course_by_id(usage_key.course_key)
    subject = 'Ha comenzado una sesión de Zoom en el curso: {}'.format(
        course.display_name_with_default)
    redirect_url = get_lms_link_for_item(usage_key)
    context = {
        "course_name": course.display_name_with_default,
        "platform_name": platform_name,
        "redirect_url": redirect_url
    }
    html_message = render_to_string('emails/meeting_start.txt', context)
    plain_message = strip_tags(html_message)
    from_email = configuration_helpers.get_value(
        'email_from_address', settings.BULK_EMAIL_DEFAULT_FROM_EMAIL)
    mail = send_mail(subject,
                     plain_message,
                     from_email, [user_email],
                     fail_silently=False,
                     html_message=html_message)
    return mail
Exemplo n.º 19
0
 def certificate_data(self):
     """
     Returns certificate data if the effective_user is enrolled.
     Note: certificate data can be None depending on learner and/or course state.
     """
     course = get_course_by_id(self.course_key)
     if self.enrollment_object:
         return get_cert_data(self.effective_user, course, self.enrollment_object.mode)
Exemplo n.º 20
0
    def has_permission(self, request, view):
        try:
            course_key = CourseKey.from_string(view.kwargs.get('course_id'))
        except InvalidKeyError:
            raise Http404()

        course = get_course_by_id(course_key)
        return has_access(request.user, 'staff', course)
Exemplo n.º 21
0
    def _get_course(self):
        """
        Fetches the course using the URL kwarg.

        Returns:
            Course: Fetched course.
        """
        return get_course_by_id(self.course_key)
Exemplo n.º 22
0
    def get_enrollment_info(self, user, course_id):
        """
        Returns the User Enrollment information.
        """
        course = get_course_by_id(course_id, depth=0)
        is_course_staff = bool(has_access(user, 'staff', course))
        manual_enrollment_reason = 'N/A'

        # check the user enrollment role
        if user.is_staff:
            platform_name = configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
            enrollment_role = _(u'{platform_name} Staff').format(platform_name=platform_name)
        elif is_course_staff:
            enrollment_role = _('Course Staff')
        else:
            enrollment_role = _('Student')

        course_enrollment = CourseEnrollment.get_enrollment(user=user, course_key=course_id)

        if is_course_staff:
            enrollment_source = _('Staff')
        else:
            # get the registration_code_redemption object if exists
            registration_code_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(
                course_enrollment)
            # get the paid_course registration item if exists
            paid_course_reg_item = PaidCourseRegistration.get_course_item_for_user_enrollment(
                user=user,
                course_id=course_id,
                course_enrollment=course_enrollment
            )

            # from where the user get here
            if registration_code_redemption is not None:
                enrollment_source = _('Used Registration Code')
            elif paid_course_reg_item is not None:
                enrollment_source = _('Credit Card - Individual')
            else:
                manual_enrollment = ManualEnrollmentAudit.get_manual_enrollment(course_enrollment)
                if manual_enrollment is not None:
                    enrollment_source = _(
                        u'manually enrolled by username: {username}'
                    ).format(username=manual_enrollment.enrolled_by.username)

                    manual_enrollment_reason = manual_enrollment.reason
                else:
                    enrollment_source = _('Manually Enrolled')

        enrollment_date = course_enrollment.created.strftime(u"%B %d, %Y")
        currently_enrolled = course_enrollment.is_active

        course_enrollment_data = collections.OrderedDict()
        course_enrollment_data['Enrollment Date'] = enrollment_date
        course_enrollment_data['Currently Enrolled'] = currently_enrolled
        course_enrollment_data['Enrollment Source'] = enrollment_source
        course_enrollment_data['Manual (Un)Enrollment Reason'] = manual_enrollment_reason
        course_enrollment_data['Enrollment Role'] = enrollment_role
        return course_enrollment_data
Exemplo n.º 23
0
def registration_code_details(request, course_id):
    """
    Post handler to mark the registration code as
        1) valid
        2) invalid
        3) Unredeem.

    """
    course_key = CourseKey.from_string(course_id)
    code = request.POST.get('registration_code')
    action_type = request.POST.get('action_type')
    course = get_course_by_id(course_key, depth=0)
    action_type_messages = {
        'invalidate_registration_code':
        _('This enrollment code has been canceled. It can no longer be used.'),
        'unredeem_registration_code':
        _('This enrollment code has been marked as unused.'),
        'validate_registration_code':
        _('The enrollment code has been restored.')
    }
    try:
        registration_code = CourseRegistrationCode.objects.get(code=code)
    except CourseRegistrationCode.DoesNotExist:
        return JsonResponse(
            {
                'message':
                _(u'The enrollment code ({code}) was not found for the {course_name} course.'
                  ).format(code=code, course_name=course.display_name)
            },
            status=400)

    if action_type == 'invalidate_registration_code':
        registration_code.is_valid = False
        registration_code.save()
        if RegistrationCodeRedemption.is_registration_code_redeemed(code):
            code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(
                code, course_key)
            delete_redemption_entry(request, code_redemption, course_key)

    if action_type == 'validate_registration_code':
        registration_code.is_valid = True
        registration_code.save()

    if action_type == 'unredeem_registration_code':
        code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(
            code, course_key)
        if code_redemption is None:
            return JsonResponse(
                {
                    'message':
                    _(u'The redemption does not exist against enrollment code ({code}).'
                      ).format(code=code)
                },
                status=400)

        delete_redemption_entry(request, code_redemption, course_key)

    return JsonResponse({'message': action_type_messages[action_type]})
Exemplo n.º 24
0
def get_valid_course(course_id, is_ccx=False, advanced_course_check=False):
    """
    Helper function used to validate and get a course from a course_id string.
    It works with both master and ccx course id.

    Args:
        course_id (str): A string representation of a Master or CCX Course ID.
        is_ccx (bool): Flag to perform the right validation
        advanced_course_check (bool): Flag to perform extra validations for the master course

    Returns:
        tuple: a tuple of course_object, course_key, error_code, http_status_code
    """
    if course_id is None:
        # the ccx detail view cannot call this function with a "None" value
        # so the following `error_code` should be never used, but putting it
        # to avoid a `NameError` exception in case this function will be used
        # elsewhere in the future
        error_code = 'course_id_not_provided'
        if not is_ccx:
            log.info('Master course ID not provided')
            error_code = 'master_course_id_not_provided'

        return None, None, error_code, status.HTTP_400_BAD_REQUEST

    try:
        course_key = CourseKey.from_string(course_id)
    except InvalidKeyError:
        log.info(u'Course ID string "%s" is not valid', course_id)
        return None, None, 'course_id_not_valid', status.HTTP_400_BAD_REQUEST

    if not is_ccx:
        try:
            course_object = courses.get_course_by_id(course_key)
        except Http404:
            log.info(u'Master Course with ID "%s" not found', course_id)
            return None, None, 'course_id_does_not_exist', status.HTTP_404_NOT_FOUND
        if advanced_course_check:
            if course_object.id.deprecated:
                return None, None, 'deprecated_master_course_id', status.HTTP_400_BAD_REQUEST
            if not course_object.enable_ccx:
                return None, None, 'ccx_not_enabled_for_master_course', status.HTTP_403_FORBIDDEN
        return course_object, course_key, None, None
    else:
        try:
            ccx_id = course_key.ccx
        except AttributeError:
            log.info(u'Course ID string "%s" is not a valid CCX ID', course_id)
            return None, None, 'course_id_not_valid_ccx_id', status.HTTP_400_BAD_REQUEST
        # get the master_course key
        master_course_key = course_key.to_course_locator()
        try:
            ccx_course = CustomCourseForEdX.objects.get(
                id=ccx_id, course_id=master_course_key)
            return ccx_course, course_key, None, None
        except CustomCourseForEdX.DoesNotExist:
            log.info(u'CCX Course with ID "%s" not found', course_id)
            return None, None, 'ccx_course_id_does_not_exist', status.HTTP_404_NOT_FOUND
Exemplo n.º 25
0
def move_to_verified_cohort(sender, instance, **kwargs):  # pylint: disable=unused-argument
    """
    If the learner has changed modes, update assigned cohort iff the course is using
    the Automatic Verified Track Cohorting MVP feature.
    """
    from lms.djangoapps.courseware.courses import get_course_by_id
    course_key = instance.course_id
    verified_cohort_enabled = VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(
        course_key)
    verified_cohort_name = VerifiedTrackCohortedCourse.verified_cohort_name_for_course(
        course_key)

    if verified_cohort_enabled and (instance.mode != instance._old_mode):  # pylint: disable=protected-access
        if not is_course_cohorted(course_key):
            log.error(
                u"Automatic verified cohorting enabled for course '%s', but course is not cohorted.",
                course_key)
        else:
            course = get_course_by_id(course_key)
            existing_manual_cohorts = get_course_cohorts(
                course, assignment_type=CourseCohort.MANUAL)
            if any(cohort.name == verified_cohort_name
                   for cohort in existing_manual_cohorts):
                # Get a random cohort to use as the default cohort (for audit learners).
                # Note that calling this method will create a "Default Group" random cohort if no random
                # cohort yet exist.
                random_cohort = get_random_cohort(course_key)
                args = {
                    'course_id': six.text_type(course_key),
                    'user_id': instance.user.id,
                    'verified_cohort_name': verified_cohort_name,
                    'default_cohort_name': random_cohort.name
                }
                log.info(
                    u"Queuing automatic cohorting for user '%s' in course '%s' "
                    u"due to change in enrollment mode from '%s' to '%s'.",
                    instance.user.id,
                    course_key,
                    instance._old_mode,
                    instance.mode  # pylint: disable=protected-access
                )

                # Do the update with a 3-second delay in hopes that the CourseEnrollment transaction has been
                # completed before the celery task runs. We want a reasonably short delay in case the learner
                # immediately goes to the courseware.
                sync_cohort_with_mode.apply_async(kwargs=args, countdown=3)

                # In case the transaction actually was not committed before the celery task runs,
                # run it again after 5 minutes. If the first completed successfully, this task will be a no-op.
                sync_cohort_with_mode.apply_async(kwargs=args, countdown=300)
            else:
                log.error(
                    u"Automatic verified cohorting enabled for course '%s', "
                    u"but verified cohort named '%s' does not exist.",
                    course_key,
                    verified_cohort_name,
                )
Exemplo n.º 26
0
 def is_enabled(cls, request, course_key):
     """
     Returns True if the user should be shown course updates for this course.
     """
     if DISABLE_UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key):
         return False
     if not CourseEnrollment.is_enrolled(request.user, course_key):
         return False
     course = get_course_by_id(course_key)
     return CourseUpdatesFragmentView.has_updates(request, course)
Exemplo n.º 27
0
    def post(self, request):
        """Handle all actions from courses view"""

        if not request.user.is_staff:
            raise Http404

        action = request.POST.get('action', '')
        track.views.server_track(request,
                                 action, {},
                                 page='courses_sysdashboard')

        courses = {course.id: course for course in self.get_courses()}
        if action == 'add_course':
            gitloc = request.POST.get('repo_location',
                                      '').strip().replace(' ',
                                                          '').replace(';', '')
            branch = request.POST.get('repo_branch',
                                      '').strip().replace(' ',
                                                          '').replace(';', '')
            self.msg += self.get_course_from_git(gitloc, branch)

        elif action == 'del_course':
            course_id = request.POST.get('course_id', '').strip()
            course_key = CourseKey.from_string(course_id)
            course_found = False
            if course_key in courses:
                course_found = True
                course = courses[course_key]
            else:
                try:
                    course = get_course_by_id(course_key)
                    course_found = True
                except Exception as err:  # pylint: disable=broad-except
                    self.msg += _(
                        HTML(
                            u'Error - cannot get course with ID {0}<br/><pre>{1}</pre>'
                        )).format(course_key, escape(str(err)))

            if course_found:
                # delete course that is stored with mongodb backend
                self.def_ms.delete_course(course.id, request.user.id)
                # don't delete user permission groups, though
                self.msg += \
                    HTML(u"<font color='red'>{0} {1} = {2} ({3})</font>").format(
                        _('Deleted'), text_type(course.location), text_type(course.id), course.display_name)

        context = {
            'datatable': self.make_datatable(list(courses.values())),
            'msg': self.msg,
            'djangopid': os.getpid(),
            'modeflag': {
                'courses': 'active-section'
            },
        }
        return render_to_response(self.template_name, context)
    def completion(self, request, suffix=''):

        context = {
            "course": get_course_by_id(self.course_id),
            "completed_survey": self.verify_completion(),
            "css": self.resource_string("static/css/surveymonkey.css"),
            "online_help_token": "online_help_token",
        }
        return Response(
            LOADER.render_template(
                "static/html/surveymonkey_completion_page.html", context))
Exemplo n.º 29
0
def get_legacy_discussion_settings(course_key):

    try:
        course_cohort_settings = CourseCohortsSettings.objects.get(course_id=course_key)
        return {
            'is_cohorted': course_cohort_settings.is_cohorted,
            'cohorted_discussions': course_cohort_settings.cohorted_discussions,
            'always_cohort_inline_discussions': course_cohort_settings.always_cohort_inline_discussions
        }
    except CourseCohortsSettings.DoesNotExist:
        course = courses.get_course_by_id(course_key)
        return _get_cohort_settings_from_modulestore(course)
Exemplo n.º 30
0
def get_legacy_discussion_settings(course_key):  # lint-amnesty, pylint: disable=missing-function-docstring

    try:
        course_cohort_settings = CourseCohortsSettings.objects.get(course_id=course_key)
        return {
            'is_cohorted': course_cohort_settings.is_cohorted,
            'cohorted_discussions': course_cohort_settings.cohorted_discussions,
            'always_cohort_inline_discussions': course_cohort_settings.always_cohort_inline_discussions
        }
    except CourseCohortsSettings.DoesNotExist:
        course = courses.get_course_by_id(course_key)
        return _get_cohort_settings_from_modulestore(course)
Exemplo n.º 31
0
    def setUp(self):
        """
        Set up the course and user context
        """
        super(CoursesRenderTest, self).setUp()

        store = modulestore()
        course_items = import_course_from_xml(store, self.user.id, TEST_DATA_DIR, ['toy'])
        course_key = course_items[0].id
        self.course = get_course_by_id(course_key)
        self.addCleanup(set_current_request, None)
        self.request = get_mock_request(UserFactory.create())
Exemplo n.º 32
0
def compute_grades_for_course(course_key, offset, batch_size, **kwargs):  # pylint: disable=unused-argument
    """
    Compute and save grades for a set of students in the specified course.

    The set of students will be determined by the order of enrollment date, and
    limited to at most <batch_size> students, starting from the specified
    offset.
    """
    course = courses.get_course_by_id(CourseKey.from_string(course_key))
    enrollments = CourseEnrollment.objects.filter(course_id=course.id).order_by('created')
    student_iter = (enrollment.user for enrollment in enrollments[offset:offset + batch_size])
    for result in CourseGradeFactory().iter(users=student_iter, course=course, force_update=True):
        if result.error is not None:
            raise result.error
Exemplo n.º 33
0
def move_to_verified_cohort(sender, instance, **kwargs):  # pylint: disable=unused-argument
    """
    If the learner has changed modes, update assigned cohort iff the course is using
    the Automatic Verified Track Cohorting MVP feature.
    """
    course_key = instance.course_id
    verified_cohort_enabled = VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(course_key)
    verified_cohort_name = VerifiedTrackCohortedCourse.verified_cohort_name_for_course(course_key)

    if verified_cohort_enabled and (instance.mode != instance._old_mode):  # pylint: disable=protected-access
        if not is_course_cohorted(course_key):
            log.error("Automatic verified cohorting enabled for course '%s', but course is not cohorted.", course_key)
        else:
            course = get_course_by_id(course_key)
            existing_manual_cohorts = get_course_cohorts(course, CourseCohort.MANUAL)
            if any(cohort.name == verified_cohort_name for cohort in existing_manual_cohorts):
                # Get a random cohort to use as the default cohort (for audit learners).
                # Note that calling this method will create a "Default Group" random cohort if no random
                # cohort yet exist.
                random_cohort = get_random_cohort(course_key)
                args = {
                    'course_id': unicode(course_key),
                    'user_id': instance.user.id,
                    'verified_cohort_name': verified_cohort_name,
                    'default_cohort_name': random_cohort.name
                }
                log.info(
                    "Queuing automatic cohorting for user '%s' in course '%s' "
                    "due to change in enrollment mode from '%s' to '%s'.",
                    instance.user.id, course_key, instance._old_mode, instance.mode  # pylint: disable=protected-access
                )

                # Do the update with a 3-second delay in hopes that the CourseEnrollment transaction has been
                # completed before the celery task runs. We want a reasonably short delay in case the learner
                # immediately goes to the courseware.
                sync_cohort_with_mode.apply_async(kwargs=args, countdown=3)

                # In case the transaction actually was not committed before the celery task runs,
                # run it again after 5 minutes. If the first completed successfully, this task will be a no-op.
                sync_cohort_with_mode.apply_async(kwargs=args, countdown=300)
            else:
                log.error(
                    "Automatic verified cohorting enabled for course '%s', "
                    "but verified cohort named '%s' does not exist.",
                    course_key,
                    verified_cohort_name,
                )
Exemplo n.º 34
0
def compute_grades_for_course(course_key, offset, batch_size):
    """
    Compute grades for a set of students in the specified course.

    The set of students will be determined by the order of enrollment date, and
    limited to at most <batch_size> students, starting from the specified
    offset.
    """

    course = courses.get_course_by_id(CourseKey.from_string(course_key))
    enrollments = CourseEnrollment.objects.filter(course_id=course.id).order_by('created')
    student_iter = (enrollment.user for enrollment in enrollments[offset:offset + batch_size])
    list(CourseGradeFactory().iter(
        course,
        students=student_iter,
        read_only=False,
    ))
Exemplo n.º 35
0
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)
Exemplo n.º 36
0
def course_info_to_ccxcon(course_key):
    """
    Function that gathers informations about the course and
    makes a post request to a CCXCon with the data.

    Args:
        course_key (CourseLocator): the master course key
    """

    try:
        course = get_course_by_id(course_key)
    except Http404:
        log.error('Master Course with key "%s" not found', unicode(course_key))
        return
    if not course.enable_ccx:
        log.debug('ccx not enabled for course key "%s"', unicode(course_key))
        return
    if not course.ccx_connector:
        log.debug('ccx connector not defined for course key "%s"', unicode(course_key))
        return
    if not is_valid_url(course.ccx_connector):
        log.error(
            'ccx connector URL "%s" for course key "%s" is not a valid URL.',
            course.ccx_connector, unicode(course_key)
        )
        return
    # get the oauth credential for this URL
    try:
        ccxcon = CCXCon.objects.get(url=course.ccx_connector)
    except CCXCon.DoesNotExist:
        log.error('ccx connector Oauth credentials not configured for URL "%s".', course.ccx_connector)
        return

    # get an oauth client with a valid token

    oauth_ccxcon = get_oauth_client(
        server_token_url=urlparse.urljoin(course.ccx_connector, CCXCON_TOKEN_URL),
        client_id=ccxcon.oauth_client_id,
        client_secret=ccxcon.oauth_client_secret
    )

    # get the entire list of instructors
    course_instructors = CourseInstructorRole(course.id).users_with_role()
    # get anonymous ids for each of them
    course_instructors_ids = [anonymous_id_for_user(user, course_key) for user in course_instructors]
    # extract the course details
    course_details = CourseDetails.fetch(course_key)

    payload = {
        'course_id': unicode(course_key),
        'title': course.display_name,
        'author_name': None,
        'overview': course_details.overview,
        'description': course_details.short_description,
        'image_url': course_details.course_image_asset_path,
        'instructors': course_instructors_ids
    }
    headers = {'content-type': 'application/json'}

    # make the POST request
    add_course_url = urlparse.urljoin(course.ccx_connector, CCXCON_COURSEXS_URL)
    resp = oauth_ccxcon.post(
        url=add_course_url,
        json=payload,
        headers=headers,
        timeout=CCXCON_REQUEST_TIMEOUT
    )

    if resp.status_code >= 500:
        raise CCXConnServerError('Server returned error Status: %s, Content: %s', resp.status_code, resp.content)
    if resp.status_code >= 400:
        log.error("Error creating course on ccxcon. Status: %s, Content: %s", resp.status_code, resp.content)
    # this API performs a POST request both for POST and PATCH, but the POST returns 201 and the PATCH returns 200
    elif resp.status_code != HTTP_200_OK and resp.status_code != HTTP_201_CREATED:
        log.error('Server returned unexpected status code %s', resp.status_code)
    else:
        log.debug('Request successful. Status: %s, Content: %s', resp.status_code, resp.content)
Exemplo n.º 37
0
    def handle(self, *args, **options):
        if os.path.exists(options['output']):
            raise CommandError("File {0} already exists".format(
                options['output']))

        status_interval = 100

        # parse out the course into a coursekey
        if options['course']:
            try:
                course_key = CourseKey.from_string(options['course'])
            # if it's not a new-style course key, parse it from an old-style
            # course key
            except InvalidKeyError:
                course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course'])

        print "Fetching enrolled students for {0}".format(course_key)
        enrolled_students = User.objects.filter(
            courseenrollment__course_id=course_key
        )
        factory = RequestMock()
        request = factory.get('/')

        total = enrolled_students.count()
        print "Total enrolled: {0}".format(total)
        course = courses.get_course_by_id(course_key)
        total = enrolled_students.count()
        start = datetime.datetime.now()
        rows = []
        header = None
        print "Fetching certificate data"
        cert_grades = {
            cert.user.username: cert.grade
            for cert in list(
                GeneratedCertificate.objects.filter(  # pylint: disable=no-member
                    course_id=course_key
                ).prefetch_related('user')
            )
        }
        print "Grading students"
        for count, student in enumerate(enrolled_students):
            count += 1
            if count % status_interval == 0:
                # Print a status update with an approximation of
                # how much time is left based on how long the last
                # interval took
                diff = datetime.datetime.now() - start
                timeleft = diff * (total - count) / status_interval
                hours, remainder = divmod(timeleft.seconds, 3600)
                minutes, __ = divmod(remainder, 60)
                print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format(
                    count, total, hours, minutes)
                start = datetime.datetime.now()
            request.user = student
            grade = CourseGradeFactory().create(student, course)
            if not header:
                header = [section['label'] for section in grade.summary[u'section_breakdown']]
                rows.append(["email", "username", "certificate-grade", "grade"] + header)
            percents = {section['label']: section['percent'] for section in grade.summary[u'section_breakdown']}
            row_percents = [percents[label] for label in header]
            if student.username in cert_grades:
                rows.append(
                    [student.email, student.username, cert_grades[student.username], grade.percent] + row_percents,
                )
            else:
                rows.append([student.email, student.username, "N/A", grade.percent] + row_percents)
        with open(options['output'], 'wb') as f:
            writer = csv.writer(f)
            writer.writerows(rows)