Пример #1
0
def _listen_for_passing_grade(sender, user, course_id, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a learner passing a course, send cert generation task,
    downstream signal from COURSE_GRADE_CHANGED
    """

    # No flags enabled
    if (
        not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY) and
        not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY)
    ):
        return

    # Only SELF_PACED_ONLY flag enabled
    if waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY):
        if not courses.get_course_by_id(course_key, depth=0).self_paced:
            return

    # Only INSTRUCTOR_PACED_ONLY flag enabled
    elif waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY):
        if courses.get_course_by_id(course_key, depth=0).self_paced:
            return
    if GeneratedCertificate.certificate_for_student(self.user, self.course_id) is None:
        generate_certificate.apply_async(
            student=user,
            course_key=course_id,
        )
        log.info(u'Certificate generation task initiated for {user} : {course} via passing grade'.format(
            user=user.id,
            course=course_id
        ))
Пример #2
0
    def handle(self, *args, **options):

        print "options = ", options

        try:
            course_ids = options['course_id_or_dir']
        except KeyError:
            print self.help
            return
        course_key = None
        # parse out the course id into a coursekey
        for course_id in course_ids:
            try:
                course_key = CourseKey.from_string(course_id)
            # if it's not a new-style course key, parse it from an old-style
            # course key
            except InvalidKeyError:
                course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
            try:
                get_course_by_id(course_key)
            except Http404 as err:
                print "-----------------------------------------------------------------------------"
                print "Sorry, cannot find course with id {}".format(course_id)
                print "Got exception {}".format(err)
                print "Please provide a course ID or course data directory name, eg content-mit-801rq"
                return

            print "-----------------------------------------------------------------------------"
            print "Computing grades for {}".format(course_id)

            offline_grade_calculation(course_key)
Пример #3
0
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs):  # pylint: disable=unused-argument
    switches = waffle.waffle()
    # All flags enabled
    if (
        not switches.is_enabled(waffle.SELF_PACED_ONLY) and
        not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY)
    ):
        return

    # Only SELF_PACED_ONLY flag enabled
    if not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY):
        if not courses.get_course_by_id(instance.course_id, depth=0).self_paced:
            return

    # Only INSTRUCTOR_PACED_ONLY flag enabled
    if not switches.is_enabled(waffle.SELF_PACED_ONLY):
        if courses.get_course_by_id(instance.course_id, depth=0).self_paced:
            return

    generate_certificate.apply_async(
        student=instance.user,
        course_key=instance.course_id,
    )
    log.info(u'Certificate generation task initiated for {user} : {course} via whitelist'.format(
        user=instance.user.id,
        course=instance.course_id
    ))
Пример #4
0
def register_code_redemption(request, registration_code):
    """
    This view allows the student to redeem the registration code
    and enroll in the course.
    """

    # Add some rate limiting here by re-using the RateLimitMixin as a helper class
    site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME)
    limiter = BadRequestRateLimiter()
    if limiter.is_rate_limit_exceeded(request):
        AUDIT_LOG.warning("Rate limit exceeded in registration code redemption.")
        return HttpResponseForbidden()

    template_to_render = 'shoppingcart/registration_code_receipt.html'
    if request.method == "GET":
        reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code,
                                                                                                  request, limiter)
        course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0)
        context = {
            'reg_code_already_redeemed': reg_code_already_redeemed,
            'reg_code_is_valid': reg_code_is_valid,
            'reg_code': registration_code,
            'site_name': site_name,
            'course': course,
            'registered_for_course': registered_for_course(course, request.user)
        }
        return render_to_response(template_to_render, context)
    elif request.method == "POST":
        reg_code_is_valid, reg_code_already_redeemed, course_registration = get_reg_code_validity(registration_code,
                                                                                                  request, limiter)

        course = get_course_by_id(getattr(course_registration, 'course_id'), depth=0)
        if reg_code_is_valid and not reg_code_already_redeemed:
            #now redeem the reg code.
            RegistrationCodeRedemption.create_invoice_generated_registration_redemption(course_registration, request.user)
            CourseEnrollment.enroll(request.user, course.id)
            context = {
                'redemption_success': True,
                'reg_code': registration_code,
                'site_name': site_name,
                'course': course,
            }
        else:
            context = {
                'reg_code_is_valid': reg_code_is_valid,
                'reg_code_already_redeemed': reg_code_already_redeemed,
                'redemption_success': False,
                'reg_code': registration_code,
                'site_name': site_name,
                'course': course,
            }
        return render_to_response(template_to_render, context)
Пример #5
0
def show_unit_extensions(request, course_id):
    """
    Shows all of the students which have due date extensions for the given unit.
    """
    course = get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id))
    unit = find_unit(course, request.GET.get('url'))
    return JsonResponse(dump_module_extensions(course, unit))
Пример #6
0
 def test_post_list(self):
     """
     Test the creation of a CCX
     """
     outbox = self.get_outbox()
     data = {
         'master_course_id': self.master_course_key_str,
         'max_students_allowed': 111,
         'display_name': 'CCX Test Title',
         'coach_email': self.coach.email
     }
     resp = self.client.post(self.list_url, data, format='json', HTTP_AUTHORIZATION=self.auth)
     self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
     # check if the response has at least the same data of the request
     for key, val in data.iteritems():
         self.assertEqual(resp.data.get(key), val)  # pylint: disable=no-member
     self.assertIn('ccx_course_id', resp.data)  # pylint: disable=no-member
     # check that the new CCX actually exists
     course_key = CourseKey.from_string(resp.data.get('ccx_course_id'))  # pylint: disable=no-member
     ccx_course = CustomCourseForEdX.objects.get(pk=course_key.ccx)
     self.assertEqual(
         unicode(CCXLocator.from_course_locator(ccx_course.course.id, ccx_course.id)),
         resp.data.get('ccx_course_id')  # pylint: disable=no-member
     )
     # check that the coach user has coach role on the master course
     coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key)
     self.assertTrue(coach_role_on_master_course.has_user(self.coach))
     # check that the coach has been enrolled in the ccx
     ccx_course_object = courses.get_course_by_id(course_key)
     self.assertTrue(
         CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=self.coach).exists()
     )
     # check that an email has been sent to the coach
     self.assertEqual(len(outbox), 1)
     self.assertIn(self.coach.email, outbox[0].recipients())  # pylint: disable=no-member
Пример #7
0
    def handle(self, *args, **options):

        print "args = ", args

        if len(args) > 0:
            course_id = args[0]
        else:
            print self.help
            return

        try:
            course = get_course_by_id(course_id)
        except Exception as err:
            if course_id in modulestore().courses:
                course = modulestore().courses[course_id]
            else:
                print "-----------------------------------------------------------------------------"
                print "Sorry, cannot find course %s" % course_id
                print "Please provide a course ID or course data directory name, eg content-mit-801rq"
                return

        print "-----------------------------------------------------------------------------"
        print "Computing grades for %s" % (course.id)

        offline_grade_calculation(course.id)
Пример #8
0
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)
Пример #9
0
 def test_patch_detail(self):
     """
     Test for successful patch
     """
     outbox = self.get_outbox()
     # create a new coach
     new_coach = AdminFactory.create()
     data = {
         'max_students_allowed': 111,
         'display_name': 'CCX Title',
         'coach_email': new_coach.email
     }
     resp = self.client.patch(self.detail_url, data, format='json', HTTP_AUTHORIZATION=self.auth)
     self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
     ccx_from_db = CustomCourseForEdX.objects.get(id=self.ccx.id)
     self.assertEqual(ccx_from_db.max_student_enrollments_allowed, data['max_students_allowed'])
     self.assertEqual(ccx_from_db.display_name, data['display_name'])
     self.assertEqual(ccx_from_db.coach.email, data['coach_email'])
     # check that the coach user has coach role on the master course
     coach_role_on_master_course = CourseCcxCoachRole(self.master_course_key)
     self.assertTrue(coach_role_on_master_course.has_user(new_coach))
     # check that the coach has been enrolled in the ccx
     ccx_course_object = courses.get_course_by_id(self.ccx_key)
     self.assertTrue(
         CourseEnrollment.objects.filter(course_id=ccx_course_object.id, user=new_coach).exists()
     )
     # check that an email has been sent to the coach
     self.assertEqual(len(outbox), 1)
     self.assertIn(new_coach.email, outbox[0].recipients())  # pylint: disable=no-member
Пример #10
0
    def report(self):
        """Report course grade."""
        students = self._get_students()

        print "\nFetching course data for {0}".format(self.course_id)
        course = courses.get_course_by_id(self.course_id)
        request = self._create_request()
        total = {'users': 0, 'pass': 0, 'notpass': 0}

        print "Summary Report: Course Name [{0}]".format(
            course.display_name.encode('utf_8'))

        for student in students.iterator():
            request.user = student
            total['users'] += 1

            certs = GeneratedCertificate.objects.filter(
                user=student, course_id=self.course_id)

            for cert in certs.iterator():
                grade = grades.grade(cert.user, request, course)
                summary = grades.progress_summary(student, request, course)
                self._report_summary(summary)
                self._add_total(cert.user, grade, total)

        self._report_total(total)
Пример #11
0
def _section_course_info(course_id, access):
    """ Provide data for the corresponding dashboard section """
    course = get_course_by_id(course_id, depth=None)

    course_id_dict = Location.parse_course_id(course_id)

    section_data = {
        'section_key': 'course_info',
        'section_display_name': _('Course Info'),
        'access': access,
        'course_id': course_id,
        'course_org': course_id_dict['org'],
        'course_num': course_id_dict['course'],
        'course_name': course_id_dict['name'],
        'course_display_name': course.display_name,
        'enrollment_count': CourseEnrollment.num_enrolled_in(course_id),
        'has_started': course.has_started(),
        'has_ended': course.has_ended(),
        'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}),
    }

    try:
        advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo
        section_data['grade_cutoffs'] = reduce(advance, course.grade_cutoffs.items(), "")[:-2]
    except Exception:
        section_data['grade_cutoffs'] = "Not Available"
    # section_data['offline_grades'] = offline_grades_available(course_id)

    try:
        section_data['course_errors'] = [(escape(a), '') for (a, _unused) in modulestore().get_item_errors(course.location)]
    except Exception:
        section_data['course_errors'] = [('Error fetching errors', '')]

    return section_data
Пример #12
0
    def handle(self, *args, **options):
        if not options['course']:
            raise CommandError(Command.course_option.help)

        try:
            course_key = CourseKey.from_string(options['course'])
        except InvalidKeyError:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course'])

        course = get_course_by_id(course_key)

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

        try:
            if options['delete']:
                if len(args) != 1:
                    raise CommandError(Command.delete_option.help)
                num = int(args[0])
                if query_yes_no('Deleting tab {0} Confirm?'.format(num), default='no'):
                    primitive_delete(course, num - 1)  # -1 for 0-based indexing
            elif options['insert']:
                if len(args) != 3:
                    raise CommandError(Command.insert_option.help)
                num = int(args[0])
                tab_type = args[1]
                name = args[2]
                if query_yes_no('Inserting tab {0} "{1}" "{2}" Confirm?'.format(num, tab_type, name), default='no'):
                    primitive_insert(course, num - 1, tab_type, name)  # -1 as above
        except ValueError as e:
            # Cute: translate to CommandError so the CLI error prints nicely.
            raise CommandError(e)
Пример #13
0
 def is_enabled(cls, request, course_key):
     """
     Returns True if this tool is enabled for the specified course key.
     """
     course = get_course_by_id(course_key)
     has_updates = CourseUpdatesFragmentView.has_updates(request, course)
     return UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key) and has_updates
Пример #14
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

        role = CourseCcxCoachRole(course_key)
        if not role.has_user(request.user):
            return HttpResponseForbidden(
                _('You must be a CCX Coach to access this view.'))

        course = get_course_by_id(course_key, depth=None)

        # 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_for_coach(course, request.user)
            if coach_ccx is None or coach_ccx.id != ccx.id:
                return HttpResponseForbidden(
                    _('You must be the coach for this ccx to access this view')
                )

        return view(request, course, ccx)
Пример #15
0
def verify_for_closed_enrollment(user, cart=None):
    """
    A multi-output helper function.
    inputs:
        user: a user object
        cart: If a cart is provided it uses the same object, otherwise fetches the user's cart.
    Returns:
        is_any_course_expired: True if any of the items in the cart has it's enrollment period closed. False otherwise.
        expired_cart_items: List of courses with enrollment period closed.
        expired_cart_item_names: List of names of the courses with enrollment period closed.
        valid_cart_item_tuples: List of courses which are still open for enrollment.
    """
    if cart is None:
        cart = Order.get_cart_for_user(user)
    expired_cart_items = []
    expired_cart_item_names = []
    valid_cart_item_tuples = []
    cart_items = cart.orderitem_set.all().select_subclasses()
    is_any_course_expired = False
    for cart_item in cart_items:
        course_key = getattr(cart_item, 'course_id', None)
        if course_key is not None:
            course = get_course_by_id(course_key, depth=0)
            if CourseEnrollment.is_enrollment_closed(user, course):
                is_any_course_expired = True
                expired_cart_items.append(cart_item)
                expired_cart_item_names.append(course.display_name)
            else:
                valid_cart_item_tuples.append((cart_item, course))

    return is_any_course_expired, expired_cart_items, expired_cart_item_names, valid_cart_item_tuples
Пример #16
0
def add_cohort(course_key, name, assignment_type):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if is_cohort_exists(course_key, name):
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

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

    tracker.emit(
        "edx.cohort.creation_requested",
        {"cohort_name": cohort.name, "cohort_id": cohort.id}
    )
    return cohort
Пример #17
0
def return_fixed_courses(request, courses, action=None):
    default_length = 8

    course_id = request.GET.get("course_id")
    if course_id:
        course_id = course_id.replace(".", '/')

    try:
        index_course = get_course_by_id(course_id)
        course_index = (courses.index(index_course) + 1)
    except:
        course_index = 0

    current_list = courses[course_index:]

    if len(current_list) > default_length:
        current_list = current_list[0:default_length]

    course_list = []
    for course in current_list:
        try:
            course_json = mobi_course_info(request, course, action)
            course_list.append(course_json)
        except:
            continue

    return JsonResponse({"count": len(courses), "course-list": course_list})
Пример #18
0
    def post(self, request):
        """Handle all actions from courses view"""

        if not request.user.is_staff:
            raise Http404

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

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

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

            is_xml_course = (modulestore().get_modulestore_type(course_key) == XML_MODULESTORE_TYPE)
            if course_found and is_xml_course:
                cdir = course.data_dir
                self.def_ms.courses.pop(cdir)

                # now move the directory (don't actually delete it)
                new_dir = "{course_dir}_deleted_{timestamp}".format(
                    course_dir=cdir,
                    timestamp=int(time.time())
                )
                os.rename(settings.DATA_DIR / cdir, settings.DATA_DIR / new_dir)

                self.msg += (u"<font color='red'>Deleted "
                             u"{0} = {1} ({2})</font>".format(
                                 cdir, course.id, course.display_name))

            elif course_found and not is_xml_course:
                # delete course that is stored with mongodb backend
                content_store = contentstore()
                commit = True
                delete_course(self.def_ms, content_store, course.id, commit)
                # don't delete user permission groups, though
                self.msg += \
                    u"<font color='red'>{0} {1} = {2} ({3})</font>".format(
                        _('Deleted'), course.location.to_deprecated_string(), course.id.to_deprecated_string(), course.display_name)
def look_up_registration_code(request, course_id):
    """
    Look for the registration_code in the database.
    and check if it is still valid, allowed to redeem or not.
    """
    course_key = CourseKey.from_string(course_id)
    code = request.GET.get('registration_code')
    course = get_course_by_id(course_key, depth=0)
    try:
        registration_code = CourseRegistrationCode.objects.get(code=code)
    except CourseRegistrationCode.DoesNotExist:
        return JsonResponse({
            'is_registration_code_exists': False,
            'is_registration_code_valid': False,
            'is_registration_code_redeemed': False,
            'message': _(u'The enrollment code ({code}) was not found for the {course_name} course.').format(
                code=code, course_name=course.display_name
            )
        }, status=400)  # status code 200: OK by default

    reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed(code)

    registration_code_detail_url = reverse('registration_code_details', kwargs={'course_id': unicode(course_id)})

    return JsonResponse({
        'is_registration_code_exists': True,
        'is_registration_code_valid': registration_code.is_valid,
        'is_registration_code_redeemed': reg_code_already_redeemed,
        'registration_code_detail_url': registration_code_detail_url
    })  # status code 200: OK by default
Пример #20
0
def show_cart(request):
    """
    This view shows cart items.
    """
    cart = Order.get_cart_for_user(request.user)
    total_cost = cart.total_cost
    cart_items = cart.orderitem_set.all().select_subclasses()
    shoppingcart_items = []
    for cart_item in cart_items:
        course_key = getattr(cart_item, 'course_id')
        if course_key:
            course = get_course_by_id(course_key, depth=0)
            shoppingcart_items.append((cart_item, course))

    site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME)

    callback_url = request.build_absolute_uri(
        reverse("shoppingcart.views.postpay_callback")
    )
    form_html = render_purchase_form_html(cart, callback_url=callback_url)
    context = {
        'order': cart,
        'shoppingcart_items': shoppingcart_items,
        'amount': total_cost,
        'site_name': site_name,
        'form_html': form_html,
    }
    return render_to_response("shoppingcart/shopping_cart.html", context)
Пример #21
0
def modify_special_forum_contributors(request, course_id):

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

    try:
        course_id = _check_rights(course_id, request.user, rolename)
    except UnauthorizedAccessError as e:
        return HttpResponseBadRequest(e.message)
    course = get_course_by_id(course_id)
    _check_custom_roles(course_id)

    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)
def _section_course_info(course_id):
    """ Provide data for the corresponding dashboard section """
    course = get_course_by_id(course_id, depth=None)

    section_data = {}
    section_data['section_key'] = 'course_info'
    section_data['section_display_name'] = 'Course Info'
    section_data['course_id'] = course_id
    section_data['course_display_name'] = course.display_name
    section_data['enrollment_count'] = CourseEnrollment.objects.filter(course_id=course_id).count()
    section_data['has_started'] = course.has_started()
    section_data['has_ended'] = course.has_ended()

    try:
        advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo
        section_data['grade_cutoffs'] = reduce(advance, course.grade_cutoffs.items(), "")[:-2]
    except Exception:
        section_data['grade_cutoffs'] = "Not Available"
    # section_data['offline_grades'] = offline_grades_available(course_id)

    try:
        section_data['course_errors'] = [(escape(a), '') for (a, _) in modulestore().get_item_errors(course.location)]
    except Exception:
        section_data['course_errors'] = [('Error fetching errors', '')]

    return section_data
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)
Пример #24
0
def is_commentable_cohorted(course_key, commentable_id):
    """
    Args:
        course_key: CourseKey
        commentable_id: string

    Returns:
        Bool: is this commentable cohorted?

    Raises:
        Http404 if the course doesn't exist.
    """
    course = courses.get_course_by_id(course_key)
    course_cohort_settings = get_course_cohort_settings(course_key)

    if not course_cohort_settings.is_cohorted or get_team(commentable_id):
        # this is the easy case :)
        ans = False
    elif (
            commentable_id in course.top_level_discussion_topic_ids or
            course_cohort_settings.always_cohort_inline_discussions is False
    ):
        # top level discussions have to be manually configured as cohorted
        # (default is not).
        # Same thing for inline discussions if the default is explicitly set to False in settings
        ans = commentable_id in course_cohort_settings.cohorted_discussions
    else:
        # inline discussions are cohorted by default
        ans = True

    log.debug(u"is_commentable_cohorted(%s, %s) = {%s}", course_key, commentable_id, ans)
    return ans
Пример #25
0
    def get(self, request, *args, **kwargs):
        """Shows logs of imports that happened as a result of a git import"""

        course_id = kwargs.get('course_id')
        if course_id:
            course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)

        # Set mongodb defaults even if it isn't defined in settings
        mongo_db = {
            'host': 'localhost',
            'user': '',
            'password': '',
            'db': 'xlog',
        }

        # Allow overrides
        if hasattr(settings, 'MONGODB_LOG'):
            for config_item in ['host', 'user', 'password', 'db', ]:
                mongo_db[config_item] = settings.MONGODB_LOG.get(
                    config_item, mongo_db[config_item])

        mongouri = 'mongodb://{user}:{password}@{host}/{db}'.format(**mongo_db)

        error_msg = ''

        try:
            if mongo_db['user'] and mongo_db['password']:
                mdb = mongoengine.connect(mongo_db['db'], host=mongouri)
            else:
                mdb = mongoengine.connect(mongo_db['db'], host=mongo_db['host'])
        except mongoengine.connection.ConnectionError:
            log.exception('Unable to connect to mongodb to save log, '
                          'please check MONGODB_LOG settings.')

        if course_id is None:
            # Require staff if not going to specific course
            if not request.user.is_staff:
                raise Http404
            cilset = CourseImportLog.objects.all().order_by('-created')
        else:
            try:
                course = get_course_by_id(course_id)
            except Exception:  # pylint: disable=broad-except
                log.info('Cannot find course {0}'.format(course_id))
                raise Http404

            # Allow only course team, instructors, and staff
            if not (request.user.is_staff or
                    CourseInstructorRole(course.id).has_user(request.user) or
                    CourseStaffRole(course.id).has_user(request.user)):
                raise Http404
            log.debug('course_id={0}'.format(course_id))
            cilset = CourseImportLog.objects.filter(course_id=course_id).order_by('-created')
            log.debug('cilset length={0}'.format(len(cilset)))
        mdb.disconnect()
        context = {'cilset': cilset,
                   'course_id': course_id.to_deprecated_string() if course_id else None,
                   'error_msg': error_msg}

        return render_to_response(self.template_name, context)
Пример #26
0
def add_cohort(course_key, name):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if CourseUserGroup.objects.filter(course_id=course_key,
                                      group_type=CourseUserGroup.COHORT,
                                      name=name).exists():
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

    cohort = CourseUserGroup.objects.create(
        course_id=course.id,
        group_type=CourseUserGroup.COHORT,
        name=name
    )
    tracker.emit(
        "edx.cohort.creation_requested",
        {"cohort_name": cohort.name, "cohort_id": cohort.id}
    )
    return cohort
Пример #27
0
    def setUp(self):
        """
        Set up tests
        """
        super(TestFieldOverrides, self).setUp()

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

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

        self.addCleanup(RequestCache.clear_request_cache)

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

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

        def cleanup_provider_classes():
            """
            After everything is done, clean up by un-doing the change to the
            OverrideFieldData object that is done during the wrap method.
            """
            OverrideFieldData.provider_classes = None
        self.addCleanup(cleanup_provider_classes)
Пример #28
0
    def publish(self):
        """Publish pdf of certificate."""
        students = self._get_students()

        print "\nFetching course data for {0}".format(self.course_id)
        course = courses.get_course_by_id(self.course_id)
        if not course.has_ended():
            raise CertPDFException('This couse is not ended.')

        print "Fetching enrollment for students({0}).".format(self.course_id)
        for student in students.iterator():
            certs = GeneratedCertificate.objects.filter(
                user=student, course_id=self.course_id,
                status=CertificateStatuses.generating)

            for cert in certs.iterator():
                if cert.download_url:
                    if not self.noop:
                        cert.status = CertificateStatuses.downloadable
                        print "Publish {0}'s certificate : Status {1}".format(
                            student.username, cert.status)
                        cert.save()
                    else:
                        print "Publish {0}'s certificate : Status {1} (Noop)".format(
                            student.username, cert.status)
                else:
                    print "Publish {0}'s certificate : Error download_url is empty.".format(
                        student.username)
Пример #29
0
def show_unit_extensions(request, course_id):
    """
    Shows all of the students which have due date extensions for the given unit.
    """
    course = get_course_by_id(course_id)
    unit = find_unit(course, request.GET.get('url'))
    return JsonResponse(dump_module_extensions(course, unit))
Пример #30
0
def is_commentable_cohorted(course_key, commentable_id):
    """
    Args:
        course_key: CourseKey
        commentable_id: string

    Returns:
        Bool: is this commentable cohorted?

    Raises:
        Http404 if the course doesn't exist.
    """
    course = courses.get_course_by_id(course_key)

    if not course.is_cohorted:
        # this is the easy case :)
        ans = False
    elif commentable_id in course.top_level_discussion_topic_ids:
        # top level discussions have to be manually configured as cohorted
        # (default is not)
        ans = commentable_id in course.cohorted_discussions
    else:
        # inline discussions are cohorted by default
        ans = True

    log.debug(u"is_commentable_cohorted({0}, {1}) = {2}".format(
        course_key, commentable_id, ans
    ))
    return ans
Пример #31
0
def iterate_grades_for(course_or_id, students):
    """
    Given a course_id and an iterable of students (User), yield a GradeResult
    for every student enrolled in the course.  GradeResult is a named tuple of:

    (student, gradeset, err_msg)

    If an error occurred, gradeset will be an empty dict and err_msg will be an
    exception message. If there was no error, err_msg is an empty string.

    The gradeset is a dictionary with the following fields:

    - grade : A final letter grade.
    - percent : The final percent for the class (rounded up).
        - section_breakdown : A breakdown of each section that makes
        up the grade. (For display)
    - grade_breakdown : A breakdown of the major components that
        make up the final grade. (For display)
    - raw_scores: contains scores for every graded module
    """
    if isinstance(course_or_id, (basestring, CourseKey)):
        course = get_course_by_id(course_or_id)
    else:
        course = course_or_id

    for student in students:
        with dog_stats_api.timer('lms.grades.iterate_grades_for', tags=[u'action:{}'.format(course.id)]):
            try:
                gradeset = summary(student, course)
                yield GradeResult(student, gradeset, "")
            except Exception as exc:  # pylint: disable=broad-except
                # Keep marching on even if this student couldn't be graded for
                # some reason, but log it for future reference.
                log.exception(
                    'Cannot grade student %s (%s) in course %s because of exception: %s',
                    student.username,
                    student.id,
                    course.id,
                    exc.message
                )
                yield GradeResult(student, {}, exc.message)
Пример #32
0
    def handle(self, *args, **__options):
        """
        Migrates existing SGA submissions.
        """
        if not args:
            raise CommandError('Please specify the course id.')
        if len(args) > 1:
            raise CommandError('Too many arguments.')
        course_id = args[0]
        course_key = CourseKey.from_string(course_id)
        course = get_course_by_id(course_key)

        student_modules = StudentModule.objects.filter(
            course_id=course.id).filter(module_state_key__contains='ev_sa')

        blocks = {}
        for student_module in student_modules:
            block_id = student_module.module_state_key
            if block_id.block_type != 'ev_sa':
                continue
            block = blocks.get(block_id)
            if not block:
                blocks[block_id] = block = modulestore().get_item(block_id)
            state = json.loads(student_module.state)
            sha1 = state.get('uploaded_sha1')
            if not sha1:
                continue
            student = student_module.student
            submission_id = block.student_submission_id(
                anonymous_id_for_user(student, course.id))
            answer = {
                "sha1": sha1,
                "filename": state.get('uploaded_filename'),
                "mimetype": state.get('uploaded_mimetype'),
            }
            submission = submissions_api.create_submission(
                submission_id, answer)
            score = state.get('score')  # float
            if score:
                submissions_api.set_score(submission['uuid'], int(score),
                                          block.max_score())
Пример #33
0
def look_up_registration_code(request, course_id):
    """
    Look for the registration_code in the database.
    and check if it is still valid, allowed to redeem or not.
    """
    course_key = CourseKey.from_string(course_id)
    code = request.GET.get('registration_code')
    course = get_course_by_id(course_key, depth=0)
    try:
        registration_code = CourseRegistrationCode.objects.get(code=code)
    except CourseRegistrationCode.DoesNotExist:
        return JsonResponse(
            {
                'is_registration_code_exists':
                False,
                'is_registration_code_valid':
                False,
                'is_registration_code_redeemed':
                False,
                'message':
                _('The enrollment code ({code}) was not found for the {course_name} course.'
                  ).format(code=code, course_name=course.display_name)
            },
            status=400)  # status code 200: OK by default

    reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed(
        code)

    registration_code_detail_url = reverse(
        'registration_code_details', kwargs={'course_id': unicode(course_id)})

    return JsonResponse({
        'is_registration_code_exists':
        True,
        'is_registration_code_valid':
        registration_code.is_valid,
        'is_registration_code_redeemed':
        reg_code_already_redeemed,
        'registration_code_detail_url':
        registration_code_detail_url
    })  # status code 200: OK by default
Пример #34
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
            try:
                ccx = CustomCourseForEdX.objects.get(pk=ccx_id)
            except CustomCourseForEdX.DoesNotExist:
                raise Http404

        if ccx:
            course_key = ccx.course_id
        course = get_course_by_id(course_key, depth=None)

        if not course.enable_ccx:
            raise Http404
        else:
            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:
                # if there is a ccx, we must validate that it is the ccx for this coach
                role = CourseCcxCoachRole(course_key)
                if not role.has_user(request.user):
                    return HttpResponseForbidden(
                        _('You must be a CCX Coach to access this view.'))
                elif 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)
Пример #35
0
def _listen_for_passing_grade(sender, user, course_id, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a learner passing a course, send cert generation task,
    downstream signal from COURSE_GRADE_CHANGED
    """
    # No flags enabled
    if (not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY)
            and not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY)):
        return

    if courses.get_course_by_id(course_id, depth=0).self_paced:
        if not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY):
            return
    else:
        if not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY):
            return

    if fire_ungenerated_certificate_task(user, course_id):
        log.info(
            u'Certificate generation task initiated for {user} : {course} via passing grade'
            .format(user=user.id, course=course_id))
