Odoo

How I Built a Custom Odoo Module from Scratch

A step-by-step guide to custom Odoo module development — manifest, models, views, and deployment. Real code, real lessons from production projects.

How I Built a Custom Odoo Module from Scratch

🧩 1. Module Structure

 
library_issue/
├── __manifest__.py
├── models/
│ └── book_issue.py
├── views/
│ └── book_issue_views.xml
├── security/
│ └── ir.model.access.csv
 

📦 2. Manifest

 
{
'name': 'Library Issue System',
'version': '1.0',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/book_issue_views.xml',
],
'installable': True,
}
 

🧠 3. Model with Business Logic (CORE PART)

 

from odoo import models, fields, api
from odoo.exceptions import ValidationError

class BookIssue(models.Model):
    _name = 'library.issue'
    _description = 'Book Issue'

    name = fields.Char(string="Reference", required=True, copy=False, default="New")
    user_id = fields.Many2one('res.users', string="User", required=True)
    book_name = fields.Char(string="Book", required=True)
    issue_date = fields.Date(default=fields.Date.today)
    return_date = fields.Date()

    state = fields.Selection([
        ('draft', 'Draft'),
        ('issued', 'Issued'),
        ('returned', 'Returned'),
        ('cancel', 'Cancelled')
    ], default='draft')

    # AUTO SEQUENCE (Production practice)
    @api.model
    def create(self, vals):
        if vals.get('name', 'New') == 'New':
            vals['name'] = self.env['ir.sequence'].next_by_code('library.issue') or 'New'
        return super().create(vals)

    # BUTTON: Issue Book
    def action_issue(self):
        for rec in self:
            if rec.state != 'draft':
                raise ValidationError("Only draft can be issued!")
            rec.state = 'issued'

    # BUTTON: Return Book
    def action_return(self):
        for rec in self:
            if rec.state != 'issued':
                raise ValidationError("Only issued books can be returned!")
            rec.state = 'returned'

    # BUTTON: Cancel
    def action_cancel(self):
        self.state = 'cancel'

 

🎨 4. Views with Buttons (REAL UI)

 

<odoo>

    <record id="view_book_issue_form" model="ir.ui.view">
        <field name="name">book.issue.form</field>
        <field name="model">library.issue</field>
        <field name="arch" type="xml">

            <form>
                <!-- HEADER BUTTONS -->
                <header>
                    <button name="action_issue" type="object" string="Issue"
                        class="btn-primary" states="draft"/>

                    <button name="action_return" type="object" string="Return"
                        class="btn-success" states="issued"/>

                    <button name="action_cancel" type="object" string="Cancel"
                        class="btn-danger" states="draft,issued"/>

                    <field name="state" widget="statusbar"/>
                </header>

                <sheet>
                    <group>
                        <field name="name"/>
                        <field name="user_id"/>
                        <field name="book_name"/>
                        <field name="issue_date"/>
                        <field name="return_date"/>
                    </group>
                </sheet>
            </form>

        </field>
    </record>

    <!-- TREE VIEW -->
    <record id="view_book_issue_tree" model="ir.ui.view">
        <field name="name">book.issue.tree</field>
        <field name="model">library.issue</field>
        <field name="arch" type="xml">
            <tree>
                <field name="name"/>
                <field name="user_id"/>
                <field name="book_name"/>
                <field name="state"/>
            </tree>
        </field>
    </record>

    <!-- ACTION -->
    <record id="action_book_issue" model="ir.actions.act_window">
        <field name="name">Book Issues</field>
        <field name="res_model">library.issue</field>
        <field name="view_mode">tree,form</field>
    </record>

    <!-- MENU -->
    <menuitem id="menu_library_root" name="Library"/>
    <menuitem id="menu_issue" name="Issues"
              parent="menu_library_root"
              action="action_book_issue"/>

</odoo>


🔐 5. Security

 
id,name,model_id:id,perm_read,perm_write,perm_create,perm_unlink
access_issue,access.issue,model_library_issue,1,1,1,1
 

⚙️ 6. Add Sequence (IMPORTANT IN REAL PROJECTS)

Create XML file or data:

<record id="seq_library_issue" model="ir.sequence">
    <field name="name">Library Issue</field>
    <field name="code">library.issue</field>
    <field name="prefix">ISS/</field>
    <field name="padding">4</field>
</record>
 

👉 Result: ISS/0001, ISS/0002


🚀 7. Real Production Flow

 
User creates request → Draft

Clicks "Issue" → Validates → Issued

Clicks "Return" → Completed

Optional Cancel anytime
← Back to
All Posts
Ask me anything
👋 Hi! I'm Mohiuddin's AI assistant. Ask me about his skills, projects, experience, or how to hire him!