排序

class M(models.Model):
_name = 'm'
_order = 'date_release desc, name'

重写内置方法

重写name_serach

@api.model
def _name_search(self, name='', args=None, operator='ilike',
limit=100, name_get_uid=None):
args = [] if args is None else args.copy()
if not(name == '' and operator == 'ilike'):
args += ['|', '|',
('name', operator, name),
('isbn', operator, name),
('author_ids.name', operator, name)
]
return super(LibraryBook, self)._name_search(
name=name, args=args, operator=operator,
limit=limit, name_get_uid=name_get_uid)

 

重写read_group

@api.model
def _get_average_cost(self):
grouped_result = self.read_group(
[('cost_price', "!=", False)], # Domain
['category_id', 'cost_price:avg'], # Fields to access
['category_id'] # group_by
)
return grouped_result

read_group()方法的内部使用SQL的group by及累加函数来获取数据。传递给read_group() 方法的最常用参数如下:

 

  • domain:用于为分组过滤记录。
  • fields:它传递你希望获取分组数据的字段名称。该参数的值可能如下:
  • 字段名:你可以向fields参数传递字段名,但如果使用这一选项,还应将该字段名同时传递给groupby参数,否则会产生错误
  • field_name:agg:你可以传递带有聚合函数的字段名。例如,在cost_price:avg中,avg是一个SQL聚合函数。PostgreSQL中的聚合函数请参见https://www.postgresql.org/docs/current/functions-aggregate.html
  • name:agg(field_name):它与前面一个相同,但使用这种语句,你可以给数据列一个别名,例如average_price:avg(cost_price)。
  • groupby:这个参数接收一个字段描述列表。记录将根据这些字段分组。对于date和datetime列,你可以传递groupby_function来根据不同的时长应用日期分组,如 date_release:month。这会根据月来应用分组。

 

read_group()还支持一些可选参数,如下:

 

  • offset:表示可以跳过可选记录数量
  • limit:表示可选的返回记录最大数量
  • orderby:如果传递了该选项,结果会根据给定字段进行排序
  • lazy:它接收布尔值,并且默认值为True。如果传递了True,结果仅通过第一个groupby进行分组,剩余的groupby会被放到__context键中。若为False,所有的groupby在一次调用中完成。

 

xml数据标色和隐藏

<!-- views.xml -->
<field name="arch" type="xml">
<tree string="Todo" decoration-danger="is_expired">
<field name="name"/>
<field name="deadline"/>
<field name="is_done"/>
<field name="is_expired" invisible="True"/>
</tree>
</field>

所有颜色

给action下拉选项中添加按钮

<record id="model_sale_contract_action_server" model="ir.actions.server">
<field name="name">批量复制</field>
<field name="type">ir.actions.server</field>
<field name="model_id" ref="model_sale_contract"/> <!--在哪里model中添加, .换成_ -->
<!--<field name="binding_model_id" ref="waste.model_sale_contract"/>-->
<field name="state">code</field>
<field name="code">
action = model.contract_copy(env.context.get("active_ids"))
</field>
</record>

search

compute字段没有store=True时, 数据库不保存字段,在其他地方domian的时候无效,这时候可以写个search

is_expired = fields.Boolean(compute='_compute_is_expired', search='_search_is_expired', string="是否过期")

def _search_is_expired(self, operator, value):
if operator != '=':
raise ValidationError('只接受“=”符号')
else:
if value == False:
return [('status', '!=', 'no_verified'), '|', ('record_year', '=', fields.date.today().year),
('validity_time', '>', fields.date.today())]
else:
return ['!', '&', ('status', '!=', 'no_verified'), '|', ('record_year', '=', fields.date.today().year),
('validity_time', '>', fields.date.today())]
# 以下domain好像直接取反就行,有待观察
# ['|',('status', '=', 'no_verified'), '&', ('record_year', '!=', fields.date.today().year),
# ('validity_time', '<=', fields.date.today())]

