class UploadUrlHandler(ApiRequestHandler): path = resource.endpoint('/dashboard/uploadurl/repository/<studentId>') @path.operation(type_='BlobStoreUploadInfo', alias='newUploadUrl', parameters=[ swagger.String( name="studentId", param_type="path", description="Id of student details to edit", required=True) ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden") ]) def post(self, studentId): """Create a new blobstore upload url. The student id is currently not used with this implementation. The student id should be sent with uploaded file. """ self.staff_required() self.render_json( {"url": blobstore.create_upload_url(config.UPLOAD_CB_URL)})
class UserApi(ApiRequestHandler): """Handler requests for a specific user. """ path = student_resource.endpoint("/dashboard/users/<userId>") @path.operation(type_="User", alias="deleteUser", parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(400, "Bad Request"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found"), ]) def delete(self, userId): """Delete a user. Note that it doesn't prevent a user from registering again. """ current_user = self.admin_required() if current_user.key.id() == userId: self.abort(400) user = models.User.get_by_id(userId) if user is None: self.abort(404) user.key.delete() self.render_json({})
class RepositoryDocumentListApi(ApiRequestHandler): """Handle Document listing requests """ path = resource.endpoint('/dashboard/repository/<studentId>/files') @path.operation( type_="DocumentList", alias="getRepositoryByStudentId", parameters=[ swagger.String(name="cursor", description="Cursor to query the next page", param_type="query"), swagger.String(name="studentId", param_type="path", description="Id of student details to edit", required=True) ], responses=[ swagger.Message(200, "Ok"), swagger.Message(400, "Bad Request"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found") ]) def get(self, studentId): """List all the files at destination to a specific student """ student = Student.get_by_id(studentId) if not student: self.abort(404) current_user = self.login_required() if (not current_user.is_staff and not current_user.is_admin and not current_user.is_domain_admin and current_user.student_id != student.key.id()): self.abort(403) return cursor_key = self.request.GET.get('cursor') # using cheap request and ndb entities cache file_keys, cursor, _ = models.Document.get_files(student.key, cursor_key, keys_only=True) ffiles = [k.get_async() for k in file_keys] self.render_json({ 'files': map(self.file_dict, [ff.get_result() for ff in ffiles]), 'cursor': cursor.urlsafe() if cursor else '' }) def file_dict(self, file_): data = file_.summary() data['url'] = self.uri_for('dashboard_download_file', keyId=file_.key.id()) return data
class AssessmentExamApi(ApiRequestHandler): """Handle request for an exam resource """ path = assessment_resource.endpoint( '/dashboard/assessments/exams/<examId:\d+>') @path.operation(type_='AssessmentExam', alias='getExamDetails', parameters=[ swagger.String( name="examId", description="Id of exam to show details for", param_type="path", required=True), ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found") ]) def get(self, examId): """Retrieve detailed informations about an exam """ self.staff_required() exam = models.AssessmentExam.get_by_id(int(examId)) if exam is None: self.abort(404) self.render_json(exam.details())
class UserListApi(ApiRequestHandler): """Handle user list resource. """ path = student_resource.endpoint("/dashboard/users") @path.operation(type_="UserList", alias="listUsers", parameters=[ swagger.String( name="cursor", description="Cursor to query the next page", param_type="query") ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), ]) def get(self): """List all Users (20 per page). The current user must be logged in as staff to see the list of users. """ self.staff_required() cursor_key = self.request.GET.get('cursor') users, cursor = models.User.get_users(cursor_key) return self.render_json({ 'type': 'users', 'users': [s.summary() for s in users], 'cursor': cursor if cursor else '' })
class AssessmentExamListApi(ApiRequestHandler): """Handle operations on the exam lists """ path = assessment_resource.endpoint('/dashboard/assessments/exams') @path.operation( type_='AssessmentExamList', alias='listExams', parameters=[ swagger.String( name="studentId", description="Id of student details to list exam for", param_type="query"), swagger.String(name="cursor", description="Cursor to query the next page", param_type="query"), ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found") ]) def get(self): """Return a list exams. """ student_id = self.request.GET.get('studentId') if student_id: self._get_exams_by_student_id(student_id) else: self._get_exams() def _get_exams(self): self.staff_required() exams = models.AssessmentExam.get_exams() self.render_json({ 'cursor': '', 'exams': [e.summary() for e in exams], }) def _get_exams_by_student_id(self, student_id): student = Student.get_by_id(student_id) if not student: self.abort(404, 'User not found') current_user = self.login_required() if (not current_user.is_staff and not current_user.is_admin and not current_user.is_domain_admin and student.key.id() != current_user.student_id): self.abort(403) exams = models.AssessmentExam.get_by_student_id(student.key.id()) self.render_json({ 'cursor': '', 'exams': [e.summary() for e in exams], 'student': student.summary() })
class StudentNameApi(ApiRequestHandler): """Handle request for a specific student. """ path = student_resource.endpoint( "/dashboard/students/<studentId>/<propName>") @path.operation(type_="Student", alias="saveStudentName", parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found"), ]) def put(self, studentId, propName): """Save student name. """ self.staff_required() methods = { 'name': models.Student.edit_name, 'email': models.Student.edit_email, 'year': models.Student.edit_year, } if propName not in methods: self.abort(404) try: payload = json.loads(self.request.body) student = methods[propName](studentId, payload) except ( ValidationError, ValueError, AttributeError, ): self.abort(400) if student is None: self.abort(404) StudentListApi.reset_list_cache() self.render_json({})
class StudentProfileUploadUrlApi(ApiRequestHandler): path = student_resource.endpoint('/dashboard/uploadurl/studentsprofile') @path.operation(type_='BlobStoreUploadInfo', alias='newStudentUploadUrl', parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden") ]) def post(self): """Create a new blobstore upload url for a student list. """ self.admin_required() self.render_json( {"url": blobstore.create_upload_url(config.PHOTO_CB_URL)})
class AssessmentUploadUrlHandler(ApiRequestHandler): path = assessment_resource.endpoint('/dashboard/uploadurl/assessments') @path.operation(type_='BlobStoreUploadInfo', alias='newExamUploadUrl', parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden") ]) def post(self): """Create a new blobstore upload url. """ self.admin_required() self.render_json( {"url": blobstore.create_upload_url(config.UPLOAD_EXAM_URL)})
class CurrentUserApi(ApiRequestHandler): """Handler request on user login status """ resource = api.resource( path="/user", desc="Operations about current user authentication") path = resource.endpoint('/dashboard/user') @path.operation(type_="User", alias="isloggedIn", parameters=[], responses=[ swagger.Message(200, "Ok"), ]) def get(self): """Return the user info if logged in or the url to login if the user is logged off. TODO: get and use user info instead of user nickname """ user = self.get_current_user() return_url = self.request.GET.get('returnUrl', config.DEFAULT_RETURN_URL) return_url = urllib.unquote(return_url) signed_return_url = utils.sign_return_url(return_url, self.session_id()) login_url = '%s?state=%s' % ( config.LOGIN_URL, signed_return_url, ) logout_url = '%s?state=%s' % ( config.LOGOUT_URL, signed_return_url, ) if not user: self.render_json({ 'isStudent': False, 'isStaff': False, 'isAdmin': False, 'isLoggedIn': False, 'loginUrl': login_url, }) return resp = user.details() resp['isLoggedIn'] = True resp['loginUrl'] = login_url resp['logoutUrl'] = logout_url self.render_json(resp)
class RepositoryDocumentApi(ApiRequestHandler): """Handle requests on a document """ path = resource.endpoint( '/dashboard/repository/<studentId>/files/<fileId>') @path.operation( type_="DocumentList", alias="deleteDocument", parameters=[ swagger.String(name="studentId", param_type="path", description="Id of student details to edit", required=True), swagger.String(name="fileId", param_type="path", description="Id of the document to delete", required=True) ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found") ]) def delete(self, studentId, fileId): """Delete a document. """ self.admin_required() student = Student.get_by_id(studentId) document = models.Document.get_by_id(fileId) if (not student or not document or document.dest_ref.id() != student.key.id()): self.abort(404) document.delete() self.render_json({'success': True})
class StudentUploadResultApi(ApiRequestHandler): path = student_resource.endpoint('/dashboard/uploadjob/students/<jobId>') @path.operation(type_='JobResult', alias='getJobStatus', parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden") ]) def post(self, jobId): """Create a new blobstore upload url for a student list. """ self.staff_required() pipeline = ProcessNewStudent.from_id(jobId) if pipeline is None: self.abort(404) self.render_json({'id': jobId, 'completed': pipeline.has_finalized()})
class RoshReviewUserStatsApi(ApiRequestHandler): """Handle request for a student Rosh Review stats. """ path = roshreview_resource.endpoint( '/dashboard/roshreview/stats/<studentId>') @path.operation( type_='RoshReviewUserStats', alias='getRoshReviewStats', # TODO: add filters parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found") ]) def get(self, studentId): """Get the detailed stats for a student. """ student_id = studentId.upper() current_user = self.login_required() if current_user.student_id != student_id: self.staff_required() stats = RoshReviewUserStats.get_by_id(student_id) if stats is None: self.abort(404) details = stats.details() today = datetime.date.today() details['history'] = [{ 'performance': 0, 'date': (today - datetime.timedelta(days=i)).isoformat() } for i in reversed(range(183))] self.render_json(details)
class StudentApi(ApiRequestHandler): """Handle request for a specific student. """ path = student_resource.endpoint("/dashboard/students/<studentId>") @path.operation(type_="Student", alias="getStudent", parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found"), ]) def get(self, studentId): """Return the student details. """ self.staff_required() student = models.Student.get_by_id(studentId) if student is None: self.abort(404) self.render_json(student.details()) @path.operation(type_="Student", alias="deleteStudent", parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found"), ]) def delete(self, studentId): """Delete a student. """ self.staff_required() student = models.Student.get_by_id(studentId) if student is None: self.abort(404) student.key.delete() StudentListApi.reset_list_cache() self.render_json({})
class FirstAidTopicListApi(ApiRequestHandler): """Handle request on Topic list resource """ path = firstaid_resource.endpoint('/dashboard/firstaid/topics') @path.operation(type_="TopicList", alias="listFirstAidTopics", parameters=[], responses=[swagger.Message(200, "Ok")]) def get(self): """Return the list of topic. """ self.response.cache_control = 'public' self.response.cache_control.max_age = 300 self.render_json({ "type": "topics", "topics": [t.summary() for t in FirstAidUserStats.get_topics()] })
class PGYListApi(ApiRequestHandler): """Handle request of PGY list resource """ path = pgy_resource.endpoint('/dashboard/pgy') @path.operation(type_="PGYList", alias="listPgy", parameters=[], responses=[swagger.Message(200, "Ok")]) def get(self): """Return the list of PGY. """ self.response.cache_control = 'public' self.response.cache_control.max_age = 300 self.render_json({ "pgy": [{ "id": year.year, "label": "Year %s" % year.year, "isActive": year.is_active } for year in models.Student.get_pgys()] })
class TopicListApi(ApiRequestHandler): """Handle request of PGY list resource """ path = roshreview_resource.endpoint('/dashboard/roshreview/topic') @path.operation(type_="TopicList", alias="listReviewTopics", parameters=[], responses=[swagger.Message(200, "Ok")]) def get(self): """Return the list of PGY. """ self.response.cache_control = 'public' self.response.cache_control.max_age = 300 self.render_json({ "type": "topics", "topics": [{ "id": topic, "label": topic.title() } for topic in RoshReviewUserStats.get_topics()] })
class FirstAidStatsApi(ApiRequestHandler): """Handle request for First Aid stats listing. """ path = firstaid_resource.endpoint('/dashboard/firstaid/stats') @path.operation( type_='FirstAidUserStatsList', alias='listFirstAidStats', # TODO: add filters parameters=[ swagger.String(name="cursor", description="Cursor to query the next page", param_type="query") ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found") ]) def get(self): """List student stats. """ self.staff_required() cursor_key = self.request.GET.get('cursor') topic_id = self.request.GET.get('topic') sort_by = self.request.GET.get('sortBy') if topic_id == 'all': topic_id = None sort_by_options = { 'performance': 'performance', 'questionTaken': 'question_taken' } sort_by = sort_by_options.get(sort_by, 'performance') try: limit = int(self.request.GET.get('limit')) except ( ValueError, TypeError, ): limit = None try: residents = self.request.GET.get('residents') if residents == 'all': residents = None else: residents = int(residents) except ( ValueError, TypeError, ): residents = None stats, cursor = FirstAidUserStats.get_stats(cursor_key=cursor_key, limit=limit, year=residents, topic_id=topic_id, sort_by=sort_by) self.render_json({ 'stats': [s.summary() for s in stats], 'cursor': cursor if cursor else '' })
class AdminApi(ApiRequestHandler): """Handle request on a admin user """ path = admin_resource.endpoint("/dashboard/admin/<userId:\d+>") @path.operation(type_="User", alias="makeAdmin", parameters=[ swagger.String(name="userId", param_type="path", description="Id of user to make admin", required=True) ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found"), ]) def put(self, userId): """Flag a user as an staff. """ self.admin_required() user_id = int(userId) user = models.User.get_by_id(user_id) if user is None: self.abort(404) models.User.make_admin(user_id) self.render_json({}) @path.operation( type_="User", alias="revokeAdmin", parameters=[ swagger.String( name="userId", param_type="path", description="Id of user to revoke admin permission from", required=True) ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), swagger.Message(404, "Not Found"), ]) def delete(self, userId): """Remove staff flag from a user. """ self.admin_required() user_id = int(userId) user = models.User.get_by_id(user_id) if user is None: self.abort(404) if user.is_domain_admin: self.abort(400) models.User.revoke_admin(user_id) self.render_json({})
class StudentListApi(ApiRequestHandler): """Handle student list resource. """ path = student_resource.endpoint("/dashboard/students") @path.operation(type_="StudentList", alias="listStudents", parameters=[ swagger.String( name="cursor", description="Cursor to query the next page", param_type="query") ], responses=[ swagger.Message(200, "Ok"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), ]) def get(self): """List all students (20 per page). The current user must be logged in as an app admin to see the list of student. """ self.staff_required() cursor_key = self.request.GET.get('cursor') limit = self.request.GET.get('limit') name = self.request.GET.get('name', '') raw_years = self.request.GET.getall('years') if limit is not None: try: limit = int(limit) except (ValueError, TypeError): limit = None years = [] for y in raw_years: try: years.append(int(y)) except ( ValueError, TypeError, ): pass props = { "limit": limit, "name": name.lower(), "years": years, "cursor_key": cursor_key } if limit is 0: students = self.get_list() cursor = None else: students, cursor = models.Student.get_students(**props) return self.render_json({ 'type': 'students', 'students': students, 'cursor': cursor if cursor else '' }) cache_ttl = 60 * 60 @staticmethod def cache_key(): return 'OEPSTUDENT_STUDENT_LIST' @classmethod def get_list(cls, **kw): key = cls.cache_key() cache = memcache.get(key) if cache is not None: return cache students, _ = models.Student.get_students(cursor_key=None, limit=0) memcache.set(key, students, time=cls.cache_ttl) return students @classmethod def reset_list_cache(cls): memcache.delete(cls.cache_key()) @path.operation(type_="Student", alias="newStudent", parameters=[], responses=[ swagger.Message(200, "Ok"), swagger.Message(400, "Bad Request"), swagger.Message(401, "Unauthorized"), swagger.Message(403, "Forbidden"), ]) def post(self): """Create a new Student """ self.admin_required() try: payload = json.loads(self.request.body) student = models.Student.new_student( payload['name']['givenName'], payload['displayName'], family_name=payload['name']['familyName'], email=payload['secondaryEmail'], student_id=payload['studentId'], year=payload['year']) except (ValidationError, ValueError, AttributeError, KeyError): self.abort(400) return self.render_json(student.details())