This guide will turn your Odoo server into a fully functional REST API server.
Understanding Odoo Controllers and Authentication
Odoo provides controller functionality where the attribute auth='none'
does not require authentication.
Alternatively, you can use auth='public', website=True
to bypass authentication.
Key Differences:
auth='public', website=True
: Runs in the Odoo environment, allowing access torequest.env[model_name]
to query the database. Each request is bound to a database (Odoo automatically selects the correct database based on the URL or headers).auth='none'
: Does not bind requests to any database.
When using auth='none'
, you must authenticate the user and specify the database for the request manually:
uid = request.session.authenticate(dbname, user, password)
Example:
uid = request.session.authenticate(odoo_db, 'admin', 'password')
After executing this command, the request will be bound to the specified user and database, allowing data queries.
Implementing JWT Authentication
This guide will use JWT (JSON Web Token) authentication, a widely used method for securing REST APIs.
Project Structure:
Note:Install libs python: ['pyjwt','simplejson']
for module working
jwt/rest_core.py
Implementation
import json
from odoo import http, _
from odoo.http import request, route
from odoo.addons.web.controllers.main import WebClient, Database
import werkzeug
import jwt
import datetime
import json
import functools
from ..constants import Api_Prefix,Secret_key
import os
def _response(headers, body, status=200, request_type='http'):
if request_type == 'json':
response = {}
response['error'] =[{
'code': status,
'message': body['message'],
}]
response['route'] = True
return response
try:
fixed_headers = {str(k): v for k, v in headers.items()}
except:
fixed_headers = headers
response = werkzeug.Response(response=json.dumps(body), status=status, headers=fixed_headers)
return response
def token_required(**kw):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kw):
headers = dict(request.httprequest.headers.items())
request_type = request._request_type
auth = headers.get('Authorization', None)
if not auth:
return {'error' : {'code': 403, 'message': 'No Authorization'}}
# parts = auth.split()
# if parts[0].lower() != 'bearer':
# return {'error' : {'code': 403, 'message': 'Authorization header must start with Bearer'}}
# elif len(parts) == 1:
# return {'error' : {'code': 403, 'message': 'Token not found'}}
# elif len(parts) > 2:
# return {'error' : {'code': 403, 'message': 'Authorization header must be Bearer + \s + token'}}
token = auth #parts[1]
try:
data = jwt.decode(token, Secret_key,'HS256')
kw['uid'] = data['uid']
except jwt.ExpiredSignatureError:
return {'error' : {'code': 401, 'message': 'Token is expired'}}
except jwt.DecodeError:
return {'error' : {'code': 401, 'message': 'Token signature is invalid'}}
response = f(*args, **kw)
return response
return wrapper
return decorator
def http_route(*args, **kwargs):
def decorator(controller_method):
@route(route=kwargs["route"] if len(args)==0 else args[0], methods=kwargs["methods"], type=kwargs.get('type','json'), auth=kwargs.get('auth','public'), csrf=kwargs.get('csrf',False))
@functools.wraps(controller_method)
def controller_method_wrapper(*iargs, **ikwargs):
response = controller_method(*iargs, **ikwargs)
return response
return controller_method_wrapper
return decorator
#request header 'Content-Type: application/json'
Defining Routes
class DatabaseSelector(Database):
@http.route(f'%s/database-list'%(Api_Prefix), type='json', auth="none", methods=['GET'])
def get_db_list(self, **kwargs):
result = super().list()
return {"result":result}
class ApiLogin(http.Controller):
def _response(self, headers, body, status=200):
try:
fixed_headers = {str(k): v for k, v in headers.items()}
except:
fixed_headers = headers
response = werkzeug.Response(response=body, status=status, headers=fixed_headers)
return response
@http_route('%s/login'%(Api_Prefix), type='json', methods=['POST'], auth='none', csrf=False)
def get_login(self, **kw):
headers = dict(request.httprequest.headers.items())
body = request.jsonrequest
username = body.get('username', False)
password = body.get('password', False)
database=body.get('dbname', False)
uid = body.get('user_id', False)
if username and password:
uid = request.session.authenticate(database, username, password) #or request.cr.dbname for dbname
if uid:
token = jwt.encode({'uid': uid, 'user' : username, 'db':database, 'exp' : datetime.datetime.utcnow() + datetime.timedelta(seconds=86400)}, Secret_key)
request_result = request.env['rest.cr'].login(uid)
if request_result:
if os.name == 'posix' and not isinstance(token, str): # Linux and Unix-like OS
request_result['access_token'] = token.decode('UTF-8')
else:
request_result['access_token'] = token
request_result['token_live'] = 86400
return {'result' : request_result}
Fetching Database List
** Using JavaScript Fetch API**
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
fetch("http://192.168.5.33:8069/api/database-list", {
method: "GET",
headers: myHeaders,
})
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.error(error));
Postman Example
JSON Payload for Login
{
"username": "admin_hr",
"password": "admin@",
"dbname": "hieu_hr"
}
JavaScript Fetch API for Login
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify({
"username": "admin_hr",
"password": "admin@",
"dbname": "hieu_hr"
});
fetch("http://192.168.5.33:8069/api/login", {
method: "POST",
headers: myHeaders,
body: raw,
})
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.error(error));
Expected Response
{
"success": true,
"data": {
"res_user": {
"id": 6,
"name": "admin_hr",
"is_admin": true
},
"access_token": "<JWT_TOKEN>",
"token_live": 86400
}
}
Fetching Data Using Token Authentication
** controllers/main.py
**
from odoo import http
from odoo.http import request
from ..constants import Api_Prefix
from ..jwt.rest_core import token_required
class MainController(http.Controller):
@http.route(f"%s/simple_model/list" % (Api_Prefix), type="json", methods=["GET"])
@token_required()
def get_simple_model_list(self, **kwargs):
data = (
request.env["simple.model"]
.with_user(kwargs.get("uid", 1))
.search(
[],
order="id desc",
)
)
result = data.read(["id", "name", "date_start", "description"])
return {"result": result}
@http.route(f"%s/simple_model/put" % (Api_Prefix), type="json", methods=["POST"])
@token_required()
def simple_model_put(self, **kwargs):
body = request.jsonrequest
name = body.get("name", False)
if not name :
raise RestException("400", "Invalid name")
try:
request.env["simple.model"].with_user(kwargs.get("uid", 1)).create({
'name': name
})
except Exception as e:
raise RestException("400", e.name)
Securing API Requests with Tokens
All API routes require a token in the request header:
"Authorization": "Bearer <JWT_TOKEN>"
With this setup, you have a secure and functional REST API integrated into Odoo.
Like
Reply