Пример #1
0
class ApiResource(utils.Resource):

    args = {}
    model = None
    schema = None
    page_schema = None
    index_column = None
    unique_column = None
    filter_match_fields = []
    filter_multi_fields = []
    filter_range_fields = []
    filter_fulltext_fields = []
    filter_overlap_fields = []
    query_options = []
    join_columns = {}
    aliases = {}
    cap = 100
    use_estimated_counts = True
    estimated_count_threshold = 500000
    use_pk_for_count = False

    @use_kwargs(Ref('args'))
    @marshal_with(Ref('page_schema'))
    def get(self, *args, **kwargs):
        query = self.build_query(*args, **kwargs)
        count, _ = counts.get_count(self, query)
        multi = False
        if isinstance(kwargs['sort'], (list, tuple)):
            multi = True

        return utils.fetch_page(
            query,
            kwargs,
            count=count,
            model=self.model,
            join_columns=self.join_columns,
            aliases=self.aliases,
            index_column=self.index_column,
            cap=self.cap,
            multi=multi,
        )

    def build_query(self, *args, _apply_options=True, **kwargs):
        query = self.model.query
        query = filters.filter_match(query, kwargs, self.filter_match_fields)
        query = filters.filter_multi(query, kwargs, self.filter_multi_fields)
        query = filters.filter_range(query, kwargs, self.filter_range_fields)
        query = filters.filter_fulltext(query, kwargs,
                                        self.filter_fulltext_fields)
        query = filters.filter_overlap(query, kwargs,
                                       self.filter_overlap_fields)
        if _apply_options:
            query = query.options(*self.query_options)
        return query
Пример #2
0
class ApiResource(utils.Resource):

    args = {}
    model = None
    schema = None
    page_schema = None
    index_column = None
    unique_column = None
    filter_match_fields = []
    filter_multi_fields = []
    filter_range_fields = []
    filter_fulltext_fields = []
    query_options = []
    join_columns = {}
    aliases = {}
    cap = 100

    @use_kwargs(Ref('args'))
    @marshal_with(Ref('page_schema'))
    def get(self, *args, **kwargs):
        query = self.build_query(*args, **kwargs)
        count = counts.count_estimate(query,
                                      models.db.session,
                                      threshold=500000)
        return utils.fetch_page(
            query,
            kwargs,
            count=count,
            model=self.model,
            join_columns=self.join_columns,
            aliases=self.aliases,
            index_column=self.index_column,
            cap=self.cap,
        )

    def build_query(self, *args, _apply_options=True, **kwargs):
        query = self.model.query
        query = filters.filter_match(query, kwargs, self.filter_match_fields)
        query = filters.filter_multi(query, kwargs, self.filter_multi_fields)
        query = filters.filter_range(query, kwargs, self.filter_range_fields)
        query = filters.filter_fulltext(query, kwargs,
                                        self.filter_fulltext_fields)
        if _apply_options:
            query = query.options(*self.query_options)
        return query

    def filter_fulltext(self, query, kwargs):
        for key, column in self.filter_fulltext_fields:
            if kwargs.get(key):
                query = utils.search_text(query, column, kwargs[key])
        return query
Пример #3
0
class CrudResource(metaclass=ResourceMeta):

    schema = None

    @marshal_with(Ref('schema'), code=200)
    def get(self, id):
        pass

    @marshal_with(Ref('schema'), code=200)
    @marshal_with(Ref('schema'), code=201)
    def post(self):
        pass

    @marshal_with(Ref('schema'), code=200)
    def put(self, id):
        pass

    @marshal_with(None, code=204)
    def delete(self, id):
        pass
Пример #4
0
class CategoryListResource(BaseResource):
    schema = CategorySchema
    tags = ['categories']

    @marshal_with(CategorySchema(many=True))
    def get(self):
        return Category.query.all()

    @use_kwargs(Ref('schema'))
    def post(self, **kwargs):
        category = Category()
        for key, value in kwargs.items():
            setattr(category, key, value)
        db.session.add(category)
        db.session.commit()
        return category, 201
