def post(self): """ CREATE a project where sessions can be created """ current_user = get_jwt_identity() user = User.query.filter_by(email=current_user).first() helpers.abort_if_unknown_user(user) # Force request to JSON, and fail silently if that fails the data is None. json_data = helpers.jsonify_request_or_abort() schema = ProjectPostSchema() helpers.abort_if_errors_in_validation(schema.validate(json_data)) data = schema.dump(json_data) from ..utils import amazon # TODO: we currently only support creating English projects english_lang = SupportedLanguage.query.filter_by(code='en').first() project = ProjectModel( default_lang=english_lang. id, # TODO: this should be the one they selected, but for now is EN creator=user.id, image=amazon.upload_base64(data['image']), is_public=data['privacy'] == 'public') admin_role = Roles.query.filter_by(name='administrator').first().id membership = Membership(uid=user.id, pid=project.id, rid=admin_role, confirmed=True) project.members.append(membership) db.session.add(project) db.session.flush() Project().add_codebook(project.id, json_data['codebook']) content = json_data['content'].get('en', None) if not content: return custom_response(400, errors=['projects.UNSUPPORTED_LANGUAGE']) project.content.extend([ ProjectLanguage(pid=project.id, lid=english_lang.id, description=content['description'], title=content['title']) ]) project.topics.extend([ TopicLanguage(project_id=project.id, lang_id=english_lang.id, text=t['text']) for t in content['topics'] ]) db.session.commit() return custom_response(201, data=ProjectModelSchema().dump(project))
def delete(self, pid): helpers.abort_on_unknown_project_id(pid) user = User.query.filter_by(email=get_jwt_identity()).first() helpers.abort_if_unknown_user(user) helpers.abort_if_not_admin_or_staff(user, pid, action="projects.DELETE") ProjectModel.query.filter_by(id=pid).update({'is_active': False}) db.session.commit() return custom_response(200)
def put(self, pid): """ The project to UPDATE: expecting a whole Project object to be sent. """ helpers.abort_on_unknown_project_id(pid) user = User.query.filter_by(email=get_jwt_identity()).first() helpers.abort_if_unknown_user(user) helpers.abort_if_not_admin_or_staff(user, pid) json_data = helpers.jsonify_request_or_abort() json_data['id'] = pid json_data['creator'] = user.id schema = ProjectModelSchema() errors = schema.validate(json_data) helpers.abort_if_errors_in_validation(errors) # When the project is updated, only the image data (base-64) is sent if it has changed. if json_data.get('image', None): from ..utils import amazon json_data['image'] = amazon.upload_base64(json_data['image']) project = ProjectModel.query.get(pid) # TODO: it's unclear why schema.load does not load image correctly, hence needing to manually set it. project.image = json_data['image'] if json_data.get( 'image', None) else project.image project.is_public = json_data['privacy'] == 'public' # Loads project data: relations are not loaded in their own schemas data = schema.load(json_data, instance=project) self.add_codebook(project.id, json_data['codebook']) # Note: it may be better to move this to schema's pre-load for language, content in json_data['content'].items(): # As the title may have changed, we must create a new slug content['slug'] = slugify(content['title']) # Overrides the title/description for the specific language that has changed plang = ProjectLanguageSchema().load( content, instance=data.content.filter_by(id=content['id']).first()) for topic in content['topics']: # Updates the topic if it changes, otherwise adds a new topic if 'id' in topic: TopicLanguageSchema().load(topic, instance=data.content.filter_by( id=topic['id']).first()) else: new_topic = TopicLanguage(project_id=project.id, lang_id=plang.lang_id, text=topic['text']) db.session.add(new_topic) # Changes are stored in memory; if error occurs, wont be left with half-changed state. db.session.commit() return custom_response(200, schema.dump(data))
def validate_and_get_data(project_id): """ Helper method as PUT/DELETE required the same validation. """ helpers.abort_if_unauthorized(Project.query.get(project_id)) user = User.query.filter_by(email=get_jwt_identity()).first() helpers.abort_if_unknown_user(user) helpers.abort_if_not_admin_or_staff(user, project_id, "membership.INVITE") data = helpers.jsonify_request_or_abort() return user, data
def get(): """ The projects the JWT user is a member of, otherwise all public projects. """ current_user = get_jwt_identity() if current_user: user = User.query.filter_by(email=current_user).first() helpers.abort_if_unknown_user(user) projects = ProjectModel.query.filter( or_(ProjectModel.members.any(Membership.user_id == user.id), ProjectModel.is_public)).order_by( ProjectModel.id.desc()).all() # Pass optional argument to show more details of members if the user is an admin.creator of the project. return custom_response( 200, data=ProjectModelSchema(many=True, user_id=user.id).dump(projects)) else: projects = ProjectModel.query.filter_by(is_public=True).order_by( ProjectModel.id.desc()).all() return custom_response( 200, data=ProjectModelSchema(many=True).dump(projects))
def delete(self, pid, mid=None): """ Removes a user and emails them that they have been removed from a project and by whom. Mapped to: /api/project/<int:id>/membership/invites/<int:mid> """ if not mid: raise CustomException(400, errors=['membership.NOT_EXISTS']) helpers.abort_if_unauthorized(Project.query.get(pid)) admin = User.query.filter_by(email=get_jwt_identity()).first() helpers.abort_if_unknown_user(admin) helpers.abort_if_not_admin_or_staff(admin, pid, "membership.INVITE") membership = Membership.query.filter_by(id=mid).first() if not membership: raise CustomException(400, errors=['membership.UNKNOWN']) elif membership.deactivated: raise CustomException(400, errors=['membership.USER_DEACTIVATED']) membership.deactivated = True db.session.commit() return custom_response(200, data=ProjectMemberWithAccess().dump(membership))
def get(self, pid): """ The public/private projects for an authenticated user. :param pid: The ID of the project to VIEW :return: A dictionary of public (i.e. available to all users) and private (user specific) projects. """ helpers.abort_on_unknown_project_id(pid) project = ProjectModel.query.filter_by(id=pid).first() helpers.abort_if_unknown_project(project) if project.is_public: return custom_response(200, ProjectModelSchema().dump(project)) current_user = get_jwt_identity() if current_user: user = User.query.filter_by(email=current_user).first() helpers.abort_if_unknown_user(user) helpers.abort_if_not_a_member_and_private(user, project) return custom_response( 200, ProjectModelSchema(user_id=user.id).dump(project)) # If the user is not authenticated and the project is private return custom_response(200, errors=['PROJECT_DOES_NOT_EXIST'])
def post(self, pid): """ CREATES a new session: only members of projects can upload to private projects. Anyone can upload to public projects as long as they are logged in via JWT; :param pid: the project to CREATE a new session for :return: the session serialized """ user = User.query.filter_by(email=get_jwt_identity()).first() helpers.abort_if_unknown_user(user) project = Project.query.get(pid) helpers.abort_if_unknown_project(project) helpers.abort_if_not_a_member_and_private(user, project) # NOTE The request is a multi-form request from the mobile device, and hence data needs # to be validated through RequestParser and converted to JSON before serializing. from werkzeug.datastructures import FileStorage parser = reqparse.RequestParser() parser.add_argument( 'recording', location='files', type=FileStorage, required=True, help="An audio recording is required, ideally encoded as MP4.") parser.add_argument( 'participants', required=True, help= "A dictionary of participants in the interview is required, i.e. who took part?" ) parser.add_argument( 'prompts', required=True, help= "A dictionary of prompts that were selected during the interview is required" ) parser.add_argument( 'consent', required=True, help= "The type of consent participants selected within the mobile application. This can" "be either everyone, members or private") parser.add_argument( 'created_on', required=True, help="The creation time of the conversation in UTC.") parser.add_argument('lang', required=True, help="The language spoken during the recording.") args = parser.parse_args() prompts = self.validate_and_serialize( args['prompts'], 'prompts', RecordingAnnotationSchema(many=True)) participants = self.validate_and_serialize( args['participants'], 'participants', ParticipantScheme(many=True)) interview_session_id = uuid4().hex lang_id = args['lang'] from datetime import datetime created_on = datetime.strptime(args['created_on'], "%m/%d/%Y %H:%M:%S") interview_session = InterviewSession(id=interview_session_id, lang_id=lang_id, creator_id=user.id, project_id=pid, created_on=created_on) self.__upload_interview_recording(args['recording'], interview_session_id, pid) self.__transcode_recording(interview_session_id, pid) interview_session.prompts.extend( self.__add_structural_prompts(prompts, interview_session_id)) interview_session.participants.extend( self.__add_participants(participants, interview_session_id, project.id, lang_id)) interview_session.consents.extend( self.__create_consent(interview_session.participants, interview_session.id, args['consent'])) db.session.add(interview_session) db.session.commit() # Once the session is saved, generate tokens as they require knowing the consent ID. # Storing tokens will allow users to edit their consent through the website and prevents # us regenerating a new consent token each time. from ..api.consent import SessionConsent for consent in InterviewSession.query.get( interview_session_id).consents.all(): consent.token = SessionConsent.generate_invite_token(consent.id) db.session.commit() send_mail = MailClient(interview_session.lang_id) # The conversation language spoken may not be a project configuration, therefore # We will use the language of the topic configuration. This could lead to: # Conversation is in English, yet topics were viewed in Italian. lang = TopicLanguage.query.get(prompts[0]['PromptID']).lang_id title = project.content.filter_by(lang_id=lang).first().title for participant in participants: send_mail.consent(participant, self.names(participants), title, interview_session.id, args['consent']) return {}, 201