Exemplo n.º 1
0
    def test_handle(self, store_mock, get_mock, del_mock, call_mock):
        pr.Command().handle(*self.args, **self.options_get)
        get_mock.assert_called_once_with(
            *[CourseLocator.from_string(arg) for arg in self.args])

        pr.Command().handle(*self.args, **self.options_create)
        call_mock.assert_called_once_with('create_report_task', *['create'],
                                          **{'course_id': self.args[0]})

        pr.Command().handle(*self.args, **self.options_delete)
        del_mock.assert_called_once_with(
            *[CourseLocator.from_string(arg) for arg in self.args])

        msg = '^"course_id" is not specified$'
        with self.assertRaisesRegexp(CommandError, msg):
            pr.Command().handle(*[], **self.options_get)

        msg = '^Cannot specify "-c" option and "-d" option at the same time.$'
        with self.assertRaisesRegexp(CommandError, msg):
            pr.Command().handle(*self.args, **self.options_error)

        msg = '^CSV not found.$'
        with self.assertRaisesRegexp(CommandError, msg):
            get_mock.side_effect = NotFoundError()
            pr.Command().handle(*self.args, **self.options_get)
Exemplo n.º 2
0
def survey_ajax(request):
    """Ajax call to submit a survey."""
    MAX_CHARACTER_LENGTH = 1000

    course_id = request.POST.get('course_id')
    unit_id = request.POST.get('unit_id')
    survey_name = request.POST.get('survey_name')
    survey_answer = request.POST.get('survey_answer')

    if not course_id or not unit_id:
        log.warning("Illegal parameter. course_id=%s, unit_id=%s" % (course_id, unit_id))
        raise Http404
    if not survey_name:
        log.warning("Illegal parameter. survey_name=%s" % survey_name)
        raise Http404
    if not survey_answer:
        log.warning("Illegal parameter. survey_answer=%s" % survey_answer)
        raise Http404
    try:
        obj = json.loads(survey_answer)
    except:
        log.warning("Illegal parameter. survey_answer=%s" % survey_answer)
        raise Http404
    for k, v in obj.iteritems():
        if len(v) > MAX_CHARACTER_LENGTH:
            log.warning("%s cannot be more than %d characters long." % (k, MAX_CHARACTER_LENGTH))
            raise Http404

    try:
        submission = SurveySubmission.objects.filter(
            course_id=CourseLocator.from_string(course_id),
            unit_id=unit_id,
            user=request.user
        ).order_by('created')[0:1].get()
    except SurveySubmission.DoesNotExist:
        pass
    else:
        return JsonResponse({
            'success': False,
            'survey_answer': submission.get_survey_answer(),
        })

    submission = SurveySubmission(
        course_id=CourseLocator.from_string(course_id),
        unit_id=unit_id,
        user=request.user,
        survey_name=survey_name,
        survey_answer=survey_answer,
    )
    submission.save()
    return JsonResponse({'success': True})
Exemplo n.º 3
0
    def setUp(self):
        self.user = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.debug = False
        self.noop = False
        self.file_prefix = ""
        self.exclude = None

        self.student = UserFactory.create()
        self.cert = GeneratedCertificateFactory.build(
            user=self.student, status="downloadable")

        self.summary = [
            {"display_name": "section_name",
             "sections": [{
                 "display_name": "subsec_name",
                 "format": "HW", "section_total": [10, 10],
                 "scores": [
                     (10, 10, True, "unit_name")]}]}]
        self.grade = {"grade": "Pass", "percent": 1}
        self.invalid_grade = {"grade": None, "percent": 1}
        self.total = {'users': 0, 'pass': 0, 'notpass': 0}
        self.total_with_grade = {
            'users': 0, 'pass': 0, 'notpass': 0, 'Pass': 0}
        self.total_items = {
            'users': 3, 'pass': 2, 'notpass': 1, 'A': 1, 'B': 1}
Exemplo n.º 4
0
def get_structure_history_graph(course):
    course_key = CourseLocator.from_string(course)
    try:
        history_graph = API.get_structure_history_graph(course_key)
    except API.CourseNotFound:
        abort(404)
    return Response(dumps(history_graph), mimetype='application/json')
Exemplo n.º 5
0
def get_course_metadata(course):
    course_key = CourseLocator.from_string(course)
    try:
        course_metadata = API.get_course_metadata(course_key)
    except API.CourseNotFound:
        abort(404)
    return Response(dumps(course_metadata), mimetype='application/json')
Exemplo n.º 6
0
    def handle(self, *args, **options):
        create_report = options['create']
        delete_report = options['delete']

        if len(args) != 1:
            raise CommandError('"course_id" is not specified')
        elif create_report and delete_report:
            raise CommandError(
                'Cannot specify "-c" option and "-d" option at the same time.')

        course_id = args[0]
        try:
            course_id = CourseLocator.from_string(course_id)
        except InvalidKeyError:
            raise CommandError("'{}' is an invalid course_id".format(course_id))
        if not modulestore().get_course(course_id):
            raise CommandError("The specified course does not exist.")

        if delete_report:
            try:
                delete_pgreport_csv(course_id)
            except NotFoundError:
                raise CommandError("CSV not found.")
            call_command('create_report_task', *['clear_cache'], **{'course_id': course_id.to_deprecated_string()})
        elif create_report:
            call_command('create_report_task', *['create'], **{'course_id': course_id.to_deprecated_string()})
        else:
            try: 
                get_pgreport_csv(course_id)
            except NotFoundError:
                raise CommandError("CSV not found.")
