Search 1.9 billion lines of Odoo code on GitHub

demo_expense_tutorial_v1

Author: My Company
License: no license
Branch: 14.0
Repository: twtrubiks/odoo-demo-addons-tutorial
Dependencies: hr_contract
Languages: Markdown (724, 70.4%), Python (95, 9.2%), and XML (209, 20.3%)
Other branches: master

<h1>odoo 入門篇</h1> <p>建議觀看影片, 會更清楚:smile:</p> <ul> <li><p><a href="https://youtu.be/vb_Z8KCI-wk">Youtube Tutorial - odoo 手把手教學 - Many2one - part1</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---many2one---part1">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/QeZfJqTGP-w">Youtube Tutorial - odoo 手把手教學 - Many2many - part2</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---many2many---part2">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/WiLdXP781N0">Youtube Tutorial - odoo 手把手教學 - One2many - part3</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---one2many---part3">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/HJcBAFXQYVc">Youtube Tutorial - odoo 手把手教學 - One2many Editable Bottom and Top - part3-1</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---one2many-editable-bottom-and-top---part3-1">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/zcWMs16p9Xw">Youtube Tutorial - odoo 手把手教學 - Search Filters - part4</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---search-filters---part4">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/twn6zz3OeRs">Youtube Tutorial - odoo 手把手教學 - 說明 noupdate 以及 domain_force - part5</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---%E8%AA%AA%E6%98%8E-noupdate-%E4%BB%A5%E5%8F%8A-domain_force---part5">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/URxuH2HG44Q">Youtube Tutorial - odoo 手把手教學 - 如何透過 button 呼叫 view, form - part6</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---%E5%A6%82%E4%BD%95%E9%80%8F%E9%81%8E-button-%E5%91%BC%E5%8F%AB-view-form---part6">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/g-dclCkwY5c">Youtube Tutorial - odoo 手把手教學 - 說明 name<em>get 和 _name</em>search - part7</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---%E8%AA%AA%E6%98%8E-name_get-%E5%92%8C-_name_search---part7">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/GBCGS2znnT8">Youtube Tutorial - odoo 手把手教學 - 使用 python 增加取代 One2many M2X record - part8</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---%E4%BD%BF%E7%94%A8-python-%E5%A2%9E%E5%8A%A0%E5%8F%96%E4%BB%A3-one2many-m2x-record---part8">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/0fpA89QcYZM">Youtube Tutorial - odoo 手把手教學 - tree create delete edit False - part9</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_expense_tutorial_v1#odoo-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---tree-create-delete-edit-false---part9">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/thhdGK9oebg">Youtube Tutorial - odoo14 手把手教學 - Active Archive Ribbon 教學 - part10</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/14.0/demo_expense_tutorial_v1#odoo14-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---active-archive-ribbon-%E6%95%99%E5%AD%B8---part10">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/tZ6_2Q3r3Ok">Youtube Tutorial - odoo14 手把手教學 - Search Panel 教學 - part11</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/14.0/demo_expense_tutorial_v1#odoo14-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---search-panel-%E6%95%99%E5%AD%B8---part11">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/OOlPZETkYKw">Youtube Tutorial - odoo14 手把手教學 - auto_join 說明 - part12</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/14.0/demo_expense_tutorial_v1#odoo14-%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%B8---auto_join-%E8%AA%AA%E6%98%8E">文章快速連結</a></p></li> </ul> <p>建議在閱讀這篇文章之前, 請先確保了解看過以下的文章 (因為都有連貫的關係)</p> <p><a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial">odoo 手把手建立第一個 addons</a></p> <p>這篇主要介紹 Many2one, Many2many, One2many 這三個東西,</p> <p>以下將介紹這個 addons 的結構</p> <h2>說明</h2> <h3>odoo 手把手教學 - Many2one - part1</h3> <ul> <li><a href="https://youtu.be/vb_Z8KCI-wk">Youtube Tutorial - odoo 手把手教學 - Many2one - part1</a></li> </ul> <p>先來看 <a href="models/models.py">models/models.py</a></p> <p><code>Many2one</code></p> <p>```python ...... class DemoExpenseTutorial(models.Model): _name = &#39;demo.expense.tutorial&#39; _description = &#39;Demo Expense Tutorial&#39;</p> <pre><code>name = fields.Char(&#39;Description&#39;, required=True) employee_id = fields.Many2one(&#39;hr.employee&#39;, string=&quot;Employee&quot;, required=True) user_id = fields.Many2one(&#39;res.users&#39;, default=lambda self: self.env.user) </code></pre> <p>...... ```</p> <p><img src="https://i.imgur.com/lBj9pgz.png" alt="alt tag"></p> <p>一個 <code>hr.employee</code> 可以對到很多個 <code>demo.expense.tutorial</code>,</p> <p>所以是 多(<code>demo.expense.tutorial</code>) 對 一(<code>hr.employee</code>) 的關係,</p> <p>來看 db 中的狀況</p> <p><code>demo_expense_tutorial</code> 會多出一個欄位 ( 對應 <code>hr_employee</code> 的 id )</p> <p><img src="https://i.imgur.com/8BVrrJa.png" alt="alt tag"></p> <p><code>user_id</code> field 中的 <code>default=lambda self: self.env.user</code> 代表預設的值會設定當前登入的 user</p> <p><img src="https://i.imgur.com/QiDj6iM.png" alt="alt tag"></p> <p>因為 One2many 比較特別, 所以我們先介紹 Many2many:laughing:</p> <h3>odoo 手把手教學 - Many2many - part2</h3> <p><code>Many2many</code></p> <ul> <li><a href="https://youtu.be/QeZfJqTGP-w">Youtube Tutorial - odoo 手把手教學 - Many2many - part2</a></li> </ul> <p>要建立 Many2many 之前, 一定要先定義一個 model,</p> <p>先定義 DemoTag (也請記得設定 <a href="security/ir.model.access.csv">security/ir.model.access.csv</a> )</p> <p><a href="models/models.py">models/models.py</a></p> <p>```python ...... class DemoTag(models.Model): _name = &#39;demo.tag&#39; _description = &#39;Demo Tags&#39;</p> <pre><code>name = fields.Char(string=&#39;Tag Name&#39;, index=True, required=True) active = fields.Boolean(default=True, help=&quot;Set active.&quot;) </code></pre> <p>...... ```</p> <p>然後接著到底下 <a href="models/models.py">models/models.py</a></p> <p>```python ...... class DemoExpenseTutorial(models.Model): <em>name = &#39;demo.expense.tutorial&#39; ...... # https://www.odoo.com/documentation/12.0/reference/orm.html#odoo.fields.Many2many # Many2many(comodel</em>name=<object object>, relation=<object object>, column1=<object object>, column2=<object object>, string=<object object>, **kwargs) # # relation: database table name #</p> <pre><code># By default, the relationship table name is the two table names # joined with an underscore and _rel appended at the end. # In the case of our books or authors relationship, it should be named demo_expense_tutorial_demo_tag_rel. </code></pre> <h3>odoo 手把手教學 - Many2many - part2</h3> <pre><code>tag_ids = fields.Many2many(&#39;demo.tag&#39;, &#39;demo_expense_tag&#39;, &#39;demo_expense_id&#39;, &#39;tag_id&#39;, string=&#39;Tges&#39;) </code></pre> <p>...... ```</p> <p>Many2many 比較多欄位, 我來說明一下,</p> <p><code>comodel_name</code> 為 <code>demo.tag</code> (需要對應的 model)</p> <p><code>relation</code> 為 <code>demo_expense_tag</code> (table 名稱),</p> <p>Many2many 會多出一個 table, 這邊是針對 table 命名,</p> <p>也就是 db 中的 table 名稱,</p> <p><img src="https://i.imgur.com/B7ren2r.png" alt="alt tag"></p> <p>如果你沒填 <code>relation</code> 這個值, 預設的 table 名稱會是 model名稱 + comodel<em>name + `</em>rel`,</p> <p>所以也就會是 <code>demo_expense_tutorial_demo_tag_rel</code>.</p> <p><code>column1</code> 為 <code>demo_expense_id</code>, <code>demo.expense.tutorial</code> table 中對應的 id.</p> <p><code>column2</code> 為 <code>tag_id</code>, <code>demo.tag</code> table 中對應的 id.</p> <p>繼續看 <a href="models/models.py">models/models.py</a></p> <p><code>python ...... # Related (Reference) fields (不會存在 db) # readonly default 為 True # store default 為 False gender = fields.Selection(&#39;Gender&#39;, related=&#39;employee_id.gender&#39;) ...... </code></p> <p><code>fields.Selection</code> 就只是下拉選單而已, 比較特別的是 <code>related</code> 這個,</p> <p><code>related=&#39;employee_id.gender&#39;</code> 這邊的意思是, 會自己去找 employee_id 中的 gender,</p> <p>到 employee 中找到 gender 為 Male</p> <p><img src="https://i.imgur.com/GDOC0hS.png" alt="alt tag"></p> <p>DemoExpenseTutorial 中的 <code>gender</code> 自然會是 Male,</p> <p><img src="https://i.imgur.com/mJrzLjk.png" alt="alt tag"></p> <p>但要注意幾件事情,</p> <p><code>related</code> 預設的 field 是不會儲存在 db 中的, store default 為 False,</p> <p>你在 table 中是找不到 <code>gender</code> field 的 (如下圖),</p> <p>如果你想要儲存在 db 中的, 請另外設定 <code>store=Ture</code>,</p> <p><img src="https://i.imgur.com/0Xcvzas.png" alt="alt tag"></p> <p>然後 readonly default 為 True, 也就是說你是不可以去修改的,</p> <p>( 如果要修改請去 employee 中找到 gender 修改 )</p> <p><img src="https://i.imgur.com/mJrzLjk.png" alt="alt tag"></p> <p>接著來看最後一個</p> <h3>odoo 手把手教學 - One2many - part3</h3> <p><code>One2many</code></p> <ul> <li><a href="https://youtu.be/WiLdXP781N0">Youtube Tutorial - odoo 手把手教學 - One2many - part3</a></li> </ul> <p><img src="https://i.imgur.com/lV2J3Tu.png" alt="alt tag"></p> <p><a href="models/models.py">models/models.py</a></p> <p>一個 <code>demo.expense.sheet.tutorial</code> 可以對應很多個 <code>demo.expense.tutorial</code></p> <p>所以是 一(<code>demo.expense.sheet.tutorial</code>) 對 多(<code>demo.expense.tutorial</code>) 的關係,</p> <p>```python ...... class DemoExpenseSheetTutorial(models.Model): _name = &#39;demo.expense.sheet.tutorial&#39; _description = &#39;Demo Expense Sheet Tutorial&#39;</p> <pre><code>name = fields.Char(&#39;Expense Demo Report Summary&#39;, required=True) # One2many is a virtual relationship, there must be a Many2one field in the other_model, # and its name must be related_field expense_line_ids = fields.One2many( &#39;demo.expense.tutorial&#39;, # related model &#39;sheet_id&#39;, # field for &quot;this&quot; on related model string=&#39;Expense Lines&#39;) </code></pre> <p>...... ```</p> <p>說明 expense<em>line</em>ids 裡面的參數意義,</p> <p><code>demo.expense.tutorial</code> 代表關連的 model (必填)</p> <p><code>sheet_id</code> 代表所關連 model 的 field (必填)</p> <p>也就是說如果你要建立 One2many, 一定也要有一個 Many2one,</p> <p>但如果建立 Many2one 則不一定要建立 One2many.</p> <p>One2many 是一個虛擬的欄位, 你在資料庫中是看不到它的存在(如下圖)</p> <p><img src="https://i.imgur.com/F6YTOdq.png" alt="alt tag"></p> <p>你只會看到 Many2one 中的 sheet_id</p> <p><img src="https://i.imgur.com/lsiLpZK.png" alt="alt tag"></p> <p><a href="models/models.py">models/models.py</a>, <code>demo.expense.tutorial</code> 中的 sheet_id</p> <p><code>python class DemoExpenseTutorial(models.Model): _name = &#39;demo.expense.tutorial&#39; _description = &#39;Demo Expense Tutorial&#39; ...... sheet_id = fields.Many2one(&#39;demo.expense.sheet.tutorial&#39;, string=&quot;Expense Report&quot;) ...... </code></p> <p>記得也要設定對應的 <a href="security/ir.model.access.csv">security/ir.model.access.csv</a> 和 <a href="security/security.xml">security/security.xml</a>.</p> <p><a href="views/view.xml">views/view.xml</a></p> <p>```xml ...... <record id="view_form_demo_expense_tutorial" model="ir.ui.view"> <field name="name">Demo Expense Tutorial Form</field> ...... &lt;!-- <field name="tag_ids"/> --&gt; <field name="tag_ids" widget="many2many_tags"/> &lt;!-- widget --&gt; <field name="sheet_id"/> ......</p> <p>```</p> <p>在 odoo 中很有多 widget, 大家可以改成其他的 widget 試試看, 像是 many2many_tags 的 widget</p> <p><img src="https://i.imgur.com/UBYyUcf.png" alt="alt tag"></p> <p><a href="views/view.xml">views/view.xml</a></p> <p><code>xml ...... &lt;record id=&quot;view_form_demo_expense_sheet_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Sheet Tutorial Form&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.sheet.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;form string=&quot;Demo Expense Sheet Tutorial&quot;&gt; &lt;sheet&gt; &lt;group&gt; &lt;field name=&quot;name&quot;/&gt; &lt;/group&gt; &lt;notebook&gt; &lt;page string=&quot;Expense&quot;&gt; &lt;field name=&quot;expense_line_ids&quot;&gt; &lt;tree&gt; &lt;field name=&quot;name&quot;/&gt; &lt;field name=&quot;employee_id&quot;/&gt; &lt;field name=&quot;tag_ids&quot; widget=&quot;many2many_tags&quot;/&gt; &lt;/tree&gt; &lt;/field&gt; &lt;/page&gt; &lt;/notebook&gt; &lt;/sheet&gt; &lt;/form&gt; &lt;/field&gt; &lt;/record&gt; ...... </code></p> <p>在 <code>view_form_demo_expense_sheet_tutorial</code> 裡的 One2many 中的 expense<em>line</em>ids fields,</p> <p>就把需要的欄位填進去即可,</p> <p><img src="https://i.imgur.com/jiFHHST.png" alt="alt tag"></p> <h3>odoo 手把手教學 - One2many Editable Bottom and Top - part3-1</h3> <p>這邊補充一下 One2many 中的 Editable Bottom 和 Top</p> <ul> <li><a href="https://youtu.be/HJcBAFXQYVc">Youtube Tutorial - odoo 手把手教學 - One2many Editable Bottom and Top - part3-1</a></li> </ul> <p><a href="views/view.xml">views/view.xml</a></p> <p><code>xml &lt;record id=&quot;view_form_demo_expense_sheet_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Sheet Tutorial Form&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.sheet.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;form string=&quot;Demo Expense Sheet Tutorial&quot;&gt; &lt;sheet&gt; ...... &lt;notebook&gt; &lt;page string=&quot;Expense&quot;&gt; &lt;field name=&quot;expense_line_ids&quot; &gt; &lt;tree&gt; &lt;!-- &lt;tree editable=&quot;top&quot;&gt; --&gt; &lt;!-- &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt; --&gt; &lt;!-- &lt;tree editable=&quot;bottom&quot;&gt; --&gt; &lt;!-- &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt; --&gt; &lt;field name=&quot;name&quot;/&gt; &lt;field name=&quot;employee_id&quot;/&gt; &lt;field name=&quot;tag_ids&quot; widget=&quot;many2many_tags&quot;/&gt; &lt;/tree&gt; &lt;/field&gt; &lt;/page&gt; &lt;/notebook&gt; &lt;/sheet&gt; &lt;/form&gt; &lt;/field&gt; &lt;/record&gt; </code></p> <p>如果你加上 <code>editable</code> 這個參數, 當你新增 record 的時候, 就不會整個跳出視窗, 可以直接在裡面輸入</p> <p>(或許比較好看:smile:)</p> <p><img src="https://i.imgur.com/tdues3g.png" alt="alt tag"></p> <p>至於 <code>editable=&quot;bottom&quot;</code> 和 <code>editable=&quot;top&quot;</code> 的差別如下</p> <p><code>editable=&quot;top&quot;</code> 一個新增的 record 會顯示在最上面</p> <p><img src="https://i.imgur.com/qWaIH59.png" alt="alt tag"></p> <p><code>editable=&quot;bottom&quot;</code>一個新增的 record 會顯示在最下面</p> <p><img src="https://i.imgur.com/d3pfgRX.png" alt="alt tag"></p> <h3>odoo 手把手教學 - Search Filters - part4</h3> <p>接著來看 filter 的功能</p> <ul> <li><a href="https://youtu.be/zcWMs16p9Xw">Youtube Tutorial - odoo 手把手教學 - Search Filters - part4</a></li> </ul> <p><code>xml ...... &lt;record id=&quot;view_filter_demo_expense_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Tutorial Filter&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;search string=&quot;Demo Expense Tutorial Filter&quot;&gt; &lt;field name=&quot;name&quot; string=&quot;Name&quot;/&gt; &lt;group expand=&quot;0&quot; string=&quot;Group By&quot;&gt; &lt;filter string=&quot;Sheet&quot; name=&quot;sheet&quot; domain=&quot;[]&quot; context=&quot;{&#39;group_by&#39;: &#39;sheet_id&#39;}&quot;/&gt; &lt;filter string=&quot;Employee&quot; name=&quot;employee&quot; domain=&quot;[]&quot; context=&quot;{&#39;group_by&#39;: &#39;employee_id&#39;}&quot;/&gt; &lt;/group&gt; &lt;/search&gt; &lt;/field&gt; &lt;/record&gt; ...... </code></p> <p><code>&lt;field name=&quot;name&quot; string=&quot;Name&quot;/&gt;</code> 主要是在 tree 中搜尋</p> <p><img src="https://i.imgur.com/eBmc2Je.png" alt="alt tag"></p> <p><code>&lt;filter string=&quot;Sheet&quot; name=&quot;sheet&quot; domain=&quot;[]&quot; context=&quot;{&#39;group_by&#39;: &#39;sheet_id&#39;}&quot;/&gt;</code></p> <p><code>&lt;filter string=&quot;Employee&quot; name=&quot;employee&quot; domain=&quot;[]&quot; context=&quot;{&#39;group_by&#39;: &#39;employee_id&#39;}&quot;/&gt;</code></p> <p>依照特定的 fields 分組</p> <p><img src="https://i.imgur.com/jojaYtz.png" alt="alt tag"></p> <p>點選後的狀態</p> <p><img src="https://i.imgur.com/acyqVIA.png" alt="alt tag"></p> <h3>odoo 手把手教學 - 說明 noupdate 以及 domain_force - part5</h3> <p>再來看看</p> <p><a href="security/ir_rule.xml">security/ir_rule.xml</a></p> <ul> <li><a href="https://youtu.be/twn6zz3OeRs">Youtube Tutorial - odoo 手把手教學 - 說明 noupdate 以及 domain_force - part5</a></li> </ul> <p>```xml ...... <data noupdate="1"></p> <pre><code> &lt;record id=&quot;ir_rule_demo_expense_user&quot; model=&quot;ir.rule&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense User&lt;/field&gt; &lt;field name=&quot;model_id&quot; ref=&quot;model_demo_expense_tutorial&quot;/&gt; &lt;field name=&quot;domain_force&quot;&gt;[(&#39;employee_id.user_id.id&#39;, &#39;=&#39;, user.id)]&lt;/field&gt; &lt;field name=&quot;groups&quot; eval=&quot;[(4, ref(&#39;demo_expense_tutorial_group_user&#39;))]&quot;/&gt; &lt;/record&gt; &lt;record id=&quot;ir_rule_demo_expense_manager&quot; model=&quot;ir.rule&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Manager&lt;/field&gt; &lt;field name=&quot;model_id&quot; ref=&quot;model_demo_expense_tutorial&quot;/&gt; &lt;field name=&quot;domain_force&quot;&gt;[(1, &#39;=&#39;, 1)]&lt;/field&gt; &lt;field name=&quot;groups&quot; eval=&quot;[(4, ref(&#39;demo_expense_tutorial_group_manager&#39;))]&quot;/&gt; &lt;/record&gt; &lt;/data&gt; </code></pre> <p>...... ```</p> <p><code>noupdate=&quot;1&quot;</code>的意思為當更新 addons 時, 是不是允許重新 import data,</p> <p><code>noupdate=&quot;1&quot;</code></p> <p>假如我們在安裝完 addons 之後, 去刪除 record data, 然後再重新去更新 addons,</p> <p>你會發現你刪除的 data 並沒有被安裝回來 (只能先移除 addons 再重新安裝).</p> <p><code>noupdate=&quot;0&quot;</code></p> <p>假如我們在安裝完 addons 之後, 去刪除 record data, 然後再重新去更新 addons,</p> <p>你會發現你刪除的 data 會被安裝回來.</p> <p><code>id=&quot;ir_rule_demo_expense_user&quot;</code> 第一段為針對 <code>demo_expense_tutorial_group_user</code></p> <p>限制 <code>domain_force</code>, 規則很簡單, 這類的 user 只能看到自己的單子, 也就是</p> <p><code>[(&#39;employee_id.user_id.id&#39;, &#39;=&#39;, user.id)]</code>.</p> <p><code>id=&quot;ir_rule_demo_expense_manager&quot;</code> 針對 <code>demo_expense_tutorial_group_manager</code></p> <p>限制 <code>domain_force</code>, 這邊比較特別 <code>[(1, &#39;=&#39;, 1)]</code>, 代表沒有限制, 也就是全部的單子都</p> <p>可以看到.</p> <p><code>demo</code> 用戶為 User, 所以只能看到自己的單子</p> <p><img src="https://i.imgur.com/dX9QuMj.png" alt="alt tag"></p> <p><code>Admin</code> 用戶為 Manager, 所以能看到全部的單子</p> <p><img src="https://i.imgur.com/CFMsgie.png" alt="alt tag"></p> <h3>odoo 手把手教學 - 如何透過 button 呼叫 view, form - part6</h3> <p>接下來介紹前面跳過的部份, 也就是透過 button 的方式呼叫 view, form,</p> <ul> <li><a href="https://youtu.be/URxuH2HG44Q">Youtube Tutorial - odoo 手把手教學 - 如何透過 button 呼叫 view, form - part6</a></li> </ul> <p><a href="models/models.py">models/models.py</a></p> <p>```python class DemoExpenseTutorial(models.Model): _name = &#39;demo.expense.tutorial&#39; _description = &#39;Demo Expense Tutorial&#39; ......</p> <pre><code>@api.multi def button_sheet_id(self): return { &#39;view_mode&#39;: &#39;form&#39;, &#39;res_model&#39;: &#39;demo.expense.sheet.tutorial&#39;, &#39;res_id&#39;: self.sheet_id.id, &#39;type&#39;: &#39;ir.actions.act_window&#39; } </code></pre> <p>```</p> <p>透過前端呼叫 <code>button_sheet_id</code>, 會回傳屬於它的 sheet_id</p> <p><img src="https://i.imgur.com/gUUgk9g.png" alt="alt tag"></p> <p>點進去會直接進入 sheet 中的 form</p> <p><img src="https://i.imgur.com/lU6P9Oj.png" alt="alt tag"></p> <p><a href="views/view.xml">views/view.xml</a></p> <p>前端的部份就是呼叫 <code>button_sheet_id</code></p> <p><code>xml &lt;record id=&quot;view_form_demo_expense_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Tutorial Form&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;form string=&quot;Demo Expense Tutorial&quot;&gt; &lt;sheet&gt; &lt;div class=&quot;oe_button_box&quot; name=&quot;button_box&quot;&gt; &lt;button class=&quot;oe_stat_button&quot; name=&quot;button_sheet_id&quot; string=&quot;SHEET ID&quot; type=&quot;object&quot; attrs=&quot;{&#39;invisible&#39;:[(&#39;sheet_id&#39;,&#39;=&#39;, False)]}&quot; icon=&quot;fa-bars&quot;/&gt; &lt;/div&gt; ...... &lt;/sheet&gt; &lt;/form&gt; &lt;/field&gt; &lt;/record&gt; </code></p> <p>既然找了 sheet<em>id, 也來做一個反查回來的, 也就是透過 sheet</em>id 找到 <code>demo.expense.tutorial</code>,</p> <p><a href="models/models.py">models/models.py</a></p> <p>```python class DemoExpenseSheetTutorial(models.Model): _name = &#39;demo.expense.sheet.tutorial&#39; _description = &#39;Demo Expense Sheet Tutorial&#39;</p> <pre><code>...... @api.multi def button_line_ids(self): return { &#39;name&#39;: &#39;Demo Expense Line IDs&#39;, &#39;view_type&#39;: &#39;form&#39;, &#39;view_mode&#39;: &#39;tree,form&#39;, &#39;res_model&#39;: &#39;demo.expense.tutorial&#39;, &#39;view_id&#39;: False, &#39;type&#39;: &#39;ir.actions.act_window&#39;, &#39;domain&#39;: [(&#39;sheet_id&#39;, &#39;=&#39;, self.id)], } ...... </code></pre> <p>```</p> <p><code>res_model</code> 為目標的 model <code>demo.expense.tutorial</code>.</p> <p><code>domain</code> 稍微說明一下 <code>[(&#39;sheet_id&#39;, &#39;=&#39;, self.id)],</code>,</p> <p><code>sheet_id</code> 是指目標 model <code>demo.expense.tutorial</code> 的 sheet_id,</p> <p><code>self.id</code> 是指當下 model <code>demo.expense.sheet.tutorial</code> 的 id.</p> <p><img src="https://i.imgur.com/oqDOi3p.png" alt="alt tag"></p> <p>點下去會帶出它的 <code>demo.expense.tutorial</code></p> <p><img src="https://i.imgur.com/2ZSl9Qj.png" alt="alt tag"></p> <p><a href="views/view.xml">views/view.xml</a> 的部份</p> <p><code>xml ...... &lt;record id=&quot;view_form_demo_expense_sheet_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Sheet Tutorial Form&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.sheet.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;form string=&quot;Demo Expense Sheet Tutorial&quot;&gt; &lt;sheet&gt; &lt;div class=&quot;oe_button_box&quot; name=&quot;button_box&quot;&gt; &lt;button class=&quot;oe_stat_button&quot; name=&quot;button_line_ids&quot; string=&quot;SHEET IDs&quot; type=&quot;object&quot; attrs=&quot;{&#39;invisible&#39;:[(&#39;expense_line_ids&#39;,&#39;=&#39;, False)]}&quot; icon=&quot;fa-bars&quot;/&gt; &lt;/div&gt; ...... &lt;/sheet&gt; &lt;/form&gt; &lt;/field&gt; &lt;/record&gt; </code></p> <h3>odoo 手把手教學 - 說明 name<em>get 和 _name</em>search - part7</h3> <p>最後來看 <a href="models/models.py">models/models.py</a> 中比較特殊的部份,</p> <ul> <li><a href="https://youtu.be/g-dclCkwY5c">Youtube Tutorial - odoo 手把手教學 - 說明 name<em>get 和 _name</em>search - part7</a></li> </ul> <p>分別是 <code>name_get</code> 和 <code>_name_search</code>,</p> <p>```python class DemoExpenseSheetTutorial(models.Model): _name = &#39;demo.expense.sheet.tutorial&#39; _description = &#39;Demo Expense Sheet Tutorial&#39;</p> <pre><code>...... @api.multi def name_get(self): names = [] for record in self: name = &#39;%s-%s&#39; % (record.create_date.date(), record.name) names.append((record.id, name)) return names # odoo12/odoo/odoo/addons/base/models/ir_model.py @api.model def _name_search(self, name=&#39;&#39;, args=None, operator=&#39;ilike&#39;, limit=100): if args is None: args = [] domain = args + [&#39;|&#39;, (&#39;id&#39;, operator, name), (&#39;name&#39;, operator, name)] # domain = args + [ (&#39;name&#39;, operator, name)] # domain = args + [ (&#39;id&#39;, operator, name)] return super(DemoExpenseSheetTutorial, self).search(domain, limit=limit).name_get() </code></pre> <p>```</p> <p>首先是 <code>name_get</code></p> <p>這個的功能主要是去修改 name 的名稱, 在這邊我們加上當下的時間</p> <p>(可以依照自己的需求下去修改)</p> <p><img src="https://i.imgur.com/JudV8pW.png" alt="alt tag"></p> <p>Many2one 時也會看到自己定義的 <code>name_get</code></p> <p>注意:exclamation: 這些增加的值是不會儲存進 db 中的, db 中還是儲存的是 name 的內容而已 (概念和 compute field 一樣:smile:)</p> <p><img src="https://i.imgur.com/sC9hNA8.png" alt="alt tag"></p> <p>再來要來說明 <code>_name_search</code>,</p> <p>如果沒有它, 假設我知道某個資料的 id 是 4, 在搜尋的地方打上 id,</p> <p>你會發現找不到資料:joy:</p> <p><img src="https://i.imgur.com/YokDfBf.png" alt="alt tag"></p> <p>但今天如果有了 <code>_name_search</code> 並實作它,</p> <p>你會發現這次你打 id 會才成功找到需要的資料:satisfied:</p> <p>我在 code 中有放幾個範例註解, 大家可以自行玩玩看:smile:</p> <p><img src="https://i.imgur.com/ztUL9Xd.png" alt="alt tag"></p> <h3>odoo 手把手教學 - 使用 python 增加取代 One2many M2X record - part8</h3> <ul> <li><a href="https://youtu.be/GBCGS2znnT8">Youtube Tutorial - odoo 手把手教學 - 使用 python 增加取代 One2many M2X record - part8</a></li> </ul> <p>參考 <a href="models/models.py">models/models.py</a></p> <p>這邊只需要注意3個 function,</p> <p><code>add_demo_expense_record</code> <code>link_demo_expense_record</code> <code>replace_demo_expense_record</code></p> <p>分別對應的 button 為下圖</p> <p>參考 <a href="views/view.xml">views/view.xml</a></p> <p><img src="https://i.imgur.com/8gmMe3j.png" alt="alt tag"></p> <p>```python class DemoExpenseSheetTutorial(models.Model): _name = &#39;demo.expense.sheet.tutorial&#39; _description = &#39;Demo Expense Sheet Tutorial&#39;</p> <pre><code>name = fields.Char(&#39;Expense Demo Report Summary&#39;, required=True) # One2many is a virtual relationship, there must be a Many2one field in the other_model, # and its name must be related_field expense_line_ids = fields.One2many( &#39;demo.expense.tutorial&#39;, # related model &#39;sheet_id&#39;, # field for &quot;this&quot; on related model string=&#39;Expense Lines&#39;) @api.multi def add_demo_expense_record(self): # (0, _ , {&#39;field&#39;: value}) creates a new record and links it to this one. data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_1&#39;) tag_data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_tag_data_1&#39;) tag_data_2 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_tag_data_2&#39;) for record in self: # creates a new record val = { &#39;name&#39;: &#39;test_data&#39;, &#39;employee_id&#39;: data_1.employee_id, &#39;tag_ids&#39;: [(6, 0, [tag_data_1.id, tag_data_2.id])] } self.expense_line_ids = [(0, 0, val)] @api.multi def link_demo_expense_record(self): # (4, id, _) links an already existing record. data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_1&#39;) for record in self: # link already existing record self.expense_line_ids = [(4, data_1.id, 0)] @api.multi def replace_demo_expense_record(self): # (6, _, [ids]) replaces the list of linked records with the provided list. data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_1&#39;) data_2 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_2&#39;) for record in self: # replace multi record self.expense_line_ids = [(6, 0, [data_1.id, data_2.id])] </code></pre> <p>```</p> <p>說明 <code>add_demo_expense_record</code></p> <p>```python ...... @api.multi def add<em>demo</em>expense_record(self): # (0, _ , {&#39;field&#39;: value}) creates a new record and links it to this one.</p> <pre><code>data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_1&#39;) tag_data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_tag_data_1&#39;) tag_data_2 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_tag_data_2&#39;) for record in self: # creates a new record val = { &#39;name&#39;: &#39;test_data&#39;, &#39;employee_id&#39;: data_1.employee_id, &#39;tag_ids&#39;: [(6, 0, [tag_data_1.id, tag_data_2.id])] } self.expense_line_ids = [(0, 0, val)] </code></pre> <p>...... ```</p> <p><code>(0, _ , {&#39;field&#39;: value})</code> 新建一筆 record 並且連接它.</p> <p><code>self.env.ref(......)</code> 這個的用法是去取得既有的資料, 路徑在 <a href="data/demo_expense_tutorial_data.xml">data/demo<em>expense</em>tutorial_data.xml</a>.</p> <p>當你點選按鈕, 下面就會一直新增資料</p> <p><img src="https://i.imgur.com/bUI3vZE.png" alt="alt tag"></p> <p>說明 <code>link_demo_expense_record</code></p> <p>```python ...... @api.multi def link<em>demo</em>expense_record(self): # (4, id, _) links an already existing record.</p> <pre><code>data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_1&#39;) for record in self: # link already existing record self.expense_line_ids = [(4, data_1.id, 0)] </code></pre> <p>......</p> <p>```</p> <p><code>(4, id, _)</code> 連接已經存在的 record.</p> <p>當你點選按鈕, 下面會直接連接一比資料, 如果已經連接就不會有動作,</p> <p><img src="https://i.imgur.com/Qw1VDyU.png" alt="alt tag"></p> <p>說明 <code>replace_demo_expense_record</code></p> <p>```python ...... @api.multi def replace<em>demo</em>expense_record(self): # (6, _, [ids]) replaces the list of linked records with the provided list.</p> <pre><code>data_1 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_1&#39;) data_2 = self.env.ref(&#39;demo_expense_tutorial_v1.demo_expense_tutorial_data_2&#39;) for record in self: # replace multi record self.expense_line_ids = [(6, 0, [data_1.id, data_2.id])] </code></pre> <p>...... ```</p> <p><code>(6, _, [ids])</code> 使用 list 取代既有的 records.</p> <p>當你點選按鈕, 會使用你定義的 list 取代全部的 records.</p> <p><img src="https://i.imgur.com/b30QdSQ.png" alt="alt tag"></p> <h3>odoo 手把手教學 - tree create delete edit False - part9</h3> <ul> <li><a href="https://youtu.be/0fpA89QcYZM">Youtube Tutorial - odoo 手把手教學 - tree create delete edit False - part9</a></li> </ul> <p>通常管理一個使用者可不可以建立 records, 是根據 security 資料夾裡面的檔案,</p> <p>也就是 <code>security.xml</code> <code>ir_rule.xml</code> <code>ir.model.access.csv</code> 主要是這個.</p> <p>記住:exclamation: odoo 可以從 model 層(db層) 或權限下手, 也可以從 view 那層下手,</p> <p>當然, 如果是從安全性的角度來看 從 model 層(db層) 或權限下手 是比較高全的:smile:</p> <p>今天就是要來介紹 從 view 那層下手,</p> <p>增加一個 tree <a href="views/view.xml">views/view.xml</a></p> <p><code>xml ...... &lt;record id=&quot;view_tree_demo_expense_tutorial_no_create&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Tutorial List No Create&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;tree string=&quot;no_create_tree&quot; create=&quot;0&quot; delete=&quot;false&quot; edit=&quot;1&quot; editable=&quot;top&quot;&gt; &lt;field name=&quot;name&quot;/&gt; &lt;field name=&quot;employee_id&quot;/&gt; &lt;/tree&gt; &lt;/field&gt; &lt;/record&gt; ...... </code></p> <p>重點在 <code>&lt;tree string=&quot;no_create_tree&quot; create=&quot;0&quot; delete=&quot;false&quot; edit=&quot;1&quot; editable=&quot;top&quot;&gt;</code></p> <p>這段, 裡面增加了一下 tag, 允許就是 <code>1</code> 或 <code>True</code>, 不允許就是 <code>0</code> 或 <code>False</code>.</p> <p>儘管你有權限建立 records, 如果你設定了 <code>create=&quot;0&quot;</code>, 你還是沒辦法建立 records.</p> <p>也記得在 <a href="views/menu.xml">views/menu.xml</a> 增加 action,</p> <p>並且要指定 <code>view_id</code> (也就是剛剛建立出來的那個)</p> <p><code>xml ...... &lt;!-- Action to open the demo_expense_tutorial_no_craete --&gt; &lt;record id=&quot;action_expense_tutorial_no_craete&quot; model=&quot;ir.actions.act_window&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Tutorial Action No Craete&lt;/field&gt; &lt;field name=&quot;res_model&quot;&gt;demo.expense.tutorial&lt;/field&gt; &lt;field name=&quot;view_type&quot;&gt;form&lt;/field&gt; &lt;field name=&quot;view_mode&quot;&gt;tree&lt;/field&gt; &lt;field name=&quot;view_id&quot; ref=&quot;view_tree_demo_expense_tutorial_no_create&quot;/&gt; &lt;/record&gt; ...... </code></p> <p>你會發現 create delete 的按鈕都消失了</p> <p><img src="https://i.imgur.com/siLhdQ4.png" alt="alt tag"></p> <h3>odoo14 手把手教學 - Active Archive Ribbon 教學 - part10</h3> <ul> <li><a href="https://youtu.be/thhdGK9oebg">Youtube Tutorial - odoo14 手把手教學 - Active Archive Ribbon 教學 - part10</a></li> </ul> <p>在 odoo 中, <code>active</code> 這個字段是有特殊意義的,</p> <p>一般來說, 當你進入 form 的 view 底下, 你會看到這個 action</p> <p><img src="https://i.imgur.com/bjFJDPJ.png" alt="alt tag"></p> <p>如果你在 model 中有了 <code>active</code> fields, 而且有將他放在該 form 底下(儘管你是隱藏)</p> <p>在 odoo14 中, 會自動出現 Archive 的選項</p> <p><img src="https://i.imgur.com/GIsrvd2.png" alt="alt tag"></p> <p>然後, 你也可以再加一個 odoo14 中的 Ribbon</p> <p><img src="https://i.imgur.com/ovupHAk.png" alt="alt tag"></p> <p>寫法如下, 可參考 <a href="views/view.xml">views/view.xml</a></p> <p><code>xml &lt;record id=&quot;view_form_demo_expense_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Tutorial Form&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;form string=&quot;Demo Expense Tutorial&quot;&gt; &lt;sheet&gt; &lt;widget name=&quot;web_ribbon&quot; title=&quot;Archived&quot; bg_color=&quot;bg-danger&quot; attrs=&quot;{&#39;invisible&#39;: [(&#39;active&#39;, &#39;=&#39;, True)]}&quot;/&gt; ...... &lt;group&gt; ...... &lt;field name=&quot;active&quot; invisible=&quot;1&quot;/&gt; &lt;/group&gt; &lt;/sheet&gt; &lt;/form&gt; &lt;/field&gt; &lt;/record&gt; </code></p> <h3>odoo14 手把手教學 - Search Panel 教學 - part11</h3> <ul> <li><a href="https://youtu.be/tZ6_2Q3r3Ok">Youtube Tutorial - odoo14 手把手教學 - Search Panel 教學 - part11</a></li> </ul> <p>在 odoo14 中, 原生就多了 Search Panel 可以使用, 今天就來看看這個東西:smile:</p> <p>下圖就是所謂的 Search Panel</p> <p><img src="https://i.imgur.com/V5YA2wJ.png" alt="alt tag"></p> <p>寫法也不難, 就是在 search 底下再加上 searchpanel, 可參考 <a href="views/view.xml">views/view.xml</a></p> <p><code>xml ...... &lt;record id=&quot;view_filter_demo_expense_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Expense Tutorial Filter&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.expense.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;search string=&quot;Demo Expense Tutorial Filter&quot;&gt; ...... &lt;searchpanel&gt; &lt;field name=&quot;employee_id&quot; select=&quot;multi&quot; icon=&quot;fa-building&quot; enable_counters=&quot;1&quot;/&gt; &lt;field name=&quot;sheet_id&quot; icon=&quot;fa-users&quot; enable_counters=&quot;1&quot;/&gt; &lt;field name=&quot;gender&quot; icon=&quot;fa-cutlery&quot; enable_counters=&quot;1&quot; color=&quot;#d10202&quot;/&gt; &lt;!-- error example --&gt; &lt;!-- &lt;field name=&quot;tag_ids&quot; icon=&quot;fa-users&quot;/&gt; --&gt; &lt;/searchpanel&gt; &lt;/search&gt; &lt;/field&gt; &lt;/record&gt; ...... </code></p> <p><code>select=&quot;multi&quot;</code> 是否可以多選.</p> <p><code>icon=&quot;fa-building&quot;</code> icon 圖示顯示.</p> <p><code>enable_counters=&quot;1&quot;</code> 是否顯示數量.</p> <p><code>color=&quot;#d10202&quot;</code> color 顯示.</p> <p>另外, 註解的部份只是和大家說明, many2many 是不支援的, 在 odoo14 中, 錯誤訊息愈來愈清楚了,</p> <p>如果你把註解打開, 更新後你會看到這個錯誤訊息</p> <p><img src="https://i.imgur.com/QWQ8Z76.png" alt="alt tag"></p> <p>另外, 放在 searchpanel 裡的東西必須是 <code>store=True</code>, 否則也會錯誤.(但 odoo 也會提醒你)</p> <p>可參考 <a href="models/models.py">models/models.py</a></p> <p><code>python gender = fields.Selection(string=&#39;Gender&#39;, related=&#39;employee_id.gender&#39;, store=True) </code></p> <h3>odoo14 手把手教學 - auto_join 說明</h3> <ul> <li><a href="https://youtu.be/OOlPZETkYKw">Youtube Tutorial - odoo14 手把手教學 - auto_join 說明</a></li> </ul> <p>今天要來介紹在 <code>Many2one</code> <code>One2many</code> <code>Many2many</code> 中有個參數是 <code>auto_join</code>,</p> <p>預設都是 False, 可參考 soucecode 中的 <code>odoo/fields.py</code></p> <p>```python</p> <p>class Many2one(<em>Relational): ...... type = &#39;many2one&#39; column</em>type = (&#39;int4&#39;, &#39;int4&#39;)</p> <pre><code>ondelete = None # what to do when value is deleted auto_join = False # whether joins are generated upon search delegate = False # whether self implements delegation </code></pre> <p>...... ```</p> <p><code>auto_join</code> 的主要功能就是允許 ORM 使用 join 的方式撈資料(用的好的話效能會更好:smile:)</p> <p>使用方法也很簡單, 直接加上 <code>auto_join=True</code> 即可</p> <p>```python class DemoExpenseTutorial(models.Model): _name = &#39;demo.expense.tutorial&#39; _description = &#39;Demo Expense Tutorial&#39; _order = &quot;sequence, id desc&quot;</p> <pre><code>name = fields.Char(&#39;Description&#39;, required=True) sheet_id = fields.Many2one(&#39;demo.expense.sheet.tutorial&#39;, string=&quot;Expense Report&quot;, ondelete=&quot;cascade&quot;, auto_join=True) </code></pre> <p>......</p> <p>class DemoExpenseSheetTutorial(models.Model): _name = &#39;demo.expense.sheet.tutorial&#39; _description = &#39;Demo Expense Sheet Tutorial&#39;</p> <pre><code>name = fields.Char(&#39;Expense Demo Report Summary&#39;, required=True) # One2many is a virtual relationship, there must be a Many2one field in the other_model, # and its name must be related_field expense_line_ids = fields.One2many( &#39;demo.expense.tutorial&#39;, # related model &#39;sheet_id&#39;, # field for &quot;this&quot; on related model string=&#39;Expense Lines&#39;) </code></pre> <p>..... ```</p> <p>至於要怎麼樣看到 <code>auto_join</code> 的變化, 就需要從 RAW SQL下去觀察:exclamation:</p> <p>可參考之前的教學 <a href="https://github.com/twtrubiks/odoo-docker-tutorial#odoo---%E5%A6%82%E4%BD%95%E9%80%8F%E9%81%8E-log_level-%E4%BA%86%E8%A7%A3-orm-raw-sql">odoo 手把手教學 - 如何透過 log_level 了解 ORM RAW SQL</a></p> <p>現在我們先來看 <code>auto_join=False</code> 的情況</p> <p><code>python sheet_id = fields.Many2one(&#39;demo.expense.sheet.tutorial&#39;, string=&quot;Expense Report&quot;, ondelete=&quot;cascade&quot;, auto_join=False) </code></p> <p>使用 shell 執行</p> <p><code>pythom self.env[&#39;demo.expense.tutorial&#39;].search([(&#39;sheet_id.name&#39;, &#39;=&#39;, &#39;111&#39;)]) </code></p> <p>你可以發現他是使用 Subquery 的方式下去撈資料</p> <p><code>sql SELECT &quot;demo_expense_tutorial&quot;.id FROM &quot;demo_expense_tutorial&quot; WHERE ((&quot;demo_expense_tutorial&quot;.&quot;active&quot; = TRUE) AND (&quot;demo_expense_tutorial&quot;.&quot;sheet_id&quot; in (SELECT &quot;demo_expense_sheet_tutorial&quot;.id FROM &quot;demo_expense_sheet_tutorial&quot; WHERE (&quot;demo_expense_sheet_tutorial&quot;.&quot;name&quot; = &#39;111&#39;) ORDER BY &quot;demo_expense_sheet_tutorial&quot;.&quot;id&quot;))) ORDER BY &quot;demo_expense_tutorial&quot;.&quot;id&quot; </code></p> <p>接著來看 <code>auto_join=True</code> 的情況</p> <p><code>python sheet_id = fields.Many2one(&#39;demo.expense.sheet.tutorial&#39;, string=&quot;Expense Report&quot;, ondelete=&quot;cascade&quot;, auto_join=True) </code></p> <p>使用 shell 執行</p> <p><code>pythom self.env[&#39;demo.expense.tutorial&#39;].search([(&#39;sheet_id.name&#39;, &#39;=&#39;, &#39;111&#39;)]) </code></p> <p>你可以發現他是使用 Left join 的方式下去撈資料</p> <p><code>sql SELECT &quot;demo_expense_tutorial&quot;.id FROM &quot;demo_expense_tutorial&quot; LEFT JOIN &quot;demo_expense_sheet_tutorial&quot; AS &quot;sheet&quot; ON (&quot;demo_expense_tutorial&quot;.&quot;sheet_id&quot; = &quot;sheet&quot;.&quot;id&quot;) WHERE ((&quot;demo_expense_tutorial&quot;.&quot;active&quot; = TRUE) AND (&quot;sheet&quot;.&quot;name&quot; = &#39;111&#39;)) ORDER BY &quot;demo_expense_tutorial&quot;.&quot;id&quot; </code></p> <p>另外, 在 odoo12 的時候有相關的 issues 可參考 <a href="https://github.com/odoo/odoo/issues/25175">auto_join incorrect results...</a></p> <p>簡單說就是在 odoo12 中的 <code>auto_join</code> 是使用 inner join 的方法, 而現在已經改成了 left join:smile:</p> <p>在 odoo12 中如果你是用 <code>auto_join=True</code> 並且執行以下的 ORM 會有 bug</p> <p><code>python self.env[&#39;demo.expense.tutorial&#39;].search([&#39;|&#39;, (&#39;sheet_id.name&#39;, &#39;=&#39;, &#39;111&#39;), (&#39;sheet_id&#39;, &#39;=&#39;, False)]) </code></p> <p>在 odoo12 中你會撈不出 <code>(&#39;sheet_id&#39;, &#39;=&#39;, False)</code>, 原因是因為他是使用 inner join 的概念.</p>