DjangoをDockerでHerokuにデプロイする

# 序

python+conda以外の全ての知識がなかったのでかなり苦労してdjango+docker+heroku環境を整えた。

今回は化合物のSMILESを入力として受け取り、その化合物を表示するところまで行きます。


#### 参考にしたサイト様
[Docker上でAnacondaを使って入れたDjangoをHerokuで動かす](https://qiita.com/sakiyamaK/items/de6344234b51545f196c)
[HerokuにConda環境で開発したアプリをデプロイする方法](https://qiita.com/kimisyo/items/b7fbdd9afe82b872a18b)
[Django 入門](https://python.keicode.com/django/creating-django-app.php)

#### 環境
Window 11
anaconda
VScode

# 1. Dockerでコンテナを作成する。

[Docker上でAnacondaを使って入れたDjangoをHerokuで動かす](https://qiita.com/sakiyamaK/items/de6344234b51545f196c)の2,3,4,5を参照してください。condaの仮想環境に入った後にインストールするものはanacondaからdjango環境を作ると同じものを入れてください。

最後に`docker commit django djangotest:1.0.0`などで適当にdockerを保存しておきます。


# 2. anacondaからdjango環境を作る。

名前は何でもよいのでwindows 11環境でdjango環境を作ります。(anaconda環境の作成は省略)
rdkitはお好みでインストールしてください。普通のアプリ作るならいらないです。

```bash:bash
(base)       > conda create -n django_env python==3.7
(base)       > conda activate django_env
(django_env) > conda install django=3.* -c conda-forge
(django_env) > conda install -c rdkit -c conda-forge rdkit
(django_env) > conda install django-heroku -c conda-forge
```

次にdjangoでアプリケーションを作ります。

プロジェクト名:wepapps、アプリ名:chem_appで作ります。

```bash:bash
(django_env) > mkdir django_sample
(django_env) > cd django_sample
(django_env) > mkdir webapps
(django_env) > cd webapps
(django_env) > django-admin startproject webapps .
(django_env) > python manage.py startapp chem_app
```

`django-admin startproject webapps .`は、`mkdir webapps`と同じ名前である必要はないです。

ここで一旦`python manage.py runserver 0.0.0.0:8000`として動くかどうかを確認します。

# 3. アプリケーションを作る

`django_sample/webapps/webapps/setting.py`で、以下を変更。

```python:setting.py

# herokuでアクセスできるようにしておく
ALLOWED_HOSTS =  ['127.0.0.1', '.herokuapp.com']

~

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [  
            # htmlを読み込めるようにしておく。
            os.path.join(BASE_DIR, 'chem_app/templates'), ## <- ココを追加
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

```

次に`django_sample/webapps/webapps/urls.py`を以下のように変更する。

```python:urls.py
from django.conf.urls import url, include
from django.contrib import admin
from django.urls import path
import chem_app.views as chem # django_sample/webapps/chem_app/views.pyをchemとして扱っています

urlpatterns = [
    path('admin/', admin.site.urls),
    path('smiles2picture/', chem.smiles2pic, name='smiles2picuture'),
    path('smiles2picture/result', chem.smiles2pic_result, name='s2p_res'),
]

```

`django_sample/webapps/chem_app/views.py`を以下のように変更する。

```python:views.py
from django.shortcuts import render
from django.http import HttpResponse  # Load Module
from rdkit import Chem
from rdkit.Chem import Draw
import base64
from io import BytesIO
from PIL import Image
import os

def pil_to_base64(img, format="jpeg"):
    buffer = BytesIO()
    img.save(buffer, format)
    img_str = base64.b64encode(buffer.getvalue()).decode("ascii")

    return img_str

def smiles2pic(request):
    return render(request,"smiles2picture.html")

def smiles2pic_result(request):
    # SMILESから画像を生成する
    smiles = request.POST.get("SMILES", None)
    print(f"smiles:{smiles}")
    m = Chem.MolFromSmiles(smiles)
    img = Draw.MolToImage(m,size=(300, 300))
    img_base64 = pil_to_base64(img, format="png")
    return render(request, 'chemtest.html',{"title":smiles,"img":img_base64})
```

`django_sample/webapps/chem_app/templates/smiles2picture.html`及びを`django_sample/webapps/chem_app/templates/chemtest.html`以下のようにして追加する。

```html:smiles2picture.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Smiles2Picture</title>
</head>
<body>
    <p>化合物のSMILESを入力</p>
    <form action="/smiles2picture/result" method="POST">
        {% csrf_token %}
        <input type="text" name="SMILES">
        <input type="submit" value="送信">
    </form>
</body>
</html>
```
```html:chemtest.html
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
    </head>
    <body>
        <img src="data:image/png;base64,{{img}}">
    </body>
</html>
```

ここではrdkitで生成した画像をファイルに保存せずにbase64にして直接htmlファイルに渡していますが、これはherokuがファイルの保存して使いまわすようなことができないからです。staticな場合にはdjangoの方でちょっと面倒な操作が必要みたいです。

ここでもう一度`python manage.py runserver 0.0.0.0:8000`を打って http://127.0.0.1:8000/smiles2picture/ にアクセスしてきちんと動くか確認します。



# 4.Dcokerfileを生成する。



`django_sample`直下には`Dockerfile`を設置します。

```Dockerfile:Dockerfile
# 自分のdockerイメージに書き換える
FROM djangotest:1.0.2

SHELL ["/bin/bash", "-c","-l"]

RUN echo "chmod 777 /root/.pyenv/versions/miniconda3-latest/etc/profile.d/conda.sh" >> ~/.bashrc
RUN echo "source /root/.pyenv/versions/miniconda3-latest/etc/profile.d/conda.sh" >> ~/.bashrc
RUN echo "conda activate base" >> ~/.bashrc
RUN echo "conda activate django" >> ~/.bashrc
RUN echo "conda env list" >> ~/.bashrc

ENV PATH /root/.pyenv/versions/miniconda3-latest/envs/django/bin:$PATH

COPY webapps/ ~/webapps
WORKDIR ~/webapps
CMD gunicorn --bind 0.0.0.0:$PORT webapps.wsgi
```

# 5. herokuの導入。

gitが入っている必要があり。
[公式サイト](https://git-scm.com/downloads)から自環境にあったものを選択して導入。
それから[heroku CLI](https://devcenter.heroku.com/ja/articles/heroku-cli#install-the-heroku-cli)を入れる。windowsだと割と何も考えずにできる。

# 6. herokuでpushする。


```bash:herokuへpush
heroku container:login
heroku create myappname
heroku container:push web -a myappname
heroku container:release web -a myappname
```

これでherokuに適用されたはずです。自分の作ったサイトにアクセスして動くか確かめてみましょう。

だめなら`heroku logs --tail -a myappname`でログが確認できます。

私の作ったサイトを仮公開しておきます。https://mychemodel2.herokuapp.com/smiles2picture

# 補遺・最終的なディレクトリ構造


```:ディレクトリ構造
DJANGO_SAMPLE
│  Dockerfile
│
└─webapps
    │  db.sqlite3
    │  manage.py
    │
    ├─chem_app
    │  │  admin.py
    │  │  apps.py
    │  │  models.py
    │  │  tests.py
    │  │  views.py
    │  │  __init__.py
    │  │
    │  ├─migrations
    │  │      __init__.py
    │  │
    │  ├─static
    │  │  └─chem_app
    │  │      └─images
    │  │          └─  test.png
    │  │
    │  └─templates
    │         chemtest.html
    │         index.html
    │         smiles2picture.html
    │  
    └─webapps
        │  asgi.py
        │  settings.py
        │  urls.py
        │  wsgi.py
        └─ __init__.py
```