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
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
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
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
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
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
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
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
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}
@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)