Category: Odoo

ir_attachment: IOError: [Errno 2] No such file or directory

恢复Odoo数据抛出错误导致无法进入页面

2018-06-14 08:26:09,705 30127 INFO yszs odoo.addons.base.ir.ir_attachment: _read_file reading /var/lib/odoo/filestore/yszs/98/9809d6d03b894abb232008e691e68e8d34a2fb71
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/odoo/addons/base/ir/ir_attachment.py", line 100, in _file_read
r = open(full_path,'rb').read().encode('base64')
IOError: [Errno 2] No such file or directory: u'/var/lib/odoo/filestore/yszs/98/9809d6d03b894abb232008e691e68e8d34a2fb71'

解决方法:

select *
from ir_attachment
where url LIKE '/web/content/%';

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

重启Odoo服务

docker 快速部署Odoo环境

写在前面

经常需要将开发的项目部署到测试服务器上以便客户可以看到,但之前都是安装环境这样的方式,不适合快速搭建。

安装Docker及docker-compose

yum install -y docker docker-compose

systemctl enable docker # 开机自动启动docker
systemctl start docker # 启动docker
systemctl restart docker # 重启dokcer
systemctl stop docker

# 验证docker是否安装成功
docker run --rm hello-world

设置Docker 加速

阿里云:https://cr.console.aliyun.com/#/accelerator

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://wkumpv63.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

使用docker-compose.yml运行容器

以下配置包含了odoo10、odoo11、postgresql 10.4

目录结构:

配置文件(该文件存放在对象的config下面,名称为 odoo.conf)

[options]
addons_path = /mnt/extra-addons

启动命令

docker-compose up -d
docker-compose stop
# 停止某一个容器
docker ps -a # 查看当前所有容器
docker stop <CONTAINER ID or NAMES>
docker start <CONTAINER ID or NAMES>
docker restart <CONTAINER ID or NAMES>

docker-compose.yml 文件内容如下

version: '2'
services:
  db:
    image: postgres:latest
    restart: always
    environment:
      - POSTGRES_USER=odoo
      - POSTGRES_PASSWORD=odoo

  odoo11:
    image: odoo:11.0
    environment:
      - HOST=db
      - PORT=5432
      - USER=odoo
      - PASSWORD=odoo
    depends_on:
      - db
    ports:
      - "8011:8069"
    tty: true
    volumes:
      - ./addons/11:/mnt/extra-addons
      - ./confg/11:/etc/odoo

  odoo10:
    image: odoo:10.0
    environment:
      - HOST=db
      - PORT=5432
      - USER=odoo
      - PASSWORD=odoo
    depends_on:
      - db
    ports:
      - "8010:8069"
    tty: true
    command: -- --dev=reload
    volumes:
      - ./addons/10:/mnt/extra-addons
      - ./addons/10/enterprise10.0:/mnt/extra-addons
      - ./confg/10:/etc/odoo
volumes:
  odoo11:
  odoo10:

PostgreSQL 管理

docker仓库:https://hub.docker.com/_/postgres/

docker exec -tiu postgres <postgres_container_name> psql
docker exec -ti <postgres_container_name> psql -U <postgres_user>
# 推荐使用如下方式,它不会干扰主容器
docker run -it --link <postgres_container_name>:postgres --rm postgres sh -c 'exec psql -h "$POSTGRES_PORT_5432_TCP_ADDR" -p "$POSTGRES_PORT_5432_TCP_PORT" -U <postgres_user>'

备份\删除数据库\恢复

# To backup
docker exec -u <your_postgres_user> <postgres_container_name> pg_dump -Fc <database_name_here> > db.dump

# To drop db
docker exec -u <your_postgres_user> <postgres_container_name> psql -c 'DROP DATABASE <your_db_name>'

# To restore
docker exec -i -u <your_postgres_user> <postgres_container_name> pg_restore -C -d postgres < db.dump

Odoo 容器参数说明

详细说明见docker-compose.yml文件volumes部分

服务器的默认配置文件位于(/etc/odoo/openerp-server.conf),若你有一个自定义的配置文件位于(/path/to/config/openerp-server.conf),那么你的运行命令就该是如下这样。

docker run -v /path/to/config:/etc/odoo -p 8069:8069 --name odoo --link db:db -t odoo

当然,你也可以直接指定内联的Odoo参数,这些参数必须在前面加上 --,如下所示:

docker run -p 8069:8069 --name odoo --link db:db -t odoo --db-filter=odoo_db_.*

当然,运行Odoo,大部分时候是需要运行我们自己开发的模块,在docker中运行的时候,我们可以使用如下方式将其加载。

docker run -v /path/to/addons:/mnt/extra-addons -p 8069:8069 --name odoo --link db:db -t odoo

升级docker版本

docker run --volumes-from old-odoo -p 8070:8069 --name new-odoo --link db:db -t odoo

防火墙设置(不需要设置)

firewall-cmd --zone=public --add-port=8069/tcp --permanent
firewall-cmd --reload

Docker 维护命令

# 查看容器信息
docker info

# 查看镜像列表
docker images

# 启动、停止
docker stop <id/container_name>
docker start <id/container_name>
docker kill <id/container_name>

# 查看正在运行的容器
docker ps -a

# 查看容器日志
docker logs <id/container_name>

# 实时查看日志输出
docker logs -f <id/container_name> (类似 tail -f) (带上时间戳-t)

# 启动容器以后台方式运行(更通用的方式)
docker run -d -it image_name

# 显示一个运行的容器里面的进程信息
docker top Name/ID  

# 移除镜像
docker stop <id/container_name>
docker rm  <id/container_name> 

# 制作镜像(根据容器id来创建新的镜像
docker commit 93639a83a38e  wsl/javaweb:0.1

