From bac54ef5494b1528eb79a706aed23cfef3407b2a Mon Sep 17 00:00:00 2001 From: gdoumenc Date: Thu, 1 Apr 2021 17:02:09 +0200 Subject: [PATCH 1/7] Code condensed Report route added --- controllers/controllers.py | 315 ++++++++++++++----------------------- 1 file changed, 114 insertions(+), 201 deletions(-) diff --git a/controllers/controllers.py b/controllers/controllers.py index c0d837e..2b2371c 100644 --- a/controllers/controllers.py +++ b/controllers/controllers.py @@ -1,37 +1,18 @@ # -*- coding: utf-8 -*- import json -import math import logging -import requests +import math -from odoo import http, _, exceptions +import requests +from odoo import http, _ from odoo.http import request -from .serializers import Serializer from .exceptions import QueryFormatError - +from .serializers import Serializer _logger = logging.getLogger(__name__) -def error_response(error, msg): - return { - "jsonrpc": "2.0", - "id": None, - "error": { - "code": 200, - "message": msg, - "data": { - "name": str(error), - "debug": "", - "message": msg, - "arguments": list(error.args), - "exception_type": type(error).__name__ - } - } - } - - class OdooAPI(http.Controller): @http.route( '/auth/', @@ -39,57 +20,43 @@ class OdooAPI(http.Controller): def authenticate(self, *args, **post): try: login = post["login"] - except KeyError: - raise exceptions.AccessDenied(message='`login` is required.') - - try: password = post["password"] - except KeyError: - raise exceptions.AccessDenied(message='`password` is required.') - - try: db = post["db"] - except KeyError: - raise exceptions.AccessDenied(message='`db` is required.') - - url_root = request.httprequest.url_root - AUTH_URL = f"{url_root}web/session/authenticate/" - - headers = {'Content-type': 'application/json'} + except KeyError as e: + return self.error_response(e, f"`{str(e)}` is required.") - data = { - "jsonrpc": "2.0", - "params": { - "login": login, - "password": password, - "db": db + try: + url_root = request.httprequest.url_root + auth_url = f"{url_root}web/session/authenticate/" + + data = { + "jsonrpc": "2.0", + "params": { + "login": login, + "password": password, + "db": db + } } - } - res = requests.post( - AUTH_URL, - data=json.dumps(data), - headers=headers - ) + res = requests.post( + auth_url, + data=json.dumps(data), + headers={'Content-type': 'application/json'} + ) - try: session_id = res.cookies["session_id"] user = json.loads(res.text) user["result"]["session_id"] = session_id - except Exception: - return "Invalid credentials." + except Exception as e: + return self.error_response(e, "Invalid credentials.") return user["result"] @http.route( '/object//', type='json', auth='user', methods=["POST"], csrf=False) def call_model_function(self, model, function, **post): - args = [] - kwargs = {} - if "args" in post: - args = post["args"] - if "kwargs" in post: - kwargs = post["kwargs"] + args = post.get("args", []) + kwargs = post.get("kwargs", {}) model = request.env[model] result = getattr(model, function)(*args, **kwargs) return result @@ -98,16 +65,19 @@ def call_model_function(self, model, function, **post): '/object///', type='json', auth='user', methods=["POST"], csrf=False) def call_obj_function(self, model, rec_id, function, **post): - args = [] - kwargs = {} - if "args" in post: - args = post["args"] - if "kwargs" in post: - kwargs = post["kwargs"] + args = post.get("args", []) + kwargs = post.get("kwargs", {}) obj = request.env[model].browse(rec_id).ensure_one() result = getattr(obj, function)(*args, **kwargs) return result + @http.route( + '/report//', + type='json', auth='user', methods=["POST"], csrf=False) + def call_render_qweb_pdf(self, model, rec_id, **post): + result = getattr('ir.actions.report', 'render_qweb_pdf')(post.get('res_ids'), post.get('data')) + return result + @http.route( '/api/', type='http', auth='user', methods=['GET'], csrf=False) @@ -115,23 +85,11 @@ def get_model_data(self, model, **params): try: records = request.env[model].search([]) except KeyError as e: - msg = "The model `%s` does not exist." % model - res = error_response(e, msg) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) - if "query" in params: - query = params["query"] - else: - query = "{*}" - - if "order" in params: - orders = json.loads(params["order"]) - else: - orders = "" + query = params.get("query", "{*}") + orders = json.loads(params["order"]) if "order" in params else "" if "filter" in params: filters = json.loads(params["filter"]) @@ -145,21 +103,16 @@ def get_model_data(self, model, **params): if "page_size" in params: page_size = int(params["page_size"]) count = len(records) - total_page_number = math.ceil(count/page_size) + total_page_number = math.ceil(count / page_size) - if "page" in params: - current_page = int(params["page"]) - else: - current_page = 1 # Default page Number - start = page_size*(current_page-1) - stop = current_page*page_size + current_page = int(params["page"]) if "page" in params else 1 + start = page_size * (current_page - 1) + stop = current_page * page_size records = records[start:stop] - next_page = current_page+1 \ - if 0 < current_page + 1 <= total_page_number \ - else None - prev_page = current_page-1 \ - if 0 < current_page - 1 <= total_page_number \ - else None + next_page = current_page + 1 \ + if 0 < current_page + 1 <= total_page_number else None + prev_page = current_page - 1 \ + if 0 < current_page - 1 <= total_page_number else None if "limit" in params: limit = int(params["limit"]) @@ -169,12 +122,7 @@ def get_model_data(self, model, **params): serializer = Serializer(records, query, many=True) data = serializer.data except (SyntaxError, QueryFormatError) as e: - res = error_response(e, e.msg) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + return self.error_response(e) res = { "count": len(records), @@ -184,11 +132,7 @@ def get_model_data(self, model, **params): "total_pages": total_page_number, "result": data } - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + return self.response(res) @http.route( '/api//', @@ -197,38 +141,19 @@ def get_model_rec(self, model, rec_id, **params): try: records = request.env[model].search([]) except KeyError as e: - msg = "The model `%s` does not exist." % model - res = error_response(e, msg) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) - - if "query" in params: - query = params["query"] - else: - query = "{*}" + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) # TODO: Handle the error raised by `ensure_one` record = records.browse(rec_id).ensure_one() try: + query = params.get("query", "{*}") serializer = Serializer(record, query) - data = serializer.data except (SyntaxError, QueryFormatError) as e: - res = error_response(e, e.msg) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + return self.error_response(e) - return http.Response( - json.dumps(data), - status=200, - mimetype='application/json' - ) + return self.response(serializer.data) @http.route( '/api//', @@ -236,15 +161,15 @@ def get_model_rec(self, model, rec_id, **params): def post_model_data(self, model, **post): try: data = post['data'] - except KeyError: + except KeyError as e: msg = "`data` parameter is not found on POST request body" - raise exceptions.ValidationError(msg) + return self.error_response(e, msg) try: model_to_post = request.env[model] - except KeyError: - msg = "The model `%s` does not exist." % model - raise exceptions.ValidationError(msg) + except KeyError as e: + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) # TODO: Handle data validation @@ -262,20 +187,19 @@ def post_model_data(self, model, **post): def put_model_record(self, model, rec_id, **post): try: data = post['data'] - except KeyError: + except KeyError as e: msg = "`data` parameter is not found on PUT request body" - raise exceptions.ValidationError(msg) + return self.error_response(e, msg) try: model_to_put = request.env[model] - except KeyError: - msg = "The model `%s` does not exist." % model - raise exceptions.ValidationError(msg) + except KeyError as e: + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) if "context" in post: # TODO: Handle error raised by `ensure_one` - rec = model_to_put.with_context(**post["context"])\ - .browse(rec_id).ensure_one() + rec = model_to_put.with_context(**post["context"]).browse(rec_id).ensure_one() else: rec = model_to_put.browse(rec_id).ensure_one() @@ -324,22 +248,21 @@ def put_model_record(self, model, rec_id, **post): def put_model_records(self, model, **post): try: data = post['data'] - except KeyError: + except KeyError as e: msg = "`data` parameter is not found on PUT request body" - raise exceptions.ValidationError(msg) + return self.error_response(e, msg) try: model_to_put = request.env[model] - except KeyError: - msg = "The model `%s` does not exist." % model - raise exceptions.ValidationError(msg) + except KeyError as e: + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) # TODO: Handle errors on filter filters = post["filter"] if "context" in post: - recs = model_to_put.with_context(**post["context"])\ - .search(filters) + recs = model_to_put.with_context(**post["context"]).search(filters) else: recs = model_to_put.search(filters) @@ -389,17 +312,12 @@ def put_model_records(self, model, **post): @http.route( '/api///', type='http', auth="user", methods=['DELETE'], csrf=False) - def delete_model_record(self, model, rec_id, **post): + def delete_model_record(self, model, rec_id, **post): try: model_to_del_rec = request.env[model] except KeyError as e: - msg = "The model `%s` does not exist." % model - res = error_response(e, msg) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) # TODO: Handle error raised by `ensure_one` rec = model_to_del_rec.browse(rec_id).ensure_one() @@ -409,38 +327,23 @@ def delete_model_record(self, model, rec_id, **post): res = { "result": is_deleted } - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + return self.response(res) except Exception as e: - res = error_response(e, str(e)) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + return self.error_response(e) # This is for bulk deletion @http.route( '/api//', type='http', auth="user", methods=['DELETE'], csrf=False) def delete_model_records(self, model, **post): - filters = json.loads(post["filter"]) - try: model_to_del_rec = request.env[model] except KeyError as e: - msg = "The model `%s` does not exist." % model - res = error_response(e, msg) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) # TODO: Handle error raised by `filters` + filters = json.loads(post["filter"]) recs = model_to_del_rec.search(filters) try: @@ -448,39 +351,49 @@ def delete_model_records(self, model, **post): res = { "result": is_deleted } - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + return self.response(res) except Exception as e: - res = error_response(e, str(e)) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + return self.error_response(e) @http.route( '/api///', type='http', auth="user", methods=['GET'], csrf=False) - def get_binary_record(self, model, rec_id, field, **post): + def get_binary_record(self, model, rec_id, field, **post): try: request.env[model] except KeyError as e: - msg = "The model `%s` does not exist." % model - res = error_response(e, msg) - return http.Response( - json.dumps(res), - status=200, - mimetype='application/json' - ) + msg = f"The model `{model}` does not exist." + return self.error_response(e, msg) - rec = request.env[model].browse(rec_id).ensure_one() - if rec.exists(): - src = getattr(rec, field).decode("utf-8") - else: - src = False + try: + rec = request.env[model].browse(rec_id).ensure_one() + src = getattr(rec, field).decode("utf-8") if rec.exists() else False + return http.Response(src) + except Exception as e: + return self.error_response(e) + + def error_response(self, e: Exception, msg: str = None): + res = { + "jsonrpc": "2.0", + "id": None, + "error": { + "code": 200, + "message": msg or str(e), + "data": { + "name": str(e), + "debug": "", + "message": msg, + "arguments": list(e.args), + "exception_type": type(e).__name__ + } + } + } + return self.response(res) + + @staticmethod + def response(res: dict): return http.Response( - src + json.dumps(res), + status=200, + mimetype='application/json' ) From d8d5f3f5442b3c42f85cf4be462b35ad9ef4b081 Mon Sep 17 00:00:00 2001 From: gdoumenc Date: Thu, 1 Apr 2021 17:39:40 +0200 Subject: [PATCH 2/7] Report route implemented --- controllers/controllers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/controllers/controllers.py b/controllers/controllers.py index 2b2371c..bd93d2c 100644 --- a/controllers/controllers.py +++ b/controllers/controllers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import base64 import json import logging import math @@ -72,11 +73,12 @@ def call_obj_function(self, model, rec_id, function, **post): return result @http.route( - '/report//', + '/report/', type='json', auth='user', methods=["POST"], csrf=False) - def call_render_qweb_pdf(self, model, rec_id, **post): - result = getattr('ir.actions.report', 'render_qweb_pdf')(post.get('res_ids'), post.get('data')) - return result + def call_render_qweb_pdf(self, rec_id, **post): + obj = request.env['ir.actions.report'].browse(rec_id).ensure_one() + content, _ = getattr(obj, 'render_qweb_pdf')(post.get('res_ids'), post.get('data')) + return base64.b64encode(content) @http.route( '/api/', From a39dfa3ac1db7ec1015492e5f745b8c4cb5c6b9d Mon Sep 17 00:00:00 2001 From: gdoumenc Date: Sun, 4 Apr 2021 18:49:48 +0200 Subject: [PATCH 3/7] More parameters controls --- controllers/controllers.py | 175 ++++++++++++++++++++----------------- controllers/exceptions.py | 10 ++- 2 files changed, 102 insertions(+), 83 deletions(-) diff --git a/controllers/controllers.py b/controllers/controllers.py index bd93d2c..c947e3f 100644 --- a/controllers/controllers.py +++ b/controllers/controllers.py @@ -8,7 +8,7 @@ from odoo import http, _ from odoo.http import request -from .exceptions import QueryFormatError +from .exceptions import ModelException, ObjectException, QueryFormatError from .serializers import Serializer _logger = logging.getLogger(__name__) @@ -24,7 +24,7 @@ def authenticate(self, *args, **post): password = post["password"] db = post["db"] except KeyError as e: - return self.error_response(e, f"`{str(e)}` is required.") + return self._error_response(400, e, f"'{str(e)}' is required in params.") try: url_root = request.httprequest.url_root @@ -48,114 +48,109 @@ def authenticate(self, *args, **post): session_id = res.cookies["session_id"] user = json.loads(res.text) user["result"]["session_id"] = session_id + return user["result"] except Exception as e: - return self.error_response(e, "Invalid credentials.") - return user["result"] + return self._error_response(401, e, "Invalid credentials.") @http.route( '/object//', type='json', auth='user', methods=["POST"], csrf=False) def call_model_function(self, model, function, **post): - args = post.get("args", []) - kwargs = post.get("kwargs", {}) - model = request.env[model] - result = getattr(model, function)(*args, **kwargs) - return result + try: + args = post.get("args", []) + kwargs = post.get("kwargs", {}) + model = self._get_model(model) + result = getattr(model, function)(*args, **kwargs) + return result + except (ModelException, ObjectException) as e: + return self._error_response(404, e) @http.route( '/object///', type='json', auth='user', methods=["POST"], csrf=False) def call_obj_function(self, model, rec_id, function, **post): - args = post.get("args", []) - kwargs = post.get("kwargs", {}) - obj = request.env[model].browse(rec_id).ensure_one() - result = getattr(obj, function)(*args, **kwargs) - return result + try: + args = post.get("args", []) + kwargs = post.get("kwargs", {}) + obj = self._get_obj(model, rec_id) + result = getattr(obj, function)(*args, **kwargs) + return result + except (ModelException, ObjectException) as e: + return self._error_response(404, e) @http.route( '/report/', type='json', auth='user', methods=["POST"], csrf=False) def call_render_qweb_pdf(self, rec_id, **post): - obj = request.env['ir.actions.report'].browse(rec_id).ensure_one() - content, _ = getattr(obj, 'render_qweb_pdf')(post.get('res_ids'), post.get('data')) - return base64.b64encode(content) + try: + obj = self._get_obj('ir.actions.report', rec_id) + content, _ = getattr(obj, 'render_qweb_pdf')(post.get('res_ids'), post.get('data')) + return base64.b64encode(content) + except (ModelException, ObjectException) as e: + return self._error_response(404, e) @http.route( '/api/', type='http', auth='user', methods=['GET'], csrf=False) def get_model_data(self, model, **params): try: - records = request.env[model].search([]) - except KeyError as e: - msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) - - query = params.get("query", "{*}") - orders = json.loads(params["order"]) if "order" in params else "" - - if "filter" in params: - filters = json.loads(params["filter"]) - records = request.env[model].search(filters, order=orders) - - prev_page = None - next_page = None - total_page_number = 1 - current_page = 1 - - if "page_size" in params: - page_size = int(params["page_size"]) - count = len(records) - total_page_number = math.ceil(count / page_size) - - current_page = int(params["page"]) if "page" in params else 1 - start = page_size * (current_page - 1) - stop = current_page * page_size - records = records[start:stop] - next_page = current_page + 1 \ - if 0 < current_page + 1 <= total_page_number else None - prev_page = current_page - 1 \ - if 0 < current_page - 1 <= total_page_number else None + model = self._get_model(model) + query = params.get("query", "{*}") + filters = json.loads(params["filters"]) if "filters" in params else [] + order = json.loads(params["order"]) if "order" in params else "" + limit = int(params.get("limit", 500)) + + records = model.search(filters, order=order, limit=limit) + + if "page_size" in params: + page_size = int(params["page_size"]) + count = len(records) + total_page_number = math.ceil(count / page_size) + + current_page = int(params.get("page", 1)) + start = page_size * (current_page - 1) + stop = current_page * page_size + records = records[start:stop] + next_page = current_page + 1 \ + if 0 < current_page + 1 <= total_page_number else None + prev_page = current_page - 1 \ + if 0 < current_page - 1 <= total_page_number else None + else: + prev_page = next_page = None + total_page_number = current_page = 1 - if "limit" in params: - limit = int(params["limit"]) - records = records[0:limit] + try: + serializer = Serializer(records, query, many=True) + data = serializer.data + except (SyntaxError, QueryFormatError) as e: + return self._error_response(400, e) - try: - serializer = Serializer(records, query, many=True) - data = serializer.data - except (SyntaxError, QueryFormatError) as e: - return self.error_response(e) + res = { + "count": len(records), + "prev": prev_page, + "current": current_page, + "next": next_page, + "total_pages": total_page_number, + "result": data + } + return self._response(res) - res = { - "count": len(records), - "prev": prev_page, - "current": current_page, - "next": next_page, - "total_pages": total_page_number, - "result": data - } - return self.response(res) + except (ModelException, ObjectException) as e: + return self._error_response(404, e) @http.route( '/api//', type='http', auth='user', methods=['GET'], csrf=False) def get_model_rec(self, model, rec_id, **params): try: - records = request.env[model].search([]) - except KeyError as e: - msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) - - # TODO: Handle the error raised by `ensure_one` - record = records.browse(rec_id).ensure_one() - - try: + obj = self._get_obj(model, rec_id) query = params.get("query", "{*}") - serializer = Serializer(record, query) + serializer = Serializer(obj, query) + return self._response(serializer.data) except (SyntaxError, QueryFormatError) as e: - return self.error_response(e) - - return self.response(serializer.data) + return self.error_response(400, e) + except (ModelException, ObjectException) as e: + return self.error_response(404, e) @http.route( '/api//', @@ -374,12 +369,28 @@ def get_binary_record(self, model, rec_id, field, **post): except Exception as e: return self.error_response(e) - def error_response(self, e: Exception, msg: str = None): + def _get_model(self, model): + try: + return request.env[model] + except KeyError as e: + msg = f"The model '{model}' does not exist." + raise ModelException(msg) + + def _get_obj(self, model, id): + try: + return self._get_model(model).browse(id).ensure_one() + except AttributeError as e: + msg = f"The object '{id}' of '{model}' does not exist." + raise ObjectException(msg, id) + except ValueError as e: + msg = f"The object '{id}' of '{model}' is not single." + raise ObjectException(msg, id) + + def _error_response(self, status, e: Exception, msg: str = None): res = { "jsonrpc": "2.0", "id": None, "error": { - "code": 200, "message": msg or str(e), "data": { "name": str(e), @@ -390,12 +401,12 @@ def error_response(self, e: Exception, msg: str = None): } } } - return self.response(res) + return self._response(res, status=status) @staticmethod - def response(res: dict): + def _response(res: dict, status=200): return http.Response( json.dumps(res), - status=200, + status=status, mimetype='application/json' ) diff --git a/controllers/exceptions.py b/controllers/exceptions.py index d810742..3b5a553 100644 --- a/controllers/exceptions.py +++ b/controllers/exceptions.py @@ -1,2 +1,10 @@ +class ModelException(Exception): + """Undefined model""" + + +class ObjectException(Exception): + """Undefined object""" + + class QueryFormatError(Exception): - """Invalid Query Format.""" \ No newline at end of file + """Invalid Query Format.""" From e43fb026abca3f6932970ae5a48a25481578e79f Mon Sep 17 00:00:00 2001 From: gdoumenc Date: Mon, 5 Apr 2021 18:10:55 +0200 Subject: [PATCH 4/7] JSON dumps report parameters --- .gitignore | 1 + controllers/controllers.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8f886cb..97f5dce 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,4 @@ XDG_CACHE_HOME # End of https://www.gitignore.io/api/python,visualstudiocode +.idea/ \ No newline at end of file diff --git a/controllers/controllers.py b/controllers/controllers.py index c947e3f..5ff5394 100644 --- a/controllers/controllers.py +++ b/controllers/controllers.py @@ -84,7 +84,9 @@ def call_obj_function(self, model, rec_id, function, **post): def call_render_qweb_pdf(self, rec_id, **post): try: obj = self._get_obj('ir.actions.report', rec_id) - content, _ = getattr(obj, 'render_qweb_pdf')(post.get('res_ids'), post.get('data')) + res_ids = json.loads(post.get('res_ids')) + data = json.loads(post.get('data', '{}')) + content, _ = getattr(obj, 'render_qweb_pdf')(res_ids, data) return base64.b64encode(content) except (ModelException, ObjectException) as e: return self._error_response(404, e) From 3d7ec22a70edd6a81d7fd33757a17c22fa17ca43 Mon Sep 17 00:00:00 2001 From: gdoumenc Date: Tue, 6 Apr 2021 14:20:47 +0200 Subject: [PATCH 5/7] None value error in serializer fixed --- controllers/serializers.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/controllers/serializers.py b/controllers/serializers.py index 508ec0b..9ca0c12 100644 --- a/controllers/serializers.py +++ b/controllers/serializers.py @@ -42,7 +42,10 @@ def build_flat_field(cls, rec, field_name): if field_name not in all_fields: msg = "'%s' field is not found" % field_name raise LookupError(msg) - field_type = rec.fields_get(field_name).get(field_name).get('type') + field = rec.fields_get(field_name).get(field_name) + if not field: + return {field_name: None} + field_type = field.get('type') if field_type in ['one2many', 'many2many']: return { field_name: [record.id for record in rec[field_name]] @@ -76,8 +79,8 @@ def build_nested_field(cls, rec, field_name, nested_parsed_query): if field_type in ['one2many', 'many2many']: return { field_name: [ - cls.serialize(record, nested_parsed_query) - for record + cls.serialize(record, nested_parsed_query) + for record in rec[field_name] ] } @@ -93,7 +96,7 @@ def build_nested_field(cls, rec, field_name, nested_parsed_query): @classmethod def serialize(cls, rec, parsed_query): data = {} - + # NOTE: self.parsed_restql_query["include"] not being empty # is not a guarantee that the exclude operator(-) has not been # used because the same self.parsed_restql_query["include"] @@ -106,17 +109,17 @@ def serialize(cls, rec, parsed_query): continue for nested_field, nested_parsed_query in field.items(): built_nested_field = cls.build_nested_field( - rec, - nested_field, + rec, + nested_field, nested_parsed_query ) data.update(built_nested_field) - - flat_fields= set(all_fields).symmetric_difference(set(parsed_query['exclude'])) + + flat_fields = set(all_fields).symmetric_difference(set(parsed_query['exclude'])) for field in flat_fields: flat_field = cls.build_flat_field(rec, field) data.update(flat_field) - + elif parsed_query["include"]: # Here we are sure that self.parsed_restql_query["exclude"] # is empty which means the exclude operator(-) is not used, @@ -126,7 +129,7 @@ def serialize(cls, rec, parsed_query): if "*" in parsed_query['include']: # Include all fields parsed_query['include'] = filter( - lambda item: item != "*", + lambda item: item != "*", parsed_query['include'] ) fields = chain(parsed_query['include'], all_fields) @@ -136,8 +139,8 @@ def serialize(cls, rec, parsed_query): if isinstance(field, dict): for nested_field, nested_parsed_query in field.items(): built_nested_field = cls.build_nested_field( - rec, - nested_field, + rec, + nested_field, nested_parsed_query ) data.update(built_nested_field) @@ -148,4 +151,4 @@ def serialize(cls, rec, parsed_query): # The query is empty i.e query={} # return nothing return {} - return data \ No newline at end of file + return data From e4a6bda2b3e10107c70de7e7f72ad550e36d41b3 Mon Sep 17 00:00:00 2001 From: gdoumenc Date: Tue, 6 Apr 2021 17:45:23 +0200 Subject: [PATCH 6/7] order param defined on api/model --- controllers/controllers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/controllers.py b/controllers/controllers.py index 5ff5394..08920f0 100644 --- a/controllers/controllers.py +++ b/controllers/controllers.py @@ -99,7 +99,7 @@ def get_model_data(self, model, **params): model = self._get_model(model) query = params.get("query", "{*}") filters = json.loads(params["filters"]) if "filters" in params else [] - order = json.loads(params["order"]) if "order" in params else "" + order = params.get("order", "id") limit = int(params.get("limit", 500)) records = model.search(filters, order=order, limit=limit) From 16e09c118e1f3d4a4f5874cbc0f601636334c7d7 Mon Sep 17 00:00:00 2001 From: gdoumenc Date: Mon, 12 Apr 2021 08:53:41 +0200 Subject: [PATCH 7/7] export scripts extended --- controllers/controllers.py | 67 +++++++++++++++----------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/controllers/controllers.py b/controllers/controllers.py index 08920f0..0a88e18 100644 --- a/controllers/controllers.py +++ b/controllers/controllers.py @@ -3,11 +3,11 @@ import json import logging import math - import requests +import werkzeug + from odoo import http, _ from odoo.http import request - from .exceptions import ModelException, ObjectException, QueryFormatError from .serializers import Serializer @@ -24,7 +24,8 @@ def authenticate(self, *args, **post): password = post["password"] db = post["db"] except KeyError as e: - return self._error_response(400, e, f"'{str(e)}' is required in params.") + msg = f"'{str(e)}' is required in params." + raise werkzeug.exceptions.BadRequest(msg) try: url_root = request.httprequest.url_root @@ -50,7 +51,7 @@ def authenticate(self, *args, **post): user["result"]["session_id"] = session_id return user["result"] except Exception as e: - return self._error_response(401, e, "Invalid credentials.") + raise werkzeug.exceptions.Unauthorized("Invalid credentials.") @http.route( '/object//', @@ -63,7 +64,7 @@ def call_model_function(self, model, function, **post): result = getattr(model, function)(*args, **kwargs) return result except (ModelException, ObjectException) as e: - return self._error_response(404, e) + raise werkzeug.exceptions.NotFound(str(e)) @http.route( '/object///', @@ -76,7 +77,7 @@ def call_obj_function(self, model, rec_id, function, **post): result = getattr(obj, function)(*args, **kwargs) return result except (ModelException, ObjectException) as e: - return self._error_response(404, e) + raise werkzeug.exceptions.NotFound(str(e)) @http.route( '/report/', @@ -89,7 +90,7 @@ def call_render_qweb_pdf(self, rec_id, **post): content, _ = getattr(obj, 'render_qweb_pdf')(res_ids, data) return base64.b64encode(content) except (ModelException, ObjectException) as e: - return self._error_response(404, e) + raise werkzeug.exceptions.NotFound(str(e)) @http.route( '/api/', @@ -125,7 +126,7 @@ def get_model_data(self, model, **params): serializer = Serializer(records, query, many=True) data = serializer.data except (SyntaxError, QueryFormatError) as e: - return self._error_response(400, e) + raise werkzeug.exceptions.NotFound(str(e)) res = { "count": len(records), @@ -138,7 +139,7 @@ def get_model_data(self, model, **params): return self._response(res) except (ModelException, ObjectException) as e: - return self._error_response(404, e) + raise werkzeug.exceptions.NotFound(str(e)) @http.route( '/api//', @@ -150,9 +151,9 @@ def get_model_rec(self, model, rec_id, **params): serializer = Serializer(obj, query) return self._response(serializer.data) except (SyntaxError, QueryFormatError) as e: - return self.error_response(400, e) + raise werkzeug.exceptions.BadRequest(str(e)) except (ModelException, ObjectException) as e: - return self.error_response(404, e) + raise werkzeug.exceptions.NotFound(str(e)) @http.route( '/api//', @@ -162,13 +163,13 @@ def post_model_data(self, model, **post): data = post['data'] except KeyError as e: msg = "`data` parameter is not found on POST request body" - return self.error_response(e, msg) + raise werkzeug.exceptions.BadRequest(msg) try: model_to_post = request.env[model] except KeyError as e: msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) + raise werkzeug.exceptions.BadRequest(msg) # TODO: Handle data validation @@ -188,13 +189,13 @@ def put_model_record(self, model, rec_id, **post): data = post['data'] except KeyError as e: msg = "`data` parameter is not found on PUT request body" - return self.error_response(e, msg) + raise werkzeug.exceptions.BadRequest(msg) try: model_to_put = request.env[model] except KeyError as e: msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) + raise werkzeug.exceptions.BadRequest(msg) if "context" in post: # TODO: Handle error raised by `ensure_one` @@ -249,13 +250,13 @@ def put_model_records(self, model, **post): data = post['data'] except KeyError as e: msg = "`data` parameter is not found on PUT request body" - return self.error_response(e, msg) + raise werkzeug.exceptions.BadRequest(msg) try: model_to_put = request.env[model] except KeyError as e: msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) + raise werkzeug.exceptions.BadRequest(msg) # TODO: Handle errors on filter filters = post["filter"] @@ -316,7 +317,7 @@ def delete_model_record(self, model, rec_id, **post): model_to_del_rec = request.env[model] except KeyError as e: msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) + raise werkzeug.exceptions.NotFound(msg) # TODO: Handle error raised by `ensure_one` rec = model_to_del_rec.browse(rec_id).ensure_one() @@ -328,7 +329,7 @@ def delete_model_record(self, model, rec_id, **post): } return self.response(res) except Exception as e: - return self.error_response(e) + return self._error_response(e, str(e), 500) # This is for bulk deletion @http.route( @@ -339,7 +340,7 @@ def delete_model_records(self, model, **post): model_to_del_rec = request.env[model] except KeyError as e: msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) + raise werkzeug.exceptions.NotFound(str(e)) # TODO: Handle error raised by `filters` filters = json.loads(post["filter"]) @@ -352,7 +353,7 @@ def delete_model_records(self, model, **post): } return self.response(res) except Exception as e: - return self.error_response(e) + return self._error_response(e, str(e), status=500) @http.route( '/api///', @@ -362,16 +363,17 @@ def get_binary_record(self, model, rec_id, field, **post): request.env[model] except KeyError as e: msg = f"The model `{model}` does not exist." - return self.error_response(e, msg) + raise werkzeug.exceptions.NotFound(msg) try: rec = request.env[model].browse(rec_id).ensure_one() src = getattr(rec, field).decode("utf-8") if rec.exists() else False return http.Response(src) except Exception as e: - return self.error_response(e) + raise werkzeug.exceptions.InternalServerError(str(e)) - def _get_model(self, model): + @staticmethod + def _get_model(model): try: return request.env[model] except KeyError as e: @@ -388,23 +390,6 @@ def _get_obj(self, model, id): msg = f"The object '{id}' of '{model}' is not single." raise ObjectException(msg, id) - def _error_response(self, status, e: Exception, msg: str = None): - res = { - "jsonrpc": "2.0", - "id": None, - "error": { - "message": msg or str(e), - "data": { - "name": str(e), - "debug": "", - "message": msg, - "arguments": list(e.args), - "exception_type": type(e).__name__ - } - } - } - return self._response(res, status=status) - @staticmethod def _response(res: dict, status=200): return http.Response(