Пример #36
0
def ensure_certif(request, course_id):
    user_id = request.user.id
    username = request.user.username
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course_tma = get_course_by_id(course_key)
    is_graded = course_tma.is_graded
    grade_cutoffs = modulestore().get_course(
        course_key, depth=0).grade_cutoffs['Pass'] * 100
    grading_note = CourseGradeFactory().create(request.user, course_tma)
    passed = grading_note.passed
    percent = float(int(grading_note.percent * 1000) / 10)
    overall_progress = get_overall_progress(request.user.id, course_key)
    context = {
        'passed': passed,
        'percent': percent,
        'is_graded': is_graded,
        'grade_cutoffs': grade_cutoffs,
        'overall_progress': overall_progress
    }

    return JsonResponse(context)
Пример #37
0
def delete_redemption_entry(request, code_redemption, course_key):
    """
    delete the redemption entry from the table and
    unenroll the user who used the registration code
    for the enrollment and send him/her the unenrollment email.
    """
    user = code_redemption.redeemed_by
    email_address = code_redemption.redeemed_by.email
    full_name = code_redemption.redeemed_by.profile.name
    CourseEnrollment.unenroll(user, course_key, skip_refund=True)

    course = get_course_by_id(course_key, depth=0)
    email_params = get_email_params(course, True, secure=request.is_secure())
    email_params['message_type'] = 'enrolled_unenroll'
    email_params['email_address'] = email_address
    email_params['full_name'] = full_name
    send_mail_to_student(email_address, email_params)

    # remove the redemption entry from the database.
    log.info('deleting redemption entry (%s) from the database.', code_redemption.id)
    code_redemption.delete()
