第三章 控制器
控制器(controller)是处理页面请求的处理器,通常的http和json请求都是由controller负责处理和转发的,odoo默认缺省的方式是http。 odoo内置的web服务器werkzeug跟Python著名的web框架Flask用的是同一个,因此,你在定义路由的时候会发现跟Flask的定义方式十分相像。
路由的定义
先来看一个web路由的栗子:
@http.route([
'/report/<converter>/<reportname>',
'/report/<converter>/<reportname>/<docids>',
], type='http', auth='user', website=True)
def report_routes(self, reportname, docids=None, converter=None, **data):
report = request.env['ir.actions.report']._get_report_from_name(reportname)
http.route装饰器的参数:
- type: 指定请求的方式,可选值http和json
- auth: 认证方式,user,public,none 其中public和none指明用户不需要登录即可访问改URL。
- website: 是否是网站的URL。
获取GET方法的参数
controller获取GET方法传进来的参数,使用reqest.params:
signature = request.params.get("signature",None)
timestamp = request.params.get("timestamp",None)
echostr = request.params.get("echostr",None)
nonce = request.params.get("nonce",None)
获取POST方法的数据
controller获取GET方法传进来的原始参数,使用request.httprequest.data:
request.httprequest.data.decode("utf-8")
对于图片等二进制文件,需要在前段form表单中添加属性enctype="multipart/form-data",然后在controller中使用request.httprequest.files属性获取文件。
下面是一个例子:
<form class="oe_login_form" role="form" t-attf-action="/baybao_faceid/register/" method="post" enctype="multipart/form-data" onsubmit="this.action = this.action + location.hash">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="form-group field-community">
<input type="file" name="img" />
</div>
</form>
后端的接收代码:
img1 = request.httprequest.files.get('img')
...
"idnoimg1": b64encode(img1.read())
由于odoo存储二进制的方式为base64编码,因此,我们在拿到二进制文件以后需要使用base64编码存储到系统中。
获取JSON数据
当API接口使用json方式传输数据时,controller中获取json数据的方式为:
data = request.jsonrequest.data
在controller中调用model对象
在controller中引用model对象的方法跟model中的方法类似:
request.env["sale.order"]
导出Excel的例子
controler不仅可以处理日常的请求,也可以返回一个文件,这里以Excel文件为例:
wkbook = xlwt.Workbook()
for name in names:
order = request.env["sale.order"].sudo().search(
[('name', '=', name)], limit=1)
if order:
wksheet = wkbook.add_sheet(f"销售订单{order.name}")
wksheet.write(0, 0, "产品")
wksheet.write(0, 1, "订购数量")
wksheet.write(0, 2, "计量单位")
wksheet.write(0, 3, "单价")
wksheet.write(0, 4, "小计")
row = 1
for line in order.order_line:
wksheet.write(row, 0, line.product_id.name)
wksheet.write(row, 1, line.product_uom_qty)
wksheet.write(row, 2, line.product_uom.name)
wksheet.write(row, 3, line.price_unit)
wksheet.write(row, 4, line.price_subtotal)
row += 1
buffer = BytesIO()
wkbook.save(buffer)
data = buffer.getvalue()
headers = [
('Content-Type', 'application/vnd.ms-excel'),
('Content-Length', len(data)),
('Content-Disposition', 'attachment; filename="sale.order.xls"')
]
return request.make_response(data,
headers=headers)
获取用户的访问IP
odoo虽然用的是同flask一样的werkzeug,但是获取用户的ip却不想flask那么简单,在flask中获取用户访问ip可以直接使用
request.remote_addr
而在odoo中要复杂一些,获取用户的IP代码如下:
request.httprequest.environ['REMOTE_ADDR']
对于前端挂在了nignx的服务器,Nignx需要配置为转发用户的真实IP:
proxy_set_header Host $host:80;
proxy_set_header Origin "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
然后在odoo中获取nginx的真实IP:
request.httprequest.environ['HTTP_X_REAL_IP']
返回404
controller的web服务器用的是werkzeug,因此像redirect、404等返回对象,我们可以利用werkzeug提供的功能来实现。
@http.route('/book_store/raise_404/', auth='public')
def index(self, **kw):
return werkzeug.exceptions.NotFound()
返回 500
@http.route('/book_store/raise_500/', auth='public')
def list(self, **kw):
return werkzeug.exceptions.InternalServerError()
跳转其他网址
跳转其他网址可以使用request对象的redirect方法:
Http Response
一般情况下,我们返回给前端的对象都是HttpResponse,odoo已经帮我们封装好了一个make_response方法,在controller中直接返回即可。
return request.make_response(content_base64, headers)
第一个参数为返回的数据对象,第二个参数为headers。
Json Response
如果要返回Json对象,我们不需要封装,直接返回dict对象即可。由于odoo使用的是jsonrpc,这里返回的结果会被自动包裹在
{
"jsonrpc":"2.0",
"id":null,
"result":"result"
}
的result中。
提示警告信息
警告信息通常用于提示用户缺少必要的输入条件或者当前环境不符合要求,我们可以通过在controller中返回一个包含waring的字典值来完成这个动作:
return {'warning': _('No picking or location corresponding to barcode %(barcode)s') % {'barcode': barcode}}
controller的继承与重载
如果想要对已有的controller进行覆盖,可以通过指定模块路径的方式来实现。
例如,我们现有一个controller
class DeliveryKdn(http.Controller):
@http.route('/delivery_kdn/eorder', auth='public')
def index(picking, id, **kw):
picking = request.env["stock.picking"].browse(int(id))
if not picking:
return
res = picking._create_eorder(
picking.partner_id, picking.env.user.partner_id)
if not res['Success']:
return request.make_response(f"打印电子面单失败:{res['Reason']}")
return request.make_response(res['PrintTemplate'])
现在,我们想要对这个controler的代码进行重构,根据不要动既有模块代码的原则,我们新建了一个模块,并继承该模块的controller。
from odoo.addons.delivery_kdniao.controllers.controllers import DeliveryKdn
class controller(DeliveryKdn):
@http.route('/delivery_kdn/eorder', auth='public')
def index(picking, id, **kw):
pass
其实原理是利用的python自带的的子类继承父类,并将覆盖掉父类同名方法的特性。另外,这里需要将两个route的URL设置为一致。