Enable Dark Mode!
how-to-create-a-generic-controller-in-odoo-18-website.jpg
By: Aftab K

How to Create a Generic Controller in Odoo 18 Website

Technical Odoo 18

Introduction

In Odoo 18, a generic controller offers a flexible framework for managing web requests across multiple models. Operating within the MVC (Model-View-Controller) design pattern, these controllers enable developers to handle HTTP requests and interact with the database efficiently.

An efficiently structured generic controller can dynamically manage CRUD (Create, Read, Update, Delete) operations for various models, helping to minimize code repetition and enhance maintainability. In this tutorial, we’ll build a generic controller that enables users to submit a website form to create records in any specified model.

Step 1: Define a Website Menu

Before we set up the controller, we must first add a menu item that provides access to the form

<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
   <record id="website_partner_menu" model="website.menu">
       <field name="name">Partner</field>
       <field name="url">/partner</field>
       <field name="parent_id" ref="website.main_menu"/>
       <field name="sequence" type="int">90</field>
   </record>
</odoo>

Step 2: We need to define the controller function

@route(['/partner'], type='http', auth='public', website=True)
def partner(self):
   """
   Renders the partner selection form, fetching all partners, CRM tags, users,
   and sales teams to display in the form.
   :return: Rendered HTML template with required partner-related data.
   """
   partners = request.env['res.partner'].sudo().search([])
   tags = request.env['crm.tag'].sudo().search([])
   users = request.env['res.users'].sudo().search([])
   sales_team = request.env['crm.team'].sudo().search([])
   return request.render(
       'generic_controller.generic_controller_form',
       {'partners': partners, 'users': users, 'tags': tags, 'sales_team': sales_team}
   )

Step 3: Setting up the Website Form Template

To begin, we’ll design the website form template, which includes various input fields and defines an action to create a new lead in the crm.lead model. The updated form layout below outlines the necessary fields and their corresponding types for use in Odoo 18.

Included Fields and Their Types:

* Opportunity – Text input (char)

* Customer – Dropdown for selecting a partner (many2one)

* Tags – Multi-select tags (many2many)

* Email – Email address input (email)

* Phone – Phone number input (char)

* Custom Boolean – Checkbox (boolean)

* Custom Number – Numeric input (integer)

* Custom Selection – Dropdown selection (selection)

* Custom Date – Date picker (date)

* File Upload – Upload field for attachments (binary)

* Salesperson – Dropdown for assigning a user (many2one)

* Sales Team – Dropdown for selecting a team (many2one)

* Submit Button – To send the form data

Example of The Form

How to Create a Generic Controller in Odoo 18 Website-cybrosys

Example Template Code for Odoo 18

