チュートリアル - Docker odooコンテナで実行するカスタムアドオン

# odoo のカスタムアドオン

このチュートリアルでは、Docker odoo コンテナを起動し、カスタムアドオンのひな形アプリケーションの実行と、サンプルアプリケーション「Open Academy」の実行を行います。
https://www.odoo.com/documentation/13.0/howtos/backend.html#build-an-odoo-module


# 準備

Dockerのインストールを行います。

## Dockerのインストール

Windows10で、Docker Desktop for Windowsをインストールします。
https://docs.docker.jp/docker-for-windows/toc.html

# odooの起動

Dockerを用いて、カスタムアドオン用ディレクトリをマウントしたodooコンテナを起動します。

## カスタムアドオン用ディレクトリのマウント

odooコンテナ内で、カスタムアドオン用の`/mnt/extra-addons`ディレクトリが`odoo.conf`ファイルで定義されています。

```odoo.conf
[options]
addons_path = /mnt/extra-addons
：
```

このチュートリアルでは、カスタムアドオンのソースコードを、Docker odoo コンテナ内ではなく、Dockerを起動しているWindows10の`C:\odoo13\addons`フォルダに保存します。このフォルダを Docker odoo コンテナから`/mnt/extra-addons`フォルダとして読み書きできるようにマウントします。

|オプション|Windows10|コンテナ|
|---------|---------|-------|
| -v |C:/odoo13/addons|/mnt/extra-addons|


## ２つのコマンド

Dockerでodooを起動する為には、コマンドプロンプトで、２つのコマンドを順に実行する必要があります。
1つ目は、postgreSQLであり、2つ目は、odooです。

``` db.cmd
docker run -d -e POSTGRES_USER=odoo -e POSTGRES_PASSWORD=odoo -e POSTGRES_DB=postgres --name db postgres:10
```

``` odoo.cmd
docker run -v C:/odoo13/addons:/mnt/extra-addons -p 8069:8069 --name odoo --link db:db -t odoo
```
※ マウントされるディレクトリをShare(共有)するかどうか、聞かれますので、「`Share it`」を選びます。

## データベースの作成とログイン
http://localhost:8069/ へアクセスし、`Create databese`でデータベースを作成します。

|項目|値|
|---|---|
|**Database Name**|openacademy|
|**Email**|admin|
|**Password**|admin|
|**Phone number**|(空欄)|
|**Language**|Japanese / 日本語|
|**Country**|Japan|
|**Demo data**|チェック|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/d26df043-1a9b-84a7-cb9a-eeb0cbf51fc8.png)


# カスタムアドオン・アプリケーション

２つの方法で、カスタムアドオン・アプリケーションを作成し、実行します。

* ひな形からの作成
* ソースコードのダウンロード

## ひな形からの作成 - myaddon

### odoo scaffold コマンド

odoo コンテナ内で、カスタムアドオン・アプリケーションのひな形'myaddon'を `/mnt/extra-addons`フォルダ内に作成します。
Dockerの`Dashboard`を開き、odooコンテナの`CLI`をクリックして、CLIを開いて、次のコマンドを実行します。

``` CLI.cmd
odoo scaffold myaddon /mnt/extra-addons
```

このコマンドを実行すると、`/mnt/extra-addons/myaddon`フォルダ内にひな形が作成されます。これは、Windows10上の`C:\odoo13\addons\myaddon`フォルダとして確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/cf8bb77e-c9d9-80c9-57d4-e7df2bc67234.png)

### アプリケーション化
作成したひな形をアプリケーションとして認識させる為、`C:\odoo13\addons\myaddon\__manifest__.py`ファイルを編集します。
`applictation`項目を`True`にすることにより、odooのアプリ管理画面のフィルターで、「アプリ」として表示されます。

```__manifest__.py
    # only loaded in demonstration mode
    'demo': [
        'demo/demo.xml',
    ],
    # As application
    'application': True,
}
```

### アプリリストの更新

odooには、アプリを管理する機能があり、まずは、作成したアドオンをodooに認識させる必要があります。その為、「リスト更新」を行う必要があるのですが、デフォルトでは、それが無効化されています。
次の手順で有効化し、リストを更新します。