Пример #38
0
    def test_get_static_tab_fragment(self):
        self.setup_user()
        course = get_course_by_id(self.course.id)
        self.addCleanup(set_current_request, None)
        request = get_mock_request(self.user)
        tab = xmodule_tabs.CourseTabList.get_tab_by_slug(
            course.tabs, 'new_tab')

        # Test render works okay
        tab_content = get_static_tab_fragment(request, course, tab).content
        self.assertIn(text_type(self.course.id), tab_content)
        self.assertIn('static_tab', tab_content)

        # Test when render raises an exception
        with patch('courseware.views.views.get_module') as mock_module_render:
            mock_module_render.return_value = MagicMock(render=Mock(
                side_effect=Exception('Render failed!')))
            static_tab_content = get_static_tab_fragment(request, course,
                                                         tab).content
            self.assertIn("this module is temporarily unavailable",
                          static_tab_content)
Пример #39
0
    def rows(self):
        for course_id in course_ids_between(self.start_word, self.end_word):
            cur_course = get_course_by_id(course_id)
            university = cur_course.org
            course = cur_course.number + " " + cur_course.display_name_with_default
            total_payments_collected = CertificateItem.verified_certificates_monetary_field_sum(
                course_id, 'purchased', 'unit_cost')
            service_fees = CertificateItem.verified_certificates_monetary_field_sum(
                course_id, 'purchased', 'service_fee')
            num_refunds = CertificateItem.verified_certificates_count(
                course_id, "refunded")
            amount_refunds = CertificateItem.verified_certificates_monetary_field_sum(
                course_id, 'refunded', 'unit_cost')
            num_transactions = (
                num_refunds * 2) + CertificateItem.verified_certificates_count(
                    course_id, "purchased")

            yield [
                university, course, num_transactions, total_payments_collected,
                service_fees, num_refunds, amount_refunds
            ]
    def _get_context(self):
        """
        """
        if self.is_course_staff():
            return dict(
                user_is_staff=True,
                rubric=self.rubric,
                users=self.get_student_list(),
                reload_url=self.runtime.local_resource_url(self, 'public/img/reload-icon.png'),
                cohorts=get_cohort_names(get_course_by_id(self.course_id)),
                teams=self.api_teams.get_course_teams(unicode(self.course_id)),
                grading_message=self.grading_message,
            )

        comment = self.get_comment()
        return dict(
            user_is_staff=False,
            grading_message=comment if comment and self.score else self.grading_message,
            score=self.score,
            max_score=self.points
        )
