路径在网络中可访问
request.make_response() # 仅返回包含 HTML 的字符串
request.render() # 返回一个模板
# 对于 json请求 。 只需要返回客户端想要的数据结构即可。
# odoo 会处理序列化。让其动作,限制数据为json可序列化的类型
# request.env 属性,包含了与模型self.env 相同的Environment对象。
# request.session 是werkzeug对Session对象的轻微封装。 OpenERPSession 对象
# route 装饰器 可带有其他的参数来进一步定义其行为。 默认允许HTTP所有方法
methods 参数 接收 方法列表
# 允许跨域 设置cors参数
# odoo 对每个请求传递 token 来保护请求免受跨站伪造请求 csrf的攻击
# 多数据库 参考下面文字
https://github.com/OCA/server-tools
限制网络可访问路径的访问
# auth='none' 任何用户都可以
# auth='public' 公共用户
# auth='user' 已验证的用户提供内容 ,通过request.env.user 指向有才能在用户
# 验证方法 在base插件的ir.http模型中
from odoo import exceptions, http, models
from odoo.http import request
class IrHttp(models.Model):
_inherit = 'ir.http'
def _auth_method_base_group_user(self):
self._auth_method_user()
if not request.env.user.has_group('base.group_user'):
raise exceptions.AccessDenied()
传递消耗参数到你的handler
@http.route('/my_library/book_details', type='http', auth='none')
def book_details(self, book_id): # 传递参数 book_id
record = request.env['library.book'].sudo().browse(int(book_id))
return u'<html><body><h1>%s</h1>Authors: %s' % (
record.name,
u', '.join(record.author_ids.mapped('name')) or 'none',
)
操作response.headers来添加或删除HTTP头部
渲染整个不同的模板,可以覆盖response.template
response首先是否是基于QWeb,使用response.is_qweb进行查询
response.render()可获取结果HTML代码
管理静态资源
web.assets_common 包含所有通用的基本工具:JQ ,Fa等资源。 odoo所有地方都加载
web.assets_backend 包含所有与web客户端,视图,字段,组件,动作管理器相关代码
web.assets_frontent 用于前台,所有与网站端相关的代码。 电商,博客,线上活动,论坛和在线聊天等。 不包含网站编辑和网站构造器
web_editor.assets_editor和web_editor.summernote: 包含网站编辑组件以及拖拽功能。 批量邮件设计器
web.report_assets_commo : QWeb 仅通过html 生成PDF文件
### odoo 通过 AssetsBundle 管理其静态资源
位于: /odoo/addons/base/models/assetsbundle.py
1. 多个 JavaScript 和css文件
2. 通过从文件内容中删除注释,多余空格以及回车换行来 最小化 JavaScript和Css文件、删除这些额外资源会减小静态资源的大小并提升页面速度
3. 对css 预处理的内置支持,如SASS和LESS。 自动会编译并添加到资源包中
4. 在达到4095的规则上限时它自动拆分样式表资源
### 页面引入
1. link 标签添加
2. QWeb中 使用 t-call-assets 引入 <t t-call-assets="my_module.my_custom_assets" tcss="false"/>
3.t-css和 t-js属性公用于加载样式表或脚本
# 开发阶段来说: odoo资源仅会生成一次
命令中使用 dev=xml 这样会直接加载资源,无需重启服务
拓展css和js
# 1. 在已有页面 加载 样式和js
<link href="/my_library/static/src/css/my_library.css" rel="stylesheet" type="text/css"/>
<link href="/my_library/static/src/scss/my_library.scss" rel="stylesheet" type="text/scss"/>
<script src="/my_library/static/src/js/my_library.js" type="text/JavaScript" />
# 2. 编写内容
参考地址:
https://alanhou.org/cms-website-development/
### odoo cms的底层名为QWeb的XML模板引擎。
1. 通过 web.assets_frontend 中列出了样式表和JavaScript文件
2. scss 语法 : odoo12版本之前使用的是less预处理器,12版本后使用的bs4和SCSS预处理器
3.RequireJS 语法:
odoo.define('模块名',function(require){ //require 必要参数
//代码
})
创建或更改QWeb
# contenteditable 属性 不可编辑
# t-call 调用模板 <t t-call="website.layout">
# website.layout 包含所有需要的工具:如 bootstrap JQ fontAw 资源
# website.layout 包含默认的头部,底部 ,代码片段和页面编辑功能
# t-foreach 属性,重复迭代成员
语法: t-as 相当于 每个成员的别名, t-esc输出别名
<t t-foreach='[1,2,3]' t-as='num'>
<p><t t-esc='num'></p>
</t>
## 查看 t-call 元素的内部。 末班通过上下文渲染
book_index 返回遍历中的当前索引值 ,从0开始
book_first 和 book_last 分别在遍历第一个和最后一个时为True
book_value 遍历的是一个字典会包含各项值。通过字典的键进行遍历
book_size 集合的大小
book_even 和 book_add 根据遍历的索引获取true的值
book_parity 在遍历的索引为偶数时包含even的值,奇数时包含odd值
# QWeb 动态设置属性值
1。t-att-$attr_name 创建一个$attr_name属性,它的值是任意有效的python表达式
<div t-att-total="10+5+5"></div>
↓
<div total='20'></div>
2。 t-attf-$attr_name 与上一个区别是{{...}}和#{...} 之间的字符串
<t t-foreach="['info', 'danger', 'warning']" t-as="color">
<div t-attf-class="alert alert-#{color}">
Simple bootstrap alert
</div>
</t>
3。 t-att=mapping 选项,末班渲染自定后转换为属性和值接受这个字典
<div t-att= "{'id':'my_el_id', 'class': 'alert alert-danger'}"/>
↓
<div id='my_el_id' class='alert alert-danger'/>
# 字段 t-field 和 t-esc
t-field='字段名'
t-options='{}' # 传递一个字典,给字段设置渲染器 {'widget':'image'}
t-esc 是 t-field的替代属性。 t-esc属性并不只局限与记录集,它也可以是任意数据类型。但在网站内不可编辑
# t-field 和 t-esc 区别
t-field 基于用户的语言值
t-esc 显示数据库中的原始值
# 条件语句 t-if
t-if="state == 'new'"
t-elif="state == 'progress'"
t-else=""
# 设置变量
<t t-set='my_var' t-value='5+1'/>
<t-esc='my_var'/>
# 子模板
<template id="first_template">
<div> Test Template </div>
</template>
<template id="second_template">
<t t-call="first_template"/>
</template>
# 行内编辑
t-field 节点加载的数据 默认是可编辑的
禁用行内编辑 contenteditable=False
# 启用 页面拖拽 功能 oe_structure
添加样式将开启 页面启用组件 拖拽功能 oe_structure
# 网站编辑器编辑视图会在视图中设置noupdate标记。这表示后续的代码修改不会在客户的数据库中体现。
# 继承 inherit_id 字段
动态路由
@http.route('/books/<model("library.book"):book>', type='http', auth="user", website=True)
def function(self,book): # book 必须传递,否则报错
pass
# 视图 t-attf-href=设置动态属性, #{} 取值
<a t-attf-href="/books/#{book.id}" class="btn btnprimary btn-sm">
<i class="fa fa-book"/> Book Detail
</a>
# 其他动态路由
/page/接收一个整数
/page/<any(about, help):page_name=””>接收给定的值
/pages/<page>接收字符串
/pages/<category>/<int:page>接收多个值
为用户提供小组件
https://alanhou.org/cms-website-development/
# 1. 添加插件视图
# 2. 添加 template 模板
# 3. 继承 组件 website.snippets 。 添加组件和选项
# 4. 在已继承的组件模板中添加组件选项
# 5. 添加 css 和js脚本来调试
从网站用户获取输入
# 1. 需要一个模型来保存用户提交的问题
class LibraryBookIssues(models.Model):
_name = 'book.issue'
book_id = fields.Many2one('library.book', required=True)
submitted_by = fields.Many2one('res.users')
isuue_description = fields.Text()
#2. 视图中添加 book_issuse_id 字段
<group string="Book Issues">
<field name="book_issue_id" nolabel="1">
<tree>
<field name="create_date"/>
<field name="submitted_by"/>
<field name="isuue_description"/>
</tree>
</field>
</group>
# 3. 添加模型访问权限 ir.model.access.csv
acl_book_issues,library.book_issue,model_book_issue,group_librarian,1,1,1,1
# 4. 编写处理函数 在 main,py 新增一个路由函数
@http.route('/books/submit_issues', type='http', auth="user", website=True)
def books_issues(self, **post):
pass #
# 5. 添加一个 带有HTML表单的模板
<template>
....
</template>
# 6. 添加form 表单 , 解决csrf 问题
<form method='post'>
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
</form>
管理搜索引擎 优化SEO选项
# odoo 模板提供了内置的SEO支持 。 希望为每个URL分离SEO选项
# 继承 website.seo.metadata mixin类
_inherit = ['website.seo.metadata']
odoo12 添加 对 openGrapht 和Twitter分享meta标签的支持。 如果希望在自己的页面添加自定义meta标签
继承mixin后 重载 _default_website_meta()
管理网站的站点地图
# 编写地图函数
from odoo.addons.http_routing.models.ir_http import slug
from odoo.addons.website.models.ir_http import sitemap_qs2dom
class Main(http.Controller):
...
def sitemap_books(env, rule, qs):
Books = env['library.book']
dom = sitemap_qs2dom(qs, '/books', Books._rec_name)
for f in Books.search(dom):
loc = '/books/%s' % slug(f)
if not qs or qs.lower() in loc:
yield {'loc': loc}
# 编写路由函数
@http.route('/books/<model("library.book"):book>',type='http', auth="user", website=True, sitemap=sitemap_books ) # 添加地图
def library_book_detail(self, book):
pass
获取访客的国家信息
# 下载配置GeoIP内置支持
# 添加字段保存国家
restrict_country_ids = fields.Many2many('res.country')
# 展示字段
<field name="restrict_country_ids" widget="many2many_tags"/>
# 正确配置nginx 和GeoIP , odoo将会对request.session.geoip添加GeoIP信息
country_code = request.session.geoip and request.session.geoip.get(‘country_code’) or ‘IN’
追踪营销活动
# ROI 投资回报率
# UTM 广告的花费进行追踪
# 1. depends 添加 utm模块
'depends': ['base', 'website', 'utm'],
# 2. 继承 utm.mixin
_inherit = ['utm.mixin']
# 3. compaign_id 添加到 视图表单中
<field name="campaign_id"/>
# # 继承了 utm.mixin
campaign_id : tm.campaign模型的Many2one字段。它用于追踪不同的活动,如夏季和圣诞特价
source_id:utm.source model.的Many2one字段。它用于追踪不同的来源,如搜索引擎和其它域名。
medium_id:utm.medium 模型的Many2one字段。它用于追踪不同的媒介,如贺卡、邮件或横幅广告。
管理多站点
# 继承 website.multi.mixin
_inherit = ['website.seo.metadata', 'website.multi.mixin']
# 1. 控制访问
domain += request.website.website_domain() # 将返回域名并过滤出不是来自该网站的图书。
# 2. can_access_from_current_website() 图书记录针对当前活跃网站的话方法can_access_from_current_website会返回值True,而针对另一个网站时返回False
if not book.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
网页客户端开发
自定义组件
// # events 捕获js事件
events: {
'click .o_color_pill': 'clickPill',
},
// # init 初始化 组件构造函数。用于进行初始化。在初始化组件时,会先调用该方法
init: function () {
this.totalColors = 10;
this._super.apply(this, arguments);
},
// willStart():
这个方法组件初始化以及在DOM中添加的过程中调用。它用于异步将数据初始化到组件中。它还会返回一个延迟对象,只需要通过super()调用即可获取。我们在后面的小节中将会使用到它
// start()
该方法在完成组件渲染且未添加到DOM中时调用。这非常有助于后渲染任务,将返回一个延迟对象。可以在this.$el中访问已渲染的对象
// destroy()
消灭组件时调用 , 如 取消事件绑定
// # 重载_renderEdit和_renderReadonly来设置DOM元素:
// # 定义 点击 handler
clickPill: function (ev) {
var $target = $(ev.currentTarget);
var data = $target.data();
if (mobile.methods.showToast) {
mobile.methods.showToast({ 'message': 'Color changed' });
}
this._setValue(data.val.toString());
}
// 注册组件
fieldRegistry.add('int_color', colorField);
// 插入组件
return {
colorField: colorField,
}; }); // closing 'my_field_widget' namespace
客户端QWeb模板
# QWeb的原因是其可扩展性, 客户端与服务端QWeb的很大区别在于。客户端无法使用Xpath表达式,需要使用jQuery选择器和操作
<t t-extend=“FieldColorPills”>
<t t-jquery=“span” t-operation=“prepend”>
<i class=“fa fa-user” />
</t>
</t>
# t-name 属性 对应 字段
# t-operation 属性 值: append ,before ,after ,inner , replace 。 attributes属性
服务端做RPC调用
# this.model 存储了当前模型的名称
# this.field 是模型调用 fields_get函数的结果
# 对于x2x字段,fields_get()函数会给出co-model或域的信息。也可以使用它来查询字段的string、size或其可在模型定义时为字段所设置的属性
新建一个视图
# 1. 新建视图 ir.ui.view 添加新视图模型
################ 抑郁 ~~~~~~~~~~~~~~~~~
导览提升客户引导
// 新增一个js
odoo.define('my_library.tour', function (require) {
"use strict";
var core = require('web.core');
var tour = require('web_tour.tour'); // 导入了 网站向导
var _t = core._t;
tour.register('library_tour', { //注册向导
url: "/web", // 注册运行时需要的URL
}, [
tour.STEPS.SHOW_APPS_MENU_ITEM, {
trigger: '.o_app[data-menuxmlid="my_library.library_base_menu"]',
content: _t('Manage books and authors in<b>Library app</b>.'),
position: 'right' // 指定上下左右的位置
},
{
trigger: '.o_list_button_add',
content: _t("Let's create new book."),
position: 'bottom'
}, {
trigger: 'input[name="name"]',
extra_trigger: '.o_form_editable',
content: _t('Set the book title'),
position: 'right',
}, {
trigger: '.o_form_button_save',
content: _t('Save this book record'),
position: 'bottom',
}
]);
});