Creating Custom Pages in Odoo 18 Using Controllers and XML
Introduction Odoo provides a powerful framework for creating custom web pages through a combination of controllers (Python) and templates (XML). This article demonstrates how to…

Introduction

Odoo provides a powerful framework for creating custom web pages through a combination of controllers (Python) and templates (XML). This article demonstrates how to create a custom web form in Odoo 18 using a real-world example: a Real Estate Channel Partner Lead.

Project Structure

A typical custom page in Odoo requires these components:

  1. Model (Python) – Defines the data structure
  2. Controller (Python) – Handles HTTP requests and responses
  3. Template (XML) – Defines the user interface
  4. Manifest File – Configures the module

Step 1: Creating the Model

Let’s start by defining our model. In this example, we have a channel.partner model to store channel partner information.

# models/channel_partner.py
from odoo import api, fields, models

class ChannelPartner(models.Model):
    _name = "channel.partner"
    _rec_name = "first_name"
    
    first_name = fields.Char(string="First Name", required=True)
    last_name = fields.Char(string="Last Name")
    email = fields.Char(string="Email", required=True)
    pan = fields.Char(string="PAN")
    mobile_number = fields.Char(string="Mobile Number")
    c_partner_id = fields.Char(string="C Partner ID", readonly=True)
    
    # Other fields omitted for brevity
    
    @api.model
    def create(self, vals):
        # Generate C Partner ID and assign it to the record
        vals["c_partner_id"] = self._generate_c_partner_id(vals.get("pan", ""))
        return super().create(vals)
    
    def _generate_c_partner_id(self, pan):
        # Generate a unique ID based on PAN number
        if pan and len(pan) >= 4:
            last_four_digits = pan[-4:]
            current_year_last_two_digits = str(fields.Date.today().year)[-2:]
            return f"UBCP{current_year_last_two_digits}{last_four_digits}".upper()
        return False

We’ve also extended the crm.lead model to integrate with our channel partner system:

# models/channel_partner.py
class ChannelCrmAdd(models.Model):
_inherit = "crm.lead"

channel_partner_id = fields.Char(string="Channel Partner")
channel_part_id = fields.Many2one("channel.partner")

# Methods for managing the relationship between leads and channel partners

Step 2: Creating the Controller

The controller handles HTTP requests and routes them to the appropriate template or action:

# controllers/main.py
import base64
from odoo import http
from odoo.http import request

class ChannelPartner(http.Controller):
@http.route(
"/channel_lead_form/",
auth="public",
type="http",
website=True,
sitemap=False
)
def lead_forms(self, **kwargs):
return http.request.render("channel_partner.channel_partner_lead_template")

@http.route(
"/submit_lead_form/",
methods=["POST", "GET"],
type="http",
auth="public",
website=True,
)
def submit_lead_form(self, **post):
# Extract values from the submitted form
project_name = post.get("project_name")
customer_name = post.get("customer_name")
customer_email = post.get("customer_email")
customer_phone = post.get("customer_phone")
channel_partner_id = post.get("channel_partner_id")
message = post.get("message")

# Check for existing lead
existing_lead = (
request.env["crm.lead"].sudo().search([("phone", "=", customer_phone)])
)
if existing_lead:
return http.request.render("channel_partner.lead_error_template")

# Validate channel partner
channel_partner = (
request.env["channel.partner"]
.sudo()
.search([("c_partner_id", "=", channel_partner_id.upper())])
)
if not channel_partner:
return http.request.render("channel_partner.incorrect_channel_partner_id")

# Create a new lead
lead_values = {
"name": project_name,
"contact_name": customer_name,
"email_from": customer_email,
"phone": customer_phone,
"channel_partner_id": channel_partner_id.upper(),
"description": message,
}

request.env["crm.lead"].sudo().create(lead_values)

# Render success template
return http.request.render("channel_partner.lead_success_template")

Key components of the controller:

  • @http.route decorator defines the URL, authentication type, and request type
  • request.render() renders an XML template
  • Form submission handling with data validation
  • Database operations using Odoo’s ORM

Step 3: Creating the Templates

Now let’s define our XML templates for the user interface:

<!– views/templates.xml –>

/* CSS styles omitted for brevity */

<div class="lead-container">
<!– Navigation bar –>
<div class="navbar">
<div class="navbar-left">
<img src="https://example.com/logo.webp" alt="Logo" />
</div>
<div class="navbar-center">
<a class="navbar-item" href="/">Home</a>
<a class="navbar-item" href="/about">About</a>
<!– More navigation items –>
</div>
<div class="navbar-right">
<img src="https://example.com/partner-logo.webp" alt="Partner Logo" />
</div>
</div>

<!– Form container –>
<div class="centered-form">
<div class="channel-partner-lead-form">
<section class=’slide-in’>
<h1 id="registration-heading">Channel Partner Lead Generation</h1>

<div class="group">
<div class="group1">

<!– More form fields –>
</div>
<div class="group2">
<!– More form fields –>
</div>
</div>

<div class="btn">
<button type="submit">Submit</button>
</div>

</section>
</div>
</div>
</div>

<!– Success template –>

.thank-you-page {
text-align: center;
margin-top: 50px;
}

<div class="thank-you-page">
<h1 style="color: green">Your Lead Generated Successfully</h1>
<button>Exit</button>

function redirectToAnotherPage() {
window.location.href = ‘/’;
}

</div>

<!– Error templates –>

<!– Content for incorrect channel partner ID –>

<!– Content for duplicate lead –>

Key elements in the templates:

  • The template tag with a unique ID and name
  • CSS styles for layout and responsiveness
  • HTML form with proper method and action attributes
  • CSRF token for security
  • Input fields with validation attributes
  • Success and error templates for different scenarios

Step 4: Creating the Manifest File

Finally, let’s create the manifest file to configure our module:

# __manifest__.py
{
‘name’: ‘Channel Partner’,
‘version’: ‘1.0’,
‘summary’: ‘Channel Partner Lead Generation’,
‘description’: """
Module for managing channel partners and lead generation.
""",
‘category’: ‘Sales/CRM’,
‘author’: ‘Your Name’,
‘website’: ‘https://yourwebsite.com’,
‘depends’: [‘base’, ‘crm’, ‘website’],
‘data’: [
‘security/ir.model.access.csv’,
‘views/channel_partner_views.xml’,
‘views/templates.xml’,
],
‘installable’: True,
‘application’: True,
‘auto_install’: False,
}

Full Example Walkthrough

Let’s put everything together to understand the complete flow:

  1. A user visits the URL /channel_lead_form/
  2. The controller routes this request to render the channel_partner_lead_template
  3. The user fills out the form and submits it
  4. The controller’s submit_lead_form method processes the submission
  5. Data is validated:
    • If the lead already exists, the error template is shown
    • If the channel partner ID is invalid, another error template is shown
  6. If validation passes, a new lead is created in the CRM
  7. The success template is rendered to confirm the submission

Conclusion

Creating custom pages in Odoo combines Python controllers for handling requests and XML templates for the user interface. This approach gives you full control over the user experience while leveraging Odoo’s powerful backend capabilities.

The example in this article demonstrates a real-world application: a lead generation form for channel partners. By following similar patterns, you can create your own custom pages in Odoo 18 to meet your specific business requirements.