Пример #41
0
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs):  # pylint: disable=unused-argument
    switches = waffle.waffle()
    # No flags enabled
    if (
        not switches.is_enabled(waffle.SELF_PACED_ONLY) and
        not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY)
    ):
        return

    if courses.get_course_by_id(instance.course_id, depth=0).self_paced:
        if not switches.is_enabled(waffle.SELF_PACED_ONLY):
            return
    else:
        if not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY):
            return

    fire_ungenerated_certificate_task(instance.user, instance.course_id)
    log.info(u'Certificate generation task initiated for {user} : {course} via whitelist'.format(
        user=instance.user.id,
        course=instance.course_id
    ))
Пример #42
0
def remove_master_course_staff_from_ccx_for_existing_ccx(apps, schema_editor):
    """
    Remove all staff and instructors of master course from respective CCX(s).

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

    """
    CustomCourseForEdX = apps.get_model("ccx", "CustomCourseForEdX")
    list_ccx = CustomCourseForEdX.objects.all()
    for ccx in list_ccx:
        if ccx.course_id.deprecated:
            # prevent migration for deprecated course ids.
            continue
        ccx_locator = CCXLocator.from_course_locator(ccx.course_id,
                                                     unicode(ccx.id))
        remove_master_course_staff_from_ccx(get_course_by_id(ccx.course_id),
                                            ccx_locator,
                                            ccx.display_name,
                                            send_email=False)