Пример #5
0
class PostListResource(BaseResource):
    schema = PostSchema
    tags = ['posts']

    def get(self):
        queryset = Post.query.order_by(Post.timestamp.desc())
        return make_pagination(queryset, 'api.posts', self.schema)

    @use_kwargs(Ref('schema'))
    def post(self, **kwargs):
        if not kwargs.get('is_valid', True):
            return jsonify(kwargs['error'].message), 422
        post = Post()
        for key, value in kwargs.items():
            setattr(post, key, value)
        db.session.add(post)
        db.session.commit()
        return post, 201
Пример #6
0
class CommentListResource(BaseResource):
    schema = CommentSchema
    tags = ['comments']

    def get(self):
        post_id = request.args.get('post_id')
        queryset = Comment.query.filter_by(reviewed=True)
        if post_id:
            queryset = queryset.filter_by(post_id=post_id)
        queryset = queryset.order_by(Comment.timestamp.desc())
        return make_pagination(queryset, 'api.comments', self.schema)

    @use_kwargs(Ref('schema'))
    def post(self, **kwargs):
        comment = Comment()
        for key, value in kwargs.items():
            setattr(comment, key, value)
        db.session.add(comment)
        db.session.commit()
        return comment, 201
Пример #7
0
class CategoryResource(BaseResource):
    schema = CategorySchema
    tags = ['categories']
    params = {'category_id': {'description': '分类ID'}}

    def get(self, category_id):
        category = Category.query.get_or_404(category_id)
        return category

    def delete(self, category_id):
        category = Category.query.get_or_404(category_id)
        db.session.delete(category)
        db.session.commit()
        return '', 204

    @use_kwargs(Ref('schema'))
    def put(self, category_id, **kwargs):
        category = Category.query.get_or_404(category_id)
        for key, value in kwargs.items():
            setattr(category, key, value)
        db.session.commit()
        return category
Пример #8
0
class PostResource(BaseResource):
    schema = PostSchema
    tags = ['posts']
    params = {'post_id': {'description': '文章ID'}}

    def get(self, post_id):
        post = Post.query.get_or_404(post_id)
        return post

    def delete(self, post_id):
        post = Post.query.get_or_404(post_id)
        db.session.delete(post)
        db.session.commit()
        return None, 204

    @use_kwargs(Ref('schema'))
    def put(self, post_id, **kwargs):
        post = Post.query.get_or_404(post_id)
        for key, value in kwargs.items():
            setattr(post, key, value)
        db.session.commit()
        return post
Пример #9
0
class CommentResource(BaseResource):
    schema = CommentSchema
    tags = ['comments']
    params = {'comment_id': {'description': '评论ID'}}

    def get(self, comment_id):
        comment = Comment.query.get_or_404(comment_id)
        return comment

    def delete(self, comment_id):
        comment = Comment.query.get_or_404(comment_id)
        db.session.delete(comment)
        db.session.commit()
        return '', 204

    @use_kwargs(Ref('schema'))
    def put(self, comment_id, **kwargs):
        comment = Comment.query.get_or_404(comment_id)
        for key, value in kwargs.items():
            setattr(comment, key, value)
        db.session.commit()
        return comment
