跳到主要内容

代码速查表

后端

Domain

from odoo.osv import expression

  • expression.AND(domains)
  • expression.OR(domains)

One2many/Many2many Command

对于x2m的关联关系,通过更直观的命名函数执行更具有可读性,以此替代三元组首位为整数的方式。

from odoo import Command or from odoo.fields import Command

  • Command.create(values): 传入values, 创建新的记录并关联到当前记录。

  • Command.update(id, values): 传入id、values,修改关联记录的数据。

  • Command.delete(id): 传入id,移除关联关系。

  • Command.unlink(id): 传入id,移除关联关系并删除指定id的记录集。

  • Command.link(id): 传入id,添加关联关系。

  • Command.clear(): 移除所有关联关系。

  • Command.set(ids): 传入ids,先移除所有关联关系,再将所有ids添加关联。

return ir.actions.client

  • display_notification(右上角提示窗)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success', # 提示窗类型,可选项: success/danger/warning/info/...
'title': _("Leads Assigned"), # 标题
'message': 'Message, %s',
'next': { # 在services.notification.add之后返回next的action
'type': 'ir.actions.act_window_close'
},
'links': [{ # 在message中通过%s占位符设置一个a标签
'label': production.name,
'url': f'#action={action.id}&id={production.id}&model=mrp.production'
}],
'sticky': True, # 是否滞留提示窗,需要手工关闭。
}
}

通过/web/export/路由快速实现导出excel

摘自odoo17,其他版本待核实。

import json
from urllib.parse import urlencode

def export_xlsx(self):
params = {
"data": json.dumps({
"domain": domain,
"fields": export_fields, # 导出的列会按照这个列表的顺序
"groupby": [],
"ids": False,
"import_compat": False,
"model": "sale.order",
})
}
params_json = json.dumps(params)
return {
"name": "Sale Order",
"type": "ir.actions.act_url",
"url": '/web/export/xlsx?%s' % urlencode(params),
"target": "new",
}

将many2many(ir.attachment)打包成zip

pack_attachments_to_zip
import zipfile
import io
import base64

class YourModel(models.Model):
def pack_attachments_to_zip(self, attachment_ids,
zip_filename="attachments.zip"):
"""
将多个附件打包成zip格式并存储为新的附件

:param attachment_ids: many2many附件的ID列表
:param zip_filename: 生成的zip文件名
:return: 新创建的zip附件记录
"""
# - 使用内存缓冲区避免创建临时文件
# - 支持自定义zip文件名
# - 保持原始文件名在zip包内
# - 返回新创建的ir.attachment记录
if not attachment_ids:
return False
if isinstance(attachment_ids, list):
attachment_ids = self.env['ir.attachment'].browse(attachment_ids)
if isinstance(attachment_ids, int):
attachment_ids = self.env['ir.attachment'].browse([attachment_ids])

# 创建内存中的zip文件
zip_buffer = io.BytesIO()

with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
# 遍历所有附件
for attachment in attachment_ids:
if attachment.datas:
# 解码附件数据
file_data = base64.b64decode(attachment.datas)
# 添加到zip文件中,使用原始文件名
zip_file.writestr(attachment.name or f"file_{attachment.id}",
file_data)

# 获取zip文件的二进制数据
zip_buffer.seek(0)
zip_data = zip_buffer.getvalue()
zip_buffer.close()

# 编码为base64
zip_base64 = base64.b64encode(zip_data)

# 创建新的附件记录
zip_attachment = self.env['ir.attachment'].create({
'name': zip_filename,
'type': 'binary',
'datas': zip_base64,
'mimetype': 'application/zip',
'res_model': self._name,
'res_id': self.id,
})

return zip_attachment

使用示例

# 在模型中调用
class YourModel(models.Model):
_name = 'your.model'

attachment_ids = fields.Many2many('ir.attachment', string='附件')
zip_attachment_id = fields.Many2one('ir.attachment', string='压缩包附件')

def create_zip_attachment(self):
if self.attachment_ids:
zip_attachment = self.pack_attachments_to_zip(
self.attachment_ids.ids,
f"attachments_{self.id}.zip"
)
self.zip_attachment_id = zip_attachment.id
return zip_attachment
return False