Пример #43
0
def add_cohort(course_key, name):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if CourseUserGroup.objects.filter(course_id=course_key,
                                      group_type=CourseUserGroup.COHORT,
                                      name=name).exists():
        raise ValueError("Can't create two cohorts with the same name")

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

    return CourseUserGroup.objects.create(
        course_id=course.id,
        group_type=CourseUserGroup.COHORT,
        name=name
    )
Пример #44
0
def move_to_verified_cohort(sender, instance, **kwargs):  # pylint: disable=unused-argument
    """
    If the learner has changed modes, update assigned cohort iff the course is using
    the Automatic Verified Track Cohorting MVP feature.
    """
    course_key = instance.course_id
    verified_cohort_enabled = VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(
        course_key)
    verified_cohort_name = VerifiedTrackCohortedCourse.verified_cohort_name_for_course(
        course_key)

    if verified_cohort_enabled and (instance.mode != instance._old_mode):  # pylint: disable=protected-access
        if not is_course_cohorted(course_key):
            log.error(
                "Automatic verified cohorting enabled for course '%s', but course is not cohorted",
                course_key)
        else:
            existing_cohorts = get_course_cohorts(get_course_by_id(course_key),
                                                  CourseCohort.MANUAL)
            if any(cohort.name == verified_cohort_name
                   for cohort in existing_cohorts):
                args = {
                    'course_id': unicode(course_key),
                    'user_id': instance.user.id,
                    'verified_cohort_name': verified_cohort_name
                }
                # Do the update with a 3-second delay in hopes that the CourseEnrollment transaction has been
                # completed before the celery task runs. We want a reasonably short delay in case the learner
                # immediately goes to the courseware.
                sync_cohort_with_mode.apply_async(kwargs=args, countdown=3)

                # In case the transaction actually was not committed before the celery task runs,
                # run it again after 5 minutes. If the first completed successfully, this task will be a no-op.
                sync_cohort_with_mode.apply_async(kwargs=args, countdown=300)
            else:
                log.error(
                    "Automatic verified cohorting enabled for course '%s', but cohort named '%s' does not exist.",
                    course_key,
                    verified_cohort_name,
                )
