Odoo 网页开发
1 2 3 4 5 6 7 8 9 | request.make_response() # 仅返回包含 HTML 的字符串 request.render() # 返回一个模板 # 对于 json请求 。 只需要返回客户端想要的数据结构即可。 # odoo 会处理序列化。让其动作,限制数据为json可序列化的类型 # request.env 属性,包含了与模型self.env 相同的Environment对象。 # request.session 是werkzeug对Session对象的轻微封装。 OpenERPSession 对象 |
1 2 3 4 5 6 7 8 | # route 装饰器 可带有其他的参数来进一步定义其行为。 默认允许HTTP所有方法 methods 参数 接收 方法列表 # 允许跨域 设置cors参数 # odoo 对每个请求传递 token 来保护请求免受跨站伪造请求 csrf的攻击 # 多数据库 参考下面文字 https: / / github.com / OCA / server - tools |
限制网络可访问路径的访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 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
1 2 3 4 5 6 7 | @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' , ) |
1 2 3 4 | 操作response.headers来添加或删除HTTP头部 渲染整个不同的模板,可以覆盖response.template response首先是否是基于QWeb,使用response.is_qweb进行查询 response.render()可获取结果HTML代码 |
管理静态资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | # 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 字段 |
动态路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @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>接收多个值 |
为用户提供小组件
1 2 3 4 5 6 7 | https: / / alanhou.org / cms - website - development / # 1. 添加插件视图 # 2. 添加 template 模板 # 3. 继承 组件 website.snippets 。 添加组件和选项 # 4. 在已继承的组件模板中添加组件选项 # 5. 添加 css 和js脚本来调试 |
从网站用户获取输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | # 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选项
1 2 3 4 5 6 7 8 9 | # odoo 模板提供了内置的SEO支持 。 希望为每个URL分离SEO选项 # 继承 website.seo.metadata mixin类 _inherit = [ 'website.seo.metadata' ] odoo12 添加 对 openGrapht 和Twitter分享meta标签的支持。 如果希望在自己的页面添加自定义meta标签 继承mixin后 重载 _default_website_meta() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 编写地图函数 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 |
获取访客的国家信息
1 2 3 4 5 6 7 8 9 10 | # 下载配置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’ |
追踪营销活动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # 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字段。它用于追踪不同的媒介,如贺卡、邮件或横幅广告。 |
管理多站点
1 2 3 4 5 6 7 8 9 10 | # 继承 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() |
网页客户端开发
自定义组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | / / # 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模板
1 2 3 4 5 6 7 8 9 10 | # 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调用
1 2 3 4 | # this.model 存储了当前模型的名称 # this.field 是模型调用 fields_get函数的结果 # 对于x2x字段,fields_get()函数会给出co-model或域的信息。也可以使用它来查询字段的string、size或其可在模型定义时为字段所设置的属性 |
新建一个视图
1 2 | # 1. 新建视图 ir.ui.view 添加新视图模型 \ |
导览提升客户引导
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | / / 新增一个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' , } ]); }); |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库