ONLYOFFICEをPython Appに組み込む方法

**この記事では、ONLYOFFICEをPythonアプリに統合する方法をご紹介します。**

ONLYOFFICEは、あらゆるプログラミング言語で書かれたウェブアプリにドキュメント、スプレッドシート、プレゼンテーションの編集機能をもたらすことができる、強力なオープンソースサービスです。この記事では、ONLYOFFICEをPythonアプリに統合する方法をご紹介します。

そのために、Python上でシンプルな文書管理システムを作り、そこにONLYOFFICE文書エディターを統合しましょう。これは思うより簡単なことです。

![１.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655664/d75dfed6-3f31-8f9f-255a-471719726b93.jpeg)

**Pythonで作るDMS**

このパートでは、ONLYOFFICEとの統合をその例で示すために、Pythonアプリのコードを書きます。エディターを統合しようと思っているアプリには、閲覧／編集のために開く必要のあるファイルのリストがある可能性が高いです。そこで、この機能を持つアプリを作ればいいのですね。また、このアプリでは、ファイルのダウンロードもできるようにする必要があります。

アプリには、[Bottle](https://bottlepy.org/docs/dev/ "Bottle")フレームワークを使用します。``pip install bottle``コマンドで作業ディレクトリにインストールします。次に、``main.py``（アプリのコード）と``index.tpl``（テンプレート）を作成し、`main.py`ファイルに以下のコードを追加する必要があります。

```python
from bottle import route, run, template, get, static_file # connecting the framework and the necessary components
@route('/') # setting up routing for requests for /
def index():
    return template('index.tpl')  # showing template in response to request


run(host="localhost", port=8080)  # running the application on port 8080
```

アプリケーションを起動すると、`http://localhost:8080` に空のページが表示されます。ドキュメントサーバーは、ゼロから新しいドキュメントを作成できないので、デフォルトのファイルを追加して、その名前のリストをテンプレートに形成する必要があります。そこで、`files`というフォルダを作成し、そこに3つのファイル（docx, xlsx, pptx）を置きましょう。

`listdir`コンポーネントを使用して、それらの名前を読み取ります。

```python
from os import listdir
```

では、`files`というフォルダにあるすべてのファイル名を格納する変数を作成しましょう。

```python
sample_files = [f for f in listdir('files')]
```

この変数をテンプレートで使用するには、`template`メソッドに渡す必要があります。

```python
def index():
    return template('index.tpl', sample_files=sample_files)
```
この変数をテンプレートに表示させてみましょう。
```python
%for file in sample_files:
    <div>
        <span>{{file}}</span>
    </div>
% end
```

アプリケーションを再起動すると、ページ上にファイル名の一覧が表示されます。さて、これらのファイルをすべてのアプリ利用者が利用できるようにする必要があります。

ここでは、そのための新しいメソッドをご紹介します。

```python
@get("/files/<filepath:re:.*\.*>")
def show_sample_files(filepath):
    return static_file(filepath, root="files")
```
**Pythonアプリでドキュメントを表示する**

ONLYOFFICEエディターを使ってDocument Serverをインストールします。[インストール方法](https://helpcenter.onlyoffice.com/installation.aspx)はたくさんありますが、Dockerを使用することをお勧めします。

```python
docker run -itd -p 80:80 onlyoffice/documentserver-de
```
テンプレート内のドキュメントエディターAPIを接続します。

```python
<script type="text/javascript" src="editor_url/web-apps/apps/api/documents/api.js"></script>
```

`editor_url` は、ドキュメントエディタへのリンクです。

各ファイルを開いて閲覧するためのボタンです。

```python
<button onclick="view('files/{{file}}')">view</button>
```
ここで、`id`を持つdivを追加する必要があります。

```python
<div id="editor"></div>
```

このdivの中でドキュメントエディタが開かれます。しかし、エディタを開く関数を呼び出した後でなければなりません。

```python
<script>
function view(filename) {
    if (/docx$/.exec(filename)) {
        filetype = "text"
    }
    if (/xlsx$/.exec(filename)) {
        filetype = "spreadsheet"
    }
    if (/pptx$/.exec(filename)) {
        filetype = "presentation",
        title: filename
    }

    new DocsAPI.DocEditor("editor",
        {
            documentType: filetype,
            document: {
                url: "host_url" + '/' + filename,
                title: filename
            },
            editorConfig: {mode: 'view'}
        });
  }
</script>
```

DocEditor関数の引数は2つで、エディターが開かれる要素の`id`と、エディターの設定値を含むJSONです。

すべてのパラメータは、[公式のAPIドキュメント](https://api.onlyoffice.com/editors/config/)に記載されています。この例では、必須パラメータである`documentType`、`document.url`、`editorConfig.mode`を使用しています。`title`も追加しましょう。これはエディタに表示されるファイル名です。

ドキュメントの種類（`documentType`）は、そのフォーマット（テキストはdocx、スプレッドシートはxlsx、プレゼンテーションはpptx）によって識別されます。

`document.url`に注意してください。これはこれから開こうとしているファイルへのリンクです。

これで、Pythonアプリでドキュメントを表示するためのすべてが揃いました。

**ファイルの編集**

「編集」ボタンを追加してみましょう。

```python
<button onclick="edit('files/{{file}}')">edit</button>
```
次に、ファイルを開いて編集するための関数を新たに作成する必要があります。これはView関数に似ているので、共通部分を別の関数にしましょう。

これで3つの関数ができました。

```python
<script>
    var editor;
    function view(filename) {
        if (editor) {
            editor.destroyEditor()
        }
        editor = new DocsAPI.DocEditor("editor",
            {
                documentType: get_file_type(filename),
                document: {
                    url: "host_url" + '/' + filename,
                    title: filename
                },
                editorConfig: {mode: 'view'}
            });
    }

    function edit(filename) {
        if (editor) {
            editor.destroyEditor()
        }
        editor = new DocsAPI.DocEditor("editor",
            {
                documentType: get_file_type(filename),
                document: {
                    url: "host_url" + '/' + filename,
                    title: filename
                }
            });
    }

    function get_file_type(filename) {
        if (/docx$/.exec(filename)) {
            return "text"
        }
        if (/xlsx$/.exec(filename)) {
            return "spreadsheet"
        }
        if (/pptx$/.exec(filename)) {
            return "presentation"
        }
    }
</script>
```

`destroyEditor`は、エディタが開かれていた場合、それを閉じます。

デフォルトでは、`editorConfig`パラメータは、値`{"mode": "edit"}`という値を持っています。そのため、`edit()`関数にはこのパラメータが存在しません。

これで、ファイルが編集用に開かれます。

**ドキュメントの共同編集**

共同編集は、編集者の設定で同じ文書に同じ[document.key](https://api.onlyoffice.com/editors/config/document#key)を使用することで実装されます。このキーがない場合、編集者はファイルを開くたびに編集セッションを作成します。

共同編集のために同じ編集セッションに接続させるためには、それぞれのdocにユニークなキーを設定する必要があります。`filename + "_key"`の形式でキーを使用することにしましょう。これを、`document`が存在するすべてのconfigに追加する必要があります。

```python
 document: {
                    url: "host_url" + '/' + filepath,
                    title: filename,
                    key: filename + '_key'
                },
```

**ファイルの保存**

ONLYOFFICEは通常、あなたが作業中にドキュメントに加えたすべての変更を保存します。エディタを閉じた後、Document Serverは保存するファイルのバージョンをビルドし、`callbackUrl`アドレスにリクエストを送信します。このリクエストには、`document.key`と、構築されたばかりのファイルへのリンクが含まれます。

実稼働環境では、`document.key`を使用して古いバージョンのファイルを見つけ、新しいものに置き換えます。今回のケースでは、データベースがないので、`callbackUrl`を使ってファイル名を送信するだけです。

`editorConfig.callbackUrl`の設定に`callbackUrl`を指定します。このパラメータを追加すると、`edit()`メソッドは次のようになります。

```python
function edit(filename) {
        const filepath = 'files/' + filename;
        if (editor) {
            editor.destroyEditor()
        }
        editor = new DocsAPI.DocEditor("editor",
            {
                documentType: get_file_type(filepath),
                document: {
                    url: "host_url" + '/' + filepath,
                    title: filename, 
                    key: filename + '_key'
                }
                ,
                editorConfig: {
                    mode: 'edit',
                    callbackUrl: "host_url" + '/callback' + '&filename=' + filename  // add file name as a request parameter
                }
            });
    }
```

次に、`/callback`アドレスにPOSTリクエストを取得した後、ファイルを保存するメソッドを記述する必要があります。

```python
@post("/callback") # processing post requests for /callback
def callback():
    if request.json['status'] == 2:
        file = requests.get(request.json['url']).content
        with open('files/' + request.query['filename'], 'wb') as f:
            f.write(file)
    return "{\"error\":0}"
```

`#status 2`はビルドされたファイルです。すべてのステータスに関するより詳細な情報は、[APIドキュメント](https://api.onlyoffice.com/editors/)に記載されています。

これで、エディタを閉じた後、新しいバージョンのファイルがストレージに保存されるようになりました。

**ユーザーの管理**

アプリ内にユーザーがいる場合、エディター設定にそのユーザーの識別子（idとname）を記述します。こうすることで、誰がドキュメントを編集しているのかを確認することができます。

例として、インターフェイスでユーザーを選択する機能を追加してみましょう。

```python
<select id="user_selector" onchange="pick_user()">
    <option value="1" selected="selected">JD</option>
    <option value="2">Turk</option>
    <option value="3">Elliot</option>
    <option value="4">Carla</option>
</select>
```

`<script>`タグの最初に`pick_user()`という関数の呼び出しを追加してみましょう。この関数の中で、idとユーザー名を表す変数を初期化します。

```python
function pick_user() {
        const user_selector = document.getElementById("user_selector");
        this.current_user_name = user_selector.options[user_selector.selectedIndex].text;
        this.current_user_id = user_selector.options[user_selector.selectedIndex].value;
    }
```

次に、`editorConfig.user.id`と`editorConfig.user.name`を使用して、エディタの設定に[ユーザの設定](https://api.onlyoffice.com/editors/config/editor#user)を追加する必要があります。これらのパラメータを、ファイル編集機能のエディタ設定に追加してみましょう。

```python
function edit(filename) {
        const filepath = 'files/' + filename;
        if (editor) {
            editor.destroyEditor()
        }
        editor = new DocsAPI.DocEditor("editor",
            {
                documentType: get_file_type(filepath),
                document: {
                    url: "host_url" + '/' + filepath,
                    title: filename
                },
                editorConfig: {
                    mode: 'edit',
                    callbackUrl: "host_url" + '/callback' + '?filename=' + filename,
                    user: {
                        id: this.current_user_id,
                        name: this.current_user_name
                    }
                }
            });
    }
```

この簡単な例が、[ONLYOFFICE](https://www.onlyoffice.com/ja/developer-edition.aspx?utm_source=dzone&utm_medium=cpc&utm_campaign=Article_2_Python)を皆さんのPythonアプリに統合する一助となれば幸いです。より多くの統合例は[GitHub](https://github.com/ONLYOFFICE/document-server-integration)でご覧ください。