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()方法哦,这里就不再做详细分析了,坑我给你们填,直接上代码吧。
先看看我的模块结构:
先写一个公共的model public.py,目的是封装好能够通用(增、删、改)的代码:
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from ..tools.connection import mysql_client as client
class Public(models.Model):
_name = 'mysql.public'
_description = '其它model需要继承此类'
_auto = False
@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 name_search(self, name='', args=None, operator='ilike', limit=100):
return
@api.model
def create(self, values):
key_list = []
val_list = []
for k, v in values.items():
key_list.append(k)
val_list.append(v)
str1 = """
INSERT INTO {} (
""".format(self._tb_name) + (",{}" * len(values)).format(*key_list) + ") VALUES ("
str2 = (",'{}'" * len(values)).format(*val_list) + ")"
str1 = str1.replace(',', '', 1)
str2 = str2.replace(',', '', 1)
sql = str1 + str2
client.insert(sql)
return self
def unlink(self):
if len(self) > 1:
tb_name = ''
for item in self:
tb_name = item._tb_name
break
for current_id in self.ids:
client.delete_by_id(tb_name, current_id)
else:
client.delete_by_id(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)
client.eitd_by_id(sql)
return True
models.py , 已经重写了必要的方法,如下:
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from .public import Public
from ..tools.connection import mysql_client as client
class MySQL(Public, models.Model):
_name = 'mysql.user'
_description = '用户'
_auto = False # 禁止自动创建psql数据库
_tb_name = 'users' # 此model对应MySQL的表名, 务必定义此属性
name = fields.Char(string='昵称')
age = fields.Integer(string='年龄')
gender = fields.Selection([
('man', '男'),
('women', '女'),
], string='性别')
# self._fields.keys()
def read(self, fields=None, load='_classic_read'):
result_set = []
# 新建的时候没有id
if not self.id:
latest_id = client.get_new_id(self._tb_name)
data = client.get_data_by_id(self._tb_name, latest_id)
else:
data = client.get_data_by_id(self._tb_name, self.id)
result = {
'id': data[0][0],
'name': data[0][1],
'age': data[0][2],
'gender': data[0][3],
}
result_set.append(result)
return result_set
def search(self, args, offset=0, limit=None, order=None, count=False):
data_set = client.get_all_by_page(self._tb_name, offset, limit)
result_set = []
for i in range(len(data_set)):
data = {
'id': data_set[i][0],
'name': data_set[i][1],
'age': data_set[i][2],
'gender': data_set[i][3],
}
result_set.append(data)
return result_set
然后是配置数据库的:
# -*- 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 , 获取连接对象以及操作数据库的对象的:
import pymysql
from odoo.http import request
from odoo.exceptions import ValidationError
class Connection(object):
def __init__(self, config):
self.connection = pymysql.connect(**config)
self.cursor = self.connection.cursor()
def __del__(self):
self.connection.close()
def get_all_by_page(self, tb_name, offset=0, limit=None):
'''
分页查询
:param tb_name:
:param offset:
:param limit:
:return:
'''
self.connection.ping(reconnect=True)
sql = '''
SELECT * FROM {} LIMIT {}, {}
'''.format(tb_name, offset, limit)
self.cursor.execute(sql)
result = self.cursor.fetchall()
return result
def get_data_by_id(self, tb_name, id):
"""
根据id查询记录
:param tb_name:
:param id:
:return:
"""
self.connection.ping(reconnect=True)
sql = '''
SELECT * FROM {} WHERE id = {}
'''.format(tb_name, id)
self.cursor.execute(sql)
return self.cursor.fetchall()
def insert(self, sql):
'''
插入数据
:param sql:
:return:
'''
try:
self.connection.ping(reconnect=True)
self.cursor.execute(sql)
self.connection.commit()
except ValidationError as e:
self.connection.rollback()
raise e
def delete_by_id(self, tb_name, id):
'''
根据id删除
:param tb_name:
:param id:
:return:
'''
try:
self.connection.ping(reconnect=True)
sql = '''
DELETE FROM {} WHERE id = {}
'''.format(tb_name, id)
self.cursor.execute(sql)
self.connection.commit()
except ValidationError as e:
self.connection.rollback()
raise e
def eitd_by_id(self, sql):
'''
根据ID修改
:param sql:
:return:
'''
try:
self.connection.ping(reconnect=True)
self.cursor.execute(sql)
self.connection.commit()
except ValidationError as e:
self.connection.rollback()
raise e
def get_new_id(self, tb_name):
'''
返回最新的id
:param tb_name:
:return:
'''
self.connection.ping(reconnect=True)
sql = """
SELECT MAX(id) FROM {}
""".format(tb_name)
self.cursor.execute(sql)
data = self.cursor.fetchall()
return data[0][0]
# 获取配置信息
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')
# 默认连接信息
DEFAULT_CONFIG = {
"host": host if host else 'localhost',
"user": user if user else 'root',
"password": password if password else 'admin',
"db": 'demo',
"charset": "utf8",
"ssl": {'ssl': {}}
}
mysql_client = Connection(DEFAULT_CONFIG)
def flush_connection():
global mysql_client
mysql_client = Connection(DEFAULT_CONFIG)
model视图:
<odoo>
<data>
<record model="ir.ui.view" id="mysql_users_view_tree">
<field name="name">mysql.user.tree</field>
<field name="model">mysql.user</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_users_view_form">
<field name="name">mysql.user.form</field>
<field name="model">mysql.user</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_users_action_window">
<field name="name">员工</field>
<field name="res_model">mysql.user</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_users" parent="mysql_l1_menu_root"
action="mysql_users_action_window"/>
</data>
</odoo>
安全配置:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mysql_user,mysql.user,model_mysql_user,,1,1,1,1
然后在我们熟悉的MySQL数据库建个简单的表:
create database demo;
use demo;
create table users(id int(5) primary key not null auto_increment,name varchar(20), age int, gender varchar(10));
完事,可以去安装了。注意的是,前面我在models.py里面写了默认的数据库配置信息,请先按照默认的配置好数据库再去安装。安装好之后在浏览器上web之后加上参数 ?debug=1打开开发者模式,点击设置,可以看到这里,在这里设置数据库信息后点击保存:
至此,已经实现跨库CURD啦。
代码没有做过多、更优雅的封装,主要是实现的过程。另外,这么做有个很明显的缺点,不够通用。当然如果各位有时间的话可以尝试去做个更加通用的。
完整代码:
网盘链接
提取码:ttka
有不对的地方或者疑问小伙伴们及时提出哈。