Example #1
0
def mktg_course_about(request, course_id):
    """
    This is the button that gets put into an iframe on the Drupal site
    """

    try:
        course = get_course_with_access(request.user, course_id, 'see_exists')
    except (ValueError, Http404) as e:
        # if a course does not exist yet, display a coming
        # soon button
        return render_to_response(
            'courseware/mktg_coming_soon.html', {'course_id': course_id}
        )

    registered = registered_for_course(course, request.user)

    if has_access(request.user, course, 'load'):
        course_target = reverse('info', args=[course.id])
    else:
        course_target = reverse('about_course', args=[course.id])

    allow_registration = has_access(request.user, course, 'enroll')

    show_courseware_link = (has_access(request.user, course, 'load') or
                            settings.FEATURES.get('ENABLE_LMS_MIGRATION'))
    course_modes = CourseMode.modes_for_course(course.id)

    return render_to_response('courseware/mktg_course_about.html', {
        'course': course,
        'registered': registered,
        'allow_registration': allow_registration,
        'course_target': course_target,
        'show_courseware_link': show_courseware_link,
        'course_modes': course_modes,
    })
Example #2
0
def course_about(request, course_id):
    if settings.MITX_FEATURES.get("ENABLE_MKTG_SITE", False):
        raise Http404

    course = get_course_with_access(request.user, course_id, "see_exists")
    registered = registered_for_course(course, request.user)

    if has_access(request.user, course, "load"):
        course_target = reverse("courseware", args=[course.id])
    else:
        course_target = reverse("about_course", args=[course.id])

    show_courseware_link = has_access(request.user, course, "load") or settings.MITX_FEATURES.get(
        "ENABLE_LMS_MIGRATION"
    )

    return render_to_response(
        "courseware/course_about.html",
        {
            "course": course,
            "registered": registered,
            "course_target": course_target,
            "show_courseware_link": show_courseware_link,
        },
    )
    def test_course_access_to_beta_users(self):
        """
        Test that beta testers can access `self_paced` course prior to start date.
        """
        now = datetime.datetime.now(pytz.UTC)
        one_month_from_now = now + datetime.timedelta(days=30)
        course_options = {
            'days_early_for_beta': 100,
            'self_paced': True,
            'start': one_month_from_now,
        }
        # Create a `self_paced` course and add a beta tester in it
        self_paced_course, self_paced_section = self.setup_course(**course_options)
        beta_tester = BetaTesterFactory(course_key=self_paced_course.id)

        # Verify course is `self_paced` and course has start date but not section.
        self.assertTrue(self_paced_course.self_paced)
        self.assertEqual(self_paced_course.start, one_month_from_now)
        self.assertIsNone(self_paced_section.start)

        # Verify that non-staff user do not have access to the course
        self.assertFalse(has_access(self.non_staff_user, 'load', self_paced_course))

        # Verify beta tester can access the course as well as the course sections
        self.assertTrue(has_access(beta_tester, 'load', self_paced_course))
        self.assertTrue(has_access(beta_tester, 'load', self_paced_section, self_paced_course.id))
    def test_beta_period(self):
        """
        Check that beta-test access works.
        """
        student_email, student_password = self.ACCOUNT_INFO[0]
        instructor_email, instructor_password = self.ACCOUNT_INFO[1]

        # Make courses start in the future
        now = datetime.datetime.now(pytz.UTC)
        tomorrow = now + datetime.timedelta(days=1)
        course_data = {'start': tomorrow}

        # self.course's hasn't started
        self.course = self.update_course(self.course, course_data)
        self.assertFalse(self.course.has_started())

        # but should be accessible for beta testers
        self.course.lms.days_early_for_beta = 2

        # student user shouldn't see it
        student_user = User.objects.get(email=student_email)
        self.assertFalse(has_access(student_user, self.course, 'load'))

        # now add the student to the beta test group
        group_name = course_beta_test_group_name(self.course.location)
        group = Group.objects.create(name=group_name)
        group.user_set.add(student_user)

        # now the student should see it
        self.assertTrue(has_access(student_user, self.course, 'load'))
Example #5
0
def _get_user_certificate(request, user, course_key, course, preview_mode=None):
    """
    Retrieves user's certificate from db. Creates one in case of preview mode.
    Returns None if there is no certificate generated for given user
    otherwise returns `GeneratedCertificate` instance.
    """
    try:
        # Attempt to load the user's generated certificate data
        if preview_mode:
            user_certificate = GeneratedCertificate.objects.get(
                user=user,
                course_id=course_key,
                mode=preview_mode
            )
        else:
            user_certificate = GeneratedCertificate.objects.get(
                user=user,
                course_id=course_key
            )

    # If there's no generated certificate data for this user, we need to see if we're in 'preview' mode...
    # If we are, we'll need to create a mock version of the user_certificate container for previewing
    except GeneratedCertificate.DoesNotExist:
        if preview_mode and (
                has_access(request.user, 'instructor', course) or
                has_access(request.user, 'staff', course)):
            user_certificate = GeneratedCertificate(
                mode=preview_mode,
                verify_uuid=unicode(uuid4().hex),
                modified_date=datetime.now().date()
            )
        else:
            return None

    return user_certificate
Example #6
0
def _create_base_discussion_view_context(request, course_key):
    """
    Returns the default template context for rendering any discussion view.
    """
    user = request.user
    cc_user = cc.User.from_django_user(user)
    user_info = cc_user.to_dict()
    course = get_course_with_access(user, 'load', course_key, check_if_enrolled=True)
    course_settings = make_course_settings(course, user)
    uses_bootstrap = USE_BOOTSTRAP_FLAG.is_enabled()
    return {
        'csrf': csrf(request)['csrf_token'],
        'course': course,
        'user': user,
        'user_info': user_info,
        'staff_access': bool(has_access(user, 'staff', course)),
        'roles': utils.get_role_ids(course_key),
        'can_create_comment': has_permission(user, "create_comment", course.id),
        'can_create_subcomment': has_permission(user, "create_sub_comment", course.id),
        'can_create_thread': has_permission(user, "create_thread", course.id),
        'flag_moderator': bool(
            has_permission(user, 'openclose_thread', course.id) or
            has_access(user, 'staff', course)
        ),
        'course_settings': course_settings,
        'disable_courseware_js': True,
        'uses_bootstrap': uses_bootstrap,
        'uses_pattern_library': not uses_bootstrap,
    }
def instructor_dashboard_2(request, course_id):
    """ Display the instructor dashboard for a course. """

    course = get_course_by_id(course_id, depth=None)

    access = {
        'admin': request.user.is_staff,
        'instructor': has_access(request.user, course, 'instructor'),
        'staff': has_access(request.user, course, 'staff'),
        'forum_admin': has_forum_access(
            request.user, course_id, FORUM_ROLE_ADMINISTRATOR
        ),
    }

    if not access['staff']:
        raise Http404()

    sections = [
        _section_course_info(course_id, access),
        _section_membership(course_id, access),
        _section_student_admin(course_id, access),
        _section_data_download(course_id),
        _section_analytics(course_id),
    ]

    context = {
        'course': course,
        'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}),
        'sections': sections,
    }

    return render_to_response('instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
    def test_student_has_access(self):
        """
        Tests course student have right access to content w/o preview.
        """
        course_key = self.course.id
        chapter = ItemFactory.create(category="chapter", parent_location=self.course.location)
        overview = CourseOverview.get_from_id(course_key)

        # Enroll student to the course
        CourseEnrollmentFactory(user=self.student, course_id=self.course.id)

        modules = [
            self.course,
            overview,
            chapter,
        ]
        with patch('courseware.access.in_preview_mode') as mock_preview:
            mock_preview.return_value = False
            for obj in modules:
                self.assertTrue(bool(access.has_access(self.student, 'load', obj, course_key=self.course.id)))

        with patch('courseware.access.in_preview_mode') as mock_preview:
            mock_preview.return_value = True
            for obj in modules:
                self.assertFalse(bool(access.has_access(self.student, 'load', obj, course_key=self.course.id)))
def instructor_dashboard_2(request, course_id):
    """ Display the instructor dashboard for a course. """

    course = get_course_by_id(course_id, depth=None)

    access = {
        "admin": request.user.is_staff,
        "instructor": has_access(request.user, course, "instructor"),
        "staff": has_access(request.user, course, "staff"),
        "forum_admin": has_forum_access(request.user, course_id, FORUM_ROLE_ADMINISTRATOR),
    }

    if not access["staff"]:
        raise Http404()

    sections = [
        _section_course_info(course_id),
        _section_membership(course_id, access),
        _section_student_admin(course_id, access),
        _section_data_download(course_id),
        _section_analytics(course_id),
    ]

    context = {
        "course": course,
        "old_dashboard_url": reverse("instructor_dashboard", kwargs={"course_id": course_id}),
        "sections": sections,
    }

    return render_to_response("instructor/instructor_dashboard_2/instructor_dashboard_2.html", context)
Example #10
0
def _get_user_certificate(request, user, course_key, course, preview_mode=None):
    """
    Retrieves user's certificate from db. Creates one in case of preview mode.
    Returns None if there is no certificate generated for given user
    otherwise returns `GeneratedCertificate` instance.
    """
    user_certificate = None
    if preview_mode:
        # certificate is being previewed from studio
        if has_access(request.user, 'instructor', course) or has_access(request.user, 'staff', course):
            user_certificate = GeneratedCertificate(
                mode=preview_mode,
                verify_uuid=unicode(uuid4().hex),
                modified_date=datetime.now().date()
            )
    else:
        # certificate is being viewed by learner or public
        try:
            user_certificate = GeneratedCertificate.eligible_certificates.get(
                user=user,
                course_id=course_key,
                status=CertificateStatuses.downloadable
            )
        except GeneratedCertificate.DoesNotExist:
            pass

    return user_certificate
Example #11
0
def mktg_course_about(request, course_id):
    """
    This is the button that gets put into an iframe on the Drupal site
    """

    try:
        course = get_course_with_access(request.user, course_id, "see_exists")
    except (ValueError, Http404) as e:
        # if a course does not exist yet, display a coming
        # soon button
        return render_to_response("courseware/mktg_coming_soon.html", {"course_id": course_id})

    registered = registered_for_course(course, request.user)

    if has_access(request.user, course, "load"):
        course_target = reverse("info", args=[course.id])
    else:
        course_target = reverse("about_course", args=[course.id])

    allow_registration = has_access(request.user, course, "enroll")

    show_courseware_link = has_access(request.user, course, "load") or settings.FEATURES.get("ENABLE_LMS_MIGRATION")
    course_modes = CourseMode.modes_for_course(course.id)

    return render_to_response(
        "courseware/mktg_course_about.html",
        {
            "course": course,
            "registered": registered,
            "allow_registration": allow_registration,
            "course_target": course_target,
            "show_courseware_link": show_courseware_link,
            "course_modes": course_modes,
        },
    )
Example #12
0
def i_am_registered_for_the_course(coursenum, metadata, user='******'):
    # Create user
    if user == 'BetaTester':
        # Create the course
        now = datetime.datetime.now(pytz.UTC)
        tomorrow = now + datetime.timedelta(days=5)
        metadata.update({'days_early_for_beta': 5, 'start': tomorrow})
        create_course_for_lti(coursenum, metadata)
        course_descriptor = world.scenario_dict['COURSE']

        # create beta tester
        user = BetaTesterFactory(course_key=course_descriptor.id)
        normal_student = UserFactory()
        instructor = InstructorFactory(course_key=course_descriptor.id)

        assert not has_access(normal_student, 'load', course_descriptor)
        assert has_access(user, 'load', course_descriptor)
        assert has_access(instructor, 'load', course_descriptor)
    else:
        metadata.update({'start': datetime.datetime(1970, 1, 1, tzinfo=UTC)})
        create_course_for_lti(coursenum, metadata)
        course_descriptor = world.scenario_dict['COURSE']
        user = InstructorFactory(course_key=course_descriptor.id)

    # Enroll the user in the course and log them in
    if has_access(user, 'load', course_descriptor):
        world.enroll_user(user, course_descriptor.id)

    world.log_in(username=user.username, password='******')
Example #13
0
    def _do_test_beta_period(self):
        """Actually test beta periods, relying on settings to be right."""

        # trust, but verify :)
        self.assertFalse(settings.MITX_FEATURES['DISABLE_START_DATES'])

        # Make courses start in the future
        tomorrow = datetime.datetime.now(UTC()) + datetime.timedelta(days=1)

        # toy course's hasn't started
        self.toy.lms.start = tomorrow
        self.assertFalse(self.toy.has_started())

        # but should be accessible for beta testers
        self.toy.lms.days_early_for_beta = 2

        # student user shouldn't see it
        student_user = get_user(self.student)
        self.assertFalse(has_access(student_user, self.toy, 'load'))

        # now add the student to the beta test group
        group_name = course_beta_test_group_name(self.toy.location)
        group = Group.objects.create(name=group_name)
        group.user_set.add(student_user)

        # now the student should see it
        self.assertTrue(has_access(student_user, self.toy, 'load'))
Example #14
0
def get_course_with_access(user, action, course_key, depth=0, check_if_enrolled=False):
    """
    Given a course_key, look up the corresponding course descriptor,
    check that the user has the access to perform the specified action
    on the course, and return the descriptor.

    Raises a 404 if the course_key is invalid, or the user doesn't have access.

    depth: The number of levels of children for the modulestore to cache. None means infinite depth

    check_if_enrolled: If true, additionally verifies that the user is either enrolled in the course
      or has staff access.
    """
    assert isinstance(course_key, CourseKey)
    course = get_course_by_id(course_key, depth=depth)
    access_response = has_access(user, action, course, course_key)

    if not access_response:
        # Deliberately return a non-specific error message to avoid
        # leaking info about access control settings
        raise CoursewareAccessException(access_response)

    if check_if_enrolled:
        # Verify that the user is either enrolled in the course or a staff member.
        # If user is not enrolled, raise UserNotEnrolled exception that will be caught by middleware.
        if not ((user.id and CourseEnrollment.is_enrolled(user, course_key)) or has_access(user, 'staff', course)):
            raise UserNotEnrolled(course_key)

    return course
Example #15
0
    def wrapper(request, course_id):
        """
        Wraps the view function, performing access check, loading the course,
        and modifying the view's call signature.
        """
        course_key = CourseKey.from_string(course_id)
        ccx = None
        if isinstance(course_key, CCXLocator):
            ccx_id = course_key.ccx
            ccx = CustomCourseForEdX.objects.get(pk=ccx_id)
            course_key = ccx.course_id

        course = get_course_by_id(course_key, depth=None)
        is_staff = has_access(request.user, 'staff', course)
        is_instructor = has_access(request.user, 'instructor', course)

        if is_staff or is_instructor:
            # if user is staff or instructor then he can view ccx coach dashboard.
            return view(request, course, ccx)
        else:
            role = CourseCcxCoachRole(course_key)
            if not role.has_user(request.user):
                return HttpResponseForbidden(_('You must be a CCX Coach to access this view.'))

            # if there is a ccx, we must validate that it is the ccx for this coach
            if ccx is not None:
                coach_ccx = get_ccx_by_ccx_id(course, request.user, ccx.id)
                if coach_ccx is None:
                    return HttpResponseForbidden(
                        _('You must be the coach for this ccx to access this view')
                    )

        return view(request, course, ccx)
Example #16
0
def mktg_course_about(request, course_id):
    """This is the button that gets put into an iframe on the Drupal site."""
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)

    try:
        permission_name = microsite.get_value(
            'COURSE_ABOUT_VISIBILITY_PERMISSION',
            settings.COURSE_ABOUT_VISIBILITY_PERMISSION
        )
        course = get_course_with_access(request.user, permission_name, course_key)
    except (ValueError, Http404):
        # If a course does not exist yet, display a "Coming Soon" button
        return render_to_response(
            'courseware/mktg_coming_soon.html', {'course_id': course_key.to_deprecated_string()}
        )

    registered = registered_for_course(course, request.user)

    if has_access(request.user, 'load', course):
        course_target = reverse('info', args=[course.id.to_deprecated_string()])
    else:
        course_target = reverse('about_course', args=[course.id.to_deprecated_string()])

    allow_registration = has_access(request.user, 'enroll', course)

    show_courseware_link = (has_access(request.user, 'load', course) or
                            settings.FEATURES.get('ENABLE_LMS_MIGRATION'))
    course_modes = CourseMode.modes_for_course_dict(course.id)

    context = {
        'course': course,
        'registered': registered,
        'allow_registration': allow_registration,
        'course_target': course_target,
        'show_courseware_link': show_courseware_link,
        'course_modes': course_modes,
    }

    if settings.FEATURES.get('ENABLE_MKTG_EMAIL_OPT_IN'):
        # Drupal will pass the organization's full name as a GET parameter. If no full name
        # is provided, the marketing iframe won't show the email opt-in checkbox.
        organization_full_name = request.GET.get('organization_full_name')
        context['organization_full_name'] = cgi.escape(organization_full_name) if organization_full_name else organization_full_name

    # The edx.org marketing site currently displays only in English.
    # To avoid displaying a different language in the register / access button,
    # we force the language to English.
    # However, OpenEdX installations with a different marketing front-end
    # may want to respect the language specified by the user or the site settings.
    force_english = settings.FEATURES.get('IS_EDX_DOMAIN', False)
    if force_english:
        translation.activate('en-us')

    try:
        return render_to_response('courseware/mktg_course_about.html', context)
    finally:
        # Just to be safe, reset the language if we forced it to be English.
        if force_english:
            translation.deactivate()
Example #17
0
 def user_can_access_course(self, user, course):
     """
     Determines if the user is staff or an instructor for the course.
     Always returns True if DEBUG mode is enabled.
     """
     return (settings.DEBUG
             or has_access(user, CourseStaffRole.ROLE, course)
             or has_access(user, CourseInstructorRole.ROLE, course))
Example #18
0
 def test_course_overview_unsupported_action(self):
     """
     Check that calling has_access with an unsupported action raises a
     ValueError.
     """
     overview = CourseOverview.get_from_id(self.course_default.id)
     with self.assertRaises(ValueError):
         access.has_access(self.user, "_non_existent_action", overview)
    def test_content_beta_period(self):
        """
        Check that beta-test access works for content.
        """
        # student user shouldn't see it
        self.assertFalse(has_access(self.normal_student, self.content, 'load', self.course.id))

        # now the student should see it
        self.assertTrue(has_access(self.beta_tester, self.content, 'load', self.course.id))