Exemplo n.º 7
0
    def test_gitlog_pagination_out_of_range_invalid(self):
        """
        Make sure the pagination behaves properly when the requested page is out
        of range.
        """

        self._setstaff_login()

        mongoengine.connect(TEST_MONGODB_LOG['db'])

        for _ in range(15):
            CourseImportLog(
                course_id=CourseLocator.from_string("test/test/test"),
                location="location",
                import_log="import_log",
                git_log="git_log",
                repo_dir="repo_dir",
                created=datetime.now()).save()

        for page, expected in [(-1, 1), (1, 1), (2, 2), (30, 2), ('abc', 1)]:
            response = self.client.get('{}?page={}'.format(
                reverse('gitlogs'), page))
            self.assertContains(response, u'Page {} of 2'.format(expected))

        CourseImportLog.objects.delete()
Exemplo n.º 8
0
    def handle(self, *args, **options):
        '''Handle management command request'''

        if len(args) != 3:
            raise CommandError('Usage is set_course_end {0}'.format(self.args))

        try:
            end_date = pytz.timezone('Europe/Moscow').localize(
                parse_date(args[1], dayfirst=False,
                           yearfirst=True)).astimezone(pytz.utc)
        except:
            raise CommandError('Could not parse date "{0}"'.format(args[1]))
        try:
            user = User.objects.get(email=args[2])
        except:
            raise CommandError('Could not find user with email "{0}"'.format(
                args[2]))

        locator = CourseLocator.from_string(args[0])

        coursedata = CourseDetails.fetch(locator)
        old_end_date = coursedata.end_date
        coursedata.end_date = end_date
        print "Changing course {0} end date from {1} to {2}".format(
            locator, old_end_date, end_date)
        coursedata.update_from_json(locator, coursedata.__dict__, user)
Exemplo n.º 9
0
 def test_from_course_locator(self):
     course_locator = CourseLocator.from_string(
         self.course_key_string)
     course_key = as_course_key(course_locator)
     assert isinstance(course_key, CourseKey)
     assert course_key == self.course_key
     assert course_key is course_locator
Exemplo n.º 10
0
def recalculate_subsection_grade_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the SCORE_CHANGED signal and trigger an update.
    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of SCORE_CHANGED):
       - points_possible: Maximum score available for the exercise
       - points_earned: Score obtained by the user
       - user: User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    student = kwargs['user']
    course_key = CourseLocator.from_string(kwargs['course_id'])
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

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

    subsections_to_update = collected_block_structure.get_transformer_block_field(
        scored_block_usage_key,
        GradesTransformer,
        'subsections',
        set()
    )
    for subsection_usage_key in subsections_to_update:
        transformed_subsection_structure = get_course_blocks(
            student,
            subsection_usage_key,
            collected_block_structure=collected_block_structure,
        )
        SubsectionGradeFactory(student).update(subsection_usage_key, transformed_subsection_structure, course)
Exemplo n.º 11
0
 def get(self, request, course_id):
     """Implements the GET method as described in the class docstring."""
     course_key = CourseLocator.from_string(course_id)
     course = get_course_with_access(request.user, 'load_forum', course_key)
     if not any([isinstance(tab, DiscussionTab) for tab in course.tabs]):
         raise Http404
     return Response(get_course_topics(course, request.user))
Exemplo n.º 12
0
def vote_for_thread(request, course_id, thread_id, value):
    """
    given a course id and thread id vote for this thread
    ajax only
    """
    thread = cc.Thread.find(thread_id)
    result = _vote_or_unvote(request, course_id, thread, value)

    course_key = CourseLocator.from_string(course_id)

    # Feature Flag to check that notifications are enabled or not.
    if value == 'up' and settings.FEATURES.get("ENABLE_NOTIFICATIONS", False):
        action_user_id = request.user.id
        original_poster_id = int(thread.user_id)

        # refetch the thread to get updated count metrics
        thread = cc.Thread.find(thread_id)

        # we have to only send the notifications when
        # the user voting the thread is not
        # the same user who created the thread
        if action_user_id != original_poster_id:
            _send_discussion_notification(
                'open-edx.lms.discussions.post-upvoted',
                str(course_key),
                thread,
                request.user,
                recipient_user_id=original_poster_id,
                extra_payload={
                    'num_upvotes': thread.votes['up_count'],
                })

    return result
Exemplo n.º 13
0
def follow_thread(request, course_id, thread_id):
    course_key = CourseLocator.from_string(course_id)
    user = cc.User.from_django_user(request.user)
    thread = cc.Thread.find(thread_id)
    user.follow(thread)
    thread_followed.send(sender=None, user=request.user, post=thread)

    # Feature Flag to check that notifications are enabled or not.
    if settings.FEATURES.get("ENABLE_NOTIFICATIONS", False):
        # only send notifications when the user
        # who is following the thread is not the same
        # who created the thread
        action_user_id = request.user.id
        original_poster_id = int(thread.user_id)

        # get number of followers
        try:
            num_followers = thread.get_num_followers()

            if original_poster_id != action_user_id:
                _send_discussion_notification(
                    'open-edx.lms.discussions.thread-followed',
                    str(course_key),
                    thread,
                    request.user,
                    recipient_user_id=original_poster_id,
                    extra_payload={
                        'num_followers': num_followers,
                    })
        except Exception as ex:
            # sending notifications is not critical,
            # so log error and continue
            log.exception(ex)

    return JsonResponse({})