<odoo>
<template id="generic_controller_form" name="Generic Controller">
    <t t-call="website.layout">
        <div id="wrap" class="oe_structure oe_empty">
            <section class="s_website_form" data-vcss="001" data-snippet="s_website_form">
                <div class="container">
                    <section class="s_text_block pt40 pb40 o_colored_level" data-snippet="s_text_block">
                        <div class="container s_allow_columns">
                            <div class="row">
                                <div class="col-lg-8 mt-4 mt-lg-0">
                                    <section class="s_website_form" data-vcss="001" data-snippet="s_website_form">
                                        <div class="container">
                                            <!-- Form Start -->
                                            <form action="/generic_controller" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*" data-model_name="crm.lead" data-success-mode="redirect" data-success-page="/contactus-thank-you">
                                                <!-- CSRF Token (Security) -->
                                                <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
                                                <!-- Hidden Model Field -->
                                                <input type="hidden" name="model" value="crm.lead"/>
                                                <div class="s_website_form_rows row s_col_no_bgcolor">
                                                    <!-- Opportunity Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom s_website_form_required" data-type="char" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="name">
                                                                <span class="s_website_form_label_content">Opportunity</span>
                                                                <span class="s_website_form_mark">*</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <input id="name" type="text" class="form-control s_website_form_input" name="name" required="" data-fill-with="name"/>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Customer Field (Many2one) -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="char" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="partner_id">
                                                                <span class="s_website_form_label_content">Customer</span>
                                                                <span class="s_website_form_mark">*</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <select name="partner_id" t-attf-class="form-control s_website_form_input required=">
                                                                    <t t-foreach="partners or []" t-as="partner">
                                                                        <option t-att-value="partner.id"><t t-esc="partner.name"/></option>
                                                                    </t>
                                                                </select>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Tags Field (Many2Many) -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="many2many" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="tag_ids">
                                                                <span class="s_website_form_label_content">Select Tags</span>
                                                                <span class="s_website_form_mark">*</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <div class="row s_col_no_resize s_col_no_bgcolor s_website_form_multiple" data-name="tag_ids" data-display="horizontal">
                                                                    <t t-foreach="tags" t-as="record">
                                                                        <div class="checkbox col-12 col-lg-4 col-md-6">
                                                                            <div class="form-check">
                                                                                <input type="checkbox" class="s_website_form_input form-check-input" name="tag_ids" t-attf-id="mailing_list_#{record.id}" t-att-value="record.id" required="1"/>
                                                                                <label class="form-check-label s_website_form_check_label" t-attf-for="mailing_list_#{record.id}"><t t-esc="record.name"/></label>
                                                                            </div>
                                                                        </div>
                                                                    </t>
                                                                </div>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Email Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="email" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact3">
                                                                <span class="s_website_form_label_content">Email</span>
                                                                <span class="s_website_form_mark">*</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <input id="email" type="email" class="form-control s_website_form_input" name="email_from" required="" data-fill-with="email"/>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Phone Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="char" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="phone">
                                                                <span class="s_website_form_label_content">Phone</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <input id="phone" type="tel" class="form-control s_website_form_input" name="phone" data-fill-with="phone"/>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Boolean Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="boolean" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="custom">
                                                                <span class="s_website_form_label_content">Custom</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <div class="form-check">
                                                                    <input type="checkbox" value="Yes" class="s_website_form_input form-check-input" name="custom" id="custom" required="1"/>
                                                                </div>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Number Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="integer" data-name="custom_number">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="custom_number">
                                                                <span class="s_website_form_label_content">Custom Number</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <input type="number" class="s_website_form_input form-control" name="custom_number" id="custom_number" required="1"/>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Selection Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="selection" data-name="custom_selection">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="custom_selection">
                                                                <span class="s_website_form_label_content">Custom Selection</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <select class="s_website_form_input form-control" name="custom_selection" id="custom_selection" required="1">
                                                                    <option value="">Select an option</option>
                                                                    <option value="option_1">Option 1</option>
                                                                    <option value="option_2">Option 2</option>
                                                                    <option value="option_3">Option 3</option>
                                                                </select>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Date Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="date" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="custom_date">
                                                                <span class="s_website_form_label_content">Custom Date</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <input type="date" class="s_website_form_input form-control" name="custom_date" id="custom_date" required="1"/>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- File Upload Field -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="binary" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <div class="form-group">
                                                                <label for="upload_document"><b>Upload Document</b></label>
                                                                <div>
                                                                    <input type="file" class="mb-2" id="upload_document" name="custom_file" accept="image/png, image/jpeg, application/pdf"/>
                                                                    <textarea class="d-none" name="upload_document_text"></textarea>
                                                                </div>
                                                            </div>
                                                        </div>
                                                        <br/>
                                                    </div>
                                                    <!-- Salesperson Field (Dropdown) -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="char" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="user_id">
                                                                <span class="s_website_form_label_content">Salesperson</span>
                                                                <span class="s_website_form_mark">*</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <select name="user_id" t-attf-class="form-control s_website_form_input" required="1">
                                                                    <t t-foreach="users or []" t-as="user">
                                                                        <option t-att-value="user.id"><t t-esc="user.name"/></option>
                                                                    </t>
                                                                </select>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Sales Team Field (Dropdown) -->
                                                    <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="char" data-name="Field">
                                                        <div class="row s_col_no_resize s_col_no_bgcolor">
                                                            <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="team_id">
                                                                <span class="s_website_form_label_content">Sales Team</span>
                                                                <span class="s_website_form_mark">*</span>
                                                            </label>
                                                            <div class="col-sm">
                                                                <select name="team_id" t-attf-class="form-control s_website_form_input" required="1">
                                                                    <t t-foreach="sales_team or []" t-as="team">
                                                                        <option t-att-value="team.id"><t t-esc="team.name"/></option>
                                                                    </t>
                                                                </select>
                                                            </div>
                                                        </div>
                                                    </div>
                                                    <!-- Submit Button -->
                                                    <div class="form-group col-12 s_website_form_submit" data-name="Submit Button">
                                                        <div style="width: 200px;" class="s_website_form_label"/>
                                                        <a href="#" role="button" class="btn btn-primary s_website_form_send">Submit</a>
                                                    </div>
                                                </div>
                                            </form>
                                        </div>
                                    </section>
                                </div>
                            </div>
                        </div>
                    </section>
                </div>
            </section>
        </div>
    </t>
</template>
</odoo>

While designing the form template, it’s important to include the model name in the markup using a hidden input field, for example: <input type="hidden" name="model" value="crm.lead"/>. The value attribute should contain the target model’s name. Additionally, you should ensure that the field names in the template correspond directly to the actual field names in the model.