def instructor_dashboard_2(request, course_id):
    """ Display the instructor dashboard for a course. """

    course = get_course_by_id(course_id, depth=None)
    is_studio_course = (modulestore().get_modulestore_type(course_id) != XML_MODULESTORE_TYPE)

    access = {
        'admin': request.user.is_staff,
        'instructor': has_access(request.user, course, 'instructor'),
        'staff': has_access(request.user, course, 'staff'),
        'forum_admin': has_forum_access(
            request.user, course_id, FORUM_ROLE_ADMINISTRATOR
        ),
    }

    if not access['staff']:
        raise Http404()

    sections = [
        _section_course_info(course_id, access),
        _section_membership(course_id, access),
        _section_student_admin(course_id, access),
        _section_data_download(course_id, access),
        _section_analytics(course_id, access),
    ]

    if (settings.FEATURES.get('INDIVIDUAL_DUE_DATES') and access['instructor']):
        sections.insert(3, _section_extensions(course))

    # Gate access to course email by feature flag & by course-specific authorization
    if settings.FEATURES['ENABLE_INSTRUCTOR_EMAIL'] and \
       is_studio_course and CourseAuthorization.instructor_email_enabled(course_id):
        sections.append(_section_send_email(course_id, access, course))

    # Gate access to Metrics tab by featue flag and staff authorization
    if settings.FEATURES['CLASS_DASHBOARD'] and access['staff']:
        sections.append(_section_metrics(course_id, access))

    studio_url = None
    if is_studio_course:
        studio_url = get_cms_course_link(course)

    enrollment_count = sections[0]['enrollment_count']
    disable_buttons = False
    max_enrollment_for_buttons = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS")
    if max_enrollment_for_buttons is not None:
        disable_buttons = enrollment_count > max_enrollment_for_buttons

    context = {
        'course': course,
        'old_dashboard_url': reverse('instructor_dashboard', kwargs={'course_id': course_id}),
        'studio_url': studio_url,
        'sections': sections,
        'disable_buttons': disable_buttons,
    }

    return render_to_response('instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
Example #21
0
def course_about(request, course_id):
    """
    Display the course's about page.

    Assumes the course_id is in a valid format.
    """

    if microsite.get_value("ENABLE_MKTG_SITE", settings.FEATURES.get("ENABLE_MKTG_SITE", False)):
        raise Http404

    course = get_course_with_access(request.user, course_id, "see_exists")
    registered = registered_for_course(course, request.user)
    staff_access = has_access(request.user, course, "staff")
    studio_url = get_studio_url(course_id, "settings/details")

    if has_access(request.user, course, "load"):
        course_target = reverse("info", args=[course.id])
    else:
        course_target = reverse("about_course", args=[course.id])

    show_courseware_link = has_access(request.user, course, "load") or settings.FEATURES.get("ENABLE_LMS_MIGRATION")

    # Note: this is a flow for payment for course registration, not the Verified Certificate flow.
    registration_price = 0
    in_cart = False
    reg_then_add_to_cart_link = ""
    if settings.FEATURES.get("ENABLE_SHOPPING_CART") and settings.FEATURES.get("ENABLE_PAID_COURSE_REGISTRATION"):
        registration_price = CourseMode.min_course_price_for_currency(
            course_id, settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
        )
        if request.user.is_authenticated():
            cart = shoppingcart.models.Order.get_cart_for_user(request.user)
            in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_id)

        reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format(
            reg_url=reverse("register_user"), course_id=course.id
        )

    # see if we have already filled up all allowed enrollments
    is_course_full = CourseEnrollment.is_course_full(course)

    return render_to_response(
        "courseware/course_about.html",
        {
            "course": course,
            "staff_access": staff_access,
            "studio_url": studio_url,
            "registered": registered,
            "course_target": course_target,
            "registration_price": registration_price,
            "in_cart": in_cart,
            "reg_then_add_to_cart_link": reg_then_add_to_cart_link,
            "show_courseware_link": show_courseware_link,
            "is_course_full": is_course_full,
        },
    )
Example #22
0
def dashboard(request):
    user = request.user
    enrollments = CourseEnrollment.objects.filter(user=user)

    # Build our courses list for the user, but ignore any courses that no longer
    # exist (because the course IDs have changed). Still, we don't delete those
    # enrollments, because it could have been a data push snafu.
    courses = []
    for enrollment in enrollments:
        try:
            courses.append(course_from_id(enrollment.course_id))
        except ItemNotFoundError:
            log.error("User {0} enrolled in non-existent course {1}"
                      .format(user.username, enrollment.course_id))

    message = ""
    if not user.is_active:
        message = render_to_string('registration/activate_account_notice.html', {'email': user.email})

    # Global staff can see what courses errored on their dashboard
    staff_access = False
    errored_courses = {}
    if has_access(user, 'global', 'staff'):
        # Show any courses that errored on load
        staff_access = True
        errored_courses = modulestore().get_errored_courses()

    show_courseware_links_for = frozenset(course.id for course in courses
                                          if has_access(request.user, course, 'load'))

    cert_statuses = {course.id: cert_info(request.user, course) for course in courses}

    exam_registrations = {course.id: exam_registration_info(request.user, course) for course in courses}

    # Get the 3 most recent news
    top_news = _get_news(top=3) if not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False) else None
            
    # get info w.r.t ExternalAuthMap
    external_auth_map = None
    try:
        external_auth_map = ExternalAuthMap.objects.get(user=user)
    except ExternalAuthMap.DoesNotExist:
        pass

    context = {'courses': courses,
               'message': message,
               'external_auth_map': external_auth_map,
               'staff_access': staff_access,
               'errored_courses': errored_courses,
               'show_courseware_links_for': show_courseware_links_for,
               'cert_statuses': cert_statuses,
               'news': top_news,
               'exam_registrations': exam_registrations,
               }

    return render_to_response('dashboard.html', context)
Example #23
0
def update_forum_role_membership(request, course_id):
    """
    Modify user's forum role.

    The requesting user must be at least staff.
    Staff forum admins can access all roles EXCEPT for FORUM_ROLE_ADMINISTRATOR
        which is limited to instructors.
    No one can revoke an instructors FORUM_ROLE_ADMINISTRATOR status.

    Query parameters:
    - `email` is the target users email
    - `rolename` is one of [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA]
    - `action` is one of ['allow', 'revoke']
    """
    course = get_course_by_id(course_id)
    has_instructor_access = has_access(request.user, course, 'instructor')
    has_forum_admin = has_forum_access(
        request.user, course_id, FORUM_ROLE_ADMINISTRATOR
    )

    email = request.GET.get('email')
    rolename = request.GET.get('rolename')
    action = request.GET.get('action')

    # default roles require either (staff & forum admin) or (instructor)
    if not (has_forum_admin or has_instructor_access):
        return HttpResponseBadRequest(
            "Operation requires staff & forum admin or instructor access"
        )

    # EXCEPT FORUM_ROLE_ADMINISTRATOR requires (instructor)
    if rolename == FORUM_ROLE_ADMINISTRATOR and not has_instructor_access:
        return HttpResponseBadRequest("Operation requires instructor access.")

    if not rolename in [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA]:
        return HttpResponseBadRequest("Unrecognized rolename '{}'.".format(rolename))

    user = User.objects.get(email=email)
    target_is_instructor = has_access(user, course, 'instructor')
    # cannot revoke instructor
    if target_is_instructor and action == 'revoke' and rolename == FORUM_ROLE_ADMINISTRATOR:
        return HttpResponseBadRequest("Cannot revoke instructor forum admin privelages.")

    try:
        access.update_forum_role_membership(course_id, user, rolename, action)
    except Role.DoesNotExist:
        return HttpResponseBadRequest("Role does not exist.")

    response_payload = {
        'course_id': course_id,
        'action': action,
    }
    response = HttpResponse(
        json.dumps(response_payload), content_type="application/json"
    )
    return response
Example #24
0
def course_about(request, course_id):
    """
    Display the course's about page.

    Assumes the course_id is in a valid format.
    """

    if microsite.get_value(
        'ENABLE_MKTG_SITE',
        settings.FEATURES.get('ENABLE_MKTG_SITE', False)
    ):
        raise Http404
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course = get_course_with_access(request.user, 'see_exists', course_key)
    registered = registered_for_course(course, request.user)
    staff_access = has_access(request.user, 'staff', course)
    studio_url = get_studio_url(course_key, 'settings/details')

    if has_access(request.user, 'load', course):
        course_target = reverse('info', args=[course.id.to_deprecated_string()])
    else:
        course_target = reverse('about_course', args=[course.id.to_deprecated_string()])

    show_courseware_link = (has_access(request.user, 'load', course) or
                            settings.FEATURES.get('ENABLE_LMS_MIGRATION'))

    # Note: this is a flow for payment for course registration, not the Verified Certificate flow.
    registration_price = 0
    in_cart = False
    reg_then_add_to_cart_link = ""
    if (settings.FEATURES.get('ENABLE_SHOPPING_CART') and
        settings.FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION')):
        registration_price = CourseMode.min_course_price_for_currency(course_key,
                                                                      settings.PAID_COURSE_REGISTRATION_CURRENCY[0])
        if request.user.is_authenticated():
            cart = shoppingcart.models.Order.get_cart_for_user(request.user)
            in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_key)

        reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format(
            reg_url=reverse('register_user'), course_id=course.id.to_deprecated_string())

    # see if we have already filled up all allowed enrollments
    is_course_full = CourseEnrollment.is_course_full(course)

    return render_to_response('courseware/course_about.html', {
        'course': course,
        'staff_access': staff_access,
        'studio_url': studio_url,
        'registered': registered,
        'course_target': course_target,
        'registration_price': registration_price,
        'in_cart': in_cart,
        'reg_then_add_to_cart_link': reg_then_add_to_cart_link,
        'show_courseware_link': show_courseware_link,
        'is_course_full': is_course_full
    })
Example #25
0
def update_forum_role_membership(request, course_id):
    """
    Modify user's forum role.

    The requesting user must be at least staff.
    Staff forum admins can access all roles EXCEPT for FORUM_ROLE_ADMINISTRATOR
        which is limited to instructors.
    No one can revoke an instructors FORUM_ROLE_ADMINISTRATOR status.

    Query parameters:
    - `email` is the target users email
    - `rolename` is one of [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA]
    - `action` is one of ['allow', 'revoke']
    """
    course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course = get_course_by_id(course_id)
    has_instructor_access = has_access(request.user, 'instructor', course)
    has_forum_admin = has_forum_access(
        request.user, course_id, FORUM_ROLE_ADMINISTRATOR
    )

    unique_student_identifier = request.GET.get('unique_student_identifier')
    rolename = request.GET.get('rolename')
    action = request.GET.get('action')

    # default roles require either (staff & forum admin) or (instructor)
    if not (has_forum_admin or has_instructor_access):
        return HttpResponseBadRequest(
            "Operation requires staff & forum admin or instructor access"
        )

    # EXCEPT FORUM_ROLE_ADMINISTRATOR requires (instructor)
    if rolename == FORUM_ROLE_ADMINISTRATOR and not has_instructor_access:
        return HttpResponseBadRequest("Operation requires instructor access.")

    if not rolename in [FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA]:
        return HttpResponseBadRequest(strip_tags(
            "Unrecognized rolename '{}'.".format(rolename)
        ))

    user = get_student_from_identifier(unique_student_identifier)
    target_is_instructor = has_access(user, 'instructor', course)
    # cannot revoke instructor
    if target_is_instructor and action == 'revoke' and rolename == FORUM_ROLE_ADMINISTRATOR:
        return HttpResponseBadRequest("Cannot revoke instructor forum admin privileges.")

    try:
        update_forum_role(course_id, user, rolename, action)
    except Role.DoesNotExist:
        return HttpResponseBadRequest("Role does not exist.")

    response_payload = {
        'course_id': course_id.to_deprecated_string(),
        'action': action,
    }
    return JsonResponse(response_payload)
Example #26
0
def check_course_access(course, user, action, check_if_enrolled=False, check_survey_complete=True):
    """
    Check that the user has the access to perform the specified action
    on the course (CourseDescriptor|CourseOverview).

    check_if_enrolled: If true, additionally verifies that the user is enrolled.
    check_survey_complete: If true, additionally verifies that the user has completed the survey.
    """
    # Allow staff full access to the course even if not enrolled
    if has_access(user, 'staff', course.id):
        return

    request = get_current_request()
    check_content_start_date_for_masquerade_user(course.id, user, request, course.start)

    access_response = has_access(user, action, course, course.id)
    if not access_response:
        # Redirect if StartDateError
        if isinstance(access_response, StartDateError):
            start_date = strftime_localized(course.start, 'SHORT_DATE')
            params = QueryDict(mutable=True)
            params['notlive'] = start_date
            raise CourseAccessRedirect('{dashboard_url}?{params}'.format(
                dashboard_url=reverse('dashboard'),
                params=params.urlencode()
            ), access_response)

        # Redirect if AuditExpiredError
        if isinstance(access_response, AuditExpiredError):
            params = QueryDict(mutable=True)
            params['access_response_error'] = access_response.additional_context_user_message
            raise CourseAccessRedirect('{dashboard_url}?{params}'.format(
                dashboard_url=reverse('dashboard'),
                params=params.urlencode()
            ), access_response)

        # Redirect if the user must answer a survey before entering the course.
        if isinstance(access_response, MilestoneAccessError):
            raise CourseAccessRedirect('{dashboard_url}'.format(
                dashboard_url=reverse('dashboard'),
            ), access_response)

        # Deliberately return a non-specific error message to avoid
        # leaking info about access control settings
        raise CoursewareAccessException(access_response)

    if check_if_enrolled:
        # If the user is not enrolled, redirect them to the about page
        if not CourseEnrollment.is_enrolled(user, course.id):
            raise CourseAccessRedirect(reverse('about_course', args=[unicode(course.id)]))

    # Redirect if the user must answer a survey before entering the course.
    if check_survey_complete and action == 'load':
        if is_survey_required_and_unanswered(user, course):
            raise CourseAccessRedirect(reverse('course_survey', args=[unicode(course.id)]))
Example #27
0
def instructor_dashboard_2(request, course_id):
    """Display the instructor dashboard for a course."""

    course = get_course_by_id(course_id, depth=None)
    is_studio_course = modulestore().get_modulestore_type(course_id) == MONGO_MODULESTORE_TYPE

    access = {
        "admin": request.user.is_staff,
        "instructor": has_access(request.user, course, "instructor"),
        "staff": has_access(request.user, course, "staff"),
        "forum_admin": has_forum_access(request.user, course_id, FORUM_ROLE_ADMINISTRATOR),
    }

    if not access["staff"]:
        raise Http404()

    sections = [
        _section_course_info(course_id, access),
        _section_membership(course_id, access),
        _section_student_admin(course_id, access),
        _section_data_download(course_id, access),
        _section_analytics(course_id, access),
    ]

    if settings.FEATURES.get("INDIVIDUAL_DUE_DATES") and access["instructor"]:
        sections.insert(3, _section_extensions(course))

    # Gate access to course email by feature flag & by course-specific authorization
    if (
        settings.FEATURES["ENABLE_INSTRUCTOR_EMAIL"]
        and is_studio_course
        and CourseAuthorization.instructor_email_enabled(course_id)
    ):
        sections.append(_section_send_email(course_id, access, course))

    studio_url = None
    if is_studio_course:
        studio_url = get_cms_course_link(course)

    enrollment_count = sections[0]["enrollment_count"]
    disable_buttons = False
    max_enrollment_for_buttons = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS")
    if max_enrollment_for_buttons is not None:
        disable_buttons = enrollment_count > max_enrollment_for_buttons

    context = {
        "course": course,
        "old_dashboard_url": reverse("instructor_dashboard", kwargs={"course_id": course_id}),
        "studio_url": studio_url,
        "sections": sections,
        "disable_buttons": disable_buttons,
    }

    return render_to_response("instructor/instructor_dashboard_2/instructor_dashboard_2.html", context)
    def test_course_beta_period(self):
        """
        Check that beta-test access works for courses.
        """
        self.assertFalse(self.course.has_started())

        # student user shouldn't see it
        self.assertFalse(has_access(self.normal_student, self.course, 'load'))

        # now the student should see it
        self.assertTrue(has_access(self.beta_tester, self.course, 'load'))
 def test_has_access_with_preview_mode(self):
     """
     Tests particular user's can access content via has_access in preview mode.
     """
     self.assertTrue(bool(access.has_access(self.global_staff, 'staff', self.course, course_key=self.course.id)))
     self.assertTrue(bool(access.has_access(self.course_staff, 'staff', self.course, course_key=self.course.id)))
     self.assertTrue(bool(access.has_access(
         self.course_instructor, 'staff', self.course, course_key=self.course.id
     )))
     self.assertFalse(bool(access.has_access(self.student, 'staff', self.course, course_key=self.course.id)))
     self.assertFalse(bool(access.has_access(self.student, 'load', self.course, course_key=self.course.id)))
Example #30
0
def mktg_course_about(request, course_id):
    """
    This is the button that gets put into an iframe on the Drupal site
    """

    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)

    try:
        course = get_course_with_access(request.user, 'see_exists', course_key)
    except (ValueError, Http404) as e:
        # if a course does not exist yet, display a coming
        # soon button
        return render_to_response(
            'courseware/mktg_coming_soon.html', {'course_id': course_key.to_deprecated_string()}
        )

    registered = registered_for_course(course, request.user)

    if has_access(request.user, 'load', course):
        course_target = reverse('info', args=[course.id.to_deprecated_string()])
    else:
        course_target = reverse('about_course', args=[course.id.to_deprecated_string()])

    allow_registration = has_access(request.user, 'enroll', course)

    show_courseware_link = (has_access(request.user, 'load', course) or
                            settings.FEATURES.get('ENABLE_LMS_MIGRATION'))
    course_modes = CourseMode.modes_for_course_dict(course.id)

    context = {
        'course': course,
        'registered': registered,
        'allow_registration': allow_registration,
        'course_target': course_target,
        'show_courseware_link': show_courseware_link,
        'course_modes': course_modes,
    }

    # The edx.org marketing site currently displays only in English.
    # To avoid displaying a different language in the register / access button,
    # we force the language to English.
    # However, OpenEdX installations with a different marketing front-end
    # may want to respect the language specified by the user or the site settings.
    force_english = settings.FEATURES.get('IS_EDX_DOMAIN', False)
    if force_english:
        translation.activate('en-us')

    try:
        return render_to_response('courseware/mktg_course_about.html', context)
    finally:
        # Just to be safe, reset the language if we forced it to be English.
        if force_english:
            translation.deactivate()
