Ad – 728×90
🛠️ Projects

Build a Library Management Module – Odoo Capstone Project

This capstone project brings together everything you've learned in the Odoo course: defining models, writing views, setting up security, creating computed fields, building wizards, adding portal pages, and automating actions. By the end you'll have a fully functional library management module with book tracking, member loans, and a customer portal.

⏱️ 3-5 hours 🎯 Intermediate 📅 Updated 2026
What you'll build:
  • Three models: library.book, library.member, library.loan
  • Form, list, kanban, and search views
  • Access rights and record rules
  • Computed fields: loan count, overdue status, fine amount
  • A wizard: "Return Book" dialog with batch processing
  • Customer portal: member's loan history
  • Scheduled action: mark overdue loans automatically

Project Overview

The library module tracks books and member loan history. The requirements:

  • Librarians manage books, members, and loans from the backend
  • Members can view their active and past loans via a portal page at /my/loans
  • Overdue loans (past due date) are automatically marked by a daily cron job
  • A "Return Book" wizard allows processing multiple returns at once

Step 1: Module Structure

Bash
library_management/
├── __init__.py
├── __manifest__.py
├── models/
│   ├── __init__.py
│   ├── library_book.py
│   ├── library_member.py
│   └── library_loan.py
├── views/
│   ├── library_book_views.xml
│   ├── library_member_views.xml
│   ├── library_loan_views.xml
│   └── library_menus.xml
├── security/
│   ├── ir.model.access.csv
│   └── library_security.xml
├── wizards/
│   ├── __init__.py
│   ├── return_book_wizard.py
│   └── return_book_wizard_views.xml
├── controllers/
│   ├── __init__.py
│   └── portal.py
├── data/
│   └── library_cron.xml
└── templates/
    └── portal_my_loans.xml

Step 2: Models

Python
# models/library_loan.py
from odoo import models, fields, api
from odoo.exceptions import ValidationError
from datetime import date


class LibraryLoan(models.Model):
    _name = 'library.loan'
    _inherit = ['portal.mixin', 'mail.thread']
    _description = 'Library Loan'
    _order = 'loan_date desc'

    member_id = fields.Many2one('res.partner', string='Member', required=True)
    book_id = fields.Many2one('library.book', string='Book', required=True)
    loan_date = fields.Date(string='Loan Date', default=fields.Date.today)
    due_date = fields.Date(string='Due Date', required=True)
    return_date = fields.Date(string='Return Date')
    state = fields.Selection([
        ('active', 'Active'),
        ('returned', 'Returned'),
        ('overdue', 'Overdue'),
    ], default='active', tracking=True)

    days_overdue = fields.Integer(
        string='Days Overdue',
        compute='_compute_days_overdue',
    )
    fine_amount = fields.Float(
        string='Fine (€)',
        compute='_compute_fine_amount',
        store=True,
    )

    @api.depends('due_date', 'return_date', 'state')
    def _compute_days_overdue(self):
        today = date.today()
        for loan in self:
            end = loan.return_date or today
            if loan.due_date and end > loan.due_date:
                loan.days_overdue = (end - loan.due_date).days
            else:
                loan.days_overdue = 0

    @api.depends('days_overdue')
    def _compute_fine_amount(self):
        rate = 0.50  # €0.50 per day overdue
        for loan in self:
            loan.fine_amount = loan.days_overdue * rate

    def _compute_access_url(self):
        for loan in self:
            loan.access_url = f'/my/loans/{loan.id}'

    @api.constrains('due_date', 'loan_date')
    def _check_due_date(self):
        for loan in self:
            if loan.due_date and loan.loan_date:
                if loan.due_date < loan.loan_date:
                    raise ValidationError(
                        'Due date must be after the loan date.'
                    )
Ad – 728×90

Step 3: Return Book Wizard

Python
# wizards/return_book_wizard.py
from odoo import models, fields, api
from datetime import date


class ReturnBookWizard(models.TransientModel):
    _name = 'library.return.wizard'
    _description = 'Return Book Wizard'

    loan_ids = fields.Many2many(
        'library.loan',
        string='Loans to Return',
        domain=[('state', 'in', ['active', 'overdue'])],
    )
    return_date = fields.Date(
        string='Return Date',
        default=fields.Date.today,
        required=True,
    )

    def action_return(self):
        self.loan_ids.write({
            'state': 'returned',
            'return_date': self.return_date,
        })
        for loan in self.loan_ids:
            loan.book_id.available_copies += 1
        return {'type': 'ir.actions.act_window_close'}

Step 4: Scheduled Action

XML
<!-- data/library_cron.xml -->
<record id="ir_cron_mark_overdue_loans" model="ir.cron">
  <field name="name">Library: Mark Overdue Loans</field>
  <field name="model_id" ref="model_library_loan"/>
  <field name="state">code</field>
  <field name="code">model.mark_overdue_loans()</field>
  <field name="interval_number">1</field>
  <field name="interval_type">days</field>
  <field name="numbercall">-1</field>
  <field name="active">True</field>
</record>
Python
# In LibraryLoan model
@api.model
def mark_overdue_loans(self):
    from datetime import date
    overdue = self.search([
        ('state', '=', 'active'),
        ('due_date', '<', date.today()),
    ])
    overdue.write({'state': 'overdue'})

Step 5: Portal Integration

Add the portal controller (extend CustomerPortal) and a QWeb template. See the Portal Controllers and Portal Templates lessons for the full patterns to follow.

Key steps for the portal page:

  1. Add portal.mixin to library.loan (already done above)
  2. Add access rights for base.group_portal in ir.model.access.csv
  3. Add record rule: portal users see only their own loans
  4. Write LibraryPortal(CustomerPortal) with portal_my_loans and portal_loan_detail routes
  5. Write QWeb templates inheriting portal.portal_layout
Extension challenges:
  • Add a library.category model and filter books by category in the list view
  • Add an email notification when a loan becomes overdue (using mail.template)
  • Extend the portal to allow members to request a loan via a form
  • Add a Kanban view for loans grouped by state
  • Add a dashboard with a count of active loans, overdue loans, and total fines