forked from oyd/Adunatio
add requirements.txt flask_views and flask_login create a has_permission and read_permission functionremotes/1725865088694522691/master
parent
bfaab9fc77
commit
ce28cf59b6
@ -0,0 +1,18 @@ |
||||
class Get: |
||||
method = 'GET' |
||||
|
||||
|
||||
class List: |
||||
method = 'GET' |
||||
|
||||
|
||||
class Delete: |
||||
method = 'DELETE' |
||||
|
||||
|
||||
class Update: |
||||
method = 'PUT' |
||||
|
||||
|
||||
class Create: |
||||
method = 'POST' |
@ -0,0 +1,43 @@ |
||||
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)) |
@ -0,0 +1,11 @@ |
||||
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'] |
@ -0,0 +1,199 @@ |
||||
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 |
||||
|
||||
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_json(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) |
||||
|
||||
count = data.count() |
||||
if pk: |
||||
json_data = self.parse_fields(data.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") |
||||
else: |
||||
try: |
||||
file = b64encode(value.thumbnail.read()).decode("utf-8") |
||||
except: |
||||
file = b64encode(value.read()).decode("utf-8") |
||||
return { |
||||
"size": value.size, |
||||
"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 |
@ -0,0 +1,122 @@ |
||||
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): |
||||
|
||||
if 'pk' in kwargs: |
||||
try: |
||||
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): |
||||
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): |
||||
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 delete(self, *args, **kwargs): |
||||
"delete method" |
Loading…
Reference in new issue