Odoo Logging changes(消息通知机制)

最近在开发一个项目管理系统,客户希望某些字段在改变的时候记录下这个字段的改变信息。这时候就可以使用Odoo的消息通知机制。

官方文档:https://www.odoo.com/documentation/10.0/reference/mixins.html#logging-changes

一、引入消息模型

class Modle(models.Model):
    _name = "demo"
    _inherit = ['mail.thread']

在form中增加消息显示代码

<form>
   ....
   省略一串代码
   ....
    <div class="oe_chatter">
        <field name="message_follower_ids"
               widget="mail_followers"/>
        <field name="message_ids" widget="mail_thread"/>
    </div>
</form>

二、定义追踪属性

在模块引入了消息机制模型后,track_visibility属性即可使用,它支持如下两种写法。

  1. track_visibility='always'
  2. track_visibility='onchange'

定义字段改变显示的内容

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data noupdate="True">
		<!-- mail-->
		<record id="mt_state_change" model="mail.message.subtype">
			<field name="name">confirmed</field>
			<field name="res_model">demo</field>
			<field name="default" eval="True"/>
			<field name="description">项目状态变更为正式立项!</field>
		</record>
    </data>
</odoo>
class Modle(models.Model):
    _name = "demo"
    _inherit = ['mail.thread']

    state = fields.Selection(selection=[('draft', 'Draft'), ('confirmed', 'Confirmed'), ], default='draft', track_visibility='onchange')
    name = fields.Char(string="IP name", required=True, track_visibility='always')

    # 重写该方法,在合适的条件下调用前面声明的消息内容
	def _track_subtype(self, init_values):
	self.ensure_one()
	if 'state' in init_values and self.state == 'confirmed':
		return 'project_manager.mt_state_change'
	return super(Modle, self)._track_subtype(init_values)

三、效果

Odoo 引入CSS及JavaScript

在module/view下创建一个新的XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
        <template id="assets_backend" name="web_kanban assets" inherit_id="web.assets_backend">
            <xpath expr="//script[last()]" position="inside">
                <link rel="stylesheet" href="/web_kanban/static/src/less/kanban_dashboard.less"/>
                <link rel="stylesheet" href="/web_kanban/static/src/less/kanban_view.less"/>

                <script type="text/javascript" src="/web_kanban/static/src/js/kanban_record.js"></script>
                <script type="text/javascript" src="/web_kanban/static/src/js/kanban_relational.js"></script>
                <script type="text/javascript" src="/web_kanban/static/src/js/kanban_quick_create.js"></script>
                <script type="text/javascript" src="/web_kanban/static/src/js/kanban_column.js"></script>
                <script type="text/javascript" src="/web_kanban/static/src/js/kanban_view.js"></script>
                <script type="text/javascript" src="/web_kanban/static/src/js/kanban_widgets.js"></script>
                <script type="text/javascript" src="/web_kanban/static/src/js/compatibility.js"></script>
            </xpath>
        </template>
</odoo>

引入XML至__manifest__.py文件

{
    'data':['views/resources.xml']
}

方法二

直接在__manifest__.py引入

'css': ['static/src/css/sale.css'],

Odoo 常用技巧

View

隐藏Field

<field name="currency_id" invisible="True"/>
<field name="currency_id" invisible="1"/>

在某种条件下隐藏

<field name="expense_description" attrs="{'invisible':[('expense_audit','!=','1')]}" />

隐藏label

<field name="description" widget="html" nolabel="True"/>
<field name="description" widget="html" nolabel="1"/>

只读 readonly

<field name="budget_id" readonly="True"/>

条件 domain

<field name="product_id" domain="[('pro_type','=','rests')]"/>

设定值 eval

<field name="fill_date" eval="datetime.now()" readonly="True"/>

表单传值 context (default_ 开始代表直接赋值过去)

<button class="oe_stat_button" name="%(budget_review_action)d" type="action" icon="fa-calendar-check-o" attrs="{'invisible':[('state','!=','check')]}" context="{'default_budget_id': id, 'default_contract_area': square, 'default_contract_price': total_price, 'default_start_date': start_date, 'default_end_date': end_date}" string="创建审核单"/>a

Widget

many2one widget (default)

  •  no_quick_create - remove the Create and edit... option.
  •  no_create_edit - remove the Create "search_value" option.
  •  no_create - no_quick_create and no_create_edit combined.
  •  no_open - in read mode: do not render as a link.
<field name="field_name" options="{'no_quick_create': True, 'no_create_edit' : True}"/>

many2many widget (default)

  • no_create - remove the Create button.
<field name="field_name" options="{'no_create': True}"/>

many2many_tags widget

  • no_quick_create - remove the Create and edit... option.
  • no_create_edit - remove the Create "search_value" option.
  • no_create - no_quick_create and no_create_edit together.
<field name="field_name" widget="many2many_tags" options="{'no_create_edit': True}"/>

one2many tree

  • create - remove the Create button.
  • edit - remove the Edit button.
  • delete - remove the Delete button.
<field name="basic_incidentals" mode="tree" nolabel="1">
   <tree create="false" edit="false" delete="false">
      <field name="name"/>
      <field name="model"/>
      <field name="specifications"/>
      <field name="price" invisible="True"/>
      <field name="number" invisible="True"/>
      <field name="unit" invisible="True"/>
      <field name="total_price" invisible="True"/>
      <field name="remarks" invisible="True"/>
   </tree>
</field>

Fields

生成一个动态的Selection,比如当前时间的前后5年!

import datetime
year = fields.Selection(string=u'年度', selection=[(num, str(num)) for num in range((datetime.datetime.now().year - 5), (datetime.datetime.now().year + 5))])