Example #31
0
def forum_form_discussion(request, course_id):
    """
    Renders the main Discussion page, potentially filtered by a search query
    """
    nr_transaction = newrelic.agent.current_transaction()

    course = get_course_with_access(request.user, course_id, 'load_forum')
    with newrelic.agent.FunctionTrace(nr_transaction,
                                      "get_discussion_category_map"):
        category_map = utils.get_discussion_category_map(course)

    try:
        unsafethreads, query_params = get_threads(
            request, course_id)  # This might process a search query
        threads = [utils.safe_content(thread) for thread in unsafethreads]
    except cc.utils.CommentClientMaintenanceError:
        log.warning("Forum is in maintenance mode")
        return render_to_response('discussion/maintenance.html', {})

    user = cc.User.from_django_user(request.user)
    user_info = user.to_dict()

    with newrelic.agent.FunctionTrace(nr_transaction,
                                      "get_metadata_for_threads"):
        annotated_content_info = utils.get_metadata_for_threads(
            course_id, threads, request.user, user_info)

    with newrelic.agent.FunctionTrace(nr_transaction,
                                      "add_courseware_context"):
        add_courseware_context(threads, course)

    if request.is_ajax():
        return utils.JsonResponse({
            'discussion_data':
            threads,  # TODO: Standardize on 'discussion_data' vs 'threads'
            'annotated_content_info': annotated_content_info,
            'num_pages': query_params['num_pages'],
            'page': query_params['page'],
        })
    else:
        #recent_active_threads = cc.search_recent_active_threads(
        #    course_id,
        #    recursive=False,
        #    query_params={'follower_id': request.user.id},
        #)

        #trending_tags = cc.search_trending_tags(
        #    course_id,
        #)
        with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
            cohorts = get_course_cohorts(course_id)
            cohorted_commentables = get_cohorted_commentables(course_id)

            user_cohort_id = get_cohort_id(request.user, course_id)

        context = {
            'csrf':
            csrf(request)['csrf_token'],
            'course':
            course,
            #'recent_active_threads': recent_active_threads,
            #'trending_tags': trending_tags,
            'staff_access':
            has_access(request.user, course, 'staff'),
            'threads':
            saxutils.escape(json.dumps(threads), escapedict),
            'thread_pages':
            query_params['num_pages'],
            'user_info':
            saxutils.escape(json.dumps(user_info), escapedict),
            'flag_moderator':
            cached_has_permission(request.user, 'openclose_thread', course.id)
            or has_access(request.user, course, 'staff'),
            'annotated_content_info':
            saxutils.escape(json.dumps(annotated_content_info), escapedict),
            'course_id':
            course.id,
            'category_map':
            category_map,
            'roles':
            saxutils.escape(json.dumps(utils.get_role_ids(course_id)),
                            escapedict),
            'is_moderator':
            cached_has_permission(request.user, "see_all_cohorts", course_id),
            'cohorts':
            cohorts,
            'user_cohort':
            user_cohort_id,
            'cohorted_commentables':
            cohorted_commentables,
            'is_course_cohorted':
            is_course_cohorted(course_id)
        }
        # print "start rendering.."
        return render_to_response('discussion/index.html', context)
Example #32
0
    def test_has_access_in_preview_mode_with_group(self):
        """
        Test that a user masquerading as a member of a group sees appropriate content in preview mode.
        """
        # Note about UserPartition and UserPartition Group IDs: these must not conflict with IDs used
        # by dynamic user partitions.
        partition_id = MINIMUM_STATIC_PARTITION_ID
        group_0_id = MINIMUM_STATIC_PARTITION_ID + 1
        group_1_id = MINIMUM_STATIC_PARTITION_ID + 2
        user_partition = UserPartition(
            partition_id,
            'Test User Partition',
            '', [Group(group_0_id, 'Group 1'),
                 Group(group_1_id, 'Group 2')],
            scheme_id='cohort')
        self.course.user_partitions.append(user_partition)
        self.course.cohort_config = {'cohorted': True}

        chapter = ItemFactory.create(category="chapter",
                                     parent_location=self.course.location)
        chapter.group_access = {partition_id: [group_0_id]}

        modulestore().update_item(self.course, ModuleStoreEnum.UserID.test)

        # User should not be able to preview when masquerading as student (and not in the group above).
        with patch('courseware.access.get_user_role') as mock_user_role:
            mock_user_role.return_value = 'student'
            self.assertFalse(
                bool(
                    access.has_access(self.global_staff,
                                      'load',
                                      chapter,
                                      course_key=self.course.id)))

        # Should be able to preview when in staff or instructor role.
        for mocked_role in ['staff', 'instructor']:
            with patch('courseware.access.get_user_role') as mock_user_role:
                mock_user_role.return_value = mocked_role
                self.assertTrue(
                    bool(
                        access.has_access(self.global_staff,
                                          'load',
                                          chapter,
                                          course_key=self.course.id)))

        # Now install masquerade group and set staff as a member of that.
        self.assertEqual(
            200,
            masquerade_as_group_member(self.global_staff, self.course,
                                       partition_id, group_0_id))
        # Can load the chapter since user is in the group.
        self.assertTrue(
            bool(
                access.has_access(self.global_staff,
                                  'load',
                                  chapter,
                                  course_key=self.course.id)))

        # Move the user to be a part of the second group.
        self.assertEqual(
            200,
            masquerade_as_group_member(self.global_staff, self.course,
                                       partition_id, group_1_id))
        # Cannot load the chapter since user is in a different group.
        self.assertFalse(
            bool(
                access.has_access(self.global_staff,
                                  'load',
                                  chapter,
                                  course_key=self.course.id)))
Example #33
0
def forum_form_discussion(request, course_key):
    """
    Renders the main Discussion page, potentially filtered by a search query
    """
    nr_transaction = newrelic.agent.current_transaction()

    course = get_course_with_access(request.user,
                                    'load',
                                    course_key,
                                    check_if_enrolled=True)
    course_settings = make_course_settings(course, request.user)

    user = cc.User.from_django_user(request.user)
    user_info = user.to_dict()

    try:
        unsafethreads, query_params = get_threads(
            request, course)  # This might process a search query
        is_staff = has_permission(request.user, 'openclose_thread', course.id)
        threads = [
            utils.prepare_content(thread, course_key, is_staff)
            for thread in unsafethreads
        ]
    except cc.utils.CommentClientMaintenanceError:
        log.warning("Forum is in maintenance mode")
        return render_to_response('discussion/maintenance.html', {})
    except ValueError:
        return HttpResponseBadRequest("Invalid group_id")

    with newrelic.agent.FunctionTrace(nr_transaction,
                                      "get_metadata_for_threads"):
        annotated_content_info = utils.get_metadata_for_threads(
            course_key, threads, request.user, user_info)

    with newrelic.agent.FunctionTrace(nr_transaction,
                                      "add_courseware_context"):
        add_courseware_context(threads, course, request.user)

    if request.is_ajax():
        return utils.JsonResponse({
            'discussion_data':
            threads,  # TODO: Standardize on 'discussion_data' vs 'threads'
            'annotated_content_info':
            annotated_content_info,
            'num_pages':
            query_params['num_pages'],
            'page':
            query_params['page'],
            'corrected_text':
            query_params['corrected_text'],
        })
    else:
        with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
            user_cohort_id = get_cohort_id(request.user, course_key)

        context = {
            'csrf':
            csrf(request)['csrf_token'],
            'course':
            course,
            #'recent_active_threads': recent_active_threads,
            'staff_access':
            bool(has_access(request.user, 'staff', course)),
            'threads':
            _attr_safe_json(threads),
            'thread_pages':
            query_params['num_pages'],
            'user_info':
            _attr_safe_json(user_info),
            'can_create_comment':
            _attr_safe_json(
                has_permission(request.user, "create_comment", course.id)),
            'can_create_subcomment':
            _attr_safe_json(
                has_permission(request.user, "create_sub_comment", course.id)),
            'can_create_thread':
            has_permission(request.user, "create_thread", course.id),
            'flag_moderator':
            bool(
                has_permission(request.user, 'openclose_thread', course.id)
                or has_access(request.user, 'staff', course)),
            'annotated_content_info':
            _attr_safe_json(annotated_content_info),
            'course_id':
            course.id.to_deprecated_string(),
            'roles':
            _attr_safe_json(utils.get_role_ids(course_key)),
            'is_moderator':
            has_permission(request.user, "see_all_cohorts", course_key),
            'cohorts':
            course_settings[
                "cohorts"],  # still needed to render _thread_list_template
            'user_cohort':
            user_cohort_id,  # read from container in NewPostView
            'is_course_cohorted':
            is_course_cohorted(
                course_key),  # still needed to render _thread_list_template
            'sort_preference':
            user.default_sort_key,
            'category_map':
            course_settings["category_map"],
            'course_settings':
            _attr_safe_json(course_settings)
        }
        # print "start rendering.."
        return render_to_response('discussion/index.html', context)
Example #34
0
def index(request, course_id, chapter=None, section=None, position=None):
    """
    Displays courseware accordion and associated content.  If course, chapter,
    and section are all specified, renders the page, or returns an error if they
    are invalid.

    If section is not specified, displays the accordion opened to the right chapter.

    If neither chapter or section are specified, redirects to user's most recent
    chapter, or the first chapter if this is the user's first visit.

    Arguments:

     - request    : HTTP request
     - course_id  : course id (str: ORG/course/URL_NAME)
     - chapter    : chapter url_name (str)
     - section    : section url_name (str)
     - position   : position in module, eg of <sequential> module (str)

    Returns:

     - HTTPresponse
    """
    user = User.objects.prefetch_related("groups").get(id=request.user.id)
    request.user = user  # keep just one instance of User
    course = get_course_with_access(user, course_id, 'load', depth=2)
    staff_access = has_access(user, course, 'staff')
    registered = registered_for_course(course, user)
    if not registered:
        # TODO (vshnayder): do course instructors need to be registered to see course?
        log.debug('User %s tried to view course %s but is not enrolled' %
                  (user, course.location.url()))
        return redirect(reverse('about_course', args=[course.id]))

    masq = setup_masquerade(request, staff_access)

    try:
        field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
            course.id, user, course, depth=2)

        course_module = get_module_for_descriptor(user, request, course,
                                                  field_data_cache, course.id)
        if course_module is None:
            log.warning(
                'If you see this, something went wrong: if we got this'
                ' far, should have gotten a course module for this user')
            return redirect(reverse('about_course', args=[course.id]))

        if chapter is None:
            return redirect_to_course_position(course_module)

        context = {
            'csrf':
            csrf(request)['csrf_token'],
            'accordion':
            render_accordion(request, course, chapter, section,
                             field_data_cache),
            'COURSE_TITLE':
            course.display_name_with_default,
            'course':
            course,
            'init':
            '',
            'content':
            '',
            'staff_access':
            staff_access,
            'masquerade':
            masq,
            'xqa_server':
            settings.MITX_FEATURES.get(
                'USE_XQA_SERVER',
                'http://*****:*****@content-qa.mitx.mit.edu/xqa')
        }

        # Only show the chat if it's enabled by the course and in the
        # settings.
        show_chat = course.show_chat and settings.MITX_FEATURES['ENABLE_CHAT']
        if show_chat:
            context['chat'] = chat_settings(course, user)
            # If we couldn't load the chat settings, then don't show
            # the widget in the courseware.
            if context['chat'] is None:
                show_chat = False

        context['show_chat'] = show_chat

        chapter_descriptor = course.get_child_by(
            lambda m: m.url_name == chapter)
        if chapter_descriptor is not None:
            save_child_position(course_module, chapter)
        else:
            raise Http404(
                'No chapter descriptor found with name {}'.format(chapter))

        chapter_module = course_module.get_child_by(
            lambda m: m.url_name == chapter)
        if chapter_module is None:
            # User may be trying to access a chapter that isn't live yet
            if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                log.debug('staff masq as student: no chapter %s' % chapter)
                return redirect(reverse('courseware', args=[course.id]))
            raise Http404

        if section is not None:
            section_descriptor = chapter_descriptor.get_child_by(
                lambda m: m.url_name == section)
            if section_descriptor is None:
                # Specifically asked-for section doesn't exist
                if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                    log.debug('staff masq as student: no section %s' % section)
                    return redirect(reverse('courseware', args=[course.id]))
                raise Http404

            # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
            # which will prefetch the children more efficiently than doing a recursive load
            section_descriptor = modulestore().get_instance(
                course.id, section_descriptor.location, depth=None)

            # Load all descendants of the section, because we're going to display its
            # html, which in general will need all of its children
            section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                course_id, user, section_descriptor, depth=None)

            section_module = get_module_for_descriptor(
                request.user, request, section_descriptor,
                section_field_data_cache, course_id, position)

            if section_module is None:
                # User may be trying to be clever and access something
                # they don't have access to.
                raise Http404

            # Save where we are in the chapter
            save_child_position(chapter_module, section)

            # check here if this section *is* a timed module.
            if section_module.category == 'timelimit':
                timer_context = update_timelimit_module(
                    user, course_id, student_module_cache, section_descriptor,
                    section_module)
                if 'timer_expiration_duration' in timer_context:
                    context.update(timer_context)
                else:
                    # if there is no expiration defined, then we know the timer has expired:
                    return HttpResponseRedirect(
                        timer_context['time_expired_redirect_url'])
            else:
                # check here if this page is within a course that has an active timed module running.  If so, then
                # add in the appropriate timer information to the rendering context:
                context.update(
                    check_for_active_timelimit_module(request, course_id,
                                                      course))

            context['content'] = section_module.runtime.render(
                section_module, None, 'student_view').content
        else:
            # section is none, so display a message
            prev_section = get_current_child(chapter_module)
            if prev_section is None:
                # Something went wrong -- perhaps this chapter has no sections visible to the user
                raise Http404
            prev_section_url = reverse('courseware_section',
                                       kwargs={
                                           'course_id': course_id,
                                           'chapter':
                                           chapter_descriptor.url_name,
                                           'section': prev_section.url_name
                                       })
            context['content'] = render_to_string(
                'courseware/welcome-back.html', {
                    'course': course,
                    'chapter_module': chapter_module,
                    'prev_section': prev_section,
                    'prev_section_url': prev_section_url
                })

        result = render_to_response('courseware/courseware.html', context)
    except Exception as e:
        if isinstance(e, Http404):
            # let it propagate
            raise

        # In production, don't want to let a 500 out for any reason
        if settings.DEBUG:
            raise
        else:
            log.exception("Error in index view: user={user}, course={course},"
                          " chapter={chapter} section={section}"
                          "position={position}".format(user=user,
                                                       course=course,
                                                       chapter=chapter,
                                                       section=section,
                                                       position=position))
            try:
                result = render_to_response('courseware/courseware-error.html',
                                            {
                                                'staff_access': staff_access,
                                                'course': course
                                            })
            except:
                # Let the exception propagate, relying on global config to at
                # at least return a nice error message
                log.exception("Error while rendering courseware-error page")
                raise

    return result
Example #35
0
def instructor_dashboard_2(request, course_id):
    """ Display the instructor dashboard for a course. """
    try:
        course_key = CourseKey.from_string(course_id)
    except InvalidKeyError:
        log.error(
            u"Unable to find course with course key %s while loading the Instructor Dashboard.",
            course_id)
        return HttpResponseServerError()

    course = get_course_by_id(course_key, depth=0)

    access = {
        'admin':
        request.user.is_staff,
        'instructor':
        bool(has_access(request.user, 'instructor', course)),
        'finance_admin':
        CourseFinanceAdminRole(course_key).has_user(request.user),
        'sales_admin':
        CourseSalesAdminRole(course_key).has_user(request.user),
        'staff':
        bool(has_access(request.user, 'staff', course)),
        'forum_admin':
        has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
    }

    if not access['staff']:
        raise Http404()

    is_white_label = CourseMode.is_white_label(course_key)

    sections = [
        _section_course_info(course, access),
        _section_membership(course, access, is_white_label),
        _section_cohort_management(course, access),
        _section_student_admin(course, access),
        _section_data_download(course, access),
    ]

    analytics_dashboard_message = None
    if settings.ANALYTICS_DASHBOARD_URL:
        # Construct a URL to the external analytics dashboard
        analytics_dashboard_url = '{0}/courses/{1}'.format(
            settings.ANALYTICS_DASHBOARD_URL, unicode(course_key))
        link_start = "<a href=\"{}\" target=\"_blank\">".format(
            analytics_dashboard_url)
        analytics_dashboard_message = _(
            "To gain insights into student enrollment and participation {link_start}"
            "visit {analytics_dashboard_name}, our new course analytics product{link_end}."
        )
        analytics_dashboard_message = analytics_dashboard_message.format(
            link_start=link_start,
            link_end="</a>",
            analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME)

        # Temporarily show the "Analytics" section until we have a better way of linking to Insights
        sections.append(_section_analytics(course, access))

    # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
    course_mode_has_price = False
    paid_modes = CourseMode.paid_modes_for_course(course_key)
    if len(paid_modes) == 1:
        course_mode_has_price = True
    elif len(paid_modes) > 1:
        log.error(
            u"Course %s has %s course modes with payment options. Course must only have "
            u"one paid course mode to enable eCommerce options.",
            unicode(course_key), len(paid_modes))

    if settings.FEATURES.get('INDIVIDUAL_DUE_DATES') and access['instructor']:
        sections.insert(3, _section_extensions(course))

    # Gate access to course email by feature flag & by course-specific authorization
    if bulk_email_is_enabled_for_course(course_key):
        sections.append(_section_send_email(course, access))

    # Gate access to Metrics tab by featue flag and staff authorization
    if settings.FEATURES['CLASS_DASHBOARD'] and access['staff']:
        sections.append(_section_metrics(course, access))

    # Gate access to Ecommerce tab
    if course_mode_has_price and (access['finance_admin']
                                  or access['sales_admin']):
        sections.append(
            _section_e_commerce(course, access, paid_modes[0], is_white_label,
                                is_white_label))

    # Gate access to Proctoring tab
    if settings.FEATURES.get('ENABLE_PROCTORED_EXAMS',
                             False) and course.enable_proctored_exams:
        sections.append(_section_proctoring(course, access))

    # Certificates panel
    # This is used to generate example certificates
    # and enable self-generated certificates for a course.
    certs_enabled = CertificateGenerationConfiguration.current().enabled
    if certs_enabled and access['admin']:
        sections.append(_section_certificates(course))

    disable_buttons = not _is_small_course(course_key)

    context = {
        'course':
        course,
        'old_dashboard_url':
        reverse('instructor_dashboard_legacy',
                kwargs={'course_id': unicode(course_key)}),
        'studio_url':
        get_studio_url(course, 'course'),
        'sections':
        sections,
        'disable_buttons':
        disable_buttons,
        'analytics_dashboard_message':
        analytics_dashboard_message
    }

    return render_to_response(
        'instructor/instructor_dashboard_2/instructor_dashboard_2.html',
        context)
