Odoo Controller Basics
Controllers handle HTTP requests in Odoo. They map URL routes to Python methods that return responses: HTML pages, JSON data, file downloads, or redirects. Understanding controllers is essential for building web pages, APIs, and portal features.
Basic HTTP Controller
from odoo import http
from odoo.http import request
class MyController(http.Controller):
@http.route('/my/page', type='http', auth='user', website=True)
def my_page(self, **kwargs):
records = request.env['my.model'].search([])
return request.render('my_module.my_template', {
'records': records,
})
Route Decorator Parameters
The @http.route() decorator configures endpoint behavior:
@http.route(
'/api/tickets', # URL path
type='http', # 'http' or 'json'
auth='user', # 'none', 'public', 'user'
methods=['GET', 'POST'], # allowed HTTP methods
csrf=True, # CSRF protection (default True for http)
website=True, # use website layout
sitemap=False, # include in sitemap
cors='*', # CORS header
)
def api_tickets(self, **kwargs):
pass
Authentication Modes
auth='none': no authentication, no database access. For health checks and static endpoints.auth='public': allows both authenticated and anonymous users. Userequest.env.userto check if logged in.auth='user': requires login. Redirects to login page if not authenticated.
Request Types
type='http': standard HTTP. Receives form data, query params. Returns HTML, files, redirects.type='json': JSON-RPC. Receives/returns JSON. Automatically parses request body and wraps response.
HTTP Response Types
# Render a QWeb template
return request.render('module.template_id', values)
# Redirect
return request.redirect('/my/other-page')
# Return raw response
return request.make_response(
'Hello World',
headers=[('Content-Type', 'text/plain')])
# JSON response (from type='http' route)
import json
return request.make_response(
json.dumps({'status': 'ok'}),
headers=[('Content-Type', 'application/json')])
# File download
return request.make_response(
file_content,
headers=[
('Content-Type', 'application/octet-stream'),
('Content-Disposition', 'attachment; filename="report.pdf"'),
])
# 404
return request.not_found()
JSON-RPC Controllers
JSON-RPC endpoints automatically parse JSON requests and wrap responses:
@http.route('/api/tickets/create', type='json', auth='user')
def create_ticket(self, name, description, priority='1'):
ticket = request.env['support.ticket'].create({
'name': name,
'description': description,
'priority': priority,
})
return {'id': ticket.id, 'name': ticket.name}
JSON-RPC requests must use POST with Content-Type: application/json and a body like:
{"jsonrpc": "2.0", "method": "call",
"params": {"name": "Bug", "description": "Details"}}
REST-like API Pattern
Build REST-style endpoints using type='http' with explicit JSON handling:
import json
class TicketAPI(http.Controller):
@http.route('/api/v1/tickets', type='http',
auth='user', methods=['GET'], csrf=False)
def list_tickets(self, **kwargs):
tickets = request.env['support.ticket'].search_read(
[], ['name', 'state', 'priority'], limit=50)
return request.make_response(
json.dumps({'data': tickets}),
headers=[('Content-Type', 'application/json')])
@http.route('/api/v1/tickets', type='http',
auth='user', methods=['POST'], csrf=False)
def create_ticket(self, **kwargs):
data = json.loads(request.httprequest.data)
ticket = request.env['support.ticket'].create({
'name': data['name'],
'description': data.get('description', ''),
})
return request.make_response(
json.dumps({'id': ticket.id}),
headers=[('Content-Type', 'application/json')])
@http.route('/api/v1/tickets/<int:ticket_id>',
type='http', auth='user',
methods=['GET'], csrf=False)
def get_ticket(self, ticket_id, **kwargs):
ticket = request.env['support.ticket'].browse(ticket_id)
if not ticket.exists():
return request.make_response(
json.dumps({'error': 'Not found'}),
status=404,
headers=[('Content-Type', 'application/json')])
return request.make_response(
json.dumps(ticket.read(['name', 'state'])[0]),
headers=[('Content-Type', 'application/json')])
Controller Inheritance
Extend existing controllers by inheriting from the original class:
from odoo.addons.website_sale.controllers.main import WebsiteSale
class WebsiteSaleCustom(WebsiteSale):
@http.route() # keep original route
def shop(self, **kwargs):
response = super().shop(**kwargs)
# Modify the response
response.qcontext['custom_data'] = 'value'
return response
Use @http.route() without arguments to keep the parent's route configuration. Override with new arguments to change the route.
Request Object
The request object provides access to:
# Database environment (ORM)
request.env['model.name'].search([])
# Current user
request.env.user
request.uid
# HTTP request details
request.httprequest.method # GET, POST, etc.
request.httprequest.path # /my/page
request.httprequest.args # query parameters
request.httprequest.form # form data
request.httprequest.data # raw body bytes
request.httprequest.headers # request headers
request.httprequest.remote_addr # client IP
# Session
request.session.sid # session ID
request.session.uid # user ID
# Database cursor
request.env.cr # for raw SQL
CSRF Protection
Odoo enables CSRF protection by default for type='http' POST routes. To handle this:
<!-- In forms, include the CSRF token -->
<form method="POST" action="/my/endpoint">
<input type="hidden" name="csrf_token"
t-att-value="request.csrf_token()"/>
<!-- form fields -->
</form>
# Or disable CSRF for API endpoints
@http.route('/api/endpoint', csrf=False)
Error Handling
from odoo.exceptions import AccessError, UserError
from werkzeug.exceptions import NotFound, Forbidden
@http.route('/api/resource/<int:res_id>', type='http', auth='user')
def get_resource(self, res_id, **kwargs):
try:
record = request.env['my.model'].browse(res_id)
if not record.exists():
raise NotFound('Resource not found')
data = record.read(['name', 'value'])
return request.make_response(
json.dumps(data), headers=[...])
except AccessError:
raise Forbidden('Access denied')
except Exception as e:
_logger.exception('API error')
return request.make_response(
json.dumps({'error': str(e)}), status=500,
headers=[('Content-Type', 'application/json')])