mongo rest api module for flask

add requirements.txt flask_views and flask_login
create a has_permission and read_permission function
pull/5/head
Mustafa Yontar 4 years ago
parent bfaab9fc77
commit ce28cf59b6
  1. 45
      internal_lib/permission_parser.py
  2. 4
      requirements.txt
  3. 18
      restapi/Methods.py
  4. 43
      restapi/__init__.py
  5. 11
      restapi/jwt.py
  6. 199
      restapi/resource.py
  7. 122
      restapi/views.py

@ -1,3 +1,6 @@
from flask_login import current_user
def parse_permission(string):
"""
Parsing permission string
@ -33,13 +36,13 @@ def parse_permission(string):
delete = True
return {
"delete":delete,
"write":write,
"read":read,
"update":update,
"module":module,
"union":union_id,
"item_id":item_id
"delete": delete,
"write": write,
"read": read,
"update": update,
"module": module,
"union": union_id,
"item_id": item_id
}
@ -53,8 +56,32 @@ def control_permission(group, module, perm_type, itemid, unionid):
return True
elif right.get(perm_type):
return True
elif right.get('item_id') in ['*',itemid]:
elif right.get('item_id') in ['*', itemid]:
return True
elif right.get('module') in ["*",module] and right.get('union') in ['*', unionid] and right.get(perm_type) and right.get('item_id') in ['*',itemid]:
elif right.get('module') in ["*", module] and right.get('union') in ['*', unionid] and right.get(
perm_type) and right.get('item_id') in ['*', itemid]:
return True
return False
def read_permission(module, qs):
union_list = []
for right_string in current_user.group.rights:
right = parse_permission(right_string)
if right.get('module') in [module, '*']:
if right.get('read'):
if right.get('union') != "*":
union_list.append(right.get('union'))
if len(union_list) > 0:
if module == 'union':
qs.filter(id__in=union_list, deleted=False)
else:
qs.filter(union__in=union_list, deleted=False)
return qs
def has_permission(module, obj, reqtype, oid):
if control_permission(current_user.group, module, reqtype, oid, obj.company):
return True
return False

@ -2,4 +2,6 @@ mongoengine
flask-mongoengine
flask
pycryptodome
flask_jwt_extended
flask_jwt_extended
flask_views
flask_login

@ -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…
Cancel
Save