diff --git a/rest_api_odoo/Postman Collections/Odoo REST Api.postman_collection.zip b/rest_api_odoo/Postman Collections/Odoo REST Api.postman_collection.zip
deleted file mode 100644
index d2a8392f5..000000000
Binary files a/rest_api_odoo/Postman Collections/Odoo REST Api.postman_collection.zip and /dev/null differ
diff --git a/rest_api_odoo/__manifest__.py b/rest_api_odoo/__manifest__.py
index 76914b0e7..694df2e04 100644
--- a/rest_api_odoo/__manifest__.py
+++ b/rest_api_odoo/__manifest__.py
@@ -33,7 +33,7 @@
"depends": ['base', 'web'],
"data": [
'security/ir.model.access.csv',
- 'views/res_users_views.xml',
+ 'security/res_api_odoo_security.xml',
'views/connection_api_views.xml'
],
'images': ['static/description/banner.png'],
diff --git a/rest_api_odoo/controllers/main.py b/rest_api_odoo/controllers/main.py
index 37169cd3b..f5892d604 100644
--- a/rest_api_odoo/controllers/main.py
+++ b/rest_api_odoo/controllers/main.py
@@ -22,222 +22,244 @@
import json
import logging
-from odoo import http
-from odoo.http import request
+
+from datetime import datetime
+from odoo import http, models
+from odoo.http import request, Response
+from ast import literal_eval
_logger = logging.getLogger(__name__)
-class RestApi(http.Controller):
- """This is a controller which is used to generate responses based on the
- api requests"""
+class IrHttp(models.AbstractModel):
+ """This model is used to authenticate the api-key when sending a request"""
+ _inherit = "ir.http"
- def auth_api_key(self, api_key):
+ @classmethod
+ def _auth_method_rest_api(cls):
"""This function is used to authenticate the api-key when sending a
request"""
- user_id = request.env['res.users'].search([('api_key', '=', api_key)])
- if api_key is not None and user_id:
- response = True
- elif not user_id:
- response = ('
Invalid API Key '
- '!
')
- else:
- response = ("No API Key Provided "
- "!
")
+ try:
+ api_key = request.httprequest.headers.get("Authorization").lstrip("Bearer ")
+ if not api_key:
+ raise PermissionError("Invalid API key provided.")
+
+ user_id = request.env["res.users.apikeys"]._check_credentials(
+ scope="rpc", key=api_key
+ )
+ request.update_env(user_id)
+ except Exception as e:
+ _logger.error(e)
+ return Response(
+ json.dumps({"message": e.args[0]}),
+ status=401,
+ content_type="application/json",
+ )
+
+
+class RestApi(http.Controller):
+ """This is a controller which is used to generate responses based on the
+ api requests"""
+
+ def simplest_type(self, input):
+ """Try cast input into native Python class, otherwise return as string"""
+ try:
+ return literal_eval(input)
+ except Exception:
+ # Handle lowercase booleans
+ if input == "true":
+ return True
+ if input == "false":
+ return False
+ return input
- return response
+ def sanitize_records(self, records):
+ """Sanitize records for response"""
+ for record in records:
+ for key, value in record.items():
+ # Manually convert datetime fields to string format
+ if isinstance(value, datetime):
+ record[key] = value.isoformat()
+ return records
- 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
of request and the parameters given"""
- option = request.env['connection.api'].search(
- [('model_id', '=', model)], limit=1)
- model_name = option.model_id.model
-
- if method != 'DELETE':
- data = json.loads(request.httprequest.data)
- else:
- data = {}
- fields = []
- if data:
- for field in data['fields']:
- fields.append(field)
- if not fields and method != 'DELETE':
- return ("No fields selected for the model"
- "
")
- if not option:
- return ("No Record Created for the model"
- "
")
try:
- if method == 'GET':
- fields = []
- for field in data['fields']:
+ model = query.pop("model")
+ option = request.env["connection.api"].search(
+ [("model_id", "=", model)], limit=1
+ )
+ model_name = option.model_id.model
+ model_display_name = option.model_id.name
+
+ try:
+ data = json.loads(request.httprequest.data)
+ except Exception:
+ data = {}
+
+ fields = []
+ if data:
+ for field in data["fields"]:
fields.append(field)
+
+ # Return records' ID by default if not specified
+ if not fields:
+ fields.append("id")
+
+ # Get all model's fields if wildcard is used
+ if "*" in fields:
+ fields = []
+ 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"):
+ fields.append(field)
+ if not option:
+ raise NotImplementedError("No Record Created for the model. ")
+ if method == "GET":
if not option.is_get:
- return ("Method Not Allowed"
- "
")
- else:
- datas = []
- if rec_id != 0:
- partner_records = request.env[
- str(model_name)].search_read(
- domain=[('id', '=', rec_id)],
- fields=fields
- )
- data = json.dumps({
- 'records': partner_records
- })
- 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
- })
- datas.append(data)
- return request.make_response(data=datas)
- except:
- return ("Invalid JSON Data"
- "
")
- if method == 'POST':
- if not option.is_post:
- return ("Method Not Allowed"
- "
")
- else:
- try:
- data = json.loads(request.httprequest.data)
- datas = []
- new_resource = request.env[str(model_name)].create(
- data['values'])
- partner_records = request.env[
- str(model_name)].search_read(
- domain=[('id', '=', new_resource.id)],
- fields=fields
- )
- new_data = json.dumps({'New resource': partner_records, })
- datas.append(new_data)
- return request.make_response(data=datas)
- except:
- return ("Invalid JSON Data"
- "
")
- if method == 'PUT':
- if not option.is_put:
- return ("Method Not Allowed"
- "
")
- else:
- if rec_id == 0:
- return ("No ID Provided"
- "
")
- else:
- resource = request.env[str(model_name)].browse(
- int(rec_id))
- if not resource.exists():
- return ("Resource not found"
- "
")
- else:
- try:
- datas = []
- data = json.loads(request.httprequest.data)
- resource.write(data['values'])
- partner_records = request.env[
- str(model_name)].search_read(
- domain=[('id', '=', resource.id)],
- fields=fields
- )
- new_data = json.dumps(
- {'Updated resource': partner_records,
- })
- datas.append(new_data)
- return request.make_response(data=datas)
-
- except:
- return ("Invalid JSON Data "
- "!
")
- if method == 'DELETE':
- if not option.is_delete:
- return ("Method Not Allowed"
- "
")
- else:
- if rec_id == 0:
- return ("No ID Provided"
- "
")
- else:
- resource = request.env[str(model_name)].browse(
- int(rec_id))
- if not resource.exists():
- return ("Resource not found"
- "
")
- else:
-
- records = request.env[
- str(model_name)].search_read(
- domain=[('id', '=', resource.id)],
- fields=['id', 'display_name']
- )
- remove = json.dumps(
- {"Resource deleted": records,
- })
- resource.unlink()
- return request.make_response(data=remove)
-
- @http.route(['/send_request'], type='http',
- auth='none',
- methods=['GET', 'POST', 'PUT', 'DELETE'], csrf=False)
+ raise NameError()
+ limit = 0
+ if query.get("limit"):
+ limit = int(str(query.get("limit")))
+ offset = 0
+ if query.get("offset"):
+ offset = int(str(query.get("offset")))
+
+ domains = []
+ for key, value in query.items():
+ if not (key == "limit" or key == "offset"):
+ domains.append((key, "=", self.simplest_type(value)))
+ partner_records = request.env[str(model_name)].search_read(
+ domains, fields, limit=limit, offset=offset
+ )
+
+ return Response(
+ json.dumps({"records": self.sanitize_records(partner_records)}),
+ content_type="application/json",
+ )
+ if method == "POST":
+ if not option.is_post:
+ raise NotImplementedError()
+ if not data or "values" not in data:
+ raise ValueError("No Data Provided")
+
+ data = json.loads(request.httprequest.data)
+ new_resource = request.env[str(model_name)].create(data["values"])
+ partner_records = request.env[str(model_name)].search_read(
+ [("id", "=", new_resource.id)], fields
+ )
+ return Response(
+ json.dumps({"new_record": self.sanitize_records(partner_records)}),
+ status=201,
+ content_type="application/json",
+ )
+ if method == "PUT":
+ if not option.is_put:
+ raise NotImplementedError()
+
+ if "id" not in query:
+ raise ValueError("No ID Provided")
+ if not data or "values" not in data:
+ raise ValueError("No Data Provided")
+
+ resource_id = str(query.get("id"))
+ resource = request.env[str(model_name)].browse(int(resource_id))
+ if not resource.exists():
+ raise ValueError("Resource not found")
+
+ data = json.loads(request.httprequest.data)
+ resource.write(data["values"])
+ partner_records = request.env[str(model_name)].search_read(
+ [("id", "=", resource.id)], fields
+ )
+ return Response(
+ json.dumps(
+ {"updated_record": self.sanitize_records(partner_records)}
+ ),
+ content_type="application/json",
+ )
+ if method == "DELETE":
+ if not option.is_delete:
+ raise NotImplementedError()
+
+ if "id" not in query:
+ raise ValueError("No ID Provided")
+
+ resource_id = str(query.get("id"))
+ resource = request.env[str(model_name)].browse(int(resource_id))
+ if not resource.exists():
+ raise ValueError("Resource not found")
+
+ partner_records = request.env[str(model_name)].search_read(
+ [("id", "=", resource.id)], fields
+ )
+ resource.unlink()
+ return Response(
+ json.dumps(
+ {
+ "message": "Resource deleted",
+ "data": self.sanitize_records(partner_records),
+ }
+ ),
+ status=202,
+ content_type="application/json",
+ )
+
+ # If not using any method above, simply return an error
+ raise NotImplementedError()
+ except ValueError as e:
+ return Response(
+ json.dumps({"message": e.args[0]}),
+ status=403,
+ content_type="application/json",
+ )
+ except NotImplementedError as e:
+ return Response(
+ json.dumps(
+ {
+ "message": f"Method not allowed. {e.args[0]}Please contact your admininstrator to enable {method} method for {model_display_name or 'this'} record."
+ }
+ ),
+ status=405,
+ content_type="application/json",
+ )
+ except Exception as e:
+ _logger.error(e)
+ return Response(
+ json.dumps({"message": f"Internal server error. {e.args[0]}"}),
+ status=500,
+ content_type="application/json",
+ )
+
+ @http.route(
+ ["/send_request"],
+ type="http",
+ auth="rest_api",
+ methods=["GET", "POST", "PUT", "DELETE"],
+ csrf=False,
+ )
def fetch_data(self, **kw):
"""This controller will be called when sending a request to the
specified url, and it will authenticate the api-key and then will
generate the result"""
http_method = request.httprequest.method
- api_key = request.httprequest.headers.get('api-key')
- auth_api = self.auth_api_key(api_key)
- model = kw.get('model')
- username = request.httprequest.headers.get('login')
- password = request.httprequest.headers.get('password')
- request.session.authenticate(request.session.db, username,
- password)
- model_id = request.env['ir.model'].search(
- [('model', '=', model)])
+ model = kw.pop("model")
+ model_id = request.env["ir.model"].search([("model", "=", model)])
if not model_id:
- return ("Invalid model, check spelling or maybe "
- "the related "
- "module is not installed"
- "
")
-
- if auth_api == True:
- if not kw.get('Id'):
- rec_id = 0
- else:
- rec_id = int(kw.get('Id'))
- result = self.generate_response(http_method, model_id.id, rec_id)
- return result
- else:
- return auth_api
-
- @http.route(['/odoo_connect'], type="http", auth="none", csrf=False,
- methods=['GET'])
- def odoo_connect(self, **kw):
- """This is the controller which initializes the api transaction by
- generating the api-key for specific user and database"""
-
- username = request.httprequest.headers.get('login')
- password = request.httprequest.headers.get('password')
- db = request.httprequest.headers.get('db')
- try:
- request.session.update(http.get_default_session(), db=db)
- auth = request.session.authenticate(request.session.db, username,
- password)
- user = request.env['res.users'].browse(auth)
- api_key = request.env.user.generate_api(username)
- datas = json.dumps({"Status": "auth successful",
- "User": user.name,
- "api-key": api_key})
- return request.make_response(data=datas)
- except:
- return ("wrong login credentials"
- "
")
+ return Response(
+ json.dumps(
+ {
+ "message": "Invalid model, check spelling or maybe the related module is not installed"
+ }
+ ),
+ status=403,
+ content_type="application/json",
+ )
+
+ return self.generate_response(http_method, model=model_id.id, **kw)
diff --git a/rest_api_odoo/models/__init__.py b/rest_api_odoo/models/__init__.py
index e9bf22051..27f8df7c3 100644
--- a/rest_api_odoo/models/__init__.py
+++ b/rest_api_odoo/models/__init__.py
@@ -21,4 +21,3 @@
#############################################################################
from . import connection_api
-from . import res_users
diff --git a/rest_api_odoo/models/res_users.py b/rest_api_odoo/models/res_users.py
deleted file mode 100644
index a2d861f36..000000000
--- a/rest_api_odoo/models/res_users.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding:utf-8 -*-
-#############################################################################
-#
-# Cybrosys Technologies Pvt. Ltd.
-#
-# Copyright (C) 2023-TODAY Cybrosys Technologies()
-# Author: Cybrosys Techno Solutions()
-#
-# You can modify it under the terms of the GNU LESSER
-# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
-#
-# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
-# (LGPL v3) along with this program.
-# If not, see .
-#
-#############################################################################
-
-import uuid
-from odoo import fields, models
-
-
-class UserLogin(models.Model):
- """This class is used to inherit users and add api key generation"""
- _inherit = 'res.users'
-
- api_key = fields.Char(string="API Key", readonly=True,
- help="Api key for connecting with the "
- "Database.The key will be "
- "generated when authenticating "
- "rest api.")
-
- def generate_api(self, username):
- """This function is used to generate api-key for each user"""
- users = self.env['res.users'].sudo().search([('login', '=', username)])
- if not users.api_key:
- users.api_key = str(uuid.uuid4())
- key = users.api_key
- else:
- key = users.api_key
- return key
diff --git a/rest_api_odoo/security/res_api_odoo_security.xml b/rest_api_odoo/security/res_api_odoo_security.xml
new file mode 100644
index 000000000..545fd5132
--- /dev/null
+++ b/rest_api_odoo/security/res_api_odoo_security.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ REST API
+ Helps you manage your REST API records.
+ 17
+
+
+
+ Manager
+
+
+
+
+
+
+
+
+
+ All REST APIs
+
+ [(1,'=',1)]
+
+
+
+
+
\ No newline at end of file
diff --git a/rest_api_odoo/static/description/assets/screenshots/screenshot_2.png b/rest_api_odoo/static/description/assets/screenshots/screenshot_2.png
index c892dd068..94edbd0e0 100644
Binary files a/rest_api_odoo/static/description/assets/screenshots/screenshot_2.png and b/rest_api_odoo/static/description/assets/screenshots/screenshot_2.png differ
diff --git a/rest_api_odoo/static/description/index.html b/rest_api_odoo/static/description/index.html
index bf41854b7..fff5c7eb1 100644
--- a/rest_api_odoo/static/description/index.html
+++ b/rest_api_odoo/static/description/index.html
@@ -23,7 +23,7 @@
- Odoo rest API
+ Odoo Rest API

@@ -138,7 +138,7 @@

-
Authentication using the generated api key.
+
Authentication using the generated API key.
@@ -217,29 +217,21 @@
First of all, we have to add a new parameter in odoo conf.
file.
-
server_wide_modules = web, base, rest_api_odoo
+ server_wide_modules = web, base, rest_api_odoo
- This will allow us to send request to server without
selecting database first.
- Incase if you have to
uninstall the module , you have to remove this parameter.
- Next we can install the module.
-
After installing the Rest api app we can see a new api key
+ After installing the Rest API app we can see a new API key
field in users.
- - Next we have to generate the api-key for the current
+ - Next we have to generate the API-key for the current
user.
-
You can import the postman collections provided in the app
- folder for authentication and interacting with database in
- various methods.
-

-
-
-
@@ -248,27 +240,8 @@
- - Next you have to select the database and login.
- - We have attached Postman collections through which
- you can
- authenticate rest api.
-
- - First, extract the zip file. Then, you will obtain the JSON-format file, which you can directly import into POSTMAN.
- - The url format will be like this - http://cybrosys:8016/odoo_connect
- Replace 'cybrosys:8016' with your localhost port number.
-
- - You have to provide database name, username and password
- through the headers while sending request.
-
- - If the authentication is successful , an api key will be
- generated for the current user.
-
- - This key will be used when sending api requests to
- database.
-
- - The response will be like this - {"Status": "auth
- successful", "User": "Mitchell Admin", "api-key":
- "66c2ebab-d4dc-42f0-87d0-d1646e887569"}.
+ - Next you have to generate the API key under My Profile > Account Security.
+ - Set the name of the API key according to your needs.
- Create records in Rest api app
+ Create records in Rest API app
- - After rest api authentication, we can create records in the
- rest api app.
+
- After creating the API key, we can create records in the
+ Rest API app.
- Here we can choose the model, and also we can
choose the http methods.
- - The api response will be based on these records.
+ - The API response will be based on these records.
- - You can send GET request to retrieve data from the
+
- You can send
GET
request to retrieve data from the
database.
- - The postman collection has been provided with app files for
- sending request from postman.
-
- - You have to provide username, password and api key through
- the header.
+
- You have to provide the API key in the header:
Authorization: Bearer {API_KEY}
+ where {API_KEY}
should be replaced with the actual API key.
- - Model can be passed as argument as the technical name , and
- also if you want
- specific record you can provide the id as well.
+
- Model can be passed as argument as the technical name, and
+ also if you want to get a specific record you can provide any
+ of the related fields to the module as well.
- - The format for GET method will be like this - http://cybrosys:8016/send_request?model=res.partner&Id=10.
+
- The format for GET method will be like this -
http://cybrosys:8016/send_request?model=res.partner&id=10.
- We can specify the fields inside the JSON data, and it will
- be like this - {"fields": ["name", "email"]}.
- - This is the format of api response - {"records": [{"id":
+ be like this -
{"fields": ["name", "email"]}
.
+ If no fields are passed , it will returns just the record's ID.
+ To get all of the fields, set the fields to wildcard - {"fields": ["*"]}
.
+ Pagination can also be used by adding {"limit": 10, "offset": 0}
+ to the query parameter above.
+
+ - This is the format of API response -
{"records": [{"id":
10, "email": "deco.addict82@example.com", "name": "Deco
- Addict"}]}.
+ Addict"}]}
.
@@ -338,31 +313,28 @@
- - Using POST method , you can create new records in the
+
- Using
POST
method , you can create new records in the
database.
- - Just make sure you enabled POST method for the model record
- in rest api app , otherwise you will get 'method not
+
- Just make sure you enabled
POST
method for the model record
+ in Rest API app , otherwise you will get 'method not
allowed' message.
- For creating record you have to provide the JSON data along
with the model.
- - You can make use of the postman collection that we have
- added with app files.
-
- - The format for sending POST request will be like this - http://cybrosys:8016/send_request?model=res.partner.
+
- The format for sending POST request will be like this -
http://cybrosys:8016/send_request?model=res.partner.
- - This is the format for JSON data - {
+
- This is the format for JSON data -
{
"fields" :["name", "phone"] ,
"values": {"name": "abc",
"phone":"55962441552"
- } }.
+ } }.
- Make sure the data entered in correct format otherwise you
will get 'Invalid JSON data' message.
- - Response will be in this format - {"New resource":
- [{"id": 51, "name": "abc", "phone": "55962441552"}]}.
+
- Response will be in this format -
{"New resource":
+ [{"id": 51, "name": "abc", "phone": "55962441552"}]}
.
@@ -373,24 +345,20 @@
Update Records
- - Updation of records in the database can be done with PUT
+
- Updation of records in the database can be done with
PUT
method.
- You have to provide the model and also the id or the record
that you want to update.
- - You can use the Postman collection that we have provided and , you
- will be always have to send request with your login
- credentials. Otherwise, it will be showing access denied.
-
- - The format for sending PUT request will be like this - http://cybrosys:8016/send_request?model=res.partner&Id=46.
+
- The format for sending
PUT
request will be like this - http://cybrosys:8016/send_request?model=res.partner&Id=46.
- Here too you have to provide the JSON data through which the
updates will be done.
- - The response format will be like this - {"Updated
+
- The response format will be like this -
{"Updated
resource": [{"id": 46, "email": "abc@example.com", "name":
- "Toni"}]}.
+ "Toni"}]}.
@@ -401,20 +369,20 @@
- - Database records can be deleted by sending DELETE method
+
- Database records can be deleted by sending
DELETE
method
request.
- For the deletion we have to provide the Model and the record
id that we want to delete.
- Make sure you have permission to delete files for the
- selected model in the rest api record.
+ selected model in the Rest API record.
- - The delete request format will be like this - http://cybrosys:8016/send_request?model=res.partner&Id=46.
+
- The delete request format will be like this -
http://cybrosys:8016/send_request?model=res.partner&Id=46.
- - The response after successful deletion will be -
+
- The response after successful deletion will be -
{"Resource deleted": [{"id": 46, "email": "abc@example.com",
- "name": "Toni"}]}.
+ "name": "Toni"}]}.
@@ -843,7 +811,7 @@