Example #36
0
    def post(self, request, course_id):
        """Takes the form submission from the page and parses it.

        Args:
            request (`Request`): The Django Request object.
            course_id (unicode): The slash-separated course key.

        Returns:
            Status code 400 when the requested mode is unsupported. When the honor mode
            is selected, redirects to the dashboard. When the verified mode is selected,
            returns error messages if the indicated contribution amount is invalid or
            below the minimum, otherwise redirects to the verification flow.

        """
        course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        user = request.user

        # This is a bit redundant with logic in student.views.change_enrollment,
        # but I don't really have the time to refactor it more nicely and test.
        course = modulestore().get_course(course_key)
        if not has_access(user, 'enroll', course):
            error_msg = _("Enrollment is closed")
            return self.get(request, course_id, error=error_msg)

        requested_mode = self._get_requested_mode(request.POST)

        allowed_modes = CourseMode.modes_for_course_dict(course_key)
        if requested_mode not in allowed_modes:
            return HttpResponseBadRequest(_("Enrollment mode not supported"))

        if requested_mode == 'honor':
            # The user will have already been enrolled in the honor mode at this
            # point, so we just redirect them to the dashboard, thereby avoiding
            # hitting the database a second time attempting to enroll them.
            return redirect(reverse('dashboard'))

        mode_info = allowed_modes[requested_mode]

        if requested_mode == 'verified':
            amount = request.POST.get("contribution") or \
                request.POST.get("contribution-other-amt") or 0

            try:
                # Validate the amount passed in and force it into two digits
                amount_value = decimal.Decimal(amount).quantize(
                    decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
            except decimal.InvalidOperation:
                error_msg = _("Invalid amount selected.")
                return self.get(request, course_id, error=error_msg)

            # Check for minimum pricing
            if amount_value < mode_info.min_price:
                error_msg = _(
                    "No selected price or selected price is too low.")
                return self.get(request, course_id, error=error_msg)

            donation_for_course = request.session.get("donation_for_course",
                                                      {})
            donation_for_course[unicode(course_key)] = amount_value
            request.session["donation_for_course"] = donation_for_course

            return redirect(
                reverse('verify_student_start_flow',
                        kwargs={'course_id': unicode(course_key)}))
Example #37
0
def check_course_access(course,
                        user,
                        action,
                        check_if_enrolled=False,
                        check_survey_complete=True):
    """
    Check that the user has the access to perform the specified action
    on the course (CourseDescriptor|CourseOverview).

    check_if_enrolled: If true, additionally verifies that the user is enrolled.
    check_survey_complete: If true, additionally verifies that the user has completed the survey.
    """
    # Allow staff full access to the course even if not enrolled
    if has_access(user, 'staff', course.id):
        return

    request = get_current_request()
    check_content_start_date_for_masquerade_user(course.id, user, request,
                                                 course.start)

    access_response = has_access(user, action, course, course.id)
    if not access_response:
        # Redirect if StartDateError
        if isinstance(access_response, StartDateError):
            start_date = strftime_localized(course.start, 'SHORT_DATE')
            params = QueryDict(mutable=True)
            params['notlive'] = start_date
            raise CourseAccessRedirect(
                '{dashboard_url}?{params}'.format(
                    dashboard_url=reverse('dashboard'),
                    params=params.urlencode()), access_response)

        # Redirect if AuditExpiredError
        if isinstance(access_response, AuditExpiredError):
            params = QueryDict(mutable=True)
            params[
                'access_response_error'] = access_response.additional_context_user_message
            raise CourseAccessRedirect(
                '{dashboard_url}?{params}'.format(
                    dashboard_url=reverse('dashboard'),
                    params=params.urlencode()), access_response)

        # Redirect if the user must answer a survey before entering the course.
        if isinstance(access_response, MilestoneAccessError):
            raise CourseAccessRedirect(
                '{dashboard_url}'.format(dashboard_url=reverse('dashboard'), ),
                access_response)

        # Deliberately return a non-specific error message to avoid
        # leaking info about access control settings
        raise CoursewareAccessException(access_response)

    if check_if_enrolled:
        # If the user is not enrolled, redirect them to the about page
        if not CourseEnrollment.is_enrolled(user, course.id):
            raise CourseAccessRedirect(
                reverse('about_course', args=[six.text_type(course.id)]))

    # Redirect if the user must answer a survey before entering the course.
    if check_survey_complete and action == 'load':
        if is_survey_required_and_unanswered(user, course):
            raise CourseAccessRedirect(
                reverse('course_survey', args=[six.text_type(course.id)]))
def instructor_dashboard_2(request, course_id):
    """ Display the instructor dashboard for a course. """
    try:
        course_key = CourseKey.from_string(course_id)
    except InvalidKeyError:
        log.error(
            u"Unable to find course with course key %s while loading the Instructor Dashboard.",
            course_id)
        return HttpResponseServerError()

    course = get_course_by_id(course_key, depth=0)

    access = {
        'admin':
        request.user.is_staff,
        'instructor':
        bool(has_access(request.user, 'instructor', course)),
        'finance_admin':
        CourseFinanceAdminRole(course_key).has_user(request.user),
        'sales_admin':
        CourseSalesAdminRole(course_key).has_user(request.user),
        'staff':
        bool(has_access(request.user, 'staff', course)),
        'forum_admin':
        has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
    }

    if not access['staff']:
        raise Http404()

    is_white_label = CourseMode.is_white_label(course_key)

    reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS',
                                                      False)

    sections = [
        _section_course_info(course, access),
        _section_membership(course, access),
        _section_cohort_management(course, access),
        _section_discussions_management(course, access),
        _section_student_admin(course, access),
        _section_data_download(course, access),
    ]

    analytics_dashboard_message = None
    if show_analytics_dashboard_message(course_key):
        # Construct a URL to the external analytics dashboard
        analytics_dashboard_url = '{0}/courses/{1}'.format(
            settings.ANALYTICS_DASHBOARD_URL, six.text_type(course_key))
        link_start = HTML(u"<a href=\"{}\" target=\"_blank\">").format(
            analytics_dashboard_url)
        analytics_dashboard_message = _(
            u"To gain insights into student enrollment and participation {link_start}"
            u"visit {analytics_dashboard_name}, our new course analytics product{link_end}."
        )
        analytics_dashboard_message = Text(analytics_dashboard_message).format(
            link_start=link_start,
            link_end=HTML("</a>"),
            analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME)

        # Temporarily show the "Analytics" section until we have a better way of linking to Insights
        sections.append(_section_analytics(course, access))

    # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
    course_mode_has_price = False
    paid_modes = CourseMode.paid_modes_for_course(course_key)
    if len(paid_modes) == 1:
        course_mode_has_price = True
    elif len(paid_modes) > 1:
        log.error(
            u"Course %s has %s course modes with payment options. Course must only have "
            u"one paid course mode to enable eCommerce options.",
            six.text_type(course_key), len(paid_modes))

    if access['instructor'] and is_enabled_for_course(course_key):
        sections.insert(3, _section_extensions(course))

    # Gate access to course email by feature flag & by course-specific authorization
    if is_bulk_email_feature_enabled(course_key):
        sections.append(_section_send_email(course, access))

    # Gate access to Metrics tab by featue flag and staff authorization
    if settings.FEATURES['CLASS_DASHBOARD'] and access['staff']:
        sections.append(_section_metrics(course, access))

    # Gate access to Ecommerce tab
    if course_mode_has_price and (access['finance_admin']
                                  or access['sales_admin']):
        sections.append(
            _section_e_commerce(course, access, paid_modes[0], is_white_label,
                                reports_enabled))

    # Gate access to Special Exam tab depending if either timed exams or proctored exams
    # are enabled in the course

    user_has_access = any([
        request.user.is_staff,
        CourseStaffRole(course_key).has_user(request.user),
        CourseInstructorRole(course_key).has_user(request.user)
    ])
    course_has_special_exams = course.enable_proctored_exams or course.enable_timed_exams
    can_see_special_exams = course_has_special_exams and user_has_access and settings.FEATURES.get(
        'ENABLE_SPECIAL_EXAMS', False)

    if can_see_special_exams:
        sections.append(_section_special_exams(course, access))

    # Certificates panel
    # This is used to generate example certificates
    # and enable self-generated certificates for a course.
    # Note: This is hidden for all CCXs
    certs_enabled = CertificateGenerationConfiguration.current(
    ).enabled and not hasattr(course_key, 'ccx')
    if certs_enabled and access['admin']:
        sections.append(_section_certificates(course))

    openassessment_blocks = modulestore().get_items(
        course_key, qualifiers={'category': 'openassessment'})
    # filter out orphaned openassessment blocks
    openassessment_blocks = [
        block for block in openassessment_blocks if block.parent is not None
    ]
    if len(openassessment_blocks) > 0:
        sections.append(
            _section_open_response_assessment(request, course,
                                              openassessment_blocks, access))

    disable_buttons = not _is_small_course(course_key)

    certificate_white_list = CertificateWhitelist.get_certificate_white_list(
        course_key)
    generate_certificate_exceptions_url = reverse(
        'generate_certificate_exceptions',
        kwargs={
            'course_id': six.text_type(course_key),
            'generate_for': ''
        })
    generate_bulk_certificate_exceptions_url = reverse(
        'generate_bulk_certificate_exceptions',
        kwargs={'course_id': six.text_type(course_key)})
    certificate_exception_view_url = reverse(
        'certificate_exception_view',
        kwargs={'course_id': six.text_type(course_key)})

    certificate_invalidation_view_url = reverse(
        'certificate_invalidation_view',
        kwargs={'course_id': six.text_type(course_key)})

    certificate_invalidations = CertificateInvalidation.get_certificate_invalidations(
        course_key)

    context = {
        'course': course,
        'studio_url': get_studio_url(course, 'course'),
        'sections': sections,
        'disable_buttons': disable_buttons,
        'analytics_dashboard_message': analytics_dashboard_message,
        'certificate_white_list': certificate_white_list,
        'certificate_invalidations': certificate_invalidations,
        'generate_certificate_exceptions_url':
        generate_certificate_exceptions_url,
        'generate_bulk_certificate_exceptions_url':
        generate_bulk_certificate_exceptions_url,
        'certificate_exception_view_url': certificate_exception_view_url,
        'certificate_invalidation_view_url': certificate_invalidation_view_url,
    }

    return render_to_response(
        'instructor/instructor_dashboard_2/instructor_dashboard_2.html',
        context)
 def is_enabled(cls, course, user=None):
     """
     Returns true if the specified user has staff access.
     """
     return bool(user and has_access(user, 'staff', course, course.id))
Example #40
0
    def to_representation(self, course_overview):
        course_id = unicode(course_overview.id)
        request = self.context.get('request')
        return {
            # identifiers
            'id':
            course_id,
            'name':
            course_overview.display_name,
            'number':
            course_overview.display_number_with_default,
            'org':
            course_overview.display_org_with_default,

            # dates
            'start':
            course_overview.start,
            'start_display':
            course_overview.start_display,
            'start_type':
            course_overview.start_type,
            'end':
            course_overview.end,

            # notification info
            'subscription_id':
            course_overview.clean_id(padding_char='_'),

            # access info
            'courseware_access':
            has_access(request.user, 'load_mobile', course_overview).to_json(),

            # various URLs
            # course_image is sent in both new and old formats
            # (within media to be compatible with the new Course API)
            'media': {
                'course_image': {
                    'uri': course_overview.course_image_url,
                    'name': 'Course Image',
                }
            },
            'course_image':
            course_overview.course_image_url,
            'course_about':
            get_link_for_about_page(course_overview),
            'course_sharing_utm_parameters':
            get_encoded_course_sharing_utm_params(),
            'course_updates':
            reverse(
                'course-updates-list',
                kwargs={'course_id': course_id},
                request=request,
            ),
            'course_handouts':
            reverse(
                'course-handouts-list',
                kwargs={'course_id': course_id},
                request=request,
            ),
            'discussion_url':
            reverse(
                'discussion_course',
                kwargs={'course_id': course_id},
                request=request,
            ) if course_overview.is_discussion_tab_enabled() else None,
            'video_outline':
            reverse(
                'video-summary-list',
                kwargs={'course_id': course_id},
                request=request,
            ),
        }
Example #41
0
def get_module_for_descriptor_internal(
        user,
        descriptor,
        student_data,
        course_id,  # pylint: disable=invalid-name
        track_function,
        xqueue_callback_url_prefix,
        request_token,
        position=None,
        wrap_xmodule_display=True,
        grade_bucket_type=None,
        static_asset_path='',
        user_location=None,
        disable_staff_debug_info=False,
        course=None):
    """
    Actually implement get_module, without requiring a request.

    See get_module() docstring for further details.

    Arguments:
        request_token (str): A unique token for this request, used to isolate xblock rendering
    """

    (system, student_data) = get_module_system_for_user(
        user=user,
        student_data=
        student_data,  # These have implicit user bindings, the rest of args are considered not to
        descriptor=descriptor,
        course_id=course_id,
        track_function=track_function,
        xqueue_callback_url_prefix=xqueue_callback_url_prefix,
        position=position,
        wrap_xmodule_display=wrap_xmodule_display,
        grade_bucket_type=grade_bucket_type,
        static_asset_path=static_asset_path,
        user_location=user_location,
        request_token=request_token,
        disable_staff_debug_info=disable_staff_debug_info,
        course=course)

    descriptor.bind_for_student(
        system,
        user.id,
        [
            partial(OverrideFieldData.wrap, user, course),
            partial(LmsFieldData, student_data=student_data),
        ],
    )

    descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id)  # pylint: disable=protected-access

    # Do not check access when it's a noauth request.
    # Not that the access check needs to happen after the descriptor is bound
    # for the student, since there may be field override data for the student
    # that affects xblock visibility.
    if getattr(user, 'known', True):
        if not has_access(user, 'load', descriptor, course_id):
            return None

    return descriptor
Example #42
0
def combined_notifications(course, user):
    """
    Show notifications to a given user for a given course.  Get notifications from the cache if possible,
    or from the grading controller server if not.
    @param course: The course object for which we are getting notifications
    @param user: The user object for which we are getting notifications
    @return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification
    image), and response (actual response from grading controller server).
    """
    #Set up return values so that we can return them for error cases
    pending_grading = False
    img_path = ""
    notifications = {}
    notification_dict = {
        'pending_grading': pending_grading,
        'img_path': img_path,
        'response': notifications
    }

    #We don't want to show anonymous users anything.
    if not user.is_authenticated():
        return notification_dict

    #Define a mock modulesystem
    system = ModuleSystem(ajax_url=None,
                          track_function=None,
                          get_module=None,
                          render_template=render_to_string,
                          replace_urls=None,
                          xblock_model_data={})
    #Initialize controller query service using our mock system
    controller_qs = ControllerQueryService(
        settings.OPEN_ENDED_GRADING_INTERFACE, system)
    student_id = unique_id_for_user(user)
    user_is_staff = has_access(user, course, 'staff')
    course_id = course.id
    notification_type = "combined"

    #See if we have a stored value in the cache
    success, notification_dict = get_value_from_cache(student_id, course_id,
                                                      notification_type)
    if success:
        return notification_dict

    #Get the time of the last login of the user
    last_login = user.last_login
    last_time_viewed = last_login - datetime.timedelta(
        seconds=(NOTIFICATION_CACHE_TIME + 60))

    try:
        #Get the notifications from the grading controller
        controller_response = controller_qs.check_combined_notifications(
            course.id, student_id, user_is_staff, last_time_viewed)
        notifications = json.loads(controller_response)
        if notifications['success']:
            if notifications['staff_needs_to_grade'] or notifications[
                    'student_needs_to_peer_grade']:
                pending_grading = True
    except:
        #Non catastrophic error, so no real action
        #This is a dev_facing_error
        log.exception(
            "Problem with getting notifications from controller query service for course {0} user {1}."
            .format(course_id, student_id))

    if pending_grading:
        img_path = "/static/images/grading_notification.png"

    notification_dict = {
        'pending_grading': pending_grading,
        'img_path': img_path,
        'response': notifications
    }

    #Store the notifications in the cache
    set_value_in_cache(student_id, course_id, notification_type,
                       notification_dict)

    return notification_dict