Пример #45
0
    def handle(self, *args, **options):
        if not options['course']:
            raise CommandError(Command.course_option.help)

        try:
            course_key = CourseKey.from_string(options['course'])
        except InvalidKeyError:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(
                options['course'])

        course = get_course_by_id(course_key)

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

        try:
            if options['delete']:
                if len(args) != 1:
                    raise CommandError(Command.delete_option.help)
                num = int(args[0])
                if query_yes_no('Deleting tab {0} Confirm?'.format(num),
                                default='no'):
                    tabs.primitive_delete(course,
                                          num - 1)  # -1 for 0-based indexing
            elif options['insert']:
                if len(args) != 3:
                    raise CommandError(Command.insert_option.help)
                num = int(args[0])
                tab_type = args[1]
                name = args[2]
                if query_yes_no(
                        'Inserting tab {0} "{1}" "{2}" Confirm?'.format(
                            num, tab_type, name),
                        default='no'):
                    tabs.primitive_insert(course, num - 1, tab_type,
                                          name)  # -1 as above
        except ValueError as e:
            # Cute: translate to CommandError so the CLI error prints nicely.
            raise CommandError(e)
Пример #46
0
def is_commentable_divided(course_key,
                           commentable_id,
                           course_discussion_settings=None):
    """
    Args:
        course_key: CourseKey
        commentable_id: string
        course_discussion_settings: CourseDiscussionSettings model instance (optional). If not
            supplied, it will be retrieved via the course_key.

    Returns:
        Bool: is this commentable divided, meaning that learners are divided into
        groups (either Cohorts or Enrollment Tracks) and only see posts within their group?

    Raises:
        Http404 if the course doesn't exist.
    """
    if not course_discussion_settings:
        course_discussion_settings = get_course_discussion_settings(course_key)

    course = courses.get_course_by_id(course_key)

    if not course_discussion_division_enabled(
            course_discussion_settings) or get_team(commentable_id):
        # this is the easy case :)
        ans = False
    elif (commentable_id in course.top_level_discussion_topic_ids
          or course_discussion_settings.always_divide_inline_discussions is
          False):
        # top level discussions have to be manually configured as divided
        # (default is not).
        # Same thing for inline discussions if the default is explicitly set to False in settings
        ans = commentable_id in course_discussion_settings.divided_discussions
    else:
        # inline discussions are divided by default
        ans = True

    log.debug(u"is_commentable_divided(%s, %s) = {%s}", course_key,
              commentable_id, ans)
    return ans
