Odoo 网页开发
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', } ]); });