通过_auto_init/init新建索引(index)

使用示例: 以下是原生代码在hh_leave表上新增一条索引。此处用到odoo.tools.sql.craete_index(可以通过from odoo.tools import create_index引用)。

addons/hr_holidays/models/hr_leave.py
    def _auto_init(self):
res = super(HolidaysRequest, self)._auto_init()
tools.create_index(self._cr, 'hr_leave_date_to_date_from_index',
self._table, ['date_to', 'date_from'])
return res
tips

官方更推荐将创建索引的操作放在init函数中,因为init函数会在_auto_init之后被调用。

_auto_init的说明

Doc

Initialize the database schema of self:

  • create the corresponding table,
  • create/update the necessary columns/tables for fields,
  • initialize new columns on existing rows,
  • add the SQL constraints given on the model,
  • add the indexes on indexed fields,

Also prepare post-init stuff to:

  • add foreign key constraints,
  • reflect models, fields, relations and constraints,
  • mark fields to recompute on existing records.

Note: you should not override this method. Instead, you can modify the model's database schema by overriding method :meth:~.init, which is called right after this one.

create_index源码

odoo/tools/sql.py
def create_index(cr, indexname, tablename, expressions, method='btree', where=''):
""" Create the given index unless it exists. """
if index_exists(cr, indexname):
return
args = ', '.join(expressions)
if where:
where = f' WHERE {where}'
cr.execute(f'CREATE INDEX "{indexname}" ON "{tablename}" USING {method} ({args}){where}')
_schema.debug("Table %r: created index %r (%s)", tablename, indexname, args)

前端

Search View - Filter 筛选日期周期

<filter string="当天" name="today"  domain="[('create_date','>=', time.strftime('%Y-%m-%d 00:00:00')),('create_date', '<', context_today().strftime('%Y-%m-%d 23:59:59'))]"/>

<filter string="本周" name="last_week" domain="[('create_date','>', (context_today() - datetime.timedelta(weeks=1)).strftime('%%Y-%%m-%%d 00:00:00'))]"/>

<filter string="本月" name="month" domain="[('create_date','>=', time.strftime('%Y-%m-01 00:00:00')),('create_date','<', (context_today() + relativedelta(months=1)).strftime('%Y-%m-01 00:00:00'))]"/>

<filter string="上月" name="month2" domain="[('create_date','<', time.strftime('%Y-%m-01 00:00:00')),('create_date','>=', (context_today() - relativedelta(months=1)).strftime('%Y-%m-01 00:00:00'))]"/>

<filter string="本年" name="year" domain="[('create_date','<=', time.strftime('%Y-12-31 23:59:59')),('create_date','>=', time.strftime('%Y-01-01 00:00:00'))]"/>



<separator/>

<filter name="过去24小时" string="Last 24h" domain="[('create_date','>', (context_today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d 00:00:00') )]"/>

<filter name="上周" string="Last Week" domain="[('create_date','>', (context_today() - datetime.timedelta(weeks=1)).strftime('%Y-%m-%d 00:00:00'))]"/>

<!--另一种写法-->

<separator/>

<filter name="week" string="本周"

domain="[

'&',

('create_date', '>=', (context_today() + relativedelta(weeks=-1,days=1,weekday=0)).strftime('%Y-%m-%d')),

('create_date', '<=', (context_today() + relativedelta(weekday=6)).strftime('%Y-%m-%d')),

]"/>

<filter name="month" string="本月"

domain="[

'&',

('create_date', '>=', (context_today() + relativedelta(day=1)).strftime('%Y-%m-%d')),

('create_date', '<=', (context_today() + relativedelta(months=1, day=1, days=-1)).strftime('%Y-%m-%d')),

]"/>

Search View - Searchpanel

<searchpanel>
<field name="category_id" limit="20" select="one" icon="fa-list-ul" enable_counters="1" hierarchize="1"/>
<field name="tag_ids" limit="20" select="multi" icon="fa-tags" enable_counters="1"/>
</searchpanel>

一些html/css/js

  • 转圈icon:<i class="fa fa-spinner fa-spin"/>