class ChassisResource(MethodResource):
    schema = Schema.from_dict(dict())
    response_schema = Schema.from_dict(dict())

    def __init__(self, app, db, schema, record_name=None, logger_service: LoggerService = None,
                 resource_protector: CustomResourceProtector = None, update_scope: Scope = None,
                 fetch_scope: Scope = None, delete_scope: Scope = None, update_permissions=None,
                 fetch_permissions=None, delete_permissions=None):
        self.app = app
        self.service = ChassisService(app, db, schema.Meta.model)
        self.db = db
        if record_name is None:
            self.record_name = "Resource"
        else:
            self.record_name = record_name

        self.schema = schema

        class ResponseSchema(ResponseWrapper):
            data = fields.Nested(schema)

        class RecordPageSchema(DjangoPageSchema):
            results = fields.List(fields.Nested(schema))

        self.response_schema = ResponseSchema()
        self.page_response_schema = RecordPageSchema()
        self.logger_service = logger_service
        self.resource_protector = resource_protector
        self.update_scopes = update_scope
        self.fetch_scopes = fetch_scope
        self.delete_scopes = delete_scope
        self.update_permissions = update_permissions
        self.fetch_permissions = fetch_permissions
        self.delete_permissions = delete_permissions

    @doc(description="View Record")
    @marshal_with(Ref("schema"), code=200)
    @marshal_with(error_response, code=404)
    def get(self, **kwargs):
        """
        Fetch record using id
        :return: area details on success or error 404 status if area doesn't exist
        """
        if self.resource_protector:
            self.app.logger.debug("Resource protector is present handling authorization")
            authenticate(self.resource_protector, self.fetch_scopes, self.fetch_permissions)
        record_id = None
        for key, value in kwargs.items():
            record_id = value
            break
        primary_column = get_primary_key(self.schema.Meta.model)
        filters = {primary_column.name: record_id}
        if hasattr(self.schema.Meta.model, "is_deleted"):
            filters["is_deleted"] = False
        record = self.schema.Meta.model.query.filter_by(**filters).first()
        if record is None:
            self.app.logger.error("Failed to find record with id %s", record_id)
            return {"status": 404, "errors": {"detail": "Record doesn't exist"}}, 404
        else:
            return record

    # @require_oauth("location.manage_areas", has_any_authority=["change_area"])
    @doc(description="Update Record")
    @use_kwargs(Ref("schema"))
    @marshal_with(Ref("schema"), code=200)
    @marshal_with(val_error_response, code=400, description="Validation errors")
    @marshal_with(error_response, code=404, description="Record doesn't exist")
    def patch(self, *args, **kwargs):
        """
        Updates records
        :param args: additional arguments
        """
        record_id = None
        # Get record id
        for key, value in kwargs.items():
            record_id = value
            break
        # Get payload
        payload = None
        for arg in args:
            if isinstance(arg, self.schema.Meta.model):
                payload = arg
                break
        self.app.logger.info(f"Updating {self.record_name}. Payload: %s.", payload)
        token = None
        if self.resource_protector:
            self.app.logger.debug("Resource protector is present handling authorization")
            token = authenticate(self.resource_protector, self.update_scopes, self.update_permissions)
        try:
            validate_foreign_keys(payload, self.db)
            validate_unique_constraints(payload, self.db, record_id)
            # attrs = inspect.getmembers(payload, lambda a: not (inspect.isroutine(a)))
            # for attr in attrs:
            #     print(attr)
            record = self.service.update(payload, record_id)
            if self.logger_service:
                self.logger_service.log_success_update(f"Updated {self.record_name} successfully",
                                                       payload.__class__, record_id, token=token)
            return record, 200
        except (ConflictError, ValidationError) as ex:
            if self.logger_service:
                self.logger_service.log_failed_update(f"Failed to update {self.record_name}. {ex.message}",
                                                      payload.__class__, record_id, token=token)
            return {"status": 400, "errors": [ex.message]}, 400
        # except ValidationError as ex:
        #     if self.logger_service:
        #         self.logger_service.log_failed_update(f"Failed to update {self.record_name}. {ex.message}",
        #                                               payload.__class__, record_id, token=token)
        #     return {"status": 400, "errors": {"detail": ex.message}}, 400

    @doc(description="Delete Record")
    @marshal_with(Schema(), code=204)
    @marshal_with(val_error_response, code=404, description="Record doesn't exist")
    def delete(self, *args, **kwargs):
        """
        Delete record
        :return: response with status 204 on success
        """
        record_id = None
        for key, value in kwargs.items():
            record_id = value
            break
        # payload = None
        # for arg in args:
        #     if isinstance(self.schema.Meta.model, arg):
        #         payload = arg
        #         payload.id = record_id
        #         break
        self.app.logger.info(f"Deleting {self.record_name} with id %s", record_id)
        token = None
        if self.resource_protector:
            self.app.logger.debug("Resource protector is present handling authorization")
            token = authenticate(self.resource_protector, self.delete_scopes, self.delete_permissions)
        try:
            self.service.delete(record_id)
            if self.logger_service:
                self.logger_service.log_success_deletion(f"Deleted {self.record_name} successfully",
                                                         self.schema.Meta.model.__class__, record_id, token=token)
            return {}, 204
        except ValidationError as ex:
            if self.logger_service:
                self.logger_service.log_failed_deletion(f"Failed to delete {self.record_name}. {ex.message}",
                                                        self.schema.Meta.model.__class__, record_id, token=token)
            return {"errors": [
                "Record doesn't exist"
            ], "status": 404}, 404
