예제 #1
0
    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))
예제 #2
0
 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)
예제 #3
0
    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))
예제 #4
0
 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
예제 #5
0
 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))
예제 #6
0
    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))
예제 #7
0
    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'])
예제 #8
0
    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