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:
- Model (Python) – Defines the data structure
- Controller (Python) – Handles HTTP requests and responses
- Template (XML) – Defines the user interface
- 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:
- A user visits the URL /channel_lead_form/
- The controller routes this request to render the channel_partner_lead_template
- The user fills out the form and submits it
- The controller’s submit_lead_form method processes the submission
- 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
- If validation passes, a new lead is created in the CRM
- 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.