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
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.')
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"])
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.')
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)
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.')
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.')
def test_no_lessons_in_course(self): response = transforms.loads(self.get( '/test/rest/data/lessons/items').body) self.assertListEqual([], response['data'])
def _get_payload(self, body): return transforms.loads(body)
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'])
def get_code(cls, student, unit_id): entity = cls.get(student, str(unit_id)) return transforms.loads(entity.data) if entity else None
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'])
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'])
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')
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))
def test_no_students(self): response = transforms.loads(self.get( '/test/rest/data/assessment_scores/items').body) self.assertListEqual([], response['data'])
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()
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))
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, [])
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)
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'
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')
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)
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', ''))