def pagination(self):
        """Return all page parameters as a dict.

        :return dict: a dict of pagination information

        To allow multiples strategies, all parameters starting with `page` will be included. e.g::

            {
                "number": '25',
                "size": '150',
            }

        Example with number strategy::

            >>> query_string = {'page[number]': '25', 'page[size]': '10'}
            >>> parsed_query.pagination
            {'number': '25', 'size': '10'}
        """
        # check values type
        result = self._get_key_values('page')
        for key, value in result.items():
            if key not in ('number', 'size'):
                raise BadRequest(
                    {'parameter': 'page'},
                    "{} is not a valid parameter of pagination".format(key))
            try:
                int(value)
            except ValueError:
                raise BadRequest({'parameter': 'page[{}]'.format(key)},
                                 "Parse error")

        return result
Example #2
0
    def delete(self, *args, **kwargs):
        """Delete relationship(s)
        """
        json_data = request.get_json()

        relationship_field, related_type_, related_id_field = self._get_relationship_data()

        if 'data' not in json_data:
            raise BadRequest('/data', 'You must provide data with a "data" route node')
        if isinstance(json_data['data'], dict):
            if 'type' not in json_data['data']:
                raise BadRequest('/data/type', 'Missing type in "data" node')
            if 'id' not in json_data['data']:
                raise BadRequest('/data/id', 'Missing id in "data" node')
            if json_data['data']['type'] != related_type_:
                raise InvalidType('/data/type', 'The type field does not match the resource type')
        if isinstance(json_data['data'], list):
            for obj in json_data['data']:
                if 'type' not in obj:
                    raise BadRequest('/data/type', 'Missing type in "data" node')
                if 'id' not in obj:
                    raise BadRequest('/data/id', 'Missing id in "data" node')
                if obj['type'] != related_type_:
                    raise InvalidType('/data/type', 'The type provided does not match the resource type')

        obj_, updated = self.data_layer.delete_relationship(json_data, relationship_field, related_id_field, **kwargs)

        qs = QSManager(request.args, self.schema)
        schema = compute_schema(self.schema, dict(), qs, qs.include)

        status_code = 200 if updated is True else 204

        return schema.dump(obj_), status_code
Example #3
0
    def patch(self, *args, **kwargs):
        """Update a relationship"""
        json_data = request.get_json() or {}

        relationship_field, model_relationship_field, related_type_, related_id_field = self._get_relationship_data(
        )

        if 'data' not in json_data:
            raise BadRequest('You must provide data with a "data" route node',
                             source={'pointer': '/data'})
        if isinstance(json_data['data'], dict):
            if 'type' not in json_data['data']:
                raise BadRequest('Missing type in "data" node',
                                 source={'pointer': '/data/type'})
            if 'id' not in json_data['data']:
                raise BadRequest('Missing id in "data" node',
                                 source={'pointer': '/data/id'})
            if json_data['data']['type'] != related_type_:
                raise InvalidType(
                    'The type field does not match the resource type',
                    source={'pointer': '/data/type'})
        if isinstance(json_data['data'], list):
            for obj in json_data['data']:
                if 'type' not in obj:
                    raise BadRequest('Missing type in "data" node',
                                     source={'pointer': '/data/type'})
                if 'id' not in obj:
                    raise BadRequest('Missing id in "data" node',
                                     source={'pointer': '/data/id'})
                if obj['type'] != related_type_:
                    raise InvalidType(
                        'The type provided does not match the resource type',
                        source={'pointer': '/data/type'})

        self.before_patch(args, kwargs, json_data=json_data)

        obj_, updated = self._data_layer.update_relationship(
            json_data, model_relationship_field, related_id_field, kwargs)

        qs = QSManager(request.args, self.schema)
        includes = qs.include
        if relationship_field not in qs.include:
            includes.append(relationship_field)
        schema = compute_schema(self.schema, dict(), qs, includes)

        if updated is False:
            return '', 204

        result = schema.dump(obj_).data
        if result.get('links', {}).get('self') is not None:
            result['links']['self'] = request.path

        self.after_patch(result)

        return result, 200
Example #4
0
    def patch(self, *args, **kwargs):
        """Update an object"""
        json_data = request.get_json() or {}

        qs = QSManager(request.args, self.schema)
        schema_kwargs = getattr(self, 'patch_schema_kwargs', dict())
        schema_kwargs.update({'partial': True})

        self.before_marshmallow(args, kwargs)

        schema = compute_schema(self.schema,
                                schema_kwargs,
                                qs,
                                qs.include)

        try:
            data, errors = schema.load(json_data)
        except IncorrectTypeError as e:
            errors = e.messages
            for error in errors['errors']:
                error['status'] = '409'
                error['title'] = "Incorrect type"
            return errors, 409
        except ValidationError as e:
            errors = e.messages
            for message in errors['errors']:
                message['status'] = '422'
                message['title'] = "Validation error"
            return errors, 422

        if errors:
            for error in errors['errors']:
                error['status'] = "422"
                error['title'] = "Validation error"
            return errors, 422

        if 'id' not in json_data['data']:
            raise BadRequest('Missing id in "data" node',
                             source={'pointer': '/data/id'})
        if json_data['data']['id'] != str(kwargs[self.data_layer.get('url_field', 'id')]):
            raise BadRequest('Value of id does not match the resource identifier in url',
                             source={'pointer': '/data/id'})

        self.before_patch(args, kwargs, data=data)

        obj = self.update_object(data, qs, kwargs)

        result = schema.dump(obj).data

        self.after_patch(result)

        return result