Example #43
0
def get_module_system_for_user(
        user,
        student_data,  # TODO  # pylint: disable=too-many-statements
        # Arguments preceding this comment have user binding, those following don't
    descriptor,
        course_id,
        track_function,
        xqueue_callback_url_prefix,
        request_token,
        position=None,
        wrap_xmodule_display=True,
        grade_bucket_type=None,
        static_asset_path='',
        user_location=None,
        disable_staff_debug_info=False,
        course=None):
    """
    Helper function that returns a module system and student_data bound to a user and a descriptor.

    The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module,
    to allow an existing module to be re-bound to a user.  Most of the user bindings happen when creating the
    closures that feed the instantiation of ModuleSystem.

    The arguments fall into two categories: those that have explicit or implicit user binding, which are user
    and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which
    are all the other arguments.  Ultimately, this isn't too different than how get_module_for_descriptor_internal
    was before refactoring.

    Arguments:
        see arguments for get_module()
        request_token (str): A token unique to the request use by xblock initialization

    Returns:
        (LmsModuleSystem, KvsFieldData):  (module system, student_data) bound to, primarily, the user and descriptor
    """
    def make_xqueue_callback(dispatch='score_update'):
        """
        Returns fully qualified callback URL for external queueing system
        """
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(course_id=course_id.to_deprecated_string(),
                        userid=str(user.id),
                        mod_id=descriptor.location.to_deprecated_string(),
                        dispatch=dispatch),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': XQUEUE_INTERFACE,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor,
                                         "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface[
            'mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface[
            'mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key':
            getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key':
            getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name':
            getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user=user,
            descriptor=descriptor,
            student_data=student_data,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course)

    def _fulfill_content_milestones(user, course_key, content_key):
        """
        Internal helper to handle milestone fulfillments for the specified content module
        """
        # Fulfillment Use Case: Entrance Exam
        # If this module is part of an entrance exam, we'll need to see if the student
        # has reached the point at which they can collect the associated milestone
        if settings.FEATURES.get('ENTRANCE_EXAMS', False):
            course = modulestore().get_course(course_key)
            content = modulestore().get_item(content_key)
            entrance_exam_enabled = getattr(course, 'entrance_exam_enabled',
                                            False)
            in_entrance_exam = getattr(content, 'in_entrance_exam', False)
            if entrance_exam_enabled and in_entrance_exam:
                # We don't have access to the true request object in this context, but we can use a mock
                request = RequestFactory().request()
                request.user = user
                exam_pct = get_entrance_exam_score(request, course)
                if exam_pct >= course.entrance_exam_minimum_score_pct:
                    exam_key = UsageKey.from_string(course.entrance_exam_id)
                    relationship_types = milestones_helpers.get_milestone_relationship_types(
                    )
                    content_milestones = milestones_helpers.get_course_content_milestones(
                        course_key,
                        exam_key,
                        relationship=relationship_types['FULFILLS'])
                    # Add each milestone to the user's set...
                    user = {'id': request.user.id}
                    for milestone in content_milestones:
                        milestones_helpers.add_user_milestone(user, milestone)

    def handle_grade_event(block, event_type, event):  # pylint: disable=unused-argument
        """
        Manages the workflow for recording and updating of student module grade state
        """
        user_id = event.get('user_id', user.id)

        grade = event.get('value')
        max_grade = event.get('max_value')

        set_score(
            user_id,
            descriptor.location,
            grade,
            max_grade,
        )

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(grade, max_grade)

        tags = [
            u"org:{}".format(course_id.org), u"course:{}".format(course_id),
            u"score_bucket:{0}".format(score_bucket)
        ]

        if grade_bucket_type is not None:
            tags.append('type:%s' % grade_bucket_type)

        dog_stats_api.increment("lms.courseware.question_answered", tags=tags)

        # Cycle through the milestone fulfillment scenarios to see if any are now applicable
        # thanks to the updated grading information that was just submitted
        _fulfill_content_milestones(
            user,
            course_id,
            descriptor.location,
        )

        # Send a signal out to any listeners who are waiting for score change
        # events.
        SCORE_CHANGED.send(sender=None,
                           points_possible=event['max_value'],
                           points_earned=event['value'],
                           user_id=user_id,
                           course_id=unicode(course_id),
                           usage_id=unicode(descriptor.location))

    def publish(block, event_type, event):
        """A function that allows XModules to publish events."""
        if event_type == 'grade' and not is_masquerading_as_specific_student(
                user, course_id):
            handle_grade_event(block, event_type, event)
        else:
            track_function(event_type, event)

    def rebind_noauth_module_to_user(module, real_user):
        """
        A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser.

        Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler.

        Arguments:
            module (any xblock type):  the module to rebind
            real_user (django.contrib.auth.models.User):  the user to bind to

        Returns:
            nothing (but the side effect is that module is re-bound to real_user)
        """
        if user.is_authenticated():
            err_msg = (
                "rebind_noauth_module_to_user can only be called from a module bound to "
                "an anonymous user")
            log.error(err_msg)
            raise LmsModuleRenderError(err_msg)

        field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents(
            course_id,
            real_user,
            module.descriptor,
            asides=XBlockAsidesConfig.possible_asides(),
        )
        student_data_real_user = KvsFieldData(
            DjangoKeyValueStore(field_data_cache_real_user))

        (inner_system, inner_student_data) = get_module_system_for_user(
            user=real_user,
            student_data=
            student_data_real_user,  # These have implicit user bindings, rest of args considered not to
            descriptor=module.descriptor,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course)

        module.descriptor.bind_for_student(
            inner_system,
            real_user.id,
            [
                partial(OverrideFieldData.wrap, real_user, course),
                partial(LmsFieldData, student_data=inner_student_data),
            ],
        )

        module.descriptor.scope_ids = (
            module.descriptor.scope_ids._replace(user_id=real_user.id)  # pylint: disable=protected-access
        )
        module.scope_ids = module.descriptor.scope_ids  # this is needed b/c NamedTuples are immutable
        # now bind the module to the new ModuleSystem instance and vice-versa
        module.runtime = inner_system
        inner_system.xmodule_instance = module

    # Build a list of wrapping functions that will be applied in order
    # to the Fragment content coming out of the xblocks that are about to be rendered.
    block_wrappers = []

    if is_masquerading_as_specific_student(user, course_id):
        block_wrappers.append(filter_displayed_blocks)

    if settings.FEATURES.get("LICENSING", False):
        block_wrappers.append(wrap_with_license)

    # Wrap the output display in a single div to allow for the XModule
    # javascript to be bound correctly
    if wrap_xmodule_display is True:
        block_wrappers.append(
            partial(
                wrap_xblock,
                'LmsRuntime',
                extra_data={'course-id': course_id.to_deprecated_string()},
                usage_id_serializer=lambda usage_id: quote_slashes(
                    usage_id.to_deprecated_string()),
                request_token=request_token,
            ))

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from

    # Rewrite urls beginning in /static to point to course-specific content
    block_wrappers.append(
        partial(replace_static_urls,
                getattr(descriptor, 'data_dir', None),
                course_id=course_id,
                static_asset_path=static_asset_path
                or descriptor.static_asset_path))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    block_wrappers.append(partial(replace_course_urls, course_id))

    # this will rewrite intra-courseware links (/jump_to_id/<id>). This format
    # is an improvement over the /course/... format for studio authored courses,
    # because it is agnostic to course-hierarchy.
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work.
    block_wrappers.append(
        partial(
            replace_jump_to_id_urls,
            course_id,
            reverse('jump_to_id',
                    kwargs={
                        'course_id': course_id.to_deprecated_string(),
                        'module_id': ''
                    }),
        ))

    if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'):
        if is_masquerading_as_specific_student(user, course_id):
            # When masquerading as a specific student, we want to show the debug button
            # unconditionally to enable resetting the state of the student we are masquerading as.
            # We already know the user has staff access when masquerading is active.
            staff_access = True
            # To figure out whether the user has instructor access, we temporarily remove the
            # masquerade_settings from the real_user.  With the masquerading settings in place,
            # the result would always be "False".
            masquerade_settings = user.real_user.masquerade_settings
            del user.real_user.masquerade_settings
            instructor_access = has_access(user.real_user, 'instructor',
                                           descriptor, course_id)
            user.real_user.masquerade_settings = masquerade_settings
        else:
            staff_access = has_access(user, 'staff', descriptor, course_id)
            instructor_access = has_access(user, 'instructor', descriptor,
                                           course_id)
        if staff_access:
            block_wrappers.append(
                partial(add_staff_markup, user, instructor_access,
                        disable_staff_debug_info))

    # These modules store data using the anonymous_student_id as a key.
    # To prevent loss of data, we will continue to provide old modules with
    # the per-student anonymized id (as we have in the past),
    # while giving selected modules a per-course anonymized id.
    # As we have the time to manually test more modules, we can add to the list
    # of modules that get the per-course anonymized id.
    is_pure_xblock = isinstance(
        descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor)
    module_class = getattr(descriptor, 'module_class', None)
    is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule)
    if is_pure_xblock or is_lti_module:
        anonymous_student_id = anonymous_id_for_user(user, course_id)
    else:
        anonymous_student_id = anonymous_id_for_user(user, None)

    field_data = LmsFieldData(descriptor._field_data, student_data)  # pylint: disable=protected-access

    user_is_staff = has_access(user, u'staff', descriptor.location, course_id)

    system = LmsModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        static_url=settings.STATIC_URL,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.runtime.resources_fs,
        get_module=inner_get_module,
        user=user,
        debug=settings.DEBUG,
        hostname=settings.SITE_NAME,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_id=course_id,
            static_asset_path=static_asset_path
            or descriptor.static_asset_path,
        ),
        replace_course_urls=partial(static_replace.replace_course_urls,
                                    course_key=course_id),
        replace_jump_to_id_urls=partial(
            static_replace.replace_jump_to_id_urls,
            course_id=course_id,
            jump_to_id_base_url=reverse('jump_to_id',
                                        kwargs={
                                            'course_id':
                                            course_id.to_deprecated_string(),
                                            'module_id':
                                            ''
                                        })),
        node_path=settings.NODE_PATH,
        publish=publish,
        anonymous_student_id=anonymous_student_id,
        course_id=course_id,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
        get_python_lib_zip=(
            lambda: get_python_lib_zip(contentstore, course_id)),
        # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
        mixins=descriptor.runtime.mixologist._mixins,  # pylint: disable=protected-access
        wrappers=block_wrappers,
        get_real_user=user_by_anonymous_id,
        services={
            'i18n': ModuleI18nService(),
            'fs': FSService(),
            'field-data': field_data,
            'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff),
            "reverification": ReverificationService()
        },
        get_user_role=lambda: get_user_role(user, course_id),
        descriptor_runtime=descriptor._runtime,  # pylint: disable=protected-access
        rebind_noauth_module_to_user=rebind_noauth_module_to_user,
        user_location=user_location,
        request_token=request_token,
    )

    # pass position specified in URL to module through ModuleSystem
    if position is not None:
        try:
            position = int(position)
        except (ValueError, TypeError):
            log.exception('Non-integer %r passed as position.', position)
            position = None

    system.set('position', position)
    if settings.FEATURES.get(
            'ENABLE_PSYCHOMETRICS') and user.is_authenticated():
        system.set(
            'psychometrics_handler',  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user,
                                                   descriptor.location))

    system.set(u'user_is_staff', user_is_staff)
    system.set(u'user_is_admin', has_access(user, u'staff', 'global'))
    system.set(u'user_is_beta_tester',
               CourseBetaTesterRole(course_id).has_user(user))
    system.set(u'days_early_for_beta',
               getattr(descriptor, 'days_early_for_beta'))

    # make an ErrorDescriptor -- assuming that the descriptor's system is ok
    if has_access(user, u'staff', descriptor.location, course_id):
        system.error_descriptor_class = ErrorDescriptor
    else:
        system.error_descriptor_class = NonStaffErrorDescriptor

    return system, field_data
def get_module_for_descriptor_internal(user,
                                       descriptor,
                                       model_data_cache,
                                       course_id,
                                       track_function,
                                       xqueue_callback_url_prefix,
                                       position=None,
                                       wrap_xmodule_display=True,
                                       grade_bucket_type=None):
    """
    Actually implement get_module, without requiring a request.

    See get_module() docstring for further details.
    """

    # Short circuit--if the user shouldn't have access, bail without doing any work
    if not has_access(user, descriptor, 'load', course_id):
        return None

    # Setup system context for module instance
    ajax_url = reverse(
        'modx_dispatch',
        kwargs=dict(course_id=course_id,
                    location=descriptor.location.url(),
                    dispatch=''),
    )
    # Intended use is as {ajax_url}/{dispatch_command}, so get rid of the trailing slash.
    ajax_url = ajax_url.rstrip('/')

    def make_xqueue_callback(dispatch='score_update'):
        # Fully qualified callback URL for external queueing system
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(course_id=course_id,
                        userid=str(user.id),
                        mod_id=descriptor.location.url(),
                        dispatch=dispatch),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': xqueue_interface,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    # This is a hacky way to pass settings to the combined open ended xmodule
    # It needs an S3 interface to upload images to S3
    # It needs the open ended grading interface in order to get peer grading to be done
    # this first checks to see if the descriptor is the correct one, and only sends settings if it is

    # Get descriptor metadata fields indicating needs for various settings
    needs_open_ended_interface = getattr(descriptor,
                                         "needs_open_ended_interface", False)
    needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)

    # Initialize interfaces to None
    open_ended_grading_interface = None
    s3_interface = None

    # Create interfaces if needed
    if needs_open_ended_interface:
        open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
        open_ended_grading_interface[
            'mock_peer_grading'] = settings.MOCK_PEER_GRADING
        open_ended_grading_interface[
            'mock_staff_grading'] = settings.MOCK_STAFF_GRADING
    if needs_s3_interface:
        s3_interface = {
            'access_key':
            getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
            'secret_access_key':
            getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
            'storage_bucket_name':
            getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
        }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user, descriptor, model_data_cache, course_id, track_function,
            make_xqueue_callback, position, wrap_xmodule_display,
            grade_bucket_type)

    def xblock_model_data(descriptor):
        return DbModel(
            LmsKeyValueStore(descriptor._model_data, model_data_cache),
            descriptor.module_class, user.id,
            LmsUsage(descriptor.location, descriptor.location))

    def publish(event):
        """A function that allows XModules to publish events. This only supports grade changes right now."""
        if event.get('event_name') != 'grade':
            return

        usage = LmsUsage(descriptor.location, descriptor.location)
        # Construct the key for the module
        key = KeyValueStore.Key(scope=Scope.user_state,
                                student_id=user.id,
                                block_scope_id=usage.id,
                                field_name='grade')

        student_module = model_data_cache.find_or_create(key)
        # Update the grades
        student_module.grade = event.get('value')
        student_module.max_grade = event.get('max_value')
        # Save all changes to the underlying KeyValueStore
        student_module.save()

        # Bin score into range and increment stats
        score_bucket = get_score_bucket(student_module.grade,
                                        student_module.max_grade)
        org, course_num, run = course_id.split("/")

        tags = [
            "org:{0}".format(org), "course:{0}".format(course_num),
            "run:{0}".format(run), "score_bucket:{0}".format(score_bucket)
        ]

        if grade_bucket_type is not None:
            tags.append('type:%s' % grade_bucket_type)

        statsd.increment("lms.courseware.question_answered", tags=tags)

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from
    system = ModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        ajax_url=ajax_url,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.system.resources_fs,
        get_module=inner_get_module,
        user=user,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_namespace=descriptor.location._replace(category=None,
                                                          name=None),
        ),
        node_path=settings.NODE_PATH,
        xblock_model_data=xblock_model_data,
        publish=publish,
        anonymous_student_id=unique_id_for_user(user),
        course_id=course_id,
        open_ended_grading_interface=open_ended_grading_interface,
        s3_interface=s3_interface,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
    )
    # pass position specified in URL to module through ModuleSystem
    system.set('position', position)
    system.set('DEBUG', settings.DEBUG)
    if settings.MITX_FEATURES.get('ENABLE_PSYCHOMETRICS'):
        system.set(
            'psychometrics_handler',  # set callback for updating PsychometricsData
            make_psychometrics_data_update_handler(course_id, user,
                                                   descriptor.location.url()))

    try:
        module = descriptor.xmodule(system)
    except:
        log.exception(
            "Error creating module from descriptor {0}".format(descriptor))

        # make an ErrorDescriptor -- assuming that the descriptor's system is ok
        if has_access(user, descriptor.location, 'staff', course_id):
            err_descriptor_class = ErrorDescriptor
        else:
            err_descriptor_class = NonStaffErrorDescriptor

        err_descriptor = err_descriptor_class.from_descriptor(
            descriptor, error_msg=exc_info_to_str(sys.exc_info()))

        # Make an error module
        return err_descriptor.xmodule(system)

    system.set('user_is_staff',
               has_access(user, descriptor.location, 'staff', course_id))
    _get_html = module.get_html

    if wrap_xmodule_display is True:
        _get_html = wrap_xmodule(module.get_html, module,
                                 'xmodule_display.html')

    module.get_html = replace_static_urls(
        _get_html,
        getattr(descriptor, 'data_dir', None),
        course_namespace=module.location._replace(category=None, name=None))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    module.get_html = replace_course_urls(module.get_html, course_id)

    # this will rewrite intra-courseware links
    # that use the shorthand /jump_to_id/<id>. This is very helpful
    # for studio authored courses (compared to the /course/... format) since it is
    # is durable with respect to moves and the author doesn't need to
    # know the hierarchy
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work
    module.get_html = replace_jump_to_id_urls(
        module.get_html, course_id,
        reverse('jump_to_id', kwargs={
            'course_id': course_id,
            'module_id': ''
        }))

    if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
        if has_access(user, module, 'staff', course_id):
            module.get_html = add_histogram(module.get_html, module, user)

    # force the module to save after rendering
    module.get_html = save_module(module.get_html, module)
    return module
Example #45
0
 def is_enabled(cls, course, user=None):  # pylint: disable=unused-argument,redefined-outer-name
     """
     Returns true if the specified user has staff access.
     """
     return bool(user and has_access(user, 'staff', course, course.id))
Example #46
0
def can_access_all_blocks(requesting_user, course_key):
    """
    Returns whether the requesting_user can access all the blocks
    in the course.
    """
    return has_access(requesting_user, CourseStaffRole.ROLE, course_key)