1. 「アプリ」で、何らかのアプリをインストール - 例えば、「ディスカス」をインストールします（※）。
2. 「管理設定」の「一般設定」で、「開発者ツール」の「開発者モードを有効化」をクリックします（※）。
3. 「アプリ」で、「アプリリストを更新」メニューが現れますので、それで更新します。
4. 検索欄に`myaddon`を入力して、アプリリストの検索を行います。
5. `myaddon`アプリが表示されていることを確認できます。

※注：1で何らかのアプリをインストールするまでは、2の「一般設定」は表示されません。

### アプリのインストール

「アプリ」で表示された`myaddon`の`インストール`で、`myaddon`アプリをインストールします。
ただし、インストールに成功しても、ひな形のままでは、`myaddon`アプリは、表示されません。

### ひな形アプリの編集
`myaddon`アプリが動作するようにソースコードのコメントアウトを削除します。

`C:\odoo13\addons\myaddon\__manifest__.py`(L.27)

```__manifest__.py
    :
    # always loaded
    'data': [
        'security/ir.model.access.csv',
        'views/views.xml',
        'views/templates.xml',
    ],
    :
```

`C:\odoo13\addons\myaddon\controllers\controllers.py`

```controllers.py
# -*- coding: utf-8 -*-
from odoo import http

class Myaddon(http.Controller):
    @http.route('/myaddon/myaddon/', auth='public')
    def index(self, **kw):
        return "Hello, world"

    @http.route('/myaddon/myaddon/objects/', auth='public')
    def list(self, **kw):
        return http.request.render('myaddon.listing', {
            'root': '/myaddon/myaddon',
            'objects': http.request.env['myaddon.myaddon'].search([]),
        })

    @http.route('/myaddon/myaddon/objects/<model("myaddon.myaddon"):obj>/', auth='public')
    def object(self, obj, **kw):
        return http.request.render('myaddon.object', {
            'object': obj
        })
```

`C:\odoo13\addons\myaddon\demo\demo.xml`

```demo.xml
<odoo>
    <data>
          
          <record id="object0" model="myaddon.myaddon">
            <field name="name">Object 0</field>
            <field name="value">0</field>
          </record>

          <record id="object1" model="myaddon.myaddon">
            <field name="name">Object 1</field>
            <field name="value">10</field>
          </record>

          <record id="object2" model="myaddon.myaddon">
            <field name="name">Object 2</field>
            <field name="value">20</field>
          </record>

          <record id="object3" model="myaddon.myaddon">
            <field name="name">Object 3</field>
            <field name="value">30</field>
          </record>

          <record id="object4" model="myaddon.myaddon">
            <field name="name">Object 4</field>
            <field name="value">40</field>
          </record>
          
    </data>
</odoo>
```

`C:\odoo13\addons\myaddon\models\models.py`

```models.py
# -*- coding: utf-8 -*-
from odoo import models, fields, api

class myaddon(models.Model):
    _name = 'myaddon.myaddon'
    _description = 'myaddon.myaddon'

    name = fields.Char()
    value = fields.Integer()
    value2 = fields.Float(compute="_value_pc", store=True)
    description = fields.Text()

    @api.depends('value')
    def _value_pc(self):
        for record in self:
            record.value2 = float(record.value) / 100
```

`C:\odoo13\addons\myaddon\views\templates.xml`

```templates.xml
<odoo>
    <data>
        <template id="listing">
          <ul>
            <li t-foreach="objects" t-as="object">
              <a t-attf-href="#{ root }/objects/#{ object.id }">
                <t t-esc="object.display_name"/>
              </a>
            </li>
          </ul>
        </template>
        <template id="object">
          <h1><t t-esc="object.display_name"/></h1>
          <dl>
            <t t-foreach="object._fields" t-as="field">
              <dt><t t-esc="field"/></dt>
              <dd><t t-esc="object[field]"/></dd>
            </t>
          </dl>
        </template>
    </data>
</odoo>
```

`C:\odoo13\addons\myaddon\views\views.xml`

