def get(self):
        """Get activity scores."""
        request = transforms.loads(self.request.get('request'))
        payload = transforms.loads(request.get('payload'))
        errors = []

        students = payload['students']
        force_refresh = payload['forceRefresh']
        course = self.get_course()

        temp_students = []
        for student in students:
            if '@' in student:
                temp_students.append(Student.get_by_email(student).user_id)

        if len(temp_students) > 0:
            students = temp_students

        if len(students) > 0:
            scores = teacher_parsers.ActivityScoreParser.get_activity_scores(students, course, force_refresh)
        else:
            errors.append('An error occurred retrieving activity scores. Contact your course administrator.')
            self.validation_error('\n'.join(errors))
            return

        payload_dict = {
            'scores': scores['scores'],
            'dateCached': scores['date'].strftime("%B %d, %Y %H:%M:%S")
        }

        transforms.send_json_response(
            self, 200, '', payload_dict=payload_dict,
            xsrf_token=crypto.XsrfTokenManager.create_xsrf_token(
                self.XSRF_TOKEN))
    def put(self):
        """Handles REST PUT verb with JSON payload."""
        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(self, 401, "Access denied.", {})
            return

        request = transforms.loads(self.request.get("request"))
        payload = request.get("payload")
        course_raw = transforms.json_to_dict(transforms.loads(payload), self.SCHEMA_DICT)["course"]

        source = None
        for acourse in sites.get_all_courses():
            if acourse.raw == course_raw:
                source = acourse
                break

        if not source:
            transforms.send_json_response(self, 404, "Object not found.", {"raw": course_raw})
            return

        course = courses.Course(self)
        errors = []
        try:
            course.import_from(source, errors)
        except Exception as e:  # pylint: disable-msg=broad-except
            logging.exception(e)
            errors.append("Import failed: %s" % e)

        if errors:
            transforms.send_json_response(self, 412, "\n".join(errors))
            return

        course.save()
        transforms.send_json_response(self, 200, "Imported.")
 def _verify_error(self, response, expected_message, expected_status=None):
     self.assertEquals(200, response.status_int)
     content = transforms.loads(response.body)
     self.assertEquals(expected_message, content['message'])
     self.assertEquals(expected_status or 403, content['status'])
     payload = transforms.loads(content['payload'])
     self.assertItemsEqual([], payload['labels'])
 def _verify_labels(self, response, expected_labels):
     self.assertEquals(200, response.status_int)
     content = transforms.loads(response.body)
     self.assertEquals('OK', content['message'])
     self.assertEquals(200, content['status'])
     payload = transforms.loads(content['payload'])
     self.assertItemsEqual(expected_labels, payload['labels'])
 def test_json_trailing_comma_in_array_fails(self):
     json_text = '{"foo": ["bar",]}'
     try:
         transforms.loads(json_text)
         raise Exception('Expected to fail')
     except ValueError:
         pass
Exemple #6
0
    def put(self):
        """Handles the put verb."""
        assert self.app_context.is_editable_fs()
        request = self.request.get('request')
        assert request
        request = transforms.loads(request)
        payload = transforms.loads(request.get('payload'))
        filename = request.get('key')

        if not (filename and self.assert_xsrf_token_or_fail(
                request, self.XSRF_TOKEN_NAME, {'key': filename})):
            return

        if not FilesRights.can_edit(self):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': filename})
            return

        if not _is_asset_in_allowed_bases(filename):
            transforms.send_json_response(
                self, 400, 'Malformed request.', {'key': filename})
            return

        self.app_context.fs.impl.put(
            os.path.join(appengine_config.BUNDLE_ROOT, filename),
            vfs.string_to_stream(unicode(payload.get('contents'))))
        transforms.send_json_response(self, 200, 'Saved.')
Exemple #7
0
    def post(self):
        # Receive a payload of the form:
        #     {
        #       "location": url_for_rest_handler,
        #       "key": the key of the object being edited,
        #       "state": object_holding_editor_state
        #     }

        request = transforms.loads(self.request.get('request'))
        if not self.assert_xsrf_token_or_fail(request, self.XSRF_TOKEN, {}):
            return

        user = self.get_user()
        if user is None:
            self.error(401)
            return

        if not roles.Roles.is_course_admin(self.app_context):
            transforms.send_json_response(self, 401, 'Access denied.', {})
            return

        payload = transforms.loads(request.get('payload'))

        key_name = EditorPrefsDao.create_key_name(
            user.user_id(), payload['location'], payload['key'])
        editor_prefs = EditorPrefsDto(key_name, payload['state'])
        EditorPrefsDao.save(editor_prefs)

        transforms.send_json_response(self, 200, 'Saved.', payload_dict={})