Пример #47
0
def course_grade_from_course_id(learner, course_id):
    """Get the edx-platform's course grade for this enrollment

    IMPORTANT: Do not use in API calls as this is an expensive operation.
    Only use in async or pipeline.

    We handle the exception so that we return a specific `CourseNotFound`
    instead of the non-specific `Http404`
    edx-platform `get_course_by_id` function raises a generic `Http404` if it
    cannot find a course in modulestore. We trap this and raise our own
    `CourseNotFound` exception as it is more specific.

    TODO: Consider optional kwarg param or Figures setting to log performance.
          Bonus points: Make id a decorator
    """
    try:
        course = get_course_by_id(course_key=as_course_key(course_id))
    except Http404:
        raise CourseNotFound('{}'.format(str(course_id)))
    course._field_data_cache = {}  # pylint: disable=protected-access
    course.set_grading_policy(course.grading_policy)
    return course_grade(learner, course)
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)
Пример #49
0
    def send_grandfather_email(self, user, certificates, mock_run=False):
        """
        Send the 'grandfathered' email informing historical students that they
        may now post their certificates on their LinkedIn profiles.
        """
        courses_list = []
        for cert in certificates:
            course = get_course_by_id(cert.course_id)
            course_url = 'https://{}{}'.format(
                settings.SITE_NAME,
                reverse('course_root', kwargs={'course_id': cert.course_id})
            )

            course_title = course.display_name_with_default

            course_img_url = 'https://{}{}'.format(settings.SITE_NAME, course_image_url(course))
            course_end_date = course.end.strftime('%b %Y')
            course_org = course.org

            courses_list.append({
                'course_url': course_url,
                'course_org': course_org,
                'course_title': course_title,
                'course_image_url': course_img_url,
                'course_end_date': course_end_date,
                'linkedin_add_url': self.certificate_url(cert),
            })

        context = {
            'courses_list': courses_list,
            'num_courses': len(courses_list),
            'google_analytics': settings.GOOGLE_ANALYTICS_LINKEDIN,
        }
        body = render_to_string('linkedin/linkedin_email.html', context)
        subject = u'{}, Add your Achievements to your LinkedIn Profile'.format(user.profile.name)
        if mock_run:
            return True
        else:
            return self.send_email(user, subject, body)
Пример #50
0
def offline_grade_calculation(course_id):
    '''
    Compute grades for all students for a specified course, and save results to the DB.
    '''

    tstart = time.time()
    enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).prefetch_related("groups").order_by('username')

    enc = MyEncoder()

    class DummyRequest(object):
        META = {}
        def __init__(self):
            return
        def get_host(self):
            return 'edx.mit.edu'
        def is_secure(self):
            return False

    request = DummyRequest()

    print "%d enrolled students" % len(enrolled_students)
    course = get_course_by_id(course_id)

    for student in enrolled_students:
        gradeset = grades.grade(student, request, course, keep_raw_scores=True)
        gs = enc.encode(gradeset)
        ocg, created = models.OfflineComputedGrade.objects.get_or_create(user=student, course_id=course_id)
        ocg.gradeset = gs
        ocg.save()
        print "%s done" % student  	# print statement used because this is run by a management command

    tend = time.time()
    dt = tend - tstart

    ocgl = models.OfflineComputedGradeLog(course_id=course_id, seconds=dt, nstudents=len(enrolled_students))
    ocgl.save()
    print ocgl
    print "All Done!"
Пример #51
0
    def setUp(self):
        """
        Set up tests
        """
        super(TestCCXGrades, self).setUp()

        # Create instructor account
        self.coach = coach = AdminFactory.create()
        self.client.login(username=coach.username, password="******")

        # Create CCX
        role = CourseCcxCoachRole(self._course.id)
        role.add_users(coach)
        ccx = CcxFactory(course_id=self._course.id, coach=self.coach)

        # override course grading policy and make last section invisible to students
        override_field_for_ccx(
            ccx, self._course, 'grading_policy', {
                'GRADER': [{
                    'drop_count': 0,
                    'min_count': 2,
                    'short_label': 'HW',
                    'type': 'Homework',
                    'weight': 1
                }],
                'GRADE_CUTOFFS': {
                    'Pass': 0.75
                },
            })
        override_field_for_ccx(ccx, self.sections[-1], 'visible_to_staff_only',
                               True)

        # create a ccx locator and retrieve the course structure using that key
        # which emulates how a student would get access.
        self.ccx_key = CCXLocator.from_course_locator(self._course.id, ccx.id)
        self.course = get_course_by_id(self.ccx_key, depth=None)
        setup_students_and_grades(self)
        self.client.login(username=coach.username, password="******")
        self.addCleanup(RequestCache.clear_request_cache)
Пример #52
0
def grade_collector_stat():
    """
    Task for update user grades.
    """
    this_update_date = datetime.now()
    users_by_course = get_items_for_grade_update()

    collected_stat = []
    for course_string_id, users in users_by_course.iteritems():
        try:
            course_key = CourseKey.from_string(course_string_id)
            course = get_course_by_id(course_key, depth=0)
        except (InvalidKeyError, Http404):
            continue

        with modulestore().bulk_operations(course_key):
            for user in users:
                grades = get_grade_summary(user, course)
                if not grades:
                    continue
                exam_info = OrderedDict()
                for grade in grades['section_breakdown']:
                    exam_info[grade['label']] = int(grade['percent'] * 100.0)
                exam_info['total'] = int(grades['percent'] * 100.0)

                collected_stat.append(({
                    'course_id': course_key,
                    'student_id': user
                }, {
                    'exam_info': json.dumps(exam_info),
                    'total': grades['percent']
                }))

    with transaction.atomic():
        for key_values, additional_info in collected_stat:
            key_values['defaults'] = additional_info
            GradeStatistic.objects.update_or_create(**key_values)

        LastGradeStatUpdate(last_update=this_update_date).save()