```views.xml
<odoo>
  <data>
    <!-- explicit list view definition -->
    <record model="ir.ui.view" id="myaddon.list">
      <field name="name">myaddon list</field>
      <field name="model">myaddon.myaddon</field>
      <field name="arch" type="xml">
        <tree>
          <field name="name"/>
          <field name="value"/>
          <field name="value2"/>
        </tree>
      </field>
    </record>

    <!-- actions opening views on models -->
    <record model="ir.actions.act_window" id="myaddon.action_window">
      <field name="name">myaddon window</field>
      <field name="res_model">myaddon.myaddon</field>
      <field name="view_mode">tree,form</field>
    </record>

    <!-- server action to the one above -->
    <record model="ir.actions.server" id="myaddon.action_server">
      <field name="name">myaddon server</field>
      <field name="model_id" ref="model_myaddon_myaddon"/>
      <field name="state">code</field>
      <field name="code">
        action = {
          "type": "ir.actions.act_window",
          "view_mode": "tree,form",
          "res_model": model._name,
        }
      </field>
    </record>

    <!-- Top menu item -->
    <menuitem name="myaddon" id="myaddon.menu_root"/>

    <!-- menu categories -->
    <menuitem name="Menu 1" id="myaddon.menu_1" parent="myaddon.menu_root"/>
    <menuitem name="Menu 2" id="myaddon.menu_2" parent="myaddon.menu_root"/>

    <!-- actions -->
    <menuitem name="List" id="myaddon.menu_1_list" parent="myaddon.menu_1"
              action="myaddon.action_window"/>
    <menuitem name="Server to list" id="myaddon" parent="myaddon.menu_2"
              action="myaddon.action_server"/>

  </data>
</odoo>
```

### アプリのアップグレード

アプリの「アップグレード」で、ひな形アプリ`myaddon`の編集を反映します。
※ 「アップグレード」する際に「モデルが見つかりません: myaddon.myaddon」のエラーが発生しますので、事前にodooコンテナを再起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/e3758ed5-0b29-e81d-8dde-2165be7cddbb.png)


### アプリの確認

「myaddon」を選択すると、デモ用データの一覧が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/ba9f8d77-4c5a-7f53-54e7-7f85e17c4455.png)


## ソースコードのダウンロード - Open Academy

### ダウンロードと展開

Open Academy のソースコードをダウンロードします(`Code` > `Download ZIP`)。
https://github.com/ugurcanusta55/openAcademy-Odoo13

ダウンロードしたファイルを`C:\odoo13\addons`フォルダに`openacademy`アプリとして、展開します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/593666fe-eca9-cc22-eb58-98022e6f6a83.png)

### ソースコードの修正

ダウンロードしたソースコードのままでは、デモ用データの読み込みに失敗しますので、`C:\odoo13\addons\openacademy\__manifest__.py`ファイルを修正します。
また、アプリとして認識されるように`'application': True,`を追記します。

```__manifest__.py
    :
    ],
    # only loaded in demonstration mode
    'demo': [
        'demo/demo.xml',
    ],
    'application': True,
}
```

### アプリリストの更新とインストール

「アプリ」画面で、「アプリリストの更新」を実施し、`openacademy`をインストールします。
インストールすると、「Open Academy」を開くことができますが、ユーザーには、閲覧権限しかありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/34d3fc61-7246-09c0-6124-eacb131494eb.png)

### 権限の付与

アプリの権限は、ユーザーに付与することができます。「管理設定」＞「ユーザーと会社」＞「ユーザー」で、ユーザーの編集を行います。「OpenAcademy / Manager」をチェックし、保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/e8336b13-00d2-4b35-686c-72f2d6206c61.png)

### 再ログイン後に有効

再ログインすると、権限が有効化され、Open Academyの編集が可能となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/fe27ccd1-c1ff-c9bf-79e5-e5194e0d958e.png)



# おわりに
このチュートリアルでは、Windows10のDocker環境でodooのカスタムアドオンを実行しました。

* Windows10のフォルダをDocker odoo コンテナから読み書きできるようにしました。
* odoo scaffold コマンドで、カスタムアドオンのひな形を生成し、実行できるようにしました。
* Open Academyというサンプルアプリケーションのソースコードをダウンロードし、実行できるようにしました。
* odoo上でカスタムアドオンを認識できるように「アプリリストの更新」のコツを学びました。
* アプリケーション特有の権限をユーザーに付与する方法を学びました。

尚、odooでは、RDBMSにpostgreSQLを使用していますが、DDLやDMLを直接的に記述する必要はありません。odooに搭載されたORM機構により、高速開発を可能としています。