Browse Source

Enhance REST API controller: GET params support and safe serialization

- Added support for retrieving fields via query parameters in GET requests, in addition to JSON body.
- Improved response serialization, including:
  * Conversion of datetime/date to ISO format.
  * Conversion of binary fields to base64.
  * Safe handling of non-serializable iterables.
- Added error handling blocks with logging for better traceability.
- Simplified fields extraction logic in GET/POST/PUT.
- Preserved compatibility with api-key authentication and authorization logic.
pull/401/head
Bernat Roig 3 weeks ago
committed by GitHub
parent
commit
6acd4f6511
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 147
      rest_api_odoo/controllers/rest_api_odoo.py

147
rest_api_odoo/controllers/rest_api_odoo.py

@ -21,6 +21,7 @@
############################################################################# #############################################################################
import json import json
import logging import logging
import base64
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from datetime import datetime, date from datetime import datetime, date
@ -46,21 +47,65 @@ class RestApi(http.Controller):
"!</h2></body></html>") "!</h2></body></html>")
return response return response
def generate_response(self, method, model, rec_id): def generate_response(self, method, model, rec_id, request_params=None):
"""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"""
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
if method != 'DELETE':
# Handle data based on method
if method == 'GET':
# For GET requests, check both JSON body and query parameters
data = request_params or {}
fields = []
# First, try to get fields from JSON body
try:
if request.httprequest.data:
json_data = json.loads(request.httprequest.data)
if 'fields' in json_data:
fields = json_data['fields']
except json.JSONDecodeError:
pass # If JSON is invalid, continue with query params
# If no fields from JSON, try query parameters
if not fields:
fields_param = data.get('fields', '')
if fields_param:
fields = [field.strip() for field in fields_param.split(',')]
# If still no fields, use defaults based on record type
if not fields:
if rec_id != 0:
# For specific record, get all fields
fields = None # This will get all available fields
else:
# For all records, use minimal fields
fields = ['id', 'display_name']
elif method != 'DELETE':
# For POST/PUT requests, parse JSON from body
try:
if request.httprequest.data:
data = json.loads(request.httprequest.data) data = json.loads(request.httprequest.data)
else: else:
data = {} data = {}
except json.JSONDecodeError:
return ("<html><body><h2>Invalid JSON Data"
"</h2></body></html>")
else:
# DELETE method
data = {}
fields = []
# Extract fields for POST/PUT methods
if method in ['POST', 'PUT'] and data:
fields = [] fields = []
if data: if 'fields' in data:
for field in data['fields']: for field in data['fields']:
fields.append(field) fields.append(field)
if not fields and method != 'DELETE':
if not fields and method != 'DELETE' and method != 'GET':
return ("<html><body><h2>No fields selected for the model" return ("<html><body><h2>No fields selected for the model"
"</h2></body></html>") "</h2></body></html>")
if not option: if not option:
@ -68,34 +113,55 @@ class RestApi(http.Controller):
"</h2></body></html>") "</h2></body></html>")
try: try:
if method == 'GET': if method == 'GET':
fields = []
for field in data['fields']:
fields.append(field)
if not option.is_get: if not option.is_get:
return ("<html><body><h2>Method Not Allowed" return ("<html><body><h2>Method Not Allowed"
"</h2></body></html>") "</h2></body></html>")
else: else:
datas = [] datas = []
if rec_id != 0: if rec_id != 0:
# For specific record
search_fields = fields if fields is not None else []
partner_records = request.env[ partner_records = request.env[
str(model_name)].search_read( str(model_name)].search_read(
domain=[('id', '=', rec_id)], domain=[('id', '=', rec_id)],
fields=fields fields=search_fields
) )
for record in partner_records: for record in partner_records:
for key, value in record.items(): for key, value in record.items():
if isinstance(value, (datetime, date)): if isinstance(value, (datetime, date)):
record[key] = value.isoformat() record[key] = value.isoformat()
elif isinstance(value, bytes):
# Convert bytes to base64 string for JSON serialization
import base64
record[key] = base64.b64encode(value).decode('utf-8')
elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
# Handle other non-serializable iterables
try:
record[key] = list(value) if value else []
except:
record[key] = str(value)
elif isinstance(value, bytes):
# Convert bytes to base64 string for JSON serialization
import base64
record[key] = base64.b64encode(value).decode('utf-8')
elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
# Handle other non-serializable iterables
try:
record[key] = list(value) if value else []
except:
record[key] = str(value)
data = json.dumps({ data = json.dumps({
'records': partner_records 'records': partner_records
}) })
datas.append(data) datas.append(data)
return request.make_response(data=datas) return request.make_response(data=datas)
else: else:
# For all records
search_fields = fields if fields is not None else ['id', 'display_name']
partner_records = request.env[ partner_records = request.env[
str(model_name)].search_read( str(model_name)].search_read(
domain=[], domain=[],
fields=fields fields=search_fields
) )
for record in partner_records: for record in partner_records:
for key, value in record.items(): for key, value in record.items():
@ -106,16 +172,17 @@ class RestApi(http.Controller):
}) })
datas.append(data) datas.append(data)
return request.make_response(data=datas) return request.make_response(data=datas)
except: except Exception as e:
return ("<html><body><h2>Invalid JSON Data" _logger.error(f"Error in GET method: {str(e)}")
return ("<html><body><h2>Error processing request"
"</h2></body></html>") "</h2></body></html>")
if method == 'POST': if method == 'POST':
if not option.is_post: if not option.is_post:
return ("<html><body><h2>Method Not Allowed" return ("<html><body><h2>Method Not Allowed"
"</h2></body></html>") "</h2></body></html>")
else: else:
try: try:
data = json.loads(request.httprequest.data)
datas = [] datas = []
new_resource = request.env[str(model_name)].create( new_resource = request.env[str(model_name)].create(
data['values']) data['values'])
@ -128,12 +195,34 @@ class RestApi(http.Controller):
for key, value in record.items(): for key, value in record.items():
if isinstance(value, (datetime, date)): if isinstance(value, (datetime, date)):
record[key] = value.isoformat() record[key] = value.isoformat()
elif isinstance(value, bytes):
# Convert bytes to base64 string for JSON serialization
import base64
record[key] = base64.b64encode(value).decode('utf-8')
elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
# Handle other non-serializable iterables
try:
record[key] = list(value) if value else []
except:
record[key] = str(value)
elif isinstance(value, bytes):
# Convert bytes to base64 string for JSON serialization
import base64
record[key] = base64.b64encode(value).decode('utf-8')
elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
# Handle other non-serializable iterables
try:
record[key] = list(value) if value else []
except:
record[key] = str(value)
new_data = json.dumps({'New resource': partner_records, }) new_data = json.dumps({'New resource': partner_records, })
datas.append(new_data) datas.append(new_data)
return request.make_response(data=datas) return request.make_response(data=datas)
except: except Exception as e:
_logger.error(f"Error in POST method: {str(e)}")
return ("<html><body><h2>Invalid JSON Data" return ("<html><body><h2>Invalid JSON Data"
"</h2></body></html>") "</h2></body></html>")
if method == 'PUT': if method == 'PUT':
if not option.is_put: if not option.is_put:
return ("<html><body><h2>Method Not Allowed" return ("<html><body><h2>Method Not Allowed"
@ -151,7 +240,6 @@ class RestApi(http.Controller):
else: else:
try: try:
datas = [] datas = []
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(
@ -162,15 +250,37 @@ class RestApi(http.Controller):
for key, value in record.items(): for key, value in record.items():
if isinstance(value, (datetime, date)): if isinstance(value, (datetime, date)):
record[key] = value.isoformat() record[key] = value.isoformat()
elif isinstance(value, bytes):
# Convert bytes to base64 string for JSON serialization
import base64
record[key] = base64.b64encode(value).decode('utf-8')
elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
# Handle other non-serializable iterables
try:
record[key] = list(value) if value else []
except:
record[key] = str(value)
elif isinstance(value, bytes):
# Convert bytes to base64 string for JSON serialization
import base64
record[key] = base64.b64encode(value).decode('utf-8')
elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
# Handle other non-serializable iterables
try:
record[key] = list(value) if value else []
except:
record[key] = str(value)
new_data = json.dumps( new_data = json.dumps(
{'Updated resource': partner_records, {'Updated resource': partner_records,
}) })
datas.append(new_data) datas.append(new_data)
return request.make_response(data=datas) return request.make_response(data=datas)
except: except Exception as e:
_logger.error(f"Error in PUT method: {str(e)}")
return ("<html><body><h2>Invalid JSON Data " return ("<html><body><h2>Invalid JSON Data "
"!</h2></body></html>") "!</h2></body></html>")
if method == 'DELETE': if method == 'DELETE':
if not option.is_delete: if not option.is_delete:
return ("<html><body><h2>Method Not Allowed" return ("<html><body><h2>Method Not Allowed"
@ -186,7 +296,6 @@ class RestApi(http.Controller):
return ("<html><body><h2>Resource not found" return ("<html><body><h2>Resource not found"
"</h2></body></html>") "</h2></body></html>")
else: else:
records = request.env[ records = request.env[
str(model_name)].search_read( str(model_name)].search_read(
domain=[('id', '=', resource.id)], domain=[('id', '=', resource.id)],
@ -227,7 +336,8 @@ class RestApi(http.Controller):
rec_id = 0 rec_id = 0
else: else:
rec_id = int(kw.get('Id')) rec_id = int(kw.get('Id'))
result = self.generate_response(http_method, model_id.id, rec_id) # Pass the query parameters for GET requests
result = self.generate_response(http_method, model_id.id, rec_id, kw)
return result return result
else: else:
return auth_api return auth_api
@ -252,6 +362,7 @@ class RestApi(http.Controller):
"User": user.name, "User": user.name,
"api-key": api_key}) "api-key": api_key})
return request.make_response(data=datas) return request.make_response(data=datas)
except: except Exception as e:
_logger.error(f"Error in authentication: {str(e)}")
return ("<html><body><h2>wrong login credentials" return ("<html><body><h2>wrong login credentials"
"</h2></body></html>") "</h2></body></html>")

Loading…
Cancel
Save