Exemplo n.º 14
0
    def handle(self, *args, **options):
        '''Handle management command request'''

        if len(args) != 3:
            raise CommandError('Usage is expire_all_blocks {0}'.format(
                self.args))

        try:
            end_date = pytz.timezone('Europe/Moscow').localize(
                parse_date(args[1], dayfirst=False,
                           yearfirst=True)).astimezone(pytz.utc)
        except:
            raise CommandError('Could not parse date "{0}"'.format(args[1]))
        try:
            user = User.objects.get(email=args[2])
        except:
            raise CommandError('Could not find user with email "{0}"'.format(
                args[2]))

        locator = CourseLocator.from_string(args[0])

        print "Altering course {0} blocks due date to {1}".format(
            locator, end_date)

        for block in modulestore().get_items(
                locator,
                qualifiers={'category': 'sequential'},
                revision=ModuleStoreEnum.RevisionOption.published_only):
            block.due = end_date
            modulestore().update_item(block, user.id)
Exemplo n.º 15
0
def vote_for_comment(request, course_id, comment_id, value):
    """
    Given a course_id and comment_id, vote for this response.  AJAX only.
    """
    comment = cc.Comment.find(comment_id)
    result = _vote_or_unvote(request, course_id, comment, value)
    comment_voted.send(sender=None, user=request.user, post=comment)

    course_key = CourseLocator.from_string(course_id)
    # Feature Flag to check that notifications are enabled or not.
    if value == 'up' and settings.FEATURES.get("ENABLE_NOTIFICATIONS", False):
        action_user_id = request.user.id
        original_poster_id = int(comment.user_id)

        thread = cc.Thread.find(comment.thread_id)

        # refetch the comment, so we have the updated counters

        comment = cc.Comment.find(comment_id)

        # we have to only send the notifications when
        # the user voting comment the comment is not
        # the same user who created the comment
        if action_user_id != original_poster_id:
            _send_discussion_notification(
                'open-edx.lms.discussions.comment-upvoted',
                str(course_key),
                thread,
                request.user,
                recipient_user_id=original_poster_id,
                extra_payload={
                    'num_upvotes': comment.votes['up_count'],
                })

    return result
Exemplo n.º 16
0
    def test_gitlog_pagination_out_of_range_invalid(self):
        """
        Make sure the pagination behaves properly when the requested page is out
        of range.
        """

        self._setstaff_login()

        mongoengine.connect(TEST_MONGODB_LOG['db'])

        for _ in xrange(15):
            CourseImportLog(
                course_id=CourseLocator.from_string("test/test/test"),
                location="location",
                import_log="import_log",
                git_log="git_log",
                repo_dir="repo_dir",
                created=datetime.now()
            ).save()

        for page, expected in [(-1, 1), (1, 1), (2, 2), (30, 2), ('abc', 1)]:
            response = self.client.get(
                '{}?page={}'.format(
                    reverse('gitlogs'),
                    page
                )
            )
            self.assertIn(
                u'Page {} of 2'.format(expected),
                response.content.decode(response.charset)
            )

        CourseImportLog.objects.delete()
Exemplo n.º 17
0
    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError('Must called with arguments: {}'.format(
                self.args))
        try:
            user = get_user_by_username_or_email(args[0])
        except:
            raise CommandError('No user exists [ {} ]'.format(args[0]))

        course_id = options['course_id']
        reactivate = options['reactivate']
        if course_id:
            try:
                course_id = CourseLocator.from_string(course_id)
            except InvalidKeyError:
                raise CommandError(
                    "'{}' is an invalid course_id".format(course_id))
            if not modulestore().get_course(course_id):
                raise CommandError("The specified course does not exist.")
            self.change_optout_state(user, course_id, reactivate)
        else:
            course_enrollments = CourseEnrollment.enrollments_for_user(user)
            for enrollment in course_enrollments:
                course_id = enrollment.course_id
                self.change_optout_state(user, course_id, reactivate)
Exemplo n.º 18
0
    def handle(self, *args, **options):
        create_report = options['create']
        delete_report = options['delete']

        if len(args) != 1:
            raise CommandError('"course_id" is not specified')
        elif create_report and delete_report:
            raise CommandError(
                'Cannot specify "-c" option and "-d" option at the same time.')

        course_id = args[0]
        try:
            course_id = CourseLocator.from_string(course_id)
        except InvalidKeyError:
            raise CommandError(
                "'{}' is an invalid course_id".format(course_id))
        if not modulestore().get_course(course_id):
            raise CommandError("The specified course does not exist.")

        if delete_report:
            try:
                delete_pgreport_csv(course_id)
            except NotFoundError:
                raise CommandError("CSV not found.")
            call_command('create_report_task', *['clear_cache'],
                         **{'course_id': course_id.to_deprecated_string()})
        elif create_report:
            call_command('create_report_task', *['create'],
                         **{'course_id': course_id.to_deprecated_string()})
        else:
            try:
                get_pgreport_csv(course_id)
            except NotFoundError:
                raise CommandError("CSV not found.")
