Search 1.9 billion lines of Odoo code on GitHub

demo_odoo_tutorial

Author: My Company
License: no license
Branch: 14.0
Repository: twtrubiks/odoo-demo-addons-tutorial
Dependencies: base, mail, and web
Languages: Markdown (440, 67.9%), Python (68, 10.5%), and XML (140, 21.6%)
Other branches: master

<h1>odoo 手把手建立第一個 addons</h1> <ul> <li><p><a href="https://youtu.be/GMrPakLNh8g">Youtube Tutorial - odoo 手把手建立第一個 addons - part1</a> - 介紹 model - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial#%E4%BB%8B%E7%B4%B9-model">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/EnD-VxuILWM">Youtube Tutorial - odoo 手把手建立第一個 addons - part2</a> - 介紹 security, menu, tree, form - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial#%E4%BB%8B%E7%B4%B9-security-menu-tree-form">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/25MSbidCf1U">Youtube Tutorial - odoo 手把手建立第一個 addons - part3</a> - 介紹 report, controller - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial#%E4%BB%8B%E7%B4%B9-report-controller">文章快速連結</a></p></li> <li><p><a href="https://youtu.be/xTezPfJAJ_Q">Youtube Tutorial - 說明 odoo manifest 中的 auto_install</a> - <a href="https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial#%E8%AA%AA%E6%98%8E-odoo-manifest-%E4%B8%AD%E7%9A%84-auto_install">文章快速連結</a></p></li> </ul> <p>建議觀看影片, 會更清楚:smile:</p> <p>以下將介紹這個 addons 的結構</p> <h2>說明</h2> <h3>介紹 model</h3> <ul> <li><a href="https://youtu.be/GMrPakLNh8g">Youtube Tutorial - odoo 手把手建立第一個 addons - part1</a> - 介紹 model</li> </ul> <p>首先是 <code>__manifest__.py</code>, 比較重要的是 <code>depends</code>,</p> <p>```python { &#39;name&#39;: &quot;demo odoo tutorial&quot;, ......</p> <pre><code># any module necessary for this one to work correctly &#39;depends&#39;: [&#39;base&#39;, &#39;mail&#39;], # always loaded &#39;data&#39;: [ &#39;security/security.xml&#39;, &#39;security/ir.model.access.csv&#39;, &#39;data/data_demo_odoo.xml&#39;, &#39;views/menu.xml&#39;, &#39;views/view.xml&#39;, &#39;reports/report.xml&#39;, &#39;views/demo_odoo_template.xml&#39;, ], ...... </code></pre> <p>}</p> <p>```</p> <p>在 odoo 的世界中, 一定會看到某個 addons 依賴 xxx addons, 想簡單一點,</p> <p>你可以把它想成是模組化(方便管理), <code>data</code> 的部份我等等再回來介紹.</p> <p>看 <a href="models">models</a> 資料夾, 裡面有 <code>__init__.py</code> 和 <code>models.py</code>,</p> <p><code>__init__.py</code> 單純就是 import <code>models.py</code> 而已.</p> <p><code>models.py</code> 這邊就很重要了</p> <p><code>python class DemoOdooTutorial(models.Model): _name = &#39;demo.odoo.tutorial&#39; _description = &#39;Demo Odoo Tutorial&#39; _inherit = [&#39;mail.thread&#39;, &#39;mail.activity.mixin&#39;] # track_visibility </code></p> <p>odoo 中的 model 主要有幾個, 分別是 AbstractModel、Model、TransientModel,</p> <p>最基本的 BaseModel, 其實 BaseModel = AbstractModel,</p> <p><a href="https://www.odoo.com/documentation/13.0/reference/orm.html#abstractmodel">https://www.odoo.com/documentation/13.0/reference/orm.html#abstractmodel</a></p> <p>```python @pycompat.implements<em>to</em>string class BaseModel(MetaModel(&#39;DummyModel&#39;, (object,), {&#39;_register&#39;: False})): &quot;&quot;&quot; Base class for Odoo models.</p> <pre><code>Odoo models are created by inheriting: * :class:`Model` for regular database-persisted models * :class:`TransientModel` for temporary data, stored in the database but automatically vacuumed every so often * :class:`AbstractModel` for abstract super classes meant to be shared by multiple inheriting models The system automatically instantiates every model once per database. Those instances represent the available models on each database, and depend on which modules are installed on that database. The actual class of each instance is built from the Python classes that create and inherit from the corresponding model. Every model instance is a &quot;recordset&quot;, i.e., an ordered collection of records of the model. Recordsets are returned by methods like :meth:`~.browse`, :meth:`~.search`, or field accesses. Records have no explicit representation: a record is represented as a recordset of one record. To create a class that should not be instantiated, the _register class attribute may be set to False. &quot;&quot;&quot; .... </code></pre> <p>```</p> <p>今天只會先提到 Model, <code>Model</code> 繼承自 AbstractModel</p> <p>```python class Model(AbstractModel): &quot;&quot;&quot; Main super-class for regular database-persisted Odoo models.</p> <pre><code>Odoo models are created by inheriting from this class:: class user(Model): ... The system will later instantiate the class once per database (on which the class&#39; module is installed). &quot;&quot;&quot; _auto = True # automatically create database backend _register = False # not visible in ORM registry, meant to be python-inherited only _abstract = False # not abstract _transient = False # not transient </code></pre> <p>```</p> <p><code>_auto = True</code> 會自動在 db 中建立 table.</p> <p><code>_name</code> 為 model 的名稱, 請注意幾件事情, model 名稱建議都使用單數, 然後不要使用 <code>_</code> 分隔名稱,</p> <p>請使用 <code>.</code> 像是範例中的 <code>demo.odoo.tutorial</code> (在 db 中, table 名稱會顯示 <code>demo_odoo_tutorial</code>, 如下圖)</p> <p><img src="https://i.imgur.com/s6ngYGo.png" alt="alt tag"></p> <p><code>_inherit</code> 在 odoo 中不管是 model 還是 view, 甚至是權限, 都會使用繼承 (這邊先知道這樣即可:smile:).</p> <p>再來說明 field</p> <p>```python ...... name = fields.Char(&#39;Description&#39;, required=True)</p> <pre><code># track_visibility=&#39;always&#39; 和 track_visibility=&#39;onchange&#39; is_done_track_onchange = fields.Boolean( string=&#39;Is Done?&#39;, default=False, track_visibility=&#39;onchange&#39;) name_track_always = fields.Char(string=&quot;track_name&quot;, track_visibility=&#39;always&#39;) start_datetime = fields.Datetime(&#39;Start DateTime&#39;, default=fields.Datetime.now()) stop_datetime = fields.Datetime(&#39;End Datetime&#39;) field_onchange_demo = fields.Char(&#39;onchange_demo&#39;) field_onchange_demo_set = fields.Char(&#39;onchange_demo_set&#39;, readonly=True) # float digits # field tutorial input_number = fields.Float(string=&#39;input number&#39;, digits=(10,3)) ...... </code></pre> <p>```</p> <p><code>track_visibility</code> 為追蹤值的改變, 這也是為甚麼要繼承 <code>mail.thread</code> 以及 <code>mail.activity.mixin</code> 的原因,</p> <p>如果你有修改值, 會紀錄改變(如下圖),</p> <p><img src="https://i.imgur.com/XjCwGHQ.png" alt="alt tag"></p> <p><code>start_datetime</code> field 有 default, 設定為當天的時間,</p> <p>當建立一筆資料時, 會顯示當下的時間,</p> <p><img src="https://i.imgur.com/VYPAz9S.png" alt="alt tag"></p> <p><code>field_onchange_demo_set</code> field 中的 <code>readonly=True</code>,</p> <p>你可以發現是無法修改的 (可能是根據其他欄位透過 code 改變它的值)</p> <p><img src="https://i.imgur.com/1A5RIDH.png" alt="alt tag"></p> <p><code>input_number</code> Float field 中的 digits 為設定進位以及小數點, 像這邊是算到小數點第3位並使用10進位</p> <p><img src="https://i.imgur.com/0ZXafMi.png" alt="alt tag"></p> <p>下一部份的 code</p> <p>```python ...... field<em>compute</em>demo = fields.Integer(compute=&quot;<em>get</em>field_compute&quot;) # readonly</p> <pre><code>_sql_constraints = [ (&#39;name_uniq&#39;, &#39;unique(name)&#39;, &#39;Description must be unique&#39;), ] @api.constrains(&#39;start_datetime&#39;, &#39;stop_datetime&#39;) def _check_date(self): for data in self: if data.start_datetime &gt; data.stop_datetime: raise ValidationError( &quot;data.stop_datetime &gt; data.start_datetime&quot; ) @api.depends(&#39;input_number&#39;) def _get_field_compute(self): for data in self: data.field_compute_demo = data.input_number * 1000 @api.onchange(&#39;field_onchange_demo&#39;) def onchange_demo(self): if self.field_onchange_demo: self.field_onchange_demo_set = &#39;set {}&#39;.format(self.field_onchange_demo) </code></pre> <p>......</p> <p>```</p> <p><code>field_compute_demo</code> field 為 compute field, compute field 預設為 readonly,</p> <p>而且這個 field 預設是不會存在 db 中的 (<code>store=False</code>, 也就是每次都是計算出來的),</p> <p>如果想要將值保存在 db 中 , 需再加上 <code>store=True</code> ,</p> <p><code>compute</code> 為 <code>_get_field_compute</code>, 透過 <code>@api.depends</code> 裝飾器的幫忙,</p> <p>這邊會根據 <code>input_number</code> field 的值 * 1000 之後,</p> <p>將值餵給 <code>field_compute_demo</code>.</p> <p><img src="https://i.imgur.com/FQOPZTH.png" alt="alt tag"></p> <p><code>_sql_constraints</code> 這個為設定一些限制(直接寫 postgresql),</p> <p>避免不允許(錯誤)的資料進入 db,</p> <p>像這邊設定 <code>name</code> field 必須為 unique,</p> <p>假如你有重複的 <code>name</code>, 系統就會提醒你(如下圖),</p> <p><img src="https://i.imgur.com/4fOtkpJ.png" alt="alt tag"></p> <p><code>def _check_date(self)</code> 這段是另一種方式限制, 透過 <code>@api.constrains</code> 裝飾器的幫忙,</p> <p>這邊限制了 <code>start_datetime</code> 必須大於 <code>stop_datetime</code>, 否則會出現 error,</p> <p><img src="https://i.imgur.com/PtSLNjx.png" alt="alt tag"></p> <p><code>def onchange_demo(self)</code> 這個則是使用了 <code>@api.onchange</code> 裝飾器的幫忙,</p> <p>主要是根據 <code>field_onchange_demo</code> 的改變, 將值餵給 <code>field_onchange_demo_set</code>,</p> <p>注意 view 中要有 <code>force_save=&quot;1&quot;</code>, 否則儲存時會消失.</p> <p><img src="https://i.imgur.com/5Iq4Rgb.png" alt="alt tag"></p> <p>你可能會發現 <code>@api.depends</code> 和 <code>@api.onchange</code> 幾乎一樣,</p> <p>其實主要區分兩個比較容易的方法, 就是 <code>@api.depends</code> 可以使用在 <code>related</code> 欄位,</p> <p>像是之後會介紹的 <code>Many2one</code> <code>Many2many</code> <code>One2many</code> 之類的.</p> <p>而 <code>@api.onchange</code> 只能使用在同一個 model 上.</p> <h3>介紹 security, menu, tree, form</h3> <ul> <li><a href="https://youtu.be/EnD-VxuILWM">Youtube Tutorial - odoo 手把手建立第一個 addons - part2</a> - 介紹 security, menu, tree, form</li> </ul> <p>接下來來看 <a href="security">security</a> 這個很重要的資料夾, 既然有了 model,</p> <p>這樣要如何控制誰有權限讀寫修改刪除呢:question:</p> <p>就是依靠 <code>ir.model.access.csv</code> 和 <code>security.xml</code> 這個檔案:exclamation:</p> <p><code>security.xml</code></p> <p>```xml <record id="module_demo_odoo_tutorial" model="ir.module.category"> <field name="name">Demo odoo tutorial category</field> </record></p> <p><record id="demo_odoo_tutorial_group_user" model="res.groups"> <field name="name">User</field> <field name="category_id" ref="module_demo_odoo_tutorial"/> <field name="implied_ids" eval="[(4, ref('base.group_user'))]"/> </record></p> <p><record id="demo_odoo_tutorial_group_manager" model="res.groups"> <field name="name">Manager</field> <field name="category_id" ref="module_demo_odoo_tutorial"/> <field name="implied_ids" eval="[(4, ref('demo_odoo_tutorial_group_user'))]"/> <field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/> </record> ```</p> <p>通常會先建立一個 category, 然後建立兩個 group, 分別是 User 和 Manager,</p> <p>這邊使用線性的繼承方式, 也就是 Manager 擁有 User 一切的權限.</p> <p><code>implied_ids</code> 也就是繼承, 裡面的數字分別代表不同的意思,</p> <p>```text</p> <p>(0, _ , {&#39;field&#39;: value}) creates a new record and links it to this one. (1, id, {&#39;field&#39;: value}) updates the values on an already linked record. (2, id, _) removes the link to and deletes the id related record. (3, id, _) removes the link to, but does not delete, the id related record. This is usually what you will use to delete related records on many-to-many fields. (4, id, _) links an already existing record. (5, _, _) removes all the links, without deleting the linked records. (6, _, [ids]) replaces the list of linked records with the provided list. ```</p> <p><code>_</code> 也可以改成 <code>0</code> or <code>False</code>,</p> <p>尾巴不相關的可以被, 像是 <code>(4, id, _)</code> 也可以寫成 <code>(4, id)</code>.</p> <p><code>ir.model.access.csv</code> 為管理 user 和 manager CRUD 的權限,</p> <p><code>csv id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_demo_odoo_user,Demo Odoo Tutorial User Access,model_demo_odoo_tutorial,demo_odoo_tutorial_group_user,1,0,0,0 access_demo_odoo_manager,Demo Odoo Tutorial Manager Access,model_demo_odoo_tutorial,demo_odoo_tutorial_group_manager,1,1,1,1 </code></p> <p>比較需要注意的地方是 model_id 的部份, 像這邊的 model 為 <code>demo.odoo.tutorial</code>,</p> <p>但這邊必須填入 <code>model_demo_odoo_tutorial</code>, 規則很簡單, 就是要前面要補上 <code>model</code>,</p> <p>然後將全部的 <code>.</code> 改成 <code>_</code> .</p> <p>到 user 中可以切換 group,</p> <p><img src="https://i.imgur.com/CclfzfB.png" alt="alt tag"></p> <p>接著來看 <a href="views">views</a> 資料夾,</p> <p>先看 <code>menu.xml</code></p> <p>```xml &lt;!-- demo<em>odoo</em>tutorial App Menu --&gt; <menuitem id="demo_odoo_tutorial_menu" name="Demo Odoo Tutorial" /></p> <pre><code>&lt;!-- Action to open the demo_odoo_tutorial --&gt; &lt;act_window id=&quot;action_odoo_tutorial&quot; name=&quot;Demo Odoo Tutorial Action&quot; res_model=&quot;demo.odoo.tutorial&quot; view_mode=&quot;tree,form&quot;/&gt; &lt;!-- Menu item to open the demo_odoo_tutorial --&gt; &lt;menuitem id=&quot;menu_odoo_tutorial&quot; name=&quot;Demo Odoo Tutorial&quot; action=&quot;action_odoo_tutorial&quot; parent=&quot;demo_odoo_tutorial_menu&quot; /&gt; </code></pre> <p>```</p> <p>建立一個 menuitem, 然後去定義它的 Action, Action 中比較重要的是 <code>res_model</code> 和 <code>view_mode</code>,</p> <p><code>res_model</code> 就填入對應的 model, <code>view_mode</code> 先簡單填入 tree 和 form,</p> <p>在 odoo 中有很多 view, 像是 pivot kanban 之類的.</p> <p>再來看 <code>view.xml</code>,</p> <p>這邊指定兩個最簡單的,</p> <p>首先是 tree, 記得將對應的 model 填進去,</p> <p><code>xml ...... &lt;record id=&quot;view_tree_demo_odoo_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Odoo Tutorial List&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.odoo.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;tree&gt; &lt;field name=&quot;name&quot;/&gt; &lt;field name=&quot;name_track_always&quot;/&gt; &lt;field name=&quot;is_done_track_onchange&quot;/&gt; &lt;field name=&quot;start_datetime&quot;/&gt; &lt;field name=&quot;stop_datetime&quot;/&gt; &lt;/tree&gt; &lt;/field&gt; &lt;/record&gt; ...... </code></p> <p>tree 如下</p> <p><img src="https://i.imgur.com/Kz2iniQ.png" alt="alt tag"></p> <p>接著是 form, 記得將對應的 model 填進去,</p> <p>(如果都沒寫, 系統會自己產生對應的 form view, 但很醜:sob:)</p> <p><code>xml ...... &lt;record id=&quot;view_form_demo_odoo_tutorial&quot; model=&quot;ir.ui.view&quot;&gt; &lt;field name=&quot;name&quot;&gt;Demo Odoo Tutorial Form&lt;/field&gt; &lt;field name=&quot;model&quot;&gt;demo.odoo.tutorial&lt;/field&gt; &lt;field name=&quot;arch&quot; type=&quot;xml&quot;&gt; &lt;form string=&quot;Demo Odoo Tutorial&quot;&gt; &lt;sheet&gt; &lt;group&gt; &lt;field name=&quot;name&quot;/&gt; &lt;field name=&quot;name_track_always&quot;/&gt; &lt;field name=&quot;is_done_track_onchange&quot;/&gt; &lt;field name=&quot;start_datetime&quot;/&gt; &lt;field name=&quot;stop_datetime&quot;/&gt; &lt;field name=&quot;field_onchange_demo&quot;/&gt; &lt;field name=&quot;field_onchange_demo_set&quot; force_save=&quot;1&quot;/&gt; &lt;field name=&quot;input_number&quot;/&gt; &lt;field name=&quot;field_compute_demo&quot;/&gt; &lt;/group&gt; &lt;/sheet&gt; &lt;div class=&quot;oe_chatter&quot;&gt; &lt;field name=&quot;message_follower_ids&quot; widget=&quot;mail_followers&quot;/&gt; &lt;field name=&quot;activity_ids&quot; widget=&quot;mail_activity&quot;/&gt; &lt;field name=&quot;message_ids&quot; widget=&quot;mail_thread&quot;/&gt; &lt;/div&gt; &lt;/form&gt; &lt;/field&gt; &lt;/record&gt; ...... </code></p> <p>form 如下</p> <p><img src="https://i.imgur.com/vuQd9Bx.png" alt="alt tag"></p> <p>請注意最後一段的 <code>message_follower_ids</code> <code>activity_ids</code> <code>message_ids</code>,</p> <p>這並不是我們所建立的 field, 而是繼承 <code>mail.thread</code> <code>mail.activity.mixin</code> 所擁有的,</p> <p>這段 code 主要是產生這個區塊</p> <p><img src="https://i.imgur.com/FOPV6i5.png" alt="alt tag"></p> <p>最後回到 <code>__manifest__.py</code> 中, 記得將對應的路徑填入 <code>data</code> 中,</p> <p>```python</p> <h1>always loaded</h1> <pre><code>&#39;data&#39;: [ &#39;security/security.xml&#39;, &#39;security/ir.model.access.csv&#39;, &#39;data/data_demo_odoo.xml&#39;, &#39;views/menu.xml&#39;, &#39;views/view.xml&#39;, &#39;reports/report.xml&#39;, &#39;views/demo_odoo_template.xml&#39;, ], </code></pre> <p>```</p> <p>接著來看 <a href="data/data_demo_odoo.xml">data/data<em>demo</em>odoo.xml</a>,</p> <p>```xml <record id="demo_odoo_1" model="demo.odoo.tutorial"> <field name="name">demo<em>odoo</em>1</field> <field name="name_track_always">demo<em>name</em>track<em>always</em>1</field> <field name="is_done_track_onchange">True</field> </record></p> <p><record id="demo_odoo_2" model="demo.odoo.tutorial"> <field name="name">demo<em>odoo</em>2</field> <field name="name_track_always">demo<em>name</em>track<em>always</em>2</field> <field name="is_done_track_onchange">True</field> </record> ```</p> <p>這邊做的事情就是當你安裝了 addons, 它會預設幫你建立一些相關的資料.</p> <p>注意:exclamation: 它和 <a href="demo/demo.xml">demo/demo.xml</a> 資料夾不太一樣, demo 資料夾是當你有勾選</p> <p>產生 demo 資料時, 你安裝 addons 會自動產生 demo data (如下圖).</p> <p><img src="https://i.imgur.com/LbcOiJL.png" alt="alt tag"></p> <h3>介紹 report, controller</h3> <ul> <li><a href="https://youtu.be/25MSbidCf1U">Youtube Tutorial - odoo 手把手建立第一個 addons - part3</a> - 介紹 report, controller</li> </ul> <p>再來是報表的部份 <a href="reports/report.xml">reports/report.xml</a>,</p> <p>這邊是定義 report 的 template,</p> <p><code>t-as=&quot;o&quot;</code> 你可以定義你喜歡的變數</p> <p>```xml <template id="report_demo_odoo_tutorial"> <t t-call="web.html_container"> <t t-foreach="docs" t-as="o"> <t t-call="web.external_layout"> <div class="page"> <h2>Odoo Report</h2> <div> <strong>Name:</strong> <p t-field="o.name"/> </div> <div> <strong>Name<em>track</em>always:</strong> <p t-field="o.name_track_always"/> </div> </div> </t> </t> </t> </template></p> <pre><code>...... </code></pre> <p>```</p> <p>後面這段則是定義 report 的檔名, report_type, 指定 model</p> <p><code>xml &lt;report id=&quot;action_report_demo&quot; string=&quot;Demo Report&quot; model=&quot;demo.odoo.tutorial&quot; report_type=&quot;qweb-pdf&quot; name=&quot;demo_odoo_tutorial.report_demo_odoo_tutorial&quot; file=&quot;demo_odoo_tutorial.report_demo_odoo_tutorial&quot; print_report_name=&quot;&#39;Demo Report - %s&#39; % ((object.name).replace(&#39;/&#39;, &#39;&#39;))&quot; /&gt; </code></p> <p>記得要將路徑填入 <code>__manifest__.py</code></p> <p><code>python &#39;data&#39;: [ ...... &#39;reports/report.xml&#39;, ...... ], </code></p> <p>會顯示在這邊</p> <p><img src="https://i.imgur.com/nHf4bxy.png" alt="alt tag"></p> <p>報表如下</p> <p><img src="https://i.imgur.com/VoY55io.png" alt="alt tag"></p> <p>再來是 <a href="controllers">controllers</a> 這個資料夾,</p> <p>如果你學過 Django,Flask 你會發現蠻像的:smile:</p> <p>因為就是定義 route , 然後撈資料, 最後回傳到對應的 view,</p> <p>(記得要將 controller 填入 <code>__init__.py</code> 中)</p> <p><a href="controllers/controllers.py">controllers/controllers.py</a></p> <p>```python class DemoOdoo(http.Controller):</p> <pre><code>@http.route(&#39;/demo/odoo&#39;, auth=&#39;user&#39;) def list(self, **kwargs): obj = http.request.env[&#39;demo.odoo.tutorial&#39;] objs = obj.search([]) return http.request.render( &#39;demo_odoo_tutorial.demo_odoo_template&#39;,{&#39;objs&#39;: objs}) </code></pre> <p>```</p> <p>至於它的 view, 在 <a href="views/demo_odoo_template.xml">views/demo<em>odoo</em>template.xml</a></p> <p><code>xml &lt;template id=&quot;demo_odoo_template&quot; name=&quot;Demo odoo List&quot;&gt; &lt;div id=&quot;wrap&quot; class=&quot;container&quot;&gt; &lt;h1&gt;Demo Odoo&lt;/h1&gt; &lt;t t-foreach=&quot;objs&quot; t-as=&quot;obj&quot;&gt; &lt;div class=&quot;row&quot;&gt; &lt;span t-field=&quot;obj.name&quot; /&gt;, &lt;span t-field=&quot;obj.is_done_track_onchange&quot; /&gt;, &lt;span t-field=&quot;obj.name_track_always&quot; /&gt; &lt;/div&gt; &lt;/t&gt; &lt;/div&gt; &lt;/template&gt; </code></p> <p>route 我們定義是 <code>@http.route(&#39;/demo/odoo&#39;, auth=&#39;user&#39;)</code>,</p> <p><code>auth=&#39;user&#39;</code> 代表要登入才可以觀看, 所以只要瀏覽 <a href="http://0.0.0.0:8069/demo/odoo/">http://0.0.0.0:8069/demo/odoo/</a></p> <p>就會看到下圖,</p> <p><img src="https://i.imgur.com/kHYQhGR.png" alt="alt tag"></p> <h3>說明 odoo manifest 中的 auto_install</h3> <ul> <li><a href="https://youtu.be/xTezPfJAJ_Q">Youtube Tutorial - 說明 odoo manifest 中的 auto_install</a></li> </ul> <p>特別說明一下 <code>__manifest__.py</code> 裡的 <code>auto_install</code>,</p> <p><code>python &#39;installable&#39;: True, &#39;auto_install&#39;: False, &#39;application&#39;: True, </code></p> <p><code>auto_install</code></p> <p>這個值很重要, 如果你不懂, 建議設定 <code>False</code>, 原因是假如你設定為 <code>True</code>,</p> <p>它會找到你路徑的全部 addons 中的 <code>__manifest__.py</code> 裡找 depends,</p> <p>你其實可以把他想成是一種反向的依賴, 很容易不小心被它雷到:scream:</p> <p>舉個例子來看這個問題, 當你安裝 <code>hr_expense</code> addons 時, <code>sale_expense</code> addons 會自動被安裝起來 :exclamation::exclamation:</p> <p><code>hr_expense</code> addons 看不到相關 depends,</p> <p><img src="https://i.imgur.com/NW5efUr.png" alt="alt tag"></p> <p><code>sale_expense</code> addons 可以看到相關 depends</p> <p><img src="https://i.imgur.com/i5N52OT.png" alt="alt tag"></p> <p>也就是當你安裝 <code>hr_expense</code> 時, 因為 <code>sale_expense</code> 裡的 <code>&#39;auto_install&#39;: True</code>,</p> <p>所以自動會把 <code>sale_expense</code> 裝起來.</p> <p>以下為 <code>sale_expense</code> 的 <code>__manifest__.py</code></p> <p>```python { &#39;name&#39;: &#39;Sales Expense&#39;, ...... &#39;depends&#39;: [&#39;sale<em>management&#39;, &#39;hr</em>expense&#39;], ...... &#39;auto_install&#39;: True, }</p> <p>```</p>