Odoo Custom Module Development Part 6 - Reports, Demo Data, and Tests

This is the sixth article in the Odoo custom module development series.

In Part 5, we added business logic to the estate module. In this final article, we will make the module easier to demonstrate and maintain by adding demo data, a PDF report, and automated tests.

These are the pieces that turn a learning project into something closer to a production-ready module.

What we will add

We will create:

  • demo data records
  • a simple sequence
  • a QWeb PDF report
  • test cases for model behavior

1. Create directories for data, reports, and tests

Inside the estate module:

mkdir -p data report tests

Create these files:

touch data/estate_demo.xml
touch data/estate_sequence.xml
touch report/estate_property_report.xml
touch report/estate_property_templates.xml
touch tests/__init__.py
touch tests/test_estate_property.py

2. Add demo data

Open data/estate_demo.xml:

<odoo>
<record id="estate_property_type_house" model="estate.property.type">
<field name="name">House</field>
</record>

<record id="estate_property_type_apartment" model="estate.property.type">
<field name="name">Apartment</field>
</record>

<record id="estate_property_demo_1" model="estate.property">
<field name="name">Demo Villa</field>
<field name="postcode">1207</field>
<field name="expected_price">250000</field>
<field name="bedrooms">4</field>
<field name="living_area">180</field>
<field name="garden">1</field>
<field name="garden_area">35</field>
<field name="property_type_id" ref="estate_property_type_house"/>
</record>

<record id="estate_property_demo_2" model="estate.property">
<field name="name">City Apartment</field>
<field name="postcode">1212</field>
<field name="expected_price">150000</field>
<field name="bedrooms">2</field>
<field name="living_area">80</field>
<field name="property_type_id" ref="estate_property_type_apartment"/>
</record>
</odoo>

Demo data is useful when you want an immediately usable database for local development or training.

3. Add a sequence for property references

Update the property model to include a reference field:

reference = fields.Char(required=True, copy=False, readonly=True, default='New')

Then override create() in models/property.py:

@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('reference', 'New') == 'New':
vals['reference'] = self.env['ir.sequence'].next_by_code('estate.property') or 'New'
return super().create(vals_list)

Now create data/estate_sequence.xml:

<odoo noupdate="1">
<record id="seq_estate_property" model="ir.sequence">
<field name="name">Estate Property</field>
<field name="code">estate.property</field>
<field name="prefix">EST/</field>
<field name="padding">5</field>
<field name="number_increment">1</field>
</record>
</odoo>

4. Show the reference in views

Add reference near the top of the list and form views.

Example list view field:

<field name="reference"/>

Example form view field:

<field name="reference" readonly="1"/>

5. Create a simple PDF report action

Open report/estate_property_report.xml:

<odoo>
<record id="action_report_estate_property" model="ir.actions.report">
<field name="name">Property Report</field>
<field name="model">estate.property</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">estate.report_estate_property</field>
<field name="report_file">estate.report_estate_property</field>
<field name="binding_model_id" ref="model_estate_property"/>
<field name="binding_type">report</field>
</record>
</odoo>

Now create the template in report/estate_property_templates.xml:

<odoo>
<template id="report_estate_property">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="web.external_layout">
<div class="page">
<h2>Property Report</h2>
<p><strong>Reference:</strong> <span t-field="o.reference"/></p>
<p><strong>Name:</strong> <span t-field="o.name"/></p>
<p><strong>Postcode:</strong> <span t-field="o.postcode"/></p>
<p><strong>Expected Price:</strong> <span t-field="o.expected_price"/></p>
<p><strong>Selling Price:</strong> <span t-field="o.selling_price"/></p>
<p><strong>Total Area:</strong> <span t-field="o.total_area"/></p>
<p><strong>State:</strong> <span t-field="o.state"/></p>
</div>
</t>
</t>
</t>
</template>
</odoo>

With this, users can print a property report from the record action menu.

6. Add tests for model behavior

Open tests/__init__.py:

from . import test_estate_property

Now add this to tests/test_estate_property.py:

from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase


class TestEstateProperty(TransactionCase):

def setUp(self):
super().setUp()
self.Property = self.env['estate.property']

def test_expected_price_must_be_positive(self):
with self.assertRaises(ValidationError):
self.Property.create({
'name': 'Invalid Property',
'expected_price': 0,
})

def test_total_area_is_computed(self):
record = self.Property.create({
'name': 'Test Property',
'expected_price': 100000,
'living_area': 120,
'garden_area': 30,
})
self.assertEqual(record.total_area, 150)

These tests check both validation and computed behavior.

7. Load files from the manifest

Update __manifest__.py to include the new files:

{
'name': 'Estate',
'version': '1.0',
'summary': 'Real estate management training module',
'sequence': 10,
'description': 'Custom training module for learning Odoo development.',
'category': 'Tutorials',
'author': 'Your Name',
'depends': ['base', 'web'],
'data': [
'security/estate_security.xml',
'security/estate_rules.xml',
'security/ir.model.access.csv',
'data/estate_sequence.xml',
'views/estate_property_views.xml',
'report/estate_property_report.xml',
'report/estate_property_templates.xml',
],
'demo': [
'data/estate_demo.xml',
],
'installable': True,
'application': True,
'license': 'LGPL-3',
}

Why add web to depends? Because the report template uses web.external_layout.

8. Upgrade the module and test the report

Run:

cd ~/odoo-dev/odoo
source .venv/bin/activate
python3 odoo-bin -c ~/odoo-dev/odoo.conf -d odoo19 -u estate --stop-after-init

Then:

  1. open a property record
  2. click the print menu
  3. choose Property Report

If wkhtmltopdf is installed correctly, Odoo should generate a PDF.

9. Run automated tests

Run module tests with:

python3 odoo-bin -c ~/odoo-dev/odoo.conf -d odoo_test --test-enable -i estate --stop-after-init

Using a dedicated test database is better than reusing your normal development database.

10. Best practices for maintainable modules

  • keep XML IDs stable once other data references them
  • use noupdate="1" for sequences and technical records that should not reset on upgrade
  • keep demo data separate from required data
  • write tests for business rules that must never silently break
  • add reports only after your model and UI are already stable

11. Common mistakes

The report action exists but PDF generation fails

This often means wkhtmltopdf is missing or incompatible.

Tests do not run

Check:

  • tests/__init__.py imports the test file
  • the module is installed during the test run
  • you are using a clean database for tests

Demo records fail during install

This usually means the referenced model or XML ID is not loaded yet. Verify file order in the manifest.

Final words

You now have a small but complete custom Odoo module with models, security, views, business logic, demo data, reporting, and tests.

That is a strong base for building real Odoo business applications.

Previous article: Part 5 - Relations, Computed Fields, and Business Logic

Related posts

Md. Monirul Alom

Md. Monirul Alom

I am a Full Stack Web developer. I love to code, travel, do some volunteer work. Whenever I get time I write for this blog