forked from oyd/Adunatio
parent
7ef1ae60b1
commit
2dbc9bc23d
@ -1,18 +0,0 @@ |
|||||||
class Get: |
|
||||||
method = 'GET' |
|
||||||
|
|
||||||
|
|
||||||
class List: |
|
||||||
method = 'GET' |
|
||||||
|
|
||||||
|
|
||||||
class Delete: |
|
||||||
method = 'DELETE' |
|
||||||
|
|
||||||
|
|
||||||
class Update: |
|
||||||
method = 'PUT' |
|
||||||
|
|
||||||
|
|
||||||
class Create: |
|
||||||
method = 'POST' |
|
@ -1,43 +0,0 @@ |
|||||||
from urllib.parse import urljoin |
|
||||||
|
|
||||||
from flask import Flask |
|
||||||
from mongoengine import Document |
|
||||||
|
|
||||||
from restapi.views import ApiView |
|
||||||
from restapi import Methods |
|
||||||
|
|
||||||
|
|
||||||
class MongoApi: |
|
||||||
app: Flask = None |
|
||||||
|
|
||||||
def __init__(self, app: Flask): |
|
||||||
self.app = app |
|
||||||
|
|
||||||
def register_model(self, model: any, uri: str = None, name: str = None) -> None: |
|
||||||
if not getattr(model, '_meta').get('methods'): |
|
||||||
raise Exception("{} model methods not set".format(model.__name__)) |
|
||||||
if not uri: |
|
||||||
uri = model.__name__.lower() |
|
||||||
if uri[:-1] != "/": |
|
||||||
uri = uri + "/" |
|
||||||
if uri[0] != "/": |
|
||||||
uri = "/" + uri |
|
||||||
if not name: |
|
||||||
name = model.__name__ |
|
||||||
self.app.add_url_rule( |
|
||||||
uri, |
|
||||||
methods=(method.method for method in getattr(model, '_meta').get('methods') if |
|
||||||
method not in (Methods.Get, Methods.Update)), |
|
||||||
view_func=ApiView.as_view(name, model=model)) |
|
||||||
|
|
||||||
single = [] |
|
||||||
if Methods.Update in getattr(model, '_meta').get('methods'): |
|
||||||
single.append('PUT') |
|
||||||
|
|
||||||
if Methods.Get in getattr(model, '_meta').get('methods'): |
|
||||||
single.append('GET') |
|
||||||
if single: |
|
||||||
self.app.add_url_rule( |
|
||||||
urljoin(uri, '<pk>/'), |
|
||||||
methods=single, |
|
||||||
view_func=ApiView.as_view("{}_GET_PUT".format(name), model=model)) |
|
@ -1,9 +0,0 @@ |
|||||||
from flask import request |
|
||||||
from flask_jwt_extended.utils import verify_token_claims |
|
||||||
from flask_jwt_extended.view_decorators import _decode_jwt_from_request |
|
||||||
|
|
||||||
|
|
||||||
def get_jwt_from_request(): |
|
||||||
jwt_data, jwt_header = _decode_jwt_from_request(request_type='access') |
|
||||||
verify_token_claims(jwt_data) |
|
||||||
return jwt_data['identity'] |
|
@ -1,212 +0,0 @@ |
|||||||
from bson import ObjectId |
|
||||||
from flask import request |
|
||||||
from mongoengine import Document, ObjectIdField, ListField, \ |
|
||||||
EmbeddedDocumentField, ReferenceField, ImageField, FileField, DecimalField, QuerySet |
|
||||||
from base64 import b64encode |
|
||||||
|
|
||||||
from mongoengine.base import BaseField |
|
||||||
|
|
||||||
|
|
||||||
class Resource: |
|
||||||
model: Document = None |
|
||||||
qs: dict = {} |
|
||||||
eqs: dict = {} |
|
||||||
build_reference: bool = False |
|
||||||
base64_image: bool = True |
|
||||||
base64_image_as_full: bool = False |
|
||||||
limit: int = 10 |
|
||||||
offset: int = 0 |
|
||||||
fields: list = [] |
|
||||||
ignore_fields: list = [] |
|
||||||
mask_fields: dict = {} |
|
||||||
meta: dict = {} |
|
||||||
as_pk: str = 'id' |
|
||||||
query_document: Document = None |
|
||||||
mongo_qs = None |
|
||||||
|
|
||||||
def __init__(self, model: Document): |
|
||||||
self.model = model |
|
||||||
self.limit = 10 |
|
||||||
if 'no_image' in request.args: |
|
||||||
self.base64_image = False |
|
||||||
if 'with_sub_docs' in request.args: |
|
||||||
self.build_reference = request.args.get('with_sub_docs') |
|
||||||
if 'thumb' in request.args: |
|
||||||
self.base64_image = True |
|
||||||
self.base64_image_as_full = False |
|
||||||
if 'full_image' in request.args: |
|
||||||
self.base64_image = True |
|
||||||
self.base64_image_as_full = True |
|
||||||
if 'limit' in request.args: |
|
||||||
self.limit = int(request.args.get('limit')) |
|
||||||
if 'fields' in request.args: |
|
||||||
self.fields = request.args.get('fields') |
|
||||||
if 'offset' in request.args: |
|
||||||
self.offset = int(request.args.get('offset')) |
|
||||||
self.meta = getattr(self.model, '_meta') |
|
||||||
|
|
||||||
if 'ignore_fields' in self.meta: |
|
||||||
self.ignore_fields = self.meta.get('ignore_fields') |
|
||||||
if 'with_sub_docs' in self.meta: |
|
||||||
self.build_reference = self.meta.get('with_sub_docs') |
|
||||||
if 'mask_fields' in self.meta: |
|
||||||
self.mask_fields = self.meta.get('mask_fields') |
|
||||||
if 'document' in self.meta: |
|
||||||
self.model = self.meta.get('document') |
|
||||||
if 'query' in self.meta: |
|
||||||
self.qs = self.meta.get('query') |
|
||||||
if 'as_pk' in self.meta: |
|
||||||
self.as_pk = self.meta.get('as_pk') |
|
||||||
if 'query_document' in self.meta: |
|
||||||
self.query_document = self.meta.get('query_document') |
|
||||||
|
|
||||||
def external_query(self, qs: dict): |
|
||||||
self.eqs = qs |
|
||||||
|
|
||||||
def query(self, qs: dict) -> None: |
|
||||||
|
|
||||||
self.qs = qs |
|
||||||
|
|
||||||
def to_qs(self, pk: str = None) -> tuple: |
|
||||||
query = {} |
|
||||||
if self.qs: |
|
||||||
for key, val in self.qs.items(): |
|
||||||
if callable(val): |
|
||||||
query.update({key: val()}) |
|
||||||
else: |
|
||||||
query.update({key: val}) |
|
||||||
if self.query_document: |
|
||||||
query = {} |
|
||||||
if self.eqs: |
|
||||||
for key, val in self.eqs.items(): |
|
||||||
if callable(val): |
|
||||||
query.update({key: val()}) |
|
||||||
else: |
|
||||||
query.update({key: val}) |
|
||||||
data = self.query_document.objects.filter(**query) |
|
||||||
field_name = "" |
|
||||||
for i in self.model._fields_ordered: |
|
||||||
try: |
|
||||||
if isinstance(getattr(self.model, i).document_type, self.query_document.__class__): |
|
||||||
field_name = i + "__in" |
|
||||||
except: |
|
||||||
pass |
|
||||||
query.clear() |
|
||||||
query = {field_name: [i.id for i in data]} |
|
||||||
for key, val in self.qs.items(): |
|
||||||
if callable(val): |
|
||||||
query.update({key: val()}) |
|
||||||
else: |
|
||||||
query.update({key: val}) |
|
||||||
print(query) |
|
||||||
data = self.model.objects.filter(**query) |
|
||||||
if pk: |
|
||||||
data.get(**{self.as_pk: pk}) |
|
||||||
return data |
|
||||||
|
|
||||||
def to_json(self, pk: str = None) -> tuple: |
|
||||||
|
|
||||||
if self.mongo_qs is None: |
|
||||||
self.mongo_qs = self.to_qs(pk) |
|
||||||
|
|
||||||
data = self.mongo_qs |
|
||||||
count = data.count() |
|
||||||
if pk: |
|
||||||
json_data = self.parse_fields(self.mongo_qs.get(**{self.as_pk:pk}),self.model) |
|
||||||
else: |
|
||||||
data = data[self.offset:self.limit + self.offset] |
|
||||||
json_data = [] |
|
||||||
for item in data: |
|
||||||
item_data = self.parse_fields(item, self.model) |
|
||||||
json_data.append(item_data) |
|
||||||
return count, json_data |
|
||||||
|
|
||||||
def parse_field(self, field: BaseField, value: any) -> any: |
|
||||||
|
|
||||||
if isinstance(field, ObjectIdField): |
|
||||||
return str(value) |
|
||||||
elif isinstance(field, ReferenceField): |
|
||||||
|
|
||||||
if self.build_reference: |
|
||||||
return self.parse_fields(value, field.document_type) |
|
||||||
if value: |
|
||||||
return str(value.id) |
|
||||||
else: |
|
||||||
return value |
|
||||||
|
|
||||||
elif isinstance(field, ImageField): |
|
||||||
if self.base64_image and value: |
|
||||||
if self.base64_image_as_full: |
|
||||||
file = b64encode(value.read()).decode("utf-8") |
|
||||||
return { |
|
||||||
"size": value.size, |
|
||||||
"upload_date": value.gridout.upload_date, |
|
||||||
"format": value.format, |
|
||||||
"base64": file |
|
||||||
} |
|
||||||
else: |
|
||||||
file = b64encode(value.thumbnail.read()).decode("utf-8") |
|
||||||
|
|
||||||
return { |
|
||||||
"upload_date": value.gridout.upload_date, |
|
||||||
"format": value.format, |
|
||||||
"base64": file |
|
||||||
} |
|
||||||
else: |
|
||||||
if value: |
|
||||||
return { |
|
||||||
"size": value.size |
|
||||||
} |
|
||||||
else: |
|
||||||
return None |
|
||||||
elif isinstance(field, DecimalField): |
|
||||||
return float(value) |
|
||||||
elif isinstance(field, FileField): |
|
||||||
return "file" |
|
||||||
elif isinstance(field, ListField): |
|
||||||
if isinstance(field.field, EmbeddedDocumentField): |
|
||||||
return [self.parse_fields(item, field.field.document_type) for item in value] |
|
||||||
elif isinstance(field.field, ReferenceField) and self.build_reference: |
|
||||||
return [self.parse_fields(item, field.field.document_type) for item in value] |
|
||||||
else: |
|
||||||
return [self.parse_field(field.field, item) for item in value] |
|
||||||
elif isinstance(value, QuerySet): |
|
||||||
if value.count() > 0: |
|
||||||
return [self.parse_fields(i, value._document) for i in value] |
|
||||||
|
|
||||||
else: |
|
||||||
return value |
|
||||||
|
|
||||||
def parse_fields(self, item: Document, model: Document) -> dict: |
|
||||||
parsed_item = {} |
|
||||||
|
|
||||||
fields = list(getattr(model, "_fields_ordered")) |
|
||||||
if self.model != model: |
|
||||||
in_meta = getattr(model,'_meta') |
|
||||||
if in_meta.get('ignore_fields'): |
|
||||||
self.ignore_fields += in_meta.get('ignore_fields') |
|
||||||
for key, val in model.__dict__.items(): |
|
||||||
if isinstance(getattr(model, key), property): |
|
||||||
fields.append(key) |
|
||||||
for i in fields: |
|
||||||
if i not in self.ignore_fields: |
|
||||||
field = getattr(model, i) |
|
||||||
if i not in self.mask_fields: |
|
||||||
if not item is None: |
|
||||||
value = getattr(item, i) |
|
||||||
else: |
|
||||||
value = None |
|
||||||
else: |
|
||||||
value = getattr(item, i) |
|
||||||
if self.mask_fields.get(i) == 'all': |
|
||||||
value = '*************' |
|
||||||
elif self.mask_fields.get(i) == 'email': |
|
||||||
value = '{}****@{}***.{}'.format(value[0], value.split("@")[1][0], |
|
||||||
'.'.join(value.split("@")[1].split(".")[1:])) |
|
||||||
else: |
|
||||||
value = '*************' |
|
||||||
if not value is None: |
|
||||||
parsed_item.update({i: self.parse_field(field, value)}) |
|
||||||
else: |
|
||||||
parsed_item.update({i: None}) |
|
||||||
return parsed_item |
|
@ -1,155 +0,0 @@ |
|||||||
import json |
|
||||||
import time |
|
||||||
|
|
||||||
import mongoengine |
|
||||||
from flask import jsonify, request |
|
||||||
from flask_views.base import View |
|
||||||
from mongoengine import ValidationError, DoesNotExist, InvalidQueryError |
|
||||||
from werkzeug.exceptions import Unauthorized, NotFound |
|
||||||
|
|
||||||
from restapi.resource import Resource |
|
||||||
|
|
||||||
|
|
||||||
class ApiView(View): |
|
||||||
model = None |
|
||||||
authentication_methods = [] |
|
||||||
|
|
||||||
def __init__(self, model): |
|
||||||
self.start = time.time() |
|
||||||
self.model = model |
|
||||||
self.resource = Resource(self.model) |
|
||||||
|
|
||||||
def dispatch_request(self, *args, **kwargs): |
|
||||||
# keep all the logic in a helper method (_dispatch_request) so that |
|
||||||
# it's easy for subclasses to override this method without them also having to copy/paste all the |
|
||||||
# authentication logic, etc. |
|
||||||
return self._dispatch_request(*args, **kwargs) |
|
||||||
|
|
||||||
def _dispatch_request(self, *args, **kwargs): |
|
||||||
authorized = True if len(self.authentication_methods) == 0 else False |
|
||||||
for authentication_method in self.authentication_methods: |
|
||||||
if authentication_method().authorized(): |
|
||||||
authorized = True |
|
||||||
if not authorized: |
|
||||||
return {'error': 'Unauthorized'}, '401 Unauthorized' |
|
||||||
|
|
||||||
try: |
|
||||||
return super(ApiView, self).dispatch_request(*args, **kwargs) |
|
||||||
except mongoengine.queryset.DoesNotExist as e: |
|
||||||
return {'error': 'Empty query: ' + str(e)}, '404 Not Found' |
|
||||||
except ValidationError as e: |
|
||||||
return e.args[0], '400 Bad Request' |
|
||||||
except Unauthorized: |
|
||||||
return {'error': 'Unauthorized'}, '401 Unauthorized' |
|
||||||
except NotFound as e: |
|
||||||
return {'error': str(e)}, '404 Not Found' |
|
||||||
|
|
||||||
def get(self, *args, **kwargs): |
|
||||||
""" |
|
||||||
TODO: check permissions |
|
||||||
:param args: |
|
||||||
:param kwargs: |
|
||||||
:return: |
|
||||||
""" |
|
||||||
if 'pk' in kwargs: |
|
||||||
try: |
|
||||||
qs = self.resource.to_qs(pk=kwargs.get('pk')) |
|
||||||
count, data = self.resource.to_json(pk=kwargs.get('pk')) |
|
||||||
response = { |
|
||||||
'response': data, |
|
||||||
'status': True, |
|
||||||
'info': { |
|
||||||
'took': time.time() - self.start |
|
||||||
}, |
|
||||||
} |
|
||||||
except (ValidationError, DoesNotExist) as e: |
|
||||||
kwargs.update({'code': 404}) |
|
||||||
response = { |
|
||||||
"status": False, |
|
||||||
"errors": "Object not found" |
|
||||||
} |
|
||||||
else: |
|
||||||
if "query" in request.args and self.model._meta.get('can_query'): |
|
||||||
self.resource.external_query(json.loads(request.args.get('query'))) |
|
||||||
count, data = self.resource.to_json() |
|
||||||
response = { |
|
||||||
'response': data, |
|
||||||
'info': { |
|
||||||
'offset': self.resource.offset, |
|
||||||
'limit': self.resource.limit, |
|
||||||
'status': True, |
|
||||||
'total': count, |
|
||||||
'took': time.time() - self.start |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
return jsonify(response), kwargs.get('code', 200) |
|
||||||
|
|
||||||
def post(self, *args, **kwargs): |
|
||||||
""" |
|
||||||
TODO: check permissions |
|
||||||
|
|
||||||
:param args: |
|
||||||
:param kwargs: |
|
||||||
:return: |
|
||||||
""" |
|
||||||
try: |
|
||||||
item = self.model(**request.json) |
|
||||||
item.validate() |
|
||||||
except ValidationError as v: |
|
||||||
return jsonify({ |
|
||||||
'status': False, |
|
||||||
'errors': [{"field": field, "error": str(error)} for field, error in v.__dict__.get('errors').items() if |
|
||||||
isinstance(error, ValidationError) and field[0] != "_"] |
|
||||||
}), 400 |
|
||||||
except Exception as e: |
|
||||||
return jsonify({ |
|
||||||
'status': False, |
|
||||||
'errors': str(e) |
|
||||||
}), 400 |
|
||||||
data = item.save() |
|
||||||
return self.get(pk=data.id, code=201) |
|
||||||
|
|
||||||
def put(self, *args, **kwargs): |
|
||||||
""" |
|
||||||
TODO: check permissions |
|
||||||
|
|
||||||
:param args: |
|
||||||
:param kwargs: |
|
||||||
:return: |
|
||||||
""" |
|
||||||
if "pk" not in kwargs: |
|
||||||
return jsonify({ |
|
||||||
'status': False, |
|
||||||
"error": "Method not allowed" |
|
||||||
}), 403 |
|
||||||
else: |
|
||||||
try: |
|
||||||
self.model.objects(id=kwargs.get('pk')).update(**request.json) |
|
||||||
return self.get(pk=kwargs.get('pk')) |
|
||||||
except ValidationError as v: |
|
||||||
print(v.__dict__) |
|
||||||
return jsonify({ |
|
||||||
'status': False, |
|
||||||
'errors': [{"field": v.__dict__.get('field_name'), "error": v.__dict__.get('_message')}] |
|
||||||
}), 400 |
|
||||||
except InvalidQueryError as e: |
|
||||||
return jsonify({ |
|
||||||
'status': False, |
|
||||||
'errors': str(e) |
|
||||||
}), 400 |
|
||||||
|
|
||||||
def has_read_permission(self, request, qs): |
|
||||||
return qs |
|
||||||
|
|
||||||
def has_add_permission(self, request, obj): |
|
||||||
return True |
|
||||||
|
|
||||||
def has_change_permission(self, request, obj): |
|
||||||
return True |
|
||||||
|
|
||||||
def has_delete_permission(self, request, obj): |
|
||||||
return True |
|
||||||
|
|
||||||
def delete(self, *args, **kwargs): |
|
||||||
"delete method" |
|
Loading…
Reference in new issue