How Server Actions Work
Server actions (ir.actions.server) let you run Python code from buttons, automated actions, or menu items without writing a custom module. In the code, you have access to records (the active recordset), env (the environment), model (the model), time, datetime, and log.
1. Mass Confirm Sale Orders
for record in records:
if record.state == 'draft':
record.action_confirm()2. Set a Field Based on Another Field
for record in records:
if record.partner_id.country_id.code == 'US':
record.write({'fiscal_position_id': env.ref('account.fiscal_position_us').id})3. Send an Email Using a Template
template = env.ref('sale.email_template_edi_sale')
for record in records:
template.send_mail(record.id, force_send=True)4. Create a Follow-Up Activity
for record in records:
record.activity_schedule(
'mail.mail_activity_data_call',
date_deadline=datetime.date.today() + datetime.timedelta(days=3),
summary='Follow up with customer',
note='Check if the customer received the quotation.',
)5. Archive Old Records
cutoff = datetime.date.today() - datetime.timedelta(days=365)
old = env['sale.order'].search([
('state', '=', 'cancel'),
('date_order', '<', str(cutoff))
])
old.write({'active': False})
log(f'Archived {len(old)} cancelled orders older than 1 year.')6. Duplicate Records
for record in records:
new = record.copy(default={'name': record.name + ' (Copy)'})
log(f'Created copy: {new.name}')7. Mass Update Tags
tag = env.ref('sale.tag_important') # or search by name
for record in records:
record.write({'tag_ids': [(4, tag.id)]})8. Generate a Unique Reference
for record in records:
if not record.client_order_ref:
seq = env['ir.sequence'].next_by_code('sale.order.reference')
record.write({'client_order_ref': seq or f'REF-{record.id}'})9. Compute and Set Margin
for record in records:
if record.amount_total:
cost = sum(line.product_id.standard_price * line.product_uom_qty for line in record.order_line)
margin = ((record.amount_total - cost) / record.amount_total) * 100
record.message_post(body=f'Margin: {margin:.1f}%')10. Reassign Salesperson by Region
region_map = {
'US': env.ref('base.user_admin').id,
'GB': env.ref('base.user_demo').id,
}
for record in records:
country = record.partner_id.country_id.code
if country in region_map:
record.write({'user_id': region_map[country]})11. Create Invoice from Sale Order
for record in records:
if record.state == 'sale' and record.invoice_status == 'to invoice':
record._create_invoices()
log(f'Invoice created for {record.name}')12. Auto-Set Payment Terms
for record in records:
if record.amount_total > 10000:
record.write({'payment_term_id': env.ref('account.account_payment_term_30days').id})
else:
record.write({'payment_term_id': env.ref('account.account_payment_term_immediate').id})13. Post Internal Note
for record in records:
record.message_post(
body='This record was reviewed automatically.',
message_type='comment',
subtype_xmlid='mail.mt_note',
)14. Subscribe Users as Followers
users = env['res.users'].search([('groups_id', 'in', env.ref('sales_team.group_sale_manager').id)])
for record in records:
record.message_subscribe(partner_ids=users.mapped('partner_id').ids)15. Validate Stock Picking
for record in records:
if record.state == 'assigned':
for move_line in record.move_line_ids:
move_line.write({'quantity': move_line.quantity_product_uom})
record.button_validate()16. Export Data to Log
total = sum(records.mapped('amount_total'))
count = len(records)
avg = total / count if count else 0
log(f'Selected {count} orders. Total: {total:.2f}, Average: {avg:.2f}')17. Update Product Prices
for record in records:
if record.list_price < record.standard_price:
record.write({'list_price': record.standard_price * 1.3})
log(f'Updated {record.name} price to {record.list_price}')18. Create a Task from Sale Order
project = env['project.project'].search([('name', '=', 'Implementation')], limit=1)
for record in records:
env['project.task'].create({
'name': f'Setup for {record.partner_id.name}',
'project_id': project.id,
'partner_id': record.partner_id.id,
'description': f'Sale Order: {record.name}\nAmount: {record.amount_total}',
})19. Merge Duplicate Partners
# Find duplicates by email
for record in records:
if record.email:
dupes = env['res.partner'].search([
('email', '=ilike', record.email),
('id', '!=', record.id)
])
if dupes:
log(f'Potential duplicates of {record.name}: {dupes.mapped("name")}')20. Bulk Set Analytic Account
analytic = env['account.analytic.account'].search([('name', '=', 'Marketing')], limit=1)
if analytic:
for record in records:
for line in record.order_line:
line.write({'analytic_distribution': {str(analytic.id): 100}})
log(f'Set analytic account on {len(records)} orders.')Using Server Actions Effectively
- Go to Settings > Technical > Server Actions to create actions
- Set the model, then choose "Execute Python Code" as the action type
- Use
recordsvariable for the selected recordset - Use
log()to output debug messages - Test on a single record before running on a batch
- Server actions run with the current user permissions unless you use
.sudo()