Python DjangoでHerokuにデプロイする

#はじめに
PythonのWebアプリフレームワークDjangoを使ってherokuへのデプロイまでをやってみる。

#初期設定
##1.Python３系のインストールを確認する
バージョンを確認する
`python --version`

pythonがどこにおいてあるか確認する
`which python`

※macには２系がデフォルトで搭載されていますが２系は廃止されるので３系をインストールしましょう
※pythonインストールがまだだったら以下の記事などを参考にインストールしてみましょう
https://qiita.com/ms-rock/items/72b8f1abc661c539bb09

##2.Djangoのインストール
[Djangoの公式ページ](https://docs.djangoproject.com/ja/3.1/intro/install/)で最新版を確認してインストールできるが、
[django-tool-belt](https://pypi.org/project/django-toolbelt/)をインストールすればデプロイに必要なものも含めて一括インストールできる。

django-toolbeltのインストール

```terminal:terminal
$ pip install django-toolbelt
$ pip list
Package         Version
--------------- -------
dj-database-url 0.5.0  
dj-static       0.0.6  
Django          2.0.6  
django-toolbelt 0.0.1  
gunicorn        19.8.1 
pip             10.0.1 
psycopg2        2.7.4  
pytz            2018.4 
setuptools      28.8.0 
static3         0.7.0  

```

トラブルシューティング
psycopg2のインストールで失敗することがあるが、以下の記事の対処方法で解決できた
https://dev.classmethod.jp/articles/mac-psycopg2-install/


##3.プロジェクトの作成
Djangoがインストールされるとdjango-adminコマンドが使用できるようになります。
`django-admin startproject (アプリ名)`

sample_appというプロジェクトを作ってみる
`$ django-admin startproject sample_app`

以下のような構成のsample_appディレクトリが生成される

```terminal:terminal

.
├── manage.py
└── sample_app
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
```

##4.venv環境を作成する
###venvとは？
python3で提供されている仮想環境を提供するモジュール。
プロジェクトごとにインストールするパッケージを切り替えたいときなどに用いられる。
※同様のPython環境管理ツールにvirtualenvというコマンドもある。
これはPython2系で必要なものであり、Python2系で作る必要がある場合はこちらになるが多くの場合は3系で作るので必要ないと思われる。
※venvで作った環境を移動させると壊れるので、環境の場所を変えたい場合は`pip freez`で書き出して、新しい環境を作る必要があります。

`python -m venv (任意の環境名)`

（例）
venv環境を作成する

```terminal:terminal
$ python -m venv env
$
```

venv環境に切り替える

```terminal:terminal
$ source ./env/bin/activate
(env)$ 
```

`pip list`でパッケージを確認する

```terminal:terminal
(env)$ pip install --upgrade pip
(env)$ pip list
pip
setuptools
``` 

[最新版のDjango](https://docs.djangoproject.com/ja/3.1/topics/install/#installing-official-release)をインストールし直す

```terminal:terminal
(env)$ pip list
Package    Version
---------- -------
pip        20.2.4
setuptools 49.2.1
(env)$ python -m pip install Django
(env)$ pip list
Package    Version
---------- -------
asgiref    3.2.10
Django     3.1.2
pip        20.2.4
pytz       2020.1
setuptools 49.2.1
sqlparse   0.4.1
```

venv環境の無効化する場合は`deactivate`

```terminal:terminal
(env)$ deactivate
$ 
```

##5.開発サーバーを起動する
manage.pyがおいてあるディレクトリに移動し、runserverコマンドを使用する
`(env) $ python manage.py runserver`

以下の画面が出れば成功
![127.0.0.1_8000_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126098/8dabc20c-37e9-97d0-66c5-2c7d2e553a35.png)

終了するにはcontroll+Cする

##6.プロジェクトの日本語設定
sample_app/settings.pyの言語コードとタイムゾーンを変更する。

```sample_app/settings.py
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'
```

サーバーを起動し、日本語の画面に更新されていれば成功
![スクリーンショット 2017-12-11 23.48.41.png](https://qiita-image-store.s3.amazonaws.com/0/126098/b8c7a755-87d7-2677-7693-74e910703194.png)

##7.データベースの設定
djangoではデフォルトでSQLiteが利用されています。
データベースの設定はsample_app/settings.pyのDATABASESでされています。
DATABASESの設定確認

```sample_app/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
```

##8.管理用テーブルを作成する
Djangoではユーザー管理、セッション管理、管理サイトなどの機能がデフォルトで用意されている。
またデータベースのテーブル作成、変更を行うためのマイグレーション機能を提供している。

INSTALLED_APPSの設定確認

```sample_app/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```

テーブルが空であることを確認する

```terminal:terminal
(env) $ python manage.py dbshell
sqlite> .table
sqlite> .q
```

マイグレーションの実行、下記コマンドを実行する。

```terminal:terminal
(env) % python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
(env) % 
```

テーブルができてることを確認する

```terminal:terminal
(env) $ python manage.py dbshell
sqlite> .table
auth_group                  auth_user_user_permissions
auth_group_permissions      django_admin_log          
auth_permission             django_content_type       
auth_user                   django_migrations         
auth_user_groups            django_session            
sqlite> 
```

##9.アプリケーションを作成する
プロジェクトとその初期設定は完了したので、実際の機能や画面などを定義してるアプリケーション本体を作成する。

appアプリケーションの作成
`(env) $ python manage.py startapp app`

以下のようなappディレクトリが生成される

```terminal:terminal
.
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py
```

アプリケーションを作成するとapp/apps.py内に、アプリケーションの構成クラスであるAppConfigクラスが作成される。sample_app/setting.pyのINSTALLED_APPSにAppConfigを追加する。

```terminal:sample_app/settings.py
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app.apps.AppConfig', # AppConfigを追加
]
```

#管理サイト
##10.管理ユーザーの作成
createsuperuserで管理ユーザーを作成する

```terminal:terminal
(env) $ python manage.py createsuperuser
Username (leave blank to use 'ユーザー名'): （任意のユーザーネームを入力する）
Email address: （任意のメールアドレスを入力する）                 
Password: （パスワードを入力する）
Password (again): （パスワードを再入力）
Superuser created successfully.
```

##11.開発サーバー起動
サーバーを起動し、`http://127.0.0.1:8000/admin`にアクセスする

```terminal:terminal
(env) $ python manage.py runserver
Django version 2.0.6, using settings 'sample_app.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

うまく行っていれば以下の画面が出力される。11で作成したユーザー名とパスワードでログインする。
<img width="1351" alt="スクリーンショット 2017-12-12 7.38.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/126098/6bfbe661-dd41-e3c1-8ab2-de52c15e71b7.png">

管理サイトへのログインが成功する
<img width="1418" alt="スクリーンショット 2017-12-12 7.40.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/126098/9d78073c-fe81-90fb-4984-21538349704f.png">

#モデルの定義

テーブルを生成する

##12.モデルの作成
app/models.pyを以下のように編集する
taskというモデルを定義しています

```python:app/models.py
from django.db import models


class Task(models.Model):
    name = models.CharField('タスク名', max_length=255)
    created_date = models.DateTimeField('作成日時', auto_now_add=True)
    update_date = models.DateTimeField('更新日時', auto_now=True)

    def __str__(self):
        return self.name

```

makemigrationコマンドでマイグレーションファイルを作成する

```terminal:terminal
(env) $ python manage.py makemigrations
Migrations for 'app':
  app/migrations/0001_initial.py
    - Create model Task
```

migrateコマンドでマイグレーションを実行する

```terminal:terminal
(env) $ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, app, auth, contenttypes, sessions
Running migrations:
  Applying app.0001_initial... OK
(env) $ 
```

app_taskというテーブルができてることを確認する

```terminal:terminal
(env) $ python manage.py dbshell
sqlite> .table
app_task                    auth_user_user_permissions
auth_group                  django_admin_log          
auth_group_permissions      django_content_type       
auth_permission             django_migrations         
auth_user                   django_session            
auth_user_groups          
sqlite>
```

##13.管理サイトへモデルを登録
app/admin.pyに以下を入力

```terminal:app/admin.py
from django.contrib import admin

# Taskモデルをインポート
from .models import Task

# 管理サイトへのモデルを登録
admin.site.register(Task)
```

サーバーを起動して管理サイトを確認する。モデルが追加されていれば成功。
<img width="1407" alt="スクリーンショット 2017-12-12 8.41.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/126098/6d25e5d7-7388-c604-e3c4-6cfa4e782b35.png">

#バージョン管理ツールの導入
14~18は分かりきっている人は飛ばして問題ない

##14.Gitの設定
以下のサイトからGitをインストールする
https://git-scm.com/downloads

ユーザー名とメールアドレスの設定をする

```terminal:terminal
(env) $ git config --global user.email （メールアドレス）
(env) $ git config --global user.name （ユーザー名）
```

##15.リポジトリの作成
`git init （リポジトリ名）`で指定した名前のリポジトリを作成します。
今回は既にリポジトリを作成しているので`git init`だけします。

```terminal:terminal
(env) $ git init
Initialized empty Git repository in 
/Users/ユーザー名/PycharmProjects/sample_app/.git/
```

##16.sshキーを生成、登録する
githubと連携するために以下のコマンドでsshキーを生成する

```terminal:terminal
$ ssh-keygen -t rsa -b 4096 -C （メールアドレス）
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/ユーザー名/.ssh/id_rsa): （エンターを押す）
・
・
・
$ tree
.
├── id_rsa
└── id_rsa.pub

```

以下のコマンドを実行します。

```terminal:terminal
$ pbcopy < ~/.ssh/id_rsa.pub
```

id_rsa.pubの中身がコピーされる。
[github SSH keys](https://github.com/settings/keys)に登録する。
「New SSH Key」からTitle（任意）とKey（コピーしたもの）を入力し、登録する。

##17.New　Repositoryからリモートリポジトリを作成、リモートリポジトリのURIをローカルに設定する
New Repositoryからリポジトリを作成

![スクリーンショット 2017-12-12 11.18.22.png](https://qiita-image-store.s3.amazonaws.com/0/126098/5757c599-1c67-c85b-a1bb-adca0b48c4b0.png)

作成したリモートリポジトリのURIをコピーして以下のコマンドを実行
`$ git remote add origin (リモートリポジトリのSSH URI)`

##18.git各種コマンド
差分ステータスを確認する
`git status`

変更をステージに上げる
`git add .`

コミットする
`git commit -m "メッセージ"`

ブランチを確認する
`git branch`

ブランチを切り替える
`git branch （切り替え先ブランチ名）`

リモートにプッシュする
`git push -u origin master`

プルする
`git pull`

#Herokuにデプロイする

##19.Herokuアカウントを用意する
Herokuアカウントがない場合はまず[Herokuアカウントの作成](https://signup.heroku.com/)をする
herokuコマンドを使うため[Heroku CLIのインストール](https://devcenter.heroku.com/articles/heroku-cli)をする

##20.アプリをHerokuで使える設定をする
ALLOWED_HOSTSはWebシステムが起動することを許可するIPアドレスを指定する項目で、Herokuでは指定がないとDisallowedHostになってしまう。
IPを制限する必要があるならIPを定義しておく必要があるが、どこからアクセスされても問題ないアプリなら*と指定しておけば良い。

```terminal:sample_app/settings.py
# Allow all host headers
ALLOWED_HOSTS = ['*']
```

HerokuではDjangoで設定されているsqlite3では動作せず、
Herokuのデフォルトのpostgresqlが使われるように設定する。
[dj_database_url](https://pypi.org/project/dj-database-url/)はdjango-toolbeltに含まれている。

```terminal:sample_app/settings.py
import dj_database_url
db_from_env = dj_database_url.config()
DATABASES['default'].update(db_from_env)
```

[dj-static](https://pypi.org/project/dj-static/)を読み込んでget_wsgi_applicationをラップしている。
GunicornなどのWSGIサーバーを使用して本番環境から静的ファイルを適用するのに必要。

```terminal:sample_app/wsgi.py
import os

from dj_static import Cling
from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_app.settings")

application = Cling(get_wsgi_application())
```

##21.デプロイに必要なファイルを作成する
Procfileを作成する。ProcfileはHeroku上で実行するコマンドを定義したファイルである。
Djangoとherokuのドキュメントを参考に以下のように入力する。

https://docs.djangoproject.com/ja/3.1/howto/deployment/wsgi/gunicorn/
https://devcenter.heroku.com/articles/django-app-configuration

```terminal:terminal
(env) $ echo "web: gunicorn sample_app.wsgi --log-file -" > Procfile
(env) $
```

runtime.txtを作成する。
runtime.txtはPythonのバージョンを指定する役割を持つ。

```terminal:terminal
(env)$ echo "python-3.8.1" > runtime.txt
(env)$ 
```

requirements.txtを作成する。
requirements.txtはpipの依存関係を表す

```terminal:terminal
(env)$ pip3 freeze > requirements.txt
(env)$ 
```

##22.Herokuアプリを作成する
以下のコマンドを実行する。

```terminal:terminal
(env)$ heroku create
・・・
(env)$
```


Heroku環境変数の設定
https://devcenter.heroku.com/articles/django-assets

```terminal:terminal
(env)$ heroku config:set DISABLE_COLLECTSTATIC=1
(env)$
```

作成したアプリ情報を確認する

```terminal:terminal
(env)$ heroku apps
・・・
(env)$
```

作成したアプリを削除したい場合(--confirmは確認無しで削除するためのコマンド）

```terminal:terminal
$ heroku apps:destroy --app (app名) --confirm (app名）
(env)$
```

Herokuにプッシュする

```terminal:terminal
(env)$ git add .
(env)$ git commit -m "Herokuの設定"
(env)$ git push heroku master
```

Herokuでマイグレーションする

```terminal:terminal
(env)$ heroku run python manage.py migrate
(env)$
```

Herokuの管理サイトユーザーを作成する

```terminal:terminal
(env)$ heroku run python manage.py createsuperuser
・
・
・
Username (leave blank to use ''): 
Email address: 
Password: 
Password (again): 
Superuser created successfully.
(env)$ 
```

Herokuを開いて確認する

```terminal:terminal
(env)$ heroku open
(env)$ 
```

https://(app名).herokuapp.com/admin/login/?next=/admin/

成功していればウェブサーバー上に以下のサイトが表示される。作ったユーザーとパスワードでログインもできる。
![スクリーンショット 2017-12-12 13.16.48.png](https://qiita-image-store.s3.amazonaws.com/0/126098/45747aae-d8a5-1f37-0362-7ec8efa8e9e8.png)

# 参考
herokuでのpythonプロジェクトのベストプラクティスはこちらのリポジトリが参考になる
https://github.com/heroku/python-getting-started