Browse Source

Allow queries by model's field

* All requests now can use any fields in the model except for binary and datetime
* Fields now support wildcard (*) to output all model fields
* GET request will return ID field if no fields are given
* Return proper HTTP error codes and messages in JSON format by default
pull/345/head
drpsyko101 9 months ago
parent
commit
4454c1be9d
  1. 244
      rest_api_odoo/controllers/main.py
  2. 51
      rest_api_odoo/static/description/index.html

244
rest_api_odoo/controllers/main.py

@ -23,7 +23,8 @@
import json import json
import logging import logging
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request, Response
from ast import literal_eval
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -38,80 +39,83 @@ class RestApi(http.Controller):
user_id = request.env['res.users'].search([('api_key', '=', api_key)]) user_id = request.env['res.users'].search([('api_key', '=', api_key)])
if api_key is not None and user_id: if api_key is not None and user_id:
response = True return True
elif not user_id: elif not user_id:
response = ('<html><body><h2>Invalid <i>API Key</i> ' return Response(json.dumps({'message': 'Invalid API Key'}), status=401)
'!</h2></body></html>') return Response(json.dumps({'message': 'No API Key Provided'}), status=400)
else:
response = ("<html><body><h2>No <i>API Key</i> Provided " def simplest_type(self, input):
"!</h2></body></html>") """Try cast input into native Python class, otherwise return as string"""
try:
return response return literal_eval(input)
except:
if input == 'true':
return True
if input == 'false':
return False
return input
def generate_response(self, method, model, rec_id): def generate_response(self, method, **query):
"""This function is used to generate the response based on the type """This function is used to generate the response based on the type
of request and the parameters given""" of request and the parameters given"""
model = query.pop('model')
option = request.env['connection.api'].search( option = request.env['connection.api'].search(
[('model_id', '=', model)], limit=1) [('model_id', '=', model)], limit=1)
model_name = option.model_id.model model_name = option.model_id.model
model_display_name = option.model_id.name
if method != 'DELETE': data = {}
try:
data = json.loads(request.httprequest.data) data = json.loads(request.httprequest.data)
else: except:
data = {} pass
fields = [] fields = []
if data: if data:
for field in data['fields']: for field in data['fields']:
fields.append(field) fields.append(field)
if not fields and method != 'DELETE': if '*' in fields:
return ("<html><body><h2>No fields selected for the model" fields = []
"</h2></body></html>") record_fields = request.env[
str(model_name)].fields_get([], attributes=['type'])
for field, value in record_fields.items():
value_type = value.get('type')
if not (value_type == 'binary' or value_type == 'datetime'):
fields.append(field)
if not option: if not option:
return ("<html><body><h2>No Record Created for the model" return Response(json.dumps({'message': f'No Record Created for the model. Please contact your admininstrator to enable {method} method for {model_display_name} record.'}), status=403)
"</h2></body></html>")
try: try:
if method == 'GET': if method == 'GET':
fields = [] if not fields:
for field in data['fields']: fields.append('id')
fields.append(field)
if not option.is_get: if not option.is_get:
return ("<html><body><h2>Method Not Allowed" return Response(
"</h2></body></html>") json.dumps({'message': f'Method not allowed. Please contact your admininstrator to enable {method} method for {model_display_name} record.'}),
status=405)
else: else:
datas = [] domains = []
if rec_id != 0: for key, value in query.items():
partner_records = request.env[ domains.append((key, '=', self.simplest_type(value)))
str(model_name)].search_read( partner_records = request.env[
domain=[('id', '=', rec_id)], str(model_name)].search_read(
fields=fields domain=domains,
) fields=fields
data = json.dumps({ )
'records': partner_records return Response(
}) json.dumps({
datas.append(data)
return request.make_response(data=datas)
else:
partner_records = request.env[
str(model_name)].search_read(
domain=[],
fields=fields
)
data = json.dumps({
'records': partner_records 'records': partner_records
}) })
datas.append(data) )
return request.make_response(data=datas) if method == 'POST':
except: if not option.is_post:
return ("<html><body><h2>Invalid JSON Data" return Response(
"</h2></body></html>") json.dumps({'message': f'Method not allowed. Please contact your admininstrator to enable {method} method for {model_display_name} record.'}),
if method == 'POST': status=405)
if not option.is_post: if not data or 'values' not in data:
return ("<html><body><h2>Method Not Allowed" return Response(json.dumps({'message': 'No Data Provided'}), status=403)
"</h2></body></html>")
else:
try: try:
data = json.loads(request.httprequest.data) data = json.loads(request.httprequest.data)
datas = []
new_resource = request.env[str(model_name)].create( new_resource = request.env[str(model_name)].create(
data['values']) data['values'])
partner_records = request.env[ partner_records = request.env[
@ -119,71 +123,61 @@ class RestApi(http.Controller):
domain=[('id', '=', new_resource.id)], domain=[('id', '=', new_resource.id)],
fields=fields fields=fields
) )
new_data = json.dumps({'New resource': partner_records, }) return Response(json.dumps({'new_record': partner_records}), status=201)
datas.append(new_data)
return request.make_response(data=datas)
except: except:
return ("<html><body><h2>Invalid JSON Data" return Response(json.dumps({'message': 'Invalid JSON Data'}), status=403)
"</h2></body></html>") if method == 'PUT':
if method == 'PUT': if not option.is_put:
if not option.is_put: return Response(
return ("<html><body><h2>Method Not Allowed" json.dumps({'message': f'Method not allowed. Please contact your admininstrator to enable {method} method for {model_display_name} record.'}),
"</h2></body></html>") status=405)
else:
if rec_id == 0: if not 'id' in query:
return ("<html><body><h2>No ID Provided" return Response(json.dumps({'message': 'No ID Provided'}), status=403)
"</h2></body></html>") if not data or 'values' not in data:
else: return Response(json.dumps({'message': 'No Data Provided'}), status=403)
resource = request.env[str(model_name)].browse(
int(rec_id)) resource = request.env[str(model_name)].browse(
if not resource.exists(): int(query.get('id')))
return ("<html><body><h2>Resource not found" if not resource.exists():
"</h2></body></html>") return Response(json.dumps({'message': 'Resource not found'}), status=404)
else:
try: try:
datas = [] data = json.loads(request.httprequest.data)
data = json.loads(request.httprequest.data) resource.write(data['values'])
resource.write(data['values']) partner_records = request.env[
partner_records = request.env[ str(model_name)].search_read(
str(model_name)].search_read( domain=[('id', '=', resource.id)],
domain=[('id', '=', resource.id)], fields=fields
fields=fields )
) return Response(json.dumps({'updated_record': partner_records}))
new_data = json.dumps(
{'Updated resource': partner_records, except:
}) return Response(json.dumps({'message': 'Invalid JSON value(s) passed'}), status=403)
datas.append(new_data) if method == 'DELETE':
return request.make_response(data=datas) if not option.is_delete:
return Response(
except: json.dumps({'message': f'Method not allowed. Please contact your admininstrator to enable {method} method for {model_display_name} record.'}),
return ("<html><body><h2>Invalid JSON Data " status=405)
"!</h2></body></html>")
if method == 'DELETE': if not 'id' in query:
if not option.is_delete: return Response(json.dumps({'message': 'No ID Provided'}), status=403)
return ("<html><body><h2>Method Not Allowed"
"</h2></body></html>") resource = request.env[str(model_name)].browse(
else: int(query.get('id')))
if rec_id == 0: if not resource.exists():
return ("<html><body><h2>No ID Provided" return Response(json.dumps({'message': 'Resource not found'}), status=404)
"</h2></body></html>")
else: else:
resource = request.env[str(model_name)].browse(
int(rec_id)) records = request.env[
if not resource.exists(): str(model_name)].search_read(
return ("<html><body><h2>Resource not found" domain=[('id', '=', resource.id)],
"</h2></body></html>") fields=fields
else: )
resource.unlink()
records = request.env[ return Response(json.dumps({'message': 'Resource deleted', 'data': records}), status=202)
str(model_name)].search_read( except:
domain=[('id', '=', resource.id)], return Response(json.dumps({'message': 'Internal Server Error'}), status=500)
fields=['id', 'display_name']
)
remove = json.dumps(
{"Resource deleted": records,
})
resource.unlink()
return request.make_response(data=remove)
@http.route(['/send_request'], type='http', @http.route(['/send_request'], type='http',
auth='none', auth='none',
@ -196,7 +190,7 @@ class RestApi(http.Controller):
http_method = request.httprequest.method http_method = request.httprequest.method
api_key = request.httprequest.headers.get('api-key') api_key = request.httprequest.headers.get('api-key')
auth_api = self.auth_api_key(api_key) auth_api = self.auth_api_key(api_key)
model = kw.get('model') model = kw.pop('model')
username = request.httprequest.headers.get('login') username = request.httprequest.headers.get('login')
password = request.httprequest.headers.get('password') password = request.httprequest.headers.get('password')
request.session.authenticate(request.session.db, username, request.session.authenticate(request.session.db, username,
@ -204,17 +198,12 @@ class RestApi(http.Controller):
model_id = request.env['ir.model'].search( model_id = request.env['ir.model'].search(
[('model', '=', model)]) [('model', '=', model)])
if not model_id: if not model_id:
return ("<html><body><h3>Invalid model, check spelling or maybe " return Response(json.dumps(
"the related " {'message': 'Invalid model, check spelling or maybe the related module is not installed'}),
"module is not installed" status=403)
"</h3></body></html>")
if auth_api == True: if auth_api == True:
if not kw.get('Id'): result = self.generate_response(http_method, model=model_id.id, **kw)
rec_id = 0
else:
rec_id = int(kw.get('Id'))
result = self.generate_response(http_method, model_id.id, rec_id)
return result return result
else: else:
return auth_api return auth_api
@ -239,5 +228,4 @@ class RestApi(http.Controller):
"api-key": api_key}) "api-key": api_key})
return request.make_response(data=datas) return request.make_response(data=datas)
except: except:
return ("<html><body><h2>wrong login credentials" return Response(json.dumps({'message': 'wrong login credentials'}), status=401)
"</h2></body></html>")