Пример #53
0
def offline_grade_calculation(course_key):
    '''
    Compute grades for all students for a specified course, and save results to the DB.
    '''

    tstart = time.time()
    enrolled_students = User.objects.filter(
        courseenrollment__course_id=course_key,
        courseenrollment__is_active=1).prefetch_related("groups").order_by(
            'username')

    enc = MyEncoder()

    print "{} enrolled students".format(len(enrolled_students))
    course = get_course_by_id(course_key)

    for student in enrolled_students:
        request = DummyRequest()
        request.user = student
        request.session = {}

        gradeset = grades.grade(student, request, course, keep_raw_scores=True)
        gs = enc.encode(gradeset)
        ocg, _created = models.OfflineComputedGrade.objects.get_or_create(
            user=student, course_id=course_key)
        ocg.gradeset = gs
        ocg.save()
        print "%s done" % student  # print statement used because this is run by a management command

    tend = time.time()
    dt = tend - tstart

    ocgl = models.OfflineComputedGradeLog(course_id=course_key,
                                          seconds=dt,
                                          nstudents=len(enrolled_students))
    ocgl.save()
    print ocgl
    print "All Done!"
Пример #54
0
def add_cohort(course_key, name, assignment_type):
    """
    Add a cohort to a course.  Raises ValueError if a cohort of the same name already
    exists.
    """
    log.debug("Adding cohort %s to %s", name, course_key)
    if is_cohort_exists(course_key, name):
        raise ValueError(_("You cannot create two cohorts with the same name"))

    try:
        course = courses.get_course_by_id(course_key)
    except Http404:
        raise ValueError("Invalid course_key")

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

    tracker.emit("edx.cohort.creation_requested", {
        "cohort_name": cohort.name,
        "cohort_id": cohort.id
    })
    return cohort
    def base_process(self, request, course_id):
        """
        Preprocess request, check permission and select course.
        """
        course_id = request.POST.get('course_id', course_id)
        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            log.error(
                "Unable to find course with course key %s while loading the Instructor Analytics Dashboard.",
                course_id)
            return HttpResponseBadRequest()

        course = get_course_by_id(course_key, depth=0)
        if not has_access(request.user, self.group_name, course):
            log.error("Statistics not available for user type `%s`",
                      request.user)
            return HttpResponseForbidden()

        return self.process(request,
                            course_key=course_key,
                            course=course,
                            course_id=course_id)
Пример #56
0
def get_group_names_by_id(course_discussion_settings):
    """
    Creates of a dict of group_id to learner-facing group names, for the division_scheme
    in use as specified by course_discussion_settings.
    Args:
        course_discussion_settings: CourseDiscussionSettings model instance

    Returns: dict of group_id to learner-facing group names. If no division_scheme
    is in use, returns an empty dict.
    """
    division_scheme = _get_course_division_scheme(course_discussion_settings)
    course_key = course_discussion_settings.course_id
    if division_scheme == CourseDiscussionSettings.COHORT:
        return get_cohort_names(courses.get_course_by_id(course_key))
    elif division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK:
        # We negate the group_ids from dynamic partitions so that they will not conflict
        # with cohort IDs (which are an auto-incrementing integer field, starting at 1).
        return {
            -1 * group.id: group.name
            for group in _get_enrollment_track_groups(course_key)
        }
    else:
        return {}
Пример #57
0
 def certificate_url(self, certificate):
     """
     Generates a certificate URL based on LinkedIn's documentation.  The
     documentation is from a Word document: DAT_DOCUMENTATION_v3.12.docx
     """
     course = get_course_by_id(certificate.course_id)
     tracking_code = '-'.join([
         'eml',
         'prof',  # the 'product'--no idea what that's supposed to mean
         'edX',  # Partner's name
         course.number,  # Certificate's name
         'gf'])
     query = [
         ('pfCertificationName', course.display_name_with_default),
         ('pfAuthorityId', settings.LINKEDIN_API['COMPANY_ID']),
         ('pfCertificationUrl', certificate.download_url),
         ('pfLicenseNo', certificate.course_id),
         ('pfCertStartDate', course.start.strftime('%Y%m')),
         ('_mSplash', '1'),
         ('trk', tracking_code),
         ('startTask', 'CERTIFICATION_NAME'),
         ('force', 'true')]
     return 'http://www.linkedin.com/profile/guided?' + urllib.urlencode(query)
    def handle(self, *args, **options):

        course_id = args[0]

        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            course_key = SlashSeparatedCourseKey.from_deprecated_string(
                course_id)

        try:
            course = get_course_by_id(course_key)
        except Exception as err:  # pylint: disable=broad-except
            print "Course not found."
            return

        enrolled_students = User.objects.filter(
            courseenrollment__course_id=course_key,
            courseenrollment__is_active=1,
        ).values_list('username', flat=True)

        for student in enrolled_students:
            print student
Пример #59
0
def _get_course_cohort_settings(course_key):
    """
    Return cohort settings for a course. NOTE that the only non-deprecated fields in
    CourseCohortSettings are `course_id` and  `is_cohorted`. Other fields should only be used for
    migration purposes.

    Arguments:
        course_key: CourseKey

    Returns:
        A CourseCohortSettings object. NOTE that the only non-deprecated field in
        CourseCohortSettings are `course_id` and  `is_cohorted`. Other fields should only be used
        for migration purposes.

    Raises:
        Http404 if course_key is invalid.
    """
    try:
        course_cohort_settings = CourseCohortsSettings.objects.get(course_id=course_key)
    except CourseCohortsSettings.DoesNotExist:
        course = courses.get_course_by_id(course_key)
        course_cohort_settings = migrate_cohort_settings(course)
    return course_cohort_settings
Пример #60
0
    def validate_course(self):
        """
        Validates that enrollment is available on course
        """
        try:
            course_key = CourseKey.from_string(self.params['course_id'])
            course = get_course_by_id(course_key)

            if not course.self_paced:
                if not enrollment_has_started(course):
                    # Translators: This is for the ForUs API
                    self.errors['course_id'].append(
                        _('The course has not yet been opened for enrollment, '
                          'please go back to the ForUs portal and enroll in other courses'
                          ))

                if enrollment_has_ended(course):
                    # Translators: This is for the ForUs API
                    self.errors['course_id'].append(
                        _('Enrollment for this course has been closed, '
                          'please go back to the ForUs portal and enroll in other courses'
                          ))

        except InvalidKeyError:
            log.warning(
                u"User {username} tried to {action} with invalid course id: {course_id}"
                .format(
                    username=self.params.get('username'),
                    action=self.params.get('enrollment_action'),
                    course_id=self.params.get('course_id'),
                ))

            self.mark_as_invalid('course_id', _('course id'))
        except Http404:
            # Translators: This is for the ForUs API
            self.errors['course_id'].append(
                _('The requested course does not exist'))