def record_event_listener(source, user, data):
    # Note the code in this method has similiarities to methods in
    # models.event_transforms, but is (a) more limited in scope, and (b) needs
    # less background information marshalled about the structure of the course

    if source == 'tag-assessment':
        # Sent when the "Check Answer" button is presson in a lesson
        data = transforms.loads(data)
        question_scores = _get_questions_scores_from_single_item(data)

    elif source == 'attempt-lesson':
        # Sent when the "Grade Questions" button is pressed in a lesson
        # or when the "Check Answers" button is pressed in an assessment
        data = transforms.loads(data)
        question_scores = _get_questions_scores_from_many_items(data)

    elif source == 'submit-assessment':
        # Sent when an assignment is submitted.
        data = transforms.loads(data)['values']
        question_scores = _get_questions_scores_from_many_items(data)

    else:
        return

    scores_by_skill = collections.defaultdict(list)
    for question_score in question_scores:
        question = models.QuestionDAO.load(question_score.quid)
        for skill_id in question.dict.get(constants.SKILLS_KEY, []):
            scores_by_skill[skill_id].append(question_score.score)

    for skill_id, scores in scores_by_skill.iteritems():
        updater = Registry.get_updater(user.user_id(), skill_id)
        for score in scores:
            updater.update(score)
        updater.save()
 def test_get_defaulted_hook_content(self):
     url = "%s?key=%s" % (ADMIN_SETTINGS_URL, cgi.escape("base.after_body_tag_begins"))
     response = transforms.loads(self.get(url).body)
     self.assertEquals(200, response["status"])
     self.assertEquals("Success.", response["message"])
     payload = transforms.loads(response["payload"])
     self.assertEquals("<!-- base.after_body_tag_begins -->", payload["hook_content"])
    def put(self):
        """A PUT REST method shared by all unit types."""
        request = transforms.loads(self.request.get("request"))
        key = request.get("key")

        if not self.assert_xsrf_token_or_fail(request, "put-unit", {"key": key}):
            return

        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(self, 401, "Access denied.", {"key": key})
            return

        unit = courses.Course(self).find_unit_by_id(key)
        if not unit:
            transforms.send_json_response(self, 404, "Object not found.", {"key": key})
            return

        payload = request.get("payload")
        updated_unit_dict = transforms.json_to_dict(transforms.loads(payload), self.SCHEMA_DICT)

        errors = []
        self.apply_updates(unit, updated_unit_dict, errors)
        if not errors:
            course = courses.Course(self)
            assert course.update_unit(unit)
            course.save()
            transforms.send_json_response(self, 200, "Saved.")
        else:
            transforms.send_json_response(self, 412, "\n".join(errors))
    def test_non_admin_permissions_failures(self):
        actions.login(STUDENT_EMAIL)
        student_xsrf_token = crypto.XsrfTokenManager.create_xsrf_token(
            settings.HtmlHookRESTHandler.XSRF_ACTION)

        response = self.get(ADMIN_SETTINGS_URL)
        self.assertEquals(200, response.status_int)
        payload = transforms.loads(response.body)
        self.assertEquals(401, payload['status'])
        self.assertEquals('Access denied.', payload['message'])

        response = self.put(ADMIN_SETTINGS_URL, {'request': transforms.dumps({
                'key': 'base:after_body_tag_begins',
                'xsrf_token': cgi.escape(student_xsrf_token),
                'payload': '{}'})})
        payload = transforms.loads(response.body)
        self.assertEquals(401, payload['status'])
        self.assertEquals('Access denied.', payload['message'])

        response = self.delete(ADMIN_SETTINGS_URL + '?xsrf_token=' +
                               cgi.escape(student_xsrf_token))
        self.assertEquals(200, response.status_int)
        payload = transforms.loads(response.body)
        self.assertEquals(401, payload['status'])
        self.assertEquals('Access denied.', payload['message'])
    def put(self):
        """Handles PUT REST verb to save lesson and associated activity."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(
                request, 'lesson-edit', {'key': key}):
            return

        if not roles.Roles.is_course_admin(self.app_context):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': key})
            return

        course = courses.Course(self)
        lesson = course.find_lesson_by_id(None, key)
        if not lesson:
            transforms.send_json_response(
                self, 404, 'Object not found.', {'key': key})
            return

        payload = request.get('payload')
        updates_dict = transforms.json_to_dict(
            transforms.loads(payload),
            self.get_schema(course, key).get_json_schema_dict())

        lesson.title = updates_dict['title']
        lesson.unit_id = updates_dict['unit_id']
        lesson.scored = (updates_dict['scored'] == 'scored')
        lesson.objectives = updates_dict['objectives']
        lesson.video = updates_dict['video']
        lesson.notes = updates_dict['notes']
        lesson.auto_index = updates_dict['auto_index']
        lesson.activity_title = updates_dict['activity_title']
        lesson.activity_listed = updates_dict['activity_listed']
        lesson.manual_progress = updates_dict['manual_progress']

        activity = updates_dict.get('activity', '').strip()
        errors = []
        if activity:
            if lesson.has_activity:
                course.set_activity_content(lesson, activity, errors=errors)
            else:
                errors.append('Old-style activities are not supported.')
        else:
            lesson.has_activity = False
            fs = self.app_context.fs
            path = fs.impl.physical_to_logical(course.get_activity_filename(
                lesson.unit_id, lesson.lesson_id))
            if fs.isfile(path):
                fs.delete(path)

        if not errors:
            common_utils.run_hooks(self.PRE_SAVE_HOOKS, lesson, updates_dict)
            assert course.update_lesson(lesson)
            course.save()
            common_utils.run_hooks(self.POST_SAVE_HOOKS, lesson)
            transforms.send_json_response(self, 200, 'Saved.')
        else:
            transforms.send_json_response(self, 412, '\n'.join(errors))
    def test_filtered_read(self):
        email = '*****@*****.**'
        actions.login(email, is_admin=True)

        # Single greater-equal filter
        response = transforms.loads(self.get(
            '/rest/data/character/items?filter=rank>=7').body)
        self.assertEquals(3, len(response['data']))
        for character in response['data']:
            self.assertTrue(character['rank'] >= 7)

        # Single less-than filter
        response = transforms.loads(self.get(
            '/rest/data/character/items?filter=rank<7').body)
        self.assertEquals(7, len(response['data']))
        for character in response['data']:
            self.assertTrue(character['rank'] < 7)

        # Multiple filters finding some rows
        response = transforms.loads(self.get(
            '/rest/data/character/items?filter=rank<5&filter=goal=L').body)
        self.assertEquals(2, len(response['data']))
        for character in response['data']:
            self.assertTrue(character['rank'] < 5)
            self.assertTrue(character['goal'] == 'L')
    def put(self):
        """A PUT REST method shared by all unit types."""
        request = transforms.loads(self.request.get("request"))
        key = request.get("key")

        if not self.assert_xsrf_token_or_fail(request, "put-unit", {"key": key}):
            return

        if not self.can_edit(self.app_context):
            transforms.send_json_response(self, 401, "Access denied.", {"key": key})
            return

        unit = courses.Course(self).find_unit_by_id(key)
        if not unit:
            transforms.send_json_response(self, 404, "Object not found.", {"key": key})
            return

        payload = request.get("payload")
        errors = []

        course = courses.Course(self)
        try:
            schema = self.get_schema(course, key)
            updated_unit_dict = transforms.json_to_dict(transforms.loads(payload), schema.get_json_schema_dict())
            schema.redact_entity_to_schema(updated_unit_dict)
            self.apply_updates(unit, updated_unit_dict, errors)
        except (TypeError, ValueError), ex:
            errors.append(str(ex))
    def test_nonsequential_pagination(self):
        email = '*****@*****.**'
        actions.login(email, is_admin=True)

        response = transforms.loads(self.get(
            '/rest/data/character/items?chunk_size=3&page_number=2').body)
        source_context = response['source_context']
        self.assertEquals(2, response['page_number'])
        self._verify_data(self.characters[6:9], response['data'])
        self._assert_have_only_logs(response, [
            'Creating new context for given parameters',
            'fetch page 0 start cursor missing; end cursor missing',
            'fetch page 0 using limit 3',
            'fetch page 0 saving end cursor',
            'fetch page 1 start cursor present; end cursor missing',
            'fetch page 1 using limit 3',
            'fetch page 1 saving end cursor',
            'fetch page 2 start cursor present; end cursor missing',
            'fetch page 2 using limit 3',
            'fetch page 2 saving end cursor',
            ])

        response = transforms.loads(self.get(
            '/rest/data/character/items?chunk_size=3&page_number=1'
            '&source_context=%s' % source_context).body)
        source_context = response['source_context']
        self._verify_data(self.characters[3:6], response['data'])
        self._assert_have_only_logs(response, [
            'Existing context matches parameters; using existing context',
            'fetch page 1 start cursor present; end cursor present',
            ])
    def test_pii_encoding(self):
        email = '*****@*****.**'
        actions.login(email, is_admin=True)
        token = data_sources_utils.generate_data_source_token(
            crypto.XsrfTokenManager)

        response = transforms.loads(self.get('/rest/data/character/items').body)
        for d in response['data']:
            # Ensure that field marked as needing transformation is cleared
            # when we don't pass in an XSRF token used for generating a secret
            # for encrypting.
            self.assertEquals('None', d['user_id'])
            self.assertEquals(str(db.Key.from_path(Character.kind(), 'None')),
                              d['key'])

            # Ensure that field marked for blacklist is suppressed.
            self.assertFalse('name' in d)

        response = transforms.loads(self.get(
            '/rest/data/character/items?data_source_token=' + token).body)

        for d in response['data']:
            # Ensure that field marked as needing transformation is cleared
            # when we don't pass in an XSRF token used for generating a secret
            # for encrypting.
            self.assertIsNotNone(d['user_id'])
            self.assertNotEquals('None', d['key'])

            # Ensure that field marked for blacklist is still suppressed.
            self.assertFalse('name' in d)
    def test_parameters_can_be_omitted_if_using_source_context(self):
        email = '*****@*****.**'
        actions.login(email, is_admin=True)

        response = transforms.loads(self.get(
            '/rest/data/character/items?filter=rank>=5&ordering=rank'
            '&chunk_size=3&page_number=1').body)
        source_context = response['source_context']
        self._verify_data([self.characters[4], self.characters[6]],
                          response['data'])

        # This should load identical items, without having to respecify
        # filters, ordering, chunk_size.
        response = transforms.loads(self.get(
            '/rest/data/character/items?page_number=1'
            '&source_context=%s' % source_context).body)
        self.assertEquals(1, response['page_number'])
        self._verify_data([self.characters[4], self.characters[6]],
                          response['data'])
        self._assert_have_only_logs(response, [
            'Continuing use of existing context',
            'fetch page 1 start cursor present; end cursor missing',
            'fetch page 1 using limit 3',
            'fetch page 1 is partial; not saving end cursor',
            ])
    def test_pagination_filtering_and_ordering(self):
        email = '*****@*****.**'
        actions.login(email, is_admin=True)

        response = transforms.loads(self.get(
            '/rest/data/character/items?filter=rank>=5&ordering=rank'
            '&chunk_size=3&page_number=1').body)
        source_context = response['source_context']
        self.assertEquals(1, response['page_number'])
        self._verify_data([self.characters[4], self.characters[6]],
                          response['data'])
        self._assert_have_only_logs(response, [
            'Creating new context for given parameters',
            'fetch page 0 start cursor missing; end cursor missing',
            'fetch page 0 using limit 3',
            'fetch page 0 saving end cursor',
            'fetch page 1 start cursor present; end cursor missing',
            'fetch page 1 using limit 3',
            'fetch page 1 is partial; not saving end cursor',
            ])

        response = transforms.loads(self.get(
            '/rest/data/character/items?filter=rank>=5&ordering=rank'
            '&chunk_size=3&page_number=0'
            '&source_context=%s' % source_context).body)
        source_context = response['source_context']
        self.assertEquals(0, response['page_number'])
        self._verify_data([self.characters[7], self.characters[1],
                           self.characters[8]], response['data'])
        self._assert_have_only_logs(response, [
            'Existing context matches parameters; using existing context',
            'fetch page 0 start cursor missing; end cursor present',
            ])
    def put(self):
        """A PUT REST method shared by all unit types."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(
                request, 'put-unit', {'key': key}):
            return

        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': key})
            return

        unit = courses.Course(self).find_unit_by_id(key)
        if not unit:
            transforms.send_json_response(
                self, 404, 'Object not found.', {'key': key})
            return

        payload = request.get('payload')
        errors = []

        try:
            updated_unit_dict = transforms.json_to_dict(
                transforms.loads(payload), self.SCHEMA_DICT)
            self.apply_updates(unit, updated_unit_dict, errors)
        except (TypeError, ValueError), ex:
            errors.append(str(ex))
    def test_oeditor_returns_state(self):
        actions.login('*****@*****.**', is_admin=True)
        xsrf_token = crypto.XsrfTokenManager.create_xsrf_token(
                oeditor.EditorPrefsRestHandler.XSRF_TOKEN)

        self._post(xsrf_token=xsrf_token)
        response = self.get('dashboard?action=edit_lesson&key=%s' % (
            self.lesson.lesson_id))

        expected = {
          'xsrf_token': xsrf_token,
          'location': self.location,
          'key': str(self.key),
          'prefs': self.EDITOR_STATE
        }
        expected = transforms.loads(transforms.dumps(expected))

        match = re.search(
            r'cb_global.editor_prefs = JSON.parse\((.*)\);', response.body)

        actual = match.group(1)
        actual = transforms.loads('"%s"' % actual)
        actual = transforms.loads(actual[1:-1])

        self.assertEquals(expected, actual)
    def put(self):
        """Store a question in the datastore in response to a PUT."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(
                request, self.XSRF_TOKEN, {'key': key}):
            return

        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': key})
            return

        payload = request.get('payload')
        question_dict = transforms.loads(payload)
        question_dict['description'] = question_dict['description'].strip()

        question_dict, errors = self.import_and_validate(question_dict, key)

        if errors:
            self.validation_error('\n'.join(errors), key=key)
            return

        if key:
            question = QuestionDTO(key, question_dict)
        else:
            question = QuestionDTO(None, question_dict)

        question.type = self.TYPE
        key_after_save = QuestionDAO.save(question)

        transforms.send_json_response(
            self, 200, 'Saved.', payload_dict={'key': key_after_save})
    def put(self):
        """A PUT REST method shared by all unit types."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(
                request, 'put-unit', {'key': key}):
            return

        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': key})
            return

        unit = courses.Course(self).find_unit_by_id(key)
        if not unit:
            transforms.send_json_response(
                self, 404, 'Object not found.', {'key': key})
            return

        payload = request.get('payload')
        updated_unit_dict = transforms.json_to_dict(
            transforms.loads(payload), self.SCHEMA_DICT)

        errors = []
        self.apply_updates(unit, updated_unit_dict, errors)
        if not errors:
            course = courses.Course(self)
            assert course.update_unit(unit)
            course.save()
            common_utils.run_hooks(self.POST_SAVE_HOOKS, unit)
            transforms.send_json_response(self, 200, 'Saved.')
        else:
            transforms.send_json_response(self, 412, '\n'.join(errors))
    def test_stable_ids(self):
        self._course.add_assessment()
        unit2 = self._course.add_unit()
        self._course.add_assessment()
        self._course.add_unit()
        self._course.add_assessment()
        self._course.add_unit()
        self._course.add_assessment()
        self._course.add_unit()
        self._course.add_assessment()
        self._course.add_unit()
        self._course.add_assessment()
        self._course.add_assessment()
        self._course.add_assessment()
        self._course.add_unit()
        self._course.save()

        response = transforms.loads(self.get(
            '/test/rest/data/units/items').body)
        self.assertListEqual(['2', '4', '6', '8', '10', '14'],
                             [u['unit_id'] for u in response['data']])

        self._course.delete_unit(unit2)
        self._course.save()

        response = transforms.loads(self.get(
            '/test/rest/data/units/items').body)
        self.assertListEqual(['4', '6', '8', '10', '14'],
                             [u['unit_id'] for u in response['data']])
    def put(self):
        """Store a question group in the datastore in response to a PUT."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(
                request, self.XSRF_TOKEN, {'key': key}):
            return

        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': key})
            return

        payload = request.get('payload')
        question_group_dict = transforms.json_to_dict(
            transforms.loads(payload),
            self.get_schema().get_json_schema_dict())

        validation_errors = self.validate(question_group_dict, key)
        if validation_errors:
            self.validation_error('\n'.join(validation_errors), key=key)
            return

        assert self.SCHEMA_VERSION == question_group_dict.get('version')

        if key:
            question_group = QuestionGroupDTO(key, question_group_dict)
        else:
            question_group = QuestionGroupDTO(None, question_group_dict)

        key_after_save = QuestionGroupDAO.save(question_group)
        transforms.send_json_response(
            self, 200, 'Saved.', payload_dict={'key': key_after_save})
    def test_non_admin_permissions_failures(self):
        actions.login(STUDENT_EMAIL)
        student_xsrf_token = crypto.XsrfTokenManager.create_xsrf_token(settings.HtmlHookRESTHandler.XSRF_ACTION)

        response = self.get(ADMIN_SETTINGS_URL)
        self.assertEquals(200, response.status_int)
        payload = transforms.loads(response.body)
        self.assertEquals(401, payload["status"])
        self.assertEquals("Access denied.", payload["message"])

        response = self.put(
            ADMIN_SETTINGS_URL,
            {
                "request": transforms.dumps(
                    {"key": "base:after_body_tag_begins", "xsrf_token": cgi.escape(student_xsrf_token), "payload": "{}"}
                )
            },
        )
        payload = transforms.loads(response.body)
        self.assertEquals(401, payload["status"])
        self.assertEquals("Access denied.", payload["message"])

        response = self.delete(ADMIN_SETTINGS_URL + "?xsrf_token=" + cgi.escape(student_xsrf_token))
        self.assertEquals(200, response.status_int)
        payload = transforms.loads(response.body)
        self.assertEquals(401, payload["status"])
        self.assertEquals("Access denied.", payload["message"])
    def setUp(self):
        super(MultipleChoiceTagTests, self).setUp()

        self.base = '/' + COURSE_NAME
        self.app_context = actions.simple_add_course(
            COURSE_NAME, ADMIN_EMAIL, 'Assessment Tags')
        self.namespace = 'ns_%s' % COURSE_NAME

        with utils.Namespace(self.namespace):
            dto = models.QuestionDTO(None, transforms.loads(self.MC_1_JSON))
            self.mc_1_id = models.QuestionDAO.save(dto)
            dto = models.QuestionDTO(None, transforms.loads(self.MC_2_JSON))
            self.mc_2_id = models.QuestionDAO.save(dto)
            dto = models.QuestionGroupDTO(
                None, transforms.loads(
                    self.QG_1_JSON_TEMPLATE % (self.mc_1_id, self.mc_2_id)))
            self.qg_1_id = models.QuestionGroupDAO.save(dto)

        self.course = courses.Course(None, self.app_context)
        self.assessment = self.course.add_assessment()
        self.assessment.now_available = True
        self.assessment.html_content = (
            '<question quid="%s" weight="1" instanceid="q1"></question>'
            '<question-group qgid="%s" instanceid="qg1"></question-group' % (
                self.mc_1_id, self.qg_1_id))
        self.course.save()
    def put(self):
        """Handles REST PUT verb with JSON payload."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(
                request, 'announcement-put', {'key': key}):
            return

        if not AnnouncementsRights.can_edit(self):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': key})
            return

        entity = AnnouncementEntity.get(key)
        if not entity:
            transforms.send_json_response(
                self, 404, 'Object not found.', {'key': key})
            return

        payload = request.get('payload')
        transforms.dict_to_entity(entity, transforms.json_to_dict(
            transforms.loads(payload),
            AnnouncementsItemRESTHandler.SCHEMA_DICT))
        entity.put()

        transforms.send_json_response(self, 200, 'Saved.')
    def test_cron_job(self):
        # ensure chunks don't exist
        response = self.get(
            'modules/drive/item/content?key=3', expect_errors=True)
        self.assertEqual(response.status_code, 404)

        response = self.get(
            'modules/drive/item/content?key=5', expect_errors=True)
        self.assertEqual(response.status_code, 404)

        # sync
        response = self.get('/cron/drive/sync')
        self.assertEqual(response.status_code, 200)
        self.execute_all_deferred_tasks()

        # now chunks should exist
        response = self.get('modules/drive/item/content?key=3')
        content = transforms.loads(response.body)
        payload = transforms.loads(content['payload'])
        self.assertIn('worksheets', payload)
        self.assertEqual(payload['id'], '3')

        response = self.get('modules/drive/item/content?key=5')
        self.assertEqual(
            response.headers['Content-Type'], 'text/html; charset=utf-8')
        self.assertEqual(response.body, '<p>Some HTML</p>')
    def test_add_to_question_group(self):
        # Create a question
        question_description = 'Question'
        question_dto = models.QuestionDTO(None, {
            'description': question_description,
            'type': 0  # MC
        })
        question_id = models.QuestionDAO.save(question_dto)

        add_to_group_selector = '.add-question-to-group'

        # No groups are present so no add_to_group icon should be present
        self.assertEqual([], self._soup_table().select(add_to_group_selector))

        # Create a group
        qg_description = 'Question Group'
        qg_dto = models.QuestionGroupDTO(None, {
            'description': qg_description,
            'items': []
        })
        qg_id = models.QuestionGroupDAO.save(qg_dto)

        # Since we now have a group, the add_to_group icon should be visible
        self.assertIsNotNone(
            self._soup_table().select(add_to_group_selector))

        # Add Question to Question Group via post_add_to_question_group
        questions_table = self._soup_table()
        xsrf_token = questions_table.get('data-qg-xsrf-token', '')
        response = self._call_add_to_question_group(
            question_id, qg_id, 1, xsrf_token)

        # Check if operation was successful
        self.assertEquals(response.status_int, 200)
        questions_table = self._soup_table()
        self.assertEquals(
            questions_table.select('.groups-cell li')[0].text.strip(),
            qg_description
        )

        # Check a bunch of calls that should fail
        response = self._call_add_to_question_group(question_id, qg_id, 1, 'a')
        self.assertEquals(response.status_int, 403)

        response = transforms.loads(self._call_add_to_question_group(
            -1, qg_id, 1, xsrf_token).body)
        self.assertEquals(response['status'], 500)

        response = transforms.loads(self._call_add_to_question_group(
            question_id, -1, 1, xsrf_token).body)
        self.assertEquals(response['status'], 500)

        response = transforms.loads(self._call_add_to_question_group(
            'a', qg_id, 1, xsrf_token).body)
        self.assertEquals(response['status'], 500)

        response = transforms.loads(self._call_add_to_question_group(
            question_id, qg_id, 'a', xsrf_token).body)
        self.assertEquals(response['status'], 500)
 def _expect_payload(self, response, status):
     content = transforms.loads(response.body)
     self.assertEquals(200, response.status_int)
     self.assertEquals(200, content["status"])
     self.assertEquals("OK.", content["message"])
     if status:
         payload = transforms.loads(content["payload"])
         self.assertEquals(status, payload["status"])
