{"id":20619780,"url":"https://github.com/twtrubiks/odoo-demo-addons-tutorial","last_synced_at":"2025-04-05T00:07:57.772Z","repository":{"id":42111199,"uuid":"213945131","full_name":"twtrubiks/odoo-demo-addons-tutorial","owner":"twtrubiks","description":"學習 odoo , 本文章會持續更新","archived":false,"fork":false,"pushed_at":"2024-10-29T11:17:32.000Z","size":299,"stargazers_count":134,"open_issues_count":0,"forks_count":59,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-29T13:17:23.407Z","etag":null,"topics":["addons","demo","erp","odoo","odoo12","odoo14","odoo15","odoo16","postgresql","python3","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twtrubiks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-09T14:47:53.000Z","updated_at":"2024-10-25T11:14:57.000Z","dependencies_parsed_at":"2024-11-07T12:26:29.428Z","dependency_job_id":"95dbedc5-02ca-4565-8375-bfd870d4938a","html_url":"https://github.com/twtrubiks/odoo-demo-addons-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fodoo-demo-addons-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fodoo-demo-addons-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fodoo-demo-addons-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fodoo-demo-addons-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twtrubiks","download_url":"https://codeload.github.com/twtrubiks/odoo-demo-addons-tutorial/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247266564,"owners_count":20910836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["addons","demo","erp","odoo","odoo12","odoo14","odoo15","odoo16","postgresql","python3","tutorial"],"created_at":"2024-11-16T12:12:30.128Z","updated_at":"2025-04-05T00:07:57.746Z","avatar_url":"https://github.com/twtrubiks.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# odoo-demo-addons-tutorial-odoo-12\n\n此版本為 odoo12,\n\nodoo14 版本請參考 [odoo14](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/14.0) 分支.\n\nodoo15 版本請參考 [odoo15](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/15.0) 分支.\n\nodoo16 版本請參考 [odoo16](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/16.0) 分支.\n\nodoo17 版本請參考 [odoo17](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/17.0) 分支.\n\nodoo18 版本請參考 [odoo18](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/18.0) 分支.\n\n本文章會持續更新 :smile:\n\n這邊文章主要是會手把手教大家撰寫 odoo 的 addons, 建議再閱讀這篇文章之前, 你已經看過以下的文章\n\n[odoo-development-environment-tutorial](https://github.com/twtrubiks/odoo-development-environment-tutorial) - 建立 odoo 開發環境 ( source code )\n\n[odoo-docker-tutorial](https://github.com/twtrubiks/odoo-docker-tutorial) - 利用 docker 快速建立 odoo 環境\n\n## 前言\n\n為甚麼我要寫一堆 addons, 因為其實 odoo 和 django 一樣的點是都很麻煩, 要寫個範例超級麻煩的,\n\n因為一個小地方錯可能就會造成錯誤之類的 :sweat:\n\n## addons 目錄\n\n非常建議按照順序看, 因為會一步一步帶大家 :smile:\n\n1. [odoo 手把手建立第一個 addons](demo_odoo_tutorial)\n\n2. [odoo 入門篇](demo_expense_tutorial_v1)\n\n3. [odoo 繼承 - class inheritance](demo_class_inheritance)\n\n4. [odoo 繼承 - prototype inheritance](demo_prototype_inheritance)\n\n5. [odoo 繼承 - delegation inheritance](demo_delegation_inheritance)\n\n6. [odoo 觀念 - actions 和 singleton](demo_actions_singleton)\n\n7. [odoo 觀念 - scheduler](demo_scheduler)\n\n8. [odoo 觀念 - sequence](demo_sequence)\n\n9. [odoo 觀念 - activity](demo_activity)\n\n10. [odoo 觀念 - TransientModel-Wizard](demo_odoo_tutorial_wizard)\n\n11. [odoo 觀念 - AbstractModel](demo_abstractmodel_tutorial)\n\n12. [odoo 觀念 - 實作 config settings](demo_config_settings)\n\n13. [odoo 觀念 - datetime 教學](demo_datetime_tutorial)\n\n14. [odoo 觀念 - 實作 scan barcode](demo_sale_scan_barcode)\n\n15. [odoo 觀念 - 實作 hierarchy](demo_hierarchy_tutorial)\n\n16. [odoo 觀念 - 如何使用 python xmlrpc 連接 odoo](xml-rpc-odoo)\n\n17. [odoo 觀念 - Translating 翻譯教學 i18n](demo_i18n_expense_tutorial)\n\n18. [odoo 觀念 - recruitment_website_form 介紹](demo_recruitment_website_form)\n\n19. [odoo 觀念 - 實作 init hook](demo_hook_tutorial)\n\n20. [odoo 教學 - 如何繼承 inherit controller](demo_inherit_controller)\n\n21. [odoo 教學 - fields_view_get 介紹教學](demo_fields_view_get_tutorial)\n\n22. [odoo 教學 - multi company](demo_multi_company)\n\n23. [odoo 教學 - testing 教學](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial#odoo-testing-%E6%95%99%E5%AD%B8)\n\n24. [odoo 觀念 - orm cache 說明](demo_orm_cache)\n\n25. [odoo 觀念 - 使用 RAW SQL 說明](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial#%E4%BD%BF%E7%94%A8-raw-sql-%E8%AA%AA%E6%98%8E)\n\n26. [odoo 14 觀念 - image mixin 教學](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/14.0/demo_image_mixin)\n\n27. [odoo 14 觀念 - Active Archive Ribbon 教學](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)\n\n28. [odoo 14 觀念 - Search Panel 教學](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)\n\n29. [odoo domain 教學](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/odoo_domain_tutorial)\n\n30. [odoo domain operator 教學](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/domain_operator_tutorial)\n\n31. [odoo index 教學](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/odoo_index_tutorial)\n\n32. [odoo 觀念 - odoo12 和 odoo14 的 ORM Write 差異](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/14.0/odoo_write_tutorial)\n\n33. [odoo 14 教學 - 透過 controller 建立簡單 api](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/14.0/demo_controller_json)\n\n34. [odoo 教學 - 透過 AbstractModel 擴充 Model](demo_abstractmodel_v2_tutorial)\n\n35. [odoo 教學 - odoo session_redis 教學](session_redis_tutorial)\n\n36. [Odoo 15 中的 LISTEN/NOTIFY 運作原理](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/15.0/odoo-pg-listen-notify)\n\n37. [Odoo 15 建立簡易 REST API](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/15.0/demo_controller_api)\n\n38. [odoo 18 OWL 範例 addons](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/18.0/demo_owl_tutorial)\n\n## 其他\n\n* [Youtube Tutorial - 使用 CLI 安裝,更新 addons](https://youtu.be/k19N2x8f4gw)\n\n建立 addons 模組\n\n```cmd\n./odoo-bin scaffold your_addons_name my-addons/\n```\n\n在介紹如何透過 cli 安裝 addons 之前, 請先知道一件事情,\n\n就是你可以選擇將指令全部放到 cli 中, 或是在 `odoo.conf` 設定,\n\n像是如果有設定 `odoo.conf`\n\n```cmd\n[options]\n......\ndb_user = odoo\ndb_password = odoo\ndb_port = 5432\n```\n\n這樣我們直接執行以下指令即可\n\n```cmd\npython3 odoo-bin -d odoo -c /home/twtrubiks/work/odoo12/odoo/config/odoo.conf\n```\n\n如果你沒有設定 `odoo.conf` , 也可以在 cli 中設定\n\n```cmd\npython3 odoo-bin -r odoo -w odoo  -d odoo -c /home/twtrubiks/work/odoo12/odoo/config/odoo.conf\n```\n\n`-r` 代表 db_user. `-w` 代表 db_password. `-d` 代表指定 database.\n\n安裝 addons\n\n```cmd\npython3 odoo-bin -i addons_1 -d odoo\n```\n\n更新 addons\n\n```cmd\npython3 odoo-bin -u addons_1 -d odoo\n```\n\n也可以一次更新或安裝多個 addons\n\n```cmd\npython3 odoo-bin -u addons_1,addons_2 -d odoo\n```\n\n例外還有比較進階的用法 `--dev`\n\n```cmd\npython3 odoo-bin -u addons_1 -d odoo --dev=all\n```\n\n`--dev=all` 代表全部都啟用.\n\n`--dev=xml` 代表當 xml 改變的時候, 會自動幫你更新(不用手動更新).\n\n`--dev=reload` 代表當 python code 改變時, 自動更新(不用手動更新).\n\n但有時候如果你覺得怪怪的, 我還是建議手動重新直接更新 addons 的指令比較好 :smile:\n\n注意 :exclamation: 沒有刪除 addons 的指令, 只能從 web 上移除.\n\n### shell\n\n* [Youtube Tutorial - odoo shell 基本教學 - CRUD](https://youtu.be/kmbiT54hUkw)\n\n```cmd\npython odoo-bin shell -w odoo -r odoo -d odoo --db_port=5432 --db_host=localhost --addons-path='/home/twtrubiks/odoo/addons'\n```\n\n如果有很多路徑請使用 `,` 隔開\n\n```cmd\n--addons-path='/home/twtrubiks/odoo/addons,/home/twtrubiks/odoo/addons2'\n```\n\n`search`\n\n```python\n\u003e\u003e\u003e self.env['res.partner'].search([])\nres.partner(14, 26, 33, 27, 10, 35, 18, 19, 11, 20, 22, 31, 23, 15, 34, 12, 21, 25, 37, 24, 36, 30, 38, 13, 29, 28, 9, 17, 32, 16, 1, 39, 40, 8, 7, 3)\n\u003e\u003e\u003e self.env['res.partner'].search([('name', 'like', 'kim')])\nres.partner(24,)\n\u003e\u003e\u003e self.env['res.partner'].browse([11, 20])\nres.partner(11, 20)\n```\n\n* [Youtube Tutorial - odoo shell orm 基本教學 - search_read](https://youtu.be/AzGnFX4pHWI)\n\n`search_read`\n\n通常比較常使用在 js 呼叫 odoo 或是第三方呼叫 odoo api,\n\n```python\n\u003e\u003e\u003e self.env['hr.expense'].search_read([], ['id', 'employee_id'])\n[{'id': 4, 'employee_id': (7, 'Marc Demo')}, {'id': 3, 'employee_id': (7, 'Marc Demo')}, {'id': 2, 'employee_id': (1, 'Mitchell Admin')}, {'id': 1, 'employee_id': (1, 'Mitchell Admin')}]\n\n\u003e\u003e\u003e self.env['hr.expense'].search_read([('employee_id', '=', 1)], ['id', 'name', 'employee_id'])\n[{'id': 2, 'name': 'Hotel Expenses', 'employee_id': (1, 'Mitchell Admin')}, {'id': 1, 'name': 'Travel by Air', 'employee_id': (1, 'Mitchell Admin')}]\n```\n\n* [Youtube Tutorial - odoo orm group 基本教學 - read_group](https://youtu.be/ALq6CcADygs)\n\n`read_group`\n\n通常使用在 SQL 中的 GROUP BY (很適合拿來處理比較大的資料, 效能應該也會比較好 :smile: ).\n\nread_group 的定義可參考原始碼中的 `odoo/models.py`\n\n```python\n......\n@api.model\ndef read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):\n    \"\"\"\n    Get the list of records in list view grouped by the given ``groupby`` fields\n\n    :param domain: list specifying search criteria [['field_name', 'operator', 'value'], ...]\n    :param list fields: list of fields present in the list view specified on the object.\n            Each element is either 'field' (field name, using the default aggregation),\n            or 'field:agg' (aggregate field with aggregation function 'agg'),\n            or 'name:agg(field)' (aggregate field with 'agg' and return it as 'name').\n            The possible aggregation functions are the ones provided by PostgreSQL\n            (https://www.postgresql.org/docs/current/static/functions-aggregate.html)\n            and 'count_distinct', with the expected meaning.\n    :param list groupby: list of groupby descriptions by which the records will be grouped.\n            A groupby description is either a field (then it will be grouped by that field)\n            or a string 'field:groupby_function'.  Right now, the only functions supported\n            are 'day', 'week', 'month', 'quarter' or 'year', and they only make sense for\n            date/datetime fields.\n......\n```\n\n比較特別要注意的地方是 fields, groupby, lazy 這幾個欄位 (請參考註解說明 :smile: ).\n\n如果你想參考寫法, 建議參考 odoo14 的, odoo12 也可以使用, 但是有些寫法比較舊了.\n\n這邊使用 `sale.order` 當作範例,\n\n假設想要得到每個 partner_id 的平均 amount_total,\n\n```python\nself.env['sale.order'].read_group([], ['partner_id', 'amount_total:avg'], ['partner_id'])\n```\n\n![alt tag](https://i.imgur.com/6eyegIE.png)\n\n同等如下 SQL\n\n```sql\nSELECT partner_id, avg(amount_total)\nFROM sale_order\nGROUP BY partner_id;\n```\n\n注意 :exclamation: :exclamation: 這邊 field 的格式為 `field:agg`.\n\nagg 代表 aggregate, odoo 的 orm 是有支援的, 更多詳細可參考 [postgresql functions-aggregate](https://www.postgresql.org/docs/current/functions-aggregate.html).\n\n假設想要得到每個 partner_id 的平均 amount_total 以及 總和 amount_total,\n\n```python\nself.env['sale.order'].read_group([], ['partner_id', 'total:sum(amount_total)', 'avg_total:avg(amount_total)'], ['partner_id'])\n```\n\n![alt tag](https://i.imgur.com/BhNR227.png)\n\n同等如下 SQL\n\n```sql\nSELECT partner_id, avg(amount_total), sum(amount_total)\nFROM sale_order\nGROUP BY partner_id;\n```\n\n注意:exclamation::exclamation:這邊的 fields 的格式為 `name:agg(field)`\n\n(因為是相同的 fields 名稱, 如果使用前一種寫法會錯誤)\n\n如果想要分的更細, 甚至可以再加上 fields, 這邊增加一個狀態\n\n```python\nself.env['sale.order'].read_group([], ['partner_id', 'total:sum(amount_total)', 'avg_total:avg(amount_total)'], ['partner_id', 'state'], lazy=False)\n```\n\n![alt tag](https://i.imgur.com/IaaFXae.png)\n\n同等如下 SQL\n\n```sql\nSELECT partner_id, state, avg(amount_total), sum(amount_total)\nFROM sale_order\nGROUP BY partner_id, state;\n```\n\n`lazy` 這個參數預設為 True, 也就代表只會拿第一個 field 下去分組,\n\n如果設定為 False, 就會把全部你所指定的 fields 都拿進去分組.\n\n根據 date_order 下去分組\n\n```python\nself.env['sale.order'].read_group([], ['total:sum(amount_total)'], ['date_order:month'])\n```\n\n同等如下 SQL\n\n```sql\nSELECT  DATE_TRUNC('month', date_order),\n\t\tsum(amount_total)\nFROM sale_order\nGROUP BY DATE_TRUNC('month', date_order);\n```\n\n`day` `week` `month` `quarter` `year` 這些都是可用的參數.\n\n![alt tag](https://i.imgur.com/cp1zX6P.png)\n\n`search_count`\n\n```python\n\u003e\u003e\u003e self.env['res.partner'].search_count([])\n73\n```\n\n`recordset.ids` 回傳 recordset 全部的 id\n\n```python\n\u003e\u003e\u003e recordset = self.env['res.partner'].search([])\n\u003e\u003e\u003e recordset.ids\n[14, 26, 33, 27, 10, 35, 18, 19, 11, 20, 22, 31, 23, 15, 34, 12, 21, 25, 37, 24, 36, 30, 38, 13, 29, 28, 9, 17, 32, 16, 1, 39, 40, 8, 7, 3]\n```\n\n繼續使用上面的範例\n\n`recordset.filtered(func)` 和 python 中的 [filter](https://github.com/twtrubiks/python-notes/blob/master/filter.py) 類似\n\n```python\n\u003e\u003e\u003e recordset.filtered(lambda r: r.name.startswith('C'))\nres.partner(33, 39)\n```\n\n`recordset.mapped(func)` 和 python 中的 [map](https://github.com/twtrubiks/python-notes/blob/master/map_tutorial.py) 類似\n\n```python\n\u003e\u003e\u003e recordset.mapped('name')\n['Azure Interior', 'Brandon Freeman', 'Colleen Diaz', 'Nicole Ford', 'Deco Addict', 'Addison Olson', 'Douglas Fletcher', 'Floyd Steward',...\n```\n\n`recordset.sorted(func)` 和 python 中的 [sorted](https://github.com/twtrubiks/python-notes/blob/master/sorted.py) 類似\n\n```python\n\u003e\u003e\u003e recordset.sorted(key=lambda r: r.id, reverse=True)\nres.partner(40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, ...\n```\n\n`create`\n\n```python\n\u003e\u003e\u003e partner = self.env['res.partner']\n\u003e\u003e\u003e partner.create({'name': 'twtrubiks', 'is_company': True})\nres.partner(66)\n\u003e\u003e\u003e self.env.cr.commit() # 需要特別執行這行才會寫進資料庫中\n```\n\n`write`\n\nupdate data\n\n```python\n\u003e\u003e\u003e partner = self.env['res.partner'].browse([2])\n\u003e\u003e\u003e partner\nres.partner(2,)\n\u003e\u003e\u003e partner.name\n'OdooBot'\n\u003e\u003e\u003e partner.write({'name': 'hello'})\nTrue\n\u003e\u003e\u003e partner.name\n'hello'\n\u003e\u003e\u003e self.env.cr.commit() # 需要特別執行這行才會寫進資料庫中\n```\n\n當你更新 `One2many` 和 `Many2many` 時, 要使用比較特別的語言,\n\n我之後會補充上來.\n\n`copy`\n\n如果 fields 有定義 `copy=False`, 就沒有辦法複製.\n\n```python\n# odoo/addons/base/data/res_users_demo.xml\n\u003e\u003e\u003e demo = self.env.ref('base.user_demo')\n\u003e\u003e\u003e demo.copy({'name': 'twtrubiks', 'login': 'twtrubiks', 'email':''})\n\u003e\u003e\u003e self.env.cr.commit() # 需要特別執行這行才會寫進資料庫中\n```\n\n`delete`\n\n```python\n\u003e\u003e\u003e user = self.env['res.users'].browse([3])\n\u003e\u003e\u003e user.unlink()\n2020-06-21 06:45:51,958 19735 INFO odoo odoo.models.unlink: User #1 deleted ir.model.data records with IDs: [1884]\n2020-06-21 06:45:51,996 19735 INFO odoo odoo.models.unlink: User #1 deleted res.users records with IDs: [3]\nTrue\n\u003e\u003e\u003e self.env.cr.commit() # 需要特別執行這行才會寫進資料庫中\n```\n\n`sudo`\n\n* [Youtube Tutorial - odoo 基本教學 - sudo](https://youtu.be/nAmNmPCSbGg)\n\n可參考 odoo 原始碼的 `odoo/models.py`\n\n```python\ndef sudo(self, user=SUPERUSER_ID):\n    \"\"\" sudo([user=SUPERUSER])\n\n    Returns a new version of this recordset attached to the provided\n    user.\n\n    By default this returns a ``SUPERUSER`` recordset, where access\n    control and record rules are bypassed.\n\n    .. note::\n\n        Using ``sudo`` could cause data access to cross the\n        boundaries of record rules, possibly mixing records that\n        are meant to be isolated (e.g. records from different\n        companies in multi-company environments).\n\n        It may lead to un-intuitive results in methods which select one\n        record among many - for example getting the default company, or\n        selecting a Bill of Materials.\n\n    .. note::\n\n        Because the record rules and access control will have to be\n        re-evaluated, the new recordset will not benefit from the current\n        environment's data cache, so later data access may incur extra\n        delays while re-fetching from the database.\n        The returned recordset has the same prefetch object as ``self``.\n\n    \"\"\"\n    return self.with_env(self.env(user=user))\n```\n\n`sudo([user=SUPERUSER])` 如果裡面沒有填入 user id, 預設就是使用 SUPERUSER, 如果\n\n有帶入 user id, 就是使用指定的 user 的權限.\n\n注意 :exclamation: 這是 odoo12 的作法,\n\n從 odoo13 開始切換 user 已經改成 `with_user(user)` :exclamation: 可參考 [odoo13-souece code](https://github.com/odoo/odoo/blob/13.0/odoo/models.py#L5160)\n\n`deprecated use of sudo(user), use with_user(user) instead`\n\n來看下面這個例子,\n\n因為沒有指定 user id, 所以是使用 SUPERUSER, 自然可以看到全部的 records,\n\n```python\n\u003e\u003e\u003e self.env['hr.expense'].sudo().search([])\nhr.expense(4, 3, 2, 1)\n```\n\n再來看這個例子, user_id = 6 只能看到自己的 records, 因為他是一般的 user,\n\n```python\n\u003e\u003e\u003e self.env['hr.expense'].sudo(user=6).search([])\nhr.expense(4, 3)\n```\n\n也就是說, 知道這個特性, 我們甚至可以讓沒有權限的人看到 records (請依照自己的需求去調整) :smile:\n\n另外提醒一下, 這個 sudo 除了在 ORM 底下生效外, 在 QWeb 中也會生效, 如下方這段 code 是可行的,\n\n```xml\n......\n\u003ctr\u003e\n    \u003ctd\u003e\u003cstrong\u003eEmail\u003c/strong\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cspan t-field=\"o.sudo().employee_id.identification_id\"/\u003e\u003c/td\u003e\n\u003c/tr\u003e\n......\n```\n\n`with_context`\n\n可參考 odoo 原始碼的 `odoo/models.py`\n\n```python\ndef with_context(self, *args, **kwargs):\n    \"\"\" with_context([context][, **overrides]) -\u003e records\n\n    Returns a new version of this recordset attached to an extended\n    context.\n\n    The extended context is either the provided ``context`` in which\n    ``overrides`` are merged or the *current* context in which\n    ``overrides`` are merged e.g.::\n\n        # current context is {'key1': True}\n        r2 = records.with_context({}, key2=True)\n        # -\u003e r2._context is {'key2': True}\n        r2 = records.with_context(key2=True)\n        # -\u003e r2._context is {'key1': True, 'key2': True}\n\n    .. note:\n\n        The returned recordset has the same prefetch object as ``self``.\n    \"\"\"\n    context = dict(args[0] if args else self._context, **kwargs)\n    return self.with_env(self.env(context=context))\n```\n\n`with_context` 可以用在很多地方, 這邊用一個翻譯的舉例, 如果我同時有 `en_US` 和 `zh_TW`\n\n這兩個語言, 可以使用 `with_context`帶入不同的語言, 會自動依照語言進行翻譯,\n\n```python\n\u003e\u003e\u003e self.env['product.product'].with_context(lang='zh_TW').browse(41).name\n'飛機票'\n\u003e\u003e\u003e self.env['product.product'].with_context(lang='en_US').browse(41).name\n'Air Flight'\n```\n\n`with_context` 也常使用在傳值中, 可參考 [odoo 觀念-TransientModel-Wizard](https://github.com/twtrubiks/odoo-demo-addons-tutorial/tree/master/demo_odoo_tutorial_wizard)\n\n### odoo shell 注意事項\n\n* [Youtube Tutorial - odoo shell 教學 - 注意事項](https://youtu.be/YS6mGE3-y1k)\n\nodoo-shell 下 command 無法 save 問題,\n\n當使用 Odoo Shell 測試資料時, 會發現當我們下了指令時, db 裡面的值沒有改變,\n\n這時候必須另外執行\n\n```cmd\nself.env.cr.commit()\n```\n\n在非 Odoo Shell 中會自動執行, 在 Odoo Shell 中不會自動執行 (需要手動執行).\n\n除非你不想要把修改資料寫進去資料庫.\n\n## odoo log 說明\n\n```log\n2019-12-05 03:04:15,734 1 INFO localhost werkzeug: 172.18.0.1 - - [05/Dec/2019 03:04:15] \"POST /longpolling/poll HTTP/1.0\" 200 - {query_count} {query_time} {remaining_time}\n```\n\nquery_count = query 次數\n\nquery_time = query 時間\n\nremaining_time = 剩餘時間\n\n如何透過 elk 搭配 odoo, 請參考 [docker-elk-tutorial 7.6.0](https://github.com/twtrubiks/docker-elk-tutorial/tree/elk-7.6.0)\n\n## odoo 使用 gmail 發信\n\n* [Youtube Tutorial - odoo 教學 - 使用 gmail 發信](https://youtu.be/CkFHCQuzEoo)\n\ngmail 需要一些前製作業, 建議先閱讀 [使用 Gmail 寄信 - 前置作業](https://github.com/twtrubiks/Flask-Mail-example#%E4%BD%BF%E7%94%A8-gmail-%E5%AF%84%E4%BF%A1---%E5%89%8D%E7%BD%AE%E4%BD%9C%E6%A5%AD) 這篇的 gmail 設定\n\nTechnical -\u003e Email -\u003e Outgoing Mail Servers\n\n![alt tag](https://i.imgur.com/mZpaHWu.png)\n\nSMTP Server\t填入 `smtp.gmail.com`\n\nSMTP Port 填入 `465`\n\nConnection Security 填入 `SSL/TLS`\n\n填入自己的 Username 和 Password\n\n![alt tag](https://i.imgur.com/V77o0hY.png)\n\n建議輸入資料後, 可以先點選測試連接 (以下是成功的畫面)\n\n![alt tag](https://i.imgur.com/rIXcdnH.png)\n\n如果出現錯誤, 請確認你的帳密是否有錯誤\n\n![alt tag](https://i.imgur.com/yMVWVF5.png)\n\n接著可以使用 odoo 內的 email 測試看是否可以成功發信\n\n![alt tag](https://i.imgur.com/sy1A69K.png)\n\n成功發信\n\n![alt tag](https://i.imgur.com/CvMuelM.png)\n\n## 如何全域修改時間日期格式\n\n路徑為 Translations -\u003e Languages, 點選語言, 就會看到以下的畫面,\n\n圖片下方有一些參數的說明(可自行依照需求調整)\n\n![alt tag](https://i.imgur.com/Z66LDIC.png)\n\n## 其他注意事項\n\n`odoo.conf` 中的 `data_dir` 參數建立好了就不要亂改,\n\n因為亂改動可能會導致你的 odoo 打開時一片空白或是破圖的狀況.\n\n```conf\n[options]\n......\ndata_dir = /home/twtrubiks/work/odoo12/odoo-data\n```\n\n另外如果你的 odoo 不知道甚麼原因導致破圖(非上述的狀況),\n\n錯誤訊息通常可能是遺失 filestore, 這時候可以嘗試以下的幾個方法,\n\n可以試試看更新 odoo 中的 `base`,\n\n或是從 db 中刪除 `ir_attachment` table,\n\n重新使用 debug mode 中的 Regenerate Assets Bundles.\n\n(assets 這個的功能是刪除舊的 css 和 js, 然後重新產生新的, 有時遇到 assets 快取的問題, 可以選這個選項)\n\n![alt tag](https://i.imgur.com/EJTK0KY.png)\n\n## 建議使用繼承 addons 的方式修改 odoo\n\n[Youtube Tutorial - odoo 教學 - 建議使用繼承 addons 的方式修改 odoo](https://youtu.be/Yncbx95YT1Q)\n\n這邊提醒大家, 建議在修改 odoo 的時候, 儘量使用 addons 繼承的方式去修改 code,\n\n原因是維護性的問題, 原生的 code 保持乾淨,\n\n雖然用 odoo developer mode 可以很快的修改 view,\n\n但是 :exclamation::exclamation:\n\n只要你一更新你修改的那個 addons, 就會自動還原 :exclamation::exclamation:\n\n這邊使用 `hr_expense` 舉的例子,\n\n我透過 Edit View 修改了 view,\n\n![alt tag](https://i.imgur.com/M6goe84.png)\n\n當你保存是會生效的.\n\n可是當你去更新 `hr_expense` 的時候, 你會發生他被還原了.\n\n所以, 使用 Edit View 選項去修改 view 可以使用在測試時.\n\n正式的修改, 還是推薦使用 addons 繼承的方式 :smile:\n\n## Donation\n\n文章都是我自己研究內化後原創，如果有幫助到您，也想鼓勵我的話，歡迎請我喝一杯咖啡:laughing:\n\n綠界科技ECPAY ( 不需註冊會員 )\n\n![alt tag](https://payment.ecpay.com.tw/Upload/QRCode/201906/QRCode_672351b8-5ab3-42dd-9c7c-c24c3e6a10a0.png)\n\n[贊助者付款](http://bit.ly/2F7Jrha)\n\n歐付寶 ( 需註冊會員 )\n\n![alt tag](https://i.imgur.com/LRct9xa.png)\n\n[贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8)\n\n## 贊助名單\n\n[贊助名單](https://github.com/twtrubiks/Thank-you-for-donate)\n\n## License\n\nMIT license\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fodoo-demo-addons-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwtrubiks%2Fodoo-demo-addons-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fodoo-demo-addons-tutorial/lists"}