应某个留言网友的要求,我们今天来了解一下odoo中的Controller相关内容。做过其它软件开发的朋友都知道,MVC(Model–View–Controller)是一种比较成熟和流行的软件架构模式,odoo14作为一个优秀的开源平台,当然也引入了这么一种架构。
其中的Model层就是我们定义的各种业务数据模型,这个部分主要是python代码的开发,也可以看作是软件项目的后端;
View层是我们定义的各种视图(tree、form、kanban等),主要是xml、css、js的开发,也可以看作是软件项目的前端;
Controller层在odoo里面可以认为是控制器,根据url地址来控制后端的业务和前端的内容展示,我们一般偏向于叫路由控制,它相当于内网和外网之间的防火墙,外网的请求到达控制器后,由控制器解析请求的url,再来匹配合适的内网业务方法。
如果我们自己开发的模块中,需要用到自定义的Controller层,一般我们都会在模块中创建新的子目录(controllers),所有关于路由控制的代码都会集中在这个子目录中。
我们来看一个很简单的路由定义源码:
from odoo import http
from odoo.http import request
class Home(http.Controller):
@http.route('/', type='http', auth="none")
def index(self, s_action=None, db=None, **kw):
return http.local_redirect('/web', query=request.params, keep_hash=True)
上面这个例子很简单,就是在浏览器中输入域名地址后,浏览器重定向到/web的新路由业务。
这里引入了http模块以及http模块里面的request对象。所有我们定义的路由控制类(class)都需要继承http.Controller,然后里面定义的每个具体的路由方法都需要使用http.route进行装饰,对于装饰器的语法和使用有疑问的朋友可以去温习一下python的高级开发技巧。
我们先来看一下http.route的定义是这样子的:
def route(route=None, **kw):
其中参数route可以是符串或数组,用来匹配对应方法执行的http请求地址,如下面的定义:
@http.route('/web', type='http', auth="none")
def web_client(self, s_action=None, **kw):
pass
@http.route([
'/web/binary/company_logo',
'/logo',
'/logo.png',
], type='http', auth="none", cors="*")
def company_logo(self, dbname=None, **kw):
pass
如果route参数是列表时,表示这个列表中的http请求都是响应同一个后台方法。这里定义的请求地址都是固定的,实际上,odoo的路由映射也支持动态的地址映射,如下面这个:
@http.route('/web/webclient/locale/<string:lang>', type='http', auth="none")
def load_locale(self, lang):
pass
这里定义的地址中<string:lang>就是一个可变内容占位符,这个占位符的格式是这样子的:
<converter(arguments):name>
其中converter表示这里是一个转换器,我们常用的可能是string、int、path几个,其中string表示不含'/'的字符串,int表示任意整数,path表示包含'/'的字符串。还有几个不常用的,如any、float、uuid。
转换器后面括号里的是这个转换器的参数,如果不写就表示使用默认参数,像我们上面的例子中就没有写这个部分,但如果我们要限制传入的lang变量只能是两位长度的字符,就可以这样子写:
@http.route('/web/webclient/locale/<string(length=2):lang>')
不同的转换器有不同的参数,比如int默认是只接收正整数,如果需要接收负数的地址,就可以使用<int(signed=True):page>这样子的定义。
占位符最后一个部分是变量名称,如例子中lang表示接收这个值的变量名称,这个名称将与被装饰方法load_locale中定义的参数lang对应。
假如我们在浏览器中录入"http://127.0.0.1/web/webclient/locale/EN",则在响应方法load_locale()时,参数lang的值就会是等于"EN"。
上面我们说的是http.route装饰器的第一个参数,另外还有下面这些参数:
type:定义请求的类型,其值只有'http'、'json'两种,一般来讲通过浏览器地址栏或非js代码来的请求都定义为http,如果是通过odoo内核的rpc调用的请求都定义为json;
auth:定义请求的权限,其值有'user','public','none'三种,如果路由权限为user,则需要验证了登录用户信息后才可以访问;如果路由权限为public,用户如果没有通过身份验证,当前请求将使用共享的Public用户访问;如果路由权限为none,则表示开放访问,没有权限验证,使用这种方式要特殊注意,有可能你访问odoo的时候还没有确定数据库连接资源;
methods:定义请求的方法列表,如果不设定,则表示所有方法都可以请求该路由,如果要指定,我们可以这样子写:methods=['GET','POST'];
cors:定义跨域资源访问权限,如果不定义,则其它网域的js不可以访问当前网域的路由映射,如果需要允许访问的话,就定义为cors="*";
csrf:定义是否启动路由的CSRF保护,默认是开启的,如果需要关闭,则定义为csrf=False。
上面我们介绍的是关于路由装饰器使用时的参数,接下来在装饰器下就要定义自己的业务方法。业务方法中的参数一般我们都是使用“**kw“来接收,这样我们在访问这个URL时,可以自由定义URL中的参数。比如我们如果使用这个网址为访问:
http://127.0.0.1/web/?db=xxx&user=test
那在我们如下的方法中就可以通过kw来获取到url的各个参数值:
@http.route("/web/")
def web(self, **kw):
print(kw.get("db"))
print(kw.get("user"))
但如果我们在装饰中使用了占位符,则一定要在业务方法的参数中把同名的参数定义上去。
接下来我们就看看在定义好的方法中怎么来获取业务逻辑需要的各种资源。
在定义好的路由映射方法中,有一个最重要的对象,就是我们在前面通过odoo.http引入的request对象,这个对象代表了客户端请求过来的所有资源。我们常用的一些资源有如下几种:
request.params(URL地址中的参数)
request.cr(请求连接的数据库游标对象)
request.uid(请求连接的用户对象ID)
request.context(请求连接的上下文参数)
request.env(请求连接的odoo环境对象)
request.session(请求连接的进程信息)
request.httprequest(HTTP请求对象)
request.render(渲染指定的模板,返回一个响应对象)
request.make_response(根据指定的变量数据返回一个响应对象)
上面这些对象里面,有关于客户端的浏览器信息(操作系统、浏览器、IP等)是包含在request.httprequest对象里面的。
在我们自己定义好的方法把业务处理完成以后,是需要返回一个处理结果的。odoo路由控制器的返回结果有几种:
一是返回一个重定向地址:
return http.local_redirect('/web', query=request.params, keep_hash=True)
或者:
return werkzeug.utils.redirect('/web/login?error=access')
如果重定向地址需要带参数,就可以上面一种,否则只是单纯重定向到固定URL可以用下面这种方式。
二是返回一个变量的数据:
body = json.dumps(menus, default=ustr)
response = request.make_response(body, [
('Content-Type', 'application/json'),
])
return response
上面make_response中第二个参数是定义head的参数值,这个列表你可以写多个值。
三是渲染一个模板结果:
response = request.render('web.login', values)
response.headers['X-Frame-Options'] = 'DENY'
return response
四是直接返回变量值(这个只有在定义type='json'路由时可用):
return {"modules": translations_per_module,
"lang_parameters": None}
好了,说到这里了,我们还有最后一个知识点。上面讲的这些都是在自己模块中定义的路由规则。要使这些规则生效,前提条件是要在数据库中安装了你的这个模块,是不是这个道理?
如果你当前服务器上有两个数据库(A、B),其中B数据库安装了你的这个模块,但A数据库没有安装。那另一个人,从浏览器上发起连线请求时到你服务器上时,就会发生错误(前提是之前没有连线过),因为这个时候服务器并不知道你要使用的是B数据库。这个问题在很多初学者做第三方接口时遇到过。明明自己测试是正确的,但第三方应用访问过来就是出错。因为你自己测试时cookie里有数据库信息了,第三方应用访问时是没有的。
要解决这个问题,odoo给我们提供了一个方案,在odoo.conf中有一个参数server_wide_modules,这个参数指定在没有数据库时,也会加载路由规则的模块名称。
现在真的是结束了,看了这么久,休息一下吧,点个"在看"再走。