odoo实现跨库读写
本文不是更换框架的数据源,只是通过代码的方式简单实现。本次实验使用MySQL数据库。
首先,你需要下载一个库: pymysql
pip install pymysql
接下来做个简单的分析,不想看的可以直接拉到最后,我提供了完整的代码,可以直接下载参考,本文使用的odoo13,其它版本举一反三。
你们可能不知道,odoo的 model有个布尔值属性 _auto,默认是 True,作用就是,安装model的时候默认在 postgresql 创建一张对应的表,我们不使用postgresql, 所以第一步就是将它设成 False。
然后看看常规的模块是怎么加载数据的:
而通过浏览器调试可以看到,加载model时会调用 search_read(),所以,可以确定它需要重写, 而通过阅读odoo/models.py源码的search_read(),还发现了它还会调用 search():
那再来看看 search():
它返回的是什么?我尝试在返回之前加个 print(), 输出它将要返回的结果:
可以看到,其实 search()最终返回的是一个装着字典数据的列表。
那OK,回到read_search(),它调用了search()之后,最终返回的是什么?我们在返回之前加个print看看:
好吧,还是一个跟前面一样的数据集:
那么,相关的基本上就分析得差不多啦,还有,form视图的读取需要调用(要实现主题我们也需要重写) read()方法哦,这里就不再做详细分析了,坑我给你们填,直接上代码吧。
先看看我的模块结构:
models.py , 已经重写完需要重写的方法,如下:
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from .connection import Connection as connection
from ..tools import mysql_tools
CLIENT, CONN, CURSOR = mysql_tools.MySQLClient(), None, None
class MySQL(models.Model):
_name = 'mysql.operation'
_description = '跨库MySQL'
_auto = False # 禁止自动创建psql数据库
_db_name = 'demo' # mysql数据库名
_tb_name = 'users' # 此model对应MySQL的表名, 务必定义此属性
name = fields.Char(string='昵称')
age = fields.Integer(string='年龄')
gender = fields.Selection([
('man', '男'),
('women', '女'),
], string='性别')
def init(self):
global CLIENT, CONN, CURSOR
CLIENT, CONN, CURSOR = connection.get_client_and_cursor(self._db_name)
# form视图加载主要是这个方法
def read(self, fields=None, load='_classic_read'):
result_record = []
# 新建的时候没有id
if not self.id:
latest_id = CLIENT.get_new_id(CONN, CURSOR, self._tb_name)
data = CLIENT.get_data_by_id(CURSOR, self._tb_name, latest_id)
else:
data = CLIENT.get_data_by_id(CURSOR, self._tb_name, self.id)
result = {
'id': data[0][0],
'name': data[0][1],
'age': data[0][2],
'gender': data[0][3],
}
result_record.append(result)
return result_record
def search(self, args, offset=0, limit=None, order=None, count=False):
datas = CLIENT.get_all_record(CURSOR, self._tb_name)
mysql_objs = []
for i in range(len(datas)):
data = {
'id': datas[i][0],
'name': datas[i][1],
'age': datas[i][2],
'gender': datas[i][3],
}
mysql_objs.append(data)
print(mysql_objs)
return mysql_objs
# tree视图加载时主要是这个,form也会用到
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
records = self.search(domain or [], offset=offset, limit=limit, order=order)
if not records:
return []
return records
@api.model
def create(self, values):
sql = """
INSERT INTO {} (name,age,gender) VALUES('{}',{},'{}')
""".format(self._tb_name, values['name'], values['age'], values['gender'])
CLIENT.insert(CONN, CURSOR, sql)
return self
def unlink(self):
CLIENT.delete_by_id(CONN, CURSOR, self._tb_name, self.id)
return True
def write(self, values):
result_list = []
for k, v in values.items():
result_list.append(k)
result_list.append(v)
sql = """
UPDATE {} SET
""".format(self._tb_name) + ((",{}" + "=" + "'{}'") * len(values)).format(
*result_list) + " WHERE id = {}".format(
self.id)
sql = sql.replace(',', '', 1) # 删除第一个逗号,拼成合法的SQL语句
print(sql)
CLIENT.eitd_by_id(CONN, CURSOR, sql)
return True
然后是配置数据库的:
# -*- coding: utf-8 -*-
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
host = fields.Char(string='主机', config_parameter='host')
user = fields.Char(string='用户', config_parameter='user')
password = fields.Char(string='密码', config_parameter='password')
这里继承了基础的设置,因为不使用官方指定的库,要在页面上设置只能通过这个方法实现。
然后是设置页面的视图:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.db_config</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="103"/>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@data-key='general_settings']" position="inside">
<h2>MySQL数据库设置</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane" style="width:250px;">
主机:<field name="host"/>
用户:<field name="user"/>
密码:<field name="password"/>
</div>
<div class="o_setting_right_pane">
<div class="text-muted">
请填写相关信息
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>
别忘了,在__manifest__.py里面 : ‘depends’: [‘base’, ‘base_setup’], 不然会报错哦,设置依赖 base_setup。
接着是 connection.py , 获取连接的:
from odoo import exceptions
from ..tools.mysql_tools import MySQLClient
from odoo.http import request
class Connection(object):
@staticmethod
def get_db_msg(db_name):
'''
返回连接信息
:param db_name:
:return:
'''
host = request.env['ir.config_parameter'].sudo().get_param('host')
user = request.env['ir.config_parameter'].sudo().get_param('user')
password = request.env['ir.config_parameter'].sudo().get_param('password')
if not host or not user or not password:
raise exceptions.UserError('请配置正确的数据库连接信息!')
db_name = db_name
return host, user, password, db_name
@staticmethod
def get_client_and_cursor(db_name):
'''
获取数据库客户端对象、光标对象
:return:
'''
host, user, password, db_name = Connection().get_db_msg(db_name)
client = MySQLClient()
conn, cursor = client.connect_and_cursor(host, user, password, db_name)
return client, conn, cursor
tools包下的 mysql_tools.py:
import pymysql
class MySQLClient(object):
def connect_and_cursor(self, host, user, password, database, charset='utf8'):
'''
获取本类对象以及光标对象(可执行一系列的SQL操作对象)
:param host:
:param user:
:param password:
:param database:
:param charset:
:return:
'''
conn = pymysql.connect(
host=host,
user=user,
password=password,
database=database,
charset=charset
)
cursor = conn.cursor()
return conn, cursor
def get_all_record(self, cursor, tb_name):
"""
获取全部记录
:param db_name: 查询的数据库名
:return: 对应数据库的全部记录
"""
sql = '''
SELECT * FROM {}
'''.format(tb_name)
if type(cursor) == 'NoneType':
return
cursor.execute(sql)
result = cursor.fetchall()
return result
def get_data_by_id(self, cursor, tb_name, id):
"""
根据id查询记录
:param id:
:return:
"""
sql = '''
SELECT * FROM {} WHERE id = {}
'''.format(tb_name, id)
cursor.execute(sql)
return cursor.fetchall()
def insert(self, conn, cursor, sql):
'''
插入数据
:param client: 连接对象
:param cursor: 光标对象
:param sql: SQL语句
:return:
'''
cursor.execute(sql)
conn.commit()
def delete_by_id(self, conn, cursor, tb_name, id):
'''
根据id删除
:param client:
:param cursor:
:param tb_name:
:param id:
:return:
'''
sql = '''
DELETE FROM {} WHERE id = {}
'''.format(tb_name, id)
cursor.execute(sql)
conn.commit()
def eitd_by_id(self, conn, cursor, sql):
'''
根据ID修改
:param client:
:param cursor:
:param tb_name:
:param id:
:return:
'''
cursor.execute(sql)
conn.commit()
def get_new_id(self, conn, cursor, tb_name):
'''
返回最新的id
:param conn:
:param cursor:
:param tb_name:
:return:
'''
sql = """
SELECT MAX(id) FROM {}
""".format(tb_name)
cursor.execute(sql)
data = cursor.fetchall()
return data[0][0]
model视图:
<odoo>
<data>
<record model="ir.ui.view" id="mysql_employees_view_tree">
<field name="name">mysql.operation.tree</field>
<field name="model">mysql.operation</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="age"/>
<field name="gender"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="mysql_employees_view_form">
<field name="name">mysql.operation.form</field>
<field name="model">mysql.operation</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="age"/>
<field name="gender"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="mysql_employees_action_window">
<field name="name">员工</field>
<field name="res_model">mysql.operation</field>
<field name="view_mode">tree,form,kanban</field>
</record>
<!-- Top menu item -->
<menuitem name="Mysql" id="mysql_l1_menu_root" web_icon="pdc_mysql,static/description/icon.png"/>
<menuitem name="用户" id="mysql_l2_menu_employees" parent="mysql_l1_menu_root"
action="mysql_employees_action_window"/>
</data>
</odoo>
安全配置:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mysql_operation,mysql.operation,model_mysql_operation,,1,1,1,1
安装好之后在浏览器上web之后加上参数 ?debug=1打开开发者模式,点击设置,可以看到这里,在这里设置数据库信息后点击保存:
至此,已经实现跨库CURD啦。
代码没有做过多、更优雅的封装,主要是实现的过程。
完整代码:
网盘链接
提取码:68e1
有不对的地方或者疑问小伙伴们及时提出哈。