Exemplo n.º 19
0
def recalculate_subsection_grade_v2(**kwargs):
    """
    Updates a saved subsection grade.

    Arguments:
        user_id (int): id of applicable User object
        course_id (string): identifying the course
        usage_id (string): identifying the course block
        only_if_higher (boolean): indicating whether grades should
            be updated only if the new raw_earned is higher than the
            previous value.
        expected_modified_time (serialized timestamp): indicates when the task
            was queued so that we can verify the underlying data update.
        score_deleted (boolean): indicating whether the grade change is
            a result of the problem's score being deleted.
        event_transaction_id(string): uuid identifying the current
            event transaction.
        event_transaction_type(string): human-readable type of the
            event at the root of the current event transaction.
    """
    try:
        course_key = CourseLocator.from_string(kwargs['course_id'])
        if not PersistentGradesEnabledFlag.feature_enabled(course_key):
            return

        score_deleted = kwargs['score_deleted']
        scored_block_usage_key = UsageKey.from_string(kwargs['usage_id']).replace(course_key=course_key)
        expected_modified_time = from_timestamp(kwargs['expected_modified_time'])

        # The request cache is not maintained on celery workers,
        # where this code runs. So we take the values from the
        # main request cache and store them in the local request
        # cache. This correlates model-level grading events with
        # higher-level ones.
        set_event_transaction_id(kwargs.pop('event_transaction_id', None))
        set_event_transaction_type(kwargs.pop('event_transaction_type', None))

        # Verify the database has been updated with the scores when the task was
        # created. This race condition occurs if the transaction in the task
        # creator's process hasn't committed before the task initiates in the worker
        # process.
        if not _has_database_updated_with_new_score(
                kwargs['user_id'], scored_block_usage_key, expected_modified_time, score_deleted,
        ):
            raise _retry_recalculate_subsection_grade(**kwargs)

        _update_subsection_grades(
            course_key,
            scored_block_usage_key,
            kwargs['only_if_higher'],
            kwargs['user_id'],
        )

    except Exception as exc:   # pylint: disable=broad-except
        if not isinstance(exc, KNOWN_RETRY_ERRORS):
            log.info("tnl-6244 grades unexpected failure: {}. kwargs={}".format(
                repr(exc),
                kwargs
            ))
        raise _retry_recalculate_subsection_grade(exc=exc, **kwargs)
Exemplo n.º 20
0
    def import_test_course(self, solution_attribute=None, solution_element=None):
        """
        Import the test course with the sga unit
        """
        # adapted from edx-platform/cms/djangoapps/contentstore/management/commands/tests/test_cleanup_assets.py
        root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
        input_dir = os.path.join(root, "test_data")

        temp_dir = tempfile.mkdtemp()
        self.addCleanup(lambda: shutil.rmtree(temp_dir))

        xml_dir = os.path.join(temp_dir, "xml")
        shutil.copytree(input_dir, xml_dir)

        with open(os.path.join(xml_dir, "2017_SGA", "vertical", "vertical.xml"), "w") as f:
            f.write(self.make_test_vertical(solution_attribute, solution_element))

        store = modulestore()
        import_course_from_xml(
            store,
            'sga_user',
            xml_dir,
        )

        return store.get_course(CourseLocator.from_string('SGAU/SGA101/course'))
Exemplo n.º 21
0
def auto_enroll_email(course_id, email, send_email=True):
    """
    Auto-enroll email in course.

    Based on lms.djangoapps.instructor.views.api.students_update_enrollment()
    """
    # Raises ValidationError if invalid
    validate_email(email)

    locator = CourseLocator.from_string(course_id)
    course = get_course_by_id(locator)

    # If we want to notify the newly enrolled student by email, fetch
    # the required parameters
    email_params = None
    language = None
    if send_email:
        email_params = get_email_params(course, True, secure=True)

        # Try to find out what language to send the email in.
        user = None
        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            pass
        else:
            language = get_user_email_language(user)

    # Enroll the email
    enroll_email(locator,
                 email,
                 auto_enroll=True,
                 email_students=send_email,
                 email_params=email_params,
                 language=language)
Exemplo n.º 22
0
    def initdb(self, default):
        """
        Initialize the database and create one test course in it
        """
        # set the default modulestore
        store_configs = self.options['stores']
        for index in range(len(store_configs)):
            if store_configs[index]['NAME'] == default:
                if index > 0:
                    store_configs[index], store_configs[0] = store_configs[0], store_configs[index]
                break
        self.store = MixedModuleStore(None, create_modulestore_instance=create_modulestore_instance, **self.options)
        self.addCleanup(self.store.close_all_connections)

        # convert to CourseKeys
        self.course_locations = {
            course_id: CourseLocator.from_string(course_id)
            for course_id in [self.MONGO_COURSEID, self.XML_COURSEID1, self.XML_COURSEID2]
        }
        # and then to the root UsageKey
        self.course_locations = {
            course_id: course_key.make_usage_key('course', course_key.run)
            for course_id, course_key in self.course_locations.iteritems()  # pylint: disable=maybe-no-member
        }
        if default == 'split':
            self.fake_location = CourseLocator(
                'foo', 'bar', 'slowly', branch=ModuleStoreEnum.BranchName.draft
            ).make_usage_key('vertical', 'baz')
        else:
            self.fake_location = Location('foo', 'bar', 'slowly', 'vertical', 'baz')
        self.xml_chapter_location = self.course_locations[self.XML_COURSEID1].replace(
            category='chapter', name='Overview'
        )
        self._create_course(default, self.course_locations[self.MONGO_COURSEID].course_key)
Exemplo n.º 23
0
 def clean_course_id(self):
     """Validate course_id"""
     value = self.cleaned_data["course_id"]
     try:
         return CourseLocator.from_string(value)
     except InvalidKeyError:
         raise ValidationError("'{}' is not a valid course id".format(value))