Once the form is in place, the next step is to configure the generic controller that will handle the logic after the form is submitted.

@route('/generic_controller', methods=['POST'], auth="public", website=True)
def menu_generic_controller_request_submit(self, **kw):
   """
   Handles the submission of a generic form to dynamically create records
   in any specified model.
   :param kw: Dictionary containing form data.
   :return: Renders the form after processing the submission.
   """
   print(kw)  # Debugging output
   values = {}
   model_name = kw.get('model')
   # Fetch all fields of the selected model
   fields = request.env['ir.model.fields'].sudo().search([('model', '=', model_name)])
   # Identify binary fields dynamically
   binary_fields = {f.name for f in fields.filtered(lambda f: f.ttype == 'binary')}
   for key, val in kw.items():
       # Remove unnecessary suffix patterns from field names
       cleaned_key = re.sub(r'\[\d+\]\[\d+\]$', '', key)
       field_id = fields.filtered(lambda r: r.name == cleaned_key)
       if not field_id:
           continue
       # Process different field types
       if field_id.ttype == 'many2one':
           val = int(val) if val else 0
       elif field_id.ttype == 'many2many':
           val = [(6, 0, [int(i) for i in val.split(',') if i.isdigit()])] if val else [(5, 0, [])]
       elif field_id.ttype == 'one2many':
           val = self._process_one2many_field(field_id, val)
       elif field_id.ttype == 'selection':
           val = val if val in dict(request.env[model_name]._fields[cleaned_key].selection).keys() else False
       elif field_id.ttype == 'integer':
           val = self._safe_cast(val, int, 0)
       elif field_id.ttype == 'float':
           val = self._safe_cast(val, float, 0.0)
       elif field_id.ttype == 'boolean':
           val = str(val).lower() in ['true', '1', 'yes', 'on']
       elif field_id.ttype in ['date', 'datetime']:
           val = self._process_date_field(field_id, val)
       elif cleaned_key in binary_fields and hasattr(val, 'read'):
           val = base64.b64encode(val.read()).decode('utf-8') if val else False
       values[cleaned_key] = val  # Store processed values
   print('Processed values:', values)
   # Create the record
   request.env[model_name].sudo().create(values)
   return request.render("generic_controller.generic_controller_form", {})
def _process_one2many_field(self, field_id, value):
   """
   Processes one2many field values, ensuring proper structure.
   :param field_id: The field definition.
   :param value: Raw submitted data.
   :return: Processed one2many field values.
   """
   one2many_lines = []
   for line in value:
       line_values = {}
       sub_fields = request.env['ir.model.fields'].sudo().search([('model', '=', field_id.relation)])
       for sub_key, sub_val in line.items():
           sub_field = sub_fields.filtered(lambda f: f.name == sub_key)
           if sub_field:
               if sub_field.ttype == 'many2one':
                   sub_val = int(sub_val) if sub_val else 0
               elif sub_field.ttype == 'many2many':
                   sub_val = [(6, 0, [int(i) for i in sub_val.split(',') if i.isdigit()])] if sub_val else [
                       (5, 0, [])]
           line_values[sub_key] = sub_val
       one2many_lines.append((0, 0, line_values))
   return one2many_lines
def _process_date_field(self, field_id, value):
   """
   Processes date and datetime fields safely.
   :param field_id: The field definition.
   :param value: Raw submitted value.
   :return: Processed date or datetime value.
   """
   try:
       if value:
           return datetime.strptime(value, "%Y-%m-%d").date() if field_id.ttype == 'date' else datetime.strptime(
               value, "%Y-%m-%dT%H:%M")
   except ValueError:
       return False  # If conversion fails, return False
def _safe_cast(self, value, cast_type, default):
   """
   Safely converts values to a specific type.
   :param value: Value to be converted.
   :param cast_type: Target type.
   :param default: Default value if conversion fails.
   :return: Converted value or default.
   """
   try:
       return cast_type(value) if value else default
   except ValueError:
       return default

The controller function processes the submitted form data by creating a new record in the target model specified in the form. It dynamically detects the model's fields and maps them to the corresponding values received from the submission. This approach utilizes Odoo 18's improved form handling and model interaction capabilities, maintaining alignment with current features and security best practices.

Using a generic controller within the Odoo 18 Website module significantly improves the flexibility and efficiency of handling form submissions. It streamlines the process of creating records in multiple models, making the overall web application more unified, scalable, and easier to maintain.

To read more about how to create a Generic Controller in Odoo 17 Website, refer to our blog How to Create a Generic Controller in Odoo 17 Website.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message