Example #47
0
def single_thread(request, course_key, discussion_id, thread_id):
    """
    Renders a response to display a single discussion thread.
    """
    nr_transaction = newrelic.agent.current_transaction()

    course = get_course_with_access(request.user,
                                    'load',
                                    course_key,
                                    check_if_enrolled=True)
    course_settings = make_course_settings(course, request.user)
    cc_user = cc.User.from_django_user(request.user)
    user_info = cc_user.to_dict()
    is_moderator = has_permission(request.user, "see_all_cohorts", course_key)

    # Verify that the student has access to this thread if belongs to a discussion module
    if discussion_id not in utils.get_discussion_categories_ids(
            course, request.user):
        raise Http404

    # Currently, the front end always loads responses via AJAX, even for this
    # page; it would be a nice optimization to avoid that extra round trip to
    # the comments service.
    try:
        thread = cc.Thread.find(thread_id).retrieve(
            recursive=request.is_ajax(),
            user_id=request.user.id,
            response_skip=request.GET.get("resp_skip"),
            response_limit=request.GET.get("resp_limit"))
    except cc.utils.CommentClientRequestError as e:
        if e.status_code == 404:
            raise Http404
        raise

    # verify that the thread belongs to the requesting student's cohort
    if is_commentable_cohorted(course_key, discussion_id) and not is_moderator:
        user_group_id = get_cohort_id(request.user, course_key)
        if getattr(thread, "group_id",
                   None) is not None and user_group_id != thread.group_id:
            raise Http404

    is_staff = has_permission(request.user, 'openclose_thread', course.id)
    if request.is_ajax():
        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_annotated_content_infos"):
            annotated_content_info = utils.get_annotated_content_infos(
                course_key, thread, request.user, user_info=user_info)
        content = utils.prepare_content(thread.to_dict(), course_key, is_staff)
        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "add_courseware_context"):
            add_courseware_context([content], course, request.user)
        return utils.JsonResponse({
            'content':
            content,
            'annotated_content_info':
            annotated_content_info,
        })

    else:
        try:
            threads, query_params = get_threads(request, course)
        except ValueError:
            return HttpResponseBadRequest("Invalid group_id")
        threads.append(thread.to_dict())

        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "add_courseware_context"):
            add_courseware_context(threads, course, request.user)

        for thread in threads:
            # patch for backward compatibility with comments service
            if "pinned" not in thread:
                thread["pinned"] = False

        threads = [
            utils.prepare_content(thread, course_key, is_staff)
            for thread in threads
        ]

        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_metadata_for_threads"):
            annotated_content_info = utils.get_metadata_for_threads(
                course_key, threads, request.user, user_info)

        with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
            user_cohort = get_cohort_id(request.user, course_key)

        context = {
            'discussion_id':
            discussion_id,
            'csrf':
            csrf(request)['csrf_token'],
            'init':
            '',  # TODO: What is this?
            'user_info':
            _attr_safe_json(user_info),
            'can_create_comment':
            _attr_safe_json(
                has_permission(request.user, "create_comment", course.id)),
            'can_create_subcomment':
            _attr_safe_json(
                has_permission(request.user, "create_sub_comment", course.id)),
            'can_create_thread':
            has_permission(request.user, "create_thread", course.id),
            'annotated_content_info':
            _attr_safe_json(annotated_content_info),
            'course':
            course,
            #'recent_active_threads': recent_active_threads,
            'course_id':
            course.id.to_deprecated_string(
            ),  # TODO: Why pass both course and course.id to template?
            'thread_id':
            thread_id,
            'threads':
            _attr_safe_json(threads),
            'roles':
            _attr_safe_json(utils.get_role_ids(course_key)),
            'is_moderator':
            is_moderator,
            'thread_pages':
            query_params['num_pages'],
            'is_course_cohorted':
            is_course_cohorted(course_key),
            'flag_moderator':
            bool(
                has_permission(request.user, 'openclose_thread', course.id)
                or has_access(request.user, 'staff', course)),
            'cohorts':
            course_settings["cohorts"],
            'user_cohort':
            user_cohort,
            'sort_preference':
            cc_user.default_sort_key,
            'category_map':
            course_settings["category_map"],
            'course_settings':
            _attr_safe_json(course_settings)
        }
        return render_to_response('discussion/index.html', context)
Example #48
0
def can_access_others_blocks(requesting_user, course_key):
    """
    Returns whether the requesting_user can access the blocks for
    other users in the given course.
    """
    return has_access(requesting_user, CourseStaffRole.ROLE, course_key)
 def should_remove(self, user):
     """ Test to see if this result should be removed due to access restriction """
     return not has_access(user, "load", self.get_item(
         self.get_usage_key()), self.get_course_key())
Example #50
0
 def is_enabled(cls, course, user=None):
     return user and user.is_authenticated and \
         bool(CourseEnrollment.is_enrolled(user, course.id) or has_access(user, 'staff', course, course.id))
Example #51
0
 def test__user_passed_as_none(self):
     """Ensure has_access handles a user being passed as null"""
     access.has_access(None, 'staff', 'global', None)
Example #52
0
    def __iter__(self):
        def parent_or_requested_block_type(usage_key):
            """
            Returns whether the usage_key's block_type is one of self.block_types or a parent type.
            """
            return (usage_key.block_type in self.block_types
                    or usage_key.block_type in BLOCK_TYPES_WITH_CHILDREN)

        def create_module(descriptor):
            """
            Factory method for creating and binding a module for the given descriptor.
            """
            field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                self.course_id,
                self.request.user,
                descriptor,
                depth=0,
            )
            course = get_course_by_id(self.course_id)
            return get_module_for_descriptor(self.request.user,
                                             self.request,
                                             descriptor,
                                             field_data_cache,
                                             self.course_id,
                                             course=course)

        with modulestore().bulk_operations(self.course_id):
            child_to_parent = {}
            stack = [self.start_block]
            while stack:
                curr_block = stack.pop()

                if curr_block.hide_from_toc:
                    # For now, if the 'hide_from_toc' setting is set on the block, do not traverse down
                    # the hierarchy.  The reason being is that these blocks may not have human-readable names
                    # to display on the mobile clients.
                    # Eventually, we'll need to figure out how we want these blocks to be displayed on the
                    # mobile clients.  As they are still accessible in the browser, just not navigatable
                    # from the table-of-contents.
                    continue

                if curr_block.location.block_type in self.block_types:
                    if not has_access(self.request.user,
                                      'load',
                                      curr_block,
                                      course_key=self.course_id):
                        continue

                    summary_fn = self.block_types[curr_block.category]
                    block_path = list(
                        path(curr_block, child_to_parent, self.start_block))
                    unit_url, section_url = find_urls(self.course_id,
                                                      curr_block,
                                                      child_to_parent,
                                                      self.request)

                    yield {
                        "path":
                        block_path,
                        "named_path": [b["name"] for b in block_path],
                        "unit_url":
                        unit_url,
                        "section_url":
                        section_url,
                        "summary":
                        summary_fn(self.course_id, curr_block, self.request,
                                   self.local_cache)
                    }

                if curr_block.has_children:
                    children = get_dynamic_descriptor_children(
                        curr_block,
                        self.request.user.id,
                        create_module,
                        usage_key_filter=parent_or_requested_block_type)
                    for block in reversed(children):
                        stack.append(block)
                        child_to_parent[block] = curr_block
Example #53
0
def courses(request, show_hidden):
    """
    Renders the courses list for the API.

    :param request: The django HttpRequest object.
    :param show_hidden: True or False, (controlled from the urls.py file) to show courses with
                        upcoming enrollment date.

    :return: JsonResponse with a list of the courses.
    """
    courses_list = branding.get_visible_courses()

    if not show_hidden:
        # Using `AnonymousUser()` to hide unpublished courses
        anonymous_user = AnonymousUser()

        # The logic bellow has been copied (with amendments) from `courseware.courses.get_courses`,
        # Just in case something changes with edX releases.
        permission_name = settings.COURSE_CATALOG_VISIBILITY_PERMISSION

        courses_list = [
            c for c in courses_list
            if has_access(anonymous_user, permission_name, c)
        ]

    courses_list = sort_by_announcement(courses_list)
    courses_list = edraak_courses_logic(courses_list)

    courses_json_list = []

    prefix = get_absolute_url_prefix(request)

    for course_overview in courses_list:

        try:
            course = get_course(course_overview.id)

            video_tag = get_course_about_section(request, course, "video")
            youtube_id = video_tag[video_tag.find("embed") +
                                   6:video_tag.find("?")]

            courses_json_list.append({
                "id":
                unicode(course.id),
                "number":
                course.display_number_with_default,
                "name":
                course.display_name_with_default_escaped,
                "organization":
                course.display_org_with_default,
                "description":
                get_course_about_section(request, course,
                                         "short_description").strip(),
                "startDate":
                course.start,
                "endDate":
                course.end,
                "enrollmentStartDate":
                course.enrollment_start,
                "enrollmentEndDate":
                course.enrollment_end,
                "overview":
                get_course_about_section(request, course, "overview").strip(),
                "aboutPage":
                prefix + reverse('about_course', args=[unicode(course.id)]),
                "image":
                prefix + course_image_url(course),
                "state":
                _get_course_status(course),
                "youtube_id":
                youtube_id,
                "effort":
                get_course_about_section(request, course, "effort").strip(),
            })
        except ValueError:
            log.error(u"Course with id '{0}' not found".format(
                course_overview.id))

    return JsonResponse(courses_json_list)
Example #54
0
    def post(self, request, course_id):
        """Takes the form submission from the page and parses it.

        Args:
            request (`Request`): The Django Request object.
            course_id (unicode): The slash-separated course key.

        Returns:
            Status code 400 when the requested mode is unsupported. When the honor mode
            is selected, redirects to the dashboard. When the verified mode is selected,
            returns error messages if the indicated contribution amount is invalid or
            below the minimum, otherwise redirects to the verification flow.

        """
        course_key = CourseKey.from_string(course_id)
        user = request.user

        # This is a bit redundant with logic in student.views.change_enrollment,
        # but I don't really have the time to refactor it more nicely and test.
        course = modulestore().get_course(course_key)
        if not has_access(user, 'enroll', course):
            error_msg = _("Enrollment is closed")
            return self.get(request, course_id, error=error_msg)

        requested_mode = self._get_requested_mode(request.POST)

        allowed_modes = CourseMode.modes_for_course_dict(course_key)
        if requested_mode not in allowed_modes:
            return HttpResponseBadRequest(_("Enrollment mode not supported"))

        if requested_mode == 'audit':
            # If the learner has arrived at this screen via the traditional enrollment workflow,
            # then they should already be enrolled in an audit mode for the course, assuming one has
            # been configured.  However, alternative enrollment workflows have been introduced into the
            # system, such as third-party discovery.  These workflows result in learners arriving
            # directly at this screen, and they will not necessarily be pre-enrolled in the audit mode.
            CourseEnrollment.enroll(request.user, course_key, CourseMode.AUDIT)
            # If the course has started redirect to course home instead
            if course.has_started():
                return redirect(
                    reverse('openedx.course_experience.course_home',
                            kwargs={'course_id': course_key}))
            return redirect(reverse('dashboard'))

        if requested_mode == 'honor':
            CourseEnrollment.enroll(user, course_key, mode=requested_mode)
            # If the course has started redirect to course home instead
            if course.has_started():
                return redirect(
                    reverse('openedx.course_experience.course_home',
                            kwargs={'course_id': course_key}))
            return redirect(reverse('dashboard'))

        mode_info = allowed_modes[requested_mode]

        if requested_mode == 'verified':
            amount = request.POST.get("contribution") or \
                request.POST.get("contribution-other-amt") or 0

            try:
                # Validate the amount passed in and force it into two digits
                amount_value = decimal.Decimal(amount).quantize(
                    decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN)
            except decimal.InvalidOperation:
                error_msg = _("Invalid amount selected.")
                return self.get(request, course_id, error=error_msg)

            # Check for minimum pricing
            if amount_value < mode_info.min_price:
                error_msg = _(
                    "No selected price or selected price is too low.")
                return self.get(request, course_id, error=error_msg)

            donation_for_course = request.session.get("donation_for_course",
                                                      {})
            donation_for_course[unicode(course_key)] = amount_value
            request.session["donation_for_course"] = donation_for_course

            return redirect(
                reverse('verify_student_start_flow',
                        kwargs={'course_id': unicode(course_key)}))
Example #55
0
def single_thread(request, course_id, discussion_id, thread_id):
    nr_transaction = newrelic.agent.current_transaction()

    course = get_course_with_access(request.user, course_id, 'load_forum')
    cc_user = cc.User.from_django_user(request.user)
    user_info = cc_user.to_dict()

    thread = cc.Thread.find(thread_id).retrieve(recursive=True,
                                                user_id=request.user.id)

    if request.is_ajax():
        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_annotated_content_infos"):
            annotated_content_info = utils.get_annotated_content_infos(
                course_id, thread, request.user, user_info=user_info)
        context = {'thread': thread.to_dict(), 'course_id': course_id}
        content = utils.safe_content(thread.to_dict())
        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "add_courseware_context"):
            add_courseware_context([content], course)
        return utils.JsonResponse({
            'content':
            content,
            'annotated_content_info':
            annotated_content_info,
        })

    else:
        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_discussion_category_map"):
            category_map = utils.get_discussion_category_map(course)

        threads, query_params = get_threads(request, course_id)
        threads.append(thread.to_dict())

        course = get_course_with_access(request.user, course_id, 'load_forum')

        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "add_courseware_context"):
            add_courseware_context(threads, course)

        for thread in threads:
            if thread.get('group_id') and not thread.get('group_name'):
                thread['group_name'] = get_cohort_by_id(
                    course_id, thread.get('group_id')).name

            #patch for backward compatibility with comments service
            if not "pinned" in thread:
                thread["pinned"] = False

        threads = [utils.safe_content(thread) for thread in threads]

        #recent_active_threads = cc.search_recent_active_threads(
        #    course_id,
        #    recursive=False,
        #    query_params={'follower_id': request.user.id},
        #)

        #trending_tags = cc.search_trending_tags(
        #    course_id,
        #)

        with newrelic.agent.FunctionTrace(nr_transaction,
                                          "get_metadata_for_threads"):
            annotated_content_info = utils.get_metadata_for_threads(
                course_id, threads, request.user, user_info)

        with newrelic.agent.FunctionTrace(nr_transaction, "get_cohort_info"):
            cohorts = get_course_cohorts(course_id)
            cohorted_commentables = get_cohorted_commentables(course_id)
            user_cohort = get_cohort_id(request.user, course_id)

        context = {
            'discussion_id':
            discussion_id,
            'csrf':
            csrf(request)['csrf_token'],
            'init':
            '',  # TODO: What is this?
            'user_info':
            saxutils.escape(json.dumps(user_info), escapedict),
            'annotated_content_info':
            saxutils.escape(json.dumps(annotated_content_info), escapedict),
            'course':
            course,
            #'recent_active_threads': recent_active_threads,
            #'trending_tags': trending_tags,
            'course_id':
            course.id,  # TODO: Why pass both course and course.id to template?
            'thread_id':
            thread_id,
            'threads':
            saxutils.escape(json.dumps(threads), escapedict),
            'category_map':
            category_map,
            'roles':
            saxutils.escape(json.dumps(utils.get_role_ids(course_id)),
                            escapedict),
            'thread_pages':
            query_params['num_pages'],
            'is_course_cohorted':
            is_course_cohorted(course_id),
            'is_moderator':
            cached_has_permission(request.user, "see_all_cohorts", course_id),
            'flag_moderator':
            cached_has_permission(request.user, 'openclose_thread', course.id)
            or has_access(request.user, course, 'staff'),
            'cohorts':
            cohorts,
            'user_cohort':
            get_cohort_id(request.user, course_id),
            'cohorted_commentables':
            cohorted_commentables
        }

        return render_to_response('discussion/single_thread.html', context)