class ChassisResourceList(MethodResource):
    schema = Schema.from_dict(dict())
    # response_schema = Schema.from_dict(dict())
    page_response_schema = Schema.from_dict(dict())
    fetch_schema = None

    def __init__(self, app, db, schema, record_name=None, logger_service: LoggerService = None,
                 resource_protector: CustomResourceProtector = None, create_scope: Scope = None,
                 fetch_scope: Scope = None, create_permissions=None, fetch_permissions=None):
        """

        :param app: Flask application reference
        :param db: Flask SQLAlchemy reference
        :param schema: Current model Marshmallow Schema with model reference
        """
        self.app = app
        self.service = ChassisService(app, db, schema.Meta.model)
        self.db = db
        if record_name is None:
            self.record_name = "Resource"
        else:
            self.record_name = record_name

        self.schema = schema

        # class ResponseSchema(ResponseWrapper):
        #     data = fields.Nested(schema)

        class RecordPageSchema(DjangoPageSchema):
            results = fields.List(fields.Nested(schema))

        # self.response_schema = ResponseSchema()
        self.page_response_schema = RecordPageSchema()
        self.logger_service = logger_service
        self.resource_protector = resource_protector
        self.create_scopes = create_scope
        self.fetch_scopes = fetch_scope
        self.create_permissions = create_permissions
        self.fetch_permissions = fetch_permissions
        # Fetch schema fields
        fetch_fields = dict(page_size=fields.Int(required=False), page=fields.Int(required=False),
                            ordering=fields.Str(required=False), q=fields.Str(required=False))
        if hasattr(self.schema.Meta.model, "created_at"):
            fetch_fields["created_after"] = fields.Date(required=False)
            fetch_fields["created_before"] = fields.Date(required=False)
        if hasattr(self.schema.Meta.model, "updated_at"):
            fetch_fields["updated_after"] = fields.Date(required=False)
            fetch_fields["updated_before"] = fields.Date(required=False)
        for column in getattr(self.schema.Meta.model, "__table__").c:
            if column.primary_key == False and column.name != "created_at" and column.name != "updated_at" and \
                    column.name != "is_deleted" and column.name != "created_by_id":
                fetch_fields[column.name] = fields.Str(required=False)

        self.fetch_schema = Schema.from_dict(fetch_fields)

    @marshal_with(Ref("schema"), code=201, description="Request processed successfully")
    @use_kwargs(Ref('schema'))
    def post(self, payload=None):
        self.app.logger.info("Creating new %s. Payload: %s", self.record_name, str(payload))
        token = None
        if self.resource_protector:
            self.app.logger.debug("Resource protector is present handling authorization")
            token = authenticate(self.resource_protector, self.create_scopes, self.create_permissions)
            if hasattr(payload, "created_by_id"):
                self.app.logger.debug("Found created by field populating session user id")
                setattr(payload, "created_by_id", token.get_user_id())
        # Validating foreign keys and unique constraints
        try:
            validate_foreign_keys(payload, self.db)
            validate_unique_constraints(payload, self.db)
        except ValidationError as ex:
            self.app.logger.debug(f"Failed to create entity {self.record_name}. {ex.message}")
            if self.logger_service:
                self.logger_service.log_failed_creation(f"Failed to create {self.record_name}. {ex.message}",
                                                        payload.__class__, token=token)
            return {"message": ex.message}, 400
        self.service.create(payload)
        if self.logger_service:
            self.logger_service.log_success_creation(f"Created {self.record_name} successfully", payload.__class__,
                                                     payload.id, token=token)
        return payload, 201

    @doc(description="View Records. Currently only supports one column sorting:"
                     "<ul>"
                     "<li>For ascending specify ordering parameter with column name</li>"
                     "<li>For descending specify ordering parameter with a negative sign on the column name e.g. "
                     "<b><i>ordering=-id</i></b></li> "
                     "</ul>")
    @marshal_with(Ref("page_response_schema"), code=200)
    @use_kwargs(Ref("fetch_schema"), location="query")
    def get(self, page_size=None, page=None, ordering=None, q=None, created_after=None, created_before=None,
            updated_after=None, updated_before=None, **kwargs):
        """
        Fetching records
        :param page_size: Pagination page size
        :param page: pagination page starting with 1
        :param ordering: Column ordering
        :param q: Search query param
        :param created_after: From creation date filter
        :param created_before: To creation date filter
        :param updated_after: From updated date filter
        :param updated_before: To updated date filter
        :return: A list of records
        """
        if page_size is None:
            page_size = 10
        if page is None:
            page = 1
        self.app.logger.info(f"Fetching {self.record_name}: Request size %s, page %s", page_size, page)
        if self.resource_protector:
            self.app.logger.debug("Resource protector is present handling authorization")
            authenticate(self.resource_protector, self.fetch_scopes, self.fetch_permissions)
        if hasattr(self.schema.Meta.model, "is_deleted"):
            query = self.schema.Meta.model.query.filter_by(is_deleted=False)
        else:
            query = self.schema.Meta.model.query
        # If q param exists search columns using q param
        if q:
            self.app.logger.debug("Found query param searching columns...")
            search_query = []
            for column in getattr(self.schema.Meta.model, "__table__").c:
                search_query.append(column.like('%' + q + "%"))
            query = query.filter(or_(*search_query))
        # Filter using creation date
        if (created_after or created_before) and hasattr(self.schema.Meta.model, "created_at"):
            self.app.logger.debug("Found created date filter. Filtering created from %s to %s",
                                  created_after, created_before)
            if created_before is None:
                query = query.filter(self.schema.Meta.model.created_at >= created_after)
            elif created_after is None:
                query = query.filter(self.schema.Meta.model.created_at <= created_before)
            else:
                query = query.filter(and_(self.schema.Meta.model.created_at >= created_after,
                                          self.schema.Meta.model.created_at <= created_before))
        # Filter using update date
        if (updated_after or updated_before) and hasattr(self.schema.Meta.model, "updated_at"):
            self.app.logger.debug("Found updated date filter. Filtering updated from %s to %s",
                                  updated_after, updated_before)
            if updated_before is None:
                query = query.filter(self.schema.Meta.model.updated_at >= updated_after)
            elif updated_after is None:
                query = query.filter(self.schema.Meta.model.updated_at <= updated_before)
            else:
                query = query.filter(and_(self.schema.Meta.model.updated_at >= updated_after,
                                          self.schema.Meta.model.updated_at <= updated_before))
        # Filtering using other columns
        if kwargs:
            query = query.filter_by(**kwargs)

        # Ordering query
        if ordering is not None:
            ordering = ordering.strip()
            if ordering[0] == "-":
                query = query.order_by(desc(ordering[1:]))
            else:
                query = query.order_by(asc(ordering))
        else:
            self.app.logger.debug("Ordering(%s) not specified skipping ordering", ordering)

        response = query.paginate(page=page, per_page=page_size)
        return {"count": response.total, "current_page": response.page, "page_size": response.per_page,
                "total_pages": response.pages, "results": response.items}
Пример #12
0
@file: resources.py
@time: 2019/06/17
@software: PyCharm
@detail: 资源
"""

from flask import jsonify, request
from flask_apispec.views import MethodResource
from flask_apispec import marshal_with, Ref, use_kwargs, doc

from bluelog.extensions import db
from .schemas import PostSchema, CategorySchema, CommentSchema
from .utils import make_pagination


@doc(tags=Ref('tags'), params=Ref('params'))
@marshal_with(Ref('schema'))
class BaseResource(MethodResource):
    schema = None
    tags = None
    params = None


class PostListResource(BaseResource):
    schema = PostSchema
    tags = ['posts']

    def get(self):
        queryset = Post.query.order_by(Post.timestamp.desc())
        return make_pagination(queryset, 'api.posts', self.schema)