In the Odoo 19 framework, controllers serve as the critical bridge between the web browser and the server's business logic. They process incoming HTTP requests, orchestrate interactions with Odoo models, and ultimately dictate the response, whether it's a rendered webpage, JSON data, or a file download. A common and powerful customization technique involves overriding existing controllers to modify default behavior, inject new data, or completely alter response logic without changing the core codebase.
This guide outlines the systematic process for extending and overriding controllers in Odoo 19.
Establishing the Module Structure
The first step is to ensure your custom module is properly structured to hold controller files.
- Create a /controllers directory at the root of your module.
- Inside this directory, create an __init__.py file. This file must import any other Python files you create within the controllers folder.
- Create your controller file (e.g., custom_controllers.py).
Your module structure should look like this:
my_custom_module/
+-- controllers/
¦ +-- __init__.py
¦ +-- custom_controllers.py
+-- __init__.py
+-- __manifest__.py
+-- ... (other files)
The __init__.py inside the controllers directory should contain:
from . import custom_controllers
And the main __init__.py file should import the controllers directory:
from . import controllers
Importing the Base Controller
Within your new controller file, you must import the original controller you intend to extend. This is typically found within the controllers directory of another Odoo addon.
from odoo import http
from odoo.addons.website_sale.controllers.main import WebsiteSale
The Two Primary Override Techniques
Odoo developers primarily use two methods to override controller functions, each suited for different scenarios.
Supering a Function (Extension)
This is the most common and recommended approach. It allows you to add new behavior to an existing function without fully rewriting it. You call the original function using super() and then modify its result.
Example: Adding a Custom Value to the Product Page Context
Let's add a promotional message to the product page by extending the _prepare_product_values method.
class ExtendedWebsiteSale(WebsiteSale):
def _prepare_product_values(self, product, category='', search='', **kwargs):
# First, get all the values from the original method
result = super()._prepare_product_values(product, category, search, **kwargs)
# Then, add our custom value to the dictionary
result['special_promo'] = "Free shipping on all orders this week!"
return result
Advantage: This method is safe and maintainable, as it preserves all the existing logic from the parent controller.
Complete Function Overriding (Replacement)
In this approach, you completely redefine the method. This is necessary when you need to fundamentally change the function's logic. You must carefully reimplement any essential behavior from the original function.
Example: Changing the Logic for a Specific Product
class OverriddenWebsiteSale(WebsiteSale):
@http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True, sitemap=sitemap_product)
def product(self, product, category='', search='', **kwargs):
# Completely custom logic for product ID 9
if product.id == 9:
product.name = "Ergonomic Executive Desk"
# For all other products, we could call super(), but since we overrode completely,
# we might need to manually handle the template rendering if we don't call the original.
# This example shows a full override, which is riskier.
return request.render("website_sale.product", {
'product': product,
'add_qty': 1,
# ... other required context values
})
Use with Caution: This method can break functionality if the original method's logic changes in future Odoo updates. It should be used sparingly.
Overriding Routed Methods: A Special Consideration
When the method you are overriding is decorated with @http.route, you must include the same decorator in your subclass. The route defines the URL that triggers the method.
Example: Modifying the /shop Page
class CustomWebsiteShop(WebsiteSale):
@http.route(['/shop', '/shop/page/<int:page>'], type='http', auth="public", website=True)
def shop(self, page=0, category=None, search='', ppg=False, **post):
# Call the super method to get the original HTTP response
response = super().shop(page=page, category=category, search=search, ppg=ppg, **post)
# The response is a Response object. We can access the template context (qcontext)
# before it is finally rendered.
if response.qcontext:
products = response.qcontext.get('products')
if products:
for product in products:
if product.id == 9:
product.name = "Normal Desk"
# Return the modified response
return response
Key Point: For routed methods, you work with the Response object. The qcontext attribute of this object contains the data dictionary that will be passed to the template, allowing you to modify values before the page is rendered to the user.
Conclusion
Overriding controllers is an essential skill for advanced Odoo 19 development. By understanding the distinction between extending with super() and replacing with a full override, developers can make precise customizations.
- Prefer super() for adding data or making minor adjustments. It is more resilient to core code changes.
- Use full overrides judiciously, only when the required change is fundamental and cannot be achieved by extending the original method.
- Always replicate the @http.route decorator when overriding routed methods to ensure your custom code is triggered by the correct URLs.
Mastering these techniques empowers developers to tailor the Odoo web experience precisely to their business requirements, creating a seamless and fully customized interface for end-users.
To read more about How to Override an Existing Controller in Odoo 18, refer to our blog How to Override an Existing Controller in Odoo 18