Example #56
0
def index(request, course_id):
    # Palette
    color_not = '#CCCCCC'
    color_fail = '#e41a1c'
    color_ok = '#F2F20D'
    color_prof = '#4daf4a'
    problem_activity='#377eb8'
    video_activity='#ff7f00'
    video_repetition='#fdbf6f'
    course_activity='#984ea3'
    graded_time='#88419d'
    ungraded_time='#8c6bb1'
    chapter_time='#8c96c6'
    play_event='#1b9e77'
    pause_event='#d95f02'
    seek_from_event='#7570b3'
    seek_to_event='#e7298a'
    change_speed_event='#66a61e'
    morning_time='#C9C96C'
    afternoon_time ='#7F7160'
    night_time ='#50587C'
    # Request data
    course_key = get_course_key(course_id)
    course = get_course_module(course_key)
    #course2= get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id))
    #user = request.user #Codigo Jose A. Gascon
    staff_access = has_access(request.user, 'staff', course).has_access#Codigo Jose A. Gascon
    instructor_access = has_access(request.user, 'instructor', course).has_access#Codigo Jose A. Gascon
    #Codigo Jose A. Gascon
    masq, user = setup_masquerade(request, course_key,staff_access, reset_masquerade_data=True)  # allow staff to toggle masquerade on info page
    user = request.user
    studio_url = get_studio_url(course, 'course_info')
    
    #reverifications = fetch_reverify_banner_info(request, course_key)
    #course = get_course_with_access(request.user, action='load', course_key=course_key, depth=None, check_if_enrolled=False)
    #user = User.objects.get(request.user.email)
    # Proficiency and pass limit
    pass_limit = get_course_grade_cutoff(course)
    proficiency_limit = (1 - pass_limit) / 2 + pass_limit
    usernames_in = []
    for student in CourseEnrollment.objects.users_enrolled_in(course_key):#Codigo Jose A. Gascon, se cambia la forma de llamar al metode users_enrolled_in
        usernames_in.append(student.username.encode('utf-8'))


    # Data for visualization in JSON
    user_for_charts = '#average' if (staff_access or instructor_access) else user
    kwargs = {
        'qualifiers': {'category': 'video', },
    }     
    # This returns video descriptors in the order they appear on the course
    video_descriptors = videos_problems_in(course)[0]
    #WARNINIG 
    #video_durations = get_info_videos_descriptors(video_descriptors)[2]
    #video_names, video_module_keys, video_durations = get_info_videos_descriptors(video_descriptors) # NO SE USAN LAS OTRAS VARIABLES
    video_names, video_module_keys, video_durations =get_DB_infovideos(course_key)
    video_names_sorted = video_names
    video_ids_sort = video_names_sorted

    #course_name = get_course_by_id(course_key, depth=None)
    names_students=[]
    only_students = []
    students_names = get_course_students(course_key)
    print students_names
    for student in students_names:
        staff_access_user = has_access(student, 'staff', course).has_access
        instructor_access_user = has_access(student, 'instructor', course).has_access
        if not (staff_access_user or instructor_access_user):
            names_students.append(student.username.encode('utf-8'))
            only_students.append(student)

    video_ids_str = []
    course_video_names = []
    problem_ids_str=[]
    for descriptor in video_descriptors:
        video_ids_str.append((course_key.make_usage_key('video', descriptor.location.name))._to_string())
        course_video_names.append(unicodedata.normalize('NFKD', descriptor.display_name_with_default).encode('ASCII', 'ignore'))
    if len(video_descriptors) > 0:
        first_video_id = course_key.make_usage_key('video', video_descriptors[0].location.name)
        # Video progress visualization. Video percentage seen total and non-overlapped.
        video_names, avg_video_time, video_percentages = get_video_time_watched(user_for_charts, course_key)  
        if avg_video_time != []:
            all_video_time_percent = map(truediv, avg_video_time, video_durations)
            all_video_time_percent = [int(round(x*100,0)) for x in all_video_time_percent]
        else:
            all_video_time_percent = avg_video_time


        column_headers = ['Video', 'Different video time', 'Total video time']
        # Codigo Javier Orcoyen
        video_prog_json = ready_for_arraytodatatable(column_headers, video_names, video_percentages, all_video_time_percent)
        video_names, all_video_time = get_module_consumption(user_for_charts, course_key, 'video')
        # Time spent on every video resource
        column_headers = ['Video', 'Time watched']
        video_distrib_json = ready_for_arraytodatatable(column_headers, video_names, all_video_time)
  
        # Video events dispersion within video length
        scatter_array = get_video_events_info(user_for_charts, first_video_id)    
  
        # Repetitions per video intervals
        user_for_vid_intervals = '#class_total_times' if user_for_charts == '#average' else user_for_charts
        video_intervals_array = get_user_video_intervals(user_for_vid_intervals, first_video_id)        

    # Case no videos in course
    else:
        video_names = None
        video_prog_json = json.dumps(None)
        video_distrib_json = json.dumps(None)
        scatter_array = json.dumps(None)
        video_intervals_array = json.dumps(None)
          
    # Time spent on every problem resource
    # Codigo Javier Orcoyen
    problem_names, time_x_problem = get_module_consumption(user_for_charts, course_key, 'problem')
    column_headers = ['Problem', 'Time on problem']
    problem_distrib_json = ready_for_arraytodatatable(column_headers, problem_names, time_x_problem)
    print 'USER'
    print user
    problems_in = videos_problems_in(course)[1]
    problem_names_sorted = [x.display_name_with_default.encode('utf-8') for x in problems_in]
    orden=[]
    orden.append(i for i, x in enumerate(problem_names_sorted))
    problem_ids_str=list(problem_names_sorted)
    for n in range(len(problem_names_sorted)):
        problem_names_sorted[n]= unicodedata.normalize('NFKD', unicode(problem_names_sorted[n],'utf-8')).encode('ASCII', 'ignore')
        problem_ids_str[n]= unicodedata.normalize('NFKD', unicode(problem_ids_str[n],'utf-8')).encode('ASCII', 'ignore')
    user_val=1
    # Daily time spent on video and/or problem resources
    video_days, video_daily_time = get_daily_consumption(user_for_charts, course_key, 'video')
    problem_days, problem_daily_time = get_daily_consumption(user_for_charts, course_key, 'problem')    
    vid_and_prob_daily_time = join_video_problem_time(video_days, video_daily_time, problem_days, problem_daily_time) 
    #Analytics visualizations
    if staff_access or instructor_access:
        # Instructor access
        std_sort = get_DB_sort_course_homework(course_key)
        # Chapter time
        cs, st = get_DB_course_spent_time(course_key, student_id=ALL_STUDENTS)
        students_spent_time = chapter_time_to_js(cs, st)
        students_grades = get_DB_student_grades(course_key, student_id=ALL_STUDENTS) 
        cs, sa = course_accesses = get_DB_course_section_accesses(course_key, student_id=ALL_STUDENTS)
        students_course_accesses = course_accesses_to_js(cs, sa)
        #students_prob_vid_progress = get_DB_course_video_problem_progress(course_key, student_id=ALL_STUDENTS)# C. J. A. Gascon ERROR
        students_time_schedule = get_DB_time_schedule(course_key, student_id=ALL_STUDENTS)
    else:
        # Sort homework                    
        # Chapter time
        std_sort = None
        cs, st = get_DB_course_spent_time(course_key, user.id)
        students_spent_time = chapter_time_to_js(cs, st) 
        students_grades = get_DB_student_grades(course_key, user.id) 
        cs, sa = course_accesses = get_DB_course_section_accesses(course_key, user.id)
        students_course_accesses = course_accesses_to_js(cs, sa) 
        students_time_schedule = get_DB_time_schedule(course_key, user.id)  
        #students_prob_vid_progress = get_DB_course_video_problem_progress(course_key, user.id)  #C. J. A. Gascon ERROR

    context = {'course': course,
               'request': request,
               'user': user,
               'user_id': user.id,
               'staff_access': staff_access,
               'instructor_access': instructor_access,
               'masquerade': masq,
               'studio_url': studio_url,
               #'reverifications': reverifications,
               'course_id': course_id,
               'students': students_to_js(only_students),
               'visualizations_id': VISUALIZATIONS_ID,
               'std_grades_dump': dumps(students_grades),
               'sort_std_dump': dumps(std_sort),
               'time_dump': dumps(students_spent_time),
               'accesses_dump': dumps(students_course_accesses),
               'std_time_schedule_dumb': dumps(students_time_schedule), 
               #'vid_prob_prog_dump': dumps(students_prob_vid_progress), #C. J. A. Gascon ERROR
               'pass_limit': pass_limit,
               'prof_limit': proficiency_limit,
               'usernames_in' : usernames_in,
               'video_names' : course_video_names,
               'video_ids' : video_ids_str,
               'video_prog_json' : video_prog_json,
               'video_distrib_json' : video_distrib_json,
               'problem_distrib_json' : problem_distrib_json,
               'video_intervals_array' : video_intervals_array,
               'vid_and_prob_daily_time' : vid_and_prob_daily_time,
               'scatter_array' : scatter_array,
               'problem_names' : problem_names,
               'problem_ids' : problem_ids_str,
               'color_not' : color_not,
               'color_ok' : color_ok,
               'color_prof' : color_prof,
               'color_fail' : color_fail,
               'problem_activity' : problem_activity,
               'video_activity' : video_activity,
               'course_activity' : course_activity,
               'video_repetition' : video_repetition,
               'graded_time' : graded_time,
               'ungraded_time' : ungraded_time,
               'chapter_time' : chapter_time,
               'user_for_charts' : user_for_charts,
               'video_ids_sort' : video_ids_sort,
               'video_names_sorted' : video_names_sorted,
               'problem_names_sorted' : problem_names_sorted,
               'play_event' : play_event,
               'pause_event' : pause_event,
               'seek_from_event' : seek_from_event,
               'seek_to_event' : seek_to_event,
               'change_speed_event' : change_speed_event,
               'morning_time' : morning_time,
               'afternoon_time' : afternoon_time,
               'night_time' : night_time,
               'names_students' : names_students,
               'user_val' : user_val,}
        
    return render_to_response('learning_analytics/learning_analytics.html', context)    
Example #57
0
    def render_to_fragment(self, request, course_id=None, **kwargs):
        """
        Renders the course's home page as a fragment.
        """
        course_key = CourseKey.from_string(course_id)
        course = get_course_with_access(request.user, 'load', course_key)

        # Render the course dates as a fragment
        dates_fragment = CourseDatesFragmentView().render_to_fragment(request, course_id=course_id, **kwargs)

        # Render the full content to enrolled users, as well as to course and global staff.
        # Unenrolled users who are not course or global staff are given only a subset.
        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
        user_access = {
            'is_anonymous': request.user.is_anonymous(),
            'is_enrolled': enrollment is not None,
            'is_staff': has_access(request.user, 'staff', course_key),
        }
        if user_access['is_enrolled'] or user_access['is_staff']:
            outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id, **kwargs)
            if LATEST_UPDATE_FLAG.is_enabled(course_key):
                update_message_fragment = LatestUpdateFragmentView().render_to_fragment(
                    request, course_id=course_id, **kwargs
                )
            else:
                update_message_fragment = WelcomeMessageFragmentView().render_to_fragment(
                    request, course_id=course_id, **kwargs
                )
            course_sock_fragment = CourseSockFragmentView().render_to_fragment(request, course=course, **kwargs)
            has_visited_course, resume_course_url = self._get_resume_course_info(request, course_id)
        else:
            # Redirect the user to the dashboard if they are not enrolled and
            # this is a course that does not support direct enrollment.
            if not can_self_enroll_in_course(course_key):
                raise CourseAccessRedirect(reverse('dashboard'))

            # Set all the fragments
            outline_fragment = None
            update_message_fragment = None
            course_sock_fragment = None
            has_visited_course = None
            resume_course_url = None

        # Get the handouts
        handouts_html = self._get_course_handouts(request, course)

        # Get the course tools enabled for this user and course
        course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key)

        # Grab the course home messages fragment to render any relevant django messages
        course_home_message_fragment = CourseHomeMessageFragmentView().render_to_fragment(
            request, course_id=course_id, user_access=user_access, **kwargs
        )

        # Get info for upgrade messaging
        upgrade_price = None
        upgrade_url = None

        # TODO Add switch to control deployment
        if SHOW_UPGRADE_MSG_ON_COURSE_HOME.is_enabled(course_key) and enrollment and enrollment.upgrade_deadline:
            verified_mode = enrollment.verified_mode
            if verified_mode:
                upgrade_price = verified_mode.min_price

                ecommerce_service = EcommerceService()
                if ecommerce_service.is_enabled(request.user):
                    upgrade_url = ecommerce_service.get_checkout_page_url(verified_mode.sku)
                else:
                    upgrade_url = reverse('verify_student_upgrade_and_verify', args=(course_key,))

        # Render the course home fragment
        context = {
            'request': request,
            'csrf': csrf(request)['csrf_token'],
            'course': course,
            'course_key': course_key,
            'outline_fragment': outline_fragment,
            'handouts_html': handouts_html,
            'course_home_message_fragment': course_home_message_fragment,
            'has_visited_course': has_visited_course,
            'resume_course_url': resume_course_url,
            'course_tools': course_tools,
            'dates_fragment': dates_fragment,
            'update_message_fragment': update_message_fragment,
            'course_sock_fragment': course_sock_fragment,
            'disable_courseware_js': True,
            'uses_pattern_library': True,
            'upgrade_price': upgrade_price,
            'upgrade_url': upgrade_url,
        }
        html = render_to_string('course_experience/course-home-fragment.html', context)
        return Fragment(html)
Example #58
0
def student_dashboard(request):
    """
    Provides the LMS dashboard view

    TODO: This is lms specific and does not belong in common code.

    Arguments:
        request: The request object.

    Returns:
        The dashboard response.

    """
    user = request.user
    if not UserProfile.objects.filter(user=user).exists():
        return redirect(reverse('account_settings'))

    platform_name = configuration_helpers.get_value("platform_name",
                                                    settings.PLATFORM_NAME)

    enable_verified_certificates = configuration_helpers.get_value(
        'ENABLE_VERIFIED_CERTIFICATES',
        settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES'))
    display_course_modes_on_dashboard = configuration_helpers.get_value(
        'DISPLAY_COURSE_MODES_ON_DASHBOARD',
        settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True))
    activation_email_support_link = configuration_helpers.get_value(
        'ACTIVATION_EMAIL_SUPPORT_LINK',
        settings.ACTIVATION_EMAIL_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK
    hide_dashboard_courses_until_activated = configuration_helpers.get_value(
        'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED',
        settings.FEATURES.get('HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED', False))
    empty_dashboard_message = configuration_helpers.get_value(
        'EMPTY_DASHBOARD_MESSAGE', None)

    # Get the org whitelist or the org blacklist for the current site
    site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site(
    )
    course_enrollments = list(
        get_course_enrollments(user, site_org_whitelist, site_org_blacklist))

    # Get the entitlements for the user and a mapping to all available sessions for that entitlement
    # If an entitlement has no available sessions, pass through a mock course overview object
    (course_entitlements, course_entitlement_available_sessions,
     unfulfilled_entitlement_pseudo_sessions
     ) = get_filtered_course_entitlements(user, site_org_whitelist,
                                          site_org_blacklist)

    # Record how many courses there are so that we can get a better
    # understanding of usage patterns on prod.
    monitoring_utils.accumulate('num_courses', len(course_enrollments))

    # Sort the enrollment pairs by the enrollment date
    course_enrollments.sort(key=lambda x: x.created, reverse=True)

    # Retrieve the course modes for each course
    enrolled_course_ids = [
        enrollment.course_id for enrollment in course_enrollments
    ]
    __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses(
        enrolled_course_ids)
    course_modes_by_course = {
        course_id: {mode.slug: mode
                    for mode in modes}
        for course_id, modes in iteritems(unexpired_course_modes)
    }

    # Check to see if the student has recently enrolled in a course.
    # If so, display a notification message confirming the enrollment.
    enrollment_message = _create_recent_enrollment_message(
        course_enrollments, course_modes_by_course)
    course_optouts = Optout.objects.filter(user=user).values_list('course_id',
                                                                  flat=True)

    # Display activation message
    activate_account_message = ''
    if not user.is_active:
        activate_account_message = Text(
            _("Check your {email_start}{email}{email_end} inbox for an account activation link from {platform_name}. "
              "If you need help, contact {link_start}{platform_name} Support{link_end}."
              )
        ).format(
            platform_name=platform_name,
            email_start=HTML("<strong>"),
            email_end=HTML("</strong>"),
            email=user.email,
            link_start=HTML(
                "<a target='_blank' href='{activation_email_support_link}'>").
            format(
                activation_email_support_link=activation_email_support_link, ),
            link_end=HTML("</a>"),
        )

    enterprise_message = get_dashboard_consent_notification(
        request, user, course_enrollments)

    # Disable lookup of Enterprise consent_required_course due to ENT-727
    # Will re-enable after fixing WL-1315
    consent_required_courses = set()
    enterprise_customer_name = None

    # Account activation message
    account_activation_messages = [
        message for message in messages.get_messages(request)
        if 'account-activation' in message.tags
    ]

    # Global staff can see what courses encountered an error on their dashboard
    staff_access = False
    errored_courses = {}
    if has_access(user, 'staff', 'global'):
        # Show any courses that encountered an error on load
        staff_access = True
        errored_courses = modulestore().get_errored_courses()

    show_courseware_links_for = {
        enrollment.course_id: has_access(request.user, 'load',
                                         enrollment.course_overview)
        for enrollment in course_enrollments
    }

    # Find programs associated with course runs being displayed. This information
    # is passed in the template context to allow rendering of program-related
    # information on the dashboard.
    meter = ProgramProgressMeter(request.site,
                                 user,
                                 enrollments=course_enrollments)
    ecommerce_service = EcommerceService()
    inverted_programs = meter.invert_programs()

    urls, programs_data = {}, {}
    bundles_on_dashboard_flag = WaffleFlag(
        WaffleFlagNamespace(name=u'student.experiments'),
        u'bundles_on_dashboard')

    # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete
    if bundles_on_dashboard_flag.is_enabled(
    ) and inverted_programs and inverted_programs.items():
        if len(course_enrollments) < 4:
            for program in inverted_programs.values():
                try:
                    program_uuid = program[0]['uuid']
                    program_data = get_programs(request.site,
                                                uuid=program_uuid)
                    program_data = ProgramDataExtender(program_data,
                                                       request.user).extend()
                    skus = program_data.get('skus')
                    checkout_page_url = ecommerce_service.get_checkout_page_url(
                        *skus)
                    program_data[
                        'completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get(
                            'uuid')
                    programs_data[program_uuid] = program_data
                except:  # pylint: disable=bare-except
                    pass

    # Construct a dictionary of course mode information
    # used to render the course list.  We re-use the course modes dict
    # we loaded earlier to avoid hitting the database.
    course_mode_info = {
        enrollment.course_id: complete_course_mode_info(
            enrollment.course_id,
            enrollment,
            modes=course_modes_by_course[enrollment.course_id])
        for enrollment in course_enrollments
    }

    # Determine the per-course verification status
    # This is a dictionary in which the keys are course locators
    # and the values are one of:
    #
    # VERIFY_STATUS_NEED_TO_VERIFY
    # VERIFY_STATUS_SUBMITTED
    # VERIFY_STATUS_APPROVED
    # VERIFY_STATUS_MISSED_DEADLINE
    #
    # Each of which correspond to a particular message to display
    # next to the course on the dashboard.
    #
    # If a course is not included in this dictionary,
    # there is no verification messaging to display.
    verify_status_by_course = check_verify_status_by_course(
        user, course_enrollments)
    cert_statuses = {
        enrollment.course_id: cert_info(request.user,
                                        enrollment.course_overview)
        for enrollment in course_enrollments
    }

    # only show email settings for Mongo course and when bulk email is turned on
    show_email_settings_for = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if (BulkEmailFlag.feature_enabled(enrollment.course_id)))

    # Verification Attempts
    # Used to generate the "you must reverify for course x" banner
    verification_status = IDVerificationService.user_status(user)
    verification_errors = get_verification_error_reasons_for_display(
        verification_status['error'])

    # Gets data for midcourse reverifications, if any are necessary or have failed
    statuses = ["approved", "denied", "pending", "must_reverify"]
    reverifications = reverification_info(statuses)

    block_courses = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if is_course_blocked(
            request,
            CourseRegistrationCode.objects.filter(
                course_id=enrollment.course_id,
                registrationcoderedemption__redeemed_by=request.user),
            enrollment.course_id))

    enrolled_courses_either_paid = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.is_paid_course())

    # If there are *any* denied reverifications that have not been toggled off,
    # we'll display the banner
    denied_banner = any(item.display for item in reverifications["denied"])

    # Populate the Order History for the side-bar.
    order_history_list = order_history(user,
                                       course_org_filter=site_org_whitelist,
                                       org_filter_out_set=site_org_blacklist)

    # get list of courses having pre-requisites yet to be completed
    courses_having_prerequisites = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.course_overview.pre_requisite_courses)
    courses_requirements_not_met = get_pre_requisite_courses_not_completed(
        user, courses_having_prerequisites)

    if 'notlive' in request.GET:
        redirect_message = _(
            "The course you are looking for does not start until {date}."
        ).format(date=request.GET['notlive'])
    elif 'course_closed' in request.GET:
        redirect_message = _(
            "The course you are looking for is closed for enrollment as of {date}."
        ).format(date=request.GET['course_closed'])
    elif 'access_response_error' in request.GET:
        # This can be populated in a generalized way with fields from access response errors
        redirect_message = request.GET['access_response_error']
    else:
        redirect_message = ''

    valid_verification_statuses = [
        'approved', 'must_reverify', 'pending', 'expired'
    ]
    display_sidebar_on_dashboard = (
        len(order_history_list)
        or (verification_status['status'] in valid_verification_statuses
            and verification_status['should_display']))

    # Filter out any course enrollment course cards that are associated with fulfilled entitlements
    for entitlement in [
            e for e in course_entitlements
            if e.enrollment_course_run is not None
    ]:
        course_enrollments = [
            enr for enr in course_enrollments
            if entitlement.enrollment_course_run.course_id != enr.course_id
        ]

    context = {
        'urls':
        urls,
        'programs_data':
        programs_data,
        'enterprise_message':
        enterprise_message,
        'consent_required_courses':
        consent_required_courses,
        'enterprise_customer_name':
        enterprise_customer_name,
        'enrollment_message':
        enrollment_message,
        'redirect_message':
        redirect_message,
        'account_activation_messages':
        account_activation_messages,
        'activate_account_message':
        activate_account_message,
        'course_enrollments':
        course_enrollments,
        'course_entitlements':
        course_entitlements,
        'course_entitlement_available_sessions':
        course_entitlement_available_sessions,
        'unfulfilled_entitlement_pseudo_sessions':
        unfulfilled_entitlement_pseudo_sessions,
        'course_optouts':
        course_optouts,
        'staff_access':
        staff_access,
        'errored_courses':
        errored_courses,
        'show_courseware_links_for':
        show_courseware_links_for,
        'all_course_modes':
        course_mode_info,
        'cert_statuses':
        cert_statuses,
        'credit_statuses':
        _credit_statuses(user, course_enrollments),
        'show_email_settings_for':
        show_email_settings_for,
        'reverifications':
        reverifications,
        'verification_display':
        verification_status['should_display'],
        'verification_status':
        verification_status['status'],
        'verification_status_by_course':
        verify_status_by_course,
        'verification_errors':
        verification_errors,
        'block_courses':
        block_courses,
        'denied_banner':
        denied_banner,
        'billing_email':
        settings.PAYMENT_SUPPORT_EMAIL,
        'user':
        user,
        'logout_url':
        reverse('logout'),
        'platform_name':
        platform_name,
        'enrolled_courses_either_paid':
        enrolled_courses_either_paid,
        'provider_states': [],
        'order_history_list':
        order_history_list,
        'courses_requirements_not_met':
        courses_requirements_not_met,
        'nav_hidden':
        True,
        'inverted_programs':
        inverted_programs,
        'show_program_listing':
        ProgramsApiConfig.is_enabled(),
        'show_journal_listing':
        journals_enabled(),  # TODO: Dashboard Plugin required
        'show_dashboard_tabs':
        True,
        'disable_courseware_js':
        True,
        'display_course_modes_on_dashboard':
        enable_verified_certificates and display_course_modes_on_dashboard,
        'display_sidebar_on_dashboard':
        display_sidebar_on_dashboard,
        'display_sidebar_account_activation_message':
        not (user.is_active or hide_dashboard_courses_until_activated),
        'display_dashboard_courses':
        (user.is_active or not hide_dashboard_courses_until_activated),
        'empty_dashboard_message':
        empty_dashboard_message,
    }

    if ecommerce_service.is_enabled(request.user):
        context.update({
            'use_ecommerce_payment_flow':
            True,
            'ecommerce_payment_page':
            ecommerce_service.payment_page_url(),
        })

    # Gather urls for course card resume buttons.
    resume_button_urls = ['' for entitlement in course_entitlements]
    for url in _get_urls_for_resume_buttons(user, course_enrollments):
        resume_button_urls.append(url)
    # There must be enough urls for dashboard.html. Template creates course
    # cards for "enrollments + entitlements".
    context.update({'resume_button_urls': resume_button_urls})

    response = render_to_response('dashboard.html', context)
    set_logged_in_cookies(request, response, user)
    return response
