1.创建模块命令
示例:D:\env\python\python.exe D:\odoo-14\server\odoo-bin scafflod module_name D:\odoo- 14\server\addons
2.后端返回视图
写法1: 找视图id,返回动作窗口字典,方法与视图示例如下:
def open_tree_or_form(self):
res_ids = self.env['qc.dmr'].sudo().search([('name', '=', '203382')])
tree_view_id = self.env.ref('xunde_quality_contorl.qc_dmr_tree_view')
form_view_id = self.env.ref('xunde_quality_control.qc_dmr_form_view')
if len(res_ids) == 1:
return {
'name' _(质检),
'view_mode': 'form',
'res_model': 'qc.dmr',
'views': [[form_view_id.id, 'form']],
'type': 'ir.actions.act_window',
'res_id': dmr_ids.id,
'target': 'current',
'context': {}
}
elif len(res_ids) > 1:
return {
'name' _(质检),
'view_mode': 'tree',
'res_model': 'qc.dmr',
'views': [(tree_view_id.id, 'tree'), (form_view_id.id, 'form')],
'type': 'ir.actions.act_window',
'domain': [('id', 'in', res_ids.ids)],
'target': 'current',
'context': {}
}
else:
return {'type': 'ir.actions.act_window_close'}
<!-- 以button方式触发方法1 -->
<header>
<button string="打开视图" name="open_tree_or_form" type="object" class='btn-primary'
groups="xunde_account.group_apply_sale"/>
</header>
<!-- 以button方式触发方法2 -->
<sheet>
<div class="oe_button_box" name="button_box">
<button type="object" name="open_tree_or_form" class="oe_stat_button" icon="fa-
pencil-square-o">
<field name="invoice_count" widget="statinfo" string="Paypal发票"/>
</button>
</div>
</sheet>
<...>
写法2:找特定action的id,并在方法里返回,示例代码如下:
def open_tree_or_form(self):
...
action = self.env['ir.actions.act_window']._for_xml_id(f"account.{action.name}")
action['context'] = {}
action['domain'] = [(...)]
action['name'] = _("...")
action['views'] = [[False, 'form']]
...
return action
3. 瞬态视图
用途:可以用于审批驳回功能,可以用于导入文件向导, 一般是在视图添加按钮,通过按钮打开瞬态视图,瞬态视图模型,一般有个default_get函数和关联主表的字段,示例代码如下:
<!-- 打开瞬态视图按钮 -->
<header>
<button name='%(action_open_reject_form)d' type='action' string='驳回' class='btn-
warning' />
</header>
<!-- 瞬态模型视图 -->
<record>
...
<footer>
<button name='action_reject' type='object' class='btn-primary' />
<button special='cancel' string='关闭' class='btn-primary' />
</footer>
</record>
# 瞬态模型示例
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class TransientModelExample(models.TransientModel):
_name = 'transientmodel.example'
_description = '瞬态模型示例'
def default_get(self, fields_list):
....
origin_id = fields.Many2one(...)
def action_reject(self):
...
4.视图继承
利用xpath语法,对原有视图进行修改,不建议过多继承视图,因为会导致视图继承混乱,后期不好维护。
<!-- xpath语法示例 -->
<xpath expr='//field[@name='field_name']' position='after'>
<field ... />
</path>
<xpath expr='...' position='attributes'>
<attribute name='invisible'>1</attribute>
</path>
<xpath expr='//notebook/page[1]' position='before'>
<page .../>
</path>
5.搜索设置默认分组
需求:打开tree视图,就有默认分组,修改搜索视图和客户端动作的上下文,示例代码如下:
<!-- 搜索视图 -->
<record id='search_view_template' model='ir.ui.view'>
<field name='' />
<filter string='' name='' domain='' />
<separator/>
<group expand='0' string='group by'>
<filter string='客户' name='by_partner_id' domain='[]'
context='{'group_by': 'name'}' />
</group>
<searchpanel>
<field name='' select='multi' string='fa-cutlery' icon='' color='#875A7B'
enable_counters='1' />
</searchpanel>
</record>
<!-- 动作 -->
<record id='action_template' model='ir.actions.act_window'>
<field name='name'>...</field>
<field name='model'>...</field>
<field name='view_mode'>tree,form</field>
<field name='view_ids' eval='[(5,0,0),
(0,0,{'view_mode': 'tree', 'view_id': ref('tree_id')}),
(0,0,{'view_mode': 'form', 'view_id': ref('form_id')})] />
<field name='search_view_id' ref='search_id' />
<field name='context'>{'search_default_by_parent_id':1}</field>
</record>
6. 字段与按钮在一行
实现字段与按钮在一行,代码如下:
<labe for='field_name' />
<div class='o_row'>
<field ... />
<button ... />
</div>
7._fields_view_get函数
该函数是视图加载时会自动调用的函数,使用场景示例:根据权限组动态控制字段的属性,包括可见性、只读性等,示例代码如下:
@api.model
def _fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
result = super(XdBusinessExample,self)._fields_view_get(view_id=view_id,
view_type=view_type,
toolbar=toolbar,
submenu=submenu)
if view_type == 'form':
if self.env.user.has_group('xd.business.purchase_group'):
doc = etree.XML(result.get('arch', ''))
page_purchase = doc.xpath('page[@name='purchase']')
for i in page_purchase:
i.set('invisible', '1')
result['arch'] = etree.tostring(doc)
return result
8. binary转附件
需求:将明细中的bianry文件全部转化为附件存储,示例代码如下:
def binary_to_attachments(self):
documemnts = self.env['related.documents'].search([('assmenbly_id', '=',
self.assembly_id.id)])
files = []
for doc in documents:
attachment = self.env['ir.attacnment'].create({'datas': doc.file, 'name':
doc.filename})
files.appned((4, attachment.id))
self.attachment_ids = files
9. search_read函数
search_read函数是再打开tree、看板视图时自动触发的函数,可以重写该方法,以控制要显示的记录,示例代码如下:
@api.model
def search_read(self, domian=None, fields=None, offset=0, limit=80, order=None):
sql = ''' select row_number() over() as id,
model_name,
user_id,
count(*) as wait_approval_count,
string_agg(res_id || '', ',') as res_ids,
state
from wait_approval wher user_id = %s and state = '%s'
group by model_name, user_id, state''' %(self.env.user.id, state)
self.env.cr.excute(sql)
records = self.env.cr.fetchall()
vals = []
for r in records:
vals.append({'model_name': r[1], 'user_id': r[2]})
res.create(vals)
domain = [('id', 'in', res.ids)]
return super(XdSearchExample, self).search_read(domain=domain, fields=fields,
offset=offset, limit=limit,
order=order)
10. 邮件通知
odoo框架自带的邮件通知模块mail.message ,示例代码如下:
def mail_message_example(self):
...
user_id = self.env['res.users'].search([('name', '=', 'xxx')], limit=1)
self.env['mail.message'].create({
'subject': '通知:xxx',
'model': xxx,
'res_id': self.id,
'record_name': xxx,
'body': u'有单据%s需要您审批' % sheet_name,
'partner_ids': [(6, 0, usre_id.partner_id.id)], # 收件人
'notification_ids': [(5, 0, 0), (0, 0, {'res_partner_id': user_id.partner_id.id,
'notification_type': 'inbox'})],
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_commet'),
'message_type': 'notification',
'author_id': self.env.user.partner_id.id,
'replay_to': False,
'email_from': False,
})
...
11. kanban
kanban写法示例如下:
<?xml version="1.0"?>
<kanban default_group_by="stage_id" class="o_kanban_small_column o_opportunity_kanban"
on_create="quick_create" quick_create_view="crm.quick_create_opportunity_form"
archivable="false" sample="1">
<field name="stage_id" options="{"group_by_tooltip":
{"requirements": "Description"}}"/>
<field name="color"/>
<field name="priority"/>
<field name="expected_revenue"/>
<field name="kanban_state"/>
<field name="activity_date_deadline"/>
<field name="user_email"/>
<field name="user_id"/>
<field name="partner_id"/>
<field name="activity_summary"/>
<field name="active"/>
<field name="company_currency"/>
<field name="activity_state"/>
<field name="activity_ids"/>
<progressbar field="activity_state" colors="{"planned":
"success", "today": "warning",
"overdue": "danger"}"
sum_field="expected_revenue" help="This bar allows to filter the
opportunities based on scheduled activities."/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="#{!selection_mode ?
kanban_color(record.color.raw_value) : ''}
oe_kanban_global_click">
<div class="o_dropdown_kanban dropdown">
<a class="dropdown-toggle o-no-caret btn" role="button" data-
toggle="dropdown" data-display="static" href="#" aria-
label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu">
<t t-if="widget.editable"><a role="menuitem" type="edit"
class="dropdown-item">Edit</a></t>
<t t-if="widget.deletable"><a role="menuitem"
type="delete" class="dropdown-item">Delete</a></t>
<ul class="oe_kanban_colorpicker" data-field="color"/>
</div>
</div>
<div class="oe_kanban_content">
<div class="o_kanban_record_title">
<strong><field name="name"/></strong>
</div>
<div class="o_kanban_record_subtitle">
<t t-if="record.expected_revenue.raw_value">
<field name="expected_revenue" widget="monetary"
options="{'currency_field':
'company_currency'}"/>
<span t-if="record.recurring_revenue and
record.recurring_revenue.raw_value"> +
</span>
</t>
<t t-if="record.recurring_revenue and
record.recurring_revenue.raw_value">
<field name="recurring_revenue"
widget="monetary" options="
{'currency_field':
'company_currency'}"/>
<field name="recurring_plan"/>
</t>
</div>
<div>
<span t-if="record.partner_id.value" t-
esc="record.partner_id.value"/>
</div>
<div>
<field name="tag_ids" widget="many2many_tags" options="
{'color_field': 'color'}"/>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<field name="priority" widget="priority"
groups="base.group_user"/>
<field name="activity_ids"
widget="kanban_activity"/>
</div>
<div class="oe_kanban_bottom_right">
<field name="user_id"
widget="many2one_avatar_user"/>
</div>
</div>
</div>
<div class="oe_clear"/>
</div>
</t>
</templates>
</kanban>
12. 自动生成序列号
利用ir.sequence模型实现,示例代码如下:
...
name = fields.Char(string='单号', default='New')
...
@api.model
def create(self, vals):
if vals.get('name', 'New') == 'New':
vals['name'] = self.env['ir.sequence'].next_by_code('customer.complaint.sheet') or
'/'
return super(XdSequenceExapmle, self).create(vals)
<record id='xxx' model='ir.sequence'>
<field name='name'>xxx</field>
<field name='code'>customer.complaint.sheet</field>
<field name='prefix'>CCS%(year)s%(month)s%(day)s</field>
<field name='padding'>3</field>
<field name='company_id' eval='False' />
</record>
13. 计算字段可搜索
通常情况下,计算字段如果不存储在数据库中,是无法搜索的,但是可以指定字段的search属性,实现可搜索,示例代码如下;
...
saleman = fields.Many2one('res.users', compute='xxx', string='业务员',
search='_search_saleman_example)
...
def xxx(self):
todo
...
def _search_saleman_example(self, operator, value):
ids = []
mrps = self.env['mrp'].search([])
users = self.env['res.users'].search([('name', '=', value)]
for r in mrps:
if r.saleman.id in users.ids:
ids.append(r.id)
if ids:
return [('id', 'in', tuple(ids))]
return [('id', '=', '0')]
14.线程与线程池
在导入导出大批量数据时,使用多线程模型,会明显加快速度,使用示例代码如下:
线程:
...
max_connections = 10
pool_sema = threading.BoundedSemaphore(max_connections)
for i in range(4):
pool_sema.acquire()
thread = threading.Thread(target=self.supplier_process, args=(arg1, arg2...))
thread.daemon = True
thread.start()
thread.join()
...
def supplier_process(self,args,**kwargs):
pass
线程池:
...
max_connections = 10
pool_sema = threading.BoundedSemaphore(max_connections)
with ThreadPoolExecutor(max_workers=5) as pool:
for i in range(4):
pool_sema.acquire()
thread = pool.submit(self.supplier_process, (pool_sema ,b), kwargs={'id': 1})
result = thread.result()
...
def supplier_process(self, *args, **kwargs):
...
pool_sema = arg[0][0]
...
pool_sema.release()
15.Html链接
可以做一个订单的外部链接,示例代码如下:
...
order_link = fields.Html(string='订单链接')
...
@api.onchange('xxx)
def _compute_order_link(self):
self.ensure_one()
href = '/mail/view?model=%s&res_id=%s' %('sale.order', rec.order_id.id)
rec.order_link = '<a href="%s" target="_blank">%s</a>' %(href, rec.order_id.name)
16. 接口设计
odoo可以提供对外的接口,以实现与其它系统的互联互通,示例代码如下:
class Main(http.Controller):
@http.route('/xunde_bids/update/<db>', type='json', methods=['POST'], auth="none",
csrf=False)
def update_bids(self, db=None, **post):
if not db and request.session.db and http.db_filter([request.session.db]):
db = request.session.db
if not db:
db = db_monodb(request.httprequest)
if not db:
raise exceptions.UserError(_('d参数不能为空'))
request.session.db = db
result = False
with registry(db).cursor() as cr:
env = Environment(cr, SUPERUSER_ID, {})
payload = json.loads(request.httprequest.data) or {}
type = payload.get('type', [])
if type:
raise exceptions.ValidationError(_('api失效!'))
data = payload.get('data', [])
result = env['purchase.requisition.line']._update_data(data)
return {'success': result} # result为True or False