51
rest_api_odoo/static/description/index.html

@ -217,7 +217,7 @@
<li>First of all, we have to add a new parameter in odoo conf. <li>First of all, we have to add a new parameter in odoo conf.
file. file.
</li> </li>
<li><b>server_wide_modules = web, base, rest_api_odoo</b><br/> <li><code>server_wide_modules = web, base, rest_api_odoo</code><br/>
- This will allow us to send request to server without - This will allow us to send request to server without
selecting database first.<br/>- Incase if you have to selecting database first.<br/>- Incase if you have to
uninstall the module , you have to remove this parameter. uninstall the module , you have to remove this parameter.
@ -251,10 +251,10 @@
<li>Next you have to select the database and login.</li> <li>Next you have to select the database and login.</li>
<li>We have attached <b>Postman collections</b> through which <li>We have attached <b>Postman collections</b> through which
you can you can
authenticate rest api. authenticate REST api.
</li> </li>
<li>First, extract the <b>zip</b> file. Then, you will obtain the JSON-format file, which you can directly import into <b>POSTMAN.</b></li> <li>First, extract the <b>zip</b> file. Then, you will obtain the JSON-format file, which you can directly import into <b>POSTMAN.</b></li>
<li>The url format will be like this - <b>http://cybrosys:8016/odoo_connect</b> <li>The url format will be like this - <code>http://cybrosys:8016/odoo_connect</code>
Replace 'cybrosys:8016' with your localhost port number. Replace 'cybrosys:8016' with your localhost port number.
</li> </li>
<li>You have to provide database name, username and password <li>You have to provide database name, username and password
@ -266,9 +266,9 @@
<li>This key will be used when sending api requests to <li>This key will be used when sending api requests to
database. database.
</li> </li>
<li>The response will be like this - <b> {"Status": "auth <li>The response will be like this - <code>{"Status": "auth
successful", "User": "Mitchell Admin", "api-key": successful", "User": "Mitchell Admin", "api-key":
"66c2ebab-d4dc-42f0-87d0-d1646e887569"}.</b></li> "66c2ebab-d4dc-42f0-87d0-d1646e887569"}</code>.</li>
</ul> </ul>
</p> </p>
<img src="assets/screenshots/screenshot_2.png" <img src="assets/screenshots/screenshot_2.png"
@ -307,7 +307,7 @@
</h3> </h3>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
<ul> <ul>
<li>You can send GET request to retrieve data from the <li>You can send <code>GET</code> request to retrieve data from the
database. database.
</li> </li>
<li>The postman collection has been provided with app files for <li>The postman collection has been provided with app files for
@ -318,15 +318,16 @@
</li> </li>
<li>Model can be passed as argument as the technical name , and <li>Model can be passed as argument as the technical name , and
also if you want also if you want
specific record you can provide the id as well. specific record you can provide any related fields to the module as well.
</li> </li>
<li>The format for GET method will be like this - <b>http://cybrosys:8016/send_request?model=res.partner&Id=10.</b> <li>The format for GET method will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&Id=10.</code>
</li> </li>
<li>We can specify the fields inside the JSON data, and it will <li>We can specify the fields inside the JSON data, and it will
be like this - <b>{"fields": ["name", "email"]}.</b></li> be like this - <code>{"fields": ["name", "email"]}</code>.</li>
<li>This is the format of api response - <b>{"records": [{"id": <li>This is the format of api response - <code>{"records": [{"id":
10, "email": "deco.addict82@example.com", "name": "Deco 10, "email": "deco.addict82@example.com", "name": "Deco
Addict"}]}.</b> Addict"}]}</code>. If no fields are passed , it will returns
just the record's ID. To get all of the fields, set the fields to wildcard - <code>{"fields": ["*"]}</code>.
</li> </li>
</ul> </ul>
@ -338,7 +339,7 @@
</h3> </h3>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
<ul> <ul>
<li>Using POST method , you can create new records in the <li>Using <code>POST</code> method , you can create new records in the
database. database.
</li> </li>
<li>Just make sure you enabled POST method for the model record <li>Just make sure you enabled POST method for the model record
@ -351,18 +352,18 @@
<li>You can make use of the postman collection that we have <li>You can make use of the postman collection that we have
added with app files. added with app files.
</li> </li>
<li>The format for sending POST request will be like this - <b>http://cybrosys:8016/send_request?model=res.partner.</b> <li>The format for sending POST request will be like this - <code>http://cybrosys:8016/send_request?model=res.partner.</code>
</li> </li>
<li>This is the format for JSON data - <b>{ <li>This is the format for JSON data - <code>{
"fields" :["name", "phone"] , "fields" :["name", "phone"] ,
"values": {"name": "abc", "values": {"name": "abc",
"phone":"55962441552" "phone":"55962441552"
} }.</b> } }.</code>
</li> </li>
<li>Make sure the data entered in correct format otherwise you <li>Make sure the data entered in correct format otherwise you
will get <b>'Invalid JSON data' message.</b></li> will get <b>'Invalid JSON data' message.</b></li>
<li>Response will be in this format - <b>{"New resource": <li>Response will be in this format - <code>{"New resource":
[{"id": 51, "name": "abc", "phone": "55962441552"}]}.</b> [{"id": 51, "name": "abc", "phone": "55962441552"}]}</code>.
</li> </li>
</ul> </ul>
</p> </p>
@ -373,7 +374,7 @@
Update Records Update Records
</h3> </h3>
<ul> <ul>
<li>Updation of records in the database can be done with PUT <li>Updation of records in the database can be done with <code>PUT</code>
method. method.
</li> </li>
<li>You have to provide the model and also the id or the record <li>You have to provide the model and also the id or the record
@ -383,14 +384,14 @@
will be always have to send request with your login will be always have to send request with your login
credentials. Otherwise, it will be showing access denied. credentials. Otherwise, it will be showing access denied.
</li> </li>
<li>The format for sending PUT request will be like this - <b>http://cybrosys:8016/send_request?model=res.partner&Id=46.</b> <li>The format for sending PUT request will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&Id=46.</code>
</li> </li>
<li>Here too you have to provide the JSON data through which the <li>Here too you have to provide the JSON data through which the
updates will be done. updates will be done.
</li> </li>
<li>The response format will be like this - <b>{"Updated <li>The response format will be like this - <code>{"Updated
resource": [{"id": 46, "email": "abc@example.com", "name": resource": [{"id": 46, "email": "abc@example.com", "name":
"Toni"}]}.</b></li> "Toni"}]}</code>.</li>
</ul> </ul>
</p> </p>
@ -401,7 +402,7 @@
</h3> </h3>
<p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;"> <p style="font-weight: 400; font-family: 'Montserrat', sans-serif; font-size: 14px;">
<ul> <ul>
<li>Database records can be deleted by sending DELETE method <li>Database records can be deleted by sending <code>DELETE</code> method
request. request.
</li> </li>
<li>For the deletion we have to provide the Model and the record <li>For the deletion we have to provide the Model and the record
@ -410,11 +411,11 @@
<li>Make sure you have permission to delete files for the <li>Make sure you have permission to delete files for the
selected model in the rest api record. selected model in the rest api record.
</li> </li>
<li>The delete request format will be like this - <b>http://cybrosys:8016/send_request?model=res.partner&Id=46.</b> <li>The delete request format will be like this - <code>http://cybrosys:8016/send_request?model=res.partner&Id=46.</code>
</li> </li>
<li> The response after successful deletion will be -<b> <li> The response after successful deletion will be -<code>
{"Resource deleted": [{"id": 46, "email": "abc@example.com", {"Resource deleted": [{"id": 46, "email": "abc@example.com",
"name": "Toni"}]}.</b></li> "name": "Toni"}]}</code>.</li>
</ul> </ul>
</p> </p>

Loading…
Cancel
Save