Exemplo n.º 24
0
def recalculate_subsection_grade_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the SCORE_CHANGED signal and trigger an update.
    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of SCORE_CHANGED):
       - points_possible: Maximum score available for the exercise
       - points_earned: Score obtained by the user
       - user: User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    try:
        course_id = kwargs.get('course_id', None)
        usage_id = kwargs.get('usage_id', None)
        student = kwargs.get('user', None)

        course_key = CourseLocator.from_string(course_id)
        if not PersistentGradesEnabledFlag.feature_enabled(course_key):
            return

        usage_key = UsageKey.from_string(usage_id).replace(course_key=course_key)

        from lms.djangoapps.grades.new.subsection_grade import SubsectionGradeFactory
        SubsectionGradeFactory(student).update(usage_key, course_key)
    except Exception as ex:  # pylint: disable=broad-except
        log.exception(
            u"Failed to process SCORE_CHANGED signal. "
            "user: %s, course_id: %s, "
            "usage_id: %s. Exception: %s", unicode(student), course_id, usage_id, ex.message
        )
Exemplo n.º 25
0
def recalculate_subsection_grade_handler(sender, **kwargs):  # pylint: disable=unused-argument
    """
    Consume the SCORE_CHANGED signal and trigger an update.
    This method expects that the kwargs dictionary will contain the following
    entries (See the definition of SCORE_CHANGED):
       - points_possible: Maximum score available for the exercise
       - points_earned: Score obtained by the user
       - user: User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    if not settings.FEATURES.get('ENABLE_SUBSECTION_GRADES_SAVED', False):
        return
    try:
        course_id = kwargs.get('course_id', None)
        usage_id = kwargs.get('usage_id', None)
        student = kwargs.get('user', None)

        course_key = CourseLocator.from_string(course_id)
        usage_key = UsageKey.from_string(usage_id).replace(
            course_key=course_key)

        from lms.djangoapps.grades.new.subsection_grade import SubsectionGradeFactory
        SubsectionGradeFactory(student).update(usage_key, course_key)
    except Exception as ex:  # pylint: disable=broad-except
        log.exception(
            u"Failed to process SCORE_CHANGED signal. "
            "user: %s, course_id: %s, "
            "usage_id: %s. Exception: %s", unicode(student), course_id,
            usage_id, ex.message)
Exemplo n.º 26
0
    def import_test_course(self, solution_attribute=None, solution_element=None):
        """
        Import the test course with the sga unit
        """
        # adapted from edx-platform/cms/djangoapps/contentstore/management/commands/tests/test_cleanup_assets.py
        root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
        input_dir = os.path.join(root, "test_data")

        temp_dir = tempfile.mkdtemp()
        self.addCleanup(lambda: shutil.rmtree(temp_dir))

        xml_dir = os.path.join(temp_dir, "xml")
        shutil.copytree(input_dir, xml_dir)

        with open(os.path.join(xml_dir, "2017_SGA", "vertical", "vertical.xml"), "w") as f:
            f.write(self.make_test_vertical(solution_attribute, solution_element))

        store = modulestore()
        import_course_from_xml(
            store,
            'sga_user',
            xml_dir,
        )

        return store.get_course(CourseLocator.from_string('SGAU/SGA101/course'))
Exemplo n.º 27
0
 def get(self, request, course_id):
     """Implements the GET method as described in the class docstring."""
     course_key = CourseLocator.from_string(course_id)
     course = get_course_with_access(request.user, 'load_forum', course_key)
     if not any([isinstance(tab, DiscussionTab) for tab in course.tabs]):
         raise Http404
     return Response(get_course_topics(course, request.user))
Exemplo n.º 28
0
 def from_string(cls, serialized):
     """Deprecated. Use :meth:`locator.CourseLocator.from_string`."""
     warnings.warn(
         "SlashSeparatedCourseKey is deprecated! Please use locator.CourseLocator",
         DeprecationWarning,
         stacklevel=2)
     return CourseLocator.from_string(serialized)
Exemplo n.º 29
0
 def clean_course_id(self):
     """Validate course_id"""
     value = self.cleaned_data["course_id"]
     try:
         return CourseLocator.from_string(value)
     except InvalidKeyError:
         raise ValidationError(u"'{}' is not a valid course id".format(value))
Exemplo n.º 30
0
def recalculate_subsection_grade(user_id, course_id, usage_id):
    """
    Updates a saved subsection grade.
    This method expects the following parameters:
       - user_id: serialized id of applicable User object
       - course_id: Unicode string representing the course
       - usage_id: Unicode string indicating the courseware instance
    """
    course_key = CourseLocator.from_string(course_id)
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

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

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

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

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

    subsections_to_update = collected_block_structure.get_transformer_block_field(
        scored_block_usage_key, GradesTransformer, 'subsections', set())
    subsection_grade_factory = SubsectionGradeFactory(
        student, course, collected_block_structure)
    for subsection_usage_key in subsections_to_update:
        transformed_subsection_structure = get_course_blocks(
            student,
            subsection_usage_key,
            collected_block_structure=collected_block_structure,
        )
        subsection_grade_factory.update(
            transformed_subsection_structure[subsection_usage_key],
            transformed_subsection_structure)
Exemplo n.º 32
0
 def clean_course_id(self):
     """Validate course_id"""
     value = self.cleaned_data["course_id"]
     try:
         return CourseLocator.from_string(value)
     except InvalidKeyError:
         raise ValidationError(f"'{value}' is not a valid course id")  # lint-amnesty, pylint: disable=raise-missing-from
Exemplo n.º 33
0
    def setUp(self):
        super(TestOrderCreation, self).setUp()
        # Set enforce_csrf_checks=True here because testing must still
        # work (webhooks are explicitly exempted from CSRF protection)
        self.client = Client(enforce_csrf_checks=True)

        conf = settings.WEBHOOK_SETTINGS['edx_shopify']

        # Calculate 3 SHA256 hashes over the payload, which the
        # webhook handler must verify and accept or reject: a correct
        # hash, a hash from the wrong (reversed) key, and a corrupted
        # hash containing an invalid base64 character.
        correct_hash = hmac.new(conf['api_key'], self.raw_payload,
                                hashlib.sha256)
        incorrect_hash = hmac.new(conf['api_key'][::-1], self.raw_payload,
                                  hashlib.sha256)
        self.correct_signature = base64.b64encode(correct_hash.digest())
        self.incorrect_signature = base64.b64encode(incorrect_hash.digest())
        self.corrupt_signature = "-%s" % base64.b64encode(
            correct_hash.digest())[1:]  # noqa: E501

        # Set up a mock course
        course_id_string = 'course-v1:org+course+run1'
        cl = CourseLocator.from_string(course_id_string)
        bul = BlockUsageLocator(cl, u'course', u'course')
        course = Mock()
        course.id = cl
        course.system = Mock()
        course.scope_ids = Mock()
        course.scope_id.user_id = None
        course.scope_ids.block_type = u'course'
        course.scope_ids.def_id = bul
        course.scope_ids.usage_id = bul
        course.location = bul
        course.display_name = u'Course - Run 1'

        self.course_id_string = course_id_string
        self.cl = cl
        self.course = course

        email_params = {
            'registration_url':
            u'https://localhost:8000/register',  # noqa: E501
            'course_about_url':
            u'https://localhost:8000/courses/course-v1:org+course+run1/about',  # noqa: E501
            'site_name':
            'localhost:8000',
            'course':
            course,
            'is_shib_course':
            None,
            'display_name':
            u'Course - Run 1',
            'auto_enroll':
            True,
            'course_url':
            u'https://localhost:8000/courses/course-v1:org+course+run1/'
        }  # noqa: E501
        self.email_params = email_params
Exemplo n.º 34
0
def recalculate_subsection_grade(user_id, course_id, usage_id, only_if_higher,
                                 raw_earned, raw_possible, **kwargs):
    """
    Updates a saved subsection grade.

    Arguments:
        user_id (int): id of applicable User object
        course_id (string): identifying the course
        usage_id (string): identifying the course block
        only_if_higher (boolean): indicating whether grades should
            be updated only if the new raw_earned is higher than the
            previous value.
        raw_earned (float): the raw points the learner earned on the
            problem that triggered the update.
        raw_possible (float): the max raw points the leaner could have
            earned on the problem.
        score_deleted (boolean): indicating whether the grade change is
            a result of the problem's score being deleted.
    """
    course_key = CourseLocator.from_string(course_id)
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

    score_deleted = kwargs['score_deleted']
    scored_block_usage_key = UsageKey.from_string(usage_id).replace(
        course_key=course_key)

    # Verify the database has been updated with the scores when the task was
    # created. This race condition occurs if the transaction in the task
    # creator's process hasn't committed before the task initiates in the worker
    # process.
    if not _has_database_updated_with_new_score(
            user_id,
            scored_block_usage_key,
            raw_earned,
            raw_possible,
            score_deleted,
    ):
        raise _retry_recalculate_subsection_grade(
            user_id,
            course_id,
            usage_id,
            only_if_higher,
            raw_earned,
            raw_possible,
            score_deleted,
        )

    _update_subsection_grades(
        course_key,
        scored_block_usage_key,
        only_if_higher,
        course_id,
        user_id,
        usage_id,
        raw_earned,
        raw_possible,
        score_deleted,
    )
Exemplo n.º 35
0
    def handle(self, *args, **options):
        if len(args) > 1:
            raise CommandError("check_draft requires one or no arguments: |<course_id>|")

        # Check args: course_id
        course_id = args[0] if len(args) > 0 else None
        if course_id:
            try:
                course_id = CourseLocator.from_string(course_id)
            except InvalidKeyError:
                raise CommandError("The course_id is not of the right format. It should be like 'org/course/run' or 'course-v1:org+course+run'")
            if not modulestore().get_course(course_id):
                raise CommandError("The specified course does not exist.")

        # Check options: active_only
        active_only = options['active_only']

        # Result
        output = PrettyTable(['Course ID', 'Course Name', 'Chapter Name', 'Sequential Name', 'Vertical Name', 'Draft?', 'Changed?'])
        output.align = 'l'

        # Find courses
        if course_id:
            course_items = modulestore().get_items(course_id, qualifiers={'category': 'course'})
            if not course_items:
                raise CommandError("The specified course does not exist.")
        else:
            course_items = modulestore().get_courses()

        for course_item in course_items:
            # Note: Use only active courses
            if active_only and course_item.has_ended():
                continue
            # Find chapter items
            chapter_items = [modulestore().get_item(item.location) for item in course_item.get_children()]
            chapter_items = sorted(chapter_items, key=lambda item: item.start)
            for chapter_item in chapter_items:
                # Find sequential items
                sequential_items = [modulestore().get_item(item.location) for item in chapter_item.get_children()]
                sequential_items = sorted(sequential_items, key=lambda item: item.start)
                for sequential_item in sequential_items:
                    #print "sequential_item.location=%s" % sequential_item.location
                    #print "sequential_item.published?=%s" % modulestore().has_item(sequential_item.location, revision=ModuleStoreEnum.RevisionOption.published_only)
                    #print "sequential_item.changed?=%s" % modulestore().has_changes(sequential_item)
                    # Find vertical items
                    vertical_items = [modulestore().get_item(item.location) for item in sequential_item.get_children()]
                    vertical_items = sorted(vertical_items, key=lambda item: item.start)
                    for vertical_item in vertical_items:
                        #print "vertical_item.location=%s" % vertical_item.location
                        #print "vertical_item.published?=%s" % modulestore().has_item(vertical_item.location, revision=ModuleStoreEnum.RevisionOption.published_only)
                        #print "vertical_item.changed?=%s" % modulestore().has_changes(vertical_item)
                        is_draft = vertical_item.is_draft
                        # Note: cribbed from cms/djangoapps/contentstore/views/tests/test_item.py
                        has_changes = modulestore().has_changes(vertical_item)
                        if is_draft or has_changes:
                            output.add_row([course_item.id, course_item.display_name, chapter_item.display_name, sequential_item.display_name, vertical_item.display_name, is_draft, has_changes])

        # Print result
        print output
Exemplo n.º 36
0
 def setUp(self):
     self.user = "******"
     self.course_id = CourseLocator.from_string("org/num/run")
     self.debug = False
     self.noop = False
     self.file_prefix = ""
     self.exclude = None
     self.seed = "seed"
Exemplo n.º 37
0
def get_course_block_counts(course):
    course_key = CourseLocator.from_string(course)
    try:
        structure_id = API.get_structure_by_key(course_key)
    except API.CourseNotFound:
        abort(404)
    counts = API.get_block_counts(structure_id)
    return Response(dumps(counts), mimetype='application/json')
Exemplo n.º 38
0
 def from_string(cls, serialized):
     """Deprecated. Use :meth:`locator.CourseLocator.from_string`."""
     warnings.warn(
         "SlashSeparatedCourseKey is deprecated! Please use locator.CourseLocator",
         DeprecationWarning,
         stacklevel=2
     )
     return CourseLocator.from_string(serialized)
Exemplo n.º 39
0
    def test_handle_publish(self, check_mock):
        cc.Command().handle(*self.args_publish, **self.kwargs)

        check_mock.assert_called_with(self.args_publish[1])
        self.cert_mock.assert_called_with(
            self.kwargs['username'], CourseLocator.from_string(self.args_publish[1]), self.kwargs['debug'],
            self.kwargs['noop'], self.kwargs['prefix'], self.kwargs['exclude'])
        self.cert_mock().publish.assert_called_with()
Exemplo n.º 40
0
 def test_basic(self):
     form = self.get_form(expected_valid=True)
     self.assertEqual(
         form.cleaned_data, {
             "course_id": CourseLocator.from_string("Foo/Bar/Baz"),
             "page": 2,
             "page_size": 13,
         })
Exemplo n.º 41
0
 def setUp(self):
     self.user = "******"
     self.course_id = CourseLocator.from_string("org/num/run")
     self.debug = False
     self.noop = False
     self.file_prefix = ""
     self.exclude = None
     self.seed = "seed"
Exemplo n.º 42
0
 def test_basic(self):
     form = self.get_form(expected_valid=True)
     assert form.cleaned_data == {
         'course_id': CourseLocator.from_string('a/b/c'),
         'flagged': False,
         'page': 2,
         'page_size': 13,
         'requested_fields': set()
     }
Exemplo n.º 43
0
class SurveySubmissionFactory(DjangoModelFactory):
    FACTORY_FOR = SurveySubmission

    course_id = CourseLocator.from_string('edX/test/course1')
    unit_id = '11111111111111111111111111111111'
    user = factory.SubFactory(UserFactory)
    survey_name = 'survey #1'
    survey_answer = '{"Q1": "1", "Q2": ["2", "3"], "Q3": "test"}'
    created = datetime(2014, 4, 1, tzinfo=UTC)
Exemplo n.º 44
0
def recalculate_subsection_grade(user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible):
    """
    Updates a saved subsection grade.
    This method expects the following parameters:
        - user_id: serialized id of applicable User object
        - course_id: Unicode string identifying the course
        - usage_id: Unicode string identifying the course block
        - only_if_higher: boolean indicating whether grades should
        be updated only if the new raw_earned is higher than the previous
        value.
        - raw_earned: the raw points the learner earned on the problem that
        triggered the update
        - raw_possible: the max raw points the leaner could have earned
        on the problem
    """
    course_key = CourseLocator.from_string(course_id)
    if not PersistentGradesEnabledFlag.feature_enabled(course_key):
        return

    scored_block_usage_key = UsageKey.from_string(usage_id).replace(course_key=course_key)
    score = get_score(user_id, scored_block_usage_key)

    # If the score is None, it has not been saved at all yet
    # and we need to retry until it has been saved.
    if score is None:
        raise _retry_recalculate_subsection_grade(
            user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible,
        )
    else:
        module_raw_earned, module_raw_possible = score  # pylint: disable=unpacking-non-sequence

    # Validate that the retrieved scores match the scores when the task was created.
    # This race condition occurs if the transaction in the task creator's process hasn't
    # committed before the task initiates in the worker process.
    grades_match = module_raw_earned == raw_earned and module_raw_possible == raw_possible

    # We have to account for the situation where a student's state
    # has been deleted- in this case, get_score returns (None, None), but
    # the score change signal will contain 0 for raw_earned.
    state_deleted = module_raw_earned is None and module_raw_possible is None and raw_earned == 0

    if not (state_deleted or grades_match):
        raise _retry_recalculate_subsection_grade(
            user_id, course_id, usage_id, only_if_higher, raw_earned, raw_possible,
        )

    _update_subsection_grades(
        course_key,
        scored_block_usage_key,
        only_if_higher,
        course_id,
        user_id,
        usage_id,
        raw_earned,
        raw_possible,
    )
Exemplo n.º 45
0
    def setUp(self):
        self.fp = StringIO.StringIO()
        self.username = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.course_name = "testcoursename"
        self.file_prefix = "prefix-"

        patcher0 = patch('pdfgen.views.logging')
        self.log_mock = patcher0.start()
        self.addCleanup(patcher0.stop)
Exemplo n.º 46
0
    def setUp(self):
        self.username = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.filepath = "/file/is/not/exists"
        self.bucket_name = settings.PDFGEN_BUCKET_NAME
        self.location = Location.APNortheast

        patcher0 = patch('pdfgen.views.logging')
        self.log = patcher0.start()
        self.addCleanup(patcher0.stop)
Exemplo n.º 47
0
    def setUp(self):
        self.fp = StringIO.StringIO()
        self.username = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.course_name = "testcoursename"
        self.file_prefix = "prefix-"

        patcher0 = patch('pdfgen.views.logging')
        self.log_mock = patcher0.start()
        self.addCleanup(patcher0.stop)
Exemplo n.º 48
0
    def setUp(self):
        self.user = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.debug = False
        self.noop = False
        self.file_prefix = ""
        self.exclude = None

        self.mail = "*****@*****.**"
        self.students = UserFactory.create_batch(3)
Exemplo n.º 49
0
    def setUp(self):
        self.user = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.debug = False
        self.noop = False
        self.file_prefix = ""
        self.exclude = None

        self.student = UserFactory.create()
        self.cert = MagicMock()
Exemplo n.º 50
0
    def setUp(self):
        self.username = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.filepath = "/file/is/not/exists"
        self.bucket_name = settings.PDFGEN_BUCKET_NAME
        self.location = Location.APNortheast

        patcher0 = patch('pdfgen.views.logging')
        self.log = patcher0.start()
        self.addCleanup(patcher0.stop)
Exemplo n.º 51
0
 def test_basic(self):
     form = self.get_form(expected_valid=True)
     self.assertEqual(
         form.cleaned_data,
         {
             "course_id": CourseLocator.from_string("Foo/Bar/Baz"),
             "page": 2,
             "page_size": 13,
         }
     )
Exemplo n.º 52
0
    def setUp(self):
        self.user = "******"
        self.course_id = CourseLocator.from_string("org/num/run")
        self.debug = False
        self.noop = False
        self.file_prefix = ""
        self.exclude = None

        self.student = UserFactory.create()
        self.cert = GeneratedCertificateFactory.build(
            user=self.student, status="downloadable")
Exemplo n.º 53
0
    def test_certificate_for_missing_course(self):
        """
        Verify that a certificate is not shown for a missing course.
        """
        # Add new certificate
        cert = self._create_certificate(course_key=CourseLocator.from_string('course-v1:edX+INVALID+1'))
        cert.save()

        response = self.client.get('/u/{username}'.format(username=self.user.username))

        self.assertNotContains(response, 'card certificate-card mode-{cert_mode}'.format(cert_mode=cert.mode))
Exemplo n.º 54
0
def get_university_id(user, course_id):
    if isinstance(user, AnonymousUser):
        return None

    try:
        return UniversityID.objects.get(
            user=user,
            course_key=CourseLocator.from_string(course_id),
        )
    except UniversityID.DoesNotExist:
        return None
Exemplo n.º 55
0
 def test_basic(self):
     self.form_data.setlist("topic_id", ["example topic_id", "example 2nd topic_id"])
     form = self.get_form(expected_valid=True)
     self.assertEqual(
         form.cleaned_data,
         {
             "course_id": CourseLocator.from_string("Foo/Bar/Baz"),
             "page": 2,
             "page_size": 13,
             "topic_id": ["example topic_id", "example 2nd topic_id"],
         }
     )
Exemplo n.º 56
0
 def test_basic(self):
     form = self.get_form(expected_valid=True)
     self.assertEqual(
         form.cleaned_data,
         {
             "course_id": CourseLocator.from_string("Foo/Bar/Baz"),
             "page": 2,
             "page_size": 13,
             "topic_id": [],
             "text_search": "",
             "following": None,
         }
     )
Exemplo n.º 57
0
    def test_handle(self, store_mock, get_mock, del_mock, call_mock):
        pr.Command().handle(*self.args, **self.options_get)
        get_mock.assert_called_once_with(*[CourseLocator.from_string(arg) for arg in self.args])

        pr.Command().handle(*self.args, **self.options_create)
        call_mock.assert_called_once_with(
            'create_report_task', *['create'], **{'course_id': self.args[0]})

        pr.Command().handle(*self.args, **self.options_delete)
        del_mock.assert_called_once_with(*[CourseLocator.from_string(arg) for arg in self.args])

        msg = '^"course_id" is not specified$'
        with self.assertRaisesRegexp(CommandError, msg):
            pr.Command().handle(*[], **self.options_get)

        msg = '^Cannot specify "-c" option and "-d" option at the same time.$'
        with self.assertRaisesRegexp(CommandError, msg):
            pr.Command().handle(*self.args, **self.options_error)

        msg = '^CSV not found.$'
        with self.assertRaisesRegexp(CommandError, msg):
            get_mock.side_effect = NotFoundError()
            pr.Command().handle(*self.args, **self.options_get)