Exemple #31
0
 def test_get_returns_null_for_no_existing_rating(self):
     self.register_student()
     response = self.get_data()
     self.assertEquals(200, response['status'])
     payload = transforms.loads(response['payload'])
     self.assertIsNone(payload['rating'])
class AnnouncementsItemRESTHandler(BaseRESTHandler):
    """Provides REST API for an announcement."""

    # TODO(psimakov): we should really use an ordered dictionary, not plain
    # text; it can't be just a normal dict because a dict iterates its items in
    # undefined order;  thus when we render a dict to JSON an order of fields
    # will not match what we specify here; the final editor will also show the
    # fields in an undefined order; for now we use the raw JSON, rather than the
    # dict, but will move to an ordered dict late.
    SCHEMA_JSON = """
        {
            "id": "Announcement Entity",
            "type": "object",
            "description": "Announcement",
            "properties": {
                "key" : {"type": "string"},
                "title": {"optional": true, "type": "string"},
                "date": {"optional": true, "type": "date"},
                "html": {"optional": true, "type": "html"},
                "is_draft": {"type": "boolean"}
                }
        }
        """

    SCHEMA_DICT = transforms.loads(SCHEMA_JSON)

    # inputex specific schema annotations to control editor look and feel
    SCHEMA_ANNOTATIONS_DICT = [
        (['title'], 'Announcement'),
        (['properties', 'key', '_inputex'], {
            'label': 'ID',
            '_type': 'uneditable'
        }),
        (['properties', 'date', '_inputex'], {
            'label': 'Date',
            '_type': 'date',
            'dateFormat': 'Y/m/d',
            'valueFormat': 'Y/m/d'
        }), (['properties', 'title', '_inputex'], {
            'label': 'Title'
        }),
        (['properties', 'html', '_inputex'], {
            'label': 'Body',
            '_type': 'html',
            'editorType': 'simple'
        }),
        oeditor.create_bool_select_annotation(['properties', 'is_draft'],
                                              'Status', 'Draft', 'Published')
    ]

    REQUIRED_MODULES = [
        'inputex-date', 'inputex-rte', 'inputex-select', 'inputex-string',
        'inputex-uneditable'
    ]

    def get(self):
        """Handles REST GET verb and returns an object as JSON payload."""
        key = self.request.get('key')

        try:
            entity = AnnouncementEntity.get(key)
        except db.BadKeyError:
            entity = None

        if not entity:
            transforms.send_json_response(self, 404, 'Object not found.',
                                          {'key': key})
            return

        viewable = AnnouncementsRights.apply_rights(self, [entity])
        if not viewable:
            transforms.send_json_response(self, 401, 'Access denied.',
                                          {'key': key})
            return
        entity = viewable[0]

        json_payload = transforms.dict_to_json(
            transforms.entity_to_dict(entity),
            AnnouncementsItemRESTHandler.SCHEMA_DICT)
        transforms.send_json_response(
            self,
            200,
            'Success.',
            payload_dict=json_payload,
            xsrf_token=XsrfTokenManager.create_xsrf_token('announcement-put'))

    def put(self):
        """Handles REST PUT verb with JSON payload."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(request, 'announcement-put',
                                              {'key': key}):
            return

        if not AnnouncementsRights.can_edit(self):
            transforms.send_json_response(self, 401, 'Access denied.',
                                          {'key': key})
            return

        entity = AnnouncementEntity.get(key)
        if not entity:
            transforms.send_json_response(self, 404, 'Object not found.',
                                          {'key': key})
            return

        payload = request.get('payload')
        transforms.dict_to_entity(
            entity,
            transforms.json_to_dict(transforms.loads(payload),
                                    AnnouncementsItemRESTHandler.SCHEMA_DICT))
        entity.put()

        transforms.send_json_response(self, 200, 'Saved.')
Exemple #33
0
    def post(self):
        """Handles POST requests, when a reviewer submits a review."""
        student = self.personalize_page_and_get_enrolled()
        if not student:
            return

        course = self.get_course()
        rp = course.get_reviews_processor()

        unit_id = self.request.get('unit_id')
        unit = self.find_unit_by_id(unit_id)
        if not unit or not course.needs_human_grader(unit):
            self.error(404)
            return

        review_step_key = self.request.get('key')
        if not review_step_key:
            self.error(404)
            return

        try:
            review_step_key = db.Key(encoded=review_step_key)
            review_step = rp.get_review_steps_by_keys(unit.unit_id,
                                                      [review_step_key])[0]
        except Exception:  # pylint: disable-msg=broad-except
            self.error(404)
            return

        # Check that the student is allowed to review this submission.
        if not student.has_same_key_as(review_step.reviewer_key):
            self.error(404)
            return

        self.template_value['navbar'] = {'course': True}
        self.template_value['unit_id'] = unit.unit_id

        # Check that the review due date has not passed.
        time_now = datetime.datetime.now()
        review_due_date = unit.workflow.get_review_due_date()
        if time_now > review_due_date:
            self.template_value['time_now'] = time_now.strftime(
                HUMAN_READABLE_DATETIME_FORMAT)
            self.template_value['review_due_date'] = (
                review_due_date.strftime(HUMAN_READABLE_DATETIME_FORMAT))
            self.template_value['error_code'] = 'review_deadline_exceeded'
            self.render('error.html')
            return

        mark_completed = (self.request.get('is_draft') == 'false')
        self.template_value['is_draft'] = (not mark_completed)

        review_payload = self.request.get('answers')
        review_payload = transforms.loads(
            review_payload) if review_payload else []
        try:
            rp.write_review(unit.unit_id, review_step_key, review_payload,
                            mark_completed)
        except domain.TransitionError:
            self.template_value['error_code'] = 'review_already_submitted'
            self.render('error.html')
            return

        self.render('review_confirmation.html')
    def post(self):
        """Handle POST requests."""

        request = transforms.loads(self.request.get('request'))
        if not self.assert_xsrf_token_or_fail(request, self.XSRF_SCOPE, {}):
            return

        user = self.get_user()
        if not user:
            transforms.send_json_response(self, 401, 'Access denied.', {})
            return

        student = models.Student.get_enrolled_student_by_email(user.email())
        if not student:
            transforms.send_json_response(self, 401, 'Access denied.', {})
            return

        if not InvitationEmail.is_available(self):
            transforms.send_json_response(self, 500, 'Unavailable.', {})
            return

        payload_json = request.get('payload')
        payload_dict = transforms.json_to_dict(payload_json, self.SCHEMA)
        email_set = {
            email.strip() for email in payload_dict.get('emailList').split(',')
            if email.strip()}

        if not email_set:
            transforms.send_json_response(
                # I18N: Error indicating no email addresses were submitted.
                self, 400, gettext.gettext('Error: Empty email list'))
            return

        invitation_data = InvitationStudentProperty.load_or_create(student)

        # Limit the number of emails a user can send, to prevent spamming
        if invitation_data.invited_list_size() + len(email_set) > MAX_EMAILS:
            missing_count = MAX_EMAILS - invitation_data.invited_list_size()
            # I18N: Error indicating that the user cannot add the desired
            # list of additional email addresses to the list of invitations;
            # the total size of the list with the additions would be more
            # than any single user is allowed to send.  No email addresses
            # were added to the list to send, and no further email messages
            # were sent.
            transforms.send_json_response(self, 200, gettext.gettext(
                'This exceeds your email cap. Number of remaining '
                'invitations: %s. No messages sent.' % missing_count))
            return

        messages = []
        for email in email_set:
            if not is_email_valid(email):
                # I18N: Error indicating an email addresses is not well-formed.
                messages.append(gettext.gettext(
                    'Error: Invalid email "%s"' % email))
            elif invitation_data.is_in_invited_list(email):
                # I18N: Error indicating an email addresses is already known.
                messages.append(gettext.gettext(
                    'Error: You have already sent an invitation email to "%s"'
                    % email))
            elif unsubscribe.has_unsubscribed(email):
                # No message to the user, for privacy reasons
                logging.info('Declined to send email to unsubscribed user')
            elif models.Student.get_enrolled_student_by_email(email):
                # No message to the user, for privacy reasons
                logging.info('Declined to send email to registered user')
            else:
                InvitationEmail(self, email, student.name).send()

        invitation_data.append_to_invited_list(email_set)
        invitation_data.put()

        if messages:
            # I18N: Error indicating not all email messages were sent.
            messages.insert(0, gettext.gettext(
                'Not all messages were sent (%s / %s):') % (
                    len(email_set) - len(messages), len(email_set)))
            transforms.send_json_response(self, 400, '\n'.join(messages))
        else:
            transforms.send_json_response(
                self, 200,
                # I18N: Success message indicating number of emails sent.
                gettext.gettext('OK, %s messages sent' % len(email_set)))
 def append_to_invited_list(self, email_list):
     value_dict = transforms.loads(self.value)
     email_set = set(value_dict.get(self.EMAIL_LIST_KEY, []))
     email_set.update(email_list)
     value_dict[self.EMAIL_LIST_KEY] = list(email_set)
     self.value = transforms.dumps(value_dict)
Exemple #36
0
 def test_get_nonexistent_asset(self):
     response = self.get(ITEM_ASSET_URL + '?key=assets/img/foo.jpg')
     payload = transforms.loads(transforms.loads(response.body)['payload'])
     self.assertEquals('assets/img/foo.jpg', payload['key'])
     self.assertEquals('/assets/img/', payload['base'])
     self.assertEquals('assets/img/', payload['asset_url'])
class ImportCourseRESTHandler(CommonUnitRESTHandler):
    """Provides REST API to course import."""

    URI = '/rest/course/import'

    SCHEMA_JSON = """
    {
        "id": "Import Course Entity",
        "type": "object",
        "description": "Import Course",
        "properties": {
            "course" : {"type": "string"}
            }
    }
    """

    SCHEMA_DICT = transforms.loads(SCHEMA_JSON)

    REQUIRED_MODULES = [
        'inputex-string', 'inputex-select', 'inputex-uneditable']

    @classmethod
    def _get_course_list(cls):
        # Make a list of courses user has the rights to.
        course_list = []
        for acourse in sites.get_all_courses():
            if not roles.Roles.is_course_admin(acourse):
                continue
            if acourse == sites.get_course_for_current_request():
                continue

            atitle = '%s (%s)' % (acourse.get_title(), acourse.get_slug())

            course_list.append({
                'value': acourse.raw, 'label': cgi.escape(atitle)})
        return course_list

    @classmethod
    def SCHEMA_ANNOTATIONS_DICT(cls):
        """Schema annotations are dynamic and include a list of courses."""
        course_list = cls._get_course_list()
        if not course_list:
            return None

        # Format annotations.
        return [
            (['title'], 'Import Course'),
            (
                ['properties', 'course', '_inputex'],
                {
                    'label': 'Available Courses',
                    '_type': 'select',
                    'choices': course_list})]

    def get(self):
        """Handles REST GET verb and returns an object as JSON payload."""
        if not CourseOutlineRights.can_view(self):
            transforms.send_json_response(self, 401, 'Access denied.', {})
            return

        first_course_in_dropdown = self._get_course_list()[0]['value']

        transforms.send_json_response(
            self, 200, None,
            payload_dict={'course': first_course_in_dropdown},
            xsrf_token=XsrfTokenManager.create_xsrf_token(
                'import-course'))

    def put(self):
        """Handles REST PUT verb with JSON payload."""
        request = transforms.loads(self.request.get('request'))

        if not self.assert_xsrf_token_or_fail(
                request, 'import-course', {'key': None}):
            return

        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(self, 401, 'Access denied.', {})
            return

        payload = request.get('payload')
        course_raw = transforms.json_to_dict(
            transforms.loads(payload), self.SCHEMA_DICT)['course']

        source = None
        for acourse in sites.get_all_courses():
            if acourse.raw == course_raw:
                source = acourse
                break

        if not source:
            transforms.send_json_response(
                self, 404, 'Object not found.', {'raw': course_raw})
            return

        course = courses.Course(self)
        errors = []
        try:
            course.import_from(source, errors)
        except Exception as e:  # pylint: disable=broad-except
            logging.exception(e)
            errors.append('Import failed: %s' % e)

        if errors:
            transforms.send_json_response(self, 412, '\n'.join(errors))
            return

        course.save()
        transforms.send_json_response(self, 200, 'Imported.')
Exemple #38
0
class FilesItemRESTHandler(BaseRESTHandler):
    """Provides REST API for a file."""

    SCHEMA_JSON = """
        {
            "id": "Text File",
            "type": "object",
            "description": "Text File",
            "properties": {
                "key" : {"type": "string"},
                "encoding" : {"type": "string"},
                "content": {"type": "text"}
                }
        }
        """

    SCHEMA_DICT = transforms.loads(SCHEMA_JSON)

    SCHEMA_ANNOTATIONS_DICT = [(['title'], 'Text File'),
                               (['properties', 'key', '_inputex'], {
                                   'label': 'ID',
                                   '_type': 'uneditable'
                               }),
                               (['properties', 'encoding', '_inputex'], {
                                   'label': 'Encoding',
                                   '_type': 'uneditable'
                               }),
                               (['properties', 'content', '_inputex'], {
                                   'label': 'Content',
                                   '_type': 'text'
                               })]

    REQUIRED_MODULES = [
        'inputex-string', 'inputex-textarea', 'inputex-select',
        'gcb-uneditable'
    ]

    URI = '/rest/files/item'
    FILE_ENCODING_TEXT = 'text/utf-8'
    FILE_ENCODING_BINARY = 'binary/base64'
    FILE_EXTENSION_TEXT = frozenset(['.js', '.css', '.yaml', '.html', '.csv'])

    @classmethod
    def is_text_file(cls, filename):
        for extention in cls.FILE_EXTENSION_TEXT:
            if filename.endswith(extention):
                return True
        return False

    def validate_content(self, filename, content):
        # TODO(psimakov): handle more file types here
        if filename == '/course.yaml':
            courses.Course.validate_course_yaml(content, self.get_course())
        elif filename.endswith('.yaml'):
            yaml.safe_load(content)

    def get(self):
        """Handles REST GET verb and returns an object as JSON payload."""
        assert self.app_context.is_editable_fs()

        key = self.request.get('key')
        if not FilesRights.can_view(self):
            transforms.send_json_response(self, 401, 'Access denied.',
                                          {'key': key})
            return

        # Load data if possible.
        fs = self.app_context.fs.impl
        filename = fs.physical_to_logical(key)
        try:
            stream = fs.get(filename)
        except:  # pylint: disable=bare-except
            stream = None
        if not stream:
            transforms.send_json_response(self, 404, 'Object not found.',
                                          {'key': key})
            return

        # Prepare data.
        entity = {'key': key}
        if self.is_text_file(key):
            entity['encoding'] = self.FILE_ENCODING_TEXT
            entity['content'] = vfs.stream_to_string(stream)
        else:
            entity['encoding'] = self.FILE_ENCODING_BINARY
            entity['content'] = base64.b64encode(stream.read())

        # Render JSON response.
        json_payload = transforms.dict_to_json(entity)
        transforms.send_json_response(
            self,
            200,
            'Success.',
            payload_dict=json_payload,
            xsrf_token=XsrfTokenManager.create_xsrf_token('file-put'))

    def put(self):
        """Handles REST PUT verb with JSON payload."""
        assert self.app_context.is_editable_fs()

        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(request, 'file-put',
                                              {'key': key}):
            return

        # TODO(psimakov): we don't allow editing of all files; restrict further
        if not FilesRights.can_edit(self):
            transforms.send_json_response(self, 401, 'Access denied.',
                                          {'key': key})
            return

        payload = request.get('payload')
        entity = transforms.loads(payload)
        encoding = entity['encoding']
        content = entity['content']

        # Validate the file content.
        errors = []
        try:
            if encoding == self.FILE_ENCODING_TEXT:
                content_stream = vfs.string_to_stream(content)
            elif encoding == self.FILE_ENCODING_BINARY:
                content_stream = base64.b64decode(content)
            else:
                errors.append('Unknown encoding: %s.' % encoding)

            self.validate_content(key, content)
        except Exception as e:  # pylint: disable=W0703
            errors.append('Validation error: %s' % e)
        if errors:
            transforms.send_json_response(self, 412, ''.join(errors))
            return

        # Store new file content.
        fs = self.app_context.fs.impl
        filename = fs.physical_to_logical(key)
        fs.put(filename, content_stream)

        # Send reply.
        transforms.send_json_response(self, 200, 'Saved.')

    def delete(self):
        """Handles REST DELETE verb."""

        key = self.request.get('key')

        if not self.assert_xsrf_token_or_fail(self.request, 'delete-asset',
                                              {'key': key}):
            return

        if not FilesRights.can_delete(self):
            transforms.send_json_response(self, 401, 'Access denied.',
                                          {'key': key})
            return

        fs = self.app_context.fs.impl
        path = fs.physical_to_logical(key)
        if not fs.isfile(path):
            transforms.send_json_response(self, 403, 'File does not exist.',
                                          None)
            return
        fs.delete(path)

        transforms.send_json_response(self, 200, 'Deleted.')
Exemple #39
0
 def test_no_lessons_in_course(self):
     response = transforms.loads(self.get(
         '/test/rest/data/lessons/items').body)
     self.assertListEqual([], response['data'])
Exemple #40
0
 def _get_payload(self, body):
     return transforms.loads(body)
Exemple #41
0
    def assert_response(self, code, body_needle, response):
        from_json = transforms.loads(response.body)

        self.assertEqual(200, response.status_code)
        self.assertEqual(code, from_json['status'])
        self.assertIn(body_needle, from_json['message'])
Exemple #42
0
 def get_code(cls, student, unit_id):
     entity = cls.get(student, str(unit_id))
     return transforms.loads(entity.data) if entity else None
Exemple #43
0
    def test_add_to_question_group(self):
        # Create a question
        question_description = 'Question'
        question_dto = models.QuestionDTO(
            None,
            {
                'description': question_description,
                'type': 0  # MC
            })
        question_id = models.QuestionDAO.save(question_dto)

        # No groups are present so no add_to_group icon should be present
        self.assertIsNone(self._load_tables()[0].find('./tbody/tr/td[ul]div'))

        # Create a group
        qg_description = 'Question Group'
        qg_dto = models.QuestionGroupDTO(None, {
            'description': qg_description,
            'items': []
        })
        qg_id = models.QuestionGroupDAO.save(qg_dto)

        # Since we now have a group, the add_to_group icon should be visible
        self.assertIsNotNone(
            self._load_tables()[0].find('./tbody/tr/td[ul]/div'))

        # Add Question to Question Group via post_add_to_question_group
        asset_tables = self._load_tables()
        xsrf_token = asset_tables[0].get('data-qg-xsrf-token', '')
        response = self._call_add_to_question_group(question_id, qg_id, 1,
                                                    xsrf_token)

        # Check if operation was successful
        self.assertEquals(response.status_int, 200)
        asset_tables = self._load_tables()
        self.assertEquals(asset_tables[0].find('./tbody/tr/td/ul/li').text,
                          qg_description)
        self.assertEquals(asset_tables[1].find('./tbody/tr/td/ul/li').text,
                          question_description)

        # Check a bunch of calls that should fail
        response = self._call_add_to_question_group(question_id, qg_id, 1, 'a')
        self.assertEquals(response.status_int, 403)

        response = transforms.loads(
            self._call_add_to_question_group(-1, qg_id, 1, xsrf_token).body)
        self.assertEquals(response['status'], 500)

        response = transforms.loads(
            self._call_add_to_question_group(question_id, -1, 1,
                                             xsrf_token).body)
        self.assertEquals(response['status'], 500)

        response = transforms.loads(
            self._call_add_to_question_group('a', qg_id, 1, xsrf_token).body)
        self.assertEquals(response['status'], 500)

        response = transforms.loads(
            self._call_add_to_question_group(question_id, qg_id, 'a',
                                             xsrf_token).body)
        self.assertEquals(response['status'], 500)
 def test_get_item_asset_url_in_subdir(self):
     response = self.get(ITEM_ASSET_URL + '?key=assets/lib/my_project')
     payload = transforms.loads(response.body)
     self.assertEquals(200, payload['status'])
 def test_get_unknown_hook_content(self):
     # Should be safe (but unhelpful) to ask for no hook.
     response = transforms.loads(self.get(ADMIN_SETTINGS_URL).body)
     payload = transforms.loads(response['payload'])
     self.assertIsNone(payload['hook_content'])
Exemple #46
0
 def test_units_schema(self):
     response = transforms.loads(self.get(
         '/test/rest/data/units/items').body)
     self.assertIn('unit_id', response['schema'])
     self.assertIn('title', response['schema'])
     self.assertIn('props', response['schema'])
Exemple #47
0
 def process_event(cls, event, static_params):
     content = transforms.loads(event.data)
     if 'loc' not in content:
         return None
     loc = content['loc']
     return loc.get('country'), loc.get('region'), loc.get('city')
Exemple #48
0
 def test_one_student_no_scores(self):
     with utils.Namespace('ns_test'):
         models.Student(user_id='123456').put()
     response = transforms.loads(self.get(
         '/test/rest/data/assessment_scores/items').body)
     self.assertListEqual([], response['data'])
    def put(self):
        """Handles PUT REST verb to save lesson and associated activity."""
        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(
                request, 'lesson-edit', {'key': key}):
            return

        if not CourseOutlineRights.can_edit(self):
            transforms.send_json_response(
                self, 401, 'Access denied.', {'key': key})
            return

        course = courses.Course(self)
        lesson = course.find_lesson_by_id(None, key)
        if not lesson:
            transforms.send_json_response(
                self, 404, 'Object not found.', {'key': key})
            return

        payload = request.get('payload')
        updates_dict = transforms.json_to_dict(
            transforms.loads(payload),
            self.get_schema(course, key).get_json_schema_dict())

        lesson.title = updates_dict['title']
        lesson.unit_id = updates_dict['unit_id']
        lesson.scored = (updates_dict['scored'] == 'scored')
        lesson.objectives = updates_dict['objectives']
        lesson.video = updates_dict['video']
        lesson.notes = updates_dict['notes']
        lesson.auto_index = updates_dict['auto_index']
        lesson.activity_title = updates_dict['activity_title']
        lesson.activity_listed = updates_dict['activity_listed']
        lesson.manual_progress = updates_dict['manual_progress']
        lesson.now_available = not updates_dict['is_draft']

        activity = updates_dict.get('activity', '').strip()
        errors = []
        if activity:
            if lesson.has_activity:
                course.set_activity_content(lesson, activity, errors=errors)
            else:
                errors.append('Old-style activities are not supported.')
        else:
            lesson.has_activity = False
            fs = self.app_context.fs
            path = fs.impl.physical_to_logical(course.get_activity_filename(
                lesson.unit_id, lesson.lesson_id))
            if fs.isfile(path):
                fs.delete(path)

        if not errors:
            common_utils.run_hooks(self.PRE_SAVE_HOOKS, lesson, updates_dict)
            assert course.update_lesson(lesson)
            course.save()
            common_utils.run_hooks(self.POST_SAVE_HOOKS, lesson)
            transforms.send_json_response(self, 200, 'Saved.')
        else:
            transforms.send_json_response(self, 412, '\n'.join(errors))
Exemple #50
0
 def test_no_students(self):
     response = transforms.loads(self.get(
         '/test/rest/data/assessment_scores/items').body)
     self.assertListEqual([], response['data'])
Exemple #51
0
 def get_student_answer(cls, unit, student):
     submitted_contents = student_work.Submission.get_contents(
         unit.unit_id, student.get_key())
     if submitted_contents:
         return transforms.loads(submitted_contents)
     return dict()
Exemple #52
0
    def put(self):
        """Handles REST PUT verb with JSON payload."""

        request = transforms.loads(self.request.get('request'))
        key = request.get('key')

        if not self.assert_xsrf_token_or_fail(request, 'section-put',
                                              {'key': key}):
            #            logging.warning('***RAM*** put FAIL (saving) ' + str(request))
            return

#        logging.warning('***RAM*** put (saving) ' + str(request))

        if not TeacherRights.can_edit_section(self):
            transforms.send_json_response(self, 401,
                                          'MobileCSP: Access denied.',
                                          {'key': key})
            return

        entity = CourseSectionEntity.get(key)
        if not entity:
            transforms.send_json_response(
                self, 404, 'MobileCSP: Course Section not found.',
                {'key': key})
            return

        schema = SectionItemRESTHandler.SCHEMA()

        payload = request.get('payload')
        update_dict = transforms.json_to_dict(transforms.loads(payload),
                                              schema.get_json_schema_dict())

        # Check for invalid emails -- email must be a registered student
        # Found the regular expression on Stackoverflow
        emails_raw = update_dict['students']
        emails = re.findall(r'[\w\.-]+@[\w\.-]+', emails_raw)

        return_code = 200
        bad_emails = []
        good_emails = []
        for email in emails:
            #            email = email.strip(' \t\n\r')
            if email:
                logging.debug('***RAM*** email = |' + email + '|')
                student = Student.get_first_by_email(email)[
                    0]  # returns a tuple
                if not student:
                    bad_emails.append(email)
                else:
                    good_emails.append(email)

        confirm_message = 'Confirmation\n'
        confirm_message += '------------\n\n'
        if bad_emails:
            logging.info('***RAM*** bad_emails found = ' + str(bad_emails))
            return_code = 401
            confirm_message = 'The following were invalid emails:\n'
            for email in bad_emails:
                confirm_message += email + '\n'
            confirm_message += 'Either there is no student with that email\n'
            confirm_message += 'registered for the course.  Or there is a \n'
            confirm_message += 'typo in the email address provided.\n\n'
        if good_emails:
            logging.info('***RAM*** good_emails found = ' + str(good_emails))
            confirm_message += 'Students with the following emails\n'
            confirm_message += 'are currently registered in your section:\n'
            for email in good_emails:
                confirm_message += email + '\n'

            update_dict['students'] = ','.join(
                good_emails)  # New-line delimited

        transforms.dict_to_entity(entity, update_dict)

        entity.put()
        if return_code == 200:
            confirm_message += 'Your section was successfully updated and saved.\n\n\n\n\n'
        else:
            confirm_message += 'Other information for your section was successfully updated and saved.\n\n\n\n\n'
        confirm_message += 'Confirmation\n'
        confirm_message += '------------\n'

        transforms.send_json_response(self, return_code, confirm_message,
                                      {'key': key})
 def invited_list_size(self):
     return len(transforms.loads(self.value))
Exemple #54
0
    def get_html_for(
        cls, handler, schema_json, annotations, object_key,
        rest_url, exit_url,
        additional_dirs=None,
        auto_return=False,
        delete_url=None, delete_message=None, delete_method='post',
        delete_button_caption='Delete',
        display_types=None,
        exit_button_caption='Close',
        extra_args=None,
        extra_css_files=None,
        extra_js_files=None,
        extra_required_modules=None,
        read_only=False,
        required_modules=None,
        save_button_caption='Save',
        save_method='put'):
        """Creates an HTML code needed to embed and operate this form.

        This method creates an HTML, JS and CSS  required to embed JSON
        schema-based object editor into a view.

        Args:
            handler: a BaseHandler class, which will host this HTML, JS and CSS
            schema_json: a text of JSON schema for the object being edited
            annotations: schema annotations dictionary
            object_key: a key of an object being edited
            rest_url: a REST endpoint for object GET/PUT operation
            exit_url: a URL to go to after the editor form is dismissed
            auto_return: whether to return to the exit_url on successful save
            additional_dirs: list of extra directories to look for
                Jinja template files, e.g., JS or CSS files included by modules.
            delete_url: optional URL for delete operation
            delete_message: string. Optional custom delete confirmation message
            delete_method: optional HTTP method for delete operation
            delete_button_caption: string. A caption for the 'Delete' button
            display_types: list of strings. All schema field types
            exit_button_caption: a caption for the 'Close' button
            extra_args: extra request params passed back in GET and POST
            extra_css_files: list of extra CSS files to be included
            extra_js_files: list of extra JS files to be included
            extra_required_modules: list of strings.
                inputex modules not covered by display_types
            read_only: optional flag; if set, removes Save and Delete operations
            required_modules: list of inputex modules required for this editor
            save_button_caption: a caption for the 'Save' button
            save_method: how the data should be saved to the server (put|upload)

        Returns:
            The HTML, JS and CSS text that will instantiate an object editor.
        """

        if required_modules:
            if not set(required_modules).issubset(set(ALL_MODULES)):
                difference = set(required_modules).difference(set(ALL_MODULES))
                raise ValueError(
                    "Unsupported inputEx modules were required: {}".format(
                        difference))
        elif display_types:
            required_modules = list(set(
                TYPES_TO_MODULES[type_name] for type_name in display_types))
        else:
            required_modules = ALL_MODULES

        if extra_required_modules:
            required_modules += extra_required_modules

        if not delete_message:
            kind = transforms.loads(schema_json).get('description')
            if not kind:
                kind = 'content'
            delete_message = 'Are you sure you want to delete this %s?' % kind

        # construct parameters
        get_url = rest_url
        get_args = {'key': object_key}
        post_url = rest_url
        post_args = {'key': object_key}

        if extra_args:
            get_args.update(extra_args)
            post_args.update(extra_args)

        if read_only:
            post_url = ''
            post_args = ''

        rte_tag_data = []
        for tag, tag_class in tags.get_tag_bindings().items():
            rte_tag_data.append({
                'name': tag,
                'vendor': tag_class.vendor(),
                'label': tag_class.name(),
                'iconUrl': tag_class().get_icon_url()})

        editor_prefs = {
            'xsrf_token': crypto.XsrfTokenManager.create_xsrf_token(
                EditorPrefsRestHandler.XSRF_TOKEN),
            'location': rest_url,
            'key': object_key,
            'prefs': {}
        }
        user = users.get_current_user()
        if user is not None:
            key_name = EditorPrefsDao.create_key_name(
                user.user_id(), rest_url, object_key)
            editor_prefs_dto = EditorPrefsDao.load(key_name)
            if editor_prefs_dto:
                editor_prefs['prefs'] = editor_prefs_dto.dict

        extra_script_tag_urls = []
        for callback in cls.EXTRA_SCRIPT_TAG_URLS:
            for url in callback():
                extra_script_tag_urls.append(url)

        template_values = {
            'enabled': custom_module.enabled,
            'schema': schema_json,
            'get_url': '%s?%s' % (get_url, urllib.urlencode(get_args, True)),
            'save_url': post_url,
            'save_args': transforms.dumps(post_args),
            'exit_button_caption': exit_button_caption,
            'exit_url': exit_url or '',
            'required_modules': COMMON_REQUIRED_MODULES + required_modules,
            'extra_css_files': extra_css_files or [],
            'extra_js_files': extra_js_files or [],
            'schema_annotations': [
                (item[0], transforms.dumps(item[1])) for item in annotations],
            'save_method': save_method,
            'auto_return': auto_return,
            'delete_button_caption': delete_button_caption,
            'save_button_caption': save_button_caption,
            'rte_tag_data': transforms.dumps(rte_tag_data),
            'delete_message': delete_message,
            'preview_xsrf_token': crypto.XsrfTokenManager.create_xsrf_token(
                PreviewHandler.XSRF_TOKEN),
            'editor_prefs': transforms.dumps(editor_prefs),
            'extra_script_tag_urls': extra_script_tag_urls
        }

        if delete_url and not read_only:
            template_values['delete_url'] = delete_url
        if delete_method:
            template_values['delete_method'] = delete_method
        if appengine_config.BUNDLE_LIB_FILES:
            template_values['bundle_lib_files'] = True

        return jinja2.utils.Markup(handler.get_template('oeditor.html', (
            [TEMPLATES_DIR] + (additional_dirs or [])
        )).render(template_values))
 def is_in_invited_list(self, email):
     value_dict = transforms.loads(self.value)
     return email in value_dict.get(self.EMAIL_LIST_KEY, [])
Exemple #56
0
    def get_students(self):
        """Renders course students view."""

        template_values = {}
        template_values['page_title'] = self.format_title('Students')

        details = """
            <h3>Enrollment Statistics</h3>
            <ul><li>pending</li></ul>
            <h3>Assessment Statistics</h3>
            <ul><li>pending</li></ul>
            """

        update_message = ''
        update_action = """
            <form
                id='gcb-compute-student-stats'
                action='dashboard?action=compute_student_stats'
                method='POST'>
                <input type="hidden" name="xsrf_token" value="%s">
                <p>
                    <button class="gcb-button" type="submit">
                        Re-Calculate Now
                    </button>
                </p>
            </form>
        """ % self.create_xsrf_token('compute_student_stats')

        job = ComputeStudentStats(self.app_context).load()
        if not job:
            update_message = """
                Student statistics have not been calculated yet."""
        else:
            if job.status_code == jobs.STATUS_CODE_COMPLETED:
                stats = transforms.loads(job.output)
                enrolled = stats['enrollment']['enrolled']
                unenrolled = stats['enrollment']['unenrolled']

                enrollment = []
                enrollment.append('<li>previously enrolled: %s</li>' %
                                  unenrolled)
                enrollment.append('<li>currently enrolled: %s</li>' % enrolled)
                enrollment.append('<li>total: %s</li>' %
                                  (unenrolled + enrolled))
                enrollment = ''.join(enrollment)

                assessment = []
                total = 0
                for key, value in stats['scores'].items():
                    total += value[0]
                    avg_score = 0
                    if value[0]:
                        avg_score = round(value[1] / value[0], 1)
                    assessment.append("""
                        <li>%s: completed %s, average score %s
                        """ % (key, value[0], avg_score))
                assessment.append('<li>total: %s</li>' % total)
                assessment = ''.join(assessment)

                details = """
                    <h3>Enrollment Statistics</h3>
                    <ul>%s</ul>
                    <h3>Assessment Statistics</h3>
                    <ul>%s</ul>
                    """ % (enrollment, assessment)

                update_message = """
                    Student statistics were last updated on
                    %s in about %s second(s).""" % (job.updated_on,
                                                    job.execution_time_sec)
            elif job.status_code == jobs.STATUS_CODE_FAILED:
                update_message = """
                    There was an error updating student statistics.
                    Here is the message:<br>
                    <blockquote>
                      <pre>\n%s</pre>
                    </blockquote>
                    """ % cgi.escape(job.output)
            else:
                update_action = ''
                update_message = """
                    Student statistics update started on %s and is running
                    now. Please come back shortly.""" % job.updated_on

        lines = []
        lines.append(details)
        lines.append(update_message)
        lines.append(update_action)
        lines = ''.join(lines)

        template_values['main_content'] = lines
        self.render_page(template_values)
Exemple #57
0
 def test_non_strict_mode_parses_json(self):
     json_text = '{"foo": "bar", "baz": ["bum",],}'
     _json = transforms.loads(json_text, strict=False)
     assert _json.get('foo') == 'bar'
Exemple #58
0
    def post(self):
        """Handles POST requests."""
        student = self.personalize_page_and_get_enrolled()
        if not student:
            return

        if not self.assert_xsrf_token_or_fail(self.request, 'assessment-post'):
            return

        if not self.assert_participant_or_fail(student):
            return

        course = self.get_course()
        assessment_type = self.request.get('assessment_type')
        if not assessment_type:
            self.error(404)
            logging.error('No assessment type supplied.')
            return

        unit = course.find_unit_by_id(assessment_type)
        if unit is None or unit.type != verify.UNIT_TYPE_ASSESSMENT:
            self.error(404)
            logging.error('No assessment named %s exists.', assessment_type)
            return

        self.template_value['navbar'] = {'course': True}

        try:
            AssessmentTracker.try_submit_test(student, assessment_type)
        except ValueError as e:
            self.template_value['error'] = e.message
            self.render('assessment_denied.html')
            return

        self.template_value['assessment'] = assessment_type
        self.template_value['assessment_name'] = unit.title
        self.template_value[
            'can_take_again'] = AssessmentTracker.can_take_again(
                student, assessment_type)
        self.template_value['retake_link'] = '/assessment?' + urllib.urlencode(
            {
                'name': assessment_type,
                'action': 'start',
            })
        self.template_value['is_last_assessment'] = (
            course.is_last_assessment(unit))

        # Convert answers from JSON to dict.
        answers = self.request.get('answers')
        answers = transforms.loads(answers) if answers else []

        grader = unit.workflow.get_grader()

        # Scores are not recorded for human-reviewed assignments.
        score = 0
        if grader == courses.AUTO_GRADER:
            score = int(round(float(self.request.get('score'))))

        # Record assessment transaction.
        student = self.update_assessment_transaction(student.key().name(),
                                                     assessment_type, answers,
                                                     score)

        if grader == courses.HUMAN_GRADER:
            rp = course.get_reviews_processor()

            # Guard against duplicate submissions of a human-graded assessment.
            previously_submitted = rp.does_submission_exist(
                unit.unit_id, student.get_key())

            if not previously_submitted:
                # Check that the submission due date has not passed.
                time_now = datetime.datetime.now()
                submission_due_date = unit.workflow.get_submission_due_date()
                if time_now > submission_due_date:
                    self.template_value['time_now'] = time_now.strftime(
                        HUMAN_READABLE_DATETIME_FORMAT)
                    self.template_value['submission_due_date'] = (
                        submission_due_date.strftime(
                            HUMAN_READABLE_DATETIME_FORMAT))
                    self.template_value['error_code'] = (
                        'assignment_deadline_exceeded')
                    self.render('error.html')
                    return

                submission_key = student_work.Submission.write(
                    unit.unit_id, student.get_key(), answers)
                rp.start_review_process_for(unit.unit_id, submission_key,
                                            student.get_key())
                # Record completion event in progress tracker.
                course.get_progress_tracker().put_assessment_completed(
                    student, assessment_type)

            self.template_value['previously_submitted'] = previously_submitted

            matcher = unit.workflow.get_matcher()
            self.template_value['matcher'] = matcher
            if matcher == review.PEER_MATCHER:
                self.template_value['review_dashboard_url'] = (
                    'reviewdashboard?unit=%s' % unit.unit_id)

            self.render('reviewed_assessment_confirmation.html')
            return
        else:
            # Record completion event in progress tracker.
            course.get_progress_tracker().put_assessment_completed(
                student, assessment_type)

            # Save the submission in the datastore, overwriting the earlier
            # version if it exists.
            submission_key = student_work.Submission.write(
                unit.unit_id, student.get_key(), answers)

            self.template_value['result'] = course.get_overall_result(student)
            self.template_value['score'] = score
            self.template_value['overall_score'] = course.get_overall_score(
                student)

            self.render('test_confirmation.html')
Exemple #59
0
    def get_student_details(cls, handler):
        """GET endpoint for Student Profile Details"""

        if not roles.Roles.is_super_admin():
            handler.error(403)
            handler.render_page({
                'page_title': cls.NAME,
                'main_content': 'Unauthorized'
            })
            return

        key = handler.request.get('key')
        if not key:
            handler.error(400)
            handler.render_page({
                'page_title':
                handler.format_title(cls.NAME),
                'main_content':
                '400 No email or user id provided'
            })
            return

        namespace_manager.set_namespace(
            appengine_config.DEFAULT_NAMESPACE_NAME)

        profile = models.PersonalProfile.get(key)
        if not profile:
            handler.error(404)
            handler.render_page({
                'page_title':
                handler.format_title(cls.NAME),
                'main_content':
                '404 Student not found in this course'
            })
            return

        template_value = dict()

        # parse the profile's enrollment info
        if profile.enrollment_info:
            template_value['enrollment_info'] = transforms.loads(
                profile.enrollment_info)

        template_value['profile'] = profile

        # List of all Courses
        courses = course_list.CourseList.all().fetch(None)
        template_value['courses'] = courses

        template_value['back_action'] = '/admin/global?tab=' + cls.ADMIN_TAB

        content = jinja2.utils.Markup(
            handler.get_template(
                'templates/profile_details.html',
                [os.path.dirname(__file__)]).render(template_value))

        handler.render_page(
            {
                'page_title': handler.format_title(cls.NAME),
                'main_content': content
            },
            in_action=cls.ADMIN_DETAILS_ACTION,
            in_tab=cls.ADMIN_TAB)
Exemple #60
0
    def test_action_icons(self):
        assessment = self.course.add_assessment()
        assessment.title = 'Test Assessment'
        assessment.now_available = True
        link = self.course.add_link()
        link.title = 'Test Link'
        link.now_available = False
        unit = self.course.add_unit()
        unit.title = 'Test Unit'
        unit.now_available = True
        lesson = self.course.add_lesson(unit)
        lesson.title = 'Test Lesson'
        lesson.now_available = False
        self.course.save()

        dom = self.parse_html_string(self.get(self.URL).body)
        course_outline = dom.find('.//div[@class="course-outline editable"]')
        xsrf_token = course_outline.get('data-status-xsrf-token', '')
        lis = course_outline.findall('.//ol[@class="course"]/li')
        self.assertEquals(len(lis), 3)

        # Test Assessment
        self._check_list_item(lis[0],
                              'assessment?name=%s' % assessment.unit_id,
                              assessment.title, 'unit', assessment.unit_id,
                              'md-lock-open')

        # Test Link
        self._check_list_item(lis[1], '', link.title, 'unit', link.unit_id,
                              'md-lock')

        # Test Unit
        unit_li = lis[2]
        self._check_list_item(unit_li, 'unit?unit=%s' % unit.unit_id,
                              'Test Unit', 'unit', unit.unit_id,
                              'md-lock-open')

        # Test Lesson
        self._check_list_item(
            unit_li.find('ol/li'),
            'unit?unit=%s&lesson=%s' % (unit.unit_id, lesson.lesson_id),
            lesson.title, 'lesson', lesson.lesson_id, 'md-lock')

        # Send POST without xsrf token, should give 403
        response = self._set_draft_status(assessment.unit_id, 'unit', 'xyz',
                                          '1')
        self.assertEquals(response.status_int, 403)

        # Set assessment to private
        response = self._set_draft_status(assessment.unit_id, 'unit',
                                          xsrf_token, '1')
        self.assertEquals(response.status_int, 200)
        payload = transforms.loads(transforms.loads(response.body)['payload'])
        self.assertEquals(payload['is_draft'], True)

        # Set lesson to public
        response = self._set_draft_status(lesson.lesson_id, 'lesson',
                                          xsrf_token, '0')
        self.assertEquals(response.status_int, 200)
        payload = transforms.loads(transforms.loads(response.body)['payload'])
        self.assertEquals(payload['is_draft'], False)

        # Refresh page, check results
        lis = self.parse_html_string(self.get(
            self.URL).body).findall('.//ol[@class="course"]/li')
        self.assertIn('md-lock',
                      lis[0].find('./div/div/div[3]').get('class', ''))
        self.assertIn('md-lock-open',
                      lis[2].find('ol/li/div/div/div[3]').get('class', ''))

        # Repeat but set assessment to public and lesson to private
        response = self._set_draft_status(assessment.unit_id, 'unit',
                                          xsrf_token, '0')
        response = self._set_draft_status(lesson.lesson_id, 'lesson',
                                          xsrf_token, '1')

        # Refresh page, check results
        lis = self.parse_html_string(self.get(
            self.URL).body).findall('.//ol[@class="course"]/li')
        self.assertIn('md-lock-open',
                      lis[0].find('./div/div/div[3]').get('class', ''))
        self.assertIn('md-lock',
                      lis[2].find('ol/li/div/div/div[3]').get('class', ''))