Example #5
0
    def delete(self, *args, **kwargs):
        """Delete relationship(s)"""
        json_data = request.get_json() or {}

        relationship_field, model_relationship_field, related_type_, related_id_field = self._get_relationship_data(
        )

        if 'data' not in json_data:
            raise BadRequest('You must provide data with a "data" route node',
                             source={'pointer': '/data'})
        if isinstance(json_data['data'], dict):
            if 'type' not in json_data['data']:
                raise BadRequest('Missing type in "data" node',
                                 source={'pointer': '/data/type'})
            if 'id' not in json_data['data']:
                raise BadRequest('Missing id in "data" node',
                                 source={'pointer': '/data/id'})
            if json_data['data']['type'] != related_type_:
                raise InvalidType(
                    'The type field does not match the resource type',
                    source={'pointer': '/data/type'})
        if isinstance(json_data['data'], list):
            for obj in json_data['data']:
                if 'type' not in obj:
                    raise BadRequest('Missing type in "data" node',
                                     source={'pointer': '/data/type'})
                if 'id' not in obj:
                    raise BadRequest('Missing id in "data" node',
                                     source={'pointer': '/data/id'})
                if obj['type'] != related_type_:
                    raise InvalidType(
                        'The type provided does not match the resource type',
                        source={'pointer': '/data/type'})

        self.before_delete(args, kwargs, json_data=json_data)

        obj_, updated = self._data_layer.delete_relationship(
            json_data, model_relationship_field, related_id_field, kwargs)

        status_code = 200
        result = {'meta': {'message': 'Relationship successfully updated'}}

        if updated is False:
            result = ''
            status_code = 204

        final_result = self.after_delete(result, status_code)

        return final_result
Example #6
0
    def delete(self, *args, **kwargs):
        """Delete relationship(s)
        """
        self.before_delete(args, kwargs)

        json_data = request.get_json()

        relationship_field, model_relationship_field, related_type_, related_id_field = self._get_relationship_data(
        )

        if 'data' not in json_data:
            raise BadRequest('/data',
                             'You must provide data with a "data" route node')
        if isinstance(json_data['data'], dict):
            if 'type' not in json_data['data']:
                raise BadRequest('/data/type', 'Missing type in "data" node')
            if 'id' not in json_data['data']:
                raise BadRequest('/data/id', 'Missing id in "data" node')
            if json_data['data']['type'] != related_type_:
                raise InvalidType(
                    '/data/type',
                    'The type field does not match the resource type')
        if isinstance(json_data['data'], list):
            for obj in json_data['data']:
                if 'type' not in obj:
                    raise BadRequest('/data/type',
                                     'Missing type in "data" node')
                if 'id' not in obj:
                    raise BadRequest('/data/id', 'Missing id in "data" node')
                if obj['type'] != related_type_:
                    raise InvalidType(
                        '/data/type',
                        'The type provided does not match the resource type')

        obj_, updated = self._data_layer.delete_relationship(
            json_data, model_relationship_field, related_id_field, kwargs)

        qs = QSManager(request.args, self.schema)
        includes = qs.include
        if relationship_field not in qs.include:
            includes.append(relationship_field)
        schema = compute_schema(self.schema, dict(), qs, includes)

        status_code = 200 if updated is True else 204
        result = schema.dump(obj_).data
        if result.get('links', {}).get('self') is not None:
            result['links']['self'] = request.path
        self.after_delete(result)
        return result, status_code
    def _get_key_values(self, name):
        """Return a dict containing key / values items for a given key, used for items like filters, page, etc.

        :param str name: name of the querystring parameter
        :return dict: a dict of key / values items
        """
        results = {}

        for key, value in self.qs.items():
            try:
                if not key.startswith(name):
                    continue

                key_start = key.index('[') + 1
                key_end = key.index(']')
                item_key = key[key_start:key_end]

                if ',' in value:
                    item_value = value.split(',')
                else:
                    item_value = value
                results.update({item_key: item_value})
            except Exception:
                raise BadRequest("Parse error", source={'parameter': key})

        return results