Example #59
0
def get_module_system_for_user(user, student_data,  # TODO  # pylint: disable=too-many-statements
                               # Arguments preceding this comment have user binding, those following don't
                               descriptor, course_id, track_function, xqueue_callback_url_prefix,
                               request_token, position=None, wrap_xmodule_display=True, grade_bucket_type=None,
                               static_asset_path='', user_location=None, disable_staff_debug_info=False,
                               course=None):
    """
    Helper function that returns a module system and student_data bound to a user and a descriptor.

    The purpose of this function is to factor out everywhere a user is implicitly bound when creating a module,
    to allow an existing module to be re-bound to a user.  Most of the user bindings happen when creating the
    closures that feed the instantiation of ModuleSystem.

    The arguments fall into two categories: those that have explicit or implicit user binding, which are user
    and student_data, and those don't and are just present so that ModuleSystem can be instantiated, which
    are all the other arguments.  Ultimately, this isn't too different than how get_module_for_descriptor_internal
    was before refactoring.

    Arguments:
        see arguments for get_module()
        request_token (str): A token unique to the request use by xblock initialization

    Returns:
        (LmsModuleSystem, KvsFieldData):  (module system, student_data) bound to, primarily, the user and descriptor
    """

    def make_xqueue_callback(dispatch='score_update'):
        """
        Returns fully qualified callback URL for external queueing system
        """
        relative_xqueue_callback_url = reverse(
            'xqueue_callback',
            kwargs=dict(
                course_id=course_id.to_deprecated_string(),
                userid=str(user.id),
                mod_id=descriptor.location.to_deprecated_string(),
                dispatch=dispatch
            ),
        )
        return xqueue_callback_url_prefix + relative_xqueue_callback_url

    # Default queuename is course-specific and is derived from the course that
    #   contains the current module.
    # TODO: Queuename should be derived from 'course_settings.json' of each course
    xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course

    xqueue = {
        'interface': XQUEUE_INTERFACE,
        'construct_callback': make_xqueue_callback,
        'default_queuename': xqueue_default_queuename.replace(' ', '_'),
        'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
    }

    def inner_get_module(descriptor):
        """
        Delegate to get_module_for_descriptor_internal() with all values except `descriptor` set.

        Because it does an access check, it may return None.
        """
        # TODO: fix this so that make_xqueue_callback uses the descriptor passed into
        # inner_get_module, not the parent's callback.  Add it as an argument....
        return get_module_for_descriptor_internal(
            user=user,
            descriptor=descriptor,
            student_data=student_data,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course
        )

    def publish(block, event_type, event):
        """A function that allows XModules to publish events."""
        if event_type == 'grade' and not is_masquerading_as_specific_student(user, course_id):
            SCORE_PUBLISHED.send(
                sender=None,
                block=block,
                user=user,
                raw_earned=event['value'],
                raw_possible=event['max_value'],
                only_if_higher=event.get('only_if_higher'),
                score_deleted=event.get('score_deleted'),
            )
        else:
            context = contexts.course_context_from_course_id(course_id)
            if block.runtime.user_id:
                context['user_id'] = block.runtime.user_id
            context['asides'] = {}
            for aside in block.runtime.get_asides(block):
                if hasattr(aside, 'get_event_context'):
                    aside_event_info = aside.get_event_context(event_type, event)
                    if aside_event_info is not None:
                        context['asides'][aside.scope_ids.block_type] = aside_event_info
            with tracker.get_tracker().context(event_type, context):
                track_function(event_type, event)

    def rebind_noauth_module_to_user(module, real_user):
        """
        A function that allows a module to get re-bound to a real user if it was previously bound to an AnonymousUser.

        Will only work within a module bound to an AnonymousUser, e.g. one that's instantiated by the noauth_handler.

        Arguments:
            module (any xblock type):  the module to rebind
            real_user (django.contrib.auth.models.User):  the user to bind to

        Returns:
            nothing (but the side effect is that module is re-bound to real_user)
        """
        if user.is_authenticated():
            err_msg = ("rebind_noauth_module_to_user can only be called from a module bound to "
                       "an anonymous user")
            log.error(err_msg)
            raise LmsModuleRenderError(err_msg)

        field_data_cache_real_user = FieldDataCache.cache_for_descriptor_descendents(
            course_id,
            real_user,
            module.descriptor,
            asides=XBlockAsidesConfig.possible_asides(),
        )
        student_data_real_user = KvsFieldData(DjangoKeyValueStore(field_data_cache_real_user))

        (inner_system, inner_student_data) = get_module_system_for_user(
            user=real_user,
            student_data=student_data_real_user,  # These have implicit user bindings, rest of args considered not to
            descriptor=module.descriptor,
            course_id=course_id,
            track_function=track_function,
            xqueue_callback_url_prefix=xqueue_callback_url_prefix,
            position=position,
            wrap_xmodule_display=wrap_xmodule_display,
            grade_bucket_type=grade_bucket_type,
            static_asset_path=static_asset_path,
            user_location=user_location,
            request_token=request_token,
            course=course
        )

        module.descriptor.bind_for_student(
            inner_system,
            real_user.id,
            [
                partial(OverrideFieldData.wrap, real_user, course),
                partial(LmsFieldData, student_data=inner_student_data),
            ],
        )

        module.descriptor.scope_ids = (
            module.descriptor.scope_ids._replace(user_id=real_user.id)
        )
        module.scope_ids = module.descriptor.scope_ids  # this is needed b/c NamedTuples are immutable
        # now bind the module to the new ModuleSystem instance and vice-versa
        module.runtime = inner_system
        inner_system.xmodule_instance = module

    # Build a list of wrapping functions that will be applied in order
    # to the Fragment content coming out of the xblocks that are about to be rendered.
    block_wrappers = []

    if is_masquerading_as_specific_student(user, course_id):
        block_wrappers.append(filter_displayed_blocks)

    if settings.FEATURES.get("LICENSING", False):
        block_wrappers.append(wrap_with_license)

    # Wrap the output display in a single div to allow for the XModule
    # javascript to be bound correctly
    if wrap_xmodule_display is True:
        block_wrappers.append(partial(
            wrap_xblock,
            'LmsRuntime',
            extra_data={'course-id': course_id.to_deprecated_string()},
            usage_id_serializer=lambda usage_id: quote_slashes(usage_id.to_deprecated_string()),
            request_token=request_token,
        ))

    # TODO (cpennington): When modules are shared between courses, the static
    # prefix is going to have to be specific to the module, not the directory
    # that the xml was loaded from

    # Rewrite urls beginning in /static to point to course-specific content
    block_wrappers.append(partial(
        replace_static_urls,
        getattr(descriptor, 'data_dir', None),
        course_id=course_id,
        static_asset_path=static_asset_path or descriptor.static_asset_path
    ))

    # Allow URLs of the form '/course/' refer to the root of multicourse directory
    #   hierarchy of this course
    block_wrappers.append(partial(replace_course_urls, course_id))

    # this will rewrite intra-courseware links (/jump_to_id/<id>). This format
    # is an improvement over the /course/... format for studio authored courses,
    # because it is agnostic to course-hierarchy.
    # NOTE: module_id is empty string here. The 'module_id' will get assigned in the replacement
    # function, we just need to specify something to get the reverse() to work.
    block_wrappers.append(partial(
        replace_jump_to_id_urls,
        course_id,
        reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''}),
    ))

    if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'):
        if is_masquerading_as_specific_student(user, course_id):
            # When masquerading as a specific student, we want to show the debug button
            # unconditionally to enable resetting the state of the student we are masquerading as.
            # We already know the user has staff access when masquerading is active.
            staff_access = True
            # To figure out whether the user has instructor access, we temporarily remove the
            # masquerade_settings from the real_user.  With the masquerading settings in place,
            # the result would always be "False".
            masquerade_settings = user.real_user.masquerade_settings
            del user.real_user.masquerade_settings
            instructor_access = bool(has_access(user.real_user, 'instructor', descriptor, course_id))
            user.real_user.masquerade_settings = masquerade_settings
        else:
            staff_access = has_access(user, 'staff', descriptor, course_id)
            instructor_access = bool(has_access(user, 'instructor', descriptor, course_id))
        if staff_access:
            block_wrappers.append(partial(add_staff_markup, user, instructor_access, disable_staff_debug_info))

    # These modules store data using the anonymous_student_id as a key.
    # To prevent loss of data, we will continue to provide old modules with
    # the per-student anonymized id (as we have in the past),
    # while giving selected modules a per-course anonymized id.
    # As we have the time to manually test more modules, we can add to the list
    # of modules that get the per-course anonymized id.
    is_pure_xblock = isinstance(descriptor, XBlock) and not isinstance(descriptor, XModuleDescriptor)
    module_class = getattr(descriptor, 'module_class', None)
    is_lti_module = not is_pure_xblock and issubclass(module_class, LTIModule)
    if is_pure_xblock or is_lti_module:
        anonymous_student_id = anonymous_id_for_user(user, course_id)
    else:
        anonymous_student_id = anonymous_id_for_user(user, None)

    field_data = LmsFieldData(descriptor._field_data, student_data)  # pylint: disable=protected-access

    user_is_staff = bool(has_access(user, u'staff', descriptor.location, course_id))

    system = LmsModuleSystem(
        track_function=track_function,
        render_template=render_to_string,
        static_url=settings.STATIC_URL,
        xqueue=xqueue,
        # TODO (cpennington): Figure out how to share info between systems
        filestore=descriptor.runtime.resources_fs,
        get_module=inner_get_module,
        user=user,
        debug=settings.DEBUG,
        hostname=settings.SITE_NAME,
        # TODO (cpennington): This should be removed when all html from
        # a module is coming through get_html and is therefore covered
        # by the replace_static_urls code below
        replace_urls=partial(
            static_replace.replace_static_urls,
            data_directory=getattr(descriptor, 'data_dir', None),
            course_id=course_id,
            static_asset_path=static_asset_path or descriptor.static_asset_path,
        ),
        replace_course_urls=partial(
            static_replace.replace_course_urls,
            course_key=course_id
        ),
        replace_jump_to_id_urls=partial(
            static_replace.replace_jump_to_id_urls,
            course_id=course_id,
            jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''})
        ),
        node_path=settings.NODE_PATH,
        publish=publish,
        anonymous_student_id=anonymous_student_id,
        course_id=course_id,
        cache=cache,
        can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
        get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)),
        # TODO: When we merge the descriptor and module systems, we can stop reaching into the mixologist (cpennington)
        mixins=descriptor.runtime.mixologist._mixins,  # pylint: disable=protected-access
        wrappers=block_wrappers,
        get_real_user=user_by_anonymous_id,
        services={
            'fs': FSService(),
            'field-data': field_data,
            'user': DjangoXBlockUserService(user, user_is_staff=user_is_staff),
            'verification': VerificationService(),
            'proctoring': ProctoringService(),
            'milestones': milestones_helpers.get_service(),
            'credit': CreditService(),
            'bookmarks': BookmarksService(user=user),
        },
        get_user_role=lambda: get_user_role(user, course_id),
        descriptor_runtime=descriptor._runtime,  # pylint: disable=protected-access
        rebind_noauth_module_to_user=rebind_noauth_module_to_user,
        user_location=user_location,
        request_token=request_token,
    )

    # pass position specified in URL to module through ModuleSystem
    if position is not None:
        try:
            position = int(position)
        except (ValueError, TypeError):
            log.exception('Non-integer %r passed as position.', position)
            position = None

    system.set('position', position)

    system.set(u'user_is_staff', user_is_staff)
    system.set(u'user_is_admin', bool(has_access(user, u'staff', 'global')))
    system.set(u'user_is_beta_tester', CourseBetaTesterRole(course_id).has_user(user))
    system.set(u'days_early_for_beta', descriptor.days_early_for_beta)

    # make an ErrorDescriptor -- assuming that the descriptor's system is ok
    if has_access(user, u'staff', descriptor.location, course_id):
        system.error_descriptor_class = ErrorDescriptor
    else:
        system.error_descriptor_class = NonStaffErrorDescriptor

    return system, field_data
Example #60
0
def get_module_by_usage_id(request,
                           course_id,
                           usage_id,
                           disable_staff_debug_info=False,
                           course=None):
    """
    Gets a module instance based on its `usage_id` in a course, for a given request/user

    Returns (instance, tracking_context)
    """
    user = request.user

    try:
        course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
        usage_key = course_id.make_usage_key_from_deprecated_string(
            unquote_slashes(usage_id))
    except InvalidKeyError:
        raise Http404("Invalid location")

    try:
        descriptor = modulestore().get_item(usage_key)
        descriptor_orig_usage_key, descriptor_orig_version = modulestore(
        ).get_block_original_usage(usage_key)
    except ItemNotFoundError:
        log.warn("Invalid location for course id %s: %s", usage_key.course_key,
                 usage_key)
        raise Http404

    tracking_context = {
        'module': {
            'display_name': descriptor.display_name_with_default,
            'usage_key': unicode(descriptor.location),
        }
    }

    # For blocks that are inherited from a content library, we add some additional metadata:
    if descriptor_orig_usage_key is not None:
        tracking_context['module']['original_usage_key'] = unicode(
            descriptor_orig_usage_key)
        tracking_context['module']['original_usage_version'] = unicode(
            descriptor_orig_version)

    unused_masquerade, user = setup_masquerade(
        request, course_id, has_access(user, 'staff', descriptor, course_id))
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course_id, user, descriptor)
    instance = get_module_for_descriptor(
        user,
        request,
        descriptor,
        field_data_cache,
        usage_key.course_key,
        disable_staff_debug_info=disable_staff_debug_info,
        course=course)
    if instance is None:
        # Either permissions just changed, or someone is trying to be clever
        # and load something they shouldn't have access to.
        log.debug("No module %s for user %s -- access denied?", usage_key,
                  user)
        raise Http404

    return (instance, tracking_context)