Series: Odoo 18 Development for Python Developers Target Audience: Python developers who are new to web development and want to learn Odoo Prerequisites: Lesson 1 completed, Odoo 18 development environment running
What is a Module? (Compared to a Python Package)
If you’ve worked with Python, you’re familiar with packages — a folder with an __init__.py file that groups related code together. An Odoo module is similar, but with extra structure and superpowers.
Python Package vs Odoo Module
| Feature | Python Package | Odoo Module |
|---|---|---|
| Required files | __init__.py |
__init__.py + __manifest__.py |
| Purpose | Organize code | Organize code + data + UI + security |
| Installation | pip install |
Install via Odoo Apps menu or CLI |
| Can be enabled/disabled | No (once imported, it’s there) | Yes (install/uninstall at runtime) |
| Has metadata | setup.py or pyproject.toml (optional) |
__manifest__.py (required) |
| Can modify other packages | Not really | Yes (through inheritance) |
What Makes a Module a Module?
An Odoo module is a directory that contains:
__init__.py— Makes it a Python package (just like regular Python)__manifest__.py— The module’s ID card. Without this file, Odoo won’t recognize the directory as a module
That’s the minimum. Just these two files, and Odoo will see it as a valid module. Of course, a module with only these two files doesn’t do anything useful — but it’s technically valid.
Real-World Analogy
Think of an Odoo module like a smartphone app:
- It has a name, version, and description (like an app in the App Store)
- It can be installed and uninstalled
- It can depend on other apps (e.g., Instagram needs Camera access)
- It can extend the system’s capabilities
- Multiple apps can work together
In Odoo, instead of an App Store, you have the Apps menu. Instead of “Camera access,” you have module dependencies.
Module Directory Structure Deep Dive
Let’s look at the complete structure of a well-organized Odoo module. We’ll use our library_app module as the example:
library_app/ # Root directory — the module's technical name
│
├── __init__.py # Root init — imports sub-packages
├── __manifest__.py # Module metadata (name, version, dependencies)
│
├── models/ # Python files — your business logic
│ ├── __init__.py # Imports all model files
│ ├── book.py # The library.book model
│ └── member.py # The library.member model
│
├── views/ # XML files — user interface definitions
│ ├── book_views.xml # Form, list, search views for books
│ ├── member_views.xml # Views for members
│ └── menu_views.xml # Menu structure
│
├── security/ # Access control
│ ├── ir.model.access.csv # Model-level permissions (CRUD)
│ └── library_security.xml # Groups and record rules
│
├── data/ # Data loaded when module is installed
│ └── book_data.xml # Default records (categories, sequences, etc.)
│
├── demo/ # Demo data (only loaded if demo mode is on)
│ └── book_demo.xml # Sample records for testing
│
├── wizard/ # Transient models (popup forms)
│ ├── __init__.py
│ ├── book_borrow_wizard.py # Python logic for the wizard
│ └── book_borrow_wizard_views.xml # XML view for the wizard
│
├── report/ # PDF report templates
│ ├── book_report.xml # QWeb template for the report
│ └── book_report_action.xml # Report action definition
│
├── controllers/ # Web controllers (HTTP routes)
│ ├── __init__.py
│ └── main.py # API endpoints or web pages
│
├── static/ # Static files (no __init__.py needed)
│ ├── description/
│ │ ├── icon.png # Module icon (shown in Apps menu)
│ │ └── index.html # Module description page (optional)
│ └── src/
│ ├── js/ # Custom JavaScript / OWL components
│ ├── css/ # Custom stylesheets
│ └── xml/ # OWL component templates
│
├── i18n/ # Translation files
│ ├── vi.po # Vietnamese translation
│ └── fr.po # French translation
│
└── tests/ # Automated tests
├── __init__.py
├── test_book.py # Unit tests for the book model
└── test_borrow.py # Tests for borrowing logic
Don’t Panic!
This looks like a lot of files, but remember:
- You don’t need all of them to start. A minimal module has just
__init__.pyand__manifest__.py - You add directories as you need them. Start simple, grow as your module gets more features
- The structure is a convention, not a strict rule. But following it makes your code predictable and easy for other developers to read
Directory Purpose Summary
| Directory | Contains | When to Add |
|---|---|---|
models/ |
Python classes (database models, business logic) | Almost always — this is where your core code lives |
views/ |
XML files (forms, lists, menus, actions) | Almost always — users need a UI |
security/ |
CSV and XML files (access rights, record rules) | Almost always — you need to control who sees what |
data/ |
XML/CSV files (default data loaded on install) | When your module needs pre-configured data |
demo/ |
XML/CSV files (sample data for testing) | When you want sample data for development |
wizard/ |
Python + XML (popup dialogs, multi-step forms) | When you need interactive user dialogs |
report/ |
XML (PDF report templates) | When users need printable documents |
controllers/ |
Python (HTTP endpoints) | When you need web APIs or public pages |
static/ |
JS, CSS, images, OWL templates | When you customize the web client UI |
i18n/ |
.po files (translations) |
When your module needs to support multiple languages |
tests/ |
Python test files | Always (you should always write tests!) |
The __manifest__.py File — Every Field Explained
The __manifest__.py file is the single most important file in any Odoo module. It’s a Python dictionary that tells Odoo everything it needs to know about your module.
Here’s a complete example with every commonly used field:
# library_app/__manifest__.py
{
# =============================================
# BASIC INFORMATION
# =============================================
'name': 'Library Management',
# The human-readable name of your module.
# This is what appears in the Apps menu.
# Keep it short and descriptive.
'version': '18.0.1.0.0',
# Module version number.
# Convention: ODOO_VERSION.MAJOR.MINOR.PATCH
# '18.0' = Odoo version
# '1.0.0' = Your module version (major.minor.patch)
'summary': 'Manage a library of books and members',
# A one-line summary shown below the module name in the Apps menu.
# Keep it under 80 characters.
'description': """
Library Management System
=========================
This module allows you to:
* Manage a catalog of books
* Track book authors and categories
* Register library members
* Handle book borrowing and returns
**Key Features:**
* Book inventory management
* Member registration
* Borrowing workflow with due dates
* Overdue book notifications
""",
# A detailed description of what your module does.
# Supports reStructuredText (RST) formatting.
# Shown when the user clicks on the module in the Apps menu.
# Tip: You can also put this in static/description/index.html for richer formatting.
'author': 'Your Name',
# Who wrote this module. Can be a person or company name.
'website': 'https://your-website.com',
# Optional: link to the author's website or module documentation.
'category': 'Services/Library',
# Category for organizing modules in the Apps menu.
# Also used to create security groups automatically.
# Common categories: Sales, Accounting, Inventory, HR, Website, Tools, Services
'license': 'LGPL-3',
# License type. Common values:
# 'LGPL-3' — for community modules (most common)
# 'OEEL-1' — for Odoo Enterprise modules
# 'GPL-3' — for strictly open-source modules
# =============================================
# DEPENDENCIES
# =============================================
'depends': ['base', 'mail'],
# List of modules that MUST be installed before this one.
# 'base' — always include this (it provides core models like res.partner)
# 'mail' — adds chatter (message/activity tracking) to your models
#
# When your module is installed, Odoo automatically installs all dependencies first.
# IMPORTANT: Only list DIRECT dependencies, not transitive ones.
# If 'mail' depends on 'base', you don't need to list 'base' explicitly
# (but it's common practice to include it for clarity).
'external_dependencies': {
'python': ['requests'], # Python packages (installed via pip)
'bin': ['wkhtmltopdf'], # System binaries
},
# Optional: External libraries your module needs.
# 'python' — pip packages that must be installed
# 'bin' — system binaries that must exist in PATH
# Odoo checks these at install time and shows an error if they're missing.
# =============================================
# DATA FILES
# =============================================
'data': [
'security/library_security.xml', # Load security groups first
'security/ir.model.access.csv', # Then access rights
'views/book_views.xml', # Then views
'views/menu_views.xml', # Then menus
'data/book_data.xml', # Then default data
],
# List of data files to load when the module is INSTALLED or UPGRADED.
# ORDER MATTERS! Files are loaded in the order listed here.
#
# Typical loading order:
# 1. Security groups (other files may reference them)
# 2. Access rights (CSV)
# 3. Views (XML)
# 4. Menus and actions
# 5. Default data
#
# Supported file types: .xml and .csv
'demo': [
'demo/book_demo.xml',
],
# Demo data files — only loaded when the database is created with
# the "Load demonstration data" checkbox enabled.
# Useful for testing and showcasing the module.
# =============================================
# TECHNICAL FLAGS
# =============================================
'installable': True,
# If True, the module appears in the Apps menu and can be installed.
# Set to False to hide a module (e.g., if it's deprecated or broken).
# Default: True
'application': True,
# If True, the module appears as a main application in the Apps menu
# (with a larger icon and prominent placement).
# If False, it appears only when the "Extra" filter is applied.
# Set True for top-level apps, False for small utility modules.
'auto_install': False,
# If True, the module is automatically installed when ALL of its
# dependencies are installed. Used for "bridge" modules.
# Example: A module with depends=['sale', 'stock'] and auto_install=True
# will be installed automatically when both Sale and Inventory are installed.
# Default: False (you almost always want this to be False)
# =============================================
# ASSETS (JavaScript, CSS for the web client)
# =============================================
'assets': {
'web.assets_backend': [
'library_app/static/src/js/**/*', # All JS files
'library_app/static/src/css/**/*', # All CSS files
'library_app/static/src/xml/**/*', # All OWL templates
],
},
# Optional: Register static assets (JS, CSS, XML) with the web client.
# 'web.assets_backend' — loaded in the main Odoo interface
# 'web.assets_frontend' — loaded on the public website
# We'll cover this in detail in Lesson 14 (OWL components).
}
The Most Important Fields (Minimum Viable Manifest)
If you’re just starting, you only need these fields:
# Minimum __manifest__.py
{
'name': 'Library Management',
'version': '18.0.1.0.0',
'depends': ['base'],
'data': [],
'installable': True,
'application': True,
}
Everything else can be added later as your module grows.
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Forgetting a file in data |
Views/security not loaded, module seems broken | Add the file path to the data list |
Wrong file order in data |
Errors like “External ID not found” | Load security → views → menus → data |
Missing dependency in depends |
KeyError or model not found |
Add the module that provides the model you need |
Typo in file path in data |
FileNotFoundError on install/upgrade |
Double-check the path matches the actual file location |
The __init__.py Chain — How Odoo Discovers Your Code
In regular Python, __init__.py makes a directory a package. In Odoo, it serves the same purpose — but there’s a specific pattern for how __init__.py files chain together.
The Chain Explained
Odoo only reads the root __init__.py of your module. From there, your code must import everything else. Here’s how it works:
Odoo starts here
│
▼
library_app/__init__.py # Root init — imports sub-packages
│
├──► models/__init__.py # Models init — imports model files
│ │
│ ├──► book.py # Defines library.book model
│ └──► member.py # Defines library.member model
│
├──► wizard/__init__.py # Wizard init — imports wizard files
│ │
│ └──► book_borrow_wizard.py
│
└──► controllers/__init__.py
│
└──► main.py
The Root __init__.py
# library_app/__init__.py
from . import models # Import the models sub-package
from . import wizard # Import the wizard sub-package (if it exists)
from . import controllers # Import the controllers sub-package (if it exists)
What’s happening:
from . import models— This tells Python to runmodels/__init__.py- The dot (
.) means “from the current package” (relative import) - You only import directories that contain Python files with Odoo models or controllers
- You do NOT import
views/,security/,data/,static/, ori18n/— those are data directories, not Python packages
Sub-package __init__.py Files
# library_app/models/__init__.py
from . import book # Import book.py (which defines the library.book model)
from . import member # Import member.py (which defines the library.member model)
What’s happening:
from . import book— This runs the code inbook.py, which registers the model with Odoo’s ORM- Each Python file that defines a model must be imported here
- If you create a new model file (e.g.,
category.py), you must addfrom . import categoryhere
Why Is This Necessary?
In Django, models are auto-discovered by the framework. In Odoo, you must explicitly import every file through the __init__.py chain. If you forget to import a file, Odoo won’t know it exists, and your model/controller won’t be registered.
This is a very common mistake for beginners:
# You created models/category.py with a new model...
# But forgot to add this line in models/__init__.py:
from . import category # ← Without this, Odoo doesn't see your model!
Quick Rule of Thumb
| Directory | Needs __init__.py? |
Needs import in parent __init__.py? |
|---|---|---|
models/ |
Yes | Yes |
wizard/ |
Yes | Yes |
controllers/ |
Yes | Yes |
tests/ |
Yes | No (Odoo auto-discovers tests) |
views/ |
No | No (loaded via __manifest__.py → data) |
security/ |
No | No (loaded via __manifest__.py → data) |
data/ |
No | No (loaded via __manifest__.py → data) |
demo/ |
No | No (loaded via __manifest__.py → demo) |
static/ |
No | No (loaded via __manifest__.py → assets) |
i18n/ |
No | No (auto-discovered by Odoo) |
report/ |
No* | No* |
*Reports are usually XML-only (loaded via data in manifest). If a report has Python logic, it goes in models/ or its own package with __init__.py.
Module Dependencies and Load Order
Why Dependencies Matter
When you write 'depends': ['base', 'mail'] in your manifest, you’re telling Odoo:
“My module needs
baseand
This is important because:
- Models from dependencies are available to your module. If you depend on
mail, you can inherit frommail.threadin your model. - Views from dependencies exist before yours. If you want to extend a view from the
salemodule, you must depend onsale. - Security groups from dependencies are available. If you reference a group defined in another module, you need that module as a dependency.
How Load Order Works
When you install your module, Odoo:
- Checks all dependencies recursively
- Installs any missing dependencies first (in dependency order)
- Loads your module last
When Odoo loads a module, it follows this order:
1. Run __init__.py chain (registers Python models)
2. Load 'data' files in the order listed in __manifest__.py
3. Load 'demo' files (if demo mode is enabled)
Dependency Examples
# Scenario 1: Basic module (most modules start here)
'depends': ['base'],
# 'base' provides: res.partner (contacts), res.users (users),
# res.company (companies), ir.* (infrastructure models)
# Scenario 2: Module with chatter (message tracking)
'depends': ['base', 'mail'],
# 'mail' provides: mail.thread mixin, activity tracking, followers
# Scenario 3: Module extending Sales
'depends': ['sale'],
# 'sale' already depends on 'base', so you don't need to list 'base' explicitly
# But many developers still list it for clarity
# Scenario 4: Module connecting Sales and Inventory
'depends': ['sale', 'stock'],
# Both Sale and Inventory must be installed
Common Dependency Pitfall
# ❌ WRONG: Depending on a module just to use its features in the UI
'depends': ['sale', 'purchase', 'stock', 'account'],
# This forces ALL these modules to be installed!
# ✅ RIGHT: Only depend on what your Python code actually needs
'depends': ['base', 'mail'],
# If you only add fields to res.partner and use mail.thread,
# you only need 'base' and 'mail'
Rule of thumb: Only add a dependency if your Python code directly imports or inherits from a model in that module, or if your XML files reference records (views, groups, actions) defined in that module.
Installing, Upgrading, and Uninstalling Modules
Understanding the module lifecycle is crucial for development.
Installing a Module
There are two ways to install a module:
Method 1: Via the Apps Menu (UI)
- Go to Apps in the main menu
- Remove the “Apps” filter in the search bar (to see all modules)
- Search for your module’s name
- Click the “Install” button
If you don’t see your module:
- Make sure your module directory is in the
addons_path(checkodoo.confordocker-compose.yml) - Click “Update Apps List” in the Apps menu (only visible in Developer Mode)
- Check that your
__manifest__.pyhas no syntax errors
Method 2: Via Command Line (Faster for development)
# Docker method:
docker compose exec odoo odoo -d odoo18dev -i library_app --stop-after-init
# Source method:
python odoo/odoo-bin -c odoo.conf -d odoo18dev -i library_app --stop-after-init
What the flags mean:
-d odoo18dev— Target database name-i library_app— Install this module (-i= install)--stop-after-init— Stop the server after installation (useful for scripts)
Upgrading a Module
When you change Python code, XML views, or data files, you need to upgrade the module for changes to take effect.
When Do You Need to Upgrade?
| What You Changed | Restart Server? | Upgrade Module? |
|---|---|---|
| Python code (logic only) | Yes | No (if dev_mode=reload is on, auto-restarts) |
| Python code (new field added) | Yes | Yes (ORM needs to add the column) |
| XML view file | No* | Yes |
| CSV security file | No* | Yes |
__manifest__.py |
Yes | Yes |
| JavaScript / CSS | No | Yes (to re-bundle assets) |
*With dev_mode=xml, XML changes are reloaded automatically without upgrade. But new files still need an upgrade.
How to Upgrade
Via UI:
- Go to Apps
- Find your module
- Click the three dots menu (⋮) → “Upgrade”
Via Command Line:
# Docker method:
docker compose exec odoo odoo -d odoo18dev -u library_app --stop-after-init
# Source method:
python odoo/odoo-bin -c odoo.conf -d odoo18dev -u library_app --stop-after-init
Note: -u means “upgrade” (vs -i for “install”).
Uninstalling a Module
Via UI:
- Go to Apps → find your module
- Click the three dots menu (⋮) → “Uninstall”
Warning: Uninstalling a module deletes all its data from the database (tables, records, views, everything). This action is irreversible. In production, be very careful with uninstallation.
The Development Cycle
During development, your typical workflow looks like this:
1. Edit Python/XML files
│
▼
2. Upgrade the module
(CLI: -u library_app, or UI: Apps → Upgrade)
│
▼
3. Refresh the browser
(Ctrl+Shift+R for hard refresh)
│
▼
4. Test your changes
│
▼
5. If something's wrong → check server logs → fix → go to step 1
Pro tip: Use dev_mode = reload,xml in your odoo.conf. This way:
- Python changes auto-restart the server
- XML changes are reloaded without a full upgrade
- You still need to upgrade when adding new fields or new files
Scaffolding a Module with odoo-bin scaffold
Odoo provides a built-in command to generate a module skeleton. It’s like django-admin startapp but for Odoo.
Using the Scaffold Command
# Docker method:
docker compose exec odoo odoo scaffold library_app /mnt/extra-addons
# Source method:
python odoo/odoo-bin scaffold library_app ./custom-addons
What this does:
- Creates a
library_app/directory in the specified path - Generates basic files:
__init__.py,__manifest__.py,models/,views/,security/, etc. - Fills them with boilerplate code and helpful comments
What the Scaffolded Module Looks Like
After running the command, you’ll get something like:
library_app/
├── __init__.py
├── __manifest__.py
├── controllers/
│ ├── __init__.py
│ └── controllers.py
├── demo/
│ └── demo.xml
├── models/
│ ├── __init__.py
│ └── models.py
├── security/
│ └── ir.model.access.csv
└── views/
├── templates.xml
└── views.xml
Should You Use Scaffold?
Pros:
- Quick way to get started
- Correct directory structure out of the box
- Includes helpful comments in generated files
Cons:
- Generated code is generic (you’ll rename/rewrite most of it)
- File names are generic (
models.pyinstead ofbook.py) - Some developers prefer building from scratch to understand every file
Our recommendation: Use scaffold once to see what it generates, then build manually for learning purposes. Once you’re experienced, use scaffold (or your own template) for speed.
In the hands-on section below, we’ll build manually so you understand every file.
Hands-on: Create a Skeleton Module “library_app”
Let’s build our module from scratch. This is the module we’ll extend throughout the entire series.
Step 1: Create the Module Directory
# Docker method — create files on your host machine
cd odoo18-dev/custom-addons
mkdir -p library_app/models
mkdir -p library_app/views
mkdir -p library_app/security
mkdir -p library_app/data
mkdir -p library_app/demo
mkdir -p library_app/static/description
# Source method
cd ~/odoo18-workspace/custom-addons
mkdir -p library_app/models
mkdir -p library_app/views
mkdir -p library_app/security
mkdir -p library_app/data
mkdir -p library_app/demo
mkdir -p library_app/static/description
What -p does: Creates parent directories as needed. If library_app/ doesn’t exist, it creates it too.
Step 2: Create __manifest__.py
Create the file library_app/__manifest__.py:
# library_app/__manifest__.py
{
'name': 'Library Management',
'version': '18.0.1.0.0',
'summary': 'Manage a library of books and members',
'description': """
Library Management System
=========================
A complete library management solution for tracking
books, authors, members, and borrowing activities.
""",
'author': 'Your Name',
'website': 'https://your-website.com',
'category': 'Services/Library',
'license': 'LGPL-3',
# Dependencies — we only need 'base' for now
# We'll add 'mail' in a later lesson when we add chatter
'depends': ['base'],
# Data files to load (empty for now — we'll add files in the next lessons)
'data': [
# 'security/ir.model.access.csv',
# 'views/book_views.xml',
# 'views/menu_views.xml',
],
# Demo data
'demo': [
# 'demo/book_demo.xml',
],
'installable': True,
'application': True,
'auto_install': False,
}
Why are some lines commented out? Because we haven’t created those files yet. If you list a file in data that doesn’t exist, Odoo will throw an error on installation. We’ll uncomment them as we create the files in upcoming lessons.
Step 3: Create the Root __init__.py
Create the file library_app/__init__.py:
# library_app/__init__.py
# Import the models sub-package so Odoo can register our models.
# When Odoo loads this module, it runs this file first.
# This file then triggers models/__init__.py, which imports our model files.
from . import models
Step 4: Create the Models __init__.py
Create the file library_app/models/__init__.py:
# library_app/models/__init__.py
# Import each model file here.
# Every time you create a new .py file in this directory,
# you must add a corresponding import line.
from . import book
Step 5: Create the Book Model (Placeholder)
Create the file library_app/models/book.py:
# library_app/models/book.py
from odoo import models, fields
class LibraryBook(models.Model):
"""A model to represent a book in the library."""
_name = 'library.book'
# _name is the technical name of the model.
# Convention: 'module_name.model_name' using dots as separators.
# Odoo creates a database table called 'library_book' (dots → underscores).
_description = 'Library Book'
# _description is a human-readable name shown in logs and error messages.
# If not set, Odoo uses _name, which is less readable.
name = fields.Char(
string='Title', # Label shown in the UI
required=True, # Cannot be empty (NOT NULL in the database)
help='The title of the book', # Tooltip shown when hovering in the UI
)
# fields.Char creates a VARCHAR column.
# 'string' is optional — if omitted, Odoo uses the Python field name
# with underscores replaced by spaces and title-cased ("name" → "Name").
Why is this model so simple? Because we’re building incrementally. This is just the skeleton. In Lesson 3, we’ll add many more fields. In Lesson 4, we’ll add relational fields. In Lesson 5, we’ll add business logic.
Step 6: Add a Module Icon (Optional but Recommended)
Odoo shows module icons in the Apps menu. Without an icon, your module gets a generic placeholder.
You can use any PNG image (ideally 128×128 pixels). Save it as:
library_app/static/description/icon.png
If you don’t have a custom icon, that’s fine — the module will still work.
Step 7: Verify the Structure
Your module should now look like this:
# Verify the structure
find library_app -type f | sort
Expected output:
library_app/__init__.py
library_app/__manifest__.py
library_app/models/__init__.py
library_app/models/book.py
Plus the empty directories we created (views/, security/, data/, demo/, static/description/).
Step 8: Install the Module
Now let’s install it and verify everything works.
First, Update the Apps List
If this is a new module that Odoo hasn’t seen before, you need to tell Odoo to scan for new modules:
Via UI (Developer Mode must be on):
- Go to Apps
- Click “Update Apps List” (top menu)
- Click “Update” in the dialog
Via CLI:
# Docker method:
docker compose restart odoo
# Source method: just restart the Odoo server
# (Ctrl+C to stop, then start again)
Then, Install the Module
Via UI:
- Go to Apps
- Remove the default “Apps” filter in the search bar
- Search for “Library”
- Click “Install” on “Library Management”
Via CLI:
# Docker method:
docker compose exec odoo odoo -d odoo18dev -i library_app --stop-after-init
# Source method:
python odoo/odoo-bin -c odoo.conf -d odoo18dev -i library_app --stop-after-init
Step 9: Verify the Installation
If installation succeeds:
- Check the Apps menu — “Library Management” should appear as installed
- Check the database — Go to Settings → Technical → Database Structure → Models, search for “library.book”. You should see your model listed.
- Check the table — Using the Odoo Shell:
# Open the shell
# Docker: docker compose exec odoo odoo shell -d odoo18dev
# Source: python odoo/odoo-bin shell -c odoo.conf -d odoo18dev
# Check if the model exists
model = env['library.book']
print(model._name) # Output: library.book
print(model._description) # Output: Library Book
# Check the fields
print(list(model._fields.keys()))
# Output includes: 'id', 'name', 'create_date', 'write_date', 'create_uid', 'write_uid', etc.
# Notice: Odoo automatically adds id, create_date, write_date, etc.!
What about the UI? You won’t see any menu items yet, because we haven’t created views or menus. The model exists in the database, but there’s no way to interact with it from the browser. We’ll fix that in Lesson 6 (Views).
If Something Goes Wrong
| Symptom | Likely Cause | Fix |
|---|---|---|
| Module not in Apps list | Not in addons_path |
Check your odoo.conf or docker-compose.yml volumes |
| Module not in Apps list | Apps list not updated | Click “Update Apps List” in Developer Mode |
Error on install: SyntaxError |
Typo in __manifest__.py or Python files |
Check for missing commas, brackets, quotes |
Error: ModuleNotFoundError |
Missing import in __init__.py |
Check the __init__.py chain |
Error: FileNotFoundError |
File listed in data doesn’t exist |
Check file paths in __manifest__.py |
| Model not found in shell | Model file not imported | Check models/__init__.py has from . import book |
Summary & What’s Next
Key Takeaways
- A module is a directory with
__init__.pyand__manifest__.py— that’s the minimum. __manifest__.pyis the module’s ID card. It defines the name, version, dependencies, and which files to load. Thedatalist order matters.- The
__init__.pychain is how Odoo discovers your Python code. Root__init__.py→ sub-package__init__.py→ model files. Forget one link, and Odoo won’t see your code. - Dependencies tell Odoo which modules must be installed first. Only list direct dependencies your code actually uses.
- Three lifecycle operations: Install (first time), Upgrade (after changes), Uninstall (removes everything).
- The development cycle: Edit → Upgrade → Refresh → Test → Repeat.
File Checklist for Our library_app
| File | Status | Purpose |
|---|---|---|
__manifest__.py |
✅ Created | Module metadata |
__init__.py (root) |
✅ Created | Imports models package |
models/__init__.py |
✅ Created | Imports book.py |
models/book.py |
✅ Created | Defines library.book (minimal) |
views/book_views.xml |
⬜ Lesson 6 | Form, list, search views |
views/menu_views.xml |
⬜ Lesson 6 | Navigation menu |
security/ir.model.access.csv |
⬜ Lesson 8 | Access control |
data/book_data.xml |
⬜ Lesson 10 | Default data |
demo/book_demo.xml |
⬜ Lesson 10 | Demo data |
What’s Next?
In Lesson 3: The ORM — Odoo’s Heart (Part 1: Models & Fields), we’ll expand our library.book model with many more field types. You’ll learn:
- The difference between
Model,TransientModel, andAbstractModel - All field types: Char, Text, Integer, Float, Boolean, Date, Selection, Html, Binary
- Field attributes: default values, required, readonly, index
- Automatic fields that Odoo creates for you
We’ll go from our one-field placeholder to a fully defined book model with 10+ fields.
Exercises: 1. If you used the scaffold command, compare the generated structure with what we built manually. What differences do you see? 2. Try creating a second model called
library.memberin a new filemodels/member.py. Remember to: – Create the Python file with a basic model (just_name,_description, and anamefield) – Import it inmodels/__init__.py– Upgrade the module – Verify it exists in the Odoo Shell 3. Try deliberately breaking something: – Remove thefrom . import bookline frommodels/__init__.py– Upgrade the module — what error do you get? – Add a non-existent file to thedatalist in__manifest__.py— what happens? – These experiments help you recognize common errors quickly 4. Explore an existing Odoo module. Look at the source code of thecontactsmodule: – Docker:docker compose exec odoo find /usr/lib/python3/dist-packages/odoo/addons/contacts -type f– Source:ls -la odoo/addons/contacts/– Open its__manifest__.pyand__init__.py— do they follow the same patterns we learned?
Previous lesson: Lesson 1 — Introduction to Odoo Next lesson: Lesson 3 — The ORM: Models & Fields