Example #8
0
    def before_create_object(self, data, view_kwargs):
        """Prior to creating a new item, check all is OK and insert the
        user_id of the current user into the request, so that the foreign
        key for user_id is correctly s0tored with created item.

        Also stores the category_id of the request URL into the data storage
        used to set the foreign key of the item pointing to the category.
        """
        # pylint: disable=no-self-use

        # POST /api/v1/categories/<int:category_id>/items

        category_id = view_kwargs.get('category_id')

        if ('name' not in data and 'description' not in data):
            raise BadRequest('Must include id (of category), name and '
                             'description fields')

        try:
            category = Category.query.filter_by(id=category_id).one()
        except NoResultFound:
            raise ObjectNotFound({'parameter': 'category_id'},
                                 "Category with id: {} not found".format(
                                     data['category_id']))

        # set the foreign key to the logged in user
        data['user_id'] = g.current_user.id

        # set the foreign key for category
        data['category_id'] = category.id
Example #9
0
    def patch(self, *args, **kwargs):
        """Update an object
        """
        json_data = request.get_json()

        qs = QSManager(request.args, self.schema)
        schema_kwargs = getattr(self.opts, 'schema_patch_kwargs', dict())
        schema_kwargs.update({'partial': True})

        schema = compute_schema(self.schema,
                                schema_kwargs,
                                qs,
                                qs.include)

        try:
            data, errors = schema.load(json_data)
        except IncorrectTypeError as e:
            errors = e.messages
            for error in errors['errors']:
                error['status'] = '409'
                error['title'] = "Incorrect type"
            return errors, 409
        except ValidationError as e:
            errors = e.messages
            for message in errors['errors']:
                message['status'] = '422'
                message['title'] = "Validation error"
            return errors, 422

        if errors:
            for error in errors['errors']:
                error['status'] = "422"
                error['title'] = "Validation error"
            return errors, 422

        if 'id' not in json_data['data']:
            raise BadRequest('/data/id', 'Missing id in "data" node')
        if json_data['data']['id'] != str(kwargs[getattr(self.data_layer, 'url_field', 'id')]):
            raise BadRequest('/data/id', 'Value of id does not match the resource identifier in url')

        obj = self.data_layer.get_object(**kwargs)
        updated = self.data_layer.update_object(obj, data, **kwargs)

        result = schema.dump(obj)

        status_code = 200 if updated is True else 204
        return result.data, status_code
    def pagination(self):
        """Return all page parameters as a dict.

        :return dict: a dict of pagination information

        To allow multiples strategies, all parameters starting with `page` will be included. e.g::

            {
                "number": '25',
                "size": '150',
            }

        Example with number strategy::

            >>> query_string = {'page[number]': '25', 'page[size]': '10'}
            >>> parsed_query.pagination
            {'number': '25', 'size': '10'}
        """
        # check values type
        result = self._get_key_values('page')
        for key, value in result.items():
            if key not in ('number', 'size'):
                raise BadRequest(
                    "{} is not a valid parameter of pagination".format(key),
                    source={'parameter': 'page'})
            try:
                int(value)
            except ValueError:
                raise BadRequest("Parse error",
                                 source={'parameter': 'page[{}]'.format(key)})

        if current_app.config.get(
                'ALLOW_DISABLE_PAGINATION', True) is False and int(
                    result.get('size', 1)) == 0:
            raise BadRequest("You are not allowed to disable pagination",
                             source={'parameter': 'page[size]'})

        if current_app.config.get(
                'MAX_PAGE_SIZE') is not None and 'size' in result:
            if int(result['size']) > current_app.config['MAX_PAGE_SIZE']:
                raise BadRequest("Maximum page size is {}".format(
                    current_app.config['MAX_PAGE_SIZE']),
                                 source={'parameter': 'page[size]'})

        return result
Example #11
0
    def before_create_object(self, data, unused_view_kwargs):
        """Prior to creating a new user, check all is OK"""
        # POST /users
        if ('email' not in data or 'password' not in data
                or 'first_name' not in data or 'last_name' not in data):
            raise BadRequest('Must include email, password, first_name and'
                             'last_name fields')

        user = self.session.query(User).filter_by(email=data['email']).first()
        if user:
            if user.blocked:
                raise JsonApiException(detail='Account has been blocked.'
                                       'Contact the site administrator.',
                                       title='Permission denied',
                                       status='403')
            else:
                raise BadRequest('Email {} already registered'.format(
                    data['email']))
Example #12
0
    def before_create_object(self, data, unused_view_kwargs):
        """Prior to creating a new category, check all is OK and insert the
        user_id of the current user into the request, so that the foreign
        key for user_id is correctly stored with created category
        """
        # pylint: disable=no-self-use

        # POST /api/v1/categories

        if 'name' not in data:
            raise BadRequest('Must include name field')

        # set the foreign key to the logged in user
        data['user_id'] = g.current_user.id