Laravel風Python製FWのMasonite＋Django管理画面＋Reactの構成でウェブアプリ開発　そして本番デプロイ

前回はMasoniteについて簡単に概要を説明しました。今回はより実践的な内容にまで踏み込んでいきます。Laravelとの徹底比較はまた後日()  

[LaravelそっくりなPython製WebフレームワークMasoniteの紹介](https://qiita.com/dumblepy/items/65b975302618cb6a8f64)

さて、メインのフレームワークにMasoniteを使っていくわけですが、今までずっと(プライベートでは)Djangoを使ってきた私にとっては、自動生成される管理画面と、モデルを書けばそのままSchemaになるDjangoのモデルの思想は、他のどのフレームワークよりも便利なものでした。  
しかしMasoniteの良さは、私が仕事で使ってきたLaravelやRailsと同じくMVCフレームワークである点や、Oratorを使って複雑にリレーションを張ったRDBからの値を取得できる点、ReactやVueなどフロント側のモダンな技術との連携の容易さ、Restfulにコントローラーを作れることなどにあると思います(このテーマについては別で掘り下げようと思います)  
ということで、MasoniteとDjangoのいいとこどりの構成でやっていきます！

具体的には

- Djangoでモデルを作る
- Djangoでマイグレーションファイルを作る(自動)
- Djangoでマイグレートする
- Djangoの管理画面を使う
- MasoniteでDBアクセス、ルーティング、リクエストとレスポンスのやり取りを行う
- フロント側のファイルはMasoniteのプロジェクト内に作る

という構成でやっていきたいと思います。

今回は、Djangoの管理画面からウェブサイトのトップページの内容を投稿し、Masoniteを使ってトップページの内容を取得し、Reactで作った画面と共にクライアントに返却する、というものを作っていきます。

### プロジェクトを作る
anyenv→pyenv→venv→pipenvの構築は前回の記事と同じです。

MasoniteとDjangoをインストール。

```bash
pipenv install masonite-cli django
```

Masoniteのプロジェクトを作ります。

```bash
craft new project
cd project
craft install #依存パッケージがインストールされる
```

次にDjangoのプロジェクトを作ります。  
MysqlやPgsqlなどのRDBを使う場合は、DjangoもMasoniteもそれぞれソケットを通じてRDBと接続するので、プロジェクトを並列に作ってもいいのですが、今回はSqliteを使うので、Masoniteの中にDjangoのプロジェクトをadminディレクトリとして作ります。

```bash
django-admin startproject admin
cd admin
python manage.py startapp my_site
```

現在のディレクトリ構成は以下のようになったと思います。

```
project ←Masoniteのプロジェクト
    admin ←Djangoのプロジェクト
        admin
            setting.py
            urls.py
            wsgi.py
        my_site
            migrations
            admin.py
            app.py
            models.py
            test.py
            views.py
        manage.py
    app
    bootstrap
    config
    databases
    resources
    routes
    storagetest
    .env
    wsgi.py
```

## Django
### モデルを作る
ではプロジェクトの設計に入ります。Djangoでモデルを作っていきます。  

```/project/admin/my_site/models.py
from django.db import models

class Toppage(models.Model):
    def __str__(self):
        return str(self.title)

    title = models.CharField(max_length=255)
    article = models.TextField(blank=True, null=True)

    class Meta:
        db_table = 'toppage'
        verbose_name_plural = 'toppage'
```

Djangoはマイグレートすると、テーブル名にアプリ名のプレフィックスが付く仕様になっています。それを防ぐためにclass Metaでdb_tableを設定します。  
verbose_name_pluralは複数形の時のテーブル名を表します。管理サイトのテーブル名として表示されます。

```python
def __str__(self):
    return str(self.title)
```

これもDjangoの管理サイトで、テーブル内の列名として表示されるものを正しくタイトルを表示するために設定しています。

### マイグレート
my_siteのモデルの変更をDjangoに認識してもらうために、setting.pyを編集します。

```/project/admin/admin/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'my_site',　#←追加
]
```

マイグレートします

```bash
cd /project/admin
python manage.py makemigrations
python manage.py migrate
```

モデルの変更を自動で感知してマイグレーションファイルを作ってくれるの！！！すごく便利！！！

あとは管理者ユーザーを作って、Djangoを起動すると管理サイトが開きます。

```bash
python manage.py createsuperuser
python manage.py runserver
```

## React
Reactでフロント側を作ります。今回はサラッと  
Masoniteでは、ビューのファイルは/project/resources/templatesの中に作られるわけですが、その中でcreate-react-appコマンドを実行することで、MVCの形を維持したままReactのプロジェクトを作ることができます。もちろん、「vue create」でも同じようにできると思います。

```bash
cd /project/resources/templates
npm install yarn
yarn add global create-react-app
create-react-app my_site
cd my_site
yarn add axios
```

App.jsに、サーバーとAPI通信し、画面に表示する処理を作ります。

```/project/resources/templates/my_site/src/App.js
import React, { Component } from 'react';
import axios from 'axios';

export default class App extends Component {
  state = {
    toppage: '',
  }
  
  getToppage=()=>{
    axios.get('/my-site/api/get_toppage')
    .then(response=>{
      this.setState({
        toppage: response.toppage;
      })
    })
    .catch(e=>{
      console.error('通信エラー');
    })
  }

  componentDidMount(){
    this.getToppage();
  }

  render(){
    return(
      <div>
        {this.state.toppage}
      </div>
    );
  }
}
```

コンパイルします

```bash
yarn build
```

するとコンパイルされたファイルが　/project/resources/templates/my_site/build/　に作られます。 
ひとまずMasoniteに「/static」にアクセスが来たときに、「/project/resources/templates/my_site/build/static/」ディレクトリを見に行くよう設定します。  
index.htmlの配信は後ほど。

```/project/config/storage.py
STATICFILES = {
    # folder          # template alias
    'resources/templates/my_site/build/static': 'static/', #←追加
    'storage/static': 'static/',
    'storage/compiled': 'static/',
    'storage/uploads': 'static/',
    'storage/public': '/',
}
```

## Masonite
ここからMasoniteでMVCを作っていきます。  
まずはDjangoが作った/project/admin/db.sqlite3のファイルを、Masoniteが読めるようにしましょう。.envを編集します。  

```/project/.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=masonite
DB_USERNAME=root
DB_PASSWORD=root
DB_LOG=True
```

↓

```/project/.env
DB_CONNECTION=sqlite
DB_DATABASE=admin/db.sqlite3
DB_LOG=True
```

### モデルを作る
Masoniteでモデルを作ります。

```bash
cd /project
craft model models/Topppage
```

トップページの内容をコントローラーに返すメソッドを作ります。

```/project/app/models/Toppage.py
from config.database import Model

class Toppage(Model):
    __table__ = 'toppage'

    @staticmethod
    def get_toppage():
        return Toppage \
            .select(
                'title',
                'article'
            ) \
            .find(1) \
            .serialize()
```

「\_\_table_\_ = 'toppage'」を設定することで、Masoniteはテーブル名がtoppageのテーブルを見に行きます。  
Masoniteが採用しているPythonのActiveRecordであるOratorの仕様ですが、serialize()をつけないとクラスとして、つけるとdictとして取得できます。

### コントローラーを作る

```bash
craft controller Toppage
```

/project/app/http/controllers/ToppageController.pyが作られます。 
htmlを返すルートと、トップページの内容を返すAPIに対応したメソッドを作ります。

```/project/app/http/controllers/ToppageController.py
from app.models.Toppage import Toppage

class ToppageController:

    def show(self):
        return view('my_site/build/index.html')

    def get_toppage(self):
        toppage = Toppage.get_toppage()
        return {'toppage': toppage}
```

コントローラーのviewメソッドは、「/project/resources/templates/」を見に行くように自動で設定されています。

### ルーティングを作る
htmlを返すルートと、トップページの内容を返すAPIに対応したルートを作ります。

```/project/routes/web.py
from masonite.helpers.routes import get

from app.http.controllers.ToppageController import ToppageController

ROUTES = [
    get('/', ToppageController.show),
    get('/my-site/api/get_toppage', ToppageController.get_toppage)
]

```

以上でサーバー側処理は終わりです。

## インフラ
ここからサーバーの設定をします。
サーバーに初めから入っているPythonのバージョンが古かったり、自分で好きなバージョンを使いたかったりすると思うので、サーバーの中も同じくanyenv→pyenv→venv→pipenvの順番で環境構築するといいでしょう。

では初めに、Djangoの静的ファイルを配信する設定を書きます。   

```/project/admin/admin/settings.py
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'templates/static')
```

静的ファイルをまるっと配信用ディレクトリに持ってきましょう。Djangoの組み込みコマンドが使えます。

```bash
python manage.py collectstatic
```

### Nginx
Nginxをインストールしましょう。

```bash
apt install nginx
```

インストール終了後に、サーバーのIPアドレス80番ポートにアクセスすると、「Welcome Nginx!」のページが見えていると思います。  
- 「/admin」にアクセスした時はDjangoの管理画面
- 「/static/admin」にアクセスしたときはDjangoの管理画面で使う静的ファイル
- それ以外のアクセスはMasoniteで受け止める

ように設定していきます。
ファイルのhttpディレクティブ内に書きます

```/etc/nginx/nginx.conf
upstream django {
        server          unix:///var/run/django.sock;
}

upstream masonite {
        server          unix:///var/run/masonite.sock;
}

server {
        listen      80;
        server_name  my_site.com;


        location /static/admin {
                alias    /path/to/your/site/project/admin/templates/static/admin;
        }

        location /admin {
                uwsgi_pass      django;
                include         /path/to/your/site/project/admin/uwsgi_params;
        }

        location / {
                uwsgi_pass      masonite;
                include         /path/to/your/site/project/project/uwsgi_params;
        }
}
```

/etc/nignx/のディレクトリにuwsgi_paramsというファイルがあるので、これをMasoniteとDjango、それぞれのプロジェクトルートへコピーしましょう。

```bash
cp /etc/nginx/uwsgi_params /path/to/your/site/project/project/
cp /etc/nginx/uwsgi_params /path/to/your/site/project/project/admin
```

### uwsgi
uwsgiとは、Pythonの本番環境でよく使われるアプリケーションサーバーのためのアプリです。gunicornも同じくPython製アプリケーションサーバーとして共によく使われます。  

まずはuwsgiをインストールします

```bash
pipenv install uwsgi
```

uwsgiの設定を書いたiniファイルを作ります。

```/path/to/your/site/project/admin/django.ini
[uwsgi]
socket = /var/run/django.sock

#permissions for the socket file
chmod-socket = 666

#application's base folder
base = /path/to/your/site/project/admin

#python module to import
module = admin.wsgi

processes = 2
threads = 1
```

```/path/to/your/site/project/masonite.ini
[uwsgi]
socket = /var/run/masonite.sock

#permissions for the socket file
chmod-socket = 666

#application's base folder
base = /path/to/your/site/project

#python module to import
module = wsgi

processes = 2
threads = 1
```

サービスの起動方法ですが、uwsgiはpipなどと同じく、コマンドで起動するPythonで作られたアプリなので、Masoniteは/project、Djangoは/project/adminとそれぞれの場所で立ち上げなくていけません。これは起動バッチを作るのが良いでしょう。  
nohupと &を使って、バックグラウンドで実行するようにしています。

```/path/to/your/site/project/run.sh
#!/bin/bash
service nginx restart
nohup uwsgi --ini masonite.ini > /dev/null 2>&1 &
```

```/path/to/your/site/project/admin/run.sh
#!/bin/bash
nohup uwsgi --ini django.ini > /dev/null 2>&1 &
```

サービス停止するときは

```bash
killall uwsgi
```

ではサービスを起動しましょう！かならずvenvを有効にしてください。

```bash
cd /path/to/your/site/
source .venv/bin/activate # venvを有効にしないとuwsgiにパスが通らない
cd project/
./run.sh
cd admin/
./run.sh
```
ブラウザからサーバーの「/admin」にアクセスするとDjangoの管理画面が、「/」にアクセスすると、Reactで作った画面が開き、Django管理画面から投稿した内容がMasoniteから配信されて画面に表示されていれば成功です。

## 最後に
今回はDjangoの管理画面とMasoniteでサービス運用する手順、モダンなフロント技術との連携、本番デプロイまで、一気に説明しました。  
これを機に、より多くのがPythonでウェブ開発をする楽しさ、効率の良さに気づいていただけたら、またPythonistaもMasoniteを触っていただけたらと思います！  
ありがとうございました。