Skip to content

Odoo Mixin Patterns Complete Guide: Reusable Model Behaviors

DeployMonkey Team · March 24, 2026 11 min read

What Are Mixins in Odoo?

Mixins are abstract models that add reusable behavior to other models through inheritance. Instead of duplicating code across models, you define the behavior once in a mixin and inherit it wherever needed. Odoo uses mixins extensively — the chatter (mail.thread), activity scheduling (mail.activity.mixin), and portal access (portal.mixin) are all mixins.

Built-in Mixins

mail.thread — Chatter and Messaging

The most commonly used mixin. Adds the chatter widget, message logging, email notifications, and field tracking:

class MyModel(models.Model):
    _name = 'my.model'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    name = fields.Char(tracking=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('confirmed', 'Confirmed'),
    ], tracking=True)
    partner_id = fields.Many2one('res.partner', tracking=True)

Key features provided:

  • message_post() — Send messages and log notes
  • tracking=True on fields — Auto-log field value changes
  • message_subscribe() — Manage followers
  • message_notify() — Send notifications without logging
  • Email gateway integration for incoming messages

mail.activity.mixin — Scheduled Activities

Adds the ability to schedule activities (to-dos, calls, meetings):

_inherit = ['mail.thread', 'mail.activity.mixin']

Provides:

  • activity_schedule() — Create activities programmatically
  • activity_feedback() — Mark activities as done with feedback
  • activity_unlink() — Cancel activities
  • Activity deadline tracking and overdue alerts
  • Kanban activity widget with color-coded deadlines

portal.mixin — Portal Access

Enables portal access URLs and sharing:

_inherit = ['portal.mixin']

def _compute_access_url(self):
    for record in self:
        record.access_url = '/my/model/%s' % record.id

Provides:

  • access_url — Computed URL for portal access
  • access_token — Security token for URL-based access
  • get_portal_url() — Generate shareable portal link
  • _portal_ensure_token() — Generate token if missing (call before get_portal_url)

image.mixin — Image Handling

Adds standardized image fields with automatic resizing:

_inherit = ['image.mixin']

Provides: image_1920, image_1024, image_512, image_256, image_128 — all computed from the original image.

Building Custom Mixins

Abstract Model Pattern

Create a mixin as an AbstractModel:

class TimestampMixin(models.AbstractModel):
    _name = 'timestamp.mixin'
    _description = 'Timestamp Mixin'

    created_by = fields.Many2one(
        'res.users', default=lambda self: self.env.uid,
        readonly=True,
    )
    approved_by = fields.Many2one('res.users', readonly=True)
    approved_date = fields.Datetime(readonly=True)

    def action_approve(self):
        self.write({
            'approved_by': self.env.uid,
            'approved_date': fields.Datetime.now(),
        })

Using Custom Mixins

class PurchaseRequest(models.Model):
    _name = 'purchase.request'
    _inherit = ['timestamp.mixin', 'mail.thread']

    name = fields.Char(required=True)
    amount = fields.Float()

Mixin with Constraints

class DateRangeMixin(models.AbstractModel):
    _name = 'date.range.mixin'
    _description = 'Date Range Mixin'

    date_start = fields.Date(required=True)
    date_end = fields.Date(required=True)

    @api.constrains('date_start', 'date_end')
    def _check_date_range(self):
        for record in self:
            if record.date_end < record.date_start:
                raise ValidationError(
                    "End date must be after start date."
                )

Mixin Inheritance Order

Python's MRO (Method Resolution Order) matters when using multiple mixins:

# Methods resolved left to right
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']

If two mixins define the same method, the leftmost one wins. Use super() to chain behavior through all parents.

Common Patterns

Soft Delete Mixin

class SoftDeleteMixin(models.AbstractModel):
    _name = 'soft.delete.mixin'
    _description = 'Soft Delete'

    active = fields.Boolean(default=True)
    deleted_at = fields.Datetime(readonly=True)
    deleted_by = fields.Many2one('res.users', readonly=True)

    def action_soft_delete(self):
        self.write({
            'active': False,
            'deleted_at': fields.Datetime.now(),
            'deleted_by': self.env.uid,
        })

Sequence Mixin

class SequenceMixin(models.AbstractModel):
    _name = 'sequence.mixin'
    _description = 'Auto Sequence'

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

    @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(
                    self._name
                ) or 'New'
        return super().create(vals_list)

Best Practices

  • Use AbstractModel for mixins — they do not create database tables
  • Keep mixins focused on a single behavior
  • Always call super() when overriding mixin methods
  • Document which fields and methods the mixin provides
  • Test mixins independently with a test model that inherits them