# 调用
(is_expired', '=', False)

sorted

records = self.sorted(key=lambda x: (-x.id, x.name))

序列

    @api.model
def create(self, vals):
vals['yhf'] = self.env['ir.sequence'].next_by_code('模块的_name')
return super(模块的类名,self).create(vals)
 <record id="xxx" model="ir.sequence">
<field name="name">name of this sequence</field>
<field name="code">zerone.book</field>
<field name="prefix">PPA%(year)s%(month)s%(day)s</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>

name 序列规则得名称,可自定义code 序列规则得编码,要求最好唯一,使用模块.表名来命确保唯一

prefix 序号编码的前缀

  • 年份:%(year)s
  • 月份:%(month)s
  • 日: %(day)s

padding 填充数据的位数

 

日期

短日期

fields.Date.today()

长日期

fields.Datetime.now()

时间与字符串转换

日期转字符串

fields.Date.to_string(date)
fields.Datetime.to_string(date)

字符串转日期

fields.Date.to_date(string)
fields.Datetime.to_date(string)

格式化日期

year = fields.Date.from_string(date).strftime('%Y')

时间运算

1、往后加8个小时

datetime.datetime.now() + timedelta(hours=8)
fields.datetime.today() + timedelta(hours=8)
• 1
• 2

2、往后加8天

datetime.datetime.now() + timedelta(days=8)
fields.date.today() + timedelta(days=8)

3、往后加8年

datetime.datetime.now() + relativedelta(years=8)
fields.date.today() + relativedelta(years=8)

4、往后追8月

datetime.datetime.now() + relativedelta(months=8)
fields.date.today() + relativedelta(months=8)

关于月时间的运算

1、取出当前月的第一天和最后一天

import calendar
import datetime
#current_time临时变量,取出当前时间
current_time = datetime.datetime.now()
#调用monthrange(年份,月份),返回一个元组,例如(2,30)
#第一个元素,表示此月第一天周几,周末到周六(0-6)
#第二个元素,表示此月一共有多少天
monthRange = calendar.monthrange(current_time.year, current_time.month)
#取出当前月的第一天
date_from = datetime.date(current_time.year,current_time.month,day=1)
#取出当前月的最后一天
date_to = datetime.date(current_time.year,current_time.month,day=monthRange[1])

打印文件保存

self.env["py3o.report"].create({"ir_actions_report_id": rec.template_id.report_id.id}).save_file(rec.id)

def save_file(self, res_ids):
model_instances = self.env[self.ir_actions_report_id.model].browse(res_ids)
reports_path = []
existing_reports_attachment = self.ir_actions_report_id._get_attachments(
res_ids
)
for model_instance in model_instances:
reports_path.append(
self._get_or_create_single_report(
model_instance, {}, existing_reports_attachment
)
)
result_path, filetype = self._merge_results(reports_path)
reports_path.append(result_path)
with open(result_path, "r+b") as fd:
res = fd.read()
self._cleanup_tempfiles(set(reports_path))
# base64.b64encode(res) 很重要!!!没有这个不是二进制文件
self.env['ir.attachment'].create({'name': f'{model_instances.name}.{filetype}', 'datas': base64.b64encode(res)}).id

修改context

# current context is {'key1': True}
r2 = records.with_context({}, key2=True)
# -> r2._context is {'key2': True}
r2 = records.with_context(key2=True)
# -> r2._context is {'key1': True, 'key2': True}

模型的继承

 

继承中用的最多的是经典继承和代理继承,原型继承则基本不会使用到

 

经典继承

声明方式:_inherit  =  'res.partner'

 

在模型类通过_inherit属性进行定义时,它向所继承模型添加了修改,而没有进行替换。意味着在继承类中中定义的字段会在父级模型中新增或修改。在数据库层,ORM对同一张数据表添加字段。ℹ️果该字段在父类中已存在,仅修改在继承类中声明的属性,其它的保持原有父类中的内容不变。

 

在继承类中定义的方法替换父类中的方法。如果你不通过super调用触发父级方法,那么父级版本的方法则不会被调用,我们也就不拥有该项功能。因此,当你通过继承在已有方法中添加新逻辑时,应包含一个带有super的语句来调用其父类中的方法。

 

ℹ️ 还应说明带有inherit的传统继承会将功能拷贝到新模型中,虽然效率并不高。

 

代理继承

 

代理继承的一般声明方式

声明方式:

_name  =  'library.member'

_inherits  =  {'res.partner':  'partner_id'}

ℹ️注意: 是_inherits属性(注意多一个 s),它的值是一个键值对字典,键是被继承的模型,而值是在模型中定义的Many2one字段名。

 

但是有一些情况下,我们不想修改已有模型,而是基于已有模型新建一个模型来使用其已有的功能。这借由Odoo的代理继承实现。

新模型创建新记录时,在原模型中也会被创建并使用many-to-one 字段关联。它仅是一个 Many2one关联,但代理机制注入了一些魔力来让他起来就好像属于新模型一样。看新模型可以看到所有原模型和新模型中的字段,但在后台两个模型分别处理各自的数据。

 

传统继承与面向对象编程的概念有很大不同。代理继承则与其相似,其中可创建一个新的模型来包含父级模型中的功能。它还支持多态继承,同时从两个或多个其它的模型中进行继承。

 

💎 何时使用代理继承?举例:向Partner模型添加的一些字段,如果这些字段其他Partner无需使用到。则使用代理继承比较好。

 

ℹ️需要注意代理继承仅用于字段,而不能用于方法。因此,如果原模型有一个do_something()方法,成员模型不会自动继承它。

 

💎 关于代理继承一个值得注意的用例是用户模型 res.users。它继承自成员(res.partner)。这表示其中在User中可见的一些字段实际存储Partner模型中(尤其是name字段)。在新用户创建时,我们还获取了一个新的自动创建的Partner。

 

代理继承的精简写法

 

使用在Many2one字段定义中使用delegate=True属性。这和_inherits完全一样。其主要优点是更为简洁。

举例:partner_id = fields.Many2one('res.partner',  ondelete='cascade',  delegate=True)

 

类似代理继承的可选方案

 

💎 代理继承可通过如下组合来进行替代:

父模型中的一个 many-to-one 字段

重载 create()方法自动创建并设置父级记录

父字段中希望暴露的特定字段的关联字段,有时这比完整的代理继承更为合适。例如res.company并没有继承res.partner,但使用到了其中好几个字段。

ℹ️ 但是,如果write也需要重载,最好还是用原来的代理继承。

 

原型继承(几乎不用)

 

通过添加一个在带有不同标识符的_name类属性来实现。以下是一个示例:

_inherit  =  'res.partner'

_name  =  'library.member'

 

新模型有其自己的数据表,包含完全独立于res.partner父模型的自身数据。因其仍继承Partner模型,此后的任意修改也会影响到新模型。原型继承在实践中鲜有使用,原因在于代理继承通常可以更高效的方式满足了这一需求,也无需复制数据结构。

实现init钩子

 

我们了解了如何通过XML或CSV文件添加、更新及删除记录。但有时,业务用例非常复杂,无法通过使用数据文件来进行解决。这些情况下,可以在声明文件中使用init钩子来执行所需要的操作。

 

按照如下步骤来添加post_init_hook:

 

  1. 在__manifest__.py文件中通过post_init_hook键来注册这个钩子:

'post_init_hook':  'add_book_hook'

  1. 在__init__.py文件中添加add_book_hook()方法:
def add_book_hook(cr,  registry):
env = api.Environment(cr, SUPERUSER_ID, {})
book_data1 = {'name': 'Book 1', 'date_release': fields.Date.today()}
book_data2 = {'name': 'Book 2', 'date_release': fields.Date.today()}
env['library.book'].create([book_data1, book_data2])

 

 

在第一步中,我们在声明文件文件中通过add_book_hook值注册了post_init_hook。这表示在模块安装之后,Odoo会在__init__.py中查找add_book_hook方法。如果找到,它会使用数据库游标和 registry调用该方法。

 

第2步中,我们声明了add_book_hook()方法,在模块安装后会被调用。我们通过该方法创建了两条记录。在实际情况中,可以在本息编写复杂的业务逻辑。

 

Odoo还支持另外两种钩子:

  • pre_init_hook:这个钩子会在开始安装模块时触发。它与post_init_hook正好相反,会在当前模块安装前触发。
  • uninstall_hook:这个钩子会在你卸载该模块时触发。它多用于模块需要垃圾回收机制时。

读写conf文件

import configparser
cf = configparser.RawConfigParser()
cf.read(r"D:\sah_pro\GOdoo13_SAN\bin\odoo.conf")
# 写配置文件,‘options’标签下,name=kong
cf.set('options', 'name', 'kong')
cf.write(open(r"D:\sah_pro\GOdoo13_SAN\bin\odoo.conf", "r+", encoding="utf-8")) # r+模式
cf.get('options', 'name')

‘r’只读。该文件必须已存在。

‘r+’可读可写。该文件必须已存在,写为追加在文件内容末尾。

‘rb’:表示以二进制方式读取文件。该文件必须已存在。

‘w’只写。打开即默认创建一个新文件,如果文件已存在,则覆盖写(即文件内原始数据会被新写入的数据清空覆盖)。

‘w+’写读。打开创建新文件并写入数据,如果文件已存在,则覆盖写。

‘wb’:表示以二进制写方式打开,只能写文件, 如果文件不存在,创建该文件;如果文件已存在,则覆盖写。

‘a’追加写。若打开的是已有文件则直接对已有文件操作,若打开文件不存在则创建新文件,只能执行写(追加在后面),不能读。

‘a+’追加读写。打开文件方式与写入方式和'a'一样,但是可以读。需注意的是你若刚用‘a+’打开一个文件,一般不能直接读取,因为此时光标已经是文件末尾,除非你把光标移动到初始位置或任意非末尾的位置。(可使用seek() 方法解决这个问题)

action

分组

  <field name="context" eval="{'group_by': 'create_uid'}"/>

过滤

<field name="domain">[('suapply_status', '=', '2')]</field>

context

<field name="context">{'is_contract': True}</field>

相同model不同action区分

<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('sample_reserve_manage_view_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('sample_reserve_manage_view_form')}),
]"/>

附件预览

http://localhost:8069/web/content/附件ID

获取selection字段的value

type= fields.Selection([('a', '吃'), ('b', '喝')], string='类型')
• 1

我们都知道self.type输出的是“a”或者“b”,但是在很多时候我们要获取到“吃”或者“喝”,比如在Report里面输出type的值,此时该怎么办呢?

type= dict(self.fields_get(allfields=['type'])['type']['selection'])[self.type]

form中添加记录追踪

效果

实现方法:tracking=True 或 tracking=1

 _inherit = ['mail.thread']
is_free_freight = fields.Boolean(string='是否免运费', tracking=True)
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread" options="{'post_refresh': 'recipients'}"/>
</div>

model中的方法

toggle_active

反转active

action_archive

active=False

action_unarchive

active=True

批量获取字典的数据

    data = {
'model': "aaa",
'fields': "bbb",
"ids": "ccc",
"domain": "ddd"
}
model, fields, ids, domain = operator.itemgetter('model', 'fields', 'ids', 'domain')(data)
print(model, fields, ids, domain)

结果:

o2m中写整个视图(有tree和form)

默认情况下,只会有自定义的tree, form是表中所有字段

解决方式

1,添加form_view_ref, 引用自定义的form(同理有tree_view_ref)

<field name="comment_ids" context="{'tree_view_ref': 'your_app.tree_view_xml_id', 'form_view_ref': 'your_app.form_view_xml_id'}"/>

2,嵌套视图

<field name="comment_ids">
<tree>
<field name = 'comment_id'/>
<field name = 'comment'/>
</tree>
<form>
<div class="form-group">
<label name="comment">Comment:</label>
<textarea class="form-control" rows="5" />
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</field>

xml中context添加默认值

数据量大查询太慢

解决办法:

  • 如果有active, 要在active上加上索引(index=True)
  • 常用作查询的字段加上index=True,

原因:表中有active:search_read()的时候走web_search_read(), web_search_read中有search_count()方法调用active=True,导致查询过慢

 

注册env

db_registry = registry(db)
with api.Environment.manage(), db_registry.cursor() as cr:
env = api.Environment(cr, SUPERUSER_ID, {})

页面资源404

 

页面变为空白,以及刷新,重启等方式都无法解决,是由于静态css文件是存储在数据库之外的,数据库备份 静态文件和css文件不会一起备份,所以 当还原数据库后,页面出现空白是由于静态文件路径是指定的,但是本地又没有,找不到导致的

 

解决方式:

对还原下来的数据库执行一下操作

DELETE FROM ir_attachment WHERE url LIKE '/web/content/%';

重启后,静态文件会重新产生

 

自定义筛选去掉部分字段

posted on 2021-04-16 11:07  cnblogs_admin_m001  阅读(